You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@cayenne.apache.org by nt...@apache.org on 2019/01/09 10:46:52 UTC
[01/11] cayenne git commit: CAY-2465 New SelectTranslator
implementation
Repository: cayenne
Updated Branches:
refs/heads/master eb0373e81 -> 60c556675
http://git-wip-us.apache.org/repos/asf/cayenne/blob/f6b2dac9/cayenne-server/src/test/java/org/apache/cayenne/access/translator/select/OrderingTranslatorIT.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/test/java/org/apache/cayenne/access/translator/select/OrderingTranslatorIT.java b/cayenne-server/src/test/java/org/apache/cayenne/access/translator/select/OrderingTranslatorIT.java
deleted file mode 100644
index 0aff229..0000000
--- a/cayenne-server/src/test/java/org/apache/cayenne/access/translator/select/OrderingTranslatorIT.java
+++ /dev/null
@@ -1,136 +0,0 @@
-/*****************************************************************
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- ****************************************************************/
-
-package org.apache.cayenne.access.translator.select;
-
-import static org.junit.Assert.assertEquals;
-
-import java.util.Arrays;
-
-import org.apache.cayenne.CayenneRuntimeException;
-import org.apache.cayenne.access.DataNode;
-import org.apache.cayenne.di.Inject;
-import org.apache.cayenne.exp.ExpressionFactory;
-import org.apache.cayenne.exp.FunctionExpressionFactory;
-import org.apache.cayenne.query.Ordering;
-import org.apache.cayenne.query.SelectQuery;
-import org.apache.cayenne.query.SortOrder;
-import org.apache.cayenne.testdo.testmap.Artist;
-import org.apache.cayenne.unit.di.server.CayenneProjects;
-import org.apache.cayenne.unit.di.server.ServerCase;
-import org.apache.cayenne.unit.di.server.ServerCaseDataSourceFactory;
-import org.apache.cayenne.unit.di.server.UseServerRuntime;
-import org.junit.Test;
-
-@UseServerRuntime(CayenneProjects.TESTMAP_PROJECT)
-public class OrderingTranslatorIT extends ServerCase {
-
- @Inject
- private DataNode node;
-
- @Inject
- private ServerCaseDataSourceFactory dataSourceFactory;
-
- /**
- * Tests ascending ordering on string attribute.
- */
- @Test
- public void testAppendPart1() throws Exception {
- Ordering o1 = new Ordering("artistName", SortOrder.ASCENDING);
- doTestAppendPart("ta.ARTIST_NAME", o1);
- }
-
- /**
- * Tests descending ordering on string attribute.
- */
- @Test
- public void testAppendPart2() throws Exception {
- Ordering o1 = new Ordering("artistName", SortOrder.DESCENDING);
- doTestAppendPart("ta.ARTIST_NAME DESC", o1);
- }
-
- @Test
- public void testAppendPart3() throws Exception {
-
- Ordering o1 = new Ordering("artistName", SortOrder.DESCENDING);
- Ordering o2 = new Ordering("paintingArray.estimatedPrice", SortOrder.ASCENDING);
-
- doTestAppendPart("ta.ARTIST_NAME DESC, ta.ESTIMATED_PRICE", o1, o2);
- }
-
- /**
- * Tests ascending case-insensitive ordering on string attribute.
- */
- @Test
- public void testAppendPart4() throws Exception {
- Ordering o1 = new Ordering("artistName", SortOrder.ASCENDING_INSENSITIVE);
- doTestAppendPart("UPPER(ta.ARTIST_NAME)", o1);
- }
-
- @Test
- public void testAppendPart5() throws Exception {
-
- Ordering o1 = new Ordering("artistName", SortOrder.DESCENDING_INSENSITIVE);
- Ordering o2 = new Ordering("paintingArray.estimatedPrice", SortOrder.ASCENDING);
-
- doTestAppendPart("UPPER(ta.ARTIST_NAME) DESC, ta.ESTIMATED_PRICE", o1, o2);
- }
-
- @Test
- public void testAppendPart6() throws Exception {
- Ordering o1 = new Ordering("artistName", SortOrder.ASCENDING_INSENSITIVE);
- Ordering o2 = new Ordering("paintingArray.estimatedPrice", SortOrder.ASCENDING_INSENSITIVE);
-
- doTestAppendPart("UPPER(ta.ARTIST_NAME), UPPER(ta.ESTIMATED_PRICE)", o1, o2);
- }
-
- @Test
- public void testAppendFunctionExpression1() throws Exception {
- Ordering o1 = new Ordering(FunctionExpressionFactory.absExp("paintingArray.estimatedPrice"));
-
- doTestAppendPart("ABS(ta.ESTIMATED_PRICE)", o1);
- }
-
- @Test
- public void testAppendFunctionExpression2() throws Exception {
- Ordering o1 = new Ordering(FunctionExpressionFactory.countExp(ExpressionFactory.pathExp("dateOfBirth")), SortOrder.ASCENDING_INSENSITIVE);
- Ordering o2 = new Ordering(FunctionExpressionFactory.sqrtExp("paintingArray.estimatedPrice"), SortOrder.DESCENDING);
-
- doTestAppendPart("UPPER(COUNT(ta.DATE_OF_BIRTH)), SQRT(ta.ESTIMATED_PRICE) DESC", o1, o2);
- }
-
- @Test(expected = CayenneRuntimeException.class)
- public void testAppendIllegalExpression() throws Exception {
- Ordering o1 = new Ordering(ExpressionFactory.and(ExpressionFactory.expTrue(), ExpressionFactory.expFalse()));
- // should throw exception
- doTestAppendPart("TRUE AND FALSE", o1);
- }
-
- private void doTestAppendPart(String expectedSQL, Ordering... orderings) {
-
- SelectQuery<Artist> q = SelectQuery.query(Artist.class);
- q.addOrderings(Arrays.asList(orderings));
-
- TstQueryAssembler assembler = new TstQueryAssembler(q, node.getAdapter(), node.getEntityResolver());
- StringBuilder out = new StringBuilder();
- String translated = new OrderingTranslator(assembler).appendPart(out).toString();
-
- assertEquals(expectedSQL, translated);
- }
-}
http://git-wip-us.apache.org/repos/asf/cayenne/blob/f6b2dac9/cayenne-server/src/test/java/org/apache/cayenne/access/translator/select/PathComponentsTest.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/test/java/org/apache/cayenne/access/translator/select/PathComponentsTest.java b/cayenne-server/src/test/java/org/apache/cayenne/access/translator/select/PathComponentsTest.java
new file mode 100644
index 0000000..89e9a57
--- /dev/null
+++ b/cayenne-server/src/test/java/org/apache/cayenne/access/translator/select/PathComponentsTest.java
@@ -0,0 +1,77 @@
+/*****************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ ****************************************************************/
+
+package org.apache.cayenne.access.translator.select;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import static org.junit.Assert.*;
+
+/**
+ * @since 4.1
+ */
+public class PathComponentsTest {
+
+ private PathComponents components1;
+ private PathComponents components2;
+ private PathComponents components3;
+
+ @Before
+ public void setUp() {
+ components1 = new PathComponents("a");
+ components2 = new PathComponents("a+.bcd");
+ components3 = new PathComponents("a.bc.defg");
+ }
+
+ @Test
+ public void size() {
+ assertEquals(1, components1.size());
+ assertEquals(2, components2.size());
+ assertEquals(3, components3.size());
+ }
+
+ @Test
+ public void getLast() {
+ assertEquals("a", components1.getLast());
+ assertEquals("bcd", components2.getLast());
+ assertEquals("defg", components3.getLast());
+ }
+
+ @Test
+ public void getParent() {
+ assertEquals("", components1.getParent());
+ assertEquals("a+", components2.getParent());
+ assertEquals("a.bc", components3.getParent());
+ }
+
+ @Test
+ public void getAll() {
+ assertArrayEquals(new String[]{"a"}, components1.getAll());
+ assertArrayEquals(new String[]{"a+", "bcd"}, components2.getAll());
+ assertArrayEquals(new String[]{"a", "bc", "defg"}, components3.getAll());
+ }
+
+ @Test
+ public void getPath() {
+ assertEquals("a", components1.getPath());
+ assertEquals("a+.bcd", components2.getPath());
+ assertEquals("a.bc.defg", components3.getPath());
+ }
+}
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/cayenne/blob/f6b2dac9/cayenne-server/src/test/java/org/apache/cayenne/access/translator/select/QualifierTranslationStageTest.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/test/java/org/apache/cayenne/access/translator/select/QualifierTranslationStageTest.java b/cayenne-server/src/test/java/org/apache/cayenne/access/translator/select/QualifierTranslationStageTest.java
new file mode 100644
index 0000000..9b56c81
--- /dev/null
+++ b/cayenne-server/src/test/java/org/apache/cayenne/access/translator/select/QualifierTranslationStageTest.java
@@ -0,0 +1,89 @@
+/*****************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ ****************************************************************/
+
+package org.apache.cayenne.access.translator.select;
+
+import org.apache.cayenne.access.sqlbuilder.sqltree.ColumnNode;
+import org.apache.cayenne.access.sqlbuilder.sqltree.Node;
+import org.apache.cayenne.access.sqlbuilder.sqltree.OpExpressionNode;
+import org.apache.cayenne.access.sqlbuilder.sqltree.ValueNode;
+import org.apache.cayenne.access.sqlbuilder.sqltree.WhereNode;
+import org.apache.cayenne.exp.ExpressionFactory;
+import org.apache.cayenne.map.DbAttribute;
+import org.apache.cayenne.map.DbEntity;
+import org.junit.Before;
+import org.junit.Test;
+
+import static org.hamcrest.CoreMatchers.instanceOf;
+import static org.junit.Assert.*;
+
+/**
+ * @since 4.2
+ */
+public class QualifierTranslationStageTest {
+
+ private TranslatorContext context;
+
+ @Before
+ public void prepareContext() {
+ DbEntity dbEntity = new DbEntity();
+ dbEntity.setName("mock");
+ DbAttribute dbAttribute = new DbAttribute();
+ dbAttribute.setName("path");
+ dbEntity.addAttribute(dbAttribute);
+
+ TranslatableQueryWrapper wrapper = new MockQueryWrapperBuilder()
+ .withQualifier(ExpressionFactory.greaterOrEqualDbExp("path", 10))
+ .withMetaData(new MockQueryMetadataBuilder()
+ .withDbEntity(dbEntity)
+ .build())
+ .build();
+ context = new MockTranslatorContext(wrapper);
+ }
+
+ @Test
+ public void perform() {
+ QualifierTranslationStage stage = new QualifierTranslationStage();
+ stage.perform(context);
+
+ Node select = context.getSelectBuilder().build();
+
+ // Content of "select" node:
+ //
+ // Where
+ // |
+ // OpExpression
+ // / \
+ // Column Value
+
+ assertEquals(1, select.getChildrenCount());
+ assertThat(select.getChild(0), instanceOf(WhereNode.class));
+ Node op = select.getChild(0).getChild(0);
+ assertThat(op, instanceOf(OpExpressionNode.class));
+ assertEquals(">=", ((OpExpressionNode)op).getOp());
+ assertEquals(2, op.getChildrenCount());
+ assertThat(op.getChild(0), instanceOf(ColumnNode.class));
+ assertThat(op.getChild(1), instanceOf(ValueNode.class));
+
+ ColumnNode columnNode = (ColumnNode)op.getChild(0);
+ ValueNode valueNode = (ValueNode)op.getChild(1);
+ assertEquals("path", columnNode.getColumn());
+ assertEquals(10, valueNode.getValue());
+ }
+}
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/cayenne/blob/f6b2dac9/cayenne-server/src/test/java/org/apache/cayenne/access/translator/select/QualifierTranslatorIT.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/test/java/org/apache/cayenne/access/translator/select/QualifierTranslatorIT.java b/cayenne-server/src/test/java/org/apache/cayenne/access/translator/select/QualifierTranslatorIT.java
deleted file mode 100644
index 2f2c71d..0000000
--- a/cayenne-server/src/test/java/org/apache/cayenne/access/translator/select/QualifierTranslatorIT.java
+++ /dev/null
@@ -1,164 +0,0 @@
-/*****************************************************************
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- ****************************************************************/
-
-package org.apache.cayenne.access.translator.select;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.fail;
-
-import java.util.Arrays;
-
-import org.apache.cayenne.ObjectId;
-import org.apache.cayenne.access.DataNode;
-import org.apache.cayenne.di.Inject;
-import org.apache.cayenne.exp.Expression;
-import org.apache.cayenne.exp.ExpressionFactory;
-import org.apache.cayenne.query.MockQuery;
-import org.apache.cayenne.query.SelectQuery;
-import org.apache.cayenne.testdo.testmap.Artist;
-import org.apache.cayenne.testdo.testmap.Exhibit;
-import org.apache.cayenne.testdo.testmap.Gallery;
-import org.apache.cayenne.testdo.testmap.Painting;
-import org.apache.cayenne.unit.di.server.CayenneProjects;
-import org.apache.cayenne.unit.di.server.ServerCase;
-import org.apache.cayenne.unit.di.server.ServerCaseDataSourceFactory;
-import org.apache.cayenne.unit.di.server.UseServerRuntime;
-import org.junit.Test;
-
-@UseServerRuntime(CayenneProjects.TESTMAP_PROJECT)
-public class QualifierTranslatorIT extends ServerCase {
-
- @Inject
- private DataNode node;
-
- @Inject
- private ServerCaseDataSourceFactory dataSourceFactory;
-
- // TODO: not an integration test; extract into *Test
- @Test
- public void testNonQualifiedQuery() {
- TstQueryAssembler qa = new TstQueryAssembler(new MockQuery(), node.getAdapter(), node.getEntityResolver());
-
- try {
- new QualifierTranslator(qa).appendPart(new StringBuilder());
- fail();
- } catch (ClassCastException ccex) {
- // exception expected
- }
- }
-
- // TODO: not an integration test; extract into *Test
- @Test
- public void testNullQualifier() {
- TstQueryAssembler qa = new TstQueryAssembler(new SelectQuery<>(), node.getAdapter(),
- node.getEntityResolver());
-
- StringBuilder out = new StringBuilder();
- new QualifierTranslator(qa).appendPart(out);
- assertEquals(0, out.length());
- }
-
- @Test
- public void testBinary_In1() {
- doExpressionTest(Exhibit.class, "toGallery.galleryName in ('g1', 'g2', 'g3')", "ta.GALLERY_NAME IN (?, ?, ?)");
- }
-
- @Test
- public void testBinary_In2() {
- Expression exp = ExpressionFactory.inExp("toGallery.galleryName",
- Arrays.asList("g1", "g2", "g3"));
- doExpressionTest(Exhibit.class, exp, "ta.GALLERY_NAME IN (?, ?, ?)");
- }
-
- @Test
- public void testBinary_In3() {
- Expression exp = ExpressionFactory.inExp("toGallery.galleryName", "g1", "g2", "g3");
- doExpressionTest(Exhibit.class, exp, "ta.GALLERY_NAME IN (?, ?, ?)");
- }
-
- @Test
- public void testBinary_Like() {
- doExpressionTest(Exhibit.class, "toGallery.galleryName like 'a%'", "ta.GALLERY_NAME LIKE ?");
- }
-
- @Test
- public void testBinary_LikeIgnoreCase() {
- doExpressionTest(Exhibit.class, "toGallery.galleryName likeIgnoreCase 'a%'",
- "UPPER(ta.GALLERY_NAME) LIKE UPPER(?)");
- }
-
- @Test
- public void testBinary_IsNull() {
- doExpressionTest(Exhibit.class, "toGallery.galleryName = null", "ta.GALLERY_NAME IS NULL");
- }
-
- @Test
- public void testBinary_IsNotNull() {
- doExpressionTest(Exhibit.class, "toGallery.galleryName != null", "ta.GALLERY_NAME IS NOT NULL");
- }
-
- @Test
- public void testTernary_Between() {
- doExpressionTest(Painting.class, "estimatedPrice between 3000 and 15000", "ta.ESTIMATED_PRICE BETWEEN ? AND ?");
- }
-
- @Test
- public void testExtras() {
- ObjectId oid1 = new ObjectId("Gallery", "GALLERY_ID", 1);
- ObjectId oid2 = new ObjectId("Gallery", "GALLERY_ID", 2);
- Gallery g1 = new Gallery();
- Gallery g2 = new Gallery();
- g1.setObjectId(oid1);
- g2.setObjectId(oid2);
-
- Expression e1 = ExpressionFactory.matchExp("toGallery", g1);
- Expression e2 = e1.orExp(ExpressionFactory.matchExp("toGallery", g2));
-
- doExpressionTest(Exhibit.class, e2, "(ta.GALLERY_ID = ?) OR (ta.GALLERY_ID = ?)");
- }
-
- @Test
- public void testTrim() {
- doExpressionTest(Artist.class, Artist.ARTIST_NAME.trim().like("P%"),
- "TRIM(ta.ARTIST_NAME) LIKE ?");
- }
-
- @Test
- public void testConcat() {
- doExpressionTest(Artist.class, Artist.ARTIST_NAME.concat(Artist.DATE_OF_BIRTH).like("P%"),
- "CONCAT(ta.ARTIST_NAME, ta.DATE_OF_BIRTH) LIKE ?");
- }
-
- private void doExpressionTest(Class<?> queryType, String qualifier, String expectedSQL) {
- doExpressionTest(queryType, ExpressionFactory.exp(qualifier), expectedSQL);
- }
-
- private void doExpressionTest(Class<?> queryType, Expression qualifier, String expectedSQL) {
-
- SelectQuery<?> q = new SelectQuery<>(queryType);
- q.setQualifier(qualifier);
-
- TstQueryAssembler qa = new TstQueryAssembler(q, node.getAdapter(), node.getEntityResolver());
-
- StringBuilder out = new StringBuilder();
- String translated = new QualifierTranslator(qa).appendPart(out).toString();
- assertEquals(expectedSQL, translated);
-
- }
-}
http://git-wip-us.apache.org/repos/asf/cayenne/blob/f6b2dac9/cayenne-server/src/test/java/org/apache/cayenne/access/translator/select/QualifierTranslatorTest.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/test/java/org/apache/cayenne/access/translator/select/QualifierTranslatorTest.java b/cayenne-server/src/test/java/org/apache/cayenne/access/translator/select/QualifierTranslatorTest.java
new file mode 100644
index 0000000..1f78ae0
--- /dev/null
+++ b/cayenne-server/src/test/java/org/apache/cayenne/access/translator/select/QualifierTranslatorTest.java
@@ -0,0 +1,524 @@
+/*****************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ ****************************************************************/
+
+package org.apache.cayenne.access.translator.select;
+
+import java.util.Arrays;
+
+import org.apache.cayenne.CayenneRuntimeException;
+import org.apache.cayenne.access.sqlbuilder.sqltree.BetweenNode;
+import org.apache.cayenne.access.sqlbuilder.sqltree.BitwiseNotNode;
+import org.apache.cayenne.access.sqlbuilder.sqltree.ColumnNode;
+import org.apache.cayenne.access.sqlbuilder.sqltree.EqualNode;
+import org.apache.cayenne.access.sqlbuilder.sqltree.FunctionNode;
+import org.apache.cayenne.access.sqlbuilder.sqltree.InNode;
+import org.apache.cayenne.access.sqlbuilder.sqltree.LikeNode;
+import org.apache.cayenne.access.sqlbuilder.sqltree.Node;
+import org.apache.cayenne.access.sqlbuilder.sqltree.NotEqualNode;
+import org.apache.cayenne.access.sqlbuilder.sqltree.NotNode;
+import org.apache.cayenne.access.sqlbuilder.sqltree.OpExpressionNode;
+import org.apache.cayenne.access.sqlbuilder.sqltree.SelectNode;
+import org.apache.cayenne.access.sqlbuilder.sqltree.TextNode;
+import org.apache.cayenne.access.sqlbuilder.sqltree.ValueNode;
+import org.apache.cayenne.exp.Expression;
+import org.apache.cayenne.exp.ExpressionFactory;
+import org.apache.cayenne.exp.Property;
+import org.apache.cayenne.exp.parser.ASTAsterisk;
+import org.apache.cayenne.map.DataMap;
+import org.apache.cayenne.map.DbAttribute;
+import org.apache.cayenne.map.DbEntity;
+import org.apache.cayenne.map.EntityResolver;
+import org.apache.cayenne.map.ObjAttribute;
+import org.apache.cayenne.map.ObjEntity;
+import org.apache.cayenne.query.ObjectSelect;
+import org.junit.Before;
+import org.junit.Test;
+
+import static org.hamcrest.CoreMatchers.instanceOf;
+import static org.junit.Assert.*;
+
+/**
+ * @since 4.2
+ */
+public class QualifierTranslatorTest {
+
+ private QualifierTranslator translator;
+
+ @Before
+ public void prepareTranslator() {
+ DbEntity dbEntity = new DbEntity();
+ dbEntity.setName("mock");
+
+ DbAttribute dbAttributeA = new DbAttribute();
+ dbAttributeA.setName("a");
+ dbEntity.addAttribute(dbAttributeA);
+
+ DbAttribute dbAttributeB = new DbAttribute();
+ dbAttributeB.setName("b");
+ dbAttributeB.setPrimaryKey(true);
+ dbEntity.addAttribute(dbAttributeB);
+
+ ObjEntity entity = new ObjEntity();
+ entity.setName("mock");
+ ObjAttribute attribute = new ObjAttribute();
+ attribute.setName("a");
+ attribute.setDbAttributePath("a");
+ entity.addAttribute(attribute);
+ entity.setDbEntity(dbEntity);
+
+ DataMap dataMap = new DataMap();
+ dataMap.addDbEntity(dbEntity);
+ dataMap.addObjEntity(entity);
+
+ EntityResolver resolver = new EntityResolver();
+ resolver.addDataMap(dataMap);
+
+ TranslatableQueryWrapper wrapper = new MockQueryWrapperBuilder()
+ .withMetaData(new MockQueryMetadataBuilder()
+ .withDbEntity(dbEntity)
+ .withObjEntity(entity)
+ .build())
+ .build();
+ TranslatorContext context = new MockTranslatorContext(wrapper, resolver);
+ translator = new QualifierTranslator(context);
+ }
+
+ @Test
+ public void translateNull() {
+ assertNull(translator.translate((Expression)null));
+ assertNull(translator.translate((Property<?>)null));
+ }
+
+ @Test
+ public void translateIn() {
+ {
+ Node in = translate("db:a in (1,2)");
+ assertThat(in, instanceOf(InNode.class));
+ assertEquals(2, in.getChildrenCount());
+ assertFalse(((InNode) in).isNot());
+ assertThat(in.getChild(0), instanceOf(ColumnNode.class));
+ assertThat(in.getChild(1), instanceOf(ValueNode.class));
+ assertEquals("a", ((ColumnNode) in.getChild(0)).getColumn());
+ assertArrayEquals(new Object[]{1, 2}, (Object[]) ((ValueNode) in.getChild(1)).getValue());
+ }
+
+ {
+ Node in = translator.translate(ExpressionFactory.inExp("a", Arrays.asList(1, 2, 3)));
+ assertThat(in, instanceOf(InNode.class));
+ assertEquals(2, in.getChildrenCount());
+ assertFalse(((InNode) in).isNot());
+ assertThat(in.getChild(0), instanceOf(ColumnNode.class));
+ assertThat(in.getChild(1), instanceOf(ValueNode.class));
+ assertEquals("a", ((ColumnNode) in.getChild(0)).getColumn());
+ assertArrayEquals(new Object[]{1, 2, 3}, (Object[]) ((ValueNode) in.getChild(1)).getValue());
+ }
+
+ {
+ Node notIn = translate("db:a not in (1,2)");
+ assertThat(notIn, instanceOf(InNode.class));
+ assertEquals(2, notIn.getChildrenCount());
+ assertTrue(((InNode) notIn).isNot());
+ assertThat(notIn.getChild(0), instanceOf(ColumnNode.class));
+ assertThat(notIn.getChild(1), instanceOf(ValueNode.class));
+ assertEquals("a", ((ColumnNode) notIn.getChild(0)).getColumn());
+ assertArrayEquals(new Object[]{1, 2}, (Object[]) ((ValueNode) notIn.getChild(1)).getValue());
+ }
+
+ }
+
+ @Test
+ public void translateBetween() {
+ {
+ Node between = translate("db:a between 1 and 5");
+ assertThat(between, instanceOf(BetweenNode.class));
+ assertEquals(3, between.getChildrenCount());
+ assertFalse(((BetweenNode) between).isNot());
+ assertThat(between.getChild(0), instanceOf(ColumnNode.class));
+ assertThat(between.getChild(1), instanceOf(ValueNode.class));
+ assertThat(between.getChild(2), instanceOf(ValueNode.class));
+ }
+
+ {
+ Node notBetween = translate("db:b not between 2 and 6");
+ assertThat(notBetween, instanceOf(BetweenNode.class));
+ assertEquals(3, notBetween.getChildrenCount());
+ assertTrue(((BetweenNode) notBetween).isNot());
+ assertThat(notBetween.getChild(0), instanceOf(ColumnNode.class));
+ assertThat(notBetween.getChild(1), instanceOf(ValueNode.class));
+ assertThat(notBetween.getChild(2), instanceOf(ValueNode.class));
+ }
+ }
+
+ @Test
+ public void translateNot() {
+ Node not = translate("not true");
+ assertThat(not, instanceOf(NotNode.class));
+ assertEquals(1, not.getChildrenCount());
+ assertThat(not.getChild(0), instanceOf(TextNode.class));
+ assertEquals(" 1=1", ((TextNode)not.getChild(0)).getText());
+ }
+
+ @Test
+ public void translateBitwiseNot() {
+ Node not = translate("~123");
+ assertThat(not, instanceOf(BitwiseNotNode.class));
+ assertEquals(1, not.getChildrenCount());
+ assertThat(not.getChild(0), instanceOf(ValueNode.class));
+ assertEquals(123, ((ValueNode)not.getChild(0)).getValue());
+ }
+
+ @Test
+ public void translateEqual() {
+ {
+ Node eq = translate("db:a = 123");
+ assertThat(eq, instanceOf(EqualNode.class));
+ assertEquals(2, eq.getChildrenCount());
+ assertThat(eq.getChild(0), instanceOf(ColumnNode.class));
+ assertThat(eq.getChild(1), instanceOf(ValueNode.class));
+ assertEquals("a", ((ColumnNode) eq.getChild(0)).getColumn());
+ assertEquals(123, ((ValueNode) eq.getChild(1)).getValue());
+ }
+
+ {
+ Node neq = translate("db:b != 321");
+ assertThat(neq, instanceOf(NotEqualNode.class));
+ assertEquals(2, neq.getChildrenCount());
+ assertThat(neq.getChild(0), instanceOf(ColumnNode.class));
+ assertThat(neq.getChild(1), instanceOf(ValueNode.class));
+ assertEquals("b", ((ColumnNode) neq.getChild(0)).getColumn());
+ assertEquals(321, ((ValueNode) neq.getChild(1)).getValue());
+ }
+ }
+
+ @Test
+ public void translateLike() {
+ {
+ // no support for escape char in exp parser
+ Node like = translator.translate(ExpressionFactory.likeExp("a", "abc", '~'));
+ assertThat(like, instanceOf(LikeNode.class));
+ assertEquals('~', ((LikeNode) like).getEscape());
+ assertFalse(((LikeNode) like).isIgnoreCase());
+ assertFalse(((LikeNode) like).isNot());
+ assertEquals(2, like.getChildrenCount());
+ assertThat(like.getChild(0), instanceOf(ColumnNode.class));
+ assertThat(like.getChild(1), instanceOf(ValueNode.class));
+ assertEquals("a", ((ColumnNode) like.getChild(0)).getColumn());
+ assertEquals("abc", ((ValueNode) like.getChild(1)).getValue());
+ }
+
+ {
+ Node notLike = translate("db:a not like 'abc'");
+ assertThat(notLike, instanceOf(LikeNode.class));
+ assertEquals(0, ((LikeNode) notLike).getEscape());
+ assertFalse(((LikeNode) notLike).isIgnoreCase());
+ assertTrue(((LikeNode) notLike).isNot());
+ assertEquals(2, notLike.getChildrenCount());
+ assertThat(notLike.getChild(0), instanceOf(ColumnNode.class));
+ assertThat(notLike.getChild(1), instanceOf(ValueNode.class));
+ assertEquals("a", ((ColumnNode) notLike.getChild(0)).getColumn());
+ assertEquals("abc", ((ValueNode) notLike.getChild(1)).getValue());
+ }
+
+ {
+ Node likeIgnoreCase = translate("db:a likeIgnoreCase 'abc'");
+ assertThat(likeIgnoreCase, instanceOf(LikeNode.class));
+ assertEquals(0, ((LikeNode) likeIgnoreCase).getEscape());
+ assertTrue(((LikeNode) likeIgnoreCase).isIgnoreCase());
+ assertFalse(((LikeNode) likeIgnoreCase).isNot());
+ assertEquals(2, likeIgnoreCase.getChildrenCount());
+ assertThat(likeIgnoreCase.getChild(0), instanceOf(ColumnNode.class));
+ assertThat(likeIgnoreCase.getChild(1), instanceOf(ValueNode.class));
+ assertEquals("a", ((ColumnNode) likeIgnoreCase.getChild(0)).getColumn());
+ assertEquals("abc", ((ValueNode) likeIgnoreCase.getChild(1)).getValue());
+ }
+
+ {
+ Node notLikeIgnoreCase = translate("db:a not likeIgnoreCase 'abc'");
+ assertThat(notLikeIgnoreCase, instanceOf(LikeNode.class));
+ assertEquals(0, ((LikeNode) notLikeIgnoreCase).getEscape());
+ assertTrue(((LikeNode) notLikeIgnoreCase).isIgnoreCase());
+ assertTrue(((LikeNode) notLikeIgnoreCase).isNot());
+ assertEquals(2, notLikeIgnoreCase.getChildrenCount());
+ assertThat(notLikeIgnoreCase.getChild(0), instanceOf(ColumnNode.class));
+ assertThat(notLikeIgnoreCase.getChild(1), instanceOf(ValueNode.class));
+ assertEquals("a", ((ColumnNode) notLikeIgnoreCase.getChild(0)).getColumn());
+ assertEquals("abc", ((ValueNode) notLikeIgnoreCase.getChild(1)).getValue());
+ }
+ }
+
+ @Test
+ public void translateFunctionCall() {
+ {
+ Node function = translate("trim(a)");
+ assertThat(function, instanceOf(FunctionNode.class));
+ assertEquals("TRIM", ((FunctionNode) function).getFunctionName());
+ assertNull(((FunctionNode) function).getAlias());
+ assertEquals(1, function.getChildrenCount());
+ assertThat(function.getChild(0), instanceOf(ColumnNode.class));
+ assertEquals("a", ((ColumnNode) function.getChild(0)).getColumn());
+ }
+
+ {
+ Node function = translate("year(a)");
+ assertThat(function, instanceOf(FunctionNode.class));
+ assertEquals("YEAR", ((FunctionNode) function).getFunctionName());
+ assertNull(((FunctionNode) function).getAlias());
+ assertEquals(1, function.getChildrenCount());
+ assertThat(function.getChild(0), instanceOf(ColumnNode.class));
+ assertEquals("a", ((ColumnNode) function.getChild(0)).getColumn());
+ }
+ }
+
+ @Test
+ public void translateMathExp() {
+ {
+ Node op = translate("1 + 2");
+ assertThat(op, instanceOf(OpExpressionNode.class));
+ assertEquals("+", ((OpExpressionNode)op).getOp());
+ assertEquals(2, op.getChildrenCount());
+ }
+
+ {
+ Node op = translate("1 - 2");
+ assertThat(op, instanceOf(OpExpressionNode.class));
+ assertEquals("-", ((OpExpressionNode)op).getOp());
+ assertEquals(2, op.getChildrenCount());
+ }
+
+ {
+ Node op = translate("1 / 2");
+ assertThat(op, instanceOf(OpExpressionNode.class));
+ assertEquals("/", ((OpExpressionNode)op).getOp());
+ assertEquals(2, op.getChildrenCount());
+ }
+
+ {
+ Node op = translate("1 * 2");
+ assertThat(op, instanceOf(OpExpressionNode.class));
+ assertEquals("*", ((OpExpressionNode)op).getOp());
+ assertEquals(2, op.getChildrenCount());
+ }
+
+ {
+ Node op = translate("-2");
+ assertThat(op, instanceOf(OpExpressionNode.class));
+ assertEquals("-", ((OpExpressionNode)op).getOp());
+ assertEquals(1, op.getChildrenCount());
+ }
+
+ {
+ Node op = translate("1 & 2");
+ assertThat(op, instanceOf(OpExpressionNode.class));
+ assertEquals("&", ((OpExpressionNode)op).getOp());
+ assertEquals(2, op.getChildrenCount());
+ }
+
+ {
+ Node op = translate("1 | 2");
+ assertThat(op, instanceOf(OpExpressionNode.class));
+ assertEquals("|", ((OpExpressionNode)op).getOp());
+ assertEquals(2, op.getChildrenCount());
+ }
+
+ {
+ Node op = translate("1 ^ 2");
+ assertThat(op, instanceOf(OpExpressionNode.class));
+ assertEquals("^", ((OpExpressionNode)op).getOp());
+ assertEquals(2, op.getChildrenCount());
+ }
+
+ {
+ Node op = translate("1 << 2");
+ assertThat(op, instanceOf(OpExpressionNode.class));
+ assertEquals("<<", ((OpExpressionNode)op).getOp());
+ assertEquals(2, op.getChildrenCount());
+ }
+
+ {
+ Node op = translate("1 >> 2");
+ assertThat(op, instanceOf(OpExpressionNode.class));
+ assertEquals(">>", ((OpExpressionNode)op).getOp());
+ assertEquals(2, op.getChildrenCount());
+ }
+
+ }
+
+ @Test
+ public void translateComparision() {
+ {
+ Node op = translate("a < 2");
+ assertThat(op, instanceOf(OpExpressionNode.class));
+ assertEquals("<", ((OpExpressionNode)op).getOp());
+ assertEquals(2, op.getChildrenCount());
+ }
+
+ {
+ Node op = translate("a > 2");
+ assertThat(op, instanceOf(OpExpressionNode.class));
+ assertEquals(">", ((OpExpressionNode)op).getOp());
+ assertEquals(2, op.getChildrenCount());
+ }
+
+ {
+ Node op = translate("a <= 2");
+ assertThat(op, instanceOf(OpExpressionNode.class));
+ assertEquals("<=", ((OpExpressionNode)op).getOp());
+ assertEquals(2, op.getChildrenCount());
+ }
+
+ {
+ Node op = translate("a >= 2");
+ assertThat(op, instanceOf(OpExpressionNode.class));
+ assertEquals(">=", ((OpExpressionNode)op).getOp());
+ assertEquals(2, op.getChildrenCount());
+ }
+ }
+
+ @Test
+ public void translateConst() {
+ {
+ Node op = translate("true");
+ assertThat(op, instanceOf(TextNode.class));
+ assertEquals(" 1=1", ((TextNode)op).getText());
+ assertEquals(0, op.getChildrenCount());
+ }
+
+ {
+ Node op = translate("false");
+ assertThat(op, instanceOf(TextNode.class));
+ assertEquals(" 1=0", ((TextNode)op).getText());
+ assertEquals(0, op.getChildrenCount());
+ }
+
+ {
+ Node op = translator.translate(new ASTAsterisk());
+ assertThat(op, instanceOf(TextNode.class));
+ assertEquals(" *", ((TextNode)op).getText());
+ assertEquals(0, op.getChildrenCount());
+ }
+ }
+
+ @Test
+ public void translateExists() {
+ Node exists = translator.translate(ExpressionFactory.exists(ObjectSelect.dbQuery("mock")));
+ assertThat(exists, instanceOf(FunctionNode.class));
+ assertEquals("EXISTS", ((FunctionNode) exists).getFunctionName());
+ assertEquals(1, exists.getChildrenCount());
+ assertThat(exists.getChild(0), instanceOf(SelectNode.class));
+ }
+
+ @Test
+ public void translateFullObject() {
+ Node fullObj = translator.translate(ExpressionFactory.fullObjectExp());
+ assertThat(fullObj, instanceOf(ColumnNode.class));
+ ColumnNode columnNode = (ColumnNode)fullObj;
+ assertEquals("b", columnNode.getColumn());
+ }
+
+ @Test(expected = CayenneRuntimeException.class)
+ public void translateEnclosingObject() {
+ // can't translate enclosing exp not in nested query
+ translator.translate(ExpressionFactory.enclosingObjectExp(ExpressionFactory.dbPathExp("a")));
+ }
+
+ @Test
+ public void translateAnd() {
+ Node and = translate("true and false");
+ assertNotNull(and);
+ assertThat(and, instanceOf(OpExpressionNode.class));
+ assertEquals("AND", ((OpExpressionNode)and).getOp());
+ assertEquals(2, and.getChildrenCount());
+ assertThat(and.getChild(0), instanceOf(TextNode.class));
+ assertEquals(" 1=1", ((TextNode)and.getChild(0)).getText());
+ assertThat(and.getChild(1), instanceOf(TextNode.class));
+ assertEquals(" 1=0", ((TextNode)and.getChild(1)).getText());
+ }
+
+ @Test
+ public void translateOr() {
+ Node or = translate("true or false");
+ assertNotNull(or);
+ assertThat(or, instanceOf(OpExpressionNode.class));
+ assertEquals("OR", ((OpExpressionNode)or).getOp());
+ assertEquals(2, or.getChildrenCount());
+ assertThat(or.getChild(0), instanceOf(TextNode.class));
+ assertThat(or.getChild(1), instanceOf(TextNode.class));
+ }
+
+ @Test
+ public void translateComplexExp() {
+ Node result = translate("(a >= 1 + 2 / 3 << 4) and (db:b != true)");
+
+ {
+ assertNotNull(result);
+ assertThat(result, instanceOf(OpExpressionNode.class));
+ assertEquals("AND", ((OpExpressionNode) result).getOp());
+ assertEquals(2, result.getChildrenCount());
+ assertThat(result.getChild(0), instanceOf(OpExpressionNode.class));
+ assertThat(result.getChild(1), instanceOf(NotEqualNode.class));
+ }
+
+ {
+ OpExpressionNode left = (OpExpressionNode) result.getChild(0);
+ assertEquals(">=", left.getOp());
+ assertEquals(2, left.getChildrenCount());
+ assertThat(left.getChild(0), instanceOf(ColumnNode.class));
+ assertEquals("a", ((ColumnNode)left.getChild(0)).getColumn());
+ assertThat(left.getChild(1), instanceOf(OpExpressionNode.class));
+ {
+ OpExpressionNode shift = (OpExpressionNode)left.getChild(1);
+ assertEquals("<<", shift.getOp());
+ assertThat(shift.getChild(0), instanceOf(OpExpressionNode.class));
+ {
+ OpExpressionNode plus = (OpExpressionNode)shift.getChild(0);
+ assertEquals("+", plus.getOp());
+ assertEquals(2, plus.getChildrenCount());
+ assertThat(plus.getChild(0), instanceOf(ValueNode.class));
+ assertEquals(1, ((ValueNode)plus.getChild(0)).getValue());
+ assertThat(plus.getChild(1), instanceOf(OpExpressionNode.class));
+ {
+ OpExpressionNode div = (OpExpressionNode)plus.getChild(1);
+ assertEquals("/", div.getOp());
+ assertEquals(2, div.getChildrenCount());
+ assertThat(div.getChild(0), instanceOf(ValueNode.class));
+ assertEquals(2, ((ValueNode)div.getChild(0)).getValue());
+ assertThat(div.getChild(0), instanceOf(ValueNode.class));
+ assertEquals(3, ((ValueNode)div.getChild(1)).getValue());
+ }
+ }
+ assertThat(shift.getChild(1), instanceOf(ValueNode.class));
+ assertEquals(4, ((ValueNode)shift.getChild(1)).getValue());
+ }
+ }
+
+ {
+ NotEqualNode right = (NotEqualNode) result.getChild(1);
+ assertEquals(2, right.getChildrenCount());
+ assertThat(right.getChild(0), instanceOf(ColumnNode.class));
+ assertEquals("b", ((ColumnNode)right.getChild(0)).getColumn());
+ assertThat(right.getChild(1), instanceOf(ValueNode.class));
+ assertEquals(Boolean.TRUE, ((ValueNode)right.getChild(1)).getValue());
+ }
+ }
+
+ private Node translate(String s) {
+ return translator.translate(ExpressionFactory.exp(s));
+ }
+
+}
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/cayenne/blob/f6b2dac9/cayenne-server/src/test/java/org/apache/cayenne/access/translator/select/QueryAssemblerIT.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/test/java/org/apache/cayenne/access/translator/select/QueryAssemblerIT.java b/cayenne-server/src/test/java/org/apache/cayenne/access/translator/select/QueryAssemblerIT.java
deleted file mode 100644
index 05c51e6..0000000
--- a/cayenne-server/src/test/java/org/apache/cayenne/access/translator/select/QueryAssemblerIT.java
+++ /dev/null
@@ -1,61 +0,0 @@
-/*****************************************************************
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- ****************************************************************/
-
-package org.apache.cayenne.access.translator.select;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotNull;
-
-import org.apache.cayenne.access.DataNode;
-import org.apache.cayenne.di.Inject;
-import org.apache.cayenne.map.DbAttribute;
-import org.apache.cayenne.query.SelectQuery;
-import org.apache.cayenne.unit.di.server.CayenneProjects;
-import org.apache.cayenne.unit.di.server.ServerCase;
-import org.apache.cayenne.unit.di.server.UseServerRuntime;
-import org.junit.Before;
-import org.junit.Test;
-
-@UseServerRuntime(CayenneProjects.TESTMAP_PROJECT)
-public class QueryAssemblerIT extends ServerCase {
-
- @Inject
- private DataNode node;
-
- private TstQueryAssembler qa;
-
- @Before
- public void setUp() throws Exception {
- this.qa = new TstQueryAssembler(new SelectQuery<Object>(), node.getAdapter(), node.getEntityResolver());
- }
-
- @Test
- public void testGetQuery() throws Exception {
- assertNotNull(qa.getQuery());
- }
-
- @Test
- public void testAddToParamList() throws Exception {
-
- assertEquals(0, qa.getBindings().length);
-
- qa.addToParamList(new DbAttribute(), new Object());
- assertEquals(1, qa.getBindings().length);
- }
-}
http://git-wip-us.apache.org/repos/asf/cayenne/blob/f6b2dac9/cayenne-server/src/test/java/org/apache/cayenne/access/translator/select/TstQueryAssembler.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/test/java/org/apache/cayenne/access/translator/select/TstQueryAssembler.java b/cayenne-server/src/test/java/org/apache/cayenne/access/translator/select/TstQueryAssembler.java
deleted file mode 100644
index 72a3803..0000000
--- a/cayenne-server/src/test/java/org/apache/cayenne/access/translator/select/TstQueryAssembler.java
+++ /dev/null
@@ -1,70 +0,0 @@
-/*****************************************************************
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- ****************************************************************/
-
-package org.apache.cayenne.access.translator.select;
-
-import java.util.ArrayList;
-import java.util.List;
-
-import org.apache.cayenne.dba.DbAdapter;
-import org.apache.cayenne.exp.Expression;
-import org.apache.cayenne.map.DbRelationship;
-import org.apache.cayenne.map.EntityResolver;
-import org.apache.cayenne.map.JoinType;
-import org.apache.cayenne.query.Query;
-
-public class TstQueryAssembler extends QueryAssembler {
-
- protected List<DbRelationship> dbRels;
-
- public TstQueryAssembler(Query q, DbAdapter adapter, EntityResolver resolver) {
- super(q, adapter, resolver);
- dbRels = new ArrayList<DbRelationship>();
- }
-
- @Override
- public void dbRelationshipAdded(DbRelationship relationship, JoinType joinType, String joinSplitAlias) {
- dbRels.add(relationship);
- }
-
- @Override
- public String getCurrentAlias() {
- return "ta";
- }
-
- @Override
- public void resetJoinStack() {
- // noop
- }
-
- @Override
- public boolean supportsTableAliases() {
- return true;
- }
-
- @Override
- public String getAliasForExpression(Expression exp) {
- return null;
- }
-
- @Override
- protected void doTranslate() {
- this.sql = "SELECT * FROM ARTIST";
- }
-}
http://git-wip-us.apache.org/repos/asf/cayenne/blob/f6b2dac9/cayenne-server/src/test/java/org/apache/cayenne/exp/property/PathAliasesIT.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/test/java/org/apache/cayenne/exp/property/PathAliasesIT.java b/cayenne-server/src/test/java/org/apache/cayenne/exp/property/PathAliasesIT.java
index e73402d..8c2bdd5 100644
--- a/cayenne-server/src/test/java/org/apache/cayenne/exp/property/PathAliasesIT.java
+++ b/cayenne-server/src/test/java/org/apache/cayenne/exp/property/PathAliasesIT.java
@@ -167,7 +167,7 @@ public class PathAliasesIT extends ServerCase {
List<Object[]> artistAndPaintingCount = ObjectSelect.columnQuery(Artist.class, Artist.ARTIST_NAME, Artist.PAINTING_ARRAY.count())
.having(Artist.PAINTING_ARRAY.alias("p1").count().lt(5L))
.select(context);
- assertEquals(4, artistAndPaintingCount.size());
+ assertEquals(19, artistAndPaintingCount.size());
assertTrue((Long)artistAndPaintingCount.get(0)[1] < 5);
}
@@ -296,7 +296,7 @@ public class PathAliasesIT extends ServerCase {
List<Object[]> artistAndPaintingCount = ObjectSelect.columnQuery(Artist.class, Artist.ARTIST_NAME, Artist.PAINTING_ARRAY.count())
.having(PropertyFactory.createBase(e1, Number.class).count().lt(5L))
.select(context);
- assertEquals(4, artistAndPaintingCount.size());
+ assertEquals(19, artistAndPaintingCount.size());
assertTrue((Long)artistAndPaintingCount.get(0)[1] < 5);
}
http://git-wip-us.apache.org/repos/asf/cayenne/blob/f6b2dac9/cayenne-server/src/test/java/org/apache/cayenne/query/ColumnSelectIT.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/test/java/org/apache/cayenne/query/ColumnSelectIT.java b/cayenne-server/src/test/java/org/apache/cayenne/query/ColumnSelectIT.java
index a0b2486..cbd2e03 100644
--- a/cayenne-server/src/test/java/org/apache/cayenne/query/ColumnSelectIT.java
+++ b/cayenne-server/src/test/java/org/apache/cayenne/query/ColumnSelectIT.java
@@ -56,6 +56,7 @@ import org.junit.Before;
import org.junit.Ignore;
import org.junit.Test;
+import static org.hamcrest.CoreMatchers.instanceOf;
import static org.junit.Assert.*;
/**
@@ -492,6 +493,7 @@ public class ColumnSelectIT extends ServerCase {
PropertyFactory.createSelf(Artist.class)
).select(context);
assertEquals(21, result.size());
+
for(Object[] next : result) {
long count = (Long)next[0];
Artist artist = (Artist)next[1];
@@ -502,12 +504,12 @@ public class ColumnSelectIT extends ServerCase {
Artist artist3 = (Artist)next[6];
assertTrue(paintingTitle.startsWith("painting"));
- assertTrue(count == 4L || count == 5L);
assertEquals("tate modern", galleryName);
assertEquals(PersistenceState.COMMITTED, artist.getPersistenceState());
assertEquals(PersistenceState.COMMITTED, artist2.getPersistenceState());
assertEquals(PersistenceState.COMMITTED, artist3.getPersistenceState());
assertEquals(artistName, artist.getArtistName());
+ assertTrue(count == 4L || count == 5L);
}
}
@@ -607,7 +609,7 @@ public class ColumnSelectIT extends ServerCase {
.pageSize(10)
.select(context);
assertNotNull(a);
- assertEquals(5, a.size());
+ assertEquals(20, a.size());
int idx = 0;
for(Object[] next : a) {
assertNotNull(next);
@@ -655,7 +657,7 @@ public class ColumnSelectIT extends ServerCase {
.pageSize(10)
.select(context);
assertNotNull(a);
- assertEquals(5, a.size());
+ assertEquals(20, a.size());
int idx = 0;
for(Object[] next : a) {
assertNotNull(next);
@@ -673,14 +675,17 @@ public class ColumnSelectIT extends ServerCase {
.columns(Artist.ARTIST_NAME, artistFull, Artist.PAINTING_ARRAY.flat())
.pageSize(10)
.select(context);
+
assertNotNull(a);
- assertEquals(21, a.size());
+ assertEquals(36, a.size());
int idx = 0;
for(Object[] next : a) {
assertNotNull(next);
assertEquals("" + idx, String.class, next[0].getClass());
assertEquals("" + idx, Artist.class, next[1].getClass());
- assertEquals("" + idx, Painting.class, next[2].getClass());
+ if(next[2] != null) {
+ assertEquals("" + idx, Painting.class, next[2].getClass());
+ }
idx++;
}
}
@@ -728,9 +733,9 @@ public class ColumnSelectIT extends ServerCase {
private void checkPrefetchResults(List<Object[]> result) {
assertEquals(21, result.size());
for(Object[] next : result) {
- assertTrue(next[0] instanceof Artist);
- assertTrue(next[1] instanceof java.util.Date);
- assertTrue(next[2] instanceof String);
+ assertThat(next[0], instanceOf(Artist.class));
+ assertThat(next[1], instanceOf(java.util.Date.class));
+ assertThat(next[2], instanceOf(String.class));
Artist artist = (Artist)next[0];
assertEquals(PersistenceState.COMMITTED, artist.getPersistenceState());
@@ -777,7 +782,7 @@ public class ColumnSelectIT extends ServerCase {
}
private void checkAggregatePrefetchResults(List<Object[]> result) {
- assertEquals(5, result.size());
+ assertEquals(20, result.size());
for(Object[] next : result) {
assertTrue(next[0] instanceof Artist);
assertTrue(next[1] instanceof Long);
@@ -847,7 +852,7 @@ public class ColumnSelectIT extends ServerCase {
List<Object[]> result = ObjectSelect.query(Artist.class)
.columns(artistProperty, Artist.ARTIST_NAME, Artist.PAINTING_ARRAY.count())
.select(context);
- assertEquals(5, result.size());
+ assertEquals(20, result.size());
for(Object[] next : result) {
assertTrue(next[0] instanceof Artist);
@@ -898,15 +903,19 @@ public class ColumnSelectIT extends ServerCase {
List<Object[]> result = ObjectSelect.query(Artist.class)
.columns(artistProperty, Artist.PAINTING_ARRAY.flat(), Artist.PAINTING_ARRAY.dot(Painting.TO_GALLERY))
.select(context);
- assertEquals(21, result.size());
+ assertEquals(36, result.size());
for(Object[] next : result) {
assertTrue(next[0] instanceof Artist);
- assertTrue(next[1] instanceof Painting);
- assertTrue(next[2] instanceof Gallery);
assertEquals(PersistenceState.COMMITTED, ((Artist)next[0]).getPersistenceState());
- assertEquals(PersistenceState.COMMITTED, ((Painting)(next[1])).getPersistenceState());
- assertEquals(PersistenceState.COMMITTED, ((Gallery)(next[2])).getPersistenceState());
+ if(next[1] != null) {
+ assertTrue(next[1] instanceof Painting);
+ assertEquals(PersistenceState.COMMITTED, ((Painting)(next[1])).getPersistenceState());
+ }
+ if(next[2] != null) {
+ assertTrue(next[2] instanceof Gallery);
+ assertEquals(PersistenceState.COMMITTED, ((Gallery) (next[2])).getPersistenceState());
+ }
}
}
@@ -918,24 +927,29 @@ public class ColumnSelectIT extends ServerCase {
assertEquals(21, result.size());
}
- @Test(expected = CayenneRuntimeException.class)
+ @Test
public void testSelfPropertyInOrderBy() {
EntityProperty<Artist> artistProperty = PropertyFactory.createSelf(Artist.class);
- ObjectSelect.query(Artist.class)
+ List<Artist> artists = ObjectSelect.query(Artist.class)
.column(artistProperty)
.orderBy(artistProperty.desc())
.select(context);
+ assertEquals(20, artists.size());
+ assertEquals("artist20", artists.get(0).getArtistName());
+ assertEquals("artist1", artists.get(19).getArtistName());
}
- @Test(expected = CayenneRuntimeException.class)
+ @Test
public void testSelfPropertyInWhere() {
Artist artist = ObjectSelect.query(Artist.class).selectFirst(context);
EntityProperty<Artist> artistProperty = PropertyFactory.createSelf(Artist.class);
- List<Artist> result = ObjectSelect.query(Artist.class)
+ Artist selectedArtist = ObjectSelect.query(Artist.class)
.column(artistProperty)
.where(artistProperty.eq(artist))
- .select(context);
- assertNotNull(result);
+ .orderBy(artistProperty.asc())
+ .selectOne(context);
+ assertNotNull(selectedArtist);
+ assertEquals(artist.getArtistName(), selectedArtist.getArtistName());
}
@Test
http://git-wip-us.apache.org/repos/asf/cayenne/blob/f6b2dac9/cayenne-server/src/test/java/org/apache/cayenne/unit/IngresUnitDbAdapter.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/test/java/org/apache/cayenne/unit/IngresUnitDbAdapter.java b/cayenne-server/src/test/java/org/apache/cayenne/unit/IngresUnitDbAdapter.java
index aa043ec..279b096 100644
--- a/cayenne-server/src/test/java/org/apache/cayenne/unit/IngresUnitDbAdapter.java
+++ b/cayenne-server/src/test/java/org/apache/cayenne/unit/IngresUnitDbAdapter.java
@@ -21,6 +21,9 @@ package org.apache.cayenne.unit;
import org.apache.cayenne.dba.DbAdapter;
+/**
+ * Ingress doesn't support sorting by an aggregate expression (ORDER BY COUNT(some_field))
+ */
public class IngresUnitDbAdapter extends UnitDbAdapter {
public IngresUnitDbAdapter(DbAdapter adapter) {
@@ -51,4 +54,9 @@ public class IngresUnitDbAdapter extends UnitDbAdapter {
public boolean supportsSelectBooleanExpression() {
return false;
}
+
+ @Override
+ public boolean isLowerCaseNames() {
+ return true;
+ }
}
http://git-wip-us.apache.org/repos/asf/cayenne/blob/f6b2dac9/cayenne-server/src/test/java/org/apache/cayenne/util/ArrayUtilTest.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/test/java/org/apache/cayenne/util/ArrayUtilTest.java b/cayenne-server/src/test/java/org/apache/cayenne/util/ArrayUtilTest.java
new file mode 100644
index 0000000..8b159b1
--- /dev/null
+++ b/cayenne-server/src/test/java/org/apache/cayenne/util/ArrayUtilTest.java
@@ -0,0 +1,354 @@
+/*****************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ ****************************************************************/
+
+package org.apache.cayenne.util;
+
+import org.junit.Test;
+
+import static org.junit.Assert.*;
+
+/**
+ * @since 4.1
+ */
+public class ArrayUtilTest {
+
+ @Test
+ public void sliceIntArray() {
+ int[] array = {1, 2, 3, 4, 5, 6, 7};
+
+ int[][] result = ArrayUtil.sliceArray(array, 2);
+
+ assertEquals(4, result.length);
+ assertArrayEquals(new int[]{1, 2}, result[0]);
+ assertArrayEquals(new int[]{3, 4}, result[1]);
+ assertArrayEquals(new int[]{5, 6}, result[2]);
+ assertArrayEquals(new int[]{7}, result[3]);
+
+ int[][] result2 = ArrayUtil.sliceArray(array, 4);
+
+ assertEquals(2, result2.length);
+ assertArrayEquals(new int[]{1, 2, 3, 4}, result2[0]);
+ assertArrayEquals(new int[]{5, 6, 7}, result2[1]);
+
+ int[][] result3 = ArrayUtil.sliceArray(array, 7);
+
+ assertEquals(1, result3.length);
+ assertArrayEquals(array, result3[0]);
+
+ int[][] result4 = ArrayUtil.sliceArray(array, 10);
+
+ assertEquals(1, result4.length);
+ assertArrayEquals(array, result4[0]);
+
+ int[] array2 = {1, 2, 3, 4, 5, 6, 7, 8};
+ int[][] result5 = ArrayUtil.sliceArray(array2, 4);
+
+ assertEquals(2, result5.length);
+ assertArrayEquals(new int[]{1, 2, 3, 4}, result5[0]);
+ assertArrayEquals(new int[]{5, 6, 7, 8}, result5[1]);
+ }
+
+ @Test
+ public void sliceLongArray() {
+ long[] array = {1, 2, 3, 4, 5, 6, 7};
+
+ long[][] result = ArrayUtil.sliceArray(array, 2);
+
+ assertEquals(4, result.length);
+ assertArrayEquals(new long[]{1, 2}, result[0]);
+ assertArrayEquals(new long[]{3, 4}, result[1]);
+ assertArrayEquals(new long[]{5, 6}, result[2]);
+ assertArrayEquals(new long[]{7}, result[3]);
+
+ long[][] result2 = ArrayUtil.sliceArray(array, 4);
+
+ assertEquals(2, result2.length);
+ assertArrayEquals(new long[]{1, 2, 3, 4}, result2[0]);
+ assertArrayEquals(new long[]{5, 6, 7}, result2[1]);
+
+ long[][] result3 = ArrayUtil.sliceArray(array, 7);
+
+ assertEquals(1, result3.length);
+ assertArrayEquals(array, result3[0]);
+
+ long[][] result4 = ArrayUtil.sliceArray(array, 10);
+
+ assertEquals(1, result4.length);
+ assertArrayEquals(array, result4[0]);
+
+ long[] array2 = {1, 2, 3, 4, 5, 6, 7, 8};
+ long[][] result5 = ArrayUtil.sliceArray(array2, 4);
+
+ assertEquals(2, result5.length);
+ assertArrayEquals(new long[]{1, 2, 3, 4}, result5[0]);
+ assertArrayEquals(new long[]{5, 6, 7, 8}, result5[1]);
+ }
+
+ @Test
+ public void sliceFloatArray() {
+ float[] array = {1, 2, 3, 4, 5, 6, 7};
+
+ float[][] result = ArrayUtil.sliceArray(array, 2);
+
+ assertEquals(4, result.length);
+ assertArrayEquals(new float[]{1, 2}, result[0], 0.000001f);
+ assertArrayEquals(new float[]{3, 4}, result[1], 0.000001f);
+ assertArrayEquals(new float[]{5, 6}, result[2], 0.000001f);
+ assertArrayEquals(new float[]{7}, result[3], 0.000001f);
+
+ float[][] result2 = ArrayUtil.sliceArray(array, 4);
+
+ assertEquals(2, result2.length);
+ assertArrayEquals(new float[]{1, 2, 3, 4}, result2[0], 0.000001f);
+ assertArrayEquals(new float[]{5, 6, 7}, result2[1], 0.000001f);
+
+ float[][] result3 = ArrayUtil.sliceArray(array, 7);
+
+ assertEquals(1, result3.length);
+ assertArrayEquals(array, result3[0], 0.000001f);
+
+ float[][] result4 = ArrayUtil.sliceArray(array, 10);
+
+ assertEquals(1, result4.length);
+ assertArrayEquals(array, result4[0], 0.000001f);
+
+ float[] array2 = {1, 2, 3, 4, 5, 6, 7, 8};
+ float[][] result5 = ArrayUtil.sliceArray(array2, 4);
+
+ assertEquals(2, result5.length);
+ assertArrayEquals(new float[]{1, 2, 3, 4}, result5[0], 0.000001f);
+ assertArrayEquals(new float[]{5, 6, 7, 8}, result5[1], 0.000001f);
+ }
+
+ @Test
+ public void sliceDoubleArray() {
+ double[] array = {1, 2, 3, 4, 5, 6, 7};
+
+ double[][] result = ArrayUtil.sliceArray(array, 2);
+
+ assertEquals(4, result.length);
+ assertArrayEquals(new double[]{1, 2}, result[0], 0.000001);
+ assertArrayEquals(new double[]{3, 4}, result[1], 0.000001);
+ assertArrayEquals(new double[]{5, 6}, result[2], 0.000001);
+ assertArrayEquals(new double[]{7}, result[3], 0.000001);
+
+ double[][] result2 = ArrayUtil.sliceArray(array, 4);
+
+ assertEquals(2, result2.length);
+ assertArrayEquals(new double[]{1, 2, 3, 4}, result2[0], 0.000001);
+ assertArrayEquals(new double[]{5, 6, 7}, result2[1], 0.000001);
+
+ double[][] result3 = ArrayUtil.sliceArray(array, 7);
+
+ assertEquals(1, result3.length);
+ assertArrayEquals(array, result3[0], 0.000001);
+
+ double[][] result4 = ArrayUtil.sliceArray(array, 10);
+
+ assertEquals(1, result4.length);
+ assertArrayEquals(array, result4[0], 0.000001);
+
+ double[] array2 = {1, 2, 3, 4, 5, 6, 7, 8};
+ double[][] result5 = ArrayUtil.sliceArray(array2, 4);
+
+ assertEquals(2, result5.length);
+ assertArrayEquals(new double[]{1, 2, 3, 4}, result5[0], 0.000001);
+ assertArrayEquals(new double[]{5, 6, 7, 8}, result5[1], 0.000001);
+ }
+
+ @Test
+ public void sliceCharArray() {
+ char[] array = {1, 2, 3, 4, 5, 6, 7};
+
+ char[][] result = ArrayUtil.sliceArray(array, 2);
+
+ assertEquals(4, result.length);
+ assertArrayEquals(new char[]{1, 2}, result[0]);
+ assertArrayEquals(new char[]{3, 4}, result[1]);
+ assertArrayEquals(new char[]{5, 6}, result[2]);
+ assertArrayEquals(new char[]{7}, result[3]);
+
+ char[][] result2 = ArrayUtil.sliceArray(array, 4);
+
+ assertEquals(2, result2.length);
+ assertArrayEquals(new char[]{1, 2, 3, 4}, result2[0]);
+ assertArrayEquals(new char[]{5, 6, 7}, result2[1]);
+
+ char[][] result3 = ArrayUtil.sliceArray(array, 7);
+
+ assertEquals(1, result3.length);
+ assertArrayEquals(array, result3[0]);
+
+ char[][] result4 = ArrayUtil.sliceArray(array, 10);
+
+ assertEquals(1, result4.length);
+ assertArrayEquals(array, result4[0]);
+
+ char[] array2 = {1, 2, 3, 4, 5, 6, 7, 8};
+ char[][] result5 = ArrayUtil.sliceArray(array2, 4);
+
+ assertEquals(2, result5.length);
+ assertArrayEquals(new char[]{1, 2, 3, 4}, result5[0]);
+ assertArrayEquals(new char[]{5, 6, 7, 8}, result5[1]);
+ }
+
+ @Test
+ public void sliceShortArray() {
+ short[] array = {1, 2, 3, 4, 5, 6, 7};
+
+ short[][] result = ArrayUtil.sliceArray(array, 2);
+
+ assertEquals(4, result.length);
+ assertArrayEquals(new short[]{1, 2}, result[0]);
+ assertArrayEquals(new short[]{3, 4}, result[1]);
+ assertArrayEquals(new short[]{5, 6}, result[2]);
+ assertArrayEquals(new short[]{7}, result[3]);
+
+ short[][] result2 = ArrayUtil.sliceArray(array, 4);
+
+ assertEquals(2, result2.length);
+ assertArrayEquals(new short[]{1, 2, 3, 4}, result2[0]);
+ assertArrayEquals(new short[]{5, 6, 7}, result2[1]);
+
+ short[][] result3 = ArrayUtil.sliceArray(array, 7);
+
+ assertEquals(1, result3.length);
+ assertArrayEquals(array, result3[0]);
+
+ short[][] result4 = ArrayUtil.sliceArray(array, 10);
+
+ assertEquals(1, result4.length);
+ assertArrayEquals(array, result4[0]);
+
+ short[] array2 = {1, 2, 3, 4, 5, 6, 7, 8};
+ short[][] result5 = ArrayUtil.sliceArray(array2, 4);
+
+ assertEquals(2, result5.length);
+ assertArrayEquals(new short[]{1, 2, 3, 4}, result5[0]);
+ assertArrayEquals(new short[]{5, 6, 7, 8}, result5[1]);
+ }
+
+ @Test
+ public void sliceByteArray() {
+ byte[] array = {1, 2, 3, 4, 5, 6, 7};
+
+ byte[][] result = ArrayUtil.sliceArray(array, 2);
+
+ assertEquals(4, result.length);
+ assertArrayEquals(new byte[]{1, 2}, result[0]);
+ assertArrayEquals(new byte[]{3, 4}, result[1]);
+ assertArrayEquals(new byte[]{5, 6}, result[2]);
+ assertArrayEquals(new byte[]{7}, result[3]);
+
+ byte[][] result2 = ArrayUtil.sliceArray(array, 4);
+
+ assertEquals(2, result2.length);
+ assertArrayEquals(new byte[]{1, 2, 3, 4}, result2[0]);
+ assertArrayEquals(new byte[]{5, 6, 7}, result2[1]);
+
+ byte[][] result3 = ArrayUtil.sliceArray(array, 7);
+
+ assertEquals(1, result3.length);
+ assertArrayEquals(array, result3[0]);
+
+ byte[][] result4 = ArrayUtil.sliceArray(array, 10);
+
+ assertEquals(1, result4.length);
+ assertArrayEquals(array, result4[0]);
+
+ byte[] array2 = {1, 2, 3, 4, 5, 6, 7, 8};
+ byte[][] result5 = ArrayUtil.sliceArray(array2, 4);
+
+ assertEquals(2, result5.length);
+ assertArrayEquals(new byte[]{1, 2, 3, 4}, result5[0]);
+ assertArrayEquals(new byte[]{5, 6, 7, 8}, result5[1]);
+ }
+
+ @Test
+ public void sliceBooleanArray() {
+ boolean[] array = {true, true, false, true, false, false, true};
+
+ boolean[][] result = ArrayUtil.sliceArray(array, 2);
+
+ assertEquals(4, result.length);
+ assertArrayEquals(new boolean[]{true, true}, result[0]);
+ assertArrayEquals(new boolean[]{false, true}, result[1]);
+ assertArrayEquals(new boolean[]{false, false}, result[2]);
+ assertArrayEquals(new boolean[]{true}, result[3]);
+
+ boolean[][] result2 = ArrayUtil.sliceArray(array, 4);
+
+ assertEquals(2, result2.length);
+ assertArrayEquals(new boolean[]{true, true, false, true}, result2[0]);
+ assertArrayEquals(new boolean[]{false, false, true}, result2[1]);
+
+ boolean[][] result3 = ArrayUtil.sliceArray(array, 7);
+
+ assertEquals(1, result3.length);
+ assertArrayEquals(array, result3[0]);
+
+ boolean[][] result4 = ArrayUtil.sliceArray(array, 10);
+
+ assertEquals(1, result4.length);
+ assertArrayEquals(array, result4[0]);
+
+ boolean[] array2 = {true, true, false, true, false, false, true, false};
+ boolean[][] result5 = ArrayUtil.sliceArray(array2, 4);
+
+ assertEquals(2, result5.length);
+ assertArrayEquals(new boolean[]{true, true, false, true}, result5[0]);
+ assertArrayEquals(new boolean[]{false, false, true, false}, result5[1]);
+ }
+
+ @Test
+ public void sliceObjectArray() {
+ Object[] array = {1, 2, 3, 4, 5, 6, 7};
+
+ Object[][] result = ArrayUtil.sliceArray(array, 2);
+
+ assertEquals(4, result.length);
+ assertArrayEquals(new Object[]{1, 2}, result[0]);
+ assertArrayEquals(new Object[]{3, 4}, result[1]);
+ assertArrayEquals(new Object[]{5, 6}, result[2]);
+ assertArrayEquals(new Object[]{7}, result[3]);
+
+ Object[][] result2 = ArrayUtil.sliceArray(array, 4);
+
+ assertEquals(2, result2.length);
+ assertArrayEquals(new Object[]{1, 2, 3, 4}, result2[0]);
+ assertArrayEquals(new Object[]{5, 6, 7}, result2[1]);
+
+ Object[][] result3 = ArrayUtil.sliceArray(array, 7);
+
+ assertEquals(1, result3.length);
+ assertArrayEquals(array, result3[0]);
+
+ Object[][] result4 = ArrayUtil.sliceArray(array, 10);
+
+ assertEquals(1, result4.length);
+ assertArrayEquals(array, result4[0]);
+
+ Object[] array2 = {1, 2, 3, 4, 5, 6, 7, 8};
+ Object[][] result5 = ArrayUtil.sliceArray(array2, 4);
+
+ assertEquals(2, result5.length);
+ assertArrayEquals(new Object[]{1, 2, 3, 4}, result5[0]);
+ assertArrayEquals(new Object[]{5, 6, 7, 8}, result5[1]);
+ }
+}
\ No newline at end of file
[10/11] cayenne git commit: CAY-2466 New internal API to build SQL
string - node internals optimization
Posted by nt...@apache.org.
CAY-2466 New internal API to build SQL string
- node internals optimization
Project: http://git-wip-us.apache.org/repos/asf/cayenne/repo
Commit: http://git-wip-us.apache.org/repos/asf/cayenne/commit/1f10c920
Tree: http://git-wip-us.apache.org/repos/asf/cayenne/tree/1f10c920
Diff: http://git-wip-us.apache.org/repos/asf/cayenne/diff/1f10c920
Branch: refs/heads/master
Commit: 1f10c920dccc7f0a1b6e6611dd27d612c6b435a4
Parents: f6b2dac
Author: Nikita Timofeev <st...@gmail.com>
Authored: Wed Jan 9 11:42:18 2019 +0300
Committer: Nikita Timofeev <st...@gmail.com>
Committed: Wed Jan 9 11:42:18 2019 +0300
----------------------------------------------------------------------
.../cayenne/access/sqlbuilder/sqltree/Node.java | 57 ++++++----
.../access/sqlbuilder/sqltree/NodeTest.java | 110 +++++++++++++++++++
2 files changed, 145 insertions(+), 22 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/cayenne/blob/1f10c920/cayenne-server/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/Node.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/Node.java b/cayenne-server/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/Node.java
index d535028..550425f 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/Node.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/Node.java
@@ -19,14 +19,10 @@
package org.apache.cayenne.access.sqlbuilder.sqltree;
-import java.util.ArrayList;
-import java.util.List;
-
import org.apache.cayenne.access.sqlbuilder.NodeTreeVisitor;
import org.apache.cayenne.access.sqlbuilder.QuotingAppendable;
import org.apache.cayenne.access.sqlbuilder.StringBuilderAppendable;
-
/**
* @since 4.2
*/
@@ -34,7 +30,9 @@ public abstract class Node {
protected Node parent;
- protected List<Node> children;
+ protected Node[] children;
+
+ protected int childrenCount;
protected final NodeType type;
@@ -47,33 +45,46 @@ public abstract class Node {
}
public Node addChild(int index, Node node) {
- children.add(index, node);
+ if(children.length <= childrenCount) {
+ // expand + copy with empty slot at index
+ Node[] newChildren = new Node[children.length + 4];
+ System.arraycopy(children, 0, newChildren, 0, index);
+ System.arraycopy(children, index, newChildren, index + 1, (childrenCount - index));
+ children = newChildren;
+ } else {
+ // move tail after index on one position
+ System.arraycopy(children, index, children, index + 1, (childrenCount - index));
+ }
+ children[index] = node;
+ childrenCount++;
node.setParent(this);
return this;
}
public Node addChild(Node node) {
if(children == null) {
- children = new ArrayList<>(4);
+ children = new Node[4];
+ } else if(children.length <= childrenCount) {
+ Node[] newChildren = new Node[children.length + 4];
+ System.arraycopy(children, 0, newChildren, 0, children.length);
+ children = newChildren;
}
- children.add(node);
+ children[childrenCount++] = node;
node.setParent(this);
return this;
}
public Node getChild(int idx) {
- return children.get(idx);
+ return children[idx];
}
public int getChildrenCount() {
- if(children == null) {
- return 0;
- }
- return children.size();
+ return childrenCount;
}
public void replaceChild(int idx, Node node) {
- children.set(idx, node).setParent(null);
+ children[idx].setParent(null);
+ children[idx] = node;
node.setParent(this);
}
@@ -89,13 +100,14 @@ public abstract class Node {
if(!visitor.onNodeStart(this)) {
return;
}
- int count = getChildrenCount();
- for(int i=0; i<count; i++) {
- if(!visitor.onChildNodeStart(this, getChild(i), i, i < (count - 1))) {
+
+ for(int i=0; i<childrenCount; i++) {
+ boolean hasMore = i < (childrenCount - 1);
+ if(!visitor.onChildNodeStart(this, children[i], i, hasMore)) {
return;
}
- getChild(i).visit(visitor);
- visitor.onChildNodeEnd(this, getChild(i), i, i < (count - 1));
+ children[i].visit(visitor);
+ visitor.onChildNodeEnd(this, children[i], i, hasMore);
}
visitor.onNodeEnd(this);
}
@@ -107,9 +119,10 @@ public abstract class Node {
public <T extends Node> T deepCopy() {
Node node = this.copy();
if(children != null) {
- node.children = new ArrayList<>(children.size());
- for(Node child : children) {
- node.children.add(child.deepCopy());
+ node.children = new Node[childrenCount];
+ node.childrenCount = childrenCount;
+ for(int i=0; i<childrenCount; i++) {
+ node.children[i] = children[i].deepCopy();
}
}
return (T)node;
http://git-wip-us.apache.org/repos/asf/cayenne/blob/1f10c920/cayenne-server/src/test/java/org/apache/cayenne/access/sqlbuilder/sqltree/NodeTest.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/test/java/org/apache/cayenne/access/sqlbuilder/sqltree/NodeTest.java b/cayenne-server/src/test/java/org/apache/cayenne/access/sqlbuilder/sqltree/NodeTest.java
new file mode 100644
index 0000000..3a8e920
--- /dev/null
+++ b/cayenne-server/src/test/java/org/apache/cayenne/access/sqlbuilder/sqltree/NodeTest.java
@@ -0,0 +1,110 @@
+/*****************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ ****************************************************************/
+
+package org.apache.cayenne.access.sqlbuilder.sqltree;
+
+import org.apache.cayenne.access.sqlbuilder.QuotingAppendable;
+import org.junit.Before;
+import org.junit.Test;
+
+import static org.junit.Assert.*;
+
+/**
+ * @since 4.2
+ */
+public class NodeTest {
+
+ static private class TestNode extends Node {
+ @Override
+ public Node copy() {
+ return new TestNode();
+ }
+
+ @Override
+ public QuotingAppendable append(QuotingAppendable buffer) {
+ return buffer.append("test");
+ }
+ }
+
+ private Node node;
+
+ @Before
+ public void createNewNode() {
+ node = new TestNode();
+ }
+
+ @Test
+ public void testInsert() {
+ assertEquals(0, node.childrenCount);
+ assertNull(node.children);
+
+ node.addChild(new TestNode());
+
+ assertEquals(1, node.childrenCount);
+ assertNotNull(node.children);
+ assertEquals(4, node.children.length);
+
+ TestNode node1 = new TestNode();
+ this.node.addChild(0, node1);
+
+ assertSame(node1, node.children[0]);
+ assertEquals(2, this.node.childrenCount);
+ assertEquals(4, this.node.children.length);
+
+ TestNode node2 = new TestNode();
+ this.node.addChild(0, node2);
+ assertSame(node2, node.children[0]);
+
+ TestNode node3 = new TestNode();
+ this.node.addChild(0, node3);
+ assertSame(node3, node.children[0]);
+
+ TestNode node4 = new TestNode();
+ this.node.addChild(0, node4);
+ assertSame(node4, node.children[0]);
+
+ assertEquals(5, this.node.childrenCount);
+ assertEquals(8, this.node.children.length);
+ }
+
+ @Test
+ public void testAdd() {
+ assertEquals(0, node.childrenCount);
+ assertNull(node.children);
+
+ node.addChild(new TestNode());
+
+ assertEquals(1, node.childrenCount);
+ assertNotNull(node.children);
+ assertEquals(4, node.children.length);
+
+ node.addChild(new TestNode());
+ node.addChild(new TestNode());
+ node.addChild(new TestNode());
+
+ assertEquals(4, node.childrenCount);
+ assertEquals(4, node.children.length);
+
+ node.addChild(new TestNode());
+
+ assertEquals(5, node.childrenCount);
+ assertEquals(8, node.children.length);
+ }
+
+}
\ No newline at end of file
[03/11] cayenne git commit: CAY-2465 New SelectTranslator
implementation
Posted by nt...@apache.org.
http://git-wip-us.apache.org/repos/asf/cayenne/blob/f6b2dac9/cayenne-server/src/main/java/org/apache/cayenne/dba/sqlite/SQLiteSelectAction.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/dba/sqlite/SQLiteSelectAction.java b/cayenne-server/src/main/java/org/apache/cayenne/dba/sqlite/SQLiteSelectAction.java
new file mode 100644
index 0000000..a578c23
--- /dev/null
+++ b/cayenne-server/src/main/java/org/apache/cayenne/dba/sqlite/SQLiteSelectAction.java
@@ -0,0 +1,43 @@
+/*****************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ ****************************************************************/
+package org.apache.cayenne.dba.sqlite;
+
+import org.apache.cayenne.access.DataNode;
+import org.apache.cayenne.access.OperationObserver;
+import org.apache.cayenne.access.jdbc.SelectAction;
+import org.apache.cayenne.query.SQLAction;
+import org.apache.cayenne.query.SelectQuery;
+
+import java.sql.Connection;
+import java.sql.SQLException;
+
+/**
+ * @since 4.1
+ */
+public class SQLiteSelectAction extends SelectAction {
+
+ public SQLiteSelectAction(SelectQuery<?> query, DataNode dataNode) {
+ super(query, dataNode);
+ }
+
+ @Override
+ protected int getInMemoryOffset(int queryOffset) {
+ return 0;
+ }
+}
http://git-wip-us.apache.org/repos/asf/cayenne/blob/f6b2dac9/cayenne-server/src/main/java/org/apache/cayenne/dba/sqlite/SQLiteTreeProcessor.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/dba/sqlite/SQLiteTreeProcessor.java b/cayenne-server/src/main/java/org/apache/cayenne/dba/sqlite/SQLiteTreeProcessor.java
new file mode 100644
index 0000000..d20bc66
--- /dev/null
+++ b/cayenne-server/src/main/java/org/apache/cayenne/dba/sqlite/SQLiteTreeProcessor.java
@@ -0,0 +1,100 @@
+package org.apache.cayenne.dba.sqlite;
+
+import org.apache.cayenne.access.sqlbuilder.QuotingAppendable;
+import org.apache.cayenne.access.sqlbuilder.sqltree.FunctionNode;
+import org.apache.cayenne.access.sqlbuilder.sqltree.LimitOffsetNode;
+import org.apache.cayenne.access.sqlbuilder.sqltree.Node;
+import org.apache.cayenne.access.sqlbuilder.sqltree.OpExpressionNode;
+import org.apache.cayenne.access.sqlbuilder.sqltree.TextNode;
+import org.apache.cayenne.access.translator.select.BaseSQLTreeProcessor;
+import org.apache.cayenne.dba.mysql.sqltree.MysqlLimitOffsetNode;
+
+/**
+ * @since 4.2
+ */
+public class SQLiteTreeProcessor extends BaseSQLTreeProcessor {
+
+ @Override
+ protected void onLimitOffsetNode(Node parent, LimitOffsetNode child, int index) {
+ replaceChild(parent, index, new MysqlLimitOffsetNode(child.getLimit(), child.getOffset()), false);
+ }
+
+ @Override
+ protected void onFunctionNode(Node parent, FunctionNode child, int index) {
+ String functionName = child.getFunctionName();
+ Node replacement = null;
+ switch (functionName) {
+ case "LOCATE":
+ replacement = new FunctionNode("INSTR", child.getAlias(), true);
+ for (int i = 0; i <= 1; i++) {
+ replacement.addChild(child.getChild(1 - i));
+ }
+ parent.replaceChild(index, replacement);
+ return;
+ case "DAY_OF_YEAR":
+ replaceExtractFunction(parent, child, index, "'%j'");
+ return;
+ case "DAY_OF_WEEK":
+ replaceExtractFunction(parent, child, index, "'%w'");
+ return;
+ case "WEEK":
+ replaceExtractFunction(parent, child, index, "'%W'");
+ return;
+ case "YEAR":
+ replaceExtractFunction(parent, child, index, "'%Y'");
+ return;
+ case "MONTH":
+ replaceExtractFunction(parent, child, index, "'%m'");
+ return;
+ case "DAY":
+ case "DAY_OF_MONTH":
+ replaceExtractFunction(parent, child, index, "'%d'");
+ return;
+ case "HOUR":
+ replaceExtractFunction(parent, child, index, "'%H'");
+ return;
+ case "MINUTE":
+ replaceExtractFunction(parent, child, index, "'%M'");
+ return;
+ case "SECOND":
+ replaceExtractFunction(parent, child, index, "'%S'");
+ return;
+
+ case "SUBSTRING":
+ replacement = new FunctionNode("SUBSTR", child.getAlias(), true);
+ break;
+ case "CONCAT":
+ replacement = new OpExpressionNode("||");
+ break;
+ case "MOD":
+ replacement = new OpExpressionNode("%");
+ break;
+ case "CURRENT_DATE":
+ case "CURRENT_TIMESTAMP":
+ case "CURRENT_TIME":
+ replacement = new FunctionNode(functionName, child.getAlias(), false);
+ break;
+ }
+
+ if(replacement != null) {
+ replaceChild(parent, index, replacement);
+ }
+ }
+
+ private void replaceExtractFunction(Node parent, FunctionNode original, int index, String format) {
+ Node replacement = new FunctionNode("cast", original.getAlias(), true) {
+ @Override
+ public void appendChildrenSeparator(QuotingAppendable buffer, int childIdx) {
+ buffer.append(" as ");
+ }
+ };
+
+ FunctionNode strftime = new FunctionNode("strftime", null, true);
+ strftime.addChild(new TextNode(format));
+ strftime.addChild(original.getChild(0));
+ replacement.addChild(strftime);
+ replacement.addChild(new TextNode("integer"));
+
+ parent.replaceChild(index, replacement);
+ }
+}
http://git-wip-us.apache.org/repos/asf/cayenne/blob/f6b2dac9/cayenne-server/src/main/java/org/apache/cayenne/dba/sqlserver/SQLServerAdapter.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/dba/sqlserver/SQLServerAdapter.java b/cayenne-server/src/main/java/org/apache/cayenne/dba/sqlserver/SQLServerAdapter.java
index a94bf62..6a99dcb 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/dba/sqlserver/SQLServerAdapter.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/dba/sqlserver/SQLServerAdapter.java
@@ -20,11 +20,10 @@
package org.apache.cayenne.dba.sqlserver;
import java.util.List;
+import java.util.function.Function;
import org.apache.cayenne.access.DataNode;
-import org.apache.cayenne.access.translator.select.QualifierTranslator;
-import org.apache.cayenne.access.translator.select.QueryAssembler;
-import org.apache.cayenne.access.translator.select.SelectTranslator;
+import org.apache.cayenne.access.sqlbuilder.sqltree.Node;
import org.apache.cayenne.access.types.ExtendedType;
import org.apache.cayenne.access.types.ExtendedTypeFactory;
import org.apache.cayenne.access.types.ValueObjectTypeRegistry;
@@ -32,10 +31,8 @@ import org.apache.cayenne.configuration.Constants;
import org.apache.cayenne.configuration.RuntimeProperties;
import org.apache.cayenne.dba.sybase.SybaseAdapter;
import org.apache.cayenne.di.Inject;
-import org.apache.cayenne.map.EntityResolver;
import org.apache.cayenne.query.Query;
import org.apache.cayenne.query.SQLAction;
-import org.apache.cayenne.query.SelectQuery;
import org.apache.cayenne.resource.ResourceLocator;
/**
@@ -91,11 +88,11 @@ public class SQLServerAdapter extends SybaseAdapter {
}
/**
- * @since 4.0
+ * @since 4.2
*/
@Override
- public SelectTranslator getSelectTranslator(SelectQuery<?> query, EntityResolver entityResolver) {
- return new SQLServerSelectTranslator(query, this, entityResolver);
+ public Function<Node, Node> getSqlTreeProcessor() {
+ return new SQLServerTreeProcessor();
}
/**
@@ -108,14 +105,4 @@ public class SQLServerAdapter extends SybaseAdapter {
return query.createSQLAction(new SQLServerActionBuilder(node));
}
- /**
- * Returns a trimming translator.
- */
- @Override
- public QualifierTranslator getQualifierTranslator(QueryAssembler queryAssembler) {
- QualifierTranslator translator = new SQLServerTrimmingQualifierTranslator(queryAssembler,
- SQLServerAdapter.TRIM_FUNCTION);
- translator.setCaseInsensitive(caseInsensitiveCollations);
- return translator;
- }
}
http://git-wip-us.apache.org/repos/asf/cayenne/blob/f6b2dac9/cayenne-server/src/main/java/org/apache/cayenne/dba/sqlserver/SQLServerSelectTranslator.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/dba/sqlserver/SQLServerSelectTranslator.java b/cayenne-server/src/main/java/org/apache/cayenne/dba/sqlserver/SQLServerSelectTranslator.java
deleted file mode 100644
index b78877c..0000000
--- a/cayenne-server/src/main/java/org/apache/cayenne/dba/sqlserver/SQLServerSelectTranslator.java
+++ /dev/null
@@ -1,54 +0,0 @@
-/*****************************************************************
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- ****************************************************************/
-package org.apache.cayenne.dba.sqlserver;
-
-import org.apache.cayenne.access.translator.select.DefaultSelectTranslator;
-import org.apache.cayenne.dba.DbAdapter;
-import org.apache.cayenne.map.EntityResolver;
-import org.apache.cayenne.query.Query;
-
-public class SQLServerSelectTranslator extends DefaultSelectTranslator {
-
- /**
- * @since 4.0
- */
- public SQLServerSelectTranslator(Query query, DbAdapter adapter, EntityResolver entityResolver) {
- super(query, adapter, entityResolver);
- }
-
- @Override
- protected void appendLimitAndOffsetClauses(StringBuilder buffer) {
-
- int limit = queryMetadata.getFetchLimit();
- int offset = queryMetadata.getFetchOffset();
-
- if (limit > 0) {
- String sql = buffer.toString();
-
- // If contains distinct insert top limit after
- if (sql.startsWith("SELECT DISTINCT ")) {
- buffer.replace(0, 15, "SELECT DISTINCT TOP " + (offset + limit));
-
- } else {
- buffer.replace(0, 6, "SELECT TOP " + (offset + limit));
- }
- }
- }
-
-}
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/cayenne/blob/f6b2dac9/cayenne-server/src/main/java/org/apache/cayenne/dba/sqlserver/SQLServerTreeProcessor.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/dba/sqlserver/SQLServerTreeProcessor.java b/cayenne-server/src/main/java/org/apache/cayenne/dba/sqlserver/SQLServerTreeProcessor.java
new file mode 100644
index 0000000..2681a53
--- /dev/null
+++ b/cayenne-server/src/main/java/org/apache/cayenne/dba/sqlserver/SQLServerTreeProcessor.java
@@ -0,0 +1,128 @@
+/*****************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ ****************************************************************/
+
+package org.apache.cayenne.dba.sqlserver;
+
+import org.apache.cayenne.access.sqlbuilder.sqltree.ColumnNode;
+import org.apache.cayenne.access.sqlbuilder.sqltree.EmptyNode;
+import org.apache.cayenne.access.sqlbuilder.sqltree.FunctionNode;
+import org.apache.cayenne.access.sqlbuilder.sqltree.LimitOffsetNode;
+import org.apache.cayenne.access.sqlbuilder.sqltree.Node;
+import org.apache.cayenne.access.sqlbuilder.sqltree.NodeType;
+import org.apache.cayenne.access.sqlbuilder.sqltree.OpExpressionNode;
+import org.apache.cayenne.access.sqlbuilder.sqltree.TextNode;
+import org.apache.cayenne.access.sqlbuilder.sqltree.TopNode;
+import org.apache.cayenne.access.translator.select.BaseSQLTreeProcessor;
+import org.apache.cayenne.dba.sqlserver.sqltree.SQLServerColumnNode;
+
+/**
+ * @since 4.2
+ */
+public class SQLServerTreeProcessor extends BaseSQLTreeProcessor {
+
+ @Override
+ protected void onLimitOffsetNode(Node parent, LimitOffsetNode child, int index) {
+ // SQLServer uses "SELECT DISTINCT TOP N" or "SELECT TOP N" instead of LIMIT
+ // Offset will be calculated in-memory
+ replaceChild(parent, index, new EmptyNode(), false);
+ if(child.getLimit() > 0) {
+ int limit = child.getLimit() + child.getOffset();
+ // we have root actually as input for processor, but it's better to keep processor stateless
+ // root shouldn't be far from limit's parent (likely it will be parent itself)
+ Node root = getRoot(parent);
+ int idx = 0;
+ if(root.getChild(0).getType() == NodeType.DISTINCT) {
+ idx = 1;
+ }
+ root.addChild(idx, new TopNode(limit));
+ }
+ }
+
+ private Node getRoot(Node node) {
+ while(node.getParent() != null) {
+ node = node.getParent();
+ }
+ return node;
+ }
+
+ @Override
+ protected void onColumnNode(Node parent, ColumnNode child, int index) {
+ replaceChild(parent, index, new SQLServerColumnNode(child));
+ }
+
+ @Override
+ protected void onFunctionNode(Node parent, FunctionNode child, int index) {
+ String functionName = child.getFunctionName();
+ Node replacement = null;
+ switch (functionName) {
+ case "LENGTH":
+ replacement = new FunctionNode("LEN", child.getAlias(), true);
+ break;
+ case "LOCATE":
+ replacement = new FunctionNode("CHARINDEX", child.getAlias(), true);
+ break;
+ case "MOD":
+ replacement = new OpExpressionNode("%");
+ break;
+ case "TRIM":
+ Node rtrim = new FunctionNode("RTRIM", null, true);
+ replacement = new FunctionNode("LTRIM", child.getAlias(), true);
+ for(int i=0; i<child.getChildrenCount(); i++) {
+ rtrim.addChild(child.getChild(i));
+ }
+ replacement.addChild(rtrim);
+ parent.replaceChild(index, replacement);
+ return;
+ case "CURRENT_DATE":
+ replacement = new FunctionNode("{fn CURDATE()}", child.getAlias(), false);
+ break;
+ case "CURRENT_TIME":
+ replacement = new FunctionNode("{fn CURTIME()}", child.getAlias(), false);
+ break;
+ case "CURRENT_TIMESTAMP":
+ replacement = new FunctionNode("CURRENT_TIMESTAMP", child.getAlias(), false);
+ break;
+
+ case "YEAR":
+ case "MONTH":
+ case "WEEK":
+ case "DAY_OF_YEAR":
+ case "DAY":
+ case "DAY_OF_MONTH":
+ case "DAY_OF_WEEK":
+ case "HOUR":
+ case "MINUTE":
+ case "SECOND":
+ replacement = new FunctionNode("DATEPART", child.getAlias(), true);
+ if("DAY_OF_MONTH".equals(functionName)) {
+ functionName = "DAY";
+ } else if("DAY_OF_WEEK".equals(functionName)) {
+ functionName = "WEEKDAY";
+ } else if("DAY_OF_YEAR".equals(functionName)) {
+ functionName = "DAYOFYEAR";
+ }
+ replacement.addChild(new TextNode(functionName));
+ break;
+ }
+
+ if(replacement != null) {
+ replaceChild(parent, index, replacement);
+ }
+ }
+}
http://git-wip-us.apache.org/repos/asf/cayenne/blob/f6b2dac9/cayenne-server/src/main/java/org/apache/cayenne/dba/sqlserver/SQLServerTrimmingQualifierTranslator.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/dba/sqlserver/SQLServerTrimmingQualifierTranslator.java b/cayenne-server/src/main/java/org/apache/cayenne/dba/sqlserver/SQLServerTrimmingQualifierTranslator.java
deleted file mode 100644
index 9847742..0000000
--- a/cayenne-server/src/main/java/org/apache/cayenne/dba/sqlserver/SQLServerTrimmingQualifierTranslator.java
+++ /dev/null
@@ -1,207 +0,0 @@
-/*****************************************************************
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- ****************************************************************/
-package org.apache.cayenne.dba.sqlserver;
-
-import org.apache.cayenne.access.translator.select.QueryAssembler;
-import org.apache.cayenne.access.translator.select.TrimmingQualifierTranslator;
-import org.apache.cayenne.exp.Expression;
-import org.apache.cayenne.exp.parser.ASTExtract;
-import org.apache.cayenne.exp.parser.ASTFunctionCall;
-import org.apache.cayenne.map.DbAttribute;
-
-import java.sql.Types;
-import java.util.ArrayList;
-import java.util.List;
-
-/**
- * @since 3.0
- */
-class SQLServerTrimmingQualifierTranslator extends TrimmingQualifierTranslator {
-
- // since LIKE IGNORE CASE requires more contextual information than the
- // super
- // translator can provide, we are using an internal element stack to trace
- // translation
- // context.. Maybe it is a good idea to introduce it in the superclass?
- private List<Expression> expressionStack;
-
- SQLServerTrimmingQualifierTranslator(QueryAssembler queryAssembler, String trimFunction) {
- super(queryAssembler, trimFunction);
- expressionStack = new ArrayList<>();
- }
-
- @Override
- public void startNode(Expression node, Expression parentNode) {
- push(node);
- super.startNode(node, parentNode);
- }
-
- @Override
- protected void processColumn(DbAttribute dbAttr) {
-
- Expression node = peek(1);
-
- boolean likeCI = node != null && dbAttr.getType() == Types.CLOB
- && (node.getType() == Expression.LIKE_IGNORE_CASE || node.getType() == Expression.NOT_LIKE_IGNORE_CASE);
-
- if (likeCI) {
- out.append("CAST(");
- }
-
- super.processColumn(dbAttr);
-
- if (likeCI) {
- out.append(" AS NVARCHAR(MAX))");
- }
- }
-
- @Override
- protected void processColumnWithQuoteSqlIdentifiers(DbAttribute dbAttr, Expression pathExp) {
- Expression node = peek(1);
-
- boolean likeCI = node != null && dbAttr.getType() == Types.CLOB
- && (node.getType() == Expression.LIKE_IGNORE_CASE || node.getType() == Expression.NOT_LIKE_IGNORE_CASE);
-
- if (likeCI) {
- out.append("CAST(");
- }
-
- super.processColumnWithQuoteSqlIdentifiers(dbAttr, node);
-
- if (likeCI) {
- out.append(" AS NVARCHAR(MAX))");
- }
- }
-
- @Override
- public void endNode(Expression node, Expression parentNode) {
- super.endNode(node, parentNode);
- pop();
- }
-
- private void push(Expression node) {
- expressionStack.add(node);
- }
-
- private void pop() {
- int len = expressionStack.size();
- if (len > 0) {
- expressionStack.remove(len - 1);
- }
- }
-
- private Expression peek(int tailIndex) {
- int index = expressionStack.size() - tailIndex - 1;
- if (index < 0) {
- return null;
- }
-
- return expressionStack.get(index);
- }
-
- /**
- * @since 4.0
- */
- @Override
- protected void appendFunction(ASTFunctionCall functionExpression) {
- switch (functionExpression.getFunctionName()) {
- case "LENGTH":
- out.append("LEN");
- break;
- case "LOCATE":
- out.append("CHARINDEX");
- break;
- case "MOD":
- // noop
- break;
- case "TRIM":
- out.append("LTRIM(RTRIM");
- break;
- case "CURRENT_DATE":
- out.append("{fn CURDATE()}");
- break;
- case "CURRENT_TIME":
- out.append("{fn CURTIME()}");
- break;
- default:
- super.appendFunction(functionExpression);
- }
- }
-
- /**
- * @since 4.0
- */
- @Override
- protected void appendFunctionArgDivider(ASTFunctionCall functionExpression) {
- if("MOD".equals(functionExpression.getFunctionName())) {
- out.append(" % ");
- } else {
- super.appendFunctionArgDivider(functionExpression);
- }
- }
-
- /**
- * @since 4.0
- */
- @Override
- protected void clearLastFunctionArgDivider(ASTFunctionCall functionExpression) {
- if("MOD".equals(functionExpression.getFunctionName())) {
- out.delete(out.length() - " % ".length(), out.length());
- } else {
- super.clearLastFunctionArgDivider(functionExpression);
- if("TRIM".equals(functionExpression.getFunctionName())) {
- out.append(")");
- }
- }
-
- if(functionExpression instanceof ASTExtract) {
- out.append(")");
- }
- }
-
- @Override
- protected boolean parenthesisNeeded(Expression node, Expression parentNode) {
- if (node.getType() == Expression.FUNCTION_CALL) {
- if (node instanceof ASTExtract) {
- return false;
- }
- }
-
- return super.parenthesisNeeded(node, parentNode);
- }
-
- @Override
- protected void appendExtractFunction(ASTExtract functionExpression) {
- out.append("DATEPART(");
- switch (functionExpression.getPart()) {
- case DAY_OF_MONTH:
- out.append("DAY");
- break;
- case DAY_OF_WEEK:
- out.append("WEEKDAY");
- break;
- case DAY_OF_YEAR:
- out.append("DAYOFYEAR");
- break;
- default:
- out.append(functionExpression.getPart().name());
- }
- out.append(" , ");
- }
-}
http://git-wip-us.apache.org/repos/asf/cayenne/blob/f6b2dac9/cayenne-server/src/main/java/org/apache/cayenne/dba/sqlserver/sqltree/SQLServerColumnNode.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/dba/sqlserver/sqltree/SQLServerColumnNode.java b/cayenne-server/src/main/java/org/apache/cayenne/dba/sqlserver/sqltree/SQLServerColumnNode.java
new file mode 100644
index 0000000..b629840
--- /dev/null
+++ b/cayenne-server/src/main/java/org/apache/cayenne/dba/sqlserver/sqltree/SQLServerColumnNode.java
@@ -0,0 +1,46 @@
+/*****************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ ****************************************************************/
+
+package org.apache.cayenne.dba.sqlserver.sqltree;
+
+import org.apache.cayenne.access.sqlbuilder.QuotingAppendable;
+import org.apache.cayenne.access.sqlbuilder.sqltree.ColumnNode;
+import org.apache.cayenne.access.sqlbuilder.sqltree.TrimmingColumnNode;
+
+/**
+ * @since 4.2
+ */
+public class SQLServerColumnNode extends TrimmingColumnNode {
+
+ public SQLServerColumnNode(ColumnNode columnNode) {
+ super(columnNode);
+ }
+
+ @Override
+ protected void appendClobColumnNode(QuotingAppendable buffer) {
+ buffer.append(" CAST(");
+ appendColumnNode(buffer);
+ buffer.append(" AS NVARCHAR(MAX))");
+ }
+
+ @Override
+ public SQLServerColumnNode copy() {
+ return new SQLServerColumnNode(columnNode.deepCopy());
+ }
+}
http://git-wip-us.apache.org/repos/asf/cayenne/blob/f6b2dac9/cayenne-server/src/main/java/org/apache/cayenne/dba/sybase/SybaseAdapter.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/dba/sybase/SybaseAdapter.java b/cayenne-server/src/main/java/org/apache/cayenne/dba/sybase/SybaseAdapter.java
index 71fa2d9..2aaff9d 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/dba/sybase/SybaseAdapter.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/dba/sybase/SybaseAdapter.java
@@ -23,12 +23,11 @@ import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.sql.Types;
import java.util.List;
+import java.util.function.Function;
+import org.apache.cayenne.access.sqlbuilder.sqltree.Node;
import org.apache.cayenne.access.translator.ParameterBinding;
import org.apache.cayenne.access.translator.ejbql.EJBQLTranslatorFactory;
-import org.apache.cayenne.access.translator.select.QualifierTranslator;
-import org.apache.cayenne.access.translator.select.QueryAssembler;
-import org.apache.cayenne.access.translator.select.SelectTranslator;
import org.apache.cayenne.access.types.ByteArrayType;
import org.apache.cayenne.access.types.ByteType;
import org.apache.cayenne.access.types.CharType;
@@ -43,15 +42,13 @@ import org.apache.cayenne.dba.DefaultQuotingStrategy;
import org.apache.cayenne.dba.JdbcAdapter;
import org.apache.cayenne.dba.PkGenerator;
import org.apache.cayenne.dba.QuotingStrategy;
+import org.apache.cayenne.dba.sqlserver.SQLServerTreeProcessor;
import org.apache.cayenne.di.Inject;
import org.apache.cayenne.map.DbAttribute;
-import org.apache.cayenne.map.EntityResolver;
-import org.apache.cayenne.query.SelectQuery;
import org.apache.cayenne.resource.ResourceLocator;
/**
- * DbAdapter implementation for <a href="http://www.sybase.com">Sybase
- * RDBMS</a>.
+ * DbAdapter implementation for <a href="http://www.sybase.com">Sybase RDBMS</a>.
*/
public class SybaseAdapter extends JdbcAdapter {
@@ -79,20 +76,12 @@ public class SybaseAdapter extends JdbcAdapter {
return new SybaseEJBQLTranslatorFactory();
}
- /**
- * @since 4.0
- */
- @Override
- public SelectTranslator getSelectTranslator(SelectQuery<?> query, EntityResolver entityResolver) {
- return new SybaseSelectTranslator(query, this, entityResolver);
- }
-
/**
- * @since 4.0
+ * @since 4.2
*/
@Override
- public QualifierTranslator getQualifierTranslator(QueryAssembler queryAssembler) {
- return new SybaseQualifierTranslator(queryAssembler);
+ public Function<Node, Node> getSqlTreeProcessor() {
+ return new SQLServerTreeProcessor();
}
/**
http://git-wip-us.apache.org/repos/asf/cayenne/blob/f6b2dac9/cayenne-server/src/main/java/org/apache/cayenne/dba/sybase/SybaseQualifierTranslator.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/dba/sybase/SybaseQualifierTranslator.java b/cayenne-server/src/main/java/org/apache/cayenne/dba/sybase/SybaseQualifierTranslator.java
deleted file mode 100644
index 0f6e239..0000000
--- a/cayenne-server/src/main/java/org/apache/cayenne/dba/sybase/SybaseQualifierTranslator.java
+++ /dev/null
@@ -1,114 +0,0 @@
-/*****************************************************************
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- ****************************************************************/
-
-package org.apache.cayenne.dba.sybase;
-
-import org.apache.cayenne.access.translator.select.QualifierTranslator;
-import org.apache.cayenne.access.translator.select.QueryAssembler;
-import org.apache.cayenne.exp.Expression;
-import org.apache.cayenne.exp.parser.ASTExtract;
-import org.apache.cayenne.exp.parser.ASTFunctionCall;
-
-/**
- * @since 4.0
- */
-public class SybaseQualifierTranslator extends QualifierTranslator {
-
- public SybaseQualifierTranslator(QueryAssembler queryAssembler) {
- super(queryAssembler);
- }
-
- @Override
- protected void appendFunction(ASTFunctionCall functionExpression) {
- switch (functionExpression.getFunctionName()) {
- case "MOD":
- case "CONCAT":
- // noop
- break;
- case "LENGTH":
- out.append("LEN");
- break;
- case "LOCATE":
- out.append("CHARINDEX");
- break;
- default:
- super.appendFunction(functionExpression);
- }
- }
-
- @Override
- protected void appendFunctionArgDivider(ASTFunctionCall functionExpression) {
- switch (functionExpression.getFunctionName()) {
- case "MOD":
- out.append(" % ");
- break;
- case "CONCAT":
- out.append(" + ");
- break;
- default:
- super.appendFunctionArgDivider(functionExpression);
- }
- }
-
- @Override
- protected void clearLastFunctionArgDivider(ASTFunctionCall functionExpression) {
- switch (functionExpression.getFunctionName()) {
- case "MOD":
- case "CONCAT":
- out.delete(out.length() - 3, out.length());
- break;
- default:
- super.clearLastFunctionArgDivider(functionExpression);
- }
-
- if(functionExpression instanceof ASTExtract) {
- out.append(")");
- }
- }
-
- @Override
- protected boolean parenthesisNeeded(Expression node, Expression parentNode) {
- if (node.getType() == Expression.FUNCTION_CALL) {
- if (node instanceof ASTExtract) {
- return false;
- }
- }
-
- return super.parenthesisNeeded(node, parentNode);
- }
-
- @Override
- protected void appendExtractFunction(ASTExtract functionExpression) {
- out.append("datepart(");
- switch (functionExpression.getPart()) {
- case DAY_OF_MONTH:
- out.append("day");
- break;
- case DAY_OF_WEEK:
- out.append("weekday");
- break;
- case DAY_OF_YEAR:
- out.append("dayofyear");
- break;
- default:
- out.append(functionExpression.getPart().name().toLowerCase());
- }
- out.append(" , ");
- }
-}
http://git-wip-us.apache.org/repos/asf/cayenne/blob/f6b2dac9/cayenne-server/src/main/java/org/apache/cayenne/dba/sybase/SybaseSelectTranslator.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/dba/sybase/SybaseSelectTranslator.java b/cayenne-server/src/main/java/org/apache/cayenne/dba/sybase/SybaseSelectTranslator.java
deleted file mode 100644
index 93af596..0000000
--- a/cayenne-server/src/main/java/org/apache/cayenne/dba/sybase/SybaseSelectTranslator.java
+++ /dev/null
@@ -1,52 +0,0 @@
-/*****************************************************************
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- ****************************************************************/
-package org.apache.cayenne.dba.sybase;
-
-import org.apache.cayenne.access.translator.select.DefaultSelectTranslator;
-import org.apache.cayenne.dba.DbAdapter;
-import org.apache.cayenne.map.EntityResolver;
-import org.apache.cayenne.query.Query;
-
-public class SybaseSelectTranslator extends DefaultSelectTranslator {
- /**
- * @since 4.0
- */
- public SybaseSelectTranslator(Query query, DbAdapter adapter, EntityResolver entityResolver) {
- super(query, adapter, entityResolver);
- }
-
- @Override
- protected void appendLimitAndOffsetClauses(StringBuilder buffer) {
-
- int limit = queryMetadata.getFetchLimit();
- int offset = queryMetadata.getFetchOffset();
-
- if (limit > 0) {
- String sql = buffer.toString();
-
- // If contains distinct insert top limit after
- if (sql.startsWith("SELECT DISTINCT ")) {
- buffer.replace(0, 15, "SELECT DISTINCT TOP " + (offset + limit));
-
- } else {
- buffer.replace(0, 6, "SELECT TOP " + (offset + limit));
- }
- }
- }
-}
http://git-wip-us.apache.org/repos/asf/cayenne/blob/f6b2dac9/cayenne-server/src/main/java/org/apache/cayenne/query/ColumnSelect.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/query/ColumnSelect.java b/cayenne-server/src/main/java/org/apache/cayenne/query/ColumnSelect.java
index 35c36f2..fafd258 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/query/ColumnSelect.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/query/ColumnSelect.java
@@ -686,6 +686,13 @@ public class ColumnSelect<T> extends FluentSelect<T> {
return having;
}
+ /**
+ * @since 4.2
+ */
+ public boolean isDistinct() {
+ return distinct;
+ }
+
@Override
public T selectFirst(ObjectContext context) {
return context.selectFirst(limit(1));
http://git-wip-us.apache.org/repos/asf/cayenne/blob/f6b2dac9/cayenne-server/src/main/java/org/apache/cayenne/query/SelectQueryMetadata.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/query/SelectQueryMetadata.java b/cayenne-server/src/main/java/org/apache/cayenne/query/SelectQueryMetadata.java
index 8a9e7d9..9ee83bf 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/query/SelectQueryMetadata.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/query/SelectQueryMetadata.java
@@ -232,7 +232,7 @@ class SelectQueryMetadata extends BaseQueryMetadata {
*/
@Override
public Map<String, String> getPathSplitAliases() {
- return pathSplitAliases != null ? pathSplitAliases : Collections.<String, String> emptyMap();
+ return pathSplitAliases != null ? pathSplitAliases : Collections.emptyMap();
}
/**
@@ -328,18 +328,17 @@ class SelectQueryMetadata extends BaseQueryMetadata {
* (possibly including joint prefetch).
* This information will be used to correctly create Persistent object back from raw result.
*
- * This method is actually repeating logic of
- * {@link org.apache.cayenne.access.translator.select.DefaultSelectTranslator#appendQueryColumns}.
- * Here we don't care about intermediate joins and few other things so it's shorter.
- * Logic of these methods should be unified and simplified, possibly to a single source of metadata,
- * generated only once and used everywhere.
- *
* @param query original query
* @param column full object column
* @param resolver entity resolver to get ObjEntity and ClassDescriptor
* @return Entity result
*/
private EntityResult buildEntityResultForColumn(SelectQuery<?> query, BaseProperty<?> column, EntityResolver resolver) {
+ // This method is actually repeating logic of DescriptorColumnExtractor.
+ // Here we don't care about intermediate joins and few other things so it's shorter.
+ // Logic of these methods should be unified and simplified, possibly to a single source of metadata,
+ // generated only once and used everywhere.
+
final EntityResult result = new EntityResult(column.getType());
// Collecting visitor for ObjAttributes and toOne relationships
http://git-wip-us.apache.org/repos/asf/cayenne/blob/f6b2dac9/cayenne-server/src/main/java/org/apache/cayenne/util/ArrayUtil.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/util/ArrayUtil.java b/cayenne-server/src/main/java/org/apache/cayenne/util/ArrayUtil.java
new file mode 100644
index 0000000..80df070
--- /dev/null
+++ b/cayenne-server/src/main/java/org/apache/cayenne/util/ArrayUtil.java
@@ -0,0 +1,260 @@
+/*****************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ ****************************************************************/
+
+package org.apache.cayenne.util;
+
+/**
+ * @since 4.1
+ */
+public final class ArrayUtil {
+
+ public static int[][] sliceArray(int[] array, int batchSize) {
+ if(array == null) {
+ return null;
+ }
+ int length = array.length;
+
+ if(length <= batchSize) {
+ return new int[][]{array};
+ }
+
+ int batches = length / batchSize;
+ if(length % batchSize > 0) {
+ batches++;
+ }
+
+ int[][] result = new int[batches][];
+ int offset = 0;
+ for(int i=0; i<batches; i++) {
+ int nextSize = i < batches - 1 ? batchSize : length - offset;
+ result[i] = new int[nextSize];
+ System.arraycopy(array, offset, result[i], 0, nextSize);
+ offset += nextSize;
+ }
+ return result;
+ }
+
+ public static long[][] sliceArray(long[] array, int batchSize) {
+ if(array == null) {
+ return null;
+ }
+ int length = array.length;
+
+ if(length <= batchSize) {
+ return new long[][]{array};
+ }
+
+ int batches = length / batchSize;
+ if(length % batchSize > 0) {
+ batches++;
+ }
+
+ long[][] result = new long[batches][];
+ int offset = 0;
+ for(int i=0; i<batches; i++) {
+ int nextSize = i < batches - 1 ? batchSize : length - offset;
+ result[i] = new long[nextSize];
+ System.arraycopy(array, offset, result[i], 0, nextSize);
+ offset += nextSize;
+ }
+ return result;
+ }
+
+ public static float[][] sliceArray(float[] array, int batchSize) {
+ if(array == null) {
+ return null;
+ }
+ int length = array.length;
+
+ if(length <= batchSize) {
+ return new float[][]{array};
+ }
+
+ int batches = length / batchSize;
+ if(length % batchSize > 0) {
+ batches++;
+ }
+
+ float[][] result = new float[batches][];
+ int offset = 0;
+ for(int i=0; i<batches; i++) {
+ int nextSize = i < batches - 1 ? batchSize : length - offset;
+ result[i] = new float[nextSize];
+ System.arraycopy(array, offset, result[i], 0, nextSize);
+ offset += nextSize;
+ }
+ return result;
+ }
+
+ public static double[][] sliceArray(double[] array, int batchSize) {
+ if(array == null) {
+ return null;
+ }
+ int length = array.length;
+
+ if(length <= batchSize) {
+ return new double[][]{array};
+ }
+
+ int batches = length / batchSize;
+ if(length % batchSize > 0) {
+ batches++;
+ }
+
+ double[][] result = new double[batches][];
+ int offset = 0;
+ for(int i=0; i<batches; i++) {
+ int nextSize = i < batches - 1 ? batchSize : length - offset;
+ result[i] = new double[nextSize];
+ System.arraycopy(array, offset, result[i], 0, nextSize);
+ offset += nextSize;
+ }
+ return result;
+ }
+
+ public static short[][] sliceArray(short[] array, int batchSize) {
+ if(array == null) {
+ return null;
+ }
+ int length = array.length;
+
+ if(length <= batchSize) {
+ return new short[][]{array};
+ }
+
+ int batches = length / batchSize;
+ if(length % batchSize > 0) {
+ batches++;
+ }
+
+ short[][] result = new short[batches][];
+ int offset = 0;
+ for(int i=0; i<batches; i++) {
+ int nextSize = i < batches - 1 ? batchSize : length - offset;
+ result[i] = new short[nextSize];
+ System.arraycopy(array, offset, result[i], 0, nextSize);
+ offset += nextSize;
+ }
+ return result;
+ }
+
+ public static char[][] sliceArray(char[] array, int batchSize) {
+ if(array == null) {
+ return null;
+ }
+ int length = array.length;
+
+ if(length <= batchSize) {
+ return new char[][]{array};
+ }
+
+ int batches = length / batchSize;
+ if(length % batchSize > 0) {
+ batches++;
+ }
+
+ char[][] result = new char[batches][];
+ int offset = 0;
+ for(int i=0; i<batches; i++) {
+ int nextSize = i < batches - 1 ? batchSize : length - offset;
+ result[i] = new char[nextSize];
+ System.arraycopy(array, offset, result[i], 0, nextSize);
+ offset += nextSize;
+ }
+ return result;
+ }
+
+ public static boolean[][] sliceArray(boolean[] array, int batchSize) {
+ if(array == null) {
+ return null;
+ }
+ int length = array.length;
+
+ if(length <= batchSize) {
+ return new boolean[][]{array};
+ }
+
+ int batches = length / batchSize;
+ if(length % batchSize > 0) {
+ batches++;
+ }
+
+ boolean[][] result = new boolean[batches][];
+ int offset = 0;
+ for(int i=0; i<batches; i++) {
+ int nextSize = i < batches - 1 ? batchSize : length - offset;
+ result[i] = new boolean[nextSize];
+ System.arraycopy(array, offset, result[i], 0, nextSize);
+ offset += nextSize;
+ }
+ return result;
+ }
+
+ public static byte[][] sliceArray(byte[] array, int batchSize) {
+ if(array == null) {
+ return null;
+ }
+ int length = array.length;
+
+ if(length <= batchSize) {
+ return new byte[][]{array};
+ }
+
+ int batches = length / batchSize;
+ if(length % batchSize > 0) {
+ batches++;
+ }
+
+ byte[][] result = new byte[batches][];
+ int offset = 0;
+ for(int i=0; i<batches; i++) {
+ int nextSize = i < batches - 1 ? batchSize : length - offset;
+ result[i] = new byte[nextSize];
+ System.arraycopy(array, offset, result[i], 0, nextSize);
+ offset += nextSize;
+ }
+ return result;
+ }
+
+ public static Object[][] sliceArray(Object[] array, int batchSize) {
+ if(array == null) {
+ return null;
+ }
+ int length = array.length;
+
+ if(length <= batchSize) {
+ return new Object[][]{array};
+ }
+
+ int batches = length / batchSize;
+ if(length % batchSize > 0) {
+ batches++;
+ }
+
+ Object[][] result = new Object[batches][];
+ int offset = 0;
+ for(int i=0; i<batches; i++) {
+ int nextSize = i < batches - 1 ? batchSize : length - offset;
+ result[i] = new Object[nextSize];
+ System.arraycopy(array, offset, result[i], 0, nextSize);
+ offset += nextSize;
+ }
+ return result;
+ }
+}
http://git-wip-us.apache.org/repos/asf/cayenne/blob/f6b2dac9/cayenne-server/src/test/java/org/apache/cayenne/FlattenedRelationshipsIT.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/test/java/org/apache/cayenne/FlattenedRelationshipsIT.java b/cayenne-server/src/test/java/org/apache/cayenne/FlattenedRelationshipsIT.java
index 7a10d10..03d3774 100644
--- a/cayenne-server/src/test/java/org/apache/cayenne/FlattenedRelationshipsIT.java
+++ b/cayenne-server/src/test/java/org/apache/cayenne/FlattenedRelationshipsIT.java
@@ -76,8 +76,7 @@ public class FlattenedRelationshipsIT extends ServerCase {
tFlattenedTest2.setColumns("FT2_ID", "FT1_ID", "NAME");
tFlattenedTest3 = new TableHelper(dbHelper, "FLATTENED_TEST_3");
- tFlattenedTest3.setColumns("FT3_ID", "FT2_ID", "NAME").setColumnTypes(
- Types.INTEGER, Types.INTEGER, Types.VARCHAR);
+ tFlattenedTest3.setColumns("FT3_ID", "FT2_ID", "NAME");
tComplexJoin = new TableHelper(dbHelper, "COMPLEX_JOIN");
tComplexJoin.setColumns("PK", "FT1_FK", "FT3_FK", "EXTRA_COLUMN");
http://git-wip-us.apache.org/repos/asf/cayenne/blob/f6b2dac9/cayenne-server/src/test/java/org/apache/cayenne/access/translator/select/BaseColumnExtractorTest.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/test/java/org/apache/cayenne/access/translator/select/BaseColumnExtractorTest.java b/cayenne-server/src/test/java/org/apache/cayenne/access/translator/select/BaseColumnExtractorTest.java
new file mode 100644
index 0000000..155c968
--- /dev/null
+++ b/cayenne-server/src/test/java/org/apache/cayenne/access/translator/select/BaseColumnExtractorTest.java
@@ -0,0 +1,43 @@
+/*****************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ ****************************************************************/
+
+package org.apache.cayenne.access.translator.select;
+
+import java.sql.Types;
+
+import org.apache.cayenne.map.DbAttribute;
+import org.apache.cayenne.map.DbEntity;
+
+/**
+ * @since 4.2
+ */
+public class BaseColumnExtractorTest {
+
+ DbEntity createMockDbEntity(String entityName) {
+ DbEntity entity = new DbEntity(entityName);
+ DbAttribute id = new DbAttribute("id");
+ id.setPrimaryKey(true);
+ id.setType(Types.BIGINT);
+ DbAttribute name = new DbAttribute("name");
+ name.setType(Types.VARBINARY);
+ entity.addAttribute(id);
+ entity.addAttribute(name);
+ return entity;
+ }
+}
http://git-wip-us.apache.org/repos/asf/cayenne/blob/f6b2dac9/cayenne-server/src/test/java/org/apache/cayenne/access/translator/select/ColumnDescriptorStageTest.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/test/java/org/apache/cayenne/access/translator/select/ColumnDescriptorStageTest.java b/cayenne-server/src/test/java/org/apache/cayenne/access/translator/select/ColumnDescriptorStageTest.java
new file mode 100644
index 0000000..dd1c7e7
--- /dev/null
+++ b/cayenne-server/src/test/java/org/apache/cayenne/access/translator/select/ColumnDescriptorStageTest.java
@@ -0,0 +1,55 @@
+/*****************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ ****************************************************************/
+
+package org.apache.cayenne.access.translator.select;
+
+import org.apache.cayenne.access.jdbc.ColumnDescriptor;
+import org.apache.cayenne.access.sqlbuilder.sqltree.EmptyNode;
+import org.apache.cayenne.exp.Property;
+import org.junit.Test;
+
+import static org.junit.Assert.*;
+
+/**
+ * @since 4.2
+ */
+public class ColumnDescriptorStageTest {
+
+ @Test
+ public void perform() {
+ TranslatableQueryWrapper wrapper = new MockQueryWrapperBuilder()
+ .withDistinct(true)
+ .withMetaData(new MockQueryMetadataBuilder()
+ .withSuppressDistinct()
+ .build())
+ .build();
+ TranslatorContext context = new MockTranslatorContext(wrapper);
+
+ context.addResultNode(new EmptyNode());
+ context.addResultNode(new EmptyNode(), "key");
+ context.addResultNode(new EmptyNode(), false, Property.COUNT, "key2");
+
+ ColumnDescriptorStage stage = new ColumnDescriptorStage();
+ stage.perform(context);
+
+ assertEquals(1, context.getColumnDescriptors().size());
+ ColumnDescriptor descriptor = context.getColumnDescriptors().iterator().next();
+ assertEquals("key", descriptor.getDataRowKey());
+ }
+}
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/cayenne/blob/f6b2dac9/cayenne-server/src/test/java/org/apache/cayenne/access/translator/select/CustomColumnSetExtractorTest.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/test/java/org/apache/cayenne/access/translator/select/CustomColumnSetExtractorTest.java b/cayenne-server/src/test/java/org/apache/cayenne/access/translator/select/CustomColumnSetExtractorTest.java
new file mode 100644
index 0000000..2836ab8
--- /dev/null
+++ b/cayenne-server/src/test/java/org/apache/cayenne/access/translator/select/CustomColumnSetExtractorTest.java
@@ -0,0 +1,94 @@
+/*****************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ ****************************************************************/
+
+package org.apache.cayenne.access.translator.select;
+
+import java.sql.Types;
+import java.util.Collection;
+import java.util.Collections;
+
+import org.apache.cayenne.access.sqlbuilder.sqltree.ColumnNode;
+import org.apache.cayenne.exp.ExpressionFactory;
+import org.apache.cayenne.exp.property.BaseProperty;
+import org.apache.cayenne.exp.property.PropertyFactory;
+import org.apache.cayenne.map.DataMap;
+import org.apache.cayenne.map.DbEntity;
+import org.apache.cayenne.map.EntityResolver;
+import org.apache.cayenne.map.ObjAttribute;
+import org.apache.cayenne.map.ObjEntity;
+import org.junit.Test;
+
+import static org.hamcrest.CoreMatchers.instanceOf;
+import static org.junit.Assert.*;
+
+/**
+ * @since 4.2
+ */
+public class CustomColumnSetExtractorTest extends BaseColumnExtractorTest {
+
+ @Test
+ public void testExtractWithoutPrefix() {
+ DbEntity mockDbEntity = createMockDbEntity("mock");
+ TranslatableQueryWrapper wrapper = new MockQueryWrapperBuilder()
+ .withMetaData(new MockQueryMetadataBuilder()
+ .withDbEntity(mockDbEntity)
+ .build())
+ .build();
+ TranslatorContext context = new MockTranslatorContext(wrapper);
+
+ DataMap dataMap = new DataMap();
+ dataMap.addDbEntity(mockDbEntity);
+
+ ObjEntity entity = new ObjEntity();
+ entity.setName("mock");
+ entity.setDataMap(dataMap);
+ entity.setDbEntity(mockDbEntity);
+
+ ObjAttribute attribute = new ObjAttribute();
+ attribute.setName("not_name");
+ attribute.setDbAttributePath("name");
+ attribute.setType("my.type");
+ entity.addAttribute(attribute);
+
+ dataMap.addObjEntity(entity);
+
+ EntityResolver resolver = new EntityResolver();
+ resolver.addDataMap(dataMap);
+
+ BaseProperty<?> property0 = PropertyFactory.createBase(ExpressionFactory.dbPathExp("name"), String.class);
+ Collection<BaseProperty<?>> properties = Collections.singleton(property0);
+
+ CustomColumnSetExtractor extractor = new CustomColumnSetExtractor(context, properties);
+ extractor.extract();
+
+ assertEquals(1, context.getResultNodeList().size());
+
+ ResultNodeDescriptor descriptor0 = context.getResultNodeList().get(0);
+
+ assertSame(property0, descriptor0.getProperty());
+ assertNotNull(descriptor0.getNode());
+ assertThat(descriptor0.getNode(), instanceOf(ColumnNode.class));
+ assertFalse(descriptor0.isAggregate());
+ assertTrue(descriptor0.isInDataRow());
+ assertNotNull(descriptor0.getDbAttribute());
+ assertNull(descriptor0.getDataRowKey());
+ assertEquals(Types.VARBINARY, descriptor0.getJdbcType());
+ assertEquals("java.lang.String", descriptor0.getJavaType());
+ }
+}
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/cayenne/blob/f6b2dac9/cayenne-server/src/test/java/org/apache/cayenne/access/translator/select/DbEntityColumnExtractorTest.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/test/java/org/apache/cayenne/access/translator/select/DbEntityColumnExtractorTest.java b/cayenne-server/src/test/java/org/apache/cayenne/access/translator/select/DbEntityColumnExtractorTest.java
new file mode 100644
index 0000000..4585347
--- /dev/null
+++ b/cayenne-server/src/test/java/org/apache/cayenne/access/translator/select/DbEntityColumnExtractorTest.java
@@ -0,0 +1,121 @@
+/*****************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ ****************************************************************/
+
+package org.apache.cayenne.access.translator.select;
+
+import org.apache.cayenne.access.sqlbuilder.sqltree.ColumnNode;
+import org.apache.cayenne.map.DataMap;
+import org.apache.cayenne.map.DbEntity;
+import org.apache.cayenne.map.DbRelationship;
+import org.apache.cayenne.map.JoinType;
+import org.junit.Test;
+
+import java.sql.Types;
+
+import static org.hamcrest.CoreMatchers.instanceOf;
+import static org.junit.Assert.*;
+
+public class DbEntityColumnExtractorTest extends BaseColumnExtractorTest {
+
+ @Test
+ public void testExtractNoPrefix() {
+ TranslatableQueryWrapper wrapper = new MockQueryWrapperBuilder()
+ .withMetaData(new MockQueryMetadataBuilder()
+ .withDbEntity(createMockDbEntity("mock"))
+ .build())
+ .build();
+ TranslatorContext context = new MockTranslatorContext(wrapper);
+
+ DbEntityColumnExtractor extractor = new DbEntityColumnExtractor(context);
+ extractor.extract(null);
+
+ assertEquals(2, context.getResultNodeList().size());
+
+ ResultNodeDescriptor descriptor0 = context.getResultNodeList().get(0);
+ ResultNodeDescriptor descriptor1 = context.getResultNodeList().get(1);
+
+ assertNull(descriptor0.getProperty());
+ assertNotNull(descriptor0.getNode());
+ assertThat(descriptor0.getNode(), instanceOf(ColumnNode.class));
+ assertFalse(descriptor0.isAggregate());
+ assertTrue(descriptor0.isInDataRow());
+ assertEquals("id", descriptor0.getDataRowKey());
+ assertNotNull(descriptor0.getDbAttribute());
+ assertEquals(Types.BIGINT, descriptor0.getJdbcType());
+
+ assertNull(descriptor1.getProperty());
+ assertNotNull(descriptor1.getNode());
+ assertThat(descriptor1.getNode(), instanceOf(ColumnNode.class));
+ assertFalse(descriptor1.isAggregate());
+ assertTrue(descriptor1.isInDataRow());
+ assertNotNull(descriptor1.getDbAttribute());
+ assertEquals("name", descriptor1.getDataRowKey());
+ assertEquals(Types.VARBINARY, descriptor1.getJdbcType());
+ assertEquals("byte[]", descriptor1.getJavaType());
+ }
+
+ @Test
+ public void testExtractWithPrefix() {
+ DbEntity mockDbEntity = createMockDbEntity("mock1");
+ DbEntity mock2DbEntity = createMockDbEntity("mock2");
+ DataMap dataMap = new DataMap();
+ dataMap.addDbEntity(mockDbEntity);
+ dataMap.addDbEntity(mock2DbEntity);
+ mockDbEntity.setDataMap(dataMap);
+
+ TranslatableQueryWrapper wrapper = new MockQueryWrapperBuilder()
+ .withMetaData(new MockQueryMetadataBuilder()
+ .withDbEntity(mockDbEntity)
+ .build())
+ .build();
+ TranslatorContext context = new MockTranslatorContext(wrapper);
+
+ DbRelationship relationship = new DbRelationship();
+ relationship.setSourceEntity(mockDbEntity);
+ relationship.setTargetEntityName("mock1");
+ context.getTableTree().addJoinTable("prefix", relationship, JoinType.INNER);
+
+ DbEntityColumnExtractor extractor = new DbEntityColumnExtractor(context);
+ extractor.extract("prefix");
+
+ assertEquals(2, context.getResultNodeList().size());
+
+ ResultNodeDescriptor descriptor0 = context.getResultNodeList().get(0);
+ ResultNodeDescriptor descriptor1 = context.getResultNodeList().get(1);
+
+ assertNull(descriptor0.getProperty());
+ assertNotNull(descriptor0.getNode());
+ assertThat(descriptor0.getNode(), instanceOf(ColumnNode.class));
+ assertFalse(descriptor0.isAggregate());
+ assertTrue(descriptor0.isInDataRow());
+ assertEquals("prefix.id", descriptor0.getDataRowKey());
+ assertNotNull(descriptor0.getDbAttribute());
+ assertEquals(Types.BIGINT, descriptor0.getJdbcType());
+
+ assertNull(descriptor1.getProperty());
+ assertNotNull(descriptor1.getNode());
+ assertThat(descriptor1.getNode(), instanceOf(ColumnNode.class));
+ assertFalse(descriptor1.isAggregate());
+ assertTrue(descriptor1.isInDataRow());
+ assertNotNull(descriptor1.getDbAttribute());
+ assertEquals("prefix.name", descriptor1.getDataRowKey());
+ assertEquals(Types.VARBINARY, descriptor1.getJdbcType());
+ }
+
+}
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/cayenne/blob/f6b2dac9/cayenne-server/src/test/java/org/apache/cayenne/access/translator/select/DefaultObjectSelectTranslatorIT.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/test/java/org/apache/cayenne/access/translator/select/DefaultObjectSelectTranslatorIT.java b/cayenne-server/src/test/java/org/apache/cayenne/access/translator/select/DefaultObjectSelectTranslatorIT.java
new file mode 100644
index 0000000..04b156c
--- /dev/null
+++ b/cayenne-server/src/test/java/org/apache/cayenne/access/translator/select/DefaultObjectSelectTranslatorIT.java
@@ -0,0 +1,136 @@
+/*****************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ ****************************************************************/
+
+package org.apache.cayenne.access.translator.select;
+
+import org.apache.cayenne.access.DataContext;
+import org.apache.cayenne.dba.DbAdapter;
+import org.apache.cayenne.di.Inject;
+import org.apache.cayenne.query.SelectQuery;
+import org.apache.cayenne.testdo.testmap.Artist;
+import org.apache.cayenne.testdo.testmap.Painting;
+import org.apache.cayenne.unit.di.server.CayenneProjects;
+import org.apache.cayenne.unit.di.server.ServerCase;
+import org.apache.cayenne.unit.di.server.UseServerRuntime;
+import org.junit.Test;
+
+import static org.junit.Assert.*;
+
+/**
+ * @since 4.2
+ */
+@UseServerRuntime(CayenneProjects.TESTMAP_PROJECT)
+public class DefaultObjectSelectTranslatorIT extends ServerCase {
+
+ @Inject
+ DataContext context;
+
+ @Inject
+ DbAdapter adapter;
+
+ @Test
+ public void simpleSql() {
+ SelectQuery<Artist> select = SelectQuery.query(Artist.class);
+ DefaultSelectTranslator translator = new DefaultSelectTranslator(select, adapter, context.getEntityResolver());
+
+ String sql = translator.getSql();
+ assertTrue(sql.startsWith("SELECT "));
+ assertTrue(sql.contains("t0.ARTIST_NAME"));
+ assertTrue(sql.contains("t0.DATE_OF_BIRTH"));
+ assertTrue(sql.contains("t0.ARTIST_ID"));
+ assertTrue(sql.indexOf("FROM ARTIST t0") > sql.indexOf("t0.ARTIST_ID"));
+
+ assertEquals(0, translator.getBindings().length);
+ assertEquals(3, translator.getResultColumns().length);
+ assertEquals("ARTIST_NAME", translator.getResultColumns()[0].getDataRowKey());
+ assertEquals("DATE_OF_BIRTH", translator.getResultColumns()[1].getDataRowKey());
+ assertEquals("ARTIST_ID", translator.getResultColumns()[2].getDataRowKey());
+ assertFalse(translator.hasJoins());
+ assertFalse(translator.isSuppressingDistinct());
+ }
+
+ @Test
+ public void selectWithComplexWhere() {
+ SelectQuery<Artist> select = SelectQuery.query(Artist.class, Artist.ARTIST_NAME.eq("artist")
+ .andExp(Artist.PAINTING_ARRAY.dot(Painting.PAINTING_TITLE).eq("painting")));
+
+ DefaultSelectTranslator translator = new DefaultSelectTranslator(select, adapter, context.getEntityResolver());
+
+ String sql = translator.getSql();
+ assertTrue(sql.startsWith("SELECT DISTINCT"));
+ assertTrue(sql.contains("t0.ARTIST_NAME"));
+ assertTrue(sql.contains("t0.DATE_OF_BIRTH"));
+ assertTrue(sql.contains("t0.ARTIST_ID"));
+ assertTrue(sql.indexOf("FROM ARTIST t0") > sql.indexOf("t0.ARTIST_ID"));
+ assertTrue(sql.indexOf("JOIN PAINTING t1") > sql.indexOf("FROM ARTIST t0"));
+ assertTrue(sql.indexOf("WHERE") > sql.indexOf("JOIN PAINTING t1"));
+
+ assertEquals(2, translator.getBindings().length);
+ assertEquals("ARTIST_NAME", translator.getBindings()[0].getAttribute().getName());
+ assertEquals("PAINTING_TITLE", translator.getBindings()[1].getAttribute().getName());
+
+ assertEquals(3, translator.getResultColumns().length);
+ assertEquals("ARTIST_NAME", translator.getResultColumns()[0].getDataRowKey());
+ assertEquals("DATE_OF_BIRTH", translator.getResultColumns()[1].getDataRowKey());
+ assertEquals("ARTIST_ID", translator.getResultColumns()[2].getDataRowKey());
+
+ assertTrue(translator.hasJoins());
+ assertFalse(translator.isSuppressingDistinct());
+ }
+
+ @Test
+ public void selectWithJointPrefetch() {
+ SelectQuery<Painting> select = SelectQuery.query(Painting.class);
+ select.addPrefetch(Painting.TO_ARTIST.joint());
+
+ DefaultSelectTranslator translator = new DefaultSelectTranslator(select, adapter, context.getEntityResolver());
+
+ String sql = translator.getSql();
+
+ assertTrue(sql.startsWith("SELECT DISTINCT"));
+ assertTrue(sql.contains("t0.ESTIMATED_PRICE"));
+ assertTrue(sql.contains("t0.PAINTING_DESCRIPTION"));
+ assertTrue(sql.contains("t0.PAINTING_TITLE"));
+ assertTrue(sql.contains("t0.ARTIST_ID"));
+ assertTrue(sql.contains("t0.GALLERY_ID"));
+ assertTrue(sql.contains("t0.PAINTING_ID"));
+ assertTrue(sql.contains("t1.ARTIST_ID"));
+ assertTrue(sql.contains("t1.DATE_OF_BIRTH"));
+ assertTrue(sql.contains("t1.ARTIST_NAME"));
+ assertTrue(sql.indexOf("FROM PAINTING t0") > sql.indexOf("t1.ARTIST_NAME"));
+ assertTrue(sql.indexOf("LEFT JOIN ARTIST t1") > sql.indexOf("FROM PAINTING t0"));
+
+ assertEquals(0, translator.getBindings().length);
+
+ assertEquals(9, translator.getResultColumns().length);
+ assertEquals("ESTIMATED_PRICE", translator.getResultColumns()[0].getDataRowKey());
+ assertEquals("PAINTING_DESCRIPTION", translator.getResultColumns()[1].getDataRowKey());
+ assertEquals("PAINTING_TITLE", translator.getResultColumns()[2].getDataRowKey());
+ assertEquals("ARTIST_ID", translator.getResultColumns()[3].getDataRowKey());
+ assertEquals("GALLERY_ID", translator.getResultColumns()[4].getDataRowKey());
+ assertEquals("PAINTING_ID", translator.getResultColumns()[5].getDataRowKey());
+
+ assertEquals("toArtist.ARTIST_ID", translator.getResultColumns()[6].getDataRowKey());
+ assertEquals("toArtist.DATE_OF_BIRTH", translator.getResultColumns()[7].getDataRowKey());
+ assertEquals("toArtist.ARTIST_NAME", translator.getResultColumns()[8].getDataRowKey());
+
+ assertTrue(translator.hasJoins());
+ assertFalse(translator.isSuppressingDistinct());
+ }
+}
\ No newline at end of file
[07/11] cayenne git commit: CAY-2465 New SelectTranslator
implementation
Posted by nt...@apache.org.
http://git-wip-us.apache.org/repos/asf/cayenne/blob/f6b2dac9/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/PathProcessor.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/PathProcessor.java b/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/PathProcessor.java
new file mode 100644
index 0000000..b330608
--- /dev/null
+++ b/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/PathProcessor.java
@@ -0,0 +1,129 @@
+/*****************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ ****************************************************************/
+
+package org.apache.cayenne.access.translator.select;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Optional;
+
+import org.apache.cayenne.map.DbAttribute;
+import org.apache.cayenne.map.DbRelationship;
+import org.apache.cayenne.map.Entity;
+
+/**
+ * @since 4.2
+ */
+abstract class PathProcessor<T extends Entity> implements PathTranslationResult {
+
+ public static final char OUTER_JOIN_INDICATOR = '+';
+ public static final char SPLIT_PATH_INDICATOR = '#';
+
+ protected final Map<String, String> pathSplitAliases;
+ protected final TranslatorContext context;
+ protected final List<String> attributePaths;
+ protected final List<DbAttribute> attributes;
+ protected final StringBuilder currentDbPath;
+
+ protected boolean lastComponent;
+ protected boolean isOuterJoin;
+ protected T entity;
+ protected DbRelationship relationship;
+ protected String currentAlias;
+
+ public PathProcessor(TranslatorContext context, T entity) {
+ this.context = Objects.requireNonNull(context);
+ this.entity = Objects.requireNonNull(entity);
+ this.pathSplitAliases = context.getMetadata().getPathSplitAliases();
+ this.currentDbPath = new StringBuilder();
+ this.attributes = new ArrayList<>(1);
+ this.attributePaths = new ArrayList<>(1);
+ }
+
+ public PathTranslationResult process(String path) {
+ PathComponents components = new PathComponents(path);
+ String[] rawComponents = components.getAll();
+ for(int i=0; i<rawComponents.length; i++) {
+ String next = rawComponents[i];
+ isOuterJoin = false;
+ lastComponent = i == rawComponents.length - 1;
+ String alias = pathSplitAliases.get(next);
+ if(alias != null) {
+ currentAlias = next;
+ processAliasedAttribute(next, alias);
+ currentAlias = null;
+ } else {
+ if(next.charAt(next.length() - 1) == OUTER_JOIN_INDICATOR) {
+ isOuterJoin = true;
+ next = next.substring(0, next.length() - 1);
+ }
+ processNormalAttribute(next);
+ }
+ }
+
+ return this;
+ }
+
+ protected void addAttribute(String path, DbAttribute attribute) {
+ attributePaths.add(path);
+ attributes.add(attribute);
+ }
+
+ abstract protected void processAliasedAttribute(String next, String alias);
+
+ abstract protected void processNormalAttribute(String next);
+
+ @Override
+ public List<DbAttribute> getDbAttributes() {
+ return attributes;
+ }
+
+ @Override
+ public List<String> getAttributePaths() {
+ return attributePaths;
+ }
+
+ @Override
+ public Optional<DbRelationship> getDbRelationship() {
+ if(relationship == null) {
+ return Optional.empty();
+ }
+ return Optional.of(relationship);
+ }
+
+ @Override
+ public String getFinalPath() {
+ return currentDbPath.toString();
+ }
+
+ protected void appendCurrentPath(String nextSegment) {
+ if(currentDbPath.length() > 0) {
+ currentDbPath.append('.');
+ }
+ currentDbPath.append(nextSegment);
+ if(currentAlias != null) {
+ currentDbPath.append(SPLIT_PATH_INDICATOR).append(currentAlias);
+ }
+ if(isOuterJoin) {
+ currentDbPath.append(OUTER_JOIN_INDICATOR);
+ }
+ }
+}
http://git-wip-us.apache.org/repos/asf/cayenne/blob/f6b2dac9/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/PathTranslationResult.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/PathTranslationResult.java b/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/PathTranslationResult.java
new file mode 100644
index 0000000..f4158de
--- /dev/null
+++ b/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/PathTranslationResult.java
@@ -0,0 +1,51 @@
+/*****************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ ****************************************************************/
+
+package org.apache.cayenne.access.translator.select;
+
+import java.util.List;
+import java.util.Optional;
+
+import org.apache.cayenne.map.DbAttribute;
+import org.apache.cayenne.map.DbRelationship;
+
+/**
+ * Interface that describes result of path translation
+ *
+ * @since 4.2
+ */
+interface PathTranslationResult {
+
+ String getFinalPath();
+
+ Optional<DbRelationship> getDbRelationship();
+
+ List<DbAttribute> getDbAttributes();
+
+ List<String> getAttributePaths();
+
+ default DbAttribute getLastAttribute() {
+ return getDbAttributes().get(getDbAttributes().size() - 1);
+ }
+
+ default String getLastAttributePath() {
+ return getAttributePaths().get(getAttributePaths().size() - 1);
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/cayenne/blob/f6b2dac9/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/PathTranslator.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/PathTranslator.java b/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/PathTranslator.java
new file mode 100644
index 0000000..c3dd57f
--- /dev/null
+++ b/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/PathTranslator.java
@@ -0,0 +1,60 @@
+/*****************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ ****************************************************************/
+
+package org.apache.cayenne.access.translator.select;
+
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+import org.apache.cayenne.map.DbEntity;
+import org.apache.cayenne.map.ObjEntity;
+
+/**
+ * @since 4.2
+ */
+class PathTranslator {
+
+ private final Map<String, PathTranslationResult> objResultCache = new ConcurrentHashMap<>();
+ private final Map<String, PathTranslationResult> dbResultCache = new ConcurrentHashMap<>();
+
+ private final TranslatorContext context;
+
+ PathTranslator(TranslatorContext context) {
+ this.context = context;
+ }
+
+ PathTranslationResult translatePath(ObjEntity entity, String path, String parentPath) {
+ return objResultCache.computeIfAbsent(parentPath + '.' + entity.getName() + '.' + path,
+ (k) -> new ObjPathProcessor(context, entity, parentPath).process(path));
+ }
+
+ PathTranslationResult translatePath(ObjEntity entity, String path) {
+ return translatePath(entity, path, null);
+ }
+
+ PathTranslationResult translatePath(DbEntity entity, String path, String parentPath) {
+ return dbResultCache.computeIfAbsent(parentPath + '.' + entity.getName() + '.' + path,
+ (k) -> new DbPathProcessor(context, entity, parentPath).process(path));
+ }
+
+ PathTranslationResult translatePath(DbEntity entity, String path) {
+ return translatePath(entity, path, null);
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/cayenne/blob/f6b2dac9/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/PrefetchNodeStage.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/PrefetchNodeStage.java b/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/PrefetchNodeStage.java
new file mode 100644
index 0000000..90c0f3d
--- /dev/null
+++ b/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/PrefetchNodeStage.java
@@ -0,0 +1,128 @@
+/*****************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ ****************************************************************/
+
+package org.apache.cayenne.access.translator.select;
+
+import org.apache.cayenne.CayenneRuntimeException;
+import org.apache.cayenne.access.sqlbuilder.sqltree.Node;
+import org.apache.cayenne.exp.Expression;
+import org.apache.cayenne.exp.ExpressionFactory;
+import org.apache.cayenne.exp.parser.ASTDbPath;
+import org.apache.cayenne.map.DbAttribute;
+import org.apache.cayenne.map.DbEntity;
+import org.apache.cayenne.map.DbRelationship;
+import org.apache.cayenne.map.JoinType;
+import org.apache.cayenne.map.ObjEntity;
+import org.apache.cayenne.map.ObjRelationship;
+import org.apache.cayenne.query.PrefetchSelectQuery;
+import org.apache.cayenne.query.PrefetchTreeNode;
+import org.apache.cayenne.query.Select;
+import org.apache.cayenne.reflect.ClassDescriptor;
+
+import static org.apache.cayenne.access.sqlbuilder.SQLBuilder.table;
+
+/**
+ * @since 4.2
+ */
+class PrefetchNodeStage implements TranslationStage {
+
+ @Override
+ public void perform(TranslatorContext context) {
+ updatePrefetchNodes(context);
+ processJoint(context);
+ processPrefetchQuery(context);
+ }
+
+ private void updatePrefetchNodes(TranslatorContext context) {
+ if(context.getQuery().getPrefetchTree() == null) {
+ return;
+ }
+ // Set entity name, in case MixedConversionStrategy will be used to select objects from this query
+ // Note: all prefetch nodes will point to query root, it is not a problem until select query can't
+ // perform some sort of union or sub-queries.
+ for(PrefetchTreeNode prefetch : context.getQuery().getPrefetchTree().getChildren()) {
+ prefetch.setEntityName(context.getMetadata().getObjEntity().getName());
+ }
+ }
+
+ private void processJoint(TranslatorContext context) {
+ PrefetchTreeNode prefetch = context.getQuery().getPrefetchTree();
+ if(prefetch == null) {
+ return;
+ }
+
+ ObjEntity objEntity = context.getMetadata().getObjEntity();
+
+ for(PrefetchTreeNode node : prefetch.adjacentJointNodes()) {
+ Expression prefetchExp = ExpressionFactory.exp(node.getPath());
+ ASTDbPath dbPrefetch = (ASTDbPath) objEntity.translateToDbPath(prefetchExp);
+ final String dbPath = dbPrefetch.getPath();
+ DbEntity dbEntity = objEntity.getDbEntity();
+
+ PathComponents components = new PathComponents(dbPath);
+ StringBuilder fullPath = new StringBuilder();
+ for(String c : components.getAll()) {
+ DbRelationship rel = dbEntity.getRelationship(c);
+ if(rel == null) {
+ throw new CayenneRuntimeException("Unable to resolve path %s for entity %s", dbPath, objEntity.getName());
+ }
+ if(fullPath.length() > 0) {
+ fullPath.append('.');
+ }
+ context.getTableTree().addJoinTable("p:" + fullPath.append(c).toString(), rel, JoinType.LEFT_OUTER);
+ dbEntity = rel.getTargetEntity();
+ }
+
+ ObjRelationship targetRel = (ObjRelationship) prefetchExp.evaluate(objEntity);
+ ClassDescriptor prefetchClassDescriptor = context.getResolver().getClassDescriptor(targetRel.getTargetEntityName());
+
+ DescriptorColumnExtractor columnExtractor = new DescriptorColumnExtractor(context, prefetchClassDescriptor);
+ columnExtractor.extract("p:" + dbPath);
+ }
+ }
+
+ private void processPrefetchQuery(TranslatorContext context) {
+ Select<?> select = context.getQuery().unwrap();
+ if(!(select instanceof PrefetchSelectQuery)) {
+ return;
+ }
+
+ PathTranslator pathTranslator = context.getPathTranslator();
+ PrefetchSelectQuery<?> prefetchSelectQuery = (PrefetchSelectQuery<?>) select;
+ for(String prefetchPath: prefetchSelectQuery.getResultPaths()) {
+ ASTDbPath pathExp = (ASTDbPath) context.getMetadata().getClassDescriptor().getEntity()
+ .translateToDbPath(ExpressionFactory.exp(prefetchPath));
+
+ String path = pathExp.getPath();
+ PathTranslationResult result = pathTranslator
+ .translatePath(context.getMetadata().getDbEntity(), path);
+ result.getDbRelationship().ifPresent(r -> {
+ DbEntity targetEntity = r.getTargetEntity();
+ context.getTableTree().addJoinTable(path, r, JoinType.INNER);
+ for (DbAttribute pk : targetEntity.getPrimaryKeys()) {
+ // note that we may select a source attribute, but label it as target for simplified snapshot processing
+ String finalPath = path + '.' + pk.getName();
+ String alias = context.getTableTree().aliasForPath(path);
+ Node columnNode = table(alias).column(pk).build();
+ context.addResultNode(columnNode, finalPath).setDbAttribute(pk);
+ }
+ });
+ }
+ }
+}
http://git-wip-us.apache.org/repos/asf/cayenne/blob/f6b2dac9/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/QualifierTranslationStage.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/QualifierTranslationStage.java b/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/QualifierTranslationStage.java
new file mode 100644
index 0000000..c4d2ba1
--- /dev/null
+++ b/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/QualifierTranslationStage.java
@@ -0,0 +1,74 @@
+/*****************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ ****************************************************************/
+
+package org.apache.cayenne.access.translator.select;
+
+import org.apache.cayenne.access.sqlbuilder.sqltree.Node;
+import org.apache.cayenne.exp.Expression;
+import org.apache.cayenne.exp.parser.ASTDbPath;
+import org.apache.cayenne.exp.parser.ASTObjPath;
+import org.apache.cayenne.exp.parser.SimpleNode;
+import org.apache.cayenne.map.DbEntity;
+import org.apache.cayenne.map.ObjEntity;
+import org.apache.cayenne.reflect.ClassDescriptor;
+
+/**
+ * @since 4.2
+ */
+class QualifierTranslationStage implements TranslationStage {
+
+ @Override
+ public void perform(TranslatorContext context) {
+ QualifierTranslator translator = context.getQualifierTranslator();
+
+ Expression expression = context.getQuery().getQualifier();
+
+ // Attaching Obj entity's qualifier
+ ObjEntity entity = context.getMetadata().getObjEntity();
+ if (entity != null) {
+ ClassDescriptor descriptor = context.getMetadata().getClassDescriptor();
+ Expression entityQualifier = descriptor.getEntityInheritanceTree().qualifierForEntityAndSubclasses();
+ if (entityQualifier != null) {
+ expression = expression == null ? entityQualifier : expression.andExp(entityQualifier);
+ }
+ }
+
+ // Attaching root Db entity's qualifier
+ DbEntity dbEntity = context.getMetadata().getDbEntity();
+ if (dbEntity != null) {
+ Expression dbQualifier = dbEntity.getQualifier();
+ if (dbQualifier != null) {
+ dbQualifier = dbQualifier.transform(node -> {
+ if (node instanceof ASTObjPath) {
+ return new ASTDbPath(((SimpleNode) node).getOperand(0));
+ }
+ return node;
+ });
+
+ expression = expression == null ? dbQualifier : expression.andExp(dbQualifier);
+ }
+ }
+
+ Node qualifierNode = translator.translate(expression);
+
+ if(qualifierNode != null) {
+ context.getSelectBuilder().where(qualifierNode);
+ }
+ }
+}
http://git-wip-us.apache.org/repos/asf/cayenne/blob/f6b2dac9/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/QualifierTranslator.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/QualifierTranslator.java b/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/QualifierTranslator.java
index 3795976..d48a53f 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/QualifierTranslator.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/QualifierTranslator.java
@@ -19,672 +19,394 @@
package org.apache.cayenne.access.translator.select;
-import java.io.IOException;
-import java.util.Arrays;
+import java.util.ArrayDeque;
+import java.util.Collection;
+import java.util.Deque;
+import java.util.HashSet;
import java.util.Iterator;
-import java.util.List;
-import java.util.function.Function;
+import java.util.Set;
import org.apache.cayenne.CayenneRuntimeException;
import org.apache.cayenne.ObjectId;
import org.apache.cayenne.Persistent;
-import org.apache.cayenne.dba.TypesMapping;
+import org.apache.cayenne.access.sqlbuilder.ExpressionNodeBuilder;
+import org.apache.cayenne.access.sqlbuilder.ValueNodeBuilder;
+import org.apache.cayenne.access.sqlbuilder.sqltree.BetweenNode;
+import org.apache.cayenne.access.sqlbuilder.sqltree.BitwiseNotNode;
+import org.apache.cayenne.access.sqlbuilder.sqltree.EmptyNode;
+import org.apache.cayenne.access.sqlbuilder.sqltree.EqualNode;
+import org.apache.cayenne.access.sqlbuilder.sqltree.FunctionNode;
+import org.apache.cayenne.access.sqlbuilder.sqltree.InNode;
+import org.apache.cayenne.access.sqlbuilder.sqltree.LikeNode;
+import org.apache.cayenne.access.sqlbuilder.sqltree.Node;
+import org.apache.cayenne.access.sqlbuilder.sqltree.NotEqualNode;
+import org.apache.cayenne.access.sqlbuilder.sqltree.NotNode;
+import org.apache.cayenne.access.sqlbuilder.sqltree.OpExpressionNode;
+import org.apache.cayenne.access.sqlbuilder.sqltree.TextNode;
import org.apache.cayenne.exp.Expression;
import org.apache.cayenne.exp.TraversalHandler;
import org.apache.cayenne.exp.parser.ASTDbPath;
-import org.apache.cayenne.exp.parser.ASTExtract;
+import org.apache.cayenne.exp.parser.ASTFullObject;
import org.apache.cayenne.exp.parser.ASTFunctionCall;
import org.apache.cayenne.exp.parser.ASTObjPath;
+import org.apache.cayenne.exp.parser.ASTSubquery;
import org.apache.cayenne.exp.parser.PatternMatchNode;
import org.apache.cayenne.exp.parser.SimpleNode;
+import org.apache.cayenne.exp.property.BaseProperty;
import org.apache.cayenne.map.DbAttribute;
-import org.apache.cayenne.map.DbRelationship;
-import org.apache.cayenne.map.JoinType;
-import org.apache.cayenne.map.ObjEntity;
-import org.apache.cayenne.query.Query;
-import org.apache.cayenne.query.SelectQuery;
-import org.apache.cayenne.reflect.ClassDescriptor;
+
+import static org.apache.cayenne.access.sqlbuilder.SQLBuilder.*;
+import static org.apache.cayenne.exp.Expression.*;
/**
- * Translates query qualifier to SQL. Used as a helper class by query
- * translators.
+ * @since 4.2
*/
-public class QualifierTranslator extends QueryAssemblerHelper implements TraversalHandler {
-
- protected DataObjectMatchTranslator objectMatchTranslator;
- protected boolean matchingObject;
- protected boolean caseInsensitive;
-
- /**
- * @since 4.0
- */
- protected boolean useAliasForExpressions;
-
- /**
- * @since 4.0
- */
- protected Expression waitingForEndNode;
-
- /**
- * @since 4.0
- */
- protected Expression qualifier;
-
- public QualifierTranslator(QueryAssembler queryAssembler) {
- super(queryAssembler);
-
- caseInsensitive = false;
- }
-
- /**
- * Translates query qualifier to SQL WHERE clause. Qualifier is obtained
- * from the parent queryAssembler.
- *
- * @since 3.0
- */
- @Override
- protected void doAppendPart() {
- doAppendPart(extractQualifier());
- }
-
- public void setCaseInsensitive(boolean caseInsensitive) {
- this.caseInsensitive = caseInsensitive;
- }
-
- /**
- * Explicitly set qualifier.
- * It will be used instead of extracting qualifier from the query itself.
- * @since 4.0
- */
- public void setQualifier(Expression qualifier) {
- this.qualifier = qualifier;
- }
-
- /**
- * @since 4.0
- */
- public void setUseAliasForExpressions(boolean useAliasForExpressions) {
- this.useAliasForExpressions = useAliasForExpressions;
- }
-
- /**
- * Translates query qualifier to SQL WHERE clause. Qualifier is a method
- * parameter.
- *
- * @since 3.0
- */
- protected void doAppendPart(Expression rootNode) {
- if (rootNode == null) {
- return;
- }
- rootNode.traverse(this);
- }
-
- protected Expression extractQualifier() {
- // if additional qualifier is set, use it
- if(this.qualifier != null) {
- return this.qualifier;
- }
-
- Query q = queryAssembler.getQuery();
-
- Expression qualifier = ((SelectQuery<?>) q).getQualifier();
-
- // append Entity qualifiers, taking inheritance into account
- ObjEntity entity = getObjEntity();
-
- if (entity != null) {
-
- ClassDescriptor descriptor = queryAssembler.getEntityResolver().getClassDescriptor(entity.getName());
- Expression entityQualifier = descriptor.getEntityInheritanceTree().qualifierForEntityAndSubclasses();
- if (entityQualifier != null) {
- qualifier = (qualifier != null) ? qualifier.andExp(entityQualifier) : entityQualifier;
- }
- }
-
- // Attaching root Db entity's qualifier
- if (getDbEntity() != null) {
- Expression dbQualifier = getDbEntity().getQualifier();
- if (dbQualifier != null) {
- dbQualifier = dbQualifier.transform(new DbEntityQualifierTransformer());
-
- qualifier = qualifier == null ? dbQualifier : qualifier.andExp(dbQualifier);
- }
- }
-
- return qualifier;
- }
-
- /**
- * Called before processing an expression to initialize
- * objectMatchTranslator if needed.
- */
- protected void detectObjectMatch(Expression exp) {
- // On demand initialization of
- // objectMatchTranslator is not possible since there may be null
- // object values that would not allow to detect the need for
- // such translator in the right time (e.g.: null = dbpath)
-
- matchingObject = false;
-
- if (exp.getOperandCount() != 2) {
- // only binary expressions are supported
- return;
- }
-
- // check if there are DataObjects among direct children of the
- // Expression
- for (int i = 0; i < 2; i++) {
- Object op = exp.getOperand(i);
- if (op instanceof Persistent || op instanceof ObjectId) {
- matchingObject = true;
-
- if (objectMatchTranslator == null) {
- objectMatchTranslator = new DataObjectMatchTranslator();
- } else {
- objectMatchTranslator.reset();
- }
- break;
- }
- }
- }
-
- protected void appendObjectMatch() throws IOException {
- if (!matchingObject || objectMatchTranslator == null) {
- throw new IllegalStateException("An invalid attempt to append object match.");
- }
-
- // turn off special handling, so that all the methods behave as a
- // superclass's
- // impl.
- matchingObject = false;
-
- boolean first = true;
-
- DbRelationship relationship = objectMatchTranslator.getRelationship();
- if (!relationship.isToMany() && !relationship.isToPK()) {
- queryAssembler.dbRelationshipAdded(relationship, JoinType.INNER, objectMatchTranslator.getJoinSplitAlias());
- }
-
- Iterator<String> it = objectMatchTranslator.keys();
- while (it.hasNext()) {
- if (first) {
- first = false;
- } else {
- out.append(" AND ");
- }
-
- String key = it.next();
- DbAttribute attr = objectMatchTranslator.getAttribute(key);
- Object val = objectMatchTranslator.getValue(key);
-
- processColumn(attr);
- out.append(objectMatchTranslator.getOperation());
- appendLiteral(val, attr, objectMatchTranslator.getExpression());
- }
-
- objectMatchTranslator.reset();
- }
-
- @Override
- public void finishedChild(Expression node, int childIndex, boolean hasMoreChildren) {
-
- if(waitingForEndNode != null) {
- return;
- }
-
- if (!hasMoreChildren) {
- return;
- }
-
- Appendable out = (matchingObject) ? new StringBuilder() : this.out;
-
- try {
- switch (node.getType()) {
- case Expression.AND:
- out.append(" AND ");
- break;
- case Expression.OR:
- out.append(" OR ");
- break;
- case Expression.EQUAL_TO:
- // translate NULL as IS NULL
- if (childIndex == 0 && node.getOperandCount() == 2 && node.getOperand(1) == null) {
- out.append(" IS ");
- } else {
- out.append(" = ");
- }
- break;
- case Expression.NOT_EQUAL_TO:
- // translate NULL as IS NOT NULL
- if (childIndex == 0 && node.getOperandCount() == 2 && node.getOperand(1) == null) {
- out.append(" IS NOT ");
- } else {
- out.append(" <> ");
- }
- break;
- case Expression.LESS_THAN:
- out.append(" < ");
- break;
- case Expression.GREATER_THAN:
- out.append(" > ");
- break;
- case Expression.LESS_THAN_EQUAL_TO:
- out.append(" <= ");
- break;
- case Expression.GREATER_THAN_EQUAL_TO:
- out.append(" >= ");
- break;
- case Expression.IN:
- out.append(" IN ");
- break;
- case Expression.NOT_IN:
- out.append(" NOT IN ");
- break;
- case Expression.LIKE:
- out.append(" LIKE ");
- break;
- case Expression.NOT_LIKE:
- out.append(" NOT LIKE ");
- break;
- case Expression.LIKE_IGNORE_CASE:
- if (caseInsensitive) {
- out.append(" LIKE ");
- } else {
- out.append(") LIKE UPPER(");
- }
- break;
- case Expression.NOT_LIKE_IGNORE_CASE:
- if (caseInsensitive) {
- out.append(" NOT LIKE ");
- } else {
- out.append(") NOT LIKE UPPER(");
- }
- break;
- case Expression.ADD:
- out.append(" + ");
- break;
- case Expression.SUBTRACT:
- out.append(" - ");
- break;
- case Expression.MULTIPLY:
- out.append(" * ");
- break;
- case Expression.DIVIDE:
- out.append(" / ");
- break;
- case Expression.BETWEEN:
- if (childIndex == 0) {
- out.append(" BETWEEN ");
- } else if (childIndex == 1) {
- out.append(" AND ");
- }
- break;
- case Expression.NOT_BETWEEN:
- if (childIndex == 0) {
- out.append(" NOT BETWEEN ");
- } else if (childIndex == 1) {
- out.append(" AND ");
- }
- break;
- case Expression.BITWISE_OR:
- out.append(" ").append(operandForBitwiseOr()).append(" ");
- break;
- case Expression.BITWISE_AND:
- out.append(" ").append(operandForBitwiseAnd()).append(" ");
- break;
- case Expression.BITWISE_XOR:
- out.append(" ").append(operandForBitwiseXor()).append(" ");
- break;
- case Expression.BITWISE_LEFT_SHIFT:
- out.append(" ").append(operandForBitwiseLeftShift()).append(" ");
- break;
- case Expression.BITWISE_RIGHT_SHIFT:
- out.append(" ").append(operandForBitwiseRightShift()).append("");
- break;
- }
- } catch (IOException ioex) {
- throw new CayenneRuntimeException("Error appending content", ioex);
- }
-
- if (matchingObject) {
- objectMatchTranslator.setOperation(out.toString());
- objectMatchTranslator.setExpression(node);
- }
- }
-
- /**
- * @since 3.1
- */
- protected String operandForBitwiseNot() {
- return "~";
- }
-
- /**
- * @since 3.1
- */
- protected String operandForBitwiseOr() {
- return "|";
- }
-
- /**
- * @since 3.1
- */
- protected String operandForBitwiseAnd() {
- return "&";
- }
-
- /**
- * @since 3.1
- */
- protected String operandForBitwiseXor() {
- return "^";
- }
-
- /**
- * @since 4.0
- */
- protected String operandForBitwiseLeftShift() {
- return "<<";
- }
-
- /**
- * @since 4.0
- */
- protected String operandForBitwiseRightShift() {
- return ">>";
- }
-
- @Override
- public void startNode(Expression node, Expression parentNode) {
-
- if(waitingForEndNode != null) {
- return;
- }
-
- if(useAliasForExpressions) {
- String alias = queryAssembler.getAliasForExpression(node);
- if(alias != null) {
- out.append(alias);
- waitingForEndNode = node;
- return;
- }
- }
-
- boolean parenthesisNeeded = parenthesisNeeded(node, parentNode);
-
- if(node.getType() == Expression.FUNCTION_CALL) {
- if(node instanceof ASTExtract) {
- appendExtractFunction((ASTExtract) node);
- } else {
- appendFunction((ASTFunctionCall) node);
- }
- if(parenthesisNeeded) {
- out.append("(");
- }
- return;
- }
-
- if(node.getType() == Expression.FULL_OBJECT && parentNode != null) {
- throw new CayenneRuntimeException("Expression is not supported in where clause.");
- }
-
- int count = node.getOperandCount();
-
- if (count == 2) {
- // binary nodes are the only ones that currently require this
- detectObjectMatch(node);
- }
-
- if (parenthesisNeeded) {
- out.append('(');
- }
-
- if (count == 0) {
- // not all databases handle true/false
- if (node.getType() == Expression.TRUE) {
- out.append("1 = 1");
- } else if (node.getType() == Expression.FALSE) {
- out.append("1 = 0");
- } else if (node.getType() == Expression.ASTERISK) {
- out.append("*");
- }
- }
-
- if (count == 1) {
- if (node.getType() == Expression.NEGATIVE) {
- out.append('-');
- } else if (node.getType() == Expression.NOT) {
- out.append("NOT ");
- } else if (node.getType() == Expression.BITWISE_NOT) {
- out.append(operandForBitwiseNot());
- }
- } else if ((node.getType() == Expression.LIKE_IGNORE_CASE || node.getType() == Expression.NOT_LIKE_IGNORE_CASE)
- && !caseInsensitive) {
- out.append("UPPER(");
- }
-
- }
-
- /**
- * @since 1.1
- */
- @Override
- public void endNode(Expression node, Expression parentNode) {
-
- if(waitingForEndNode != null) {
- if(node == waitingForEndNode) {
- waitingForEndNode = null;
- }
- return;
- }
-
- try {
- // check if we need to use objectMatchTranslator to finish building the expression
- if (node.getOperandCount() == 2 && matchingObject) {
- appendObjectMatch();
- }
-
- boolean parenthesisNeeded = parenthesisNeeded(node, parentNode);
- boolean likeIgnoreCase = (node.getType() == Expression.LIKE_IGNORE_CASE || node.getType() == Expression.NOT_LIKE_IGNORE_CASE);
- boolean isPatternMatchNode = PatternMatchNode.class.isAssignableFrom(node.getClass());
-
- // closing UPPER parenthesis
- if (likeIgnoreCase && !caseInsensitive) {
- out.append(')');
- }
-
- if (isPatternMatchNode) {
- appendLikeEscapeCharacter((PatternMatchNode) node);
- }
-
- // clean up trailing comma in function argument list
- if(node.getType() == Expression.FUNCTION_CALL) {
- clearLastFunctionArgDivider((ASTFunctionCall)node);
- }
-
- // closing LIKE parenthesis
- if (parenthesisNeeded) {
- out.append(')');
- }
-
- // if inside function call, put comma between arguments
- if(parentNode != null && parentNode.getType() == Expression.FUNCTION_CALL) {
- appendFunctionArgDivider((ASTFunctionCall) parentNode);
- }
- } catch (IOException ioex) {
- throw new CayenneRuntimeException("Error appending content", ioex);
- }
- }
-
- @Override
- public void objectNode(Object leaf, Expression parentNode) {
- if(waitingForEndNode != null) {
- return;
- }
-
- try {
- switch (parentNode.getType()) {
- case Expression.OBJ_PATH:
- appendObjPath(parentNode);
- break;
- case Expression.DB_PATH:
- appendDbPath(parentNode);
- break;
- case Expression.LIST:
- appendList(parentNode, paramsDbType(parentNode));
- break;
- case Expression.FUNCTION_CALL:
- appendFunctionArg(leaf, (ASTFunctionCall)parentNode);
- break;
- default:
- appendLiteral(leaf, paramsDbType(parentNode), parentNode);
- }
- } catch (IOException ioex) {
- throw new CayenneRuntimeException("Error appending content", ioex);
- }
- }
-
- protected boolean parenthesisNeeded(Expression node, Expression parentNode) {
- if (node.getType() == Expression.FUNCTION_CALL) {
- return ((ASTFunctionCall)node).needParenthesis();
- }
-
- if (parentNode == null) {
- return false;
- }
-
- // only unary expressions can go w/o parenthesis
- if (node.getOperandCount() > 1) {
- return true;
- }
-
- if (node.getType() == Expression.OBJ_PATH
- || node.getType() == Expression.DB_PATH
- || node.getType() == Expression.ASTERISK) {
- return false;
- }
-
- return true;
- }
-
- private final void appendList(Expression listExpr, DbAttribute paramDesc) throws IOException {
- Iterator<?> it;
- Object list = listExpr.getOperand(0);
- if (list instanceof List) {
- it = ((List<?>) list).iterator();
- } else if (list instanceof Object[]) {
- it = Arrays.asList((Object[]) list).iterator();
- } else {
- String className = (list != null) ? list.getClass().getName() : "<null>";
- throw new IllegalArgumentException("Unsupported type for the list expressions: " + className);
- }
-
- // process first element outside the loop
- // (unroll loop to avoid condition checking
- if (it.hasNext()) {
- appendLiteral(it.next(), paramDesc, listExpr);
- } else {
- return;
- }
-
- while (it.hasNext()) {
- out.append(", ");
- appendLiteral(it.next(), paramDesc, listExpr);
- }
- }
-
- @Override
- protected void appendLiteral(Object val, DbAttribute attr, Expression parentExpression) throws IOException {
-
- if (!matchingObject) {
- super.appendLiteral(val, attr, parentExpression);
- } else if (val == null || (val instanceof Persistent)) {
- objectMatchTranslator.setDataObject((Persistent) val);
- } else if (val instanceof ObjectId) {
- objectMatchTranslator.setObjectId((ObjectId) val);
- } else {
- throw new IllegalArgumentException("Attempt to use literal other than DataObject during object match.");
- }
- }
-
- @Override
- protected void processRelTermination(DbRelationship rel, JoinType joinType, String joinSplitAlias) {
-
- if (!matchingObject) {
- super.processRelTermination(rel, joinType, joinSplitAlias);
- } else {
- if (rel.isToMany()) {
- // append joins
- queryAssembler.dbRelationshipAdded(rel, joinType, joinSplitAlias);
- }
- objectMatchTranslator.setRelationship(rel, joinSplitAlias);
- }
- }
-
- /**
- * Append function name to result SQL
- * Override this method to rename or skip function if generic name isn't supported on target DB.
- * @since 4.0
- */
- protected void appendFunction(ASTFunctionCall functionExpression) {
- out.append(functionExpression.getFunctionName());
- }
-
- /**
- * Special case for extract date/time parts functions as they have many variants
- * @since 4.0
- */
- protected void appendExtractFunction(ASTExtract functionExpression) {
- appendFunction(functionExpression);
- }
-
- /**
- * Append scalar argument of a function call
- * Used only for values stored in ASTScalar other
- * expressions appended in objectNode() method
- *
- * @since 4.0
- */
- protected void appendFunctionArg(Object value, ASTFunctionCall functionExpression) throws IOException {
- // Create fake DbAttribute to pass argument info down to bind it to SQL prepared statement
- DbAttribute dbAttrForArg = new DbAttribute();
- dbAttrForArg.setType(TypesMapping.getSqlTypeByJava(value.getClass()));
- super.appendLiteral(value, dbAttrForArg, functionExpression);
- appendFunctionArgDivider(functionExpression);
- }
-
- /**
- * Append divider between function arguments.
- * In overriding methods can be replaced e.g. for " || " for CONCAT operation
- * @since 4.0
- */
- protected void appendFunctionArgDivider(ASTFunctionCall functionExpression) {
- out.append(", ");
- }
-
- /**
- * Clear last divider as we currently don't now position of argument until parent element is ended.
- * @since 4.0
- */
- protected void clearLastFunctionArgDivider(ASTFunctionCall functionExpression) {
- if(functionExpression.getOperandCount() > 0) {
- out.delete(out.length() - 2, out.length());
- }
- }
-
- /**
- * Class to translate DB Entity qualifiers annotation to Obj-entity
- * qualifiers annotation This is done by changing all Obj-paths to Db-paths
- * and rejecting all original Db-paths
- */
- class DbEntityQualifierTransformer implements Function<Object, Object> {
-
- public Object apply(Object input) {
- if (input instanceof ASTObjPath) {
- return new ASTDbPath(((SimpleNode) input).getOperand(0));
- }
- return input;
- }
- }
+class QualifierTranslator implements TraversalHandler {
+
+ private final TranslatorContext context;
+ private final PathTranslator pathTranslator;
+ private final Set<Object> expressionsToSkip;
+ private final Deque<Node> nodeStack;
+
+ private Node currentNode;
+
+ QualifierTranslator(TranslatorContext context) {
+ this.context = context;
+ this.pathTranslator = context.getPathTranslator();
+ this.expressionsToSkip = new HashSet<>();
+ this.nodeStack = new ArrayDeque<>();
+ }
+
+ Node translate(BaseProperty<?> property) {
+ if(property == null) {
+ return null;
+ }
+
+ Node result = translate(property.getExpression());
+ if(property.getAlias() != null) {
+ return aliased(result, property.getAlias()).build();
+ }
+ return result;
+ }
+
+ Node translate(Expression qualifier) {
+ if(qualifier == null) {
+ return null;
+ }
+
+ Node rootNode = new EmptyNode();
+ expressionsToSkip.clear();
+ boolean hasCurrentNode = currentNode != null;
+ if(hasCurrentNode) {
+ nodeStack.push(currentNode);
+ }
+
+ currentNode = rootNode;
+ qualifier.traverse(this);
+
+ if(hasCurrentNode) {
+ currentNode = nodeStack.pop();
+ } else {
+ currentNode = null;
+ }
+
+ if(rootNode.getChildrenCount() == 1) {
+ // trim empty node
+ Node child = rootNode.getChild(0);
+ child.setParent(null);
+ return child;
+ }
+ return rootNode;
+ }
+
+ @Override
+ public void startNode(Expression node, Expression parentNode) {
+ if(expressionsToSkip.contains(node) || expressionsToSkip.contains(parentNode)) {
+ return;
+ }
+ Node nextNode = expressionNodeToSqlNode(node, parentNode);
+ if(nextNode == null) {
+ return;
+ }
+ currentNode.addChild(nextNode);
+ nextNode.setParent(currentNode);
+ currentNode = nextNode;
+ }
+
+ private Node expressionNodeToSqlNode(Expression node, Expression parentNode) {
+ switch (node.getType()) {
+ case NOT_IN:
+ return new InNode(true);
+ case IN:
+ return new InNode(false);
+ case NOT_BETWEEN:
+ case BETWEEN:
+ return new BetweenNode(node.getType() == NOT_BETWEEN);
+ case NOT:
+ return new NotNode();
+ case BITWISE_NOT:
+ return new BitwiseNotNode();
+ case EQUAL_TO:
+ return new EqualNode();
+ case NOT_EQUAL_TO:
+ return new NotEqualNode();
+
+ case LIKE:
+ case NOT_LIKE:
+ case LIKE_IGNORE_CASE:
+ case NOT_LIKE_IGNORE_CASE:
+ PatternMatchNode patternMatchNode = (PatternMatchNode)node;
+ boolean not = node.getType() == NOT_LIKE || node.getType() == NOT_LIKE_IGNORE_CASE;
+ return new LikeNode(patternMatchNode.isIgnoringCase(), not, patternMatchNode.getEscapeChar());
+
+ case OBJ_PATH:
+ String path = (String)node.getOperand(0);
+ PathTranslationResult result = pathTranslator.translatePath(context.getMetadata().getObjEntity(), path);
+ return processPathTranslationResult(node, parentNode, result);
+
+ case DB_PATH:
+ String dbPath = (String)node.getOperand(0);
+ PathTranslationResult dbResult = pathTranslator.translatePath(context.getMetadata().getDbEntity(), dbPath);
+ return processPathTranslationResult(node, parentNode, dbResult);
+
+ case FUNCTION_CALL:
+ ASTFunctionCall functionCall = (ASTFunctionCall)node;
+ return function(functionCall.getFunctionName()).build();
+
+ case ADD:
+ case SUBTRACT:
+ case MULTIPLY:
+ case DIVIDE:
+ case NEGATIVE:
+ case BITWISE_AND:
+ case BITWISE_LEFT_SHIFT:
+ case BITWISE_OR:
+ case BITWISE_RIGHT_SHIFT:
+ case BITWISE_XOR:
+ case OR:
+ case AND:
+ case LESS_THAN:
+ case LESS_THAN_EQUAL_TO:
+ case GREATER_THAN:
+ case GREATER_THAN_EQUAL_TO:
+ return new OpExpressionNode(expToStr(node.getType()));
+
+ case TRUE:
+ case FALSE:
+ case ASTERISK:
+ return new TextNode(' ' + expToStr(node.getType()));
+
+ case EXISTS:
+ return new FunctionNode("EXISTS", null, false);
+
+ case SUBQUERY:
+ ASTSubquery subquery = (ASTSubquery)node;
+ DefaultSelectTranslator translator = new DefaultSelectTranslator(subquery.getQuery(), context);
+ translator.translate();
+ return translator.getContext().getSelectBuilder().build();
+
+ case ENCLOSING_OBJECT:
+ // Translate via parent context's translator
+ Expression expression = (Expression) node.getOperand(0);
+ if(context.getParentContext() == null) {
+ throw new CayenneRuntimeException("Unable to translate qualifier, no parent context to use for expression " + node);
+ }
+ expressionsToSkip.add(expression);
+ return context.getParentContext().getQualifierTranslator().translate(expression);
+
+ case FULL_OBJECT:
+ ASTFullObject fullObject = (ASTFullObject)node;
+ if(fullObject.getOperandCount() == 0) {
+ Collection<DbAttribute> dbAttributes = context.getMetadata().getDbEntity().getPrimaryKeys();
+ if(dbAttributes.size() > 1) {
+ throw new CayenneRuntimeException("Unable to translate reference on entity with more than one PK.");
+ }
+ DbAttribute attribute = dbAttributes.iterator().next();
+ String alias = context.getTableTree().aliasForAttributePath(attribute.getName());
+ return table(alias).column(attribute).build();
+ } else {
+ return null;
+ }
+ }
+
+ return null;
+ }
+
+ private Node processPathTranslationResult(Expression node, Expression parentNode, PathTranslationResult result) {
+ if(result.getDbRelationship().isPresent()
+ && result.getDbAttributes().size() > 1
+ && result.getDbRelationship().get().getTargetEntity().getPrimaryKeys().size() > 1) {
+ return createMultiAttributeMatch(node, parentNode, result);
+ } else if(result.getDbAttributes().isEmpty()) {
+ return new EmptyNode();
+ } else {
+ String alias = context.getTableTree().aliasForPath(result.getLastAttributePath());
+ return table(alias).column(result.getLastAttribute()).build();
+ }
+ }
+
+ private Node createMultiAttributeMatch(Expression node, Expression parentNode, PathTranslationResult result) {
+ ObjectId objectId = null;
+ expressionsToSkip.add(node);
+ expressionsToSkip.add(parentNode);
+
+ int siblings = parentNode.getOperandCount();
+ for(int i=0; i<siblings; i++) {
+ Object operand = parentNode.getOperand(i);
+ if(node == operand) {
+ continue;
+ }
+
+ if(operand instanceof Persistent) {
+ objectId = ((Persistent) operand).getObjectId();
+ break;
+ } else if(operand instanceof ObjectId) {
+ objectId = (ObjectId)operand;
+ break;
+ } else if(operand instanceof ASTObjPath) {
+ // TODO: support comparision of multi attribute ObjPath with other multi attribute ObjPath
+ throw new UnsupportedOperationException("Comparision of multiple attributes not supported for ObjPath");
+ }
+ }
+
+ if(objectId == null) {
+ throw new CayenneRuntimeException("Multi attribute ObjPath isn't matched with valid value. " +
+ "List or Persistent object required.");
+ }
+
+ ExpressionNodeBuilder expressionNodeBuilder = null;
+ ExpressionNodeBuilder eq;
+
+ Iterator<DbAttribute> pkIt = result.getDbRelationship().get().getTargetEntity().getPrimaryKeys().iterator();
+ int count = result.getDbAttributes().size();
+
+ for(int i=0; i<count; i++) {
+ String path = result.getAttributePaths().get(i);
+ DbAttribute attribute = result.getDbAttributes().get(i);
+ DbAttribute pk = pkIt.next();
+ String alias = context.getTableTree().aliasForPath(path);
+ Object nextValue = objectId.getIdSnapshot().get(pk.getName());
+ eq = table(alias).column(attribute).eq(value(nextValue));
+ if (expressionNodeBuilder == null) {
+ expressionNodeBuilder = eq;
+ } else {
+ expressionNodeBuilder = expressionNodeBuilder.and(eq);
+ }
+ }
+
+ return expressionNodeBuilder.build();
+ }
+
+ @Override
+ public void endNode(Expression node, Expression parentNode) {
+ if(expressionsToSkip.contains(node) || expressionsToSkip.contains(parentNode)) {
+ return;
+ }
+ if(currentNode.getParent() != null) {
+ currentNode = currentNode.getParent();
+ }
+ }
+
+ @Override
+ public void objectNode(Object leaf, Expression parentNode) {
+ if(expressionsToSkip.contains(parentNode)) {
+ return;
+ }
+ if(parentNode.getType() == Expression.OBJ_PATH || parentNode.getType() == Expression.DB_PATH) {
+ return;
+ }
+
+ ValueNodeBuilder valueNodeBuilder = value(leaf).attribute(findDbAttribute(parentNode));
+ if(parentNode.getType() == Expression.LIST) {
+ valueNodeBuilder.array(true);
+ }
+ Node nextNode = valueNodeBuilder.build();
+
+ currentNode.addChild(nextNode);
+ nextNode.setParent(currentNode);
+ }
+
+ protected DbAttribute findDbAttribute(Expression node) {
+ int len = node.getOperandCount();
+ if (len != 2) {
+ if (node instanceof SimpleNode) {
+ Expression parent = (Expression) ((SimpleNode) node).jjtGetParent();
+ if (parent != null) {
+ node = parent;
+ } else {
+ return null;
+ }
+ }
+ }
+
+ PathTranslationResult result = null;
+ for(int i=0; i<node.getOperandCount(); i++) {
+ Object op = node.getOperand(i);
+ if(op instanceof ASTObjPath) {
+ result = pathTranslator.translatePath(context.getMetadata().getObjEntity(), ((ASTObjPath) op).getPath());
+ break;
+ } else if(op instanceof ASTDbPath) {
+ result = pathTranslator.translatePath(context.getMetadata().getDbEntity(), ((ASTDbPath) op).getPath());
+ break;
+ }
+ }
+
+ if(result == null) {
+ return null;
+ }
+
+ return result.getLastAttribute();
+ }
+
+ @Override
+ public void finishedChild(Expression node, int childIndex, boolean hasMoreChildren) {
+ }
+
+ private String expToStr(int type) {
+ switch (type) {
+ case AND:
+ return "AND";
+ case OR:
+ return "OR";
+ case LESS_THAN:
+ return "<";
+ case LESS_THAN_EQUAL_TO:
+ return "<=";
+ case GREATER_THAN:
+ return ">";
+ case GREATER_THAN_EQUAL_TO:
+ return ">=";
+ case ADD:
+ return "+";
+ case SUBTRACT:
+ return "-";
+ case MULTIPLY:
+ return "*";
+ case DIVIDE:
+ return "/";
+ case NEGATIVE:
+ return "-";
+ case BITWISE_AND:
+ return "&";
+ case BITWISE_OR:
+ return "|";
+ case BITWISE_XOR:
+ return "^";
+ case BITWISE_NOT:
+ return "!";
+ case BITWISE_LEFT_SHIFT:
+ return "<<";
+ case BITWISE_RIGHT_SHIFT:
+ return ">>";
+ case TRUE:
+ return "1=1";
+ case FALSE:
+ return "1=0";
+ case ASTERISK:
+ return "*";
+ default:
+ return "{other}";
+ }
+ }
+
}
http://git-wip-us.apache.org/repos/asf/cayenne/blob/f6b2dac9/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/QueryAssembler.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/QueryAssembler.java b/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/QueryAssembler.java
deleted file mode 100644
index 3293168..0000000
--- a/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/QueryAssembler.java
+++ /dev/null
@@ -1,197 +0,0 @@
-/*****************************************************************
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License. You may obtain a copy of the License at
- * <p/>
- * http://www.apache.org/licenses/LICENSE-2.0
- * <p/>
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- ****************************************************************/
-
-package org.apache.cayenne.access.translator.select;
-
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Map;
-
-import org.apache.cayenne.access.translator.DbAttributeBinding;
-import org.apache.cayenne.access.types.ExtendedType;
-import org.apache.cayenne.dba.DbAdapter;
-import org.apache.cayenne.exp.Expression;
-import org.apache.cayenne.map.DbAttribute;
-import org.apache.cayenne.map.DbRelationship;
-import org.apache.cayenne.map.EntityResolver;
-import org.apache.cayenne.map.JoinType;
-import org.apache.cayenne.query.Query;
-import org.apache.cayenne.query.QueryMetadata;
-
-/**
- * Abstract superclass of Query translators.
- */
-public abstract class QueryAssembler {
-
- protected Query query;
- protected QueryMetadata queryMetadata;
- protected boolean translated;
- protected String sql;
- protected DbAdapter adapter;
- protected EntityResolver entityResolver;
- protected List<DbAttributeBinding> bindings;
- /**
- * @since 4.0
- */
- protected AddBindingListener addBindingListener;
-
- /**
- * @since 4.0
- */
- public QueryAssembler(Query query, DbAdapter adapter, EntityResolver entityResolver) {
- this.entityResolver = entityResolver;
- this.adapter = adapter;
- this.query = query;
- this.queryMetadata = query.getMetaData(entityResolver);
- this.bindings = new ArrayList<>();
- }
-
- /**
- * Returns aliases for the path splits defined in the query.
- *
- * @since 3.0
- */
- protected Map<String, String> getPathAliases() {
- return queryMetadata.getPathSplitAliases();
- }
-
- public EntityResolver getEntityResolver() {
- return entityResolver;
- }
-
- public DbAdapter getAdapter() {
- return adapter;
- }
-
- /**
- * Returns query object being processed.
- */
- public Query getQuery() {
- return query;
- }
-
- public QueryMetadata getQueryMetadata() {
- return queryMetadata;
- }
-
- /**
- * A callback invoked by a child qualifier or ordering processor allowing
- * query assembler to reset its join stack.
- *
- * @since 3.0
- */
- public abstract void resetJoinStack();
-
- /**
- * Returns an alias of the table which is currently at the top of the join
- * stack.
- *
- * @since 3.0
- */
- public abstract String getCurrentAlias();
-
- /**
- * Appends a join with given semantics to the query.
- *
- * @since 3.0
- */
- public abstract void dbRelationshipAdded(DbRelationship relationship, JoinType joinType, String joinSplitAlias);
-
- /**
- * Translates query into an SQL string formatted to use in a
- * PreparedStatement.
- */
- public String getSql() {
- ensureTranslated();
- return sql;
- }
-
- /**
- * @since 4.0
- */
- protected void ensureTranslated() {
- if (!translated) {
- doTranslate();
- translated = true;
- }
- }
-
- /**
- * @since 4.0
- */
- protected abstract void doTranslate();
-
- /**
- * Returns <code>true</code> if table aliases are supported. Default
- * implementation returns false.
- */
- public boolean supportsTableAliases() {
- return false;
- }
-
- /**
- * Registers <code>anObject</code> as a PreparedStatement parameter.
- *
- * @param anObject
- * object that represents a value of DbAttribute
- * @param dbAttr
- * DbAttribute being processed.
- */
- public void addToParamList(DbAttribute dbAttr, Object anObject) {
- ExtendedType extendedType = anObject != null
- ? adapter.getExtendedTypes().getRegisteredType(anObject.getClass())
- : adapter.getExtendedTypes().getDefaultType();
-
- DbAttributeBinding binding = new DbAttributeBinding(dbAttr);
- binding.setStatementPosition(bindings.size() + 1);
- binding.setValue(anObject);
- binding.setExtendedType(extendedType);
-
- bindings.add(binding);
- if(addBindingListener != null) {
- addBindingListener.onAdd(binding);
- }
- }
-
- /**
- * @since 4.0
- */
- public DbAttributeBinding[] getBindings() {
- return bindings.toArray(new DbAttributeBinding[bindings.size()]);
- }
-
- /**
- * @since 4.0
- */
- public abstract String getAliasForExpression(Expression exp);
-
- /**
- * @since 4.0
- */
- public void setAddBindingListener(AddBindingListener addBindingListener) {
- this.addBindingListener = addBindingListener;
- }
-
- /**
- * @since 4.0
- */
- protected interface AddBindingListener {
- void onAdd(DbAttributeBinding binding);
- }
-}
http://git-wip-us.apache.org/repos/asf/cayenne/blob/f6b2dac9/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/QueryAssemblerHelper.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/QueryAssemblerHelper.java b/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/QueryAssemblerHelper.java
deleted file mode 100644
index fcdad65..0000000
--- a/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/QueryAssemblerHelper.java
+++ /dev/null
@@ -1,488 +0,0 @@
-/*****************************************************************
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- ****************************************************************/
-
-package org.apache.cayenne.access.translator.select;
-
-import org.apache.cayenne.CayenneRuntimeException;
-import org.apache.cayenne.ObjectId;
-import org.apache.cayenne.Persistent;
-import org.apache.cayenne.dba.QuotingStrategy;
-import org.apache.cayenne.exp.Expression;
-import org.apache.cayenne.exp.parser.PatternMatchNode;
-import org.apache.cayenne.exp.parser.SimpleNode;
-import org.apache.cayenne.map.*;
-import org.apache.cayenne.util.CayenneMapEntry;
-
-import java.io.IOException;
-import java.util.Collection;
-import java.util.Iterator;
-import java.util.List;
-import java.util.Map;
-
-/**
- * Translates parts of the query to SQL. Always works in the context of parent
- * Translator.
- */
-public abstract class QueryAssemblerHelper {
-
- protected QueryAssembler queryAssembler;
- protected StringBuilder out;
- protected QuotingStrategy strategy;
-
- /**
- * Force joining tables for all relations, not only for toMany
- * @since 4.0
- */
- private boolean forceJoinForRelations;
-
- /**
- * Creates QueryAssemblerHelper initializing with parent
- * {@link QueryAssembler} and output buffer object.
- */
- public QueryAssemblerHelper(QueryAssembler queryAssembler) {
- this.queryAssembler = queryAssembler;
- strategy = queryAssembler.getAdapter().getQuotingStrategy();
- }
-
- public ObjEntity getObjEntity() {
- return queryAssembler.getQueryMetadata().getObjEntity();
- }
-
- public DbEntity getDbEntity() {
- return queryAssembler.getQueryMetadata().getDbEntity();
- }
-
- /**
- * @since 3.0
- */
- public StringBuilder appendPart(StringBuilder out) {
- this.out = out;
- doAppendPart();
- return out;
- }
-
- /**
- * Sets ouput buffer
- */
- void setOut(StringBuilder out) {
- this.out = out;
- }
-
- /**
- * @return output buffer
- */
- StringBuilder getOut() {
- return out;
- }
-
- /**
- * @since 3.0
- */
- protected abstract void doAppendPart();
-
- /**
- * <p>
- * Outputs the standard JDBC (database agnostic) expression for supplying
- * the escape character to the database server when supplying a LIKE clause.
- * This has been factored-out because some database adaptors handle LIKE
- * differently and they need access to this common method in order not to
- * repeat this code.
- * <p>
- * If there is no escape character defined then this method will not output
- * anything. An escape character of 0 will mean no escape character.
- *
- * @since 3.1
- */
- protected void appendLikeEscapeCharacter(PatternMatchNode patternMatchNode) throws IOException {
- char escapeChar = patternMatchNode.getEscapeChar();
-
- if ('?' == escapeChar) {
- throw new CayenneRuntimeException("the escape character of '?' is illegal for LIKE clauses.");
- }
-
- if (0 != escapeChar) {
- out.append(" {escape '");
- out.append(escapeChar);
- out.append("'}");
- }
- }
-
- /**
- * Processes parts of the OBJ_PATH expression.
- */
- protected void appendObjPath(Expression pathExp) {
-
- queryAssembler.resetJoinStack();
- String joinSplitAlias = null;
-
- for (PathComponent<ObjAttribute, ObjRelationship> component : getObjEntity().resolvePath(pathExp,
- queryAssembler.getPathAliases())) {
-
- if (component.isAlias()) {
- joinSplitAlias = component.getName();
- for (PathComponent<ObjAttribute, ObjRelationship> aliasPart : component.getAliasedPath()) {
-
- ObjRelationship relationship = aliasPart.getRelationship();
-
- if (relationship == null) {
- throw new IllegalStateException("Non-relationship aliased path part: " + aliasPart.getName());
- }
-
- if (aliasPart.isLast() && component.isLast()) {
- processRelTermination(relationship, aliasPart.getJoinType(), joinSplitAlias);
- } else {
- // find and add joins ....
- for (DbRelationship dbRel : relationship.getDbRelationships()) {
- queryAssembler.dbRelationshipAdded(dbRel, aliasPart.getJoinType(), joinSplitAlias);
- }
- }
- }
-
- continue;
- }
-
- ObjRelationship relationship = component.getRelationship();
- ObjAttribute attribute = component.getAttribute();
-
- if (relationship != null) {
-
- // if this is a last relationship in the path,
- // it needs special handling
- if (component.isLast()) {
- processRelTermination(relationship, component.getJoinType(), joinSplitAlias);
- } else {
- // find and add joins ....
- for (DbRelationship dbRel : relationship.getDbRelationships()) {
- queryAssembler.dbRelationshipAdded(dbRel, component.getJoinType(), joinSplitAlias);
- }
- }
- } else {
- Iterator<CayenneMapEntry> dbPathIterator = attribute.getDbPathIterator();
- while (dbPathIterator.hasNext()) {
- Object pathPart = dbPathIterator.next();
-
- if (pathPart == null) {
- throw new CayenneRuntimeException("ObjAttribute has no component: %s", attribute.getName());
- } else if (pathPart instanceof DbRelationship) {
- queryAssembler.dbRelationshipAdded((DbRelationship) pathPart, JoinType.INNER, joinSplitAlias);
- } else if (pathPart instanceof DbAttribute) {
- processColumnWithQuoteSqlIdentifiers((DbAttribute) pathPart, pathExp);
- }
- }
-
- }
- }
- }
-
- protected void appendDbPath(Expression pathExp) {
-
- queryAssembler.resetJoinStack();
- String joinSplitAlias = null;
-
- for (PathComponent<DbAttribute, DbRelationship> component : getDbEntity().resolvePath(pathExp,
- queryAssembler.getPathAliases())) {
-
- if (component.isAlias()) {
- joinSplitAlias = component.getName();
- for (PathComponent<DbAttribute, DbRelationship> aliasPart : component.getAliasedPath()) {
-
- DbRelationship relationship = aliasPart.getRelationship();
-
- if (relationship == null) {
- throw new IllegalStateException("Non-relationship aliased path part: " + aliasPart.getName());
- }
-
- if (aliasPart.isLast() && component.isLast()) {
- processRelTermination(relationship, aliasPart.getJoinType(), joinSplitAlias);
- } else {
- queryAssembler.dbRelationshipAdded(relationship, component.getJoinType(), joinSplitAlias);
- }
- }
-
- continue;
- }
-
- DbRelationship relationship = component.getRelationship();
-
- if (relationship != null) {
-
- // if this is a last relationship in the path,
- // it needs special handling
- if (component.isLast()) {
- processRelTermination(relationship, component.getJoinType(), joinSplitAlias);
- } else {
- // find and add joins ....
- queryAssembler.dbRelationshipAdded(relationship, component.getJoinType(), joinSplitAlias);
- }
- } else {
- processColumnWithQuoteSqlIdentifiers(component.getAttribute(), pathExp);
- }
- }
- }
-
- protected void processColumn(DbAttribute dbAttr) {
- processColumnWithQuoteSqlIdentifiers(dbAttr, null);
- }
-
- protected void processColumnWithQuoteSqlIdentifiers(DbAttribute dbAttr, Expression pathExp) {
-
- String alias = (queryAssembler.supportsTableAliases()) ? queryAssembler.getCurrentAlias() : null;
- out.append(strategy.quotedIdentifier(dbAttr.getEntity(), alias, dbAttr.getName()));
- }
-
- /**
- * Appends SQL code to the query buffer to handle <code>val</code> as a
- * parameter to the PreparedStatement being built. Adds <code>val</code>
- * into QueryAssembler parameter list.
- * <p>
- * If <code>val</code> is null, "NULL" is appended to the query.
- * </p>
- * <p>
- * If <code>val</code> is a DataObject, its primary key value is used as a
- * parameter. <i>Only objects with a single column primary key can be
- * used.</i>
- *
- * @param val
- * object that should be appended as a literal to the query. Must
- * be of one of "standard JDBC" types, null or a DataObject.
- * @param attr
- * DbAttribute that has information on what type of parameter is
- * being appended.
- */
- protected void appendLiteral(Object val, DbAttribute attr, Expression parentExpression) throws IOException {
-
- if (val == null) {
- out.append("NULL");
- } else if (val instanceof Persistent) {
- // TODO: see cay1796
- // This check is unlikely to happen,
- // since Expression got ObjectId from Persistent object.
- // Left for future research.
- ObjectId id = ((Persistent) val).getObjectId();
-
- // check if this id is acceptable to be a parameter
- if (id == null) {
- throw new CayenneRuntimeException("Can't use TRANSIENT object as a query parameter.");
- }
-
- if (id.isTemporary()) {
- throw new CayenneRuntimeException("Can't use NEW object as a query parameter.");
- }
-
- Map<String, Object> snap = id.getIdSnapshot();
- if (snap.size() != 1) {
- throw new CayenneRuntimeException("Object must have a single primary key column to serve " +
- "as a query parameter. This object has %s: %s", snap.size(), snap);
- }
-
- // checks have been passed, use id value
- appendLiteralDirect(snap.get(snap.keySet().iterator().next()), attr, parentExpression);
- } else if (val instanceof ObjectId) {
-
- ObjectId id = (ObjectId) val;
-
- if (id.isTemporary()) {
- throw new CayenneRuntimeException("Can't use NEW object as a query parameter.");
- }
-
- Map<String, Object> snap = id.getIdSnapshot();
- if (snap.size() != 1) {
- throw new CayenneRuntimeException("Object must have a single primary key column to serve " +
- "as a query parameter. This object has %s: %s", snap.size(), snap);
- }
-
- // checks have been passed, use id value
- appendLiteralDirect(snap.get(snap.keySet().iterator().next()), attr, parentExpression);
- } else {
- appendLiteralDirect(val, attr, parentExpression);
- }
- }
-
- /**
- * Appends SQL code to the query buffer to handle <code>val</code> as a
- * parameter to the PreparedStatement being built. Adds <code>val</code>
- * into QueryAssembler parameter list.
- */
- protected void appendLiteralDirect(Object val, DbAttribute attr, Expression parentExpression) throws IOException {
- out.append('?');
- queryAssembler.addToParamList(attr, val);
- }
-
- /**
- * Returns database type of expression parameters or null if it can not be
- * determined.
- */
- protected DbAttribute paramsDbType(Expression e) {
- int len = e.getOperandCount();
-
- // for unary expressions, find parent binary - this is a hack mainly to
- // support
- // ASTList
- if (len < 2) {
-
- if (e instanceof SimpleNode) {
- Expression parent = (Expression) ((SimpleNode) e).jjtGetParent();
- if (parent != null) {
- return paramsDbType(parent);
- }
- }
-
- return null;
- }
-
- // naive algorithm:
-
- // if at least one of the sibling operands is a
- // OBJ_PATH or DB_PATH expression, use its attribute type as
- // a final answer.
-
- // find attribute or relationship matching the value
- DbAttribute attribute = null;
- DbRelationship relationship = null;
- for (int i = 0; i < len; i++) {
- Object op = e.getOperand(i);
-
- if (op instanceof Expression) {
- Expression expression = (Expression) op;
- if (expression.getType() == Expression.OBJ_PATH) {
- PathComponent<ObjAttribute, ObjRelationship> last = getObjEntity().lastPathComponent(expression,
- queryAssembler.getPathAliases());
-
- // TODO: handle EmbeddableAttribute
- // if (last instanceof EmbeddableAttribute)
- // break;
-
- if (last.getAttribute() != null) {
- attribute = last.getAttribute().getDbAttribute();
- break;
- } else if (last.getRelationship() != null) {
- List<DbRelationship> dbPath = last.getRelationship().getDbRelationships();
- if (dbPath.size() > 0) {
- relationship = dbPath.get(dbPath.size() - 1);
- break;
- }
- }
- } else if (expression.getType() == Expression.DB_PATH) {
- PathComponent<DbAttribute, DbRelationship> last = getDbEntity().lastPathComponent(expression,
- queryAssembler.getPathAliases());
- if (last.getAttribute() != null) {
- attribute = last.getAttribute();
- break;
- } else if (last.getRelationship() != null) {
- relationship = last.getRelationship();
- break;
- }
- }
- }
- }
-
- if (attribute != null) {
- return attribute;
- }
-
- if (relationship != null) {
- // Can't properly handle multiple joins....
- if (relationship.getJoins().size() == 1) {
- DbJoin join = relationship.getJoins().get(0);
- return join.getSource();
- }
- }
-
- return null;
- }
-
- /**
- * Processes case when an OBJ_PATH expression ends with relationship. If
- * this is a "to many" relationship, a join is added and a column expression
- * for the target entity primary key. If this is a "to one" relationship,
- * column expression for the source foreign key is added.
- *
- * @since 3.0
- */
- protected void processRelTermination(ObjRelationship rel, JoinType joinType, String joinSplitAlias) {
-
- Iterator<DbRelationship> dbRels = rel.getDbRelationships().iterator();
-
- // scan DbRelationships
- while (dbRels.hasNext()) {
- DbRelationship dbRel = dbRels.next();
-
- // if this is a last relationship in the path,
- // it needs special handling
- if (!dbRels.hasNext()) {
- processRelTermination(dbRel, joinType, joinSplitAlias);
- } else {
- // find and add joins ....
- queryAssembler.dbRelationshipAdded(dbRel, joinType, joinSplitAlias);
- }
- }
- }
-
- /**
- * Handles case when a DB_NAME expression ends with relationship. If this is
- * a "to many" relationship, a join is added and a column expression for the
- * target entity primary key. If this is a "to one" relationship, column
- * expression for the source foreign key is added.
- *
- * @since 3.0
- */
- protected void processRelTermination(DbRelationship rel, JoinType joinType, String joinSplitAlias) {
-
- if (forceJoinForRelations || rel.isToMany()) {
- // append joins
- queryAssembler.dbRelationshipAdded(rel, joinType, joinSplitAlias);
- }
-
- // get last DbRelationship on the list
- List<DbJoin> joins = rel.getJoins();
- if (joins.size() != 1) {
- String msg = "OBJ_PATH expressions are only supported for a single-join relationships. " +
- "This relationship has %s joins.";
- throw new CayenneRuntimeException(msg, joins.size());
- }
-
- DbJoin join = joins.get(0);
-
- DbAttribute attribute;
-
- if (rel.isToMany()) {
- DbEntity ent = join.getRelationship().getTargetEntity();
- Collection<DbAttribute> pk = ent.getPrimaryKeys();
- if (pk.size() != 1) {
- String msg = "DB_NAME expressions can only support targets with a single column PK. " +
- "This entity has %d columns in primary key.";
- throw new CayenneRuntimeException(msg, pk.size());
- }
-
- attribute = pk.iterator().next();
- } else {
- attribute = forceJoinForRelations ? join.getTarget() : join.getSource();
- }
-
- processColumn(attribute);
- }
-
- /**
- * Force joining tables for all relations, not only for toMany
- * @since 4.0
- */
- protected void setForceJoinForRelations(boolean forceJoinForRelations) {
- this.forceJoinForRelations = forceJoinForRelations;
- }
-}
http://git-wip-us.apache.org/repos/asf/cayenne/blob/f6b2dac9/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/ResultNodeDescriptor.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/ResultNodeDescriptor.java b/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/ResultNodeDescriptor.java
new file mode 100644
index 0000000..1488c6a
--- /dev/null
+++ b/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/ResultNodeDescriptor.java
@@ -0,0 +1,138 @@
+/*****************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ ****************************************************************/
+
+package org.apache.cayenne.access.translator.select;
+
+import org.apache.cayenne.access.sqlbuilder.sqltree.ColumnNode;
+import org.apache.cayenne.access.sqlbuilder.sqltree.Node;
+import org.apache.cayenne.access.sqlbuilder.sqltree.NodeType;
+import org.apache.cayenne.access.sqlbuilder.sqltree.SimpleNodeTreeVisitor;
+import org.apache.cayenne.dba.TypesMapping;
+import org.apache.cayenne.exp.parser.ASTAggregateFunctionCall;
+import org.apache.cayenne.exp.property.BaseProperty;
+import org.apache.cayenne.map.DbAttribute;
+
+/**
+ * @since 4.2
+ */
+class ResultNodeDescriptor {
+ private final Node node;
+ private final boolean inDataRow;
+ private final boolean isAggregate;
+ private final BaseProperty<?> property;
+
+ private String dataRowKey;
+ private DbAttribute dbAttribute;
+ private String javaType;
+
+ ResultNodeDescriptor(Node node, boolean inDataRow, BaseProperty<?> property, String dataRowKey) {
+ this.node = node;
+ this.inDataRow = inDataRow;
+ this.property = property;
+ this.dataRowKey = dataRowKey;
+ this.isAggregate = property != null
+ && property.getExpression() instanceof ASTAggregateFunctionCall;
+ }
+
+ public boolean isAggregate() {
+ return isAggregate;
+ }
+
+ public boolean isInDataRow() {
+ return inDataRow;
+ }
+
+ public BaseProperty<?> getProperty() {
+ return property;
+ }
+
+ public Node getNode() {
+ return node;
+ }
+
+ public String getDataRowKey() {
+ if (dataRowKey != null) {
+ return dataRowKey;
+ }
+ if (property != null) {
+ return property.getAlias();
+ }
+ if (getDbAttribute() != null) {
+ return getDbAttribute().getName();
+ }
+ return null;
+ }
+
+ public void setDataRowKey(String dataRowKey) {
+ this.dataRowKey = dataRowKey;
+ }
+
+ public ResultNodeDescriptor setJavaType(String javaType) {
+ this.javaType = javaType;
+ return this;
+ }
+
+ public ResultNodeDescriptor setDbAttribute(DbAttribute dbAttribute) {
+ this.dbAttribute = dbAttribute;
+ return this;
+ }
+
+ public String getJavaType() {
+ if (javaType != null) {
+ return javaType;
+ }
+ if (property != null) {
+ return property.getType().getCanonicalName();
+ }
+ if (getDbAttribute() != null) {
+ return TypesMapping.getJavaBySqlType(getDbAttribute().getType());
+ }
+ return null;
+ }
+
+ public int getJdbcType() {
+ if (getDbAttribute() != null) {
+ return getDbAttribute().getType();
+ }
+
+ if (getProperty() != null) {
+ return TypesMapping.getSqlTypeByJava(getProperty().getType());
+ }
+
+ return TypesMapping.NOT_DEFINED;
+ }
+
+ public DbAttribute getDbAttribute() {
+ if (this.dbAttribute != null) {
+ return this.dbAttribute;
+ }
+ DbAttribute[] dbAttribute = {null};
+ node.visit(new SimpleNodeTreeVisitor() {
+ @Override
+ public boolean onNodeStart(Node node) {
+ if (node.getType() == NodeType.COLUMN) {
+ dbAttribute[0] = ((ColumnNode) node).getAttribute();
+ return false;
+ }
+ return true;
+ }
+ });
+ return this.dbAttribute = dbAttribute[0];
+ }
+}
http://git-wip-us.apache.org/repos/asf/cayenne/blob/f6b2dac9/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/SQLGenerationStage.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/SQLGenerationStage.java b/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/SQLGenerationStage.java
new file mode 100644
index 0000000..9551035
--- /dev/null
+++ b/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/SQLGenerationStage.java
@@ -0,0 +1,44 @@
+/*****************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ ****************************************************************/
+
+package org.apache.cayenne.access.translator.select;
+
+import org.apache.cayenne.access.sqlbuilder.SQLGenerationVisitor;
+import org.apache.cayenne.access.sqlbuilder.sqltree.Node;
+
+/**
+ * @since 4.2
+ */
+class SQLGenerationStage implements TranslationStage {
+
+ @Override
+ public void perform(TranslatorContext context) {
+ if(context.isSkipSQLGeneration()) {
+ return;
+ }
+ // Build final SQL tree
+ Node node = context.getSelectBuilder().build();
+ // convert to database flavour
+ node = context.getAdapter().getSqlTreeProcessor().apply(node);
+ // generate SQL
+ SQLGenerationVisitor visitor = new SQLGenerationVisitor(new DefaultQuotingAppendable(context));
+ node.visit(visitor);
+ context.setFinalSQL(visitor.getSQLString());
+ }
+}
http://git-wip-us.apache.org/repos/asf/cayenne/blob/f6b2dac9/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/SelectQueryWrapper.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/SelectQueryWrapper.java b/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/SelectQueryWrapper.java
new file mode 100644
index 0000000..576bb8d
--- /dev/null
+++ b/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/SelectQueryWrapper.java
@@ -0,0 +1,84 @@
+/*****************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ ****************************************************************/
+
+package org.apache.cayenne.access.translator.select;
+
+import java.util.Collection;
+import java.util.Objects;
+
+import org.apache.cayenne.exp.Expression;
+import org.apache.cayenne.exp.property.BaseProperty;
+import org.apache.cayenne.map.EntityResolver;
+import org.apache.cayenne.query.Ordering;
+import org.apache.cayenne.query.PrefetchTreeNode;
+import org.apache.cayenne.query.QueryMetadata;
+import org.apache.cayenne.query.Select;
+import org.apache.cayenne.query.SelectQuery;
+
+/**
+ * @since 4.2
+ */
+public class SelectQueryWrapper implements TranslatableQueryWrapper {
+
+ private final SelectQuery<?> selectQuery;
+
+ public SelectQueryWrapper(SelectQuery<?> selectQuery) {
+ this.selectQuery = Objects.requireNonNull(selectQuery);
+ }
+
+ @Override
+ public boolean isDistinct() {
+ return selectQuery.isDistinct();
+ }
+
+ @Override
+ public QueryMetadata getMetaData(EntityResolver resolver) {
+ return selectQuery.getMetaData(resolver);
+ }
+
+ @Override
+ public PrefetchTreeNode getPrefetchTree() {
+ return selectQuery.getPrefetchTree();
+ }
+
+ @Override
+ public Expression getQualifier() {
+ return selectQuery.getQualifier();
+ }
+
+ @Override
+ public Collection<Ordering> getOrderings() {
+ return selectQuery.getOrderings();
+ }
+
+ @Override
+ public Collection<BaseProperty<?>> getColumns() {
+ return selectQuery.getColumns();
+ }
+
+ @Override
+ public Expression getHavingQualifier() {
+ return selectQuery.getHavingQualifier();
+ }
+
+ @Override
+ public Select<?> unwrap() {
+ return selectQuery;
+ }
+}
[04/11] cayenne git commit: CAY-2465 New SelectTranslator
implementation
Posted by nt...@apache.org.
http://git-wip-us.apache.org/repos/asf/cayenne/blob/f6b2dac9/cayenne-server/src/main/java/org/apache/cayenne/dba/openbase/OpenBaseJoinStack.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/dba/openbase/OpenBaseJoinStack.java b/cayenne-server/src/main/java/org/apache/cayenne/dba/openbase/OpenBaseJoinStack.java
deleted file mode 100644
index 9add9df..0000000
--- a/cayenne-server/src/main/java/org/apache/cayenne/dba/openbase/OpenBaseJoinStack.java
+++ /dev/null
@@ -1,115 +0,0 @@
-/*****************************************************************
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- ****************************************************************/
-package org.apache.cayenne.dba.openbase;
-
-import java.util.List;
-
-import org.apache.cayenne.access.translator.select.JoinStack;
-import org.apache.cayenne.access.translator.select.JoinTreeNode;
-import org.apache.cayenne.access.translator.select.QueryAssembler;
-import org.apache.cayenne.dba.DbAdapter;
-import org.apache.cayenne.map.DataMap;
-import org.apache.cayenne.map.DbEntity;
-import org.apache.cayenne.map.DbJoin;
-import org.apache.cayenne.map.DbRelationship;
-
-/**
- * OpenBase does not support standard JOIN keyword and have strange syntax for
- * defining inner/outer joins
- *
- * @see http ://www.openbase.com/help/KnowledgeBase/
- * 400_OpenBaseSQL/401_SelectStatements.html
- * @since 3.0
- */
-class OpenBaseJoinStack extends JoinStack {
-
- protected OpenBaseJoinStack(DbAdapter dbAdapter, QueryAssembler assembler) {
- super(dbAdapter, assembler);
- }
-
- @Override
- protected void appendJoinSubtree(StringBuilder out, JoinTreeNode node) {
- DbRelationship relationship = node.getRelationship();
-
- if (relationship == null) {
- return;
- }
-
- DbEntity targetEntity = relationship.getTargetEntity();
- String targetAlias = node.getTargetTableAlias();
-
- out.append(", ").append(targetEntity.getFullyQualifiedName()).append(' ').append(targetAlias);
-
- for (JoinTreeNode child : node.getChildren()) {
- appendJoinSubtree(out, child);
- }
- }
-
- @Override
- protected void appendQualifier(StringBuilder out, boolean firstQualifierElement) {
- boolean first = firstQualifierElement;
- for (JoinTreeNode node : rootNode.getChildren()) {
- if (!first) {
- out.append(" AND ");
- }
- appendQualifierSubtree(out, node);
- first = false;
- }
- }
-
- protected void appendQualifierSubtree(StringBuilder out, JoinTreeNode node) {
- DbRelationship relationship = node.getRelationship();
-
- String srcAlias = node.getSourceTableAlias();
- String targetAlias = node.getTargetTableAlias();
-
- List<DbJoin> joins = relationship.getJoins();
- int len = joins.size();
- for (int i = 0; i < len; i++) {
- DbJoin join = joins.get(i);
-
- if (i > 0) {
- out.append(" AND ");
- }
-
- out.append(srcAlias).append('.').append(join.getSourceName());
-
- switch (node.getJoinType()) {
- case INNER:
- out.append(" = ");
- break;
- case LEFT_OUTER:
- out.append(" * ");
- break;
- default:
- throw new IllegalArgumentException("Unsupported join type: " + node.getJoinType());
- }
-
- out.append(targetAlias).append('.').append(join.getTargetName());
-
- }
-
- for (JoinTreeNode child : node.getChildren()) {
- out.append(" AND ");
- appendQualifierSubtree(out, child);
- }
-
- }
-
-}
http://git-wip-us.apache.org/repos/asf/cayenne/blob/f6b2dac9/cayenne-server/src/main/java/org/apache/cayenne/dba/openbase/OpenBaseQualifierTranslator.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/dba/openbase/OpenBaseQualifierTranslator.java b/cayenne-server/src/main/java/org/apache/cayenne/dba/openbase/OpenBaseQualifierTranslator.java
deleted file mode 100644
index dbedc60..0000000
--- a/cayenne-server/src/main/java/org/apache/cayenne/dba/openbase/OpenBaseQualifierTranslator.java
+++ /dev/null
@@ -1,177 +0,0 @@
-/*****************************************************************
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- ****************************************************************/
-
-package org.apache.cayenne.dba.openbase;
-
-import java.io.IOException;
-
-import org.apache.cayenne.CayenneRuntimeException;
-import org.apache.cayenne.access.translator.select.QualifierTranslator;
-import org.apache.cayenne.access.translator.select.QueryAssembler;
-import org.apache.cayenne.exp.Expression;
-import org.apache.cayenne.exp.parser.ASTExtract;
-import org.apache.cayenne.exp.parser.PatternMatchNode;
-import org.apache.cayenne.map.DbAttribute;
-
-/**
- * Translates query qualifier to SQL. Used as a helper class by query
- * translators.
- *
- * @since 1.1
- */
-public class OpenBaseQualifierTranslator extends QualifierTranslator {
-
- public OpenBaseQualifierTranslator(QueryAssembler queryAssembler) {
- super(queryAssembler);
- }
-
- @Override
- public void startNode(Expression node, Expression parentNode) {
-
- if (node.getOperandCount() == 2) {
- // binary nodes are the only ones that currently require this
- detectObjectMatch(node);
-
- if (parenthesisNeeded(node, parentNode)) {
- out.append('(');
- }
-
- // super implementation has special handling
- // of LIKE_IGNORE_CASE and NOT_LIKE_IGNORE_CASE
- // OpenBase is case-insensitive by default
- // ...
-
- } else {
- super.startNode(node, parentNode);
- }
- }
-
- @Override
- public void endNode(Expression node, Expression parentNode) {
- if (node.getOperandCount() == 2) {
-
- try {
- // check if we need to use objectMatchTranslator to finish
- // building the
- // expression
- if (matchingObject) {
- appendObjectMatch();
- }
-
- if (PatternMatchNode.class.isAssignableFrom(node.getClass())) {
- appendLikeEscapeCharacter((PatternMatchNode) node);
- }
-
- if (parenthesisNeeded(node, parentNode)) {
- out.append(')');
- }
-
- // super implementation has special handling
- // of LIKE_IGNORE_CASE and NOT_LIKE_IGNORE_CASE
- // OpenBase is case-insensitive by default
- // ...
- } catch (IOException ioex) {
- throw new CayenneRuntimeException("Error appending content", ioex);
- }
- } else {
- super.endNode(node, parentNode);
- }
- }
-
- @Override
- protected void appendLiteralDirect(Object val, DbAttribute attr, Expression parentExpression) throws IOException {
-
- // Special handling of string matching is needed:
- // Case-sensitive LIKE must be converted to [x][Y][z] format
- if (val instanceof String
- && (parentExpression.getType() == Expression.LIKE || parentExpression.getType() == Expression.NOT_LIKE)) {
-
- val = caseSensitiveLikePattern((String) val);
- }
-
- super.appendLiteralDirect(val, attr, parentExpression);
- }
-
- private String caseSensitiveLikePattern(String pattern) {
- int len = pattern.length();
- StringBuilder buffer = new StringBuilder(len * 3);
-
- for (int i = 0; i < len; i++) {
- char c = pattern.charAt(i);
- if (c == '%' || c == '?') {
- buffer.append(c);
- } else {
- buffer.append("[").append(c).append("]");
- }
- }
-
- return buffer.toString();
- }
-
- @Override
- public void finishedChild(Expression node, int childIndex, boolean hasMoreChildren) {
- if (!hasMoreChildren) {
- return;
- }
-
- // super implementation has special handling
- // of LIKE_IGNORE_CASE and NOT_LIKE_IGNORE_CASE
- // OpenBase is case-insensitive by default
- // ...
-
- try {
- switch (node.getType()) {
-
- case Expression.LIKE_IGNORE_CASE:
- finishedChildNodeAppendExpression(node, " LIKE ");
- break;
- case Expression.NOT_LIKE_IGNORE_CASE:
- finishedChildNodeAppendExpression(node, " NOT LIKE ");
- break;
- default:
- super.finishedChild(node, childIndex, hasMoreChildren);
- }
- } catch (IOException ioex) {
- throw new CayenneRuntimeException("Error appending content", ioex);
- }
- }
-
- private void finishedChildNodeAppendExpression(Expression node, String operation) throws IOException {
- Appendable out = matchingObject ? new StringBuilder() : this.out;
- out.append(operation);
- if (matchingObject) {
- objectMatchTranslator.setOperation(out.toString());
- objectMatchTranslator.setExpression(node);
- }
- }
-
- @Override
- protected void appendExtractFunction(ASTExtract functionExpression) {
- switch (functionExpression.getPart()) {
- case DAY_OF_WEEK:
- case DAY_OF_MONTH:
- case DAY_OF_YEAR:
- // openbase variants are without '_'
- out.append(functionExpression.getPart().name().replace("_", ""));
- break;
- default:
- appendFunction(functionExpression);
- }
- }
-}
http://git-wip-us.apache.org/repos/asf/cayenne/blob/f6b2dac9/cayenne-server/src/main/java/org/apache/cayenne/dba/openbase/OpenBaseSQLTreeProcessor.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/dba/openbase/OpenBaseSQLTreeProcessor.java b/cayenne-server/src/main/java/org/apache/cayenne/dba/openbase/OpenBaseSQLTreeProcessor.java
new file mode 100644
index 0000000..29eaaa1
--- /dev/null
+++ b/cayenne-server/src/main/java/org/apache/cayenne/dba/openbase/OpenBaseSQLTreeProcessor.java
@@ -0,0 +1,109 @@
+/*****************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ ****************************************************************/
+
+package org.apache.cayenne.dba.openbase;
+
+import org.apache.cayenne.access.sqlbuilder.QuotingAppendable;
+import org.apache.cayenne.access.sqlbuilder.sqltree.FunctionNode;
+import org.apache.cayenne.access.sqlbuilder.sqltree.LikeNode;
+import org.apache.cayenne.access.sqlbuilder.sqltree.LimitOffsetNode;
+import org.apache.cayenne.access.sqlbuilder.sqltree.Node;
+import org.apache.cayenne.access.sqlbuilder.sqltree.NodeType;
+import org.apache.cayenne.access.sqlbuilder.sqltree.ValueNode;
+import org.apache.cayenne.access.translator.select.BaseSQLTreeProcessor;
+
+/**
+ * @since 4.2
+ */
+public class OpenBaseSQLTreeProcessor extends BaseSQLTreeProcessor {
+
+ @Override
+ protected void onLikeNode(Node parent, LikeNode child, int index) {
+ // OpenBase is case-insensitive by default
+ if(child.isIgnoreCase()) {
+ replaceChild(parent, index, new LikeNode(false, child.isNot(), child.getEscape()));
+ }
+ }
+
+ @Override
+ protected void onValueNode(Node parent, ValueNode child, int index) {
+ // Special handling of string matching is needed:
+ // Case-sensitive LIKE must be converted to [x][Y][z] format
+ if(parent.getType() == NodeType.LIKE) {
+ if(!((LikeNode)parent).isIgnoreCase() && child.getValue() instanceof CharSequence) {
+ replaceChild(parent, index,
+ new ValueNode(caseSensitiveLikePattern((CharSequence)child.getValue()), child.isArray(), child.getAttribute()));
+ }
+ }
+ }
+
+ @Override
+ protected void onLimitOffsetNode(Node parent, LimitOffsetNode child, int index) {
+ replaceChild(parent, index, new OpenBaseLimitNode(child));
+ }
+
+ @Override
+ protected void onFunctionNode(Node parent, FunctionNode child, int index) {
+ switch (child.getFunctionName()) {
+ case "DAY_OF_WEEK":
+ case "DAY_OF_MONTH":
+ case "DAY_OF_YEAR":
+ replaceChild(parent, index, new FunctionNode(child.getFunctionName().replace("_", ""), child.getAlias()));
+ break;
+ }
+ }
+
+ private String caseSensitiveLikePattern(CharSequence pattern) {
+ int len = pattern.length();
+ StringBuilder buffer = new StringBuilder(len * 3);
+
+ for (int i = 0; i < len; i++) {
+ char c = pattern.charAt(i);
+ if (c == '%' || c == '?') {
+ buffer.append(c);
+ } else {
+ buffer.append("[").append(c).append("]");
+ }
+ }
+
+ return buffer.toString();
+ }
+
+ private static class OpenBaseLimitNode extends Node {
+
+ private final LimitOffsetNode child;
+
+ public OpenBaseLimitNode(LimitOffsetNode child) {
+ this.child = child;
+ }
+
+ @Override
+ public QuotingAppendable append(QuotingAppendable buffer) {
+ if(child.getLimit() > 0) {
+ buffer.append(" RETURN RESULTS ").append(child.getLimit());
+ }
+ return buffer;
+ }
+
+ @Override
+ public Node copy() {
+ return new OpenBaseLimitNode(child);
+ }
+ }
+}
http://git-wip-us.apache.org/repos/asf/cayenne/blob/f6b2dac9/cayenne-server/src/main/java/org/apache/cayenne/dba/openbase/OpenBaseSelectTranslator.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/dba/openbase/OpenBaseSelectTranslator.java b/cayenne-server/src/main/java/org/apache/cayenne/dba/openbase/OpenBaseSelectTranslator.java
deleted file mode 100644
index e4ff7cc..0000000
--- a/cayenne-server/src/main/java/org/apache/cayenne/dba/openbase/OpenBaseSelectTranslator.java
+++ /dev/null
@@ -1,53 +0,0 @@
-/*****************************************************************
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- ****************************************************************/
-
-package org.apache.cayenne.dba.openbase;
-
-import org.apache.cayenne.access.translator.select.DefaultSelectTranslator;
-import org.apache.cayenne.access.translator.select.JoinStack;
-import org.apache.cayenne.dba.DbAdapter;
-import org.apache.cayenne.map.EntityResolver;
-import org.apache.cayenne.query.Query;
-
-/**
- * @since 1.2
- */
-class OpenBaseSelectTranslator extends DefaultSelectTranslator {
-
- /**
- * @since 4.0
- */
- public OpenBaseSelectTranslator(Query query, DbAdapter adapter, EntityResolver entityResolver) {
- super(query, adapter, entityResolver);
- }
-
- @Override
- protected JoinStack createJoinStack() {
- return new OpenBaseJoinStack(getAdapter(), this);
- }
-
- @Override
- protected void appendLimitAndOffsetClauses(StringBuilder buffer) {
- int limit = queryMetadata.getFetchLimit();
- if (limit > 0) {
- buffer.append(" RETURN RESULTS ").append(limit);
- }
- }
-
-}
http://git-wip-us.apache.org/repos/asf/cayenne/blob/f6b2dac9/cayenne-server/src/main/java/org/apache/cayenne/dba/oracle/Oracle8Adapter.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/dba/oracle/Oracle8Adapter.java b/cayenne-server/src/main/java/org/apache/cayenne/dba/oracle/Oracle8Adapter.java
index 60f820e..a51f4d8 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/dba/oracle/Oracle8Adapter.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/dba/oracle/Oracle8Adapter.java
@@ -20,19 +20,14 @@
package org.apache.cayenne.dba.oracle;
import org.apache.cayenne.access.DataNode;
-import org.apache.cayenne.access.translator.select.QualifierTranslator;
-import org.apache.cayenne.access.translator.select.QueryAssembler;
-import org.apache.cayenne.access.translator.select.SelectTranslator;
import org.apache.cayenne.access.types.ExtendedType;
import org.apache.cayenne.access.types.ExtendedTypeFactory;
import org.apache.cayenne.access.types.ValueObjectTypeRegistry;
import org.apache.cayenne.configuration.Constants;
import org.apache.cayenne.configuration.RuntimeProperties;
import org.apache.cayenne.di.Inject;
-import org.apache.cayenne.map.EntityResolver;
import org.apache.cayenne.query.Query;
import org.apache.cayenne.query.SQLAction;
-import org.apache.cayenne.query.SelectQuery;
import org.apache.cayenne.resource.ResourceLocator;
import java.lang.reflect.Method;
@@ -84,14 +79,6 @@ public class Oracle8Adapter extends OracleAdapter {
}
/**
- * @since 4.0
- */
- @Override
- public SelectTranslator getSelectTranslator(SelectQuery<?> query, EntityResolver entityResolver) {
- return new Oracle8SelectTranslator(query, this, entityResolver);
- }
-
- /**
* Uses OracleActionBuilder to create the right action.
*/
@Override
@@ -109,10 +96,4 @@ public class Oracle8Adapter extends OracleAdapter {
return super.findResource(name);
}
- @Override
- public QualifierTranslator getQualifierTranslator(QueryAssembler queryAssembler) {
- QualifierTranslator translator = new Oracle8QualifierTranslator(queryAssembler);
- translator.setCaseInsensitive(caseInsensitiveCollations);
- return translator;
- }
}
http://git-wip-us.apache.org/repos/asf/cayenne/blob/f6b2dac9/cayenne-server/src/main/java/org/apache/cayenne/dba/oracle/Oracle8JoinStack.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/dba/oracle/Oracle8JoinStack.java b/cayenne-server/src/main/java/org/apache/cayenne/dba/oracle/Oracle8JoinStack.java
deleted file mode 100644
index 183775a..0000000
--- a/cayenne-server/src/main/java/org/apache/cayenne/dba/oracle/Oracle8JoinStack.java
+++ /dev/null
@@ -1,108 +0,0 @@
-/*****************************************************************
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- ****************************************************************/
-package org.apache.cayenne.dba.oracle;
-
-import java.util.List;
-
-import org.apache.cayenne.access.translator.select.JoinStack;
-import org.apache.cayenne.access.translator.select.JoinTreeNode;
-import org.apache.cayenne.access.translator.select.QueryAssembler;
-import org.apache.cayenne.dba.DbAdapter;
-import org.apache.cayenne.map.DataMap;
-import org.apache.cayenne.map.DbEntity;
-import org.apache.cayenne.map.DbJoin;
-import org.apache.cayenne.map.DbRelationship;
-
-/**
- * @since 3.0
- */
-// cloned from OpenBaseJoin stack... need better strategies of reuse...
-class Oracle8JoinStack extends JoinStack {
-
- Oracle8JoinStack(DbAdapter dbAdapter, QueryAssembler assembler) {
- super(dbAdapter, assembler);
- }
-
- @Override
- protected void appendJoinSubtree(StringBuilder out, JoinTreeNode node) {
- DbRelationship relationship = node.getRelationship();
-
- if (relationship == null) {
- return;
- }
-
- DbEntity targetEntity = relationship.getTargetEntity();
- String targetAlias = node.getTargetTableAlias();
-
- out.append(", ").append(targetEntity.getFullyQualifiedName()).append(' ').append(targetAlias);
-
- for (JoinTreeNode child : node.getChildren()) {
- appendJoinSubtree(out, child);
- }
- }
-
- @Override
- protected void appendQualifier(StringBuilder out, boolean firstQualifierElement) {
- boolean first = firstQualifierElement;
- for (JoinTreeNode node : rootNode.getChildren()) {
- if (!first) {
- out.append(" AND ");
- }
- appendQualifierSubtree(out, node);
- first = false;
- }
- }
-
- protected void appendQualifierSubtree(StringBuilder out, JoinTreeNode node) {
- DbRelationship relationship = node.getRelationship();
-
- String srcAlias = node.getSourceTableAlias();
- String targetAlias = node.getTargetTableAlias();
-
- List<DbJoin> joins = relationship.getJoins();
- int len = joins.size();
- for (int i = 0; i < len; i++) {
- DbJoin join = joins.get(i);
-
- if (i > 0) {
- out.append(" AND ");
- }
-
- out.append(srcAlias).append('.').append(join.getSourceName());
-
- switch (node.getJoinType()) {
- case INNER:
- out.append(" = ");
- break;
- case LEFT_OUTER:
- out.append(" * ");
- break;
- default:
- throw new IllegalArgumentException("Unsupported join type: " + node.getJoinType());
- }
-
- out.append(targetAlias).append('.').append(join.getTargetName());
- }
-
- for (JoinTreeNode child : node.getChildren()) {
- out.append(" AND ");
- appendQualifierSubtree(out, child);
- }
- }
-}
http://git-wip-us.apache.org/repos/asf/cayenne/blob/f6b2dac9/cayenne-server/src/main/java/org/apache/cayenne/dba/oracle/Oracle8QualifierTranslator.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/dba/oracle/Oracle8QualifierTranslator.java b/cayenne-server/src/main/java/org/apache/cayenne/dba/oracle/Oracle8QualifierTranslator.java
deleted file mode 100644
index d756392..0000000
--- a/cayenne-server/src/main/java/org/apache/cayenne/dba/oracle/Oracle8QualifierTranslator.java
+++ /dev/null
@@ -1,49 +0,0 @@
-/*****************************************************************
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- ****************************************************************/
-package org.apache.cayenne.dba.oracle;
-
-import org.apache.cayenne.access.translator.select.QueryAssembler;
-
-/**
- * Extends the TrimmingQualifierTranslator that Cayenne normally uses for
- * Oracle. Overrides doAppendPart() to wrap the qualifierBuffer in parentheses
- * if it contains an "OR" expression. This avoids a bug that can happen on
- * Oracle8 if the query also contains a join.
- *
- * @since 3.0
- */
-class Oracle8QualifierTranslator extends OracleQualifierTranslator {
-
- public Oracle8QualifierTranslator(QueryAssembler queryAssembler) {
- super(queryAssembler);
- }
-
- @Override
- protected void doAppendPart() {
- super.doAppendPart();
-
- if (out instanceof StringBuilder) {
- StringBuilder buffer = (StringBuilder) out;
- if (buffer.indexOf(" OR ") != -1) {
- buffer.insert(0, '(');
- buffer.append(')');
- }
- }
- }
-}
http://git-wip-us.apache.org/repos/asf/cayenne/blob/f6b2dac9/cayenne-server/src/main/java/org/apache/cayenne/dba/oracle/Oracle8SelectTranslator.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/dba/oracle/Oracle8SelectTranslator.java b/cayenne-server/src/main/java/org/apache/cayenne/dba/oracle/Oracle8SelectTranslator.java
deleted file mode 100644
index 6df771c..0000000
--- a/cayenne-server/src/main/java/org/apache/cayenne/dba/oracle/Oracle8SelectTranslator.java
+++ /dev/null
@@ -1,47 +0,0 @@
-/*****************************************************************
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- ****************************************************************/
-package org.apache.cayenne.dba.oracle;
-
-import org.apache.cayenne.access.translator.select.JoinStack;
-import org.apache.cayenne.dba.DbAdapter;
-import org.apache.cayenne.map.EntityResolver;
-import org.apache.cayenne.query.Query;
-
-/**
- * @since 3.0
- */
-class Oracle8SelectTranslator extends OracleSelectTranslator {
-
- /**
- * @since 4.0
- */
- public Oracle8SelectTranslator(Query query, DbAdapter adapter, EntityResolver entityResolver) {
- super(query, adapter, entityResolver);
- }
-
- /**
- * Returns an old style joint stack for Oracle8 that does not support
- * explicit join syntax.
- */
- @Override
- protected JoinStack createJoinStack() {
- return new Oracle8JoinStack(getAdapter(), this);
- }
-
-}
http://git-wip-us.apache.org/repos/asf/cayenne/blob/f6b2dac9/cayenne-server/src/main/java/org/apache/cayenne/dba/oracle/OracleAdapter.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/dba/oracle/OracleAdapter.java b/cayenne-server/src/main/java/org/apache/cayenne/dba/oracle/OracleAdapter.java
index 77f2be1..cc58f0d 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/dba/oracle/OracleAdapter.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/dba/oracle/OracleAdapter.java
@@ -21,11 +21,9 @@ package org.apache.cayenne.dba.oracle;
import org.apache.cayenne.CayenneRuntimeException;
import org.apache.cayenne.access.DataNode;
+import org.apache.cayenne.access.sqlbuilder.sqltree.Node;
import org.apache.cayenne.access.translator.ParameterBinding;
import org.apache.cayenne.access.translator.ejbql.EJBQLTranslatorFactory;
-import org.apache.cayenne.access.translator.select.QualifierTranslator;
-import org.apache.cayenne.access.translator.select.QueryAssembler;
-import org.apache.cayenne.access.translator.select.SelectTranslator;
import org.apache.cayenne.access.types.ByteType;
import org.apache.cayenne.access.types.ExtendedType;
import org.apache.cayenne.access.types.ExtendedTypeFactory;
@@ -39,12 +37,10 @@ import org.apache.cayenne.dba.PkGenerator;
import org.apache.cayenne.di.Inject;
import org.apache.cayenne.map.DbAttribute;
import org.apache.cayenne.map.DbEntity;
-import org.apache.cayenne.map.EntityResolver;
import org.apache.cayenne.query.BatchQuery;
import org.apache.cayenne.query.InsertBatchQuery;
import org.apache.cayenne.query.Query;
import org.apache.cayenne.query.SQLAction;
-import org.apache.cayenne.query.SelectQuery;
import org.apache.cayenne.query.UpdateBatchQuery;
import org.apache.cayenne.resource.ResourceLocator;
@@ -57,6 +53,7 @@ import java.sql.Types;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
+import java.util.function.Function;
/**
* DbAdapter implementation for <a href="http://www.oracle.com">Oracle RDBMS
@@ -171,11 +168,11 @@ public class OracleAdapter extends JdbcAdapter {
}
/**
- * @since 4.0
+ * @since 4.2
*/
@Override
- public SelectTranslator getSelectTranslator(SelectQuery<?> query, EntityResolver entityResolver) {
- return new OracleSelectTranslator(query, this, entityResolver);
+ public Function<Node, Node> getSqlTreeProcessor() {
+ return new OracleSQLTreeProcessor();
}
/**
@@ -281,16 +278,6 @@ public class OracleAdapter extends JdbcAdapter {
}
/**
- * Returns a trimming translator.
- */
- @Override
- public QualifierTranslator getQualifierTranslator(QueryAssembler queryAssembler) {
- QualifierTranslator translator = new Oracle8QualifierTranslator(queryAssembler);
- translator.setCaseInsensitive(caseInsensitiveCollations);
- return translator;
- }
-
- /**
* Uses OracleActionBuilder to create the right action.
*
* @since 1.2
http://git-wip-us.apache.org/repos/asf/cayenne/blob/f6b2dac9/cayenne-server/src/main/java/org/apache/cayenne/dba/oracle/OracleQualifierTranslator.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/dba/oracle/OracleQualifierTranslator.java b/cayenne-server/src/main/java/org/apache/cayenne/dba/oracle/OracleQualifierTranslator.java
deleted file mode 100644
index fcc76a4..0000000
--- a/cayenne-server/src/main/java/org/apache/cayenne/dba/oracle/OracleQualifierTranslator.java
+++ /dev/null
@@ -1,212 +0,0 @@
-/*****************************************************************
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- ****************************************************************/
-package org.apache.cayenne.dba.oracle;
-
-import org.apache.cayenne.access.translator.select.QueryAssembler;
-import org.apache.cayenne.access.translator.select.TrimmingQualifierTranslator;
-import org.apache.cayenne.exp.Expression;
-import org.apache.cayenne.exp.parser.ASTExtract;
-import org.apache.cayenne.exp.parser.ASTFunctionCall;
-import org.apache.cayenne.exp.parser.ASTIn;
-import org.apache.cayenne.exp.parser.ASTList;
-import org.apache.cayenne.exp.parser.ASTNotIn;
-import org.apache.cayenne.exp.parser.ASTPath;
-import org.apache.cayenne.exp.parser.Node;
-
-import java.util.ArrayList;
-import java.util.List;
-import java.util.function.Function;
-
-/**
- * Oracle qualifier translator. In particular, trims INs with more than 1000
- * elements to an OR-set of INs with <= 1000 elements
- */
-public class OracleQualifierTranslator extends TrimmingQualifierTranslator {
-
- public OracleQualifierTranslator(QueryAssembler queryAssembler) {
- super(queryAssembler, OracleAdapter.TRIM_FUNCTION);
- }
-
- @Override
- protected void doAppendPart(Expression rootNode) {
- if (rootNode == null) {
- return;
- }
- rootNode = rootNode.transform(new INTrimmer());
- rootNode.traverse(this);
- }
-
- public static class INTrimmer implements Function<Object, Object> {
-
- public Expression trimmedInExpression(Expression exp, int maxInSize) {
- Expression list = (Expression) exp.getOperand(1);
- Object[] objects = (Object[]) list.evaluate(null);
-
- if (objects.length <= maxInSize) {
- return exp;
- }
-
- Expression trimmed = trimmedInExpression((ASTPath) exp.getOperand(0), objects, maxInSize);
- if (exp instanceof ASTNotIn) {
- return trimmed.notExp();
- }
- return trimmed;
- }
-
- Expression trimmedInExpression(ASTPath path, Object[] values, int maxInSize) {
- Expression res = null;
-
- List<Object> in = new ArrayList<>(maxInSize);
- for (Object v : values) {
- in.add(v);
- if (in.size() == maxInSize) {
- Expression inExp = new ASTIn(path, new ASTList(in));
- res = res != null ? res.orExp(inExp) : inExp;
- in = new ArrayList<>(maxInSize);
- }
- }
- if (in.size() > 0) {
- Expression inExp = new ASTIn(path, new ASTList(in));
- res = res != null ? res.orExp(inExp) : inExp;
- }
- return res;
- }
-
- public Object apply(Object input) {
- if (input instanceof ASTIn || input instanceof ASTNotIn) {
- return trimmedInExpression((Expression) input, 1000);
- }
- return input;
- }
- }
-
- /**
- * @since 4.0
- */
- @Override
- public void endNode(Expression node, Expression parentNode) {
- super.endNode(node, parentNode);
- if(node.getType() == Expression.FUNCTION_CALL) {
- if("LOCATE".equals(((ASTFunctionCall)node).getFunctionName())) {
- // order of args in INSTR is different, so swap them back
- swapNodeChildren((ASTFunctionCall)node, 0, 1);
- }
- }
- }
-
- /**
- * @since 4.0
- */
- @Override
- protected void appendFunction(ASTFunctionCall functionExpression) {
- if("CONCAT".equals(functionExpression.getFunctionName())) {
- // CONCAT(x, y, z) -> (x || y || z)
- } else if("SUBSTRING".equals(functionExpression.getFunctionName())) {
- out.append("SUBSTR");
- } else if("LOCATE".equals(functionExpression.getFunctionName())) {
- // LOCATE(substr, str) -> INSTR(str, substr)
- out.append("INSTR");
- swapNodeChildren(functionExpression, 0, 1);
- } else if("CURRENT_TIME".equals(functionExpression.getFunctionName())) {
- out.append("{fn CURTIME()}");
- } else {
- super.appendFunction(functionExpression);
- }
- }
-
- /**
- * @since 4.0
- */
- @Override
- protected void appendFunctionArgDivider(ASTFunctionCall functionExpression) {
- if("CONCAT".equals(functionExpression.getFunctionName())) {
- out.append(" || ");
- } else {
- super.appendFunctionArgDivider(functionExpression);
- }
- }
-
- /**
- * @since 4.0
- */
- @Override
- protected void clearLastFunctionArgDivider(ASTFunctionCall functionExpression) {
- if("CONCAT".equals(functionExpression.getFunctionName())) {
- out.delete(out.length() - " || ".length(), out.length());
- } else {
- super.clearLastFunctionArgDivider(functionExpression);
- }
-
- if(functionExpression instanceof ASTExtract) {
- switch (((ASTExtract)functionExpression).getPart()) {
- case DAY_OF_YEAR:
- out.append(", 'DDD'");
- break;
- case DAY_OF_WEEK:
- out.append(", 'D'");
- break;
- case WEEK:
- out.append(", 'IW'");
- break;
- }
- out.append(")");
- }
- }
-
- @Override
- protected boolean parenthesisNeeded(Expression node, Expression parentNode) {
- if (node.getType() == Expression.FUNCTION_CALL) {
- if (node instanceof ASTExtract) {
- return false;
- }
- }
-
- return super.parenthesisNeeded(node, parentNode);
- }
-
- @Override
- protected void appendExtractFunction(ASTExtract functionExpression) {
- switch (functionExpression.getPart()) {
- // use TO_CHAR(date, format) function for parts that is unsupported by EXTRACT()
- case DAY_OF_YEAR:
- case DAY_OF_WEEK:
- case WEEK:
- out.append("TO_CHAR(");
- break;
- case DAY_OF_MONTH:
- out.append("EXTRACT(DAY FROM ");
- break;
- default:
- out.append("EXTRACT(");
- out.append(functionExpression.getPart().name());
- out.append(" FROM ");
- }
-
- }
-
- /**
- * @since 4.0
- */
- private void swapNodeChildren(Node node, int i, int j) {
- Node ni = node.jjtGetChild(i);
- Node nj = node.jjtGetChild(j);
- node.jjtAddChild(ni, j);
- node.jjtAddChild(nj, i);
- }
-}
http://git-wip-us.apache.org/repos/asf/cayenne/blob/f6b2dac9/cayenne-server/src/main/java/org/apache/cayenne/dba/oracle/OracleSQLTreeProcessor.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/dba/oracle/OracleSQLTreeProcessor.java b/cayenne-server/src/main/java/org/apache/cayenne/dba/oracle/OracleSQLTreeProcessor.java
new file mode 100644
index 0000000..ba84579
--- /dev/null
+++ b/cayenne-server/src/main/java/org/apache/cayenne/dba/oracle/OracleSQLTreeProcessor.java
@@ -0,0 +1,241 @@
+/*****************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ ****************************************************************/
+
+package org.apache.cayenne.dba.oracle;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.cayenne.access.sqlbuilder.ExpressionNodeBuilder;
+import org.apache.cayenne.access.sqlbuilder.QuotingAppendable;
+import org.apache.cayenne.access.sqlbuilder.SelectBuilder;
+import org.apache.cayenne.access.sqlbuilder.sqltree.ColumnNode;
+import org.apache.cayenne.access.sqlbuilder.sqltree.EmptyNode;
+import org.apache.cayenne.access.sqlbuilder.sqltree.FunctionNode;
+import org.apache.cayenne.access.sqlbuilder.sqltree.InNode;
+import org.apache.cayenne.access.sqlbuilder.sqltree.LimitOffsetNode;
+import org.apache.cayenne.access.sqlbuilder.sqltree.Node;
+import org.apache.cayenne.access.sqlbuilder.sqltree.NodeType;
+import org.apache.cayenne.access.sqlbuilder.sqltree.OpExpressionNode;
+import org.apache.cayenne.access.sqlbuilder.sqltree.TextNode;
+import org.apache.cayenne.access.sqlbuilder.sqltree.ValueNode;
+import org.apache.cayenne.access.sqlbuilder.sqltree.TrimmingColumnNode;
+import org.apache.cayenne.access.translator.select.BaseSQLTreeProcessor;
+import org.apache.cayenne.util.ArrayUtil;
+
+import static org.apache.cayenne.access.sqlbuilder.SQLBuilder.*;
+
+/**
+ * @since 4.2
+ */
+public class OracleSQLTreeProcessor extends BaseSQLTreeProcessor {
+
+ private static final int ORACLE_IN_BATCH_SIZE = 1000;
+
+ private SelectBuilder selectBuilder;
+
+ private Node root;
+
+ @Override
+ protected void onResultNode(Node parent, Node child, int index) {
+ for(int i=0; i<child.getChildrenCount(); i++) {
+ child.replaceChild(i, aliased(child.getChild(i), "c" + i).build());
+ }
+ }
+
+ @Override
+ protected void onColumnNode(Node parent, ColumnNode child, int index) {
+ replaceChild(parent, index, new TrimmingColumnNode(child));
+ }
+
+ @Override
+ protected void onLimitOffsetNode(Node parent, LimitOffsetNode child, int index) {
+ if(child.getLimit() > 0 || child.getOffset() > 0) {
+ int limit = child.getLimit();
+ int offset = child.getOffset();
+ int max = (limit <= 0) ? Integer.MAX_VALUE : limit + offset;
+
+ /*
+ Transform query with limit/offset into following form:
+ SELECT * FROM (
+ SELECT tid.*, ROWNUM rnum
+ FROM ( MAIN_QUERY ) tid
+ WHERE ROWNUM <= OFFSET + LIMIT
+ ) WHERE rnum > OFFSET
+ */
+ selectBuilder = select(all())
+ .from(select(text(" tid.*"), text(" ROWNUM rnum")) // using text not column to avoid quoting
+ .from(aliased(() -> root, "tid"))
+ .where(exp(text(" ROWNUM")).lte(value(max))))
+ .where(exp(text(" rnum")).gt(value(offset)));
+ }
+ parent.replaceChild(index, new EmptyNode());
+ }
+
+ @Override
+ protected void onInNode(Node parent, InNode child, int index) {
+ boolean not = child.isNot();
+ Node arg = child.getChild(0);
+ Node childNode = child.getChild(1);
+ if(childNode.getType() != NodeType.VALUE) {
+ return;
+ }
+
+ ValueNode valueNode = (ValueNode)childNode;
+ Object value = valueNode.getValue();
+ if(!value.getClass().isArray()) {
+ return;
+ }
+
+ List<Node> newChildren = new ArrayList<>();
+
+ // need to slice for batches of 1000 values
+ if(value instanceof Object[]) {
+ for(Object[] slice : ArrayUtil.sliceArray((Object[])value, ORACLE_IN_BATCH_SIZE)) {
+ newChildren.add(newSliceNode(child, arg, valueNode, slice));
+ }
+ } else if(value instanceof int[]) {
+ for(int[] slice : ArrayUtil.sliceArray((int[])value, ORACLE_IN_BATCH_SIZE)) {
+ newChildren.add(newSliceNode(child, arg, valueNode, slice));
+ }
+ } else if(value instanceof long[]) {
+ for(long[] slice : ArrayUtil.sliceArray((long[])value, ORACLE_IN_BATCH_SIZE)) {
+ newChildren.add(newSliceNode(child, arg, valueNode, slice));
+ }
+ } else if(value instanceof float[]) {
+ for(float[] slice : ArrayUtil.sliceArray((float[])value, ORACLE_IN_BATCH_SIZE)) {
+ newChildren.add(newSliceNode(child, arg, valueNode, slice));
+ }
+ } else if(value instanceof double[]) {
+ for(double[] slice : ArrayUtil.sliceArray((double[])value, ORACLE_IN_BATCH_SIZE)) {
+ newChildren.add(newSliceNode(child, arg, valueNode, slice));
+ }
+ } else if(value instanceof short[]) {
+ for(short[] slice : ArrayUtil.sliceArray((short[])value, ORACLE_IN_BATCH_SIZE)) {
+ newChildren.add(newSliceNode(child, arg, valueNode, slice));
+ }
+ } else if(value instanceof char[]) {
+ for(char[] slice : ArrayUtil.sliceArray((char[])value, ORACLE_IN_BATCH_SIZE)) {
+ newChildren.add(newSliceNode(child, arg, valueNode, slice));
+ }
+ } else if(value instanceof boolean[]) {
+ for(boolean[] slice : ArrayUtil.sliceArray((boolean[])value, ORACLE_IN_BATCH_SIZE)) {
+ newChildren.add(newSliceNode(child, arg, valueNode, slice));
+ }
+ } else if(value instanceof byte[]) {
+ for(byte[] slice : ArrayUtil.sliceArray((byte[])value, ORACLE_IN_BATCH_SIZE)) {
+ newChildren.add(newSliceNode(child, arg, valueNode, slice));
+ }
+ }
+
+ ExpressionNodeBuilder exp = exp(node(newChildren.get(0)));
+ for(int i=1; i<newChildren.size(); i++) {
+ if(not) {
+ exp = exp.and(node(newChildren.get(i)));
+ } else {
+ exp = exp.or(node(newChildren.get(i)));
+ }
+ }
+ parent.replaceChild(index, exp.build());
+ }
+
+ private InNode newSliceNode(InNode child, Node arg, ValueNode valueNode, Object slice) {
+ InNode nextNode = new InNode(child.isNot());
+ nextNode.addChild(arg.deepCopy());
+ nextNode.addChild(new ValueNode(slice, valueNode.isArray(), valueNode.getAttribute()));
+ return nextNode;
+ }
+
+ @Override
+ protected void onFunctionNode(Node parent, FunctionNode child, int index) {
+ String functionName = child.getFunctionName();
+ Node functionReplacement = null;
+ switch (functionName) {
+ case "LOCATE":
+ functionReplacement = new FunctionNode("INSTR", child.getAlias(), true);
+ for(int i=0; i<=1; i++) {
+ functionReplacement.addChild(child.getChild(1-i));
+ }
+ parent.replaceChild(index, functionReplacement);
+ return;
+
+ case "DAY_OF_YEAR":
+ case "DAY_OF_WEEK":
+ case "WEEK":
+ functionReplacement = new FunctionNode("TO_CHAR", child.getAlias(), true);
+ functionReplacement.addChild(child.getChild(0));
+ if("DAY_OF_YEAR".equals(functionName)) {
+ functionName = "'DDD'";
+ } else if("DAY_OF_WEEK".equals(functionName)) {
+ functionName = "'D'";
+ } else {
+ functionName = "'IW'";
+ }
+ functionReplacement.addChild(new TextNode(functionName));
+ parent.replaceChild(index, functionReplacement);
+ return;
+
+ case "SUBSTRING":
+ functionReplacement = new FunctionNode("SUBSTR", child.getAlias(), true);
+ break;
+ case "CONCAT":
+ functionReplacement = new OpExpressionNode("||");
+ break;
+ case "CURRENT_TIMESTAMP":
+ case "CURRENT_DATE":
+ functionReplacement = new FunctionNode(functionName, child.getAlias(), false);
+ break;
+ case "CURRENT_TIME":
+ functionReplacement = new FunctionNode("{fn CURTIME()}", child.getAlias(), false);
+ break;
+ case "YEAR":
+ case "MONTH":
+ case "DAY":
+ case "DAY_OF_MONTH":
+ case "HOUR":
+ case "MINUTE":
+ case "SECOND":
+ functionReplacement = new FunctionNode("EXTRACT", child.getAlias(), true) {
+ @Override
+ public void appendChildrenSeparator(QuotingAppendable buffer, int childIdx) {
+ buffer.append(' ');
+ }
+ };
+ if("DAY_OF_MONTH".equals(functionName)) {
+ functionName = "DAY";
+ }
+ functionReplacement.addChild(new TextNode(functionName + " FROM "));
+ break;
+ }
+
+ if(functionReplacement != null) {
+ replaceChild(parent, index, functionReplacement);
+ }
+ }
+
+ @Override
+ public Node apply(Node node) {
+ root = node;
+ super.apply(node);
+ if(selectBuilder != null) {
+ return selectBuilder.build();
+ }
+ return node;
+ }
+}
http://git-wip-us.apache.org/repos/asf/cayenne/blob/f6b2dac9/cayenne-server/src/main/java/org/apache/cayenne/dba/oracle/OracleSelectTranslator.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/dba/oracle/OracleSelectTranslator.java b/cayenne-server/src/main/java/org/apache/cayenne/dba/oracle/OracleSelectTranslator.java
deleted file mode 100644
index c98aad9..0000000
--- a/cayenne-server/src/main/java/org/apache/cayenne/dba/oracle/OracleSelectTranslator.java
+++ /dev/null
@@ -1,78 +0,0 @@
-/*****************************************************************
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- ****************************************************************/
-
-package org.apache.cayenne.dba.oracle;
-
-import java.util.List;
-
-import org.apache.cayenne.access.DataNode;
-import org.apache.cayenne.access.translator.select.DefaultSelectTranslator;
-import org.apache.cayenne.dba.DbAdapter;
-import org.apache.cayenne.map.EntityResolver;
-import org.apache.cayenne.query.Query;
-
-/**
- * Select translator that implements Oracle-specific optimizations.
- *
- */
-class OracleSelectTranslator extends DefaultSelectTranslator {
-
- /**
- * @since 4.0
- */
- public OracleSelectTranslator(Query query, DbAdapter adapter, EntityResolver entityResolver) {
- super(query, adapter, entityResolver);
- }
-
- @Override
- protected void appendLimitAndOffsetClauses(StringBuilder buffer) {
- int offset = queryMetadata.getFetchOffset();
- int limit = queryMetadata.getFetchLimit();
-
- if (limit > 0 || offset > 0) {
- int max = (limit <= 0) ? Integer.MAX_VALUE : limit + offset;
-
- buffer.insert(0, "select * from ( select tid.*, ROWNUM rnum from (");
- buffer.append(") tid where ROWNUM <=").append(max).append(") where rnum > ").append(offset);
- }
- }
-
- @Override
- protected void appendSelectColumns(StringBuilder buffer, List<String> selectColumnExpList) {
-
- // we need to add aliases to all columns to make fetch
- // limit and offset work properly on Oracle (see CAY-1266)
-
- // append columns (unroll the loop's first element)
- int columnCount = selectColumnExpList.size();
- buffer.append(selectColumnExpList.get(0));
- if(!selectColumnExpList.get(0).contains(" AS ")) {
- buffer.append(" AS c0");
- }
-
- // assume there is at least 1 element
- for (int i = 1; i < columnCount; i++) {
- buffer.append(", ");
- buffer.append(selectColumnExpList.get(i));
- if(!selectColumnExpList.get(i).contains(" AS ")) {
- buffer.append(" AS c").append(i);
- }
- }
- }
-}
http://git-wip-us.apache.org/repos/asf/cayenne/blob/f6b2dac9/cayenne-server/src/main/java/org/apache/cayenne/dba/postgres/PostgreSQLTreeProcessor.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/dba/postgres/PostgreSQLTreeProcessor.java b/cayenne-server/src/main/java/org/apache/cayenne/dba/postgres/PostgreSQLTreeProcessor.java
new file mode 100644
index 0000000..9bbc34e
--- /dev/null
+++ b/cayenne-server/src/main/java/org/apache/cayenne/dba/postgres/PostgreSQLTreeProcessor.java
@@ -0,0 +1,83 @@
+/*****************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ ****************************************************************/
+
+package org.apache.cayenne.dba.postgres;
+
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Set;
+
+import org.apache.cayenne.access.sqlbuilder.sqltree.ColumnNode;
+import org.apache.cayenne.access.sqlbuilder.sqltree.FunctionNode;
+import org.apache.cayenne.access.sqlbuilder.sqltree.LikeNode;
+import org.apache.cayenne.access.sqlbuilder.sqltree.LimitOffsetNode;
+import org.apache.cayenne.access.sqlbuilder.sqltree.Node;
+import org.apache.cayenne.access.sqlbuilder.sqltree.TrimmingColumnNode;
+import org.apache.cayenne.access.translator.select.BaseSQLTreeProcessor;
+import org.apache.cayenne.dba.postgres.sqltree.PositionFunctionNode;
+import org.apache.cayenne.dba.postgres.sqltree.PostgresExtractFunctionNode;
+import org.apache.cayenne.dba.postgres.sqltree.PostgresLikeNode;
+import org.apache.cayenne.dba.postgres.sqltree.PostgresLimitOffsetNode;
+
+/**
+ * @since 4.2
+ */
+public class PostgreSQLTreeProcessor extends BaseSQLTreeProcessor {
+
+ private static final Set<String> EXTRACT_FUNCTION_NAMES = new HashSet<>(Arrays.asList(
+ "DAY_OF_MONTH", "DAY", "MONTH", "HOUR", "WEEK", "YEAR", "DAY_OF_WEEK", "DAY_OF_YEAR", "MINUTE", "SECOND"
+ ));
+
+ @Override
+ protected void onLimitOffsetNode(Node parent, LimitOffsetNode child, int index) {
+ replaceChild(parent, index, new PostgresLimitOffsetNode(child), false);
+ }
+
+ @Override
+ protected void onColumnNode(Node parent, ColumnNode child, int index) {
+ replaceChild(parent, index, new TrimmingColumnNode(child));
+ }
+
+ @Override
+ protected void onLikeNode(Node parent, LikeNode child, int index) {
+ if(child.isIgnoreCase()) {
+ replaceChild(parent, index, new PostgresLikeNode(child.isNot(), child.getEscape()));
+ }
+ }
+
+ @Override
+ protected void onFunctionNode(Node parent, FunctionNode child, int index) {
+ Node replacement = null;
+ String functionName = child.getFunctionName();
+ if(EXTRACT_FUNCTION_NAMES.contains(functionName)) {
+ replacement = new PostgresExtractFunctionNode(functionName);
+ } else if("CURRENT_DATE".equals(functionName)
+ || "CURRENT_TIME".equals(functionName)
+ || "CURRENT_TIMESTAMP".equals(functionName)) {
+ replacement = new FunctionNode(functionName, child.getAlias(), false);
+ } else if("LOCATE".equals(functionName)) {
+ replacement = new PositionFunctionNode(child.getAlias());
+ }
+
+ if(replacement != null) {
+ replaceChild(parent, index, replacement);
+ }
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/cayenne/blob/f6b2dac9/cayenne-server/src/main/java/org/apache/cayenne/dba/postgres/PostgresAdapter.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/dba/postgres/PostgresAdapter.java b/cayenne-server/src/main/java/org/apache/cayenne/dba/postgres/PostgresAdapter.java
index dbee328..24033e1 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/dba/postgres/PostgresAdapter.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/dba/postgres/PostgresAdapter.java
@@ -21,10 +21,8 @@ package org.apache.cayenne.dba.postgres;
import org.apache.cayenne.CayenneRuntimeException;
import org.apache.cayenne.access.DataNode;
+import org.apache.cayenne.access.sqlbuilder.sqltree.Node;
import org.apache.cayenne.access.translator.ParameterBinding;
-import org.apache.cayenne.access.translator.select.QualifierTranslator;
-import org.apache.cayenne.access.translator.select.QueryAssembler;
-import org.apache.cayenne.access.translator.select.SelectTranslator;
import org.apache.cayenne.access.types.CharType;
import org.apache.cayenne.access.types.ExtendedType;
import org.apache.cayenne.access.types.ExtendedTypeFactory;
@@ -39,10 +37,8 @@ import org.apache.cayenne.dba.TypesMapping;
import org.apache.cayenne.di.Inject;
import org.apache.cayenne.map.DbAttribute;
import org.apache.cayenne.map.DbEntity;
-import org.apache.cayenne.map.EntityResolver;
import org.apache.cayenne.query.Query;
import org.apache.cayenne.query.SQLAction;
-import org.apache.cayenne.query.SelectQuery;
import org.apache.cayenne.resource.ResourceLocator;
import java.sql.PreparedStatement;
@@ -52,6 +48,7 @@ import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
+import java.util.function.Function;
/**
* DbAdapter implementation for <a href="http://www.postgresql.org">PostgreSQL
@@ -80,12 +77,12 @@ public class PostgresAdapter extends JdbcAdapter {
setSupportsGeneratedKeys(true);
}
- /**
- * @since 4.0
- */
+ /**
+ * @since 4.2
+ */
@Override
- public SelectTranslator getSelectTranslator(SelectQuery<?> query, EntityResolver entityResolver) {
- return new PostgresSelectTranslator(query, this, entityResolver);
+ public Function<Node, Node> getSqlTreeProcessor() {
+ return new PostgreSQLTreeProcessor();
}
/**
@@ -258,16 +255,6 @@ public class PostgresAdapter extends JdbcAdapter {
}
/**
- * Returns a trimming translator.
- */
- @Override
- public QualifierTranslator getQualifierTranslator(QueryAssembler queryAssembler) {
- QualifierTranslator translator = new PostgresQualifierTranslator(queryAssembler);
- translator.setCaseInsensitive(caseInsensitiveCollations);
- return translator;
- }
-
- /**
* @see JdbcAdapter#createPkGenerator()
*/
@Override
http://git-wip-us.apache.org/repos/asf/cayenne/blob/f6b2dac9/cayenne-server/src/main/java/org/apache/cayenne/dba/postgres/PostgresQualifierTranslator.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/dba/postgres/PostgresQualifierTranslator.java b/cayenne-server/src/main/java/org/apache/cayenne/dba/postgres/PostgresQualifierTranslator.java
deleted file mode 100644
index 3fdc22c..0000000
--- a/cayenne-server/src/main/java/org/apache/cayenne/dba/postgres/PostgresQualifierTranslator.java
+++ /dev/null
@@ -1,189 +0,0 @@
-/*****************************************************************
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- ****************************************************************/
-
-package org.apache.cayenne.dba.postgres;
-
-import java.io.IOException;
-
-import org.apache.cayenne.CayenneRuntimeException;
-import org.apache.cayenne.access.translator.select.QueryAssembler;
-import org.apache.cayenne.access.translator.select.TrimmingQualifierTranslator;
-import org.apache.cayenne.exp.Expression;
-import org.apache.cayenne.exp.parser.ASTExtract;
-import org.apache.cayenne.exp.parser.ASTFunctionCall;
-import org.apache.cayenne.exp.parser.PatternMatchNode;
-
-/**
- * Uses Postgres extensions to optimize various translations.
- *
- * @since 1.1
- */
-public class PostgresQualifierTranslator extends TrimmingQualifierTranslator {
-
- public PostgresQualifierTranslator(QueryAssembler queryAssembler) {
- super(queryAssembler, "RTRIM");
- }
-
- @Override
- public void startNode(Expression node, Expression parentNode) {
- // super implementation has special handling of LIKE_IGNORE_CASE and NOT_LIKE_IGNORE_CASE
- // Postgres uses ILIKE
- boolean likeIgnoreCase = (node.getType() == Expression.LIKE_IGNORE_CASE || node.getType() == Expression.NOT_LIKE_IGNORE_CASE);
- if (likeIgnoreCase) {
- // binary nodes are the only ones that currently require this
- detectObjectMatch(node);
- if (parenthesisNeeded(node, parentNode)) {
- out.append('(');
- }
- } else {
- super.startNode(node, parentNode);
- }
- }
-
- @Override
- public void endNode(Expression node, Expression parentNode) {
- // super implementation has special handling of LIKE_IGNORE_CASE and NOT_LIKE_IGNORE_CASE
- // Postgres uses ILIKE
- boolean likeIgnoreCase = (node.getType() == Expression.LIKE_IGNORE_CASE || node.getType() == Expression.NOT_LIKE_IGNORE_CASE);
-
- if (likeIgnoreCase) {
- try {
- if (matchingObject) {
- appendObjectMatch();
- }
-
- if (PatternMatchNode.class.isAssignableFrom(node.getClass())) {
- appendLikeEscapeCharacter((PatternMatchNode) node);
- }
-
- if (parenthesisNeeded(node, parentNode)) {
- out.append(')');
- }
- } catch (IOException ioex) {
- throw new CayenneRuntimeException("Error appending content", ioex);
- }
- } else {
- super.endNode(node, parentNode);
- }
- }
-
- @Override
- public void finishedChild(Expression node, int childIndex, boolean hasMoreChildren) {
- if (!hasMoreChildren) {
- return;
- }
-
- try {
- // use ILIKE
- switch (node.getType()) {
- case Expression.LIKE_IGNORE_CASE:
- finishedChildNodeAppendExpression(node, " ILIKE ");
- break;
- case Expression.NOT_LIKE_IGNORE_CASE:
- finishedChildNodeAppendExpression(node, " NOT ILIKE ");
- break;
- default:
- super.finishedChild(node, childIndex, hasMoreChildren);
- }
- } catch (IOException ioex) {
- throw new CayenneRuntimeException("Error appending content", ioex);
- }
- }
-
- private void finishedChildNodeAppendExpression(Expression node, String operation) throws IOException {
- Appendable buf = matchingObject ? new StringBuilder() : this.out;
- buf.append(operation);
- if (matchingObject) {
- objectMatchTranslator.setOperation(buf.toString());
- objectMatchTranslator.setExpression(node);
- }
- }
-
- /**
- * @since 4.0
- */
- @Override
- protected void appendFunction(ASTFunctionCall functionExpression) {
- if("LOCATE".equals(functionExpression.getFunctionName())) {
- out.append("POSITION");
- } else {
- super.appendFunction(functionExpression);
- }
- }
-
- /**
- * @since 4.0
- */
- @Override
- protected void appendFunctionArgDivider(ASTFunctionCall functionExpression) {
- if("LOCATE".equals(functionExpression.getFunctionName())) {
- out.append(" in ");
- } else {
- super.appendFunctionArgDivider(functionExpression);
- }
- }
-
- /**
- * @since 4.0
- */
- @Override
- protected void clearLastFunctionArgDivider(ASTFunctionCall functionExpression) {
- if("LOCATE".equals(functionExpression.getFunctionName())) {
- out.delete(out.length() - " in ".length(), out.length());
- } else {
- super.clearLastFunctionArgDivider(functionExpression);
- }
-
- if(functionExpression instanceof ASTExtract) {
- out.append(")");
- }
- }
-
- @Override
- protected boolean parenthesisNeeded(Expression node, Expression parentNode) {
- if (node.getType() == Expression.FUNCTION_CALL) {
- if (node instanceof ASTExtract) {
- return false;
- }
- }
-
- return super.parenthesisNeeded(node, parentNode);
- }
-
-
- @Override
- protected void appendExtractFunction(ASTExtract functionExpression) {
- out.append("EXTRACT(");
- switch (functionExpression.getPart()) {
- case DAY_OF_MONTH:
- out.append("day");
- break;
- case DAY_OF_WEEK:
- out.append("dow");
- break;
- case DAY_OF_YEAR:
- out.append("doy");
- break;
- default:
- out.append(functionExpression.getPartCamelCaseName());
- }
-
- out.append(" FROM ");
- }
-}
http://git-wip-us.apache.org/repos/asf/cayenne/blob/f6b2dac9/cayenne-server/src/main/java/org/apache/cayenne/dba/postgres/PostgresSelectTranslator.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/dba/postgres/PostgresSelectTranslator.java b/cayenne-server/src/main/java/org/apache/cayenne/dba/postgres/PostgresSelectTranslator.java
deleted file mode 100644
index 8d1eded..0000000
--- a/cayenne-server/src/main/java/org/apache/cayenne/dba/postgres/PostgresSelectTranslator.java
+++ /dev/null
@@ -1,54 +0,0 @@
-/*****************************************************************
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- ****************************************************************/
-
-package org.apache.cayenne.dba.postgres;
-
-import org.apache.cayenne.access.translator.select.DefaultSelectTranslator;
-import org.apache.cayenne.dba.DbAdapter;
-import org.apache.cayenne.map.EntityResolver;
-import org.apache.cayenne.query.Query;
-
-/**
- * @since 1.2
- */
-class PostgresSelectTranslator extends DefaultSelectTranslator {
-
- /**
- * @since 4.0
- */
- public PostgresSelectTranslator(Query query, DbAdapter adapter, EntityResolver entityResolver) {
- super(query, adapter, entityResolver);
- }
-
- @Override
- protected void appendLimitAndOffsetClauses(StringBuilder buffer) {
-
- // limit results
- int offset = queryMetadata.getFetchOffset();
- int limit = queryMetadata.getFetchLimit();
-
- if (limit > 0) {
- buffer.append(" LIMIT ").append(limit);
- }
-
- if (offset > 0) {
- buffer.append(" OFFSET ").append(offset);
- }
- }
-}
http://git-wip-us.apache.org/repos/asf/cayenne/blob/f6b2dac9/cayenne-server/src/main/java/org/apache/cayenne/dba/postgres/sqltree/PositionFunctionNode.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/dba/postgres/sqltree/PositionFunctionNode.java b/cayenne-server/src/main/java/org/apache/cayenne/dba/postgres/sqltree/PositionFunctionNode.java
new file mode 100644
index 0000000..39c1df7
--- /dev/null
+++ b/cayenne-server/src/main/java/org/apache/cayenne/dba/postgres/sqltree/PositionFunctionNode.java
@@ -0,0 +1,43 @@
+/*****************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ ****************************************************************/
+
+package org.apache.cayenne.dba.postgres.sqltree;
+
+import org.apache.cayenne.access.sqlbuilder.QuotingAppendable;
+import org.apache.cayenne.access.sqlbuilder.sqltree.FunctionNode;
+import org.apache.cayenne.access.sqlbuilder.sqltree.Node;
+
+/**
+ * @since 4.2
+ */
+public class PositionFunctionNode extends FunctionNode {
+ public PositionFunctionNode(String alias) {
+ super("POSITION", alias, true);
+ }
+
+ @Override
+ public void appendChildrenSeparator(QuotingAppendable buffer, int childIdx) {
+ buffer.append(" IN ");
+ }
+
+ @Override
+ public Node copy() {
+ return new PositionFunctionNode(getAlias());
+ }
+}
http://git-wip-us.apache.org/repos/asf/cayenne/blob/f6b2dac9/cayenne-server/src/main/java/org/apache/cayenne/dba/postgres/sqltree/PostgresExtractFunctionNode.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/dba/postgres/sqltree/PostgresExtractFunctionNode.java b/cayenne-server/src/main/java/org/apache/cayenne/dba/postgres/sqltree/PostgresExtractFunctionNode.java
new file mode 100644
index 0000000..ff4224b
--- /dev/null
+++ b/cayenne-server/src/main/java/org/apache/cayenne/dba/postgres/sqltree/PostgresExtractFunctionNode.java
@@ -0,0 +1,59 @@
+/*****************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ ****************************************************************/
+
+package org.apache.cayenne.dba.postgres.sqltree;
+
+import org.apache.cayenne.access.sqlbuilder.QuotingAppendable;
+import org.apache.cayenne.access.sqlbuilder.sqltree.Node;
+
+/**
+ * @since 4.2
+ */
+public class PostgresExtractFunctionNode extends Node {
+ private final String functionName;
+
+ public PostgresExtractFunctionNode(String functionName) {
+ this.functionName = functionName;
+ }
+
+ @Override
+ public QuotingAppendable append(QuotingAppendable buffer) {
+ buffer.append(" EXTRACT(");
+ if ("DAY_OF_MONTH".equals(functionName)) {
+ buffer.append("day");
+ } else if ("DAY_OF_WEEK".equals(functionName)) {
+ buffer.append("dow");
+ } else if ("DAY_OF_YEAR".equals(functionName)) {
+ buffer.append("doy");
+ } else {
+ buffer.append(functionName);
+ }
+ return buffer.append(" FROM ");
+ }
+
+ @Override
+ public void appendChildrenEnd(QuotingAppendable buffer) {
+ buffer.append(")");
+ }
+
+ @Override
+ public Node copy() {
+ return new PostgresExtractFunctionNode(functionName);
+ }
+}
http://git-wip-us.apache.org/repos/asf/cayenne/blob/f6b2dac9/cayenne-server/src/main/java/org/apache/cayenne/dba/postgres/sqltree/PostgresLikeNode.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/dba/postgres/sqltree/PostgresLikeNode.java b/cayenne-server/src/main/java/org/apache/cayenne/dba/postgres/sqltree/PostgresLikeNode.java
new file mode 100644
index 0000000..4f0e236
--- /dev/null
+++ b/cayenne-server/src/main/java/org/apache/cayenne/dba/postgres/sqltree/PostgresLikeNode.java
@@ -0,0 +1,46 @@
+/*****************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ ****************************************************************/
+
+package org.apache.cayenne.dba.postgres.sqltree;
+
+import org.apache.cayenne.access.sqlbuilder.QuotingAppendable;
+import org.apache.cayenne.access.sqlbuilder.sqltree.LikeNode;
+import org.apache.cayenne.access.sqlbuilder.sqltree.Node;
+
+/**
+ * @since 4.2
+ */
+public class PostgresLikeNode extends LikeNode {
+ public PostgresLikeNode(boolean isNot, char escape) {
+ super(false, isNot, escape);
+ }
+
+ @Override
+ public void appendChildrenSeparator(QuotingAppendable buffer, int childIdx) {
+ if (not) {
+ buffer.append(" NOT");
+ }
+ buffer.append(" ILIKE ");
+ }
+
+ @Override
+ public Node copy() {
+ return new PostgresLikeNode(isNot(), getEscape());
+ }
+}
http://git-wip-us.apache.org/repos/asf/cayenne/blob/f6b2dac9/cayenne-server/src/main/java/org/apache/cayenne/dba/postgres/sqltree/PostgresLimitOffsetNode.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/dba/postgres/sqltree/PostgresLimitOffsetNode.java b/cayenne-server/src/main/java/org/apache/cayenne/dba/postgres/sqltree/PostgresLimitOffsetNode.java
new file mode 100644
index 0000000..045091a
--- /dev/null
+++ b/cayenne-server/src/main/java/org/apache/cayenne/dba/postgres/sqltree/PostgresLimitOffsetNode.java
@@ -0,0 +1,60 @@
+/*****************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ ****************************************************************/
+
+package org.apache.cayenne.dba.postgres.sqltree;
+
+import org.apache.cayenne.access.sqlbuilder.QuotingAppendable;
+import org.apache.cayenne.access.sqlbuilder.sqltree.LimitOffsetNode;
+import org.apache.cayenne.access.sqlbuilder.sqltree.Node;
+
+/**
+ * @since 4.2
+ */
+public class PostgresLimitOffsetNode extends LimitOffsetNode {
+
+ public PostgresLimitOffsetNode(LimitOffsetNode node) {
+ super(node.getLimit(), node.getOffset());
+ }
+
+ private PostgresLimitOffsetNode(int limit, int offset) {
+ super(limit, offset);
+ }
+
+ @Override
+ public QuotingAppendable append(QuotingAppendable buffer) {
+ if(limit == 0 && offset == 0) {
+ return buffer;
+ }
+
+ if(limit > 0) {
+ buffer.append(" LIMIT ").append(limit);
+ }
+
+ if(offset > 0) {
+ buffer.append(" OFFSET ").append(offset);
+ }
+
+ return buffer;
+ }
+
+ @Override
+ public Node copy() {
+ return new PostgresLimitOffsetNode(limit, offset);
+ }
+}
http://git-wip-us.apache.org/repos/asf/cayenne/blob/f6b2dac9/cayenne-server/src/main/java/org/apache/cayenne/dba/sqlite/SQLiteActionBuilder.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/dba/sqlite/SQLiteActionBuilder.java b/cayenne-server/src/main/java/org/apache/cayenne/dba/sqlite/SQLiteActionBuilder.java
index 5f5cdeb..c4cc53c 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/dba/sqlite/SQLiteActionBuilder.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/dba/sqlite/SQLiteActionBuilder.java
@@ -22,6 +22,7 @@ import org.apache.cayenne.access.DataNode;
import org.apache.cayenne.dba.JdbcActionBuilder;
import org.apache.cayenne.query.SQLAction;
import org.apache.cayenne.query.SQLTemplate;
+import org.apache.cayenne.query.SelectQuery;
/**
* @since 3.0
@@ -36,4 +37,12 @@ class SQLiteActionBuilder extends JdbcActionBuilder {
public SQLAction sqlAction(SQLTemplate query) {
return new SQLiteSQLTemplateAction(query, dataNode);
}
+
+ /**
+ * @since 4.1
+ */
+ @Override
+ public <T> SQLAction objectSelectAction(SelectQuery<T> query) {
+ return new SQLiteSelectAction(query, dataNode);
+ }
}
http://git-wip-us.apache.org/repos/asf/cayenne/blob/f6b2dac9/cayenne-server/src/main/java/org/apache/cayenne/dba/sqlite/SQLiteAdapter.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/dba/sqlite/SQLiteAdapter.java b/cayenne-server/src/main/java/org/apache/cayenne/dba/sqlite/SQLiteAdapter.java
index 2542282..20943ee 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/dba/sqlite/SQLiteAdapter.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/dba/sqlite/SQLiteAdapter.java
@@ -19,8 +19,7 @@
package org.apache.cayenne.dba.sqlite;
import org.apache.cayenne.access.DataNode;
-import org.apache.cayenne.access.translator.select.QualifierTranslator;
-import org.apache.cayenne.access.translator.select.QueryAssembler;
+import org.apache.cayenne.access.sqlbuilder.sqltree.Node;
import org.apache.cayenne.access.types.ExtendedType;
import org.apache.cayenne.access.types.ExtendedTypeFactory;
import org.apache.cayenne.access.types.ExtendedTypeMap;
@@ -36,11 +35,11 @@ import org.apache.cayenne.query.Query;
import org.apache.cayenne.query.SQLAction;
import org.apache.cayenne.resource.ResourceLocator;
-import java.sql.Types;
import java.util.Calendar;
import java.util.Collection;
import java.util.GregorianCalendar;
import java.util.List;
+import java.util.function.Function;
/**
* A SQLite database adapter that works with Zentus JDBC driver. See
@@ -87,11 +86,11 @@ public class SQLiteAdapter extends JdbcAdapter {
}
/**
- * @since 4.0
+ * @since 4.2
*/
@Override
- public QualifierTranslator getQualifierTranslator(QueryAssembler queryAssembler) {
- return new SQLiteQualifierTranslator(queryAssembler);
+ public Function<Node, Node> getSqlTreeProcessor() {
+ return new SQLiteTreeProcessor();
}
@Override
@@ -112,18 +111,6 @@ public class SQLiteAdapter extends JdbcAdapter {
return query.createSQLAction(new SQLiteActionBuilder(node));
}
- private int mapNTypes(int sqlType) {
- switch (sqlType) {
- case Types.NCHAR : return Types.CHAR;
- case Types.NCLOB : return Types.CLOB;
- case Types.NVARCHAR : return Types.VARCHAR;
- case Types.LONGNVARCHAR : return Types.LONGVARCHAR;
-
- default:
- return sqlType;
- }
- }
-
/**
* Appends AUTOINCREMENT clause to the column definition for generated columns.
*/
http://git-wip-us.apache.org/repos/asf/cayenne/blob/f6b2dac9/cayenne-server/src/main/java/org/apache/cayenne/dba/sqlite/SQLiteQualifierTranslator.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/dba/sqlite/SQLiteQualifierTranslator.java b/cayenne-server/src/main/java/org/apache/cayenne/dba/sqlite/SQLiteQualifierTranslator.java
deleted file mode 100644
index d31019b..0000000
--- a/cayenne-server/src/main/java/org/apache/cayenne/dba/sqlite/SQLiteQualifierTranslator.java
+++ /dev/null
@@ -1,162 +0,0 @@
-/*****************************************************************
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- ****************************************************************/
-
-package org.apache.cayenne.dba.sqlite;
-
-import org.apache.cayenne.access.translator.select.QualifierTranslator;
-import org.apache.cayenne.access.translator.select.QueryAssembler;
-import org.apache.cayenne.exp.Expression;
-import org.apache.cayenne.exp.parser.ASTExtract;
-import org.apache.cayenne.exp.parser.ASTFunctionCall;
-import org.apache.cayenne.exp.parser.Node;
-
-/**
- * @since 4.0
- */
-public class SQLiteQualifierTranslator extends QualifierTranslator {
-
- public SQLiteQualifierTranslator(QueryAssembler queryAssembler) {
- super(queryAssembler);
- }
-
- @Override
- public void endNode(Expression node, Expression parentNode) {
- super.endNode(node, parentNode);
- if(node.getType() == Expression.FUNCTION_CALL) {
- if("LOCATE".equals(((ASTFunctionCall)node).getFunctionName())) {
- // order of args in INSTR is different, so swap them back
- swapNodeChildren((ASTFunctionCall)node, 0, 1);
- }
- }
- }
-
- @Override
- protected void appendFunction(ASTFunctionCall functionExpression) {
- switch (functionExpression.getFunctionName()) {
- case "MOD":
- case "CONCAT":
- // noop
- break;
- case "SUBSTRING":
- out.append("SUBSTR");
- break;
- case "LOCATE":
- // LOCATE(substr, str) -> INSTR(str, substr)
- out.append("INSTR");
- swapNodeChildren(functionExpression, 0, 1);
- break;
- default:
- super.appendFunction(functionExpression);
- }
- }
-
- @Override
- protected void appendFunctionArgDivider(ASTFunctionCall functionExpression) {
- switch (functionExpression.getFunctionName()) {
- case "MOD":
- out.append(" % ");
- break;
- case "CONCAT":
- out.append(" || ");
- break;
- default:
- super.appendFunctionArgDivider(functionExpression);
- }
- }
-
- @Override
- protected void clearLastFunctionArgDivider(ASTFunctionCall functionExpression) {
- switch (functionExpression.getFunctionName()) {
- case "MOD":
- out.delete(out.length() - 3, out.length());
- break;
- case "CONCAT":
- out.delete(out.length() - 4, out.length());
- break;
- default:
- super.clearLastFunctionArgDivider(functionExpression);
- }
- if(functionExpression instanceof ASTExtract) {
- out.append(") as integer)");
- }
- }
-
- @Override
- protected boolean parenthesisNeeded(Expression node, Expression parentNode) {
- if (node.getType() == Expression.FUNCTION_CALL) {
- if (node instanceof ASTExtract) {
- return false;
- }
- }
-
- return super.parenthesisNeeded(node, parentNode);
- }
-
-
- /**
- * Translates to cast(strftime('format', column) as integer).
- * Depends on connection property "date_class", can be set in connection URL (date_class=text).
- *
- * https://www.sqlite.org/lang_datefunc.html
- */
- @Override
- protected void appendExtractFunction(ASTExtract functionExpression) {
- out.append("cast(strftime(");
-
- switch (functionExpression.getPart()) {
- case YEAR:
- out.append("'%Y'");
- break;
- case MONTH:
- out.append("'%m'");
- break;
- case WEEK:
- out.append("'%W'");
- break;
- case DAY:
- case DAY_OF_MONTH:
- out.append("'%d'");
- break;
- case DAY_OF_WEEK:
- out.append("'%w'");
- break;
- case DAY_OF_YEAR:
- out.append("'%j'");
- break;
- case HOUR:
- out.append("'%H'");
- break;
- case MINUTE:
- out.append("'%M'");
- break;
- case SECOND:
- out.append("'%S'");
- break;
- }
-
- out.append(", ");
- }
-
- private void swapNodeChildren(Node node, int i, int j) {
- Node ni = node.jjtGetChild(i);
- Node nj = node.jjtGetChild(j);
- node.jjtAddChild(ni, j);
- node.jjtAddChild(nj, i);
- }
-}
[05/11] cayenne git commit: CAY-2465 New SelectTranslator
implementation
Posted by nt...@apache.org.
http://git-wip-us.apache.org/repos/asf/cayenne/blob/f6b2dac9/cayenne-server/src/main/java/org/apache/cayenne/dba/firebird/FirebirdSQLTreeProcessor.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/dba/firebird/FirebirdSQLTreeProcessor.java b/cayenne-server/src/main/java/org/apache/cayenne/dba/firebird/FirebirdSQLTreeProcessor.java
new file mode 100644
index 0000000..e97fd2e
--- /dev/null
+++ b/cayenne-server/src/main/java/org/apache/cayenne/dba/firebird/FirebirdSQLTreeProcessor.java
@@ -0,0 +1,189 @@
+/*****************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ ****************************************************************/
+
+package org.apache.cayenne.dba.firebird;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.cayenne.access.sqlbuilder.ExpressionNodeBuilder;
+import org.apache.cayenne.access.sqlbuilder.QuotingAppendable;
+import org.apache.cayenne.access.sqlbuilder.sqltree.FunctionNode;
+import org.apache.cayenne.access.sqlbuilder.sqltree.InNode;
+import org.apache.cayenne.access.sqlbuilder.sqltree.LimitOffsetNode;
+import org.apache.cayenne.access.sqlbuilder.sqltree.Node;
+import org.apache.cayenne.access.sqlbuilder.sqltree.NodeType;
+import org.apache.cayenne.access.sqlbuilder.sqltree.OpExpressionNode;
+import org.apache.cayenne.access.sqlbuilder.sqltree.TextNode;
+import org.apache.cayenne.access.sqlbuilder.sqltree.ValueNode;
+import org.apache.cayenne.access.translator.select.BaseSQLTreeProcessor;
+import org.apache.cayenne.dba.firebird.sqltree.FirebirdLimitNode;
+import org.apache.cayenne.dba.firebird.sqltree.FirebirdSubstringFunctionNode;
+import org.apache.cayenne.util.ArrayUtil;
+
+import static org.apache.cayenne.access.sqlbuilder.SQLBuilder.exp;
+import static org.apache.cayenne.access.sqlbuilder.SQLBuilder.node;
+
+/**
+ * @since 4.2
+ */
+public class FirebirdSQLTreeProcessor extends BaseSQLTreeProcessor {
+
+ private static final int FIREBIRD_IN_BATCH_SIZE = 1500;
+
+ @Override
+ protected void onValueNode(Node parent, ValueNode child, int index) {
+ replaceChild(parent, index, new ValueNode(child.getValue(), child.isArray(), child.getAttribute()) {
+ @Override
+ protected void appendStringValue(QuotingAppendable buffer, CharSequence value) {
+ buffer.append("CAST(");
+ super.appendStringValue(buffer, value);
+ buffer.append(" AS VARCHAR(").append(value.length()).append("))");
+ }
+ });
+ }
+
+ @Override
+ protected void onLimitOffsetNode(Node parent, LimitOffsetNode child, int index) {
+ if(child.getLimit() == 0 && child.getOffset() == 0) {
+ return;
+ }
+ int from = child.getOffset() + 1;
+ int to = child.getLimit() == 0 ? Integer.MAX_VALUE : from + child.getLimit();
+ replaceChild(parent, index, new FirebirdLimitNode(from, to));
+ }
+
+ @Override
+ protected void onInNode(Node parent, InNode child, int index) {
+ Node arg = child.getChild(0);
+ Node childNode = child.getChild(1);
+ if(childNode.getType() != NodeType.VALUE) {
+ return;
+ }
+
+ ValueNode valueNode = (ValueNode)childNode;
+ Object value = valueNode.getValue();
+ if(!value.getClass().isArray()) {
+ return;
+ }
+
+ List<Node> newChildren = new ArrayList<>();
+
+ // need to slice for batches of 1500 values
+ if(value instanceof Object[]) {
+ for(Object[] slice : ArrayUtil.sliceArray((Object[])value, FIREBIRD_IN_BATCH_SIZE)) {
+ newChildren.add(newSliceNode(child, arg, valueNode, slice));
+ }
+ } else if(value instanceof int[]) {
+ for(int[] slice : ArrayUtil.sliceArray((int[])value, FIREBIRD_IN_BATCH_SIZE)) {
+ newChildren.add(newSliceNode(child, arg, valueNode, slice));
+ }
+ } else if(value instanceof long[]) {
+ for(long[] slice : ArrayUtil.sliceArray((long[])value, FIREBIRD_IN_BATCH_SIZE)) {
+ newChildren.add(newSliceNode(child, arg, valueNode, slice));
+ }
+ } else if(value instanceof float[]) {
+ for(float[] slice : ArrayUtil.sliceArray((float[])value, FIREBIRD_IN_BATCH_SIZE)) {
+ newChildren.add(newSliceNode(child, arg, valueNode, slice));
+ }
+ } else if(value instanceof double[]) {
+ for(double[] slice : ArrayUtil.sliceArray((double[])value, FIREBIRD_IN_BATCH_SIZE)) {
+ newChildren.add(newSliceNode(child, arg, valueNode, slice));
+ }
+ } else if(value instanceof short[]) {
+ for(short[] slice : ArrayUtil.sliceArray((short[])value, FIREBIRD_IN_BATCH_SIZE)) {
+ newChildren.add(newSliceNode(child, arg, valueNode, slice));
+ }
+ } else if(value instanceof char[]) {
+ for(char[] slice : ArrayUtil.sliceArray((char[])value, FIREBIRD_IN_BATCH_SIZE)) {
+ newChildren.add(newSliceNode(child, arg, valueNode, slice));
+ }
+ } else if(value instanceof boolean[]) {
+ for(boolean[] slice : ArrayUtil.sliceArray((boolean[])value, FIREBIRD_IN_BATCH_SIZE)) {
+ newChildren.add(newSliceNode(child, arg, valueNode, slice));
+ }
+ } else if(value instanceof byte[]) {
+ for(byte[] slice : ArrayUtil.sliceArray((byte[])value, FIREBIRD_IN_BATCH_SIZE)) {
+ newChildren.add(newSliceNode(child, arg, valueNode, slice));
+ }
+ }
+
+ ExpressionNodeBuilder exp = exp(node(newChildren.get(0)));
+ for(int i=1; i<newChildren.size(); i++) {
+ exp = exp.or(node(newChildren.get(i)));
+ }
+ parent.replaceChild(index, exp.build());
+ }
+
+ private InNode newSliceNode(InNode child, Node arg, ValueNode valueNode, Object slice) {
+ InNode nextNode = new InNode(child.isNot());
+ nextNode.addChild(arg.deepCopy());
+ nextNode.addChild(new ValueNode(slice, valueNode.isArray(), valueNode.getAttribute()));
+ return nextNode;
+ }
+
+ @Override
+ protected void onFunctionNode(Node parent, FunctionNode child, int index) {
+ switch (child.getFunctionName()) {
+ case "LENGTH":
+ replaceChild(parent, index, new FunctionNode("CHAR_LENGTH", child.getAlias()));
+ break;
+ case "LOCATE":
+ replaceChild(parent, index, new FunctionNode("POSITION", child.getAlias()));
+ break;
+ case "CONCAT":
+ replaceChild(parent, index, new OpExpressionNode("||"));
+ break;
+
+ case "SUBSTRING":
+ replaceChild(parent, index, new FirebirdSubstringFunctionNode(child.getAlias()));
+ break;
+
+ case "YEAR":
+ case "MONTH":
+ case "DAY":
+ case "DAY_OF_MONTH":
+ case "DAY_OF_WEEK":
+ case "DAY_OF_YEAR":
+ case "WEEK":
+ case "HOUR":
+ case "MINUTE":
+ case "SECOND":
+ Node functionReplacement = new FunctionNode("EXTRACT", child.getAlias(), true) {
+ @Override
+ public void appendChildrenSeparator(QuotingAppendable buffer, int childIdx) {
+ buffer.append(' ');
+ }
+ };
+
+ String partName = child.getFunctionName();
+ if("DAY_OF_MONTH".equals(partName)) {
+ partName = "DAY";
+ } else if("DAY_OF_WEEK".equals(partName)) {
+ partName = "WEEKDAY";
+ } else if("DAY_OF_YEAR".equals(partName)) {
+ partName = "YEARDAY";
+ }
+ functionReplacement.addChild(new TextNode(partName + " FROM "));
+ replaceChild(parent, index, functionReplacement);
+ break;
+ }
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/cayenne/blob/f6b2dac9/cayenne-server/src/main/java/org/apache/cayenne/dba/firebird/FirebirdSelectAction.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/dba/firebird/FirebirdSelectAction.java b/cayenne-server/src/main/java/org/apache/cayenne/dba/firebird/FirebirdSelectAction.java
new file mode 100644
index 0000000..184a909
--- /dev/null
+++ b/cayenne-server/src/main/java/org/apache/cayenne/dba/firebird/FirebirdSelectAction.java
@@ -0,0 +1,40 @@
+/*****************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ ****************************************************************/
+
+package org.apache.cayenne.dba.firebird;
+
+import org.apache.cayenne.access.DataNode;
+import org.apache.cayenne.access.jdbc.SelectAction;
+import org.apache.cayenne.query.SQLAction;
+import org.apache.cayenne.query.SelectQuery;
+
+/**
+ * @since 4.1
+ */
+public class FirebirdSelectAction extends SelectAction {
+
+ public FirebirdSelectAction(SelectQuery<?> query, DataNode dataNode) {
+ super(query, dataNode);
+ }
+
+ @Override
+ protected int getInMemoryOffset(int queryOffset) {
+ return 0;
+ }
+}
http://git-wip-us.apache.org/repos/asf/cayenne/blob/f6b2dac9/cayenne-server/src/main/java/org/apache/cayenne/dba/firebird/sqltree/FirebirdLimitNode.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/dba/firebird/sqltree/FirebirdLimitNode.java b/cayenne-server/src/main/java/org/apache/cayenne/dba/firebird/sqltree/FirebirdLimitNode.java
new file mode 100644
index 0000000..9572e79
--- /dev/null
+++ b/cayenne-server/src/main/java/org/apache/cayenne/dba/firebird/sqltree/FirebirdLimitNode.java
@@ -0,0 +1,50 @@
+/*****************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ ****************************************************************/
+
+package org.apache.cayenne.dba.firebird.sqltree;
+
+import org.apache.cayenne.access.sqlbuilder.QuotingAppendable;
+import org.apache.cayenne.access.sqlbuilder.sqltree.Node;
+
+/**
+ * @since 4.2
+ */
+public class FirebirdLimitNode extends Node {
+ private final int from;
+ private final int to;
+
+ public FirebirdLimitNode(int from, int to) {
+ this.from = from;
+ this.to = to;
+ }
+
+ @Override
+ public QuotingAppendable append(QuotingAppendable buffer) {
+ buffer.append(" ROWS ");
+ if(from > 0) {
+ buffer.append(from).append(" TO ");
+ }
+ return buffer.append(to);
+ }
+
+ @Override
+ public Node copy() {
+ return new FirebirdLimitNode(from, to);
+ }
+}
http://git-wip-us.apache.org/repos/asf/cayenne/blob/f6b2dac9/cayenne-server/src/main/java/org/apache/cayenne/dba/firebird/sqltree/FirebirdSubstringFunctionNode.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/dba/firebird/sqltree/FirebirdSubstringFunctionNode.java b/cayenne-server/src/main/java/org/apache/cayenne/dba/firebird/sqltree/FirebirdSubstringFunctionNode.java
new file mode 100644
index 0000000..f7fe3e1
--- /dev/null
+++ b/cayenne-server/src/main/java/org/apache/cayenne/dba/firebird/sqltree/FirebirdSubstringFunctionNode.java
@@ -0,0 +1,65 @@
+/*****************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ ****************************************************************/
+
+package org.apache.cayenne.dba.firebird.sqltree;
+
+import org.apache.cayenne.access.sqlbuilder.QuotingAppendable;
+import org.apache.cayenne.access.sqlbuilder.sqltree.FunctionNode;
+import org.apache.cayenne.access.sqlbuilder.sqltree.Node;
+
+/**
+ * SUBSTRING function for Firebird
+ *
+ * It has following format:
+ *
+ * SUBSTRING (string FROM CAST(? AS INTEGER) FOR CAST(? AS INTEGER))
+ *
+ * @since 4.1
+ */
+public class FirebirdSubstringFunctionNode extends FunctionNode {
+ public FirebirdSubstringFunctionNode(String alias) {
+ super("SUBSTRING", alias);
+ }
+
+ @Override
+ public void appendChildrenSeparator(QuotingAppendable buffer, int childIdx) {
+ if(skipContent()) {
+ return;
+ }
+ if(childIdx == 0) {
+ buffer.append(" FROM CAST(");
+ } else if(childIdx == 1) {
+ buffer.append(" AS INTEGER) FOR CAST(");
+ }
+ }
+
+ @Override
+ public void appendChildrenEnd(QuotingAppendable buffer) {
+ if(skipContent()) {
+ return;
+ }
+ buffer.append(" AS INTEGER)");
+ super.appendChildrenEnd(buffer);
+ }
+
+ @Override
+ public Node copy() {
+ return new FirebirdSubstringFunctionNode(getAlias());
+ }
+}
http://git-wip-us.apache.org/repos/asf/cayenne/blob/f6b2dac9/cayenne-server/src/main/java/org/apache/cayenne/dba/frontbase/FrontBaseAdapter.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/dba/frontbase/FrontBaseAdapter.java b/cayenne-server/src/main/java/org/apache/cayenne/dba/frontbase/FrontBaseAdapter.java
index 69b9876..76d1db6 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/dba/frontbase/FrontBaseAdapter.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/dba/frontbase/FrontBaseAdapter.java
@@ -20,9 +20,7 @@
package org.apache.cayenne.dba.frontbase;
import org.apache.cayenne.CayenneRuntimeException;
-import org.apache.cayenne.access.translator.select.QualifierTranslator;
-import org.apache.cayenne.access.translator.select.QueryAssembler;
-import org.apache.cayenne.access.translator.select.SelectTranslator;
+import org.apache.cayenne.access.sqlbuilder.sqltree.Node;
import org.apache.cayenne.access.types.ExtendedType;
import org.apache.cayenne.access.types.ExtendedTypeFactory;
import org.apache.cayenne.access.types.ExtendedTypeMap;
@@ -36,8 +34,6 @@ import org.apache.cayenne.dba.TypesMapping;
import org.apache.cayenne.di.Inject;
import org.apache.cayenne.map.DbAttribute;
import org.apache.cayenne.map.DbEntity;
-import org.apache.cayenne.map.EntityResolver;
-import org.apache.cayenne.query.SelectQuery;
import org.apache.cayenne.resource.ResourceLocator;
import java.sql.Types;
@@ -45,6 +41,7 @@ import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
+import java.util.function.Function;
/**
* DbAdapter implementation for <a href="http://www.frontbase.com/">FrontBase
@@ -79,20 +76,12 @@ public class FrontBaseAdapter extends JdbcAdapter {
setSupportsBatchUpdates(true);
}
- /**
- * @since 4.0
- */
- @Override
- public SelectTranslator getSelectTranslator(SelectQuery<?> query, EntityResolver entityResolver) {
- return new FrontBaseSelectTranslator(query, this, entityResolver);
- }
-
- /**
- * @since 4.0
- */
+ /**
+ * @since 4.2
+ */
@Override
- public QualifierTranslator getQualifierTranslator(QueryAssembler queryAssembler) {
- return new FrontBaseQualifierTranslator(queryAssembler);
+ public Function<Node, Node> getSqlTreeProcessor() {
+ return new FrontBaseSQLTreeProcessor();
}
@Override
http://git-wip-us.apache.org/repos/asf/cayenne/blob/f6b2dac9/cayenne-server/src/main/java/org/apache/cayenne/dba/frontbase/FrontBaseQualifierTranslator.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/dba/frontbase/FrontBaseQualifierTranslator.java b/cayenne-server/src/main/java/org/apache/cayenne/dba/frontbase/FrontBaseQualifierTranslator.java
deleted file mode 100644
index fa97c6d..0000000
--- a/cayenne-server/src/main/java/org/apache/cayenne/dba/frontbase/FrontBaseQualifierTranslator.java
+++ /dev/null
@@ -1,134 +0,0 @@
-/*****************************************************************
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- ****************************************************************/
-
-package org.apache.cayenne.dba.frontbase;
-
-import org.apache.cayenne.CayenneRuntimeException;
-import org.apache.cayenne.access.translator.select.QualifierTranslator;
-import org.apache.cayenne.access.translator.select.QueryAssembler;
-import org.apache.cayenne.exp.Expression;
-import org.apache.cayenne.exp.parser.ASTExtract;
-import org.apache.cayenne.exp.parser.ASTFunctionCall;
-
-/**
- * @since 4.0
- */
-public class FrontBaseQualifierTranslator extends QualifierTranslator {
-
- private int substringArg = 0;
-
- public FrontBaseQualifierTranslator(QueryAssembler queryAssembler) {
- super(queryAssembler);
- }
-
- @Override
- protected void appendFunction(ASTFunctionCall functionExpression) {
- switch (functionExpression.getFunctionName()) {
- case "CONCAT":
- // noop
- break;
- case "LOCATE":
- out.append("POSITION");
- break;
- case "LENGTH":
- out.append("CHAR_LENGTH");
- break;
- case "SUBSTRING":
- substringArg = 0;
- default:
- super.appendFunction(functionExpression);
- }
- }
-
- @Override
- protected void appendFunctionArgDivider(ASTFunctionCall functionExpression) {
- switch (functionExpression.getFunctionName()) {
- case "CONCAT":
- out.append(" || ");
- break;
- case "LOCATE":
- out.append(" IN ");
- break;
- case "SUBSTRING":
- // SUBSTRING (str FROM offset FOR length)
- switch (substringArg++) {
- case 0:
- out.append(" FROM ");
- break;
- case 1:
- out.append(" FOR ");
- break;
- }
- break;
- default:
- super.appendFunctionArgDivider(functionExpression);
- }
- }
-
- @Override
- protected void clearLastFunctionArgDivider(ASTFunctionCall functionExpression) {
- switch (functionExpression.getFunctionName()) {
- case "CONCAT":
- out.delete(out.length() - " || ".length(), out.length());
- break;
- case "LOCATE":
- out.delete(out.length() - " IN ".length(), out.length());
- break;
- case "SUBSTRING":
- // no offset arg
- if(substringArg == 2) {
- out.delete(out.length() - " FOR ".length(), out.length());
- }
- break;
- default:
- super.clearLastFunctionArgDivider(functionExpression);
- }
- if(functionExpression instanceof ASTExtract) {
- out.append(")");
- }
- }
-
- @Override
- protected boolean parenthesisNeeded(Expression node, Expression parentNode) {
- if (node.getType() == Expression.FUNCTION_CALL) {
- if (node instanceof ASTExtract) {
- return false;
- }
- }
-
- return super.parenthesisNeeded(node, parentNode);
- }
-
- @Override
- protected void appendExtractFunction(ASTExtract functionExpression) {
- out.append("EXTRACT(");
- switch (functionExpression.getPart()) {
- case DAY_OF_WEEK:
- case DAY_OF_YEAR:
- case WEEK:
- throw new CayenneRuntimeException("Function %s() is unsupported in FrontBase.", functionExpression.getPartCamelCaseName());
- case DAY_OF_MONTH:
- out.append("DAY");
- break;
- default:
- out.append(functionExpression.getPart().name());
- }
- out.append(" FROM ");
- }
-}
http://git-wip-us.apache.org/repos/asf/cayenne/blob/f6b2dac9/cayenne-server/src/main/java/org/apache/cayenne/dba/frontbase/FrontBaseSQLTreeProcessor.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/dba/frontbase/FrontBaseSQLTreeProcessor.java b/cayenne-server/src/main/java/org/apache/cayenne/dba/frontbase/FrontBaseSQLTreeProcessor.java
new file mode 100644
index 0000000..4fce3d7
--- /dev/null
+++ b/cayenne-server/src/main/java/org/apache/cayenne/dba/frontbase/FrontBaseSQLTreeProcessor.java
@@ -0,0 +1,104 @@
+/*****************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ ****************************************************************/
+
+package org.apache.cayenne.dba.frontbase;
+
+import org.apache.cayenne.CayenneRuntimeException;
+import org.apache.cayenne.access.sqlbuilder.QuotingAppendable;
+import org.apache.cayenne.access.sqlbuilder.sqltree.FunctionNode;
+import org.apache.cayenne.access.sqlbuilder.sqltree.Node;
+import org.apache.cayenne.access.sqlbuilder.sqltree.OpExpressionNode;
+import org.apache.cayenne.access.sqlbuilder.sqltree.TextNode;
+import org.apache.cayenne.access.translator.select.BaseSQLTreeProcessor;
+
+/**
+ * @since 4.2
+ */
+public class FrontBaseSQLTreeProcessor extends BaseSQLTreeProcessor {
+
+ @Override
+ protected void onFunctionNode(Node parent, FunctionNode child, int index) {
+ switch (child.getFunctionName()) {
+ case "CONCAT":
+ replaceChild(parent, index, new OpExpressionNode("||"));
+ break;
+ case "LOCATE":
+ // POSITION (substr IN str)
+ replaceChild(parent, index, new FunctionNode("POSITION", child.getAlias()) {
+ @Override
+ public void appendChildrenSeparator(QuotingAppendable buffer, int childIdx) {
+ buffer.append(" IN ");
+ }
+ });
+ break;
+ case "LENGTH":
+ replaceChild(parent, index, new FunctionNode("CHAR_LENGTH", child.getAlias()));
+ break;
+ case "SUBSTRING":
+ // SUBSTRING (str FROM offset FOR length)
+ replaceChild(parent, index, new FunctionNode("SUBSTRING", child.getAlias()){
+ @Override
+ public void appendChildrenSeparator(QuotingAppendable buffer, int childIdx) {
+ if(childIdx == 0) {
+ buffer.append(" FROM ");
+ } else if(childIdx == 1) {
+ buffer.append(" FOR ");
+ }
+ }
+ });
+ break;
+ case "YEAR":
+ case "MONTH":
+ case "DAY":
+ case "DAY_OF_MONTH":
+ case "HOUR":
+ case "MINUTE":
+ case "SECOND":
+ Node functionReplacement = new ExtractFunctionNode(child.getAlias());
+ String functionName = child.getFunctionName();
+ if("DAY_OF_MONTH".equals(functionName)) {
+ functionName = "DAY";
+ }
+ functionReplacement.addChild(new TextNode(functionName));
+ replaceChild(parent, index, functionReplacement);
+ break;
+
+ case "DAY_OF_WEEK":
+ case "DAY_OF_YEAR":
+ case "WEEK":
+ throw new CayenneRuntimeException("Function %s() is unsupported in FrontBase.", child.getFunctionName());
+ }
+ }
+
+ private static class ExtractFunctionNode extends FunctionNode {
+ public ExtractFunctionNode(String alias) {
+ super("EXTRACT", alias, true);
+ }
+
+ @Override
+ public void appendChildrenSeparator(QuotingAppendable buffer, int childIdx) {
+ buffer.append(" FROM ");
+ }
+
+ @Override
+ public Node copy() {
+ return new ExtractFunctionNode(getAlias());
+ }
+ }
+}
http://git-wip-us.apache.org/repos/asf/cayenne/blob/f6b2dac9/cayenne-server/src/main/java/org/apache/cayenne/dba/frontbase/FrontBaseSelectTranslator.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/dba/frontbase/FrontBaseSelectTranslator.java b/cayenne-server/src/main/java/org/apache/cayenne/dba/frontbase/FrontBaseSelectTranslator.java
deleted file mode 100644
index 1e71f5f..0000000
--- a/cayenne-server/src/main/java/org/apache/cayenne/dba/frontbase/FrontBaseSelectTranslator.java
+++ /dev/null
@@ -1,52 +0,0 @@
-/*****************************************************************
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- ****************************************************************/
-
-package org.apache.cayenne.dba.frontbase;
-
-import org.apache.cayenne.access.translator.select.DefaultSelectTranslator;
-import org.apache.cayenne.dba.DbAdapter;
-import org.apache.cayenne.map.EntityResolver;
-import org.apache.cayenne.query.Query;
-
-/**
- * @since 1.2
- */
-class FrontBaseSelectTranslator extends DefaultSelectTranslator {
-
- static final String SELECT_PREFIX = "SELECT";
-
- /**
- * @since 4.0
- */
- public FrontBaseSelectTranslator(Query query, DbAdapter adapter, EntityResolver entityResolver) {
- super(query, adapter, entityResolver);
- }
-
- @Override
- protected void appendLimitAndOffsetClauses(StringBuilder buffer) {
-
- int limit = queryMetadata.getFetchLimit();
- if (limit > 0 && buffer.length() > SELECT_PREFIX.length()) {
-
- if (SELECT_PREFIX.equals(buffer.substring(0, SELECT_PREFIX.length()))) {
- buffer.insert(SELECT_PREFIX.length(), " TOP " + limit);
- }
- }
- }
-}
http://git-wip-us.apache.org/repos/asf/cayenne/blob/f6b2dac9/cayenne-server/src/main/java/org/apache/cayenne/dba/h2/H2ActionBuilder.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/dba/h2/H2ActionBuilder.java b/cayenne-server/src/main/java/org/apache/cayenne/dba/h2/H2ActionBuilder.java
new file mode 100644
index 0000000..2cf2a10
--- /dev/null
+++ b/cayenne-server/src/main/java/org/apache/cayenne/dba/h2/H2ActionBuilder.java
@@ -0,0 +1,39 @@
+/*****************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ ****************************************************************/
+
+package org.apache.cayenne.dba.h2;
+
+import org.apache.cayenne.access.DataNode;
+import org.apache.cayenne.dba.JdbcActionBuilder;
+import org.apache.cayenne.query.SQLAction;
+import org.apache.cayenne.query.SelectQuery;
+
+/**
+ * @since 4.1
+ */
+public class H2ActionBuilder extends JdbcActionBuilder {
+ public H2ActionBuilder(DataNode node) {
+ super(node);
+ }
+
+ @Override
+ public <T> SQLAction objectSelectAction(SelectQuery<T> query) {
+ return new H2SelectAction(query, dataNode);
+ }
+}
http://git-wip-us.apache.org/repos/asf/cayenne/blob/f6b2dac9/cayenne-server/src/main/java/org/apache/cayenne/dba/h2/H2Adapter.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/dba/h2/H2Adapter.java b/cayenne-server/src/main/java/org/apache/cayenne/dba/h2/H2Adapter.java
index 6b47636..39cc43c 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/dba/h2/H2Adapter.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/dba/h2/H2Adapter.java
@@ -19,6 +19,8 @@
package org.apache.cayenne.dba.h2;
+import org.apache.cayenne.access.DataNode;
+import org.apache.cayenne.access.sqlbuilder.sqltree.Node;
import org.apache.cayenne.access.types.ExtendedType;
import org.apache.cayenne.access.types.ExtendedTypeFactory;
import org.apache.cayenne.access.types.ValueObjectTypeRegistry;
@@ -28,9 +30,12 @@ import org.apache.cayenne.dba.JdbcAdapter;
import org.apache.cayenne.dba.PkGenerator;
import org.apache.cayenne.di.Inject;
import org.apache.cayenne.map.DbAttribute;
+import org.apache.cayenne.query.Query;
+import org.apache.cayenne.query.SQLAction;
import org.apache.cayenne.resource.ResourceLocator;
import java.util.List;
+import java.util.function.Function;
/**
* DbAdapter implementation for <a href="http://www.h2database.com/">H2
@@ -66,6 +71,19 @@ public class H2Adapter extends JdbcAdapter {
}
}
+ /**
+ * @since 4.2
+ */
+ @Override
+ public Function<Node, Node> getSqlTreeProcessor() {
+ return new H2SQLTreeProcessor();
+ }
+
+ @Override
+ public SQLAction getAction(Query query, DataNode node) {
+ return query.createSQLAction(new H2ActionBuilder(node));
+ }
+
@Override
protected PkGenerator createPkGenerator() {
return new H2PkGenerator(this);
http://git-wip-us.apache.org/repos/asf/cayenne/blob/f6b2dac9/cayenne-server/src/main/java/org/apache/cayenne/dba/h2/H2SQLTreeProcessor.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/dba/h2/H2SQLTreeProcessor.java b/cayenne-server/src/main/java/org/apache/cayenne/dba/h2/H2SQLTreeProcessor.java
new file mode 100644
index 0000000..cd797f5
--- /dev/null
+++ b/cayenne-server/src/main/java/org/apache/cayenne/dba/h2/H2SQLTreeProcessor.java
@@ -0,0 +1,37 @@
+/*****************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ ****************************************************************/
+
+package org.apache.cayenne.dba.h2;
+
+import org.apache.cayenne.access.sqlbuilder.sqltree.LimitOffsetNode;
+import org.apache.cayenne.access.sqlbuilder.sqltree.Node;
+import org.apache.cayenne.access.sqlbuilder.sqltree.OffsetFetchNextNode;
+import org.apache.cayenne.access.translator.select.BaseSQLTreeProcessor;
+
+/**
+ * @since 4.2
+ */
+public class H2SQLTreeProcessor extends BaseSQLTreeProcessor {
+
+ @Override
+ protected void onLimitOffsetNode(Node parent, LimitOffsetNode child, int index) {
+ replaceChild(parent, index, new OffsetFetchNextNode(child), false);
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/cayenne/blob/f6b2dac9/cayenne-server/src/main/java/org/apache/cayenne/dba/h2/H2SelectAction.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/dba/h2/H2SelectAction.java b/cayenne-server/src/main/java/org/apache/cayenne/dba/h2/H2SelectAction.java
new file mode 100644
index 0000000..df7380d
--- /dev/null
+++ b/cayenne-server/src/main/java/org/apache/cayenne/dba/h2/H2SelectAction.java
@@ -0,0 +1,39 @@
+/*****************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ ****************************************************************/
+
+package org.apache.cayenne.dba.h2;
+
+import org.apache.cayenne.access.DataNode;
+import org.apache.cayenne.access.jdbc.SelectAction;
+import org.apache.cayenne.query.SelectQuery;
+
+/**
+ * @since 4.1
+ */
+public class H2SelectAction extends SelectAction {
+
+ public H2SelectAction(SelectQuery<?> query, DataNode dataNode) {
+ super(query, dataNode);
+ }
+
+ @Override
+ protected int getInMemoryOffset(int queryOffset) {
+ return 0;
+ }
+}
http://git-wip-us.apache.org/repos/asf/cayenne/blob/f6b2dac9/cayenne-server/src/main/java/org/apache/cayenne/dba/hsqldb/HSQLDBAdapter.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/dba/hsqldb/HSQLDBAdapter.java b/cayenne-server/src/main/java/org/apache/cayenne/dba/hsqldb/HSQLDBAdapter.java
index 64c59b3..99f4ae0 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/dba/hsqldb/HSQLDBAdapter.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/dba/hsqldb/HSQLDBAdapter.java
@@ -21,12 +21,9 @@ package org.apache.cayenne.dba.hsqldb;
import org.apache.cayenne.CayenneRuntimeException;
import org.apache.cayenne.access.DataNode;
-import org.apache.cayenne.access.translator.ParameterBinding;
+import org.apache.cayenne.access.sqlbuilder.sqltree.Node;
import org.apache.cayenne.access.translator.ejbql.EJBQLTranslatorFactory;
import org.apache.cayenne.access.translator.ejbql.JdbcEJBQLTranslatorFactory;
-import org.apache.cayenne.access.translator.select.QualifierTranslator;
-import org.apache.cayenne.access.translator.select.QueryAssembler;
-import org.apache.cayenne.access.translator.select.SelectTranslator;
import org.apache.cayenne.access.types.CharType;
import org.apache.cayenne.access.types.ExtendedType;
import org.apache.cayenne.access.types.ExtendedTypeFactory;
@@ -40,18 +37,15 @@ import org.apache.cayenne.map.DbAttribute;
import org.apache.cayenne.map.DbEntity;
import org.apache.cayenne.map.DbJoin;
import org.apache.cayenne.map.DbRelationship;
-import org.apache.cayenne.map.EntityResolver;
import org.apache.cayenne.query.Query;
import org.apache.cayenne.query.SQLAction;
-import org.apache.cayenne.query.SelectQuery;
import org.apache.cayenne.resource.ResourceLocator;
-import java.sql.PreparedStatement;
-import java.sql.SQLException;
import java.sql.Types;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
+import java.util.function.Function;
/**
* DbAdapter implementation for the <a href="http://hsqldb.sourceforge.net/">
@@ -91,22 +85,11 @@ public class HSQLDBAdapter extends JdbcAdapter {
}
/**
- * @since 4.0
- */
- @Override
- public SelectTranslator getSelectTranslator(SelectQuery<?> query, EntityResolver entityResolver) {
- return new HSQLSelectTranslator(query, this, entityResolver);
- }
-
- /**
- * Returns a trimming translator.
- * @since 4.0
+ * @since 4.2
*/
@Override
- public QualifierTranslator getQualifierTranslator(QueryAssembler queryAssembler) {
- QualifierTranslator translator = new HSQLQualifierTranslator(queryAssembler);
- translator.setCaseInsensitive(caseInsensitiveCollations);
- return translator;
+ public Function<Node, Node> getSqlTreeProcessor() {
+ return new HSQLTreeProcessor();
}
/**
http://git-wip-us.apache.org/repos/asf/cayenne/blob/f6b2dac9/cayenne-server/src/main/java/org/apache/cayenne/dba/hsqldb/HSQLQualifierTranslator.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/dba/hsqldb/HSQLQualifierTranslator.java b/cayenne-server/src/main/java/org/apache/cayenne/dba/hsqldb/HSQLQualifierTranslator.java
deleted file mode 100644
index fce980c..0000000
--- a/cayenne-server/src/main/java/org/apache/cayenne/dba/hsqldb/HSQLQualifierTranslator.java
+++ /dev/null
@@ -1,86 +0,0 @@
-/*****************************************************************
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- ****************************************************************/
-
-package org.apache.cayenne.dba.hsqldb;
-
-import java.io.IOException;
-
-import org.apache.cayenne.CayenneRuntimeException;
-import org.apache.cayenne.access.translator.select.QueryAssembler;
-import org.apache.cayenne.access.translator.select.TrimmingQualifierTranslator;
-import org.apache.cayenne.exp.parser.ASTExtract;
-import org.apache.cayenne.exp.parser.ASTFunctionCall;
-import org.apache.cayenne.exp.parser.PatternMatchNode;
-
-/**
- * @since 4.0
- */
-public class HSQLQualifierTranslator extends TrimmingQualifierTranslator {
-
- public HSQLQualifierTranslator(QueryAssembler queryAssembler) {
- super(queryAssembler, HSQLDBAdapter.TRIM_FUNCTION);
- }
-
- @Override
- protected void appendLikeEscapeCharacter(PatternMatchNode patternMatchNode)
- throws IOException {
-
- char escapeChar = patternMatchNode.getEscapeChar();
-
- if ('?' == escapeChar) {
- throw new CayenneRuntimeException("the escape character of '?' is illegal for LIKE clauses.");
- }
-
- if (0 != escapeChar) {
- // this is a difference with super implementation - HSQL driver seems does not
- // support JDBC escape syntax, so creating an explicit SQL escape:
- out.append(" ESCAPE '");
- out.append(escapeChar);
- out.append("'");
- }
- }
-
- @Override
- protected void appendFunction(ASTFunctionCall functionExpression) {
- // from documentation:
- // CURRENT_TIME returns a value of TIME WITH TIME ZONE type.
- // LOCALTIME returns a value of TIME type.
- // CURTIME() is a synonym for LOCALTIME.
- // use LOCALTIME to better align with other DBs
- if("CURRENT_TIME".equals(functionExpression.getFunctionName())) {
- out.append("LOCALTIME");
- } else {
- super.appendFunction(functionExpression);
- }
- }
-
- @Override
- protected void appendExtractFunction(ASTExtract functionExpression) {
- switch (functionExpression.getPart()) {
- case DAY_OF_WEEK:
- case DAY_OF_MONTH:
- case DAY_OF_YEAR:
- // hsqldb variants are without '_'
- out.append(functionExpression.getPart().name().replace("_", ""));
- break;
- default:
- appendFunction(functionExpression);
- }
- }
-}
http://git-wip-us.apache.org/repos/asf/cayenne/blob/f6b2dac9/cayenne-server/src/main/java/org/apache/cayenne/dba/hsqldb/HSQLSelectTranslator.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/dba/hsqldb/HSQLSelectTranslator.java b/cayenne-server/src/main/java/org/apache/cayenne/dba/hsqldb/HSQLSelectTranslator.java
deleted file mode 100644
index ce3bb58..0000000
--- a/cayenne-server/src/main/java/org/apache/cayenne/dba/hsqldb/HSQLSelectTranslator.java
+++ /dev/null
@@ -1,57 +0,0 @@
-/*****************************************************************
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- ****************************************************************/
-
-package org.apache.cayenne.dba.hsqldb;
-
-import org.apache.cayenne.access.translator.select.DefaultSelectTranslator;
-import org.apache.cayenne.dba.DbAdapter;
-import org.apache.cayenne.map.EntityResolver;
-import org.apache.cayenne.query.Query;
-
-/**
- * @since 1.2
- */
-class HSQLSelectTranslator extends DefaultSelectTranslator {
-
- /**
- * @since 4.0
- */
- public HSQLSelectTranslator(Query query, DbAdapter adapter, EntityResolver entityResolver) {
- super(query, adapter, entityResolver);
- }
-
- @Override
- protected void appendLimitAndOffsetClauses(StringBuilder buffer) {
- int offset = queryMetadata.getFetchOffset();
- int limit = queryMetadata.getFetchLimit();
-
- if (offset > 0 || limit > 0) {
- buffer.append(" LIMIT ");
-
- // both OFFSET and LIMIT must be present, so come up with defaults
- // if one of
- // them is not set by the user
- if (limit == 0) {
- limit = Integer.MAX_VALUE;
- }
-
- buffer.append(limit).append(" OFFSET ").append(offset);
- }
- }
-}
http://git-wip-us.apache.org/repos/asf/cayenne/blob/f6b2dac9/cayenne-server/src/main/java/org/apache/cayenne/dba/hsqldb/HSQLTreeProcessor.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/dba/hsqldb/HSQLTreeProcessor.java b/cayenne-server/src/main/java/org/apache/cayenne/dba/hsqldb/HSQLTreeProcessor.java
new file mode 100644
index 0000000..152f3ca
--- /dev/null
+++ b/cayenne-server/src/main/java/org/apache/cayenne/dba/hsqldb/HSQLTreeProcessor.java
@@ -0,0 +1,70 @@
+/*****************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ ****************************************************************/
+
+package org.apache.cayenne.dba.hsqldb;
+
+import org.apache.cayenne.access.sqlbuilder.sqltree.ColumnNode;
+import org.apache.cayenne.access.sqlbuilder.sqltree.FunctionNode;
+import org.apache.cayenne.access.sqlbuilder.sqltree.Node;
+import org.apache.cayenne.access.sqlbuilder.sqltree.OpExpressionNode;
+import org.apache.cayenne.access.translator.select.BaseSQLTreeProcessor;
+import org.apache.cayenne.access.sqlbuilder.sqltree.TrimmingColumnNode;
+
+/**
+ * @since 4.2
+ */
+public class HSQLTreeProcessor extends BaseSQLTreeProcessor {
+
+ @Override
+ protected void onColumnNode(Node parent, ColumnNode child, int index) {
+ replaceChild(parent, index, new TrimmingColumnNode(child));
+ }
+
+ @Override
+ protected void onFunctionNode(Node parent, FunctionNode child, int index) {
+ Node replacement = getReplacementForFunction(child);
+ if(replacement != null) {
+ replaceChild(parent, index, replacement);
+ }
+ }
+
+ private Node getReplacementForFunction(FunctionNode child) {
+ switch (child.getFunctionName()) {
+ case "DAY_OF_MONTH":
+ case "DAY_OF_WEEK":
+ case "DAY_OF_YEAR":
+ // hsqldb variants are without '_'
+ return new FunctionNode(child.getFunctionName().replace("_", ""), child.getAlias(), true);
+
+ case "CURRENT_DATE":
+ case "CURRENT_TIMESTAMP":
+ return new FunctionNode(child.getFunctionName(), child.getAlias(), false);
+ case "CURRENT_TIME":
+ // from documentation:
+ // CURRENT_TIME returns a value of TIME WITH TIME ZONE type.
+ // LOCALTIME returns a value of TIME type.
+ // CURTIME() is a synonym for LOCALTIME.
+ // use LOCALTIME to better align with other DBs
+ return new FunctionNode("LOCALTIME", child.getAlias(), false);
+ case "CONCAT":
+ return new OpExpressionNode("||");
+ }
+ return null;
+ }
+}
http://git-wip-us.apache.org/repos/asf/cayenne/blob/f6b2dac9/cayenne-server/src/main/java/org/apache/cayenne/dba/ingres/IngresAdapter.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/dba/ingres/IngresAdapter.java b/cayenne-server/src/main/java/org/apache/cayenne/dba/ingres/IngresAdapter.java
index 60a39b6..08c156d 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/dba/ingres/IngresAdapter.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/dba/ingres/IngresAdapter.java
@@ -21,11 +21,8 @@ package org.apache.cayenne.dba.ingres;
import org.apache.cayenne.CayenneRuntimeException;
import org.apache.cayenne.access.DataNode;
+import org.apache.cayenne.access.sqlbuilder.sqltree.Node;
import org.apache.cayenne.access.translator.ParameterBinding;
-import org.apache.cayenne.access.translator.select.QualifierTranslator;
-import org.apache.cayenne.access.translator.select.QueryAssembler;
-import org.apache.cayenne.access.translator.select.SelectTranslator;
-import org.apache.cayenne.access.translator.select.TrimmingQualifierTranslator;
import org.apache.cayenne.access.types.ExtendedType;
import org.apache.cayenne.access.types.ExtendedTypeFactory;
import org.apache.cayenne.access.types.ExtendedTypeMap;
@@ -37,16 +34,15 @@ import org.apache.cayenne.dba.PkGenerator;
import org.apache.cayenne.dba.TypesMapping;
import org.apache.cayenne.di.Inject;
import org.apache.cayenne.map.DbAttribute;
-import org.apache.cayenne.map.EntityResolver;
import org.apache.cayenne.query.Query;
import org.apache.cayenne.query.SQLAction;
-import org.apache.cayenne.query.SelectQuery;
import org.apache.cayenne.resource.ResourceLocator;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.sql.Types;
import java.util.List;
+import java.util.function.Function;
/**
* DbAdapter implementation for <a
@@ -75,17 +71,12 @@ public class IngresAdapter extends JdbcAdapter {
setSupportsGeneratedKeys(true);
}
- /**
- * @since 4.0
- */
- @Override
- public SelectTranslator getSelectTranslator(SelectQuery<?> query, EntityResolver entityResolver) {
- return new IngresSelectTranslator(query, this, entityResolver);
- }
-
+ /**
+ * @since 4.2
+ */
@Override
- public QualifierTranslator getQualifierTranslator(QueryAssembler queryAssembler) {
- return new IngresQualifierTranslator(queryAssembler);
+ public Function<Node, Node> getSqlTreeProcessor() {
+ return new IngressSQLTreeProcessor();
}
@Override
http://git-wip-us.apache.org/repos/asf/cayenne/blob/f6b2dac9/cayenne-server/src/main/java/org/apache/cayenne/dba/ingres/IngresQualifierTranslator.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/dba/ingres/IngresQualifierTranslator.java b/cayenne-server/src/main/java/org/apache/cayenne/dba/ingres/IngresQualifierTranslator.java
deleted file mode 100644
index 8334ff4..0000000
--- a/cayenne-server/src/main/java/org/apache/cayenne/dba/ingres/IngresQualifierTranslator.java
+++ /dev/null
@@ -1,122 +0,0 @@
-/*****************************************************************
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- ****************************************************************/
-
-package org.apache.cayenne.dba.ingres;
-
-import java.io.IOException;
-
-import org.apache.cayenne.access.translator.select.QueryAssembler;
-import org.apache.cayenne.access.translator.select.TrimmingQualifierTranslator;
-import org.apache.cayenne.exp.Expression;
-import org.apache.cayenne.exp.parser.ASTExtract;
-import org.apache.cayenne.exp.parser.ASTFunctionCall;
-import org.apache.cayenne.exp.parser.Node;
-
-/**
- * @since 4.0
- */
-class IngresQualifierTranslator extends TrimmingQualifierTranslator {
-
- IngresQualifierTranslator(QueryAssembler queryAssembler) {
- super(queryAssembler, IngresAdapter.TRIM_FUNCTION);
- }
-
- @Override
- public void endNode(Expression node, Expression parentNode) {
- super.endNode(node, parentNode);
- if(node.getType() == Expression.FUNCTION_CALL) {
- if("LOCATE".equals(((ASTFunctionCall)node).getFunctionName())) {
- // order of args in ingres version of LOCATE is different, so swap them back
- swapNodeChildren((ASTFunctionCall)node, 0, 1);
- }
- }
- }
-
- @Override
- protected void appendFunction(ASTFunctionCall functionExpression) {
- if("CONCAT".equals(functionExpression.getFunctionName())) {
- // noop
- } else if("LOCATE".equals(functionExpression.getFunctionName())) {
- // order of args in ingres version of LOCATE is different
- // LOCATE(substr, str) -> LOCATE(str, substr)
- out.append("LOCATE");
- swapNodeChildren(functionExpression, 0, 1);
- } else if("TRIM".equals(functionExpression.getFunctionName())) {
- // simple TRIM removes only trailing spaces
- out.append("LTRIM(RTRIM");
- } else {
- super.appendFunction(functionExpression);
- }
- }
-
- @Override
- protected void appendFunctionArgDivider(ASTFunctionCall functionExpression) {
- if("CONCAT".equals(functionExpression.getFunctionName())) {
- out.append(" + ");
- } else {
- super.appendFunctionArgDivider(functionExpression);
- }
- }
-
- @Override
- protected void appendFunctionArg(Object value, ASTFunctionCall functionExpression) throws IOException {
- if("SUBSTRING".equals(functionExpression.getFunctionName())) {
- out.append("CAST(");
- super.appendFunctionArg(value, functionExpression);
- clearLastFunctionArgDivider(functionExpression);
- out.append(" AS INTEGER)");
- appendFunctionArgDivider(functionExpression);
- } else {
- super.appendFunctionArg(value, functionExpression);
- }
- }
-
- @Override
- protected void clearLastFunctionArgDivider(ASTFunctionCall functionExpression) {
- if("CONCAT".equals(functionExpression.getFunctionName())) {
- out.delete(out.length() - " + ".length(), out.length());
- } else {
- super.clearLastFunctionArgDivider(functionExpression);
- if("TRIM".equals(functionExpression.getFunctionName())) {
- out.append(")");
- }
- }
- }
-
- @Override
- protected void appendExtractFunction(ASTExtract functionExpression) {
- switch (functionExpression.getPart()) {
- case DAY_OF_WEEK:
- case DAY_OF_MONTH:
- case DAY_OF_YEAR:
- // ingres variants are without '_'
- out.append(functionExpression.getPart().name().replace("_", ""));
- break;
- default:
- appendFunction(functionExpression);
- }
- }
-
- private void swapNodeChildren(Node node, int i, int j) {
- Node ni = node.jjtGetChild(i);
- Node nj = node.jjtGetChild(j);
- node.jjtAddChild(ni, j);
- node.jjtAddChild(nj, i);
- }
-}
http://git-wip-us.apache.org/repos/asf/cayenne/blob/f6b2dac9/cayenne-server/src/main/java/org/apache/cayenne/dba/ingres/IngresSelectTranslator.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/dba/ingres/IngresSelectTranslator.java b/cayenne-server/src/main/java/org/apache/cayenne/dba/ingres/IngresSelectTranslator.java
deleted file mode 100644
index f466d5f..0000000
--- a/cayenne-server/src/main/java/org/apache/cayenne/dba/ingres/IngresSelectTranslator.java
+++ /dev/null
@@ -1,49 +0,0 @@
-/*****************************************************************
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- ****************************************************************/
-package org.apache.cayenne.dba.ingres;
-
-import org.apache.cayenne.access.translator.select.DefaultSelectTranslator;
-import org.apache.cayenne.dba.DbAdapter;
-import org.apache.cayenne.map.EntityResolver;
-import org.apache.cayenne.query.Query;
-
-public class IngresSelectTranslator extends DefaultSelectTranslator {
-
- /**
- * @since 4.0
- */
- public IngresSelectTranslator(Query query, DbAdapter adapter, EntityResolver entityResolver) {
- super(query, adapter, entityResolver);
- }
-
- @Override
- protected void appendLimitAndOffsetClauses(StringBuilder buffer) {
- // limit results
- int offset = queryMetadata.getFetchOffset();
- int limit = queryMetadata.getFetchLimit();
-
- if (offset > 0) {
- buffer.append(" OFFSET ").append(offset);
- }
-
- if (limit > 0) {
- buffer.append(" FETCH NEXT ").append(limit).append(" ROWS ONLY ");
- }
- }
-}
http://git-wip-us.apache.org/repos/asf/cayenne/blob/f6b2dac9/cayenne-server/src/main/java/org/apache/cayenne/dba/ingres/IngressSQLTreeProcessor.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/dba/ingres/IngressSQLTreeProcessor.java b/cayenne-server/src/main/java/org/apache/cayenne/dba/ingres/IngressSQLTreeProcessor.java
new file mode 100644
index 0000000..02a21fd
--- /dev/null
+++ b/cayenne-server/src/main/java/org/apache/cayenne/dba/ingres/IngressSQLTreeProcessor.java
@@ -0,0 +1,108 @@
+/*****************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ ****************************************************************/
+
+package org.apache.cayenne.dba.ingres;
+
+import org.apache.cayenne.access.sqlbuilder.QuotingAppendable;
+import org.apache.cayenne.access.sqlbuilder.sqltree.ColumnNode;
+import org.apache.cayenne.access.sqlbuilder.sqltree.FunctionNode;
+import org.apache.cayenne.access.sqlbuilder.sqltree.LimitOffsetNode;
+import org.apache.cayenne.access.sqlbuilder.sqltree.Node;
+import org.apache.cayenne.access.sqlbuilder.sqltree.OffsetFetchNextNode;
+import org.apache.cayenne.access.sqlbuilder.sqltree.OpExpressionNode;
+import org.apache.cayenne.access.sqlbuilder.sqltree.TrimmingColumnNode;
+import org.apache.cayenne.access.translator.select.BaseSQLTreeProcessor;
+
+/**
+ * @since 4.2
+ */
+public class IngressSQLTreeProcessor extends BaseSQLTreeProcessor {
+
+ @Override
+ protected void onColumnNode(Node parent, ColumnNode child, int index) {
+ replaceChild(parent, index, new TrimmingColumnNode(child));
+ }
+
+ @Override
+ protected void onLimitOffsetNode(Node parent, LimitOffsetNode child, int index) {
+ replaceChild(parent, index, new OffsetFetchNextNode(child) {
+ @Override
+ public QuotingAppendable append(QuotingAppendable buffer) {
+ // OFFSET X FETCH NEXT Y ROWS ONLY
+ if(offset > 0) {
+ buffer.append("OFFSET ").append(offset);
+ }
+ if(limit > 0) {
+ buffer.append("FETCH NEXT ").append(limit).append(" ROWS ONLY");
+ }
+ return buffer;
+ }
+ });
+ }
+
+ @Override
+ protected void onFunctionNode(Node parent, FunctionNode child, int index) {
+ switch (child.getFunctionName()) {
+ case "CONCAT":
+ replaceChild(parent, index, new OpExpressionNode("+"));
+ return;
+ case "LOCATE":
+ Node child0 = child.getChild(0);
+ child.replaceChild(0, child.getChild(1));
+ child.replaceChild(1, child0);
+ return;
+ case "SUBSTRING":
+ Node replacement = new FunctionNode("SUBSTRING", child.getAlias(), true) {
+ @Override
+ public void appendChildrenSeparator(QuotingAppendable buffer, int childIdx) {
+ // 0, CAST(1 AS INTEGER), CAST(2 AS INTEGER)
+ if(childIdx == 1 || childIdx == 2) {
+ buffer.append(" AS INTEGER)");
+ }
+ if(childIdx == 0 || childIdx == 1) {
+ buffer.append(", CAST(");
+ }
+ }
+
+ @Override
+ public void appendChildrenEnd(QuotingAppendable buffer) {
+ buffer.append(" AS INTEGER)");
+ super.appendChildrenEnd(buffer);
+ }
+ };
+ replaceChild(parent, index, replacement);
+ return;
+ case "TRIM":
+ replaceChild(parent, index, new FunctionNode("RTRIM(LTRIM", child.getAlias(), true) {
+ @Override
+ public void appendChildrenEnd(QuotingAppendable buffer) {
+ buffer.append(')');
+ super.appendChildrenEnd(buffer);
+ }
+ });
+ return;
+ case "DAY_OF_WEEK":
+ case "DAY_OF_MONTH":
+ case "DAY_OF_YEAR":
+ // ingres variants are without '_'
+ replaceChild(parent, index, new FunctionNode(child.getFunctionName().replace("_", ""), child.getAlias(), true));
+ return;
+ }
+ }
+}
http://git-wip-us.apache.org/repos/asf/cayenne/blob/f6b2dac9/cayenne-server/src/main/java/org/apache/cayenne/dba/mysql/MySQLAdapter.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/dba/mysql/MySQLAdapter.java b/cayenne-server/src/main/java/org/apache/cayenne/dba/mysql/MySQLAdapter.java
index b50fbc0..5230990 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/dba/mysql/MySQLAdapter.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/dba/mysql/MySQLAdapter.java
@@ -21,12 +21,10 @@ package org.apache.cayenne.dba.mysql;
import org.apache.cayenne.CayenneRuntimeException;
import org.apache.cayenne.access.DataNode;
+import org.apache.cayenne.access.sqlbuilder.sqltree.Node;
import org.apache.cayenne.access.translator.ParameterBinding;
import org.apache.cayenne.access.translator.ejbql.EJBQLTranslatorFactory;
import org.apache.cayenne.access.translator.ejbql.JdbcEJBQLTranslatorFactory;
-import org.apache.cayenne.access.translator.select.QualifierTranslator;
-import org.apache.cayenne.access.translator.select.QueryAssembler;
-import org.apache.cayenne.access.translator.select.SelectTranslator;
import org.apache.cayenne.access.types.ByteArrayType;
import org.apache.cayenne.access.types.CharType;
import org.apache.cayenne.access.types.ExtendedType;
@@ -43,10 +41,8 @@ import org.apache.cayenne.dba.TypesMapping;
import org.apache.cayenne.di.Inject;
import org.apache.cayenne.map.DbAttribute;
import org.apache.cayenne.map.DbEntity;
-import org.apache.cayenne.map.EntityResolver;
import org.apache.cayenne.query.Query;
import org.apache.cayenne.query.SQLAction;
-import org.apache.cayenne.query.SelectQuery;
import org.apache.cayenne.resource.ResourceLocator;
import java.sql.PreparedStatement;
@@ -59,6 +55,7 @@ import java.util.Collections;
import java.util.Comparator;
import java.util.Iterator;
import java.util.List;
+import java.util.function.Function;
/**
* DbAdapter implementation for <a href="http://www.mysql.com">MySQL RDBMS</a>.
@@ -109,16 +106,12 @@ public class MySQLAdapter extends JdbcAdapter {
return new DefaultQuotingStrategy("`", "`");
}
+ /**
+ * @since 4.2
+ */
@Override
- public SelectTranslator getSelectTranslator(SelectQuery<?> query, EntityResolver entityResolver) {
- return new MySQLSelectTranslator(query, this, entityResolver);
- }
-
- @Override
- public QualifierTranslator getQualifierTranslator(QueryAssembler queryAssembler) {
- QualifierTranslator translator = new MySQLQualifierTranslator(queryAssembler);
- translator.setCaseInsensitive(caseInsensitiveCollations);
- return translator;
+ public Function<Node, Node> getSqlTreeProcessor() {
+ return MySQLTreeProcessor.getInstance();
}
/**
http://git-wip-us.apache.org/repos/asf/cayenne/blob/f6b2dac9/cayenne-server/src/main/java/org/apache/cayenne/dba/mysql/MySQLQualifierTranslator.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/dba/mysql/MySQLQualifierTranslator.java b/cayenne-server/src/main/java/org/apache/cayenne/dba/mysql/MySQLQualifierTranslator.java
deleted file mode 100644
index 2b93c22..0000000
--- a/cayenne-server/src/main/java/org/apache/cayenne/dba/mysql/MySQLQualifierTranslator.java
+++ /dev/null
@@ -1,105 +0,0 @@
-/*****************************************************************
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- ****************************************************************/
-package org.apache.cayenne.dba.mysql;
-
-import java.io.IOException;
-
-import org.apache.cayenne.CayenneRuntimeException;
-import org.apache.cayenne.access.translator.select.QualifierTranslator;
-import org.apache.cayenne.access.translator.select.QueryAssembler;
-import org.apache.cayenne.exp.Expression;
-import org.apache.cayenne.exp.parser.ASTExtract;
-import org.apache.cayenne.exp.parser.PatternMatchNode;
-
-class MySQLQualifierTranslator extends QualifierTranslator {
-
- public MySQLQualifierTranslator(QueryAssembler queryAssembler) {
- super(queryAssembler);
- }
-
- @Override
- protected void appendLikeEscapeCharacter(PatternMatchNode patternMatchNode)
- throws IOException {
-
- char escapeChar = patternMatchNode.getEscapeChar();
-
- if ('?' == escapeChar) {
- throw new CayenneRuntimeException("the escape character of '?' is illegal for LIKE clauses.");
- }
-
- if (0 != escapeChar) {
- // this is a difference with super implementation - MySQL driver does not
- // support JDBC escape syntax, so creating an explicit SQL escape:
- out.append(" ESCAPE '");
- out.append(escapeChar);
- out.append("'");
- }
- }
-
- @Override
- public void finishedChild(Expression node, int childIndex, boolean hasMoreChildren) {
-
- if (!caseInsensitive) {
- super.finishedChild(node, childIndex, hasMoreChildren);
- }
- else {
-
- if (!hasMoreChildren) {
- return;
- }
-
- // if we have something except LIKE or NOT LIKE then no need in specific handling
- if (node.getType() != Expression.LIKE
- && node.getType() != Expression.NOT_LIKE) {
- super.finishedChild(node, childIndex, hasMoreChildren);
- return;
- }
-
- try {
- Appendable out = (matchingObject) ? new StringBuilder() : this.out;
-
- switch (node.getType()) {
- case Expression.LIKE:
- out.append(" LIKE BINARY ");
- break;
- case Expression.NOT_LIKE:
- out.append(" NOT LIKE BINARY ");
- break;
- }
- }
- catch (IOException ioex) {
- throw new CayenneRuntimeException("Error appending content", ioex);
- }
- }
- }
-
- @Override
- protected void appendExtractFunction(ASTExtract functionExpression) {
- switch (functionExpression.getPart()) {
- case DAY_OF_WEEK:
- case DAY_OF_MONTH:
- case DAY_OF_YEAR:
- // mysql variants are without '_'
- out.append(functionExpression.getPart().name().replace("_", ""));
- break;
- default:
- appendFunction(functionExpression);
- }
- }
-}
http://git-wip-us.apache.org/repos/asf/cayenne/blob/f6b2dac9/cayenne-server/src/main/java/org/apache/cayenne/dba/mysql/MySQLSelectTranslator.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/dba/mysql/MySQLSelectTranslator.java b/cayenne-server/src/main/java/org/apache/cayenne/dba/mysql/MySQLSelectTranslator.java
deleted file mode 100644
index c9f0809..0000000
--- a/cayenne-server/src/main/java/org/apache/cayenne/dba/mysql/MySQLSelectTranslator.java
+++ /dev/null
@@ -1,57 +0,0 @@
-/*****************************************************************
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- ****************************************************************/
-
-package org.apache.cayenne.dba.mysql;
-
-import org.apache.cayenne.access.translator.select.DefaultSelectTranslator;
-import org.apache.cayenne.dba.DbAdapter;
-import org.apache.cayenne.map.EntityResolver;
-import org.apache.cayenne.query.Query;
-
-/**
- * @since 1.2
- */
-class MySQLSelectTranslator extends DefaultSelectTranslator {
-
- /**
- * @since 4.0
- */
- public MySQLSelectTranslator(Query query, DbAdapter adapter, EntityResolver entityResolver) {
- super(query, adapter, entityResolver);
- }
-
- @Override
- protected void appendLimitAndOffsetClauses(StringBuilder buffer) {
- int offset = queryMetadata.getFetchOffset();
- int limit = queryMetadata.getFetchLimit();
-
- if (offset > 0 || limit > 0) {
- buffer.append(" LIMIT ");
-
- // both OFFSET and LIMIT must be present, so come up with defaults
- // if one of
- // them is not set by the user
- if (limit == 0) {
- limit = Integer.MAX_VALUE;
- }
-
- buffer.append(limit).append(" OFFSET ").append(offset);
- }
- }
-}
http://git-wip-us.apache.org/repos/asf/cayenne/blob/f6b2dac9/cayenne-server/src/main/java/org/apache/cayenne/dba/mysql/MySQLTreeProcessor.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/dba/mysql/MySQLTreeProcessor.java b/cayenne-server/src/main/java/org/apache/cayenne/dba/mysql/MySQLTreeProcessor.java
new file mode 100644
index 0000000..d816112
--- /dev/null
+++ b/cayenne-server/src/main/java/org/apache/cayenne/dba/mysql/MySQLTreeProcessor.java
@@ -0,0 +1,68 @@
+/*****************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ ****************************************************************/
+
+package org.apache.cayenne.dba.mysql;
+
+import org.apache.cayenne.access.sqlbuilder.sqltree.FunctionNode;
+import org.apache.cayenne.access.sqlbuilder.sqltree.LikeNode;
+import org.apache.cayenne.access.sqlbuilder.sqltree.LimitOffsetNode;
+import org.apache.cayenne.access.sqlbuilder.sqltree.Node;
+import org.apache.cayenne.access.translator.select.BaseSQLTreeProcessor;
+import org.apache.cayenne.dba.mysql.sqltree.MysqlLikeNode;
+import org.apache.cayenne.dba.mysql.sqltree.MysqlLimitOffsetNode;
+
+/**
+ * @since 4.2
+ */
+public class MySQLTreeProcessor extends BaseSQLTreeProcessor {
+
+ private static final MySQLTreeProcessor INSTANCE = new MySQLTreeProcessor();
+
+ public static MySQLTreeProcessor getInstance() {
+ return INSTANCE;
+ }
+
+ private MySQLTreeProcessor() {
+ }
+
+ @Override
+ protected void onLikeNode(Node parent, LikeNode child, int index) {
+ if(!child.isIgnoreCase()) {
+ replaceChild(parent, index, new MysqlLikeNode(child.isNot(), child.getEscape()));
+ }
+ }
+
+ @Override
+ protected void onLimitOffsetNode(Node parent, LimitOffsetNode child, int index) {
+ Node replacement = new MysqlLimitOffsetNode(child.getLimit(), child.getOffset());
+ replaceChild(parent, index, replacement, false);
+ }
+
+ @Override
+ protected void onFunctionNode(Node parent, FunctionNode child, int index) {
+ String functionName = child.getFunctionName();
+ if("DAY_OF_MONTH".equals(functionName)
+ || "DAY_OF_WEEK".equals(functionName)
+ || "DAY_OF_YEAR".equals(functionName)) {
+ FunctionNode replacement = new FunctionNode(functionName.replace("_", ""), child.getAlias(), true);
+ replaceChild(parent, index, replacement);
+ }
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/cayenne/blob/f6b2dac9/cayenne-server/src/main/java/org/apache/cayenne/dba/mysql/sqltree/MysqlLikeNode.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/dba/mysql/sqltree/MysqlLikeNode.java b/cayenne-server/src/main/java/org/apache/cayenne/dba/mysql/sqltree/MysqlLikeNode.java
new file mode 100644
index 0000000..5f25f14
--- /dev/null
+++ b/cayenne-server/src/main/java/org/apache/cayenne/dba/mysql/sqltree/MysqlLikeNode.java
@@ -0,0 +1,46 @@
+/*****************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ ****************************************************************/
+
+package org.apache.cayenne.dba.mysql.sqltree;
+
+import org.apache.cayenne.access.sqlbuilder.QuotingAppendable;
+import org.apache.cayenne.access.sqlbuilder.sqltree.LikeNode;
+import org.apache.cayenne.access.sqlbuilder.sqltree.Node;
+
+/**
+ * @since 4.2
+ */
+public class MysqlLikeNode extends LikeNode {
+ public MysqlLikeNode(boolean isNot, char escape) {
+ super(false, isNot, escape);
+ }
+
+ @Override
+ public void appendChildrenSeparator(QuotingAppendable buffer, int childIdx) {
+ if (not) {
+ buffer.append(" NOT");
+ }
+ buffer.append(" LIKE BINARY ");
+ }
+
+ @Override
+ public Node copy() {
+ return new MysqlLikeNode(isNot(), getEscape());
+ }
+}
http://git-wip-us.apache.org/repos/asf/cayenne/blob/f6b2dac9/cayenne-server/src/main/java/org/apache/cayenne/dba/mysql/sqltree/MysqlLimitOffsetNode.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/dba/mysql/sqltree/MysqlLimitOffsetNode.java b/cayenne-server/src/main/java/org/apache/cayenne/dba/mysql/sqltree/MysqlLimitOffsetNode.java
new file mode 100644
index 0000000..9bc0105
--- /dev/null
+++ b/cayenne-server/src/main/java/org/apache/cayenne/dba/mysql/sqltree/MysqlLimitOffsetNode.java
@@ -0,0 +1,40 @@
+/*****************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ ****************************************************************/
+
+package org.apache.cayenne.dba.mysql.sqltree;
+
+import org.apache.cayenne.access.sqlbuilder.sqltree.LimitOffsetNode;
+import org.apache.cayenne.access.sqlbuilder.sqltree.Node;
+
+/**
+ * @since 4.2
+ */
+public class MysqlLimitOffsetNode extends LimitOffsetNode {
+
+ public MysqlLimitOffsetNode(int limit, int offset) {
+ // Per MySQL documentation: "To retrieve all rows from a certain offset up to the end of the result set,
+ // you can use some large number for the second parameter."
+ super(limit == 0 && offset > 0 ? Integer.MAX_VALUE : limit, offset);
+ }
+
+ @Override
+ public Node copy() {
+ return new MysqlLimitOffsetNode(limit, offset);
+ }
+}
http://git-wip-us.apache.org/repos/asf/cayenne/blob/f6b2dac9/cayenne-server/src/main/java/org/apache/cayenne/dba/openbase/OpenBaseAdapter.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/dba/openbase/OpenBaseAdapter.java b/cayenne-server/src/main/java/org/apache/cayenne/dba/openbase/OpenBaseAdapter.java
index 1a30779..c245942 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/dba/openbase/OpenBaseAdapter.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/dba/openbase/OpenBaseAdapter.java
@@ -25,11 +25,10 @@ import java.sql.ResultSet;
import java.sql.Types;
import java.util.Iterator;
import java.util.List;
+import java.util.function.Function;
import org.apache.cayenne.CayenneRuntimeException;
-import org.apache.cayenne.access.translator.select.QualifierTranslator;
-import org.apache.cayenne.access.translator.select.QueryAssembler;
-import org.apache.cayenne.access.translator.select.SelectTranslator;
+import org.apache.cayenne.access.sqlbuilder.sqltree.Node;
import org.apache.cayenne.access.types.ByteType;
import org.apache.cayenne.access.types.CharType;
import org.apache.cayenne.access.types.ExtendedType;
@@ -46,8 +45,6 @@ import org.apache.cayenne.map.DbAttribute;
import org.apache.cayenne.map.DbEntity;
import org.apache.cayenne.map.DbJoin;
import org.apache.cayenne.map.DbRelationship;
-import org.apache.cayenne.map.EntityResolver;
-import org.apache.cayenne.query.SelectQuery;
import org.apache.cayenne.resource.ResourceLocator;
/**
@@ -78,11 +75,11 @@ public class OpenBaseAdapter extends JdbcAdapter {
}
/**
- * @since 4.0
+ * @since 4.2
*/
@Override
- public SelectTranslator getSelectTranslator(SelectQuery<?> query, EntityResolver entityResolver) {
- return new OpenBaseSelectTranslator(query, this, entityResolver);
+ public Function<Node, Node> getSqlTreeProcessor() {
+ return new OpenBaseSQLTreeProcessor();
}
@Override
@@ -126,14 +123,6 @@ public class OpenBaseAdapter extends JdbcAdapter {
}
/**
- * Returns OpenBase-specific translator for queries.
- */
- @Override
- public QualifierTranslator getQualifierTranslator(QueryAssembler queryAssembler) {
- return new OpenBaseQualifierTranslator(queryAssembler);
- }
-
- /**
* Creates and returns a primary key generator. Overrides superclass
* implementation to return an instance of OpenBasePkGenerator that uses
* built-in multi-server primary key generation.
[11/11] cayenne git commit: CAY-2468 Support subqueries in Expression
API
Posted by nt...@apache.org.
CAY-2468 Support subqueries in Expression API
Project: http://git-wip-us.apache.org/repos/asf/cayenne/repo
Commit: http://git-wip-us.apache.org/repos/asf/cayenne/commit/60c55667
Tree: http://git-wip-us.apache.org/repos/asf/cayenne/tree/60c55667
Diff: http://git-wip-us.apache.org/repos/asf/cayenne/diff/60c55667
Branch: refs/heads/master
Commit: 60c55667568bffaf92f738289a6ca7b2f2b2bc68
Parents: 1f10c92
Author: Nikita Timofeev <st...@gmail.com>
Authored: Wed Jan 9 11:43:12 2019 +0300
Committer: Nikita Timofeev <st...@gmail.com>
Committed: Wed Jan 9 11:43:12 2019 +0300
----------------------------------------------------------------------
.../java/org/apache/cayenne/exp/Expression.java | 18 +-
.../apache/cayenne/exp/ExpressionFactory.java | 54 ++++++
.../cayenne/exp/parser/ASTEnclosingObject.java | 71 ++++++++
.../apache/cayenne/exp/parser/ASTExists.java | 62 +++++++
.../cayenne/exp/parser/ASTFullObject.java | 8 +
.../org/apache/cayenne/exp/parser/ASTIn.java | 4 +-
.../org/apache/cayenne/exp/parser/ASTNotIn.java | 4 +-
.../apache/cayenne/exp/parser/ASTSubquery.java | 95 +++++++++++
.../cayenne/exp/parser/PatternMatchNode.java | 7 +
.../cayenne/exp/property/BaseProperty.java | 22 +++
.../cayenne/exp/property/DateProperty.java | 8 +
.../cayenne/exp/property/EntityProperty.java | 9 +
.../cayenne/exp/property/ListProperty.java | 8 +
.../cayenne/exp/property/MapProperty.java | 7 +
.../cayenne/exp/property/NumericProperty.java | 8 +
.../cayenne/exp/property/SetProperty.java | 8 +
.../cayenne/exp/property/StringProperty.java | 8 +
.../cayenne/query/ObjectSelect_SubqueryIT.java | 167 +++++++++++++++++++
18 files changed, 561 insertions(+), 7 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/cayenne/blob/60c55667/cayenne-server/src/main/java/org/apache/cayenne/exp/Expression.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/exp/Expression.java b/cayenne-server/src/main/java/org/apache/cayenne/exp/Expression.java
index 569416c..0e099d7 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/exp/Expression.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/exp/Expression.java
@@ -19,10 +19,7 @@
package org.apache.cayenne.exp;
-import static org.apache.cayenne.exp.ExpressionFactory.exp;
-
import java.io.IOException;
-import java.io.PrintWriter;
import java.io.Serializable;
import java.util.Collection;
import java.util.HashMap;
@@ -167,6 +164,21 @@ public abstract class Expression implements Serializable, XMLSerializable {
*/
public static final int FULL_OBJECT = 47;
+ /**
+ * @since 4.2
+ */
+ public static final int ENCLOSING_OBJECT = 48;
+
+ /**
+ * @since 4.2
+ */
+ public static final int EXISTS = 49;
+
+ /**
+ * @since 4.2
+ */
+ public static final int SUBQUERY = 50;
+
protected int type;
/**
http://git-wip-us.apache.org/repos/asf/cayenne/blob/60c55667/cayenne-server/src/main/java/org/apache/cayenne/exp/ExpressionFactory.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/exp/ExpressionFactory.java b/cayenne-server/src/main/java/org/apache/cayenne/exp/ExpressionFactory.java
index 3ad5e77..ad9775e 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/exp/ExpressionFactory.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/exp/ExpressionFactory.java
@@ -31,7 +31,9 @@ import org.apache.cayenne.exp.parser.ASTBitwiseRightShift;
import org.apache.cayenne.exp.parser.ASTBitwiseXor;
import org.apache.cayenne.exp.parser.ASTDbPath;
import org.apache.cayenne.exp.parser.ASTDivide;
+import org.apache.cayenne.exp.parser.ASTEnclosingObject;
import org.apache.cayenne.exp.parser.ASTEqual;
+import org.apache.cayenne.exp.parser.ASTExists;
import org.apache.cayenne.exp.parser.ASTFalse;
import org.apache.cayenne.exp.parser.ASTFullObject;
import org.apache.cayenne.exp.parser.ASTGreater;
@@ -54,6 +56,7 @@ import org.apache.cayenne.exp.parser.ASTObjPath;
import org.apache.cayenne.exp.parser.ASTOr;
import org.apache.cayenne.exp.parser.ASTPath;
import org.apache.cayenne.exp.parser.ASTScalar;
+import org.apache.cayenne.exp.parser.ASTSubquery;
import org.apache.cayenne.exp.parser.ASTSubtract;
import org.apache.cayenne.exp.parser.ASTTrue;
import org.apache.cayenne.exp.parser.ExpressionParser;
@@ -61,6 +64,9 @@ import org.apache.cayenne.exp.parser.ExpressionParserTokenManager;
import org.apache.cayenne.exp.parser.JavaCharStream;
import org.apache.cayenne.exp.parser.SimpleNode;
import org.apache.cayenne.map.Entity;
+import org.apache.cayenne.query.ColumnSelect;
+import org.apache.cayenne.query.ObjectSelect;
+import org.apache.cayenne.query.SelectQuery;
import java.io.Reader;
import java.io.StringReader;
@@ -1257,6 +1263,13 @@ public class ExpressionFactory {
}
/**
+ * @since 4.2
+ */
+ public static Expression enclosingObjectExp(Expression exp) {
+ return new ASTEnclosingObject(exp);
+ }
+
+ /**
* @since 4.0
*/
public static Expression and(Collection<Expression> expressions) {
@@ -1343,4 +1356,45 @@ public class ExpressionFactory {
throw new ExpressionException("%s", th, message != null ? message : "");
}
}
+
+ /**
+ * @since 4.2
+ */
+ public static Expression exists(SelectQuery<?> subQuery) {
+ return new ASTExists(new ASTSubquery(subQuery));
+ }
+
+ /**
+ * @since 4.2
+ */
+ public static Expression exists(ObjectSelect<?> subQuery) {
+ return new ASTExists(new ASTSubquery(subQuery));
+ }
+
+ /**
+ * @since 4.2
+ */
+ public static Expression exists(ColumnSelect<?> subQuery) {
+ return new ASTExists(new ASTSubquery(subQuery));
+ }
+
+ /**
+ * @since 4.2
+ */
+ public static Expression inExp(Expression exp, ColumnSelect<?> subQuery) {
+ if(!(exp instanceof SimpleNode)) {
+ throw new IllegalArgumentException("exp should be instance of SimpleNode");
+ }
+ return new ASTIn((SimpleNode)exp, new ASTSubquery(subQuery));
+ }
+
+ /**
+ * @since 4.2
+ */
+ public static Expression notInExp(Expression exp, ColumnSelect<?> subQuery) {
+ if(!(exp instanceof SimpleNode)) {
+ throw new IllegalArgumentException("exp should be instance of SimpleNode");
+ }
+ return new ASTNotIn((SimpleNode)exp, new ASTSubquery(subQuery));
+ }
}
http://git-wip-us.apache.org/repos/asf/cayenne/blob/60c55667/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTEnclosingObject.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTEnclosingObject.java b/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTEnclosingObject.java
new file mode 100644
index 0000000..2789781
--- /dev/null
+++ b/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTEnclosingObject.java
@@ -0,0 +1,71 @@
+/*****************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ ****************************************************************/
+
+package org.apache.cayenne.exp.parser;
+
+import java.io.IOException;
+
+import org.apache.cayenne.exp.Expression;
+
+/**
+ * @since 4.2
+ */
+public class ASTEnclosingObject extends SimpleNode {
+
+ public ASTEnclosingObject(Expression expression) {
+ this();
+ Node node = wrapChild(expression);
+ jjtAddChild(node, 0);
+ node.jjtSetParent(this);
+ }
+
+ public ASTEnclosingObject() {
+ this(0);
+ }
+
+ protected ASTEnclosingObject(int i) {
+ super(i);
+ }
+
+ @Override
+ protected String getExpressionOperator(int index) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ protected Object evaluateNode(Object o) throws Exception {
+ return o;
+ }
+
+ @Override
+ public void appendAsString(Appendable out) throws IOException {
+ out.append("SUPER:");
+ super.appendAsString(out);
+ }
+
+ @Override
+ public Expression shallowCopy() {
+ return new ASTEnclosingObject(id);
+ }
+
+ @Override
+ public int getType() {
+ return Expression.ENCLOSING_OBJECT;
+ }
+}
http://git-wip-us.apache.org/repos/asf/cayenne/blob/60c55667/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTExists.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTExists.java b/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTExists.java
new file mode 100644
index 0000000..9f06dc2
--- /dev/null
+++ b/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTExists.java
@@ -0,0 +1,62 @@
+/*****************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ ****************************************************************/
+
+package org.apache.cayenne.exp.parser;
+
+import org.apache.cayenne.exp.Expression;
+
+/**
+ * @since 4.2
+ */
+public class ASTExists extends ConditionNode {
+
+ public ASTExists(ASTSubquery subquery) {
+ super(0);
+ jjtAddChild(subquery, 0);
+ }
+
+ ASTExists(int id) {
+ super(id);
+ }
+
+ @Override
+ protected int getRequiredChildrenCount() {
+ return 1;
+ }
+
+ @Override
+ protected Boolean evaluateSubNode(Object o, Object[] evaluatedChildren) throws Exception {
+ return null;
+ }
+
+ @Override
+ protected String getExpressionOperator(int index) {
+ return null;
+ }
+
+ @Override
+ public Expression shallowCopy() {
+ return new ASTExists(id);
+ }
+
+ @Override
+ public int getType() {
+ return Expression.EXISTS;
+ }
+}
http://git-wip-us.apache.org/repos/asf/cayenne/blob/60c55667/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTFullObject.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTFullObject.java b/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTFullObject.java
index 25f6d97..da15668 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTFullObject.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTFullObject.java
@@ -19,6 +19,8 @@
package org.apache.cayenne.exp.parser;
+import java.io.IOException;
+
import org.apache.cayenne.exp.Expression;
/**
@@ -52,6 +54,12 @@ public class ASTFullObject extends SimpleNode {
}
@Override
+ public void appendAsString(Appendable out) throws IOException {
+ out.append(":FULL_OBJECT:");
+ super.appendAsString(out);
+ }
+
+ @Override
public Expression shallowCopy() {
return new ASTFullObject(id);
}
http://git-wip-us.apache.org/repos/asf/cayenne/blob/60c55667/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTIn.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTIn.java b/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTIn.java
index d1801ce..000a4c6 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTIn.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTIn.java
@@ -42,10 +42,10 @@ public class ASTIn extends ConditionNode {
super(ExpressionParserTreeConstants.JJTIN);
}
- public ASTIn(SimpleNode path, ASTList list) {
+ public ASTIn(SimpleNode path, SimpleNode node) {
super(ExpressionParserTreeConstants.JJTIN);
jjtAddChild(path, 0);
- jjtAddChild(list, 1);
+ jjtAddChild(node, 1);
connectChildren();
}
http://git-wip-us.apache.org/repos/asf/cayenne/blob/60c55667/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTNotIn.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTNotIn.java b/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTNotIn.java
index 4ce7d46..74761e2 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTNotIn.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTNotIn.java
@@ -37,10 +37,10 @@ public class ASTNotIn extends ConditionNode {
super(ExpressionParserTreeConstants.JJTNOTIN);
}
- public ASTNotIn(SimpleNode path, ASTList list) {
+ public ASTNotIn(SimpleNode path, SimpleNode node) {
super(ExpressionParserTreeConstants.JJTNOTIN);
jjtAddChild(path, 0);
- jjtAddChild(list, 1);
+ jjtAddChild(node, 1);
connectChildren();
}
http://git-wip-us.apache.org/repos/asf/cayenne/blob/60c55667/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTSubquery.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTSubquery.java b/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTSubquery.java
new file mode 100644
index 0000000..f157501
--- /dev/null
+++ b/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTSubquery.java
@@ -0,0 +1,95 @@
+/*****************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ ****************************************************************/
+
+package org.apache.cayenne.exp.parser;
+
+import java.io.IOException;
+
+import org.apache.cayenne.ObjectContext;
+import org.apache.cayenne.Persistent;
+import org.apache.cayenne.exp.Expression;
+import org.apache.cayenne.query.ColumnSelect;
+import org.apache.cayenne.access.translator.select.ColumnSelectWrapper;
+import org.apache.cayenne.query.ObjectSelect;
+import org.apache.cayenne.access.translator.select.ObjectSelectWrapper;
+import org.apache.cayenne.query.SelectQuery;
+import org.apache.cayenne.access.translator.select.SelectQueryWrapper;
+import org.apache.cayenne.access.translator.select.TranslatableQueryWrapper;
+
+/**
+ * @since 4.2
+ */
+public class ASTSubquery extends SimpleNode {
+
+ private final TranslatableQueryWrapper query;
+
+ public ASTSubquery(SelectQuery<?> query) {
+ this(new SelectQueryWrapper(query));
+ }
+
+ public ASTSubquery(ObjectSelect<?> query) {
+ this(new ObjectSelectWrapper(query));
+ }
+
+ public ASTSubquery(ColumnSelect<?> query) {
+ this(new ColumnSelectWrapper(query));
+ }
+
+ public ASTSubquery(TranslatableQueryWrapper query) {
+ super(0);
+ this.query = query;
+ }
+
+ @Override
+ protected String getExpressionOperator(int index) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ protected Object evaluateNode(Object o) {
+ ObjectContext context;
+ if(o instanceof Persistent) {
+ context = ((Persistent) o).getObjectContext();
+ } else {
+ throw new UnsupportedOperationException("Can't evaluate subquery expression against non-persistent object");
+ }
+
+ return context.select(query.unwrap());
+ }
+
+ @Override
+ public Expression shallowCopy() {
+ return new ASTSubquery(query);
+ }
+
+ @Override
+ public int getType() {
+ return Expression.SUBQUERY;
+ }
+
+ @Override
+ public void appendAsString(Appendable out) throws IOException {
+ out.append("EXISTS");
+ }
+
+ public TranslatableQueryWrapper getQuery() {
+ return query;
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/cayenne/blob/60c55667/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/PatternMatchNode.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/PatternMatchNode.java b/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/PatternMatchNode.java
index 6fb7bff..f850443 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/PatternMatchNode.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/PatternMatchNode.java
@@ -85,6 +85,13 @@ public abstract class PatternMatchNode extends ConditionNode {
escapeChar = value;
}
+ /**
+ * @since 4.2
+ */
+ public boolean isIgnoringCase() {
+ return ignoringCase;
+ }
+
protected boolean matchPattern(String string) {
return (string != null) && getPattern().matcher(string).find();
}
http://git-wip-us.apache.org/repos/asf/cayenne/blob/60c55667/cayenne-server/src/main/java/org/apache/cayenne/exp/property/BaseProperty.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/exp/property/BaseProperty.java b/cayenne-server/src/main/java/org/apache/cayenne/exp/property/BaseProperty.java
index 1ce3836..fa16417 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/exp/property/BaseProperty.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/exp/property/BaseProperty.java
@@ -28,6 +28,7 @@ import org.apache.cayenne.exp.Expression;
import org.apache.cayenne.exp.ExpressionFactory;
import org.apache.cayenne.exp.FunctionExpressionFactory;
import org.apache.cayenne.exp.parser.ASTPath;
+import org.apache.cayenne.query.ColumnSelect;
import org.apache.cayenne.query.Ordering;
import org.apache.cayenne.query.Orderings;
import org.apache.cayenne.query.SortOrder;
@@ -386,4 +387,25 @@ public class BaseProperty<E> implements Property<E> {
public Expression nin(Collection<E> values) {
return ExpressionFactory.notInExp(getExpression(), values);
}
+
+ /**
+ * @return An expression for finding objects with values in the given subquery
+ */
+ public Expression in(ColumnSelect<? extends E> subquery) {
+ return ExpressionFactory.inExp(getExpression(), subquery);
+ }
+
+ /**
+ * @return An expression for finding objects with values not in the given subquery
+ */
+ public Expression nin(ColumnSelect<? extends E> subquery) {
+ return ExpressionFactory.notInExp(getExpression(), subquery);
+ }
+
+ /**
+ * @return property that will be translated relative to parent query
+ */
+ public BaseProperty<E> enclosing() {
+ return PropertyFactory.createBase(ExpressionFactory.enclosingObjectExp(getExpression()), getType());
+ }
}
http://git-wip-us.apache.org/repos/asf/cayenne/blob/60c55667/cayenne-server/src/main/java/org/apache/cayenne/exp/property/DateProperty.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/exp/property/DateProperty.java b/cayenne-server/src/main/java/org/apache/cayenne/exp/property/DateProperty.java
index 8c32512..ab67541 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/exp/property/DateProperty.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/exp/property/DateProperty.java
@@ -20,6 +20,7 @@
package org.apache.cayenne.exp.property;
import org.apache.cayenne.exp.Expression;
+import org.apache.cayenne.exp.ExpressionFactory;
import org.apache.cayenne.exp.FunctionExpressionFactory;
/**
@@ -137,4 +138,11 @@ public class DateProperty<E> extends BaseProperty<E> implements ComparableProper
public DateProperty<E> min() {
return PropertyFactory.createDate(FunctionExpressionFactory.minExp(getExpression()), getType());
}
+
+ /**
+ * @return property that will be translated relative to parent query
+ */
+ public DateProperty<E> enclosing() {
+ return PropertyFactory.createDate(ExpressionFactory.enclosingObjectExp(getExpression()), getType());
+ }
}
http://git-wip-us.apache.org/repos/asf/cayenne/blob/60c55667/cayenne-server/src/main/java/org/apache/cayenne/exp/property/EntityProperty.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/exp/property/EntityProperty.java b/cayenne-server/src/main/java/org/apache/cayenne/exp/property/EntityProperty.java
index 41c225f..502e40b 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/exp/property/EntityProperty.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/exp/property/EntityProperty.java
@@ -21,6 +21,7 @@ package org.apache.cayenne.exp.property;
import org.apache.cayenne.Persistent;
import org.apache.cayenne.exp.Expression;
+import org.apache.cayenne.exp.ExpressionFactory;
import org.apache.cayenne.exp.parser.ASTPath;
/**
@@ -70,4 +71,12 @@ public class EntityProperty<E extends Persistent> extends BaseProperty<E> implem
? this
: PropertyFactory.createEntity(getName() + "+", getType());
}
+
+ /**
+ * @return property that will be translated relative to parent query
+ */
+ public EntityProperty<E> enclosing() {
+ return PropertyFactory.createEntity(ExpressionFactory.enclosingObjectExp(getExpression()), getType());
+ }
+
}
http://git-wip-us.apache.org/repos/asf/cayenne/blob/60c55667/cayenne-server/src/main/java/org/apache/cayenne/exp/property/ListProperty.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/exp/property/ListProperty.java b/cayenne-server/src/main/java/org/apache/cayenne/exp/property/ListProperty.java
index ded0084..861fa69 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/exp/property/ListProperty.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/exp/property/ListProperty.java
@@ -23,6 +23,7 @@ import java.util.List;
import org.apache.cayenne.Persistent;
import org.apache.cayenne.exp.Expression;
+import org.apache.cayenne.exp.ExpressionFactory;
import org.apache.cayenne.exp.parser.ASTPath;
/**
@@ -66,4 +67,11 @@ public class ListProperty<V extends Persistent> extends CollectionProperty<V, Li
? this
: PropertyFactory.createList(getName() + "+", getEntityType());
}
+
+ /**
+ * @return property that will be translated relative to parent query
+ */
+ public ListProperty<V> enclosing() {
+ return PropertyFactory.createList(null, ExpressionFactory.enclosingObjectExp(getExpression()), getEntityType());
+ }
}
http://git-wip-us.apache.org/repos/asf/cayenne/blob/60c55667/cayenne-server/src/main/java/org/apache/cayenne/exp/property/MapProperty.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/exp/property/MapProperty.java b/cayenne-server/src/main/java/org/apache/cayenne/exp/property/MapProperty.java
index b9b24b3..bd03eff 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/exp/property/MapProperty.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/exp/property/MapProperty.java
@@ -232,4 +232,11 @@ public class MapProperty<K, V extends Persistent> extends BaseProperty<Map<K, V>
protected Class<V> getEntityType() {
return entityType;
}
+
+ /**
+ * @return property that will be translated relative to parent query
+ */
+ public MapProperty<K, V> enclosing() {
+ return PropertyFactory.createMap(null, ExpressionFactory.enclosingObjectExp(getExpression()), getKeyType(), getEntityType());
+ }
}
http://git-wip-us.apache.org/repos/asf/cayenne/blob/60c55667/cayenne-server/src/main/java/org/apache/cayenne/exp/property/NumericProperty.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/exp/property/NumericProperty.java b/cayenne-server/src/main/java/org/apache/cayenne/exp/property/NumericProperty.java
index 14e07d1..a1f67d9 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/exp/property/NumericProperty.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/exp/property/NumericProperty.java
@@ -20,6 +20,7 @@
package org.apache.cayenne.exp.property;
import org.apache.cayenne.exp.Expression;
+import org.apache.cayenne.exp.ExpressionFactory;
import org.apache.cayenne.exp.FunctionExpressionFactory;
import org.apache.cayenne.exp.parser.ASTAdd;
import org.apache.cayenne.exp.parser.ASTDivide;
@@ -183,4 +184,11 @@ public class NumericProperty<E extends Number> extends BaseProperty<E> implement
return PropertyFactory.createNumeric(alias, this.getExpression(), this.getType());
}
+ /**
+ * @return property that will be translated relative to parent query
+ */
+ public NumericProperty<E> enclosing() {
+ return PropertyFactory.createNumeric(ExpressionFactory.enclosingObjectExp(getExpression()), getType());
+ }
+
}
http://git-wip-us.apache.org/repos/asf/cayenne/blob/60c55667/cayenne-server/src/main/java/org/apache/cayenne/exp/property/SetProperty.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/exp/property/SetProperty.java b/cayenne-server/src/main/java/org/apache/cayenne/exp/property/SetProperty.java
index 12b1fd2..b6c5d47 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/exp/property/SetProperty.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/exp/property/SetProperty.java
@@ -23,6 +23,7 @@ import java.util.Set;
import org.apache.cayenne.Persistent;
import org.apache.cayenne.exp.Expression;
+import org.apache.cayenne.exp.ExpressionFactory;
import org.apache.cayenne.exp.parser.ASTPath;
/**
@@ -62,4 +63,11 @@ public class SetProperty<V extends Persistent> extends CollectionProperty<V, Set
? this
: PropertyFactory.createSet(getName() + "+", getEntityType());
}
+
+ /**
+ * @return property that will be translated relative to parent query
+ */
+ public SetProperty<V> enclosing() {
+ return PropertyFactory.createSet(null, ExpressionFactory.enclosingObjectExp(getExpression()), getEntityType());
+ }
}
http://git-wip-us.apache.org/repos/asf/cayenne/blob/60c55667/cayenne-server/src/main/java/org/apache/cayenne/exp/property/StringProperty.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/exp/property/StringProperty.java b/cayenne-server/src/main/java/org/apache/cayenne/exp/property/StringProperty.java
index 87a6e0f..1c09822 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/exp/property/StringProperty.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/exp/property/StringProperty.java
@@ -301,4 +301,12 @@ public class StringProperty<E extends CharSequence> extends BaseProperty<E> {
public StringProperty<E> alias(String alias) {
return PropertyFactory.createString(alias, this.getExpression(), this.getType());
}
+
+ /**
+ * @return property that will be translated relative to parent query
+ */
+ public StringProperty<E> enclosing() {
+ return PropertyFactory.createString(ExpressionFactory.enclosingObjectExp(getExpression()), getType());
+ }
+
}
http://git-wip-us.apache.org/repos/asf/cayenne/blob/60c55667/cayenne-server/src/test/java/org/apache/cayenne/query/ObjectSelect_SubqueryIT.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/test/java/org/apache/cayenne/query/ObjectSelect_SubqueryIT.java b/cayenne-server/src/test/java/org/apache/cayenne/query/ObjectSelect_SubqueryIT.java
new file mode 100644
index 0000000..8a91131
--- /dev/null
+++ b/cayenne-server/src/test/java/org/apache/cayenne/query/ObjectSelect_SubqueryIT.java
@@ -0,0 +1,167 @@
+/*****************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ ****************************************************************/
+
+package org.apache.cayenne.query;
+
+import java.util.Date;
+import java.util.List;
+
+import org.apache.cayenne.access.DataContext;
+import org.apache.cayenne.di.Inject;
+import org.apache.cayenne.exp.Expression;
+import org.apache.cayenne.exp.ExpressionFactory;
+import org.apache.cayenne.exp.property.PropertyFactory;
+import org.apache.cayenne.test.jdbc.DBHelper;
+import org.apache.cayenne.test.jdbc.TableHelper;
+import org.apache.cayenne.testdo.testmap.Artist;
+import org.apache.cayenne.testdo.testmap.Gallery;
+import org.apache.cayenne.testdo.testmap.Painting;
+import org.apache.cayenne.unit.di.server.CayenneProjects;
+import org.apache.cayenne.unit.di.server.ServerCase;
+import org.apache.cayenne.unit.di.server.UseServerRuntime;
+import org.junit.Before;
+import org.junit.Test;
+
+import static org.junit.Assert.assertEquals;
+
+/**
+ * @since 4.2
+ */
+@UseServerRuntime(CayenneProjects.TESTMAP_PROJECT)
+public class ObjectSelect_SubqueryIT extends ServerCase {
+
+ @Inject
+ DataContext context;
+
+ @Inject
+ private DBHelper dbHelper;
+
+ @Before
+ public void createArtistsDataSet() throws Exception {
+ TableHelper tArtist = new TableHelper(dbHelper, "ARTIST");
+ tArtist.setColumns("ARTIST_ID", "ARTIST_NAME", "DATE_OF_BIRTH");
+
+ long dateBase = System.currentTimeMillis() - 2 * 24 * 3600 * 1000;
+ for (int i = 1; i <= 20; i++) {
+ tArtist.insert(i, "artist" + i, new java.sql.Date(dateBase + 24 * 3600 * 1000 * i));
+ }
+
+ TableHelper tGallery = new TableHelper(dbHelper, "GALLERY");
+ tGallery.setColumns("GALLERY_ID", "GALLERY_NAME");
+ tGallery.insert(1, "tate modern");
+
+ TableHelper tPaintings = new TableHelper(dbHelper, "PAINTING");
+ tPaintings.setColumns("PAINTING_ID", "PAINTING_TITLE", "ARTIST_ID", "GALLERY_ID");
+ for (int i = 1; i <= 20; i++) {
+ tPaintings.insert(i, "painting" + i, i % 5 + 1, 1);
+ }
+ }
+
+ @Test
+ public void selectQuery_simpleExists() {
+ long count = ObjectSelect.query(Artist.class)
+ .where(ExpressionFactory.exists(SelectQuery.query(Painting.class, Painting.PAINTING_TITLE.like("painting%"))))
+ .selectCount(context);
+ assertEquals(20L, count);
+ }
+
+ @Test
+ public void selectQuery_existsWithExpressionFromParentQuery() {
+ Expression exp = Painting.TO_ARTIST.eq(PropertyFactory.createSelf(Artist.class).enclosing())
+ .andExp(Painting.PAINTING_TITLE.like("painting%"))
+ .andExp(Artist.ARTIST_NAME.enclosing().like("art%"));
+
+ SelectQuery<Painting> subQuery = SelectQuery.query(Painting.class, exp);
+ subQuery.setColumns(Painting.PAINTING_TITLE);
+
+ long count = ObjectSelect.query(Artist.class)
+ .where(ExpressionFactory.exists(subQuery))
+ .selectCount(context);
+ assertEquals(5L, count);
+ }
+
+ @Test
+ public void selectQuery_twoLevelExists() {
+ Expression exp = Painting.PAINTING_TITLE.like("painting%")
+ .andExp(ExpressionFactory.exists(SelectQuery.query(Gallery.class)));
+ long count = ObjectSelect.query(Artist.class)
+ .where(ExpressionFactory.exists(SelectQuery.query(Painting.class, exp)))
+ .selectCount(context);
+ assertEquals(20L, count);
+ }
+
+ @Test
+ public void selectQuery_twoLevelExistsWithExpressionFromParentQuery() {
+ Expression deepNestedExp = Artist.ARTIST_NAME.enclosing().enclosing().like("art%")
+ .andExp(Painting.TO_GALLERY.enclosing().eq(PropertyFactory.createSelf(Gallery.class)));
+
+ Expression exp = Painting.PAINTING_TITLE.like("painting%")
+ .andExp(ExpressionFactory.exists(SelectQuery.query(Gallery.class, deepNestedExp)))
+ .andExp(Painting.TO_ARTIST.eq(PropertyFactory.createSelf(Artist.class).enclosing()));
+
+ long count = ObjectSelect.query(Artist.class)
+ .where(ExpressionFactory.exists(SelectQuery.query(Painting.class, exp)))
+ .selectCount(context);
+ assertEquals(5L, count);
+ }
+
+ @Test
+ public void objectSelect_twoLevelExistsWithExpressionFromParentQuery() {
+ ObjectSelect<Gallery> deepSubquery = ObjectSelect.query(Gallery.class)
+ .where(Artist.ARTIST_NAME.enclosing().enclosing().like("art%"))
+ .and(Painting.TO_GALLERY.enclosing().eq(PropertyFactory.createSelf(Gallery.class)));
+
+ ObjectSelect<Painting> subquery = ObjectSelect.query(Painting.class)
+ .where(Painting.PAINTING_TITLE.like("painting%"))
+ .and(Painting.TO_ARTIST.eq(PropertyFactory.createSelf(Artist.class).enclosing()))
+ .and(ExpressionFactory.exists(deepSubquery));
+
+ long count = ObjectSelect.query(Artist.class)
+ .where(ExpressionFactory.exists(subquery))
+ .selectCount(context);
+
+ assertEquals(5L, count);
+ }
+
+ @Test
+ public void columnSelect_simpleInSubquery() {
+
+ ColumnSelect<String> subquery = ObjectSelect.columnQuery(Artist.class, Artist.ARTIST_NAME)
+ .where(Artist.DATE_OF_BIRTH.lt(new Date()));
+
+ long count = ObjectSelect.query(Painting.class)
+ .where(Painting.TO_ARTIST.dot(Artist.ARTIST_NAME).in(subquery))
+ .selectCount(context);
+
+ assertEquals(4L, count);
+ }
+
+ @Test
+ public void columnSelect_simpleNotInSubquery() {
+
+ ColumnSelect<String> subquery = ObjectSelect.columnQuery(Artist.class, Artist.ARTIST_NAME)
+ .where(Artist.DATE_OF_BIRTH.lt(new Date()));
+
+ long count = ObjectSelect.query(Painting.class)
+ .where(Painting.TO_ARTIST.dot(Artist.ARTIST_NAME).nin(subquery))
+ .selectCount(context);
+
+ assertEquals(16L, count);
+ }
+}
[09/11] cayenne git commit: CAY-2465 New SelectTranslator
implementation
Posted by nt...@apache.org.
CAY-2465 New SelectTranslator implementation
Project: http://git-wip-us.apache.org/repos/asf/cayenne/repo
Commit: http://git-wip-us.apache.org/repos/asf/cayenne/commit/f6b2dac9
Tree: http://git-wip-us.apache.org/repos/asf/cayenne/tree/f6b2dac9
Diff: http://git-wip-us.apache.org/repos/asf/cayenne/diff/f6b2dac9
Branch: refs/heads/master
Commit: f6b2dac9667343928324cd7ce364ee6e5df17275
Parents: eb0373e
Author: Nikita Timofeev <st...@gmail.com>
Authored: Wed Jan 9 11:41:46 2019 +0300
Committer: Nikita Timofeev <st...@gmail.com>
Committed: Wed Jan 9 11:41:46 2019 +0300
----------------------------------------------------------------------
.../access/MixedResultIncrementalFaultList.java | 12 +-
.../cayenne/access/jdbc/ColumnDescriptor.java | 7 +
.../access/sqlbuilder/AliasedNodeBuilder.java | 6 +-
.../access/sqlbuilder/ColumnNodeBuilder.java | 7 +-
.../cayenne/access/sqlbuilder/SQLBuilder.java | 15 +
.../access/sqlbuilder/ValueNodeBuilder.java | 9 +-
.../access/sqlbuilder/sqltree/AliasedNode.java | 55 +
.../access/sqlbuilder/sqltree/BetweenNode.java | 4 +
.../access/sqlbuilder/sqltree/ColumnNode.java | 6 +-
.../sqlbuilder/sqltree/ExpressionNode.java | 4 +-
.../access/sqlbuilder/sqltree/FunctionNode.java | 6 +-
.../sqlbuilder/sqltree/OpExpressionNode.java | 4 +
.../access/sqlbuilder/sqltree/TextNode.java | 4 +
.../access/sqlbuilder/sqltree/ValueNode.java | 27 +-
.../translator/select/BaseColumnExtractor.java | 44 +
.../translator/select/BaseSQLTreeProcessor.java | 128 +++
.../select/ColumnDescriptorStage.java | 56 +
.../translator/select/ColumnExtractor.java | 33 +
.../translator/select/ColumnExtractorStage.java | 54 +
.../translator/select/ColumnSelectWrapper.java | 84 ++
.../select/CustomColumnSetExtractor.java | 133 +++
.../select/DataObjectMatchTranslator.java | 152 ---
.../select/DbEntityColumnExtractor.java | 45 +
.../translator/select/DbPathProcessor.java | 108 ++
.../select/DefaultQuotingAppendable.java | 47 +
.../select/DefaultSelectTranslator.java | 1076 ++----------------
.../select/DescriptorColumnExtractor.java | 140 +++
.../access/translator/select/DistinctStage.java | 71 ++
.../access/translator/select/GroupByStage.java | 54 +
.../select/HavingTranslationStage.java | 40 +
.../translator/select/IdColumnExtractor.java | 49 +
.../access/translator/select/JoinStack.java | 219 ----
.../access/translator/select/JoinTreeNode.java | 145 ---
.../translator/select/LimitOffsetStage.java | 37 +
.../translator/select/ObjPathProcessor.java | 132 +++
.../translator/select/ObjectSelectWrapper.java | 84 ++
.../access/translator/select/OrderingStage.java | 81 ++
.../translator/select/OrderingTranslator.java | 127 ---
.../translator/select/PathComponents.java | 95 ++
.../access/translator/select/PathProcessor.java | 129 +++
.../select/PathTranslationResult.java | 51 +
.../translator/select/PathTranslator.java | 60 +
.../translator/select/PrefetchNodeStage.java | 128 +++
.../select/QualifierTranslationStage.java | 74 ++
.../translator/select/QualifierTranslator.java | 1026 ++++++-----------
.../translator/select/QueryAssembler.java | 197 ----
.../translator/select/QueryAssemblerHelper.java | 488 --------
.../translator/select/ResultNodeDescriptor.java | 138 +++
.../translator/select/SQLGenerationStage.java | 44 +
.../translator/select/SelectQueryWrapper.java | 84 ++
.../access/translator/select/TableTree.java | 109 ++
.../access/translator/select/TableTreeNode.java | 75 ++
.../translator/select/TableTreeStage.java | 104 ++
.../select/TranslatableQueryWrapper.java | 57 +
.../translator/select/TranslationStage.java | 27 +
.../translator/select/TranslatorContext.java | 245 ++++
.../select/TrimmingQualifierTranslator.java | 92 --
.../org/apache/cayenne/dba/AutoAdapter.java | 8 +-
.../java/org/apache/cayenne/dba/DbAdapter.java | 10 +-
.../cayenne/dba/DefaultQuotingStrategy.java | 48 +-
.../org/apache/cayenne/dba/JdbcAdapter.java | 25 +-
.../org/apache/cayenne/dba/QuotingStrategy.java | 20 +-
.../cayenne/dba/db2/DB2ActionBuilder.java | 6 +
.../org/apache/cayenne/dba/db2/DB2Adapter.java | 12 +-
.../cayenne/dba/db2/DB2QualifierTranslator.java | 174 ---
.../cayenne/dba/db2/DB2SQLTreeProcessor.java | 85 ++
.../apache/cayenne/dba/db2/DB2SelectAction.java | 39 +
.../cayenne/dba/derby/DerbyActionBuilder.java | 40 +
.../apache/cayenne/dba/derby/DerbyAdapter.java | 23 +-
.../dba/derby/DerbyQualifierTranslator.java | 141 ---
.../dba/derby/DerbySQLTreeProcessor.java | 81 ++
.../cayenne/dba/derby/DerbySelectAction.java | 39 +
.../dba/derby/sqltree/DerbyValueNode.java | 51 +
.../dba/firebird/FirebirdActionBuilder.java | 41 +
.../cayenne/dba/firebird/FirebirdAdapter.java | 21 +-
.../firebird/FirebirdQualifierTranslator.java | 181 ---
.../dba/firebird/FirebirdSQLTreeProcessor.java | 189 +++
.../dba/firebird/FirebirdSelectAction.java | 40 +
.../dba/firebird/sqltree/FirebirdLimitNode.java | 50 +
.../sqltree/FirebirdSubstringFunctionNode.java | 65 ++
.../cayenne/dba/frontbase/FrontBaseAdapter.java | 25 +-
.../frontbase/FrontBaseQualifierTranslator.java | 134 ---
.../frontbase/FrontBaseSQLTreeProcessor.java | 104 ++
.../frontbase/FrontBaseSelectTranslator.java | 52 -
.../apache/cayenne/dba/h2/H2ActionBuilder.java | 39 +
.../org/apache/cayenne/dba/h2/H2Adapter.java | 18 +
.../cayenne/dba/h2/H2SQLTreeProcessor.java | 37 +
.../apache/cayenne/dba/h2/H2SelectAction.java | 39 +
.../cayenne/dba/hsqldb/HSQLDBAdapter.java | 27 +-
.../dba/hsqldb/HSQLQualifierTranslator.java | 86 --
.../dba/hsqldb/HSQLSelectTranslator.java | 57 -
.../cayenne/dba/hsqldb/HSQLTreeProcessor.java | 70 ++
.../cayenne/dba/ingres/IngresAdapter.java | 23 +-
.../dba/ingres/IngresQualifierTranslator.java | 122 --
.../dba/ingres/IngresSelectTranslator.java | 49 -
.../dba/ingres/IngressSQLTreeProcessor.java | 108 ++
.../apache/cayenne/dba/mysql/MySQLAdapter.java | 21 +-
.../dba/mysql/MySQLQualifierTranslator.java | 105 --
.../dba/mysql/MySQLSelectTranslator.java | 57 -
.../cayenne/dba/mysql/MySQLTreeProcessor.java | 68 ++
.../dba/mysql/sqltree/MysqlLikeNode.java | 46 +
.../dba/mysql/sqltree/MysqlLimitOffsetNode.java | 40 +
.../cayenne/dba/openbase/OpenBaseAdapter.java | 21 +-
.../cayenne/dba/openbase/OpenBaseJoinStack.java | 115 --
.../openbase/OpenBaseQualifierTranslator.java | 177 ---
.../dba/openbase/OpenBaseSQLTreeProcessor.java | 109 ++
.../dba/openbase/OpenBaseSelectTranslator.java | 53 -
.../cayenne/dba/oracle/Oracle8Adapter.java | 19 -
.../cayenne/dba/oracle/Oracle8JoinStack.java | 108 --
.../dba/oracle/Oracle8QualifierTranslator.java | 49 -
.../dba/oracle/Oracle8SelectTranslator.java | 47 -
.../cayenne/dba/oracle/OracleAdapter.java | 23 +-
.../dba/oracle/OracleQualifierTranslator.java | 212 ----
.../dba/oracle/OracleSQLTreeProcessor.java | 241 ++++
.../dba/oracle/OracleSelectTranslator.java | 78 --
.../dba/postgres/PostgreSQLTreeProcessor.java | 83 ++
.../cayenne/dba/postgres/PostgresAdapter.java | 27 +-
.../postgres/PostgresQualifierTranslator.java | 189 ---
.../dba/postgres/PostgresSelectTranslator.java | 54 -
.../postgres/sqltree/PositionFunctionNode.java | 43 +
.../sqltree/PostgresExtractFunctionNode.java | 59 +
.../dba/postgres/sqltree/PostgresLikeNode.java | 46 +
.../sqltree/PostgresLimitOffsetNode.java | 60 +
.../cayenne/dba/sqlite/SQLiteActionBuilder.java | 9 +
.../cayenne/dba/sqlite/SQLiteAdapter.java | 23 +-
.../dba/sqlite/SQLiteQualifierTranslator.java | 162 ---
.../cayenne/dba/sqlite/SQLiteSelectAction.java | 43 +
.../cayenne/dba/sqlite/SQLiteTreeProcessor.java | 100 ++
.../cayenne/dba/sqlserver/SQLServerAdapter.java | 23 +-
.../sqlserver/SQLServerSelectTranslator.java | 54 -
.../dba/sqlserver/SQLServerTreeProcessor.java | 128 +++
.../SQLServerTrimmingQualifierTranslator.java | 207 ----
.../sqlserver/sqltree/SQLServerColumnNode.java | 46 +
.../cayenne/dba/sybase/SybaseAdapter.java | 25 +-
.../dba/sybase/SybaseQualifierTranslator.java | 114 --
.../dba/sybase/SybaseSelectTranslator.java | 52 -
.../org/apache/cayenne/query/ColumnSelect.java | 7 +
.../cayenne/query/SelectQueryMetadata.java | 13 +-
.../java/org/apache/cayenne/util/ArrayUtil.java | 260 +++++
.../cayenne/FlattenedRelationshipsIT.java | 3 +-
.../select/BaseColumnExtractorTest.java | 43 +
.../select/ColumnDescriptorStageTest.java | 55 +
.../select/CustomColumnSetExtractorTest.java | 94 ++
.../select/DbEntityColumnExtractorTest.java | 121 ++
.../select/DefaultObjectSelectTranslatorIT.java | 136 +++
.../select/DefaultSelectTranslatorIT.java | 249 ++--
.../select/DescriptorColumnExtractorTest.java | 97 ++
.../translator/select/DistinctStageTest.java | 103 ++
.../translator/select/GroupByStageTest.java | 86 ++
.../select/HavingTranslationStageTest.java | 90 ++
.../select/IdColumnExtractorTest.java | 113 ++
.../translator/select/LimitOffsetStageTest.java | 65 ++
.../select/MockQueryMetadataBuilder.java | 100 ++
.../select/MockQueryWrapperBuilder.java | 137 +++
.../select/MockTranslatorContext.java | 35 +
.../translator/select/ObjPathProcessorIT.java | 92 ++
.../translator/select/ObjPathProcessorIT2.java | 91 ++
.../translator/select/ObjPathProcessorIT3.java | 82 ++
.../translator/select/ObjPathProcessorIT4.java | 72 ++
.../translator/select/OrderingStageTest.java | 110 ++
.../translator/select/OrderingTranslatorIT.java | 136 ---
.../translator/select/PathComponentsTest.java | 77 ++
.../select/QualifierTranslationStageTest.java | 89 ++
.../select/QualifierTranslatorIT.java | 164 ---
.../select/QualifierTranslatorTest.java | 524 +++++++++
.../translator/select/QueryAssemblerIT.java | 61 -
.../translator/select/TstQueryAssembler.java | 70 --
.../cayenne/exp/property/PathAliasesIT.java | 4 +-
.../apache/cayenne/query/ColumnSelectIT.java | 56 +-
.../cayenne/unit/IngresUnitDbAdapter.java | 8 +
.../org/apache/cayenne/util/ArrayUtilTest.java | 354 ++++++
171 files changed, 9096 insertions(+), 6744 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/cayenne/blob/f6b2dac9/cayenne-server/src/main/java/org/apache/cayenne/access/MixedResultIncrementalFaultList.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/access/MixedResultIncrementalFaultList.java b/cayenne-server/src/main/java/org/apache/cayenne/access/MixedResultIncrementalFaultList.java
index 9ca2136..f8c02b8 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/access/MixedResultIncrementalFaultList.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/access/MixedResultIncrementalFaultList.java
@@ -46,7 +46,6 @@ import org.apache.cayenne.util.Util;
* if there is no Persistent objects in the result Collection it will be iterated as is, without faulting anything.
*
* @see QueryMetadata#getPageSize()
- * @see org.apache.cayenne.access.translator.select.DefaultSelectTranslator
* @see org.apache.cayenne.query.SelectQueryMetadata
*
* @since 4.0
@@ -142,7 +141,10 @@ class MixedResultIncrementalFaultList<E> extends IncrementalFaultList<E> {
for (int i = fromIndex; i < toIndex; i++) {
Object[] object = (Object[])elements.get(i);
if (helper.unresolvedSuspect(object[dataIdx])) {
- quals.add(buildIdQualifier(dataIdx, object));
+ Expression nextQualifier = buildIdQualifier(dataIdx, object);
+ if(nextQualifier != null) {
+ quals.add(nextQualifier);
+ }
}
}
@@ -188,7 +190,9 @@ class MixedResultIncrementalFaultList<E> extends IncrementalFaultList<E> {
Expression buildIdQualifier(int index, Object[] data) {
Map<String, Object> map;
if(data[index] instanceof Map) {
- map = (Map<String, Object>)data[index];
+ map = (Map<String, Object>) data[index];
+ } else if(data[index] == null) {
+ return null;
} else {
map = new HashMap<>();
int i = 0;
@@ -235,6 +239,8 @@ class MixedResultIncrementalFaultList<E> extends IncrementalFaultList<E> {
return false;
}
}
+ } else if(dataInTheList[dataIdx] == null) {
+ return false;
} else {
for(Object id : map.values()) {
if (!dataInTheList[dataIdx++].equals(id)) {
http://git-wip-us.apache.org/repos/asf/cayenne/blob/f6b2dac9/cayenne-server/src/main/java/org/apache/cayenne/access/jdbc/ColumnDescriptor.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/access/jdbc/ColumnDescriptor.java b/cayenne-server/src/main/java/org/apache/cayenne/access/jdbc/ColumnDescriptor.java
index 632f5e3..90cd61b 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/access/jdbc/ColumnDescriptor.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/access/jdbc/ColumnDescriptor.java
@@ -161,6 +161,13 @@ public class ColumnDescriptor {
}
/**
+ * @since 4.2
+ */
+ public void setAttribute(DbAttribute attribute) {
+ this.attribute = attribute;
+ }
+
+ /**
* Returns true if another object is a ColumnDescriptor with the same name,
* name prefix, table and procedure names. Other fields are ignored in the
* equality test.
http://git-wip-us.apache.org/repos/asf/cayenne/blob/f6b2dac9/cayenne-server/src/main/java/org/apache/cayenne/access/sqlbuilder/AliasedNodeBuilder.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/access/sqlbuilder/AliasedNodeBuilder.java b/cayenne-server/src/main/java/org/apache/cayenne/access/sqlbuilder/AliasedNodeBuilder.java
index 50f07bd..aae7835 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/access/sqlbuilder/AliasedNodeBuilder.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/access/sqlbuilder/AliasedNodeBuilder.java
@@ -19,9 +19,8 @@
package org.apache.cayenne.access.sqlbuilder;
-import org.apache.cayenne.access.sqlbuilder.sqltree.EmptyNode;
+import org.apache.cayenne.access.sqlbuilder.sqltree.AliasedNode;
import org.apache.cayenne.access.sqlbuilder.sqltree.Node;
-import org.apache.cayenne.access.sqlbuilder.sqltree.TextNode;
/**
* @since 4.2
@@ -38,9 +37,8 @@ class AliasedNodeBuilder implements NodeBuilder {
@Override
public Node build() {
- Node root = new EmptyNode();
+ Node root = new AliasedNode(alias);
root.addChild(nodeBuilder.build());
- root.addChild(new TextNode(" " + alias));
return root;
}
}
http://git-wip-us.apache.org/repos/asf/cayenne/blob/f6b2dac9/cayenne-server/src/main/java/org/apache/cayenne/access/sqlbuilder/ColumnNodeBuilder.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/access/sqlbuilder/ColumnNodeBuilder.java b/cayenne-server/src/main/java/org/apache/cayenne/access/sqlbuilder/ColumnNodeBuilder.java
index 8236744..55c1032 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/access/sqlbuilder/ColumnNodeBuilder.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/access/sqlbuilder/ColumnNodeBuilder.java
@@ -74,13 +74,10 @@ public class ColumnNodeBuilder implements ExpressionTrait {
@Override
public Node build() {
- ColumnNode columnNode;
if(unescaped) {
- columnNode = new UnescapedColumnNode(table, column, alias, attribute);
- } else {
- columnNode = new ColumnNode(table, column, alias, attribute);
+ return new UnescapedColumnNode(table, column, alias, attribute);
}
- return columnNode;
+ return new ColumnNode(table, column, alias, attribute);
}
}
http://git-wip-us.apache.org/repos/asf/cayenne/blob/f6b2dac9/cayenne-server/src/main/java/org/apache/cayenne/access/sqlbuilder/SQLBuilder.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/access/sqlbuilder/SQLBuilder.java b/cayenne-server/src/main/java/org/apache/cayenne/access/sqlbuilder/SQLBuilder.java
index 0dcdfc1..dfa3d2e 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/access/sqlbuilder/SQLBuilder.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/access/sqlbuilder/SQLBuilder.java
@@ -19,6 +19,7 @@
package org.apache.cayenne.access.sqlbuilder;
+import org.apache.cayenne.access.sqlbuilder.sqltree.AliasedNode;
import org.apache.cayenne.access.sqlbuilder.sqltree.ColumnNode;
import org.apache.cayenne.access.sqlbuilder.sqltree.FunctionNode;
import org.apache.cayenne.access.sqlbuilder.sqltree.Node;
@@ -87,6 +88,17 @@ public final class SQLBuilder {
if(suppressAlias(node)) {
return node(node);
}
+
+ if(node instanceof FunctionNode) {
+ ((FunctionNode) node).setAlias(alias);
+ return node(node);
+ }
+
+ if(node instanceof ColumnNode) {
+ ((ColumnNode) node).setAlias(alias);
+ return node(node);
+ }
+
return new AliasedNodeBuilder(node(node), alias);
}
@@ -154,6 +166,9 @@ public final class SQLBuilder {
} else if(node.getType() == NodeType.FUNCTION && ((FunctionNode) node).getAlias() != null) {
suppressAlias = true;
return false;
+ } else if(node instanceof AliasedNode) {
+ suppressAlias = true;
+ return false;
}
return true;
}
http://git-wip-us.apache.org/repos/asf/cayenne/blob/f6b2dac9/cayenne-server/src/main/java/org/apache/cayenne/access/sqlbuilder/ValueNodeBuilder.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/access/sqlbuilder/ValueNodeBuilder.java b/cayenne-server/src/main/java/org/apache/cayenne/access/sqlbuilder/ValueNodeBuilder.java
index 5e027c6..4e3e659 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/access/sqlbuilder/ValueNodeBuilder.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/access/sqlbuilder/ValueNodeBuilder.java
@@ -32,6 +32,8 @@ public class ValueNodeBuilder implements NodeBuilder, ExpressionTrait {
private DbAttribute attribute;
+ private boolean isArray;
+
ValueNodeBuilder(Object value) {
this.value = value;
}
@@ -41,8 +43,13 @@ public class ValueNodeBuilder implements NodeBuilder, ExpressionTrait {
return this;
}
+ public ValueNodeBuilder array(boolean isArray) {
+ this.isArray = isArray;
+ return this;
+ }
+
@Override
public Node build() {
- return new ValueNode(value, attribute);
+ return new ValueNode(value, isArray, attribute);
}
}
http://git-wip-us.apache.org/repos/asf/cayenne/blob/f6b2dac9/cayenne-server/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/AliasedNode.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/AliasedNode.java b/cayenne-server/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/AliasedNode.java
new file mode 100644
index 0000000..524fc14
--- /dev/null
+++ b/cayenne-server/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/AliasedNode.java
@@ -0,0 +1,55 @@
+/*****************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ ****************************************************************/
+
+package org.apache.cayenne.access.sqlbuilder.sqltree;
+
+import org.apache.cayenne.access.sqlbuilder.QuotingAppendable;
+
+/**
+ * @since 4.2
+ */
+public class AliasedNode extends Node {
+
+ protected final String alias;
+
+ public AliasedNode(String alias) {
+ this.alias = alias;
+ }
+
+ @Override
+ public Node copy() {
+ return new AliasedNode(alias);
+ }
+
+ @Override
+ public QuotingAppendable append(QuotingAppendable buffer) {
+ return buffer;
+ }
+
+ @Override
+ public void appendChildrenEnd(QuotingAppendable buffer) {
+ super.appendChildrenEnd(buffer);
+ buffer.append(' ').append(alias);
+ }
+
+ public String getAlias() {
+ return alias;
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/cayenne/blob/f6b2dac9/cayenne-server/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/BetweenNode.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/BetweenNode.java b/cayenne-server/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/BetweenNode.java
index 1102a8a..6826931 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/BetweenNode.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/BetweenNode.java
@@ -49,4 +49,8 @@ public class BetweenNode extends ExpressionNode {
public Node copy() {
return new BetweenNode(not);
}
+
+ public boolean isNot() {
+ return not;
+ }
}
http://git-wip-us.apache.org/repos/asf/cayenne/blob/f6b2dac9/cayenne-server/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/ColumnNode.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/ColumnNode.java b/cayenne-server/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/ColumnNode.java
index b610104..9c49a4b 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/ColumnNode.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/ColumnNode.java
@@ -29,8 +29,8 @@ public class ColumnNode extends Node {
protected final String table;
protected final String column;
- protected final String alias;
protected final DbAttribute attribute;
+ protected String alias;
public ColumnNode(String table, String column, String alias, DbAttribute attribute) {
super(NodeType.COLUMN);
@@ -65,6 +65,10 @@ public class ColumnNode extends Node {
return alias;
}
+ public void setAlias(String alias) {
+ this.alias = alias;
+ }
+
public DbAttribute getAttribute() {
return attribute;
}
http://git-wip-us.apache.org/repos/asf/cayenne/blob/f6b2dac9/cayenne-server/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/ExpressionNode.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/ExpressionNode.java b/cayenne-server/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/ExpressionNode.java
index 8344380..3e9795e 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/ExpressionNode.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/ExpressionNode.java
@@ -41,14 +41,14 @@ public class ExpressionNode extends Node {
@Override
public void appendChildrenStart(QuotingAppendable buffer) {
- if(parent.type != NodeType.WHERE && parent.type != NodeType.JOIN) {
+ if(parent != null && parent.type != NodeType.WHERE && parent.type != NodeType.JOIN) {
buffer.append(" (");
}
}
@Override
public void appendChildrenEnd(QuotingAppendable buffer) {
- if(parent.type != NodeType.WHERE && parent.type != NodeType.JOIN) {
+ if(parent != null && parent.type != NodeType.WHERE && parent.type != NodeType.JOIN) {
buffer.append(" )");
}
}
http://git-wip-us.apache.org/repos/asf/cayenne/blob/f6b2dac9/cayenne-server/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/FunctionNode.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/FunctionNode.java b/cayenne-server/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/FunctionNode.java
index 23b0fa7..3673ed0 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/FunctionNode.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/FunctionNode.java
@@ -28,8 +28,8 @@ import org.apache.cayenne.access.sqlbuilder.QuotingAppendable;
public class FunctionNode extends Node {
private final String functionName;
- private final String alias;
private final boolean needParentheses;
+ private String alias;
public FunctionNode(String functionName, String alias) {
this(functionName, alias, true);
@@ -103,6 +103,10 @@ public class FunctionNode extends Node {
return alias;
}
+ public void setAlias(String alias) {
+ this.alias = alias;
+ }
+
@Override
public Node copy() {
return new FunctionNode(functionName, alias, needParentheses);
http://git-wip-us.apache.org/repos/asf/cayenne/blob/f6b2dac9/cayenne-server/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/OpExpressionNode.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/OpExpressionNode.java b/cayenne-server/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/OpExpressionNode.java
index 7099000..6b1b55d 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/OpExpressionNode.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/OpExpressionNode.java
@@ -41,4 +41,8 @@ public class OpExpressionNode extends ExpressionNode {
public Node copy() {
return new OpExpressionNode(op);
}
+
+ public String getOp() {
+ return op;
+ }
}
http://git-wip-us.apache.org/repos/asf/cayenne/blob/f6b2dac9/cayenne-server/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/TextNode.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/TextNode.java b/cayenne-server/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/TextNode.java
index 45ee5cf..afd0f47 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/TextNode.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/TextNode.java
@@ -41,4 +41,8 @@ public class TextNode extends Node {
public Node copy() {
return new TextNode(text);
}
+
+ public CharSequence getText() {
+ return text;
+ }
}
http://git-wip-us.apache.org/repos/asf/cayenne/blob/f6b2dac9/cayenne-server/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/ValueNode.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/ValueNode.java b/cayenne-server/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/ValueNode.java
index 2ba5fbc..2911397 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/ValueNode.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/ValueNode.java
@@ -34,12 +34,14 @@ import org.apache.cayenne.map.DbAttribute;
public class ValueNode extends Node {
private final Object value;
+ private final boolean isArray;
// Used as hint for type of this value
private final DbAttribute attribute;
- public ValueNode(Object value, DbAttribute attribute) {
+ public ValueNode(Object value, boolean isArray, DbAttribute attribute) {
super(NodeType.VALUE);
this.value = value;
+ this.isArray = isArray;
this.attribute = attribute;
}
@@ -51,6 +53,10 @@ public class ValueNode extends Node {
return attribute;
}
+ public boolean isArray() {
+ return isArray;
+ }
+
@Override
public QuotingAppendable append(QuotingAppendable buffer) {
appendValue(value, buffer);
@@ -62,8 +68,7 @@ public class ValueNode extends Node {
return;
}
- boolean isArray = val.getClass().isArray();
- if(isArray) {
+ if(isArray && val.getClass().isArray()) {
if(val instanceof short[]) {
appendValue((short[])val, buffer);
} else if(val instanceof char[]) {
@@ -82,7 +87,7 @@ public class ValueNode extends Node {
appendValue((Object[]) val, buffer);
} else if(val instanceof byte[]) {
// append byte[] array as single object
- appendObjectValue(buffer, val);
+ appendValue((byte[])val, buffer);
} else {
throw new CayenneRuntimeException("Unsupported array type %s", val.getClass().getName());
}
@@ -227,6 +232,18 @@ public class ValueNode extends Node {
}
}
+ private void appendValue(byte[] val, QuotingAppendable buffer) {
+ boolean first = true;
+ for(byte i : val) {
+ if(first) {
+ first = false;
+ } else {
+ buffer.append(',');
+ }
+ appendValue(i, buffer);
+ }
+ }
+
private void appendValue(Object[] val, QuotingAppendable buffer) {
boolean first = true;
for(Object i : val) {
@@ -241,6 +258,6 @@ public class ValueNode extends Node {
@Override
public Node copy() {
- return new ValueNode(value, attribute);
+ return new ValueNode(value, isArray, attribute);
}
}
http://git-wip-us.apache.org/repos/asf/cayenne/blob/f6b2dac9/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/BaseColumnExtractor.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/BaseColumnExtractor.java b/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/BaseColumnExtractor.java
new file mode 100644
index 0000000..20cc5f7
--- /dev/null
+++ b/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/BaseColumnExtractor.java
@@ -0,0 +1,44 @@
+/*****************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ ****************************************************************/
+
+package org.apache.cayenne.access.translator.select;
+
+import org.apache.cayenne.access.sqlbuilder.sqltree.Node;
+import org.apache.cayenne.map.DbAttribute;
+
+import static org.apache.cayenne.access.sqlbuilder.SQLBuilder.table;
+
+/**
+ * @since 4.2
+ */
+abstract class BaseColumnExtractor implements ColumnExtractor {
+
+ protected final TranslatorContext context;
+
+ BaseColumnExtractor(TranslatorContext context) {
+ this.context = context;
+ }
+
+ protected void addDbAttribute(String prefix, String labelPrefix, DbAttribute dba) {
+ String alias = context.getTableTree().aliasForPath(prefix);
+ String dataRowKey = labelPrefix != null ? labelPrefix + '.' + dba.getName() : dba.getName();
+ Node columnNode = table(alias).column(dba).build();
+ context.addResultNode(columnNode, dataRowKey).setDbAttribute(dba);
+ }
+}
http://git-wip-us.apache.org/repos/asf/cayenne/blob/f6b2dac9/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/BaseSQLTreeProcessor.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/BaseSQLTreeProcessor.java b/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/BaseSQLTreeProcessor.java
new file mode 100644
index 0000000..2119e26
--- /dev/null
+++ b/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/BaseSQLTreeProcessor.java
@@ -0,0 +1,128 @@
+/*****************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ ****************************************************************/
+
+package org.apache.cayenne.access.translator.select;
+
+import java.util.function.Function;
+
+import org.apache.cayenne.access.sqlbuilder.sqltree.ColumnNode;
+import org.apache.cayenne.access.sqlbuilder.sqltree.DistinctNode;
+import org.apache.cayenne.access.sqlbuilder.sqltree.FunctionNode;
+import org.apache.cayenne.access.sqlbuilder.sqltree.InNode;
+import org.apache.cayenne.access.sqlbuilder.sqltree.LikeNode;
+import org.apache.cayenne.access.sqlbuilder.sqltree.LimitOffsetNode;
+import org.apache.cayenne.access.sqlbuilder.sqltree.Node;
+import org.apache.cayenne.access.sqlbuilder.sqltree.SimpleNodeTreeVisitor;
+import org.apache.cayenne.access.sqlbuilder.sqltree.ValueNode;
+
+
+/**
+ * @since 4.2
+ */
+public class BaseSQLTreeProcessor extends SimpleNodeTreeVisitor implements Function<Node, Node> {
+
+ @Override
+ public Node apply(Node node) {
+ node.visit(this);
+ return node;
+ }
+
+ protected void onValueNode(Node parent, ValueNode child, int index) {
+ }
+
+ protected void onFunctionNode(Node parent, FunctionNode child, int index) {
+ }
+
+ protected void onLimitOffsetNode(Node parent, LimitOffsetNode child, int index) {
+ }
+
+ protected void onColumnNode(Node parent, ColumnNode child, int index) {
+ }
+
+ protected void onInNode(Node parent, InNode child, int index) {
+ }
+
+ protected void onLikeNode(Node parent, LikeNode child, int index) {
+ }
+
+ protected void onResultNode(Node parent, Node child, int index) {
+ }
+
+ protected void onDistinctNode(Node parent, DistinctNode child, int index) {
+ }
+
+ protected void onUndefinedNode(Node parent, Node child, int index) {
+ }
+
+ protected void replaceChild(Node parent, int index, Node newChild) {
+ replaceChild(parent, index, newChild, true);
+ }
+
+ protected void replaceChild(Node parent, int index, Node newChild, boolean transferChildren) {
+ if (transferChildren) {
+ Node oldChild = parent.getChild(index);
+ for (int i = 0; i < oldChild.getChildrenCount(); i++) {
+ newChild.addChild(oldChild.getChild(i));
+ }
+ }
+ parent.replaceChild(index, newChild);
+ }
+
+ @Override
+ public boolean onChildNodeStart(Node parent, Node child, int index, boolean hasMore) {
+ switch (child.getType()) {
+ case VALUE:
+ onValueNode(parent, (ValueNode) child, index);
+ break;
+
+ case FUNCTION:
+ onFunctionNode(parent, (FunctionNode) child, index);
+ break;
+
+ case LIMIT_OFFSET:
+ onLimitOffsetNode(parent, (LimitOffsetNode) child, index);
+ break;
+
+ case COLUMN:
+ onColumnNode(parent, (ColumnNode) child, index);
+ break;
+
+ case IN:
+ onInNode(parent, (InNode) child, index);
+ break;
+
+ case LIKE:
+ onLikeNode(parent, (LikeNode) child, index);
+ break;
+
+ case RESULT:
+ onResultNode(parent, child, index);
+ break;
+
+ case DISTINCT:
+ onDistinctNode(parent, (DistinctNode) child, index);
+ break;
+
+ default:
+ onUndefinedNode(parent, child, index);
+ break;
+ }
+ return true;
+ }
+}
http://git-wip-us.apache.org/repos/asf/cayenne/blob/f6b2dac9/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/ColumnDescriptorStage.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/ColumnDescriptorStage.java b/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/ColumnDescriptorStage.java
new file mode 100644
index 0000000..d15803a
--- /dev/null
+++ b/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/ColumnDescriptorStage.java
@@ -0,0 +1,56 @@
+/*****************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ ****************************************************************/
+
+package org.apache.cayenne.access.translator.select;
+
+import org.apache.cayenne.access.jdbc.ColumnDescriptor;
+import org.apache.cayenne.map.DbAttribute;
+
+/**
+ * @since 4.2
+ */
+class ColumnDescriptorStage implements TranslationStage {
+
+ @Override
+ public void perform(TranslatorContext context) {
+ int i = 0;
+ for(ResultNodeDescriptor resultNode : context.getResultNodeList()) {
+ context.getSelectBuilder().result(resultNode::getNode);
+
+ if(!resultNode.isInDataRow()) {
+ continue;
+ }
+
+ String name;
+ DbAttribute attribute = resultNode.getDbAttribute();
+ if(attribute != null) {
+ name = attribute.getName();
+ } else {
+ // generated name
+ name = "c" + i++;
+ }
+
+ ColumnDescriptor descriptor = new ColumnDescriptor(name, resultNode.getJdbcType(), resultNode.getJavaType());
+ descriptor.setAttribute(attribute);
+ descriptor.setDataRowKey(resultNode.getDataRowKey());
+
+ context.getColumnDescriptors().add(descriptor);
+ }
+ }
+}
http://git-wip-us.apache.org/repos/asf/cayenne/blob/f6b2dac9/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/ColumnExtractor.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/ColumnExtractor.java b/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/ColumnExtractor.java
new file mode 100644
index 0000000..e1551a7
--- /dev/null
+++ b/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/ColumnExtractor.java
@@ -0,0 +1,33 @@
+/*****************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ ****************************************************************/
+
+package org.apache.cayenne.access.translator.select;
+
+/**
+ * @since 4.2
+ */
+interface ColumnExtractor {
+
+ void extract(String prefix);
+
+ default void extract() {
+ extract(null);
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/cayenne/blob/f6b2dac9/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/ColumnExtractorStage.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/ColumnExtractorStage.java b/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/ColumnExtractorStage.java
new file mode 100644
index 0000000..cd51d77
--- /dev/null
+++ b/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/ColumnExtractorStage.java
@@ -0,0 +1,54 @@
+/*****************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ ****************************************************************/
+
+package org.apache.cayenne.access.translator.select;
+
+/**
+ * Get result columns based on query, options are: <ol>
+ * <li> for column queries - defined set of columns
+ * <li> for paginated or nested queries - root pk columns
+ * <li> for queries with defined class descriptor - full column set including flattened
+ * <li> for everything else - all columns of the root db entity
+ * </ol>
+ *
+ * @since 4.2
+ */
+class ColumnExtractorStage implements TranslationStage {
+
+ @Override
+ public void perform(TranslatorContext context) {
+ ColumnExtractor extractor;
+
+ if(context.getQuery().getColumns() != null && !context.getQuery().getColumns().isEmpty()) {
+ extractor = new CustomColumnSetExtractor(context, context.getQuery().getColumns());
+ } else if (context.getParentContext() != null || context.getMetadata().getPageSize() > 0) {
+ if(context.getMetadata().getObjEntity() != null) {
+ extractor = new IdColumnExtractor(context, context.getMetadata().getObjEntity());
+ } else {
+ extractor = new IdColumnExtractor(context, context.getMetadata().getDbEntity());
+ }
+ } else if (context.getMetadata().getClassDescriptor() != null) {
+ extractor = new DescriptorColumnExtractor(context, context.getMetadata().getClassDescriptor());
+ } else {
+ extractor = new DbEntityColumnExtractor(context);
+ }
+
+ extractor.extract();
+ }
+}
http://git-wip-us.apache.org/repos/asf/cayenne/blob/f6b2dac9/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/ColumnSelectWrapper.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/ColumnSelectWrapper.java b/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/ColumnSelectWrapper.java
new file mode 100644
index 0000000..21a0069
--- /dev/null
+++ b/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/ColumnSelectWrapper.java
@@ -0,0 +1,84 @@
+/*****************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ ****************************************************************/
+
+package org.apache.cayenne.access.translator.select;
+
+import java.util.Collection;
+import java.util.Objects;
+
+import org.apache.cayenne.exp.Expression;
+import org.apache.cayenne.exp.property.BaseProperty;
+import org.apache.cayenne.map.EntityResolver;
+import org.apache.cayenne.query.ColumnSelect;
+import org.apache.cayenne.query.Ordering;
+import org.apache.cayenne.query.PrefetchTreeNode;
+import org.apache.cayenne.query.QueryMetadata;
+import org.apache.cayenne.query.Select;
+
+/**
+ * @since 4.2
+ */
+public class ColumnSelectWrapper implements TranslatableQueryWrapper {
+
+ private final ColumnSelect<?> columnSelect;
+
+ public ColumnSelectWrapper(ColumnSelect<?> columnSelect) {
+ this.columnSelect = Objects.requireNonNull(columnSelect);
+ }
+
+ @Override
+ public boolean isDistinct() {
+ return columnSelect.isDistinct();
+ }
+
+ @Override
+ public QueryMetadata getMetaData(EntityResolver resolver) {
+ return columnSelect.getMetaData(resolver);
+ }
+
+ @Override
+ public PrefetchTreeNode getPrefetchTree() {
+ return columnSelect.getPrefetches();
+ }
+
+ @Override
+ public Expression getQualifier() {
+ return columnSelect.getWhere();
+ }
+
+ @Override
+ public Collection<Ordering> getOrderings() {
+ return columnSelect.getOrderings();
+ }
+
+ @Override
+ public Collection<BaseProperty<?>> getColumns() {
+ return columnSelect.getColumns();
+ }
+
+ @Override
+ public Expression getHavingQualifier() {
+ return columnSelect.getHaving();
+ }
+
+ @Override
+ public Select<?> unwrap() {
+ return columnSelect;
+ }
+}
http://git-wip-us.apache.org/repos/asf/cayenne/blob/f6b2dac9/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/CustomColumnSetExtractor.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/CustomColumnSetExtractor.java b/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/CustomColumnSetExtractor.java
new file mode 100644
index 0000000..534fd3a
--- /dev/null
+++ b/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/CustomColumnSetExtractor.java
@@ -0,0 +1,133 @@
+/*****************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ ****************************************************************/
+
+package org.apache.cayenne.access.translator.select;
+
+import java.util.Collection;
+import java.util.Map;
+
+import org.apache.cayenne.CayenneRuntimeException;
+import org.apache.cayenne.Persistent;
+import org.apache.cayenne.access.sqlbuilder.sqltree.Node;
+import org.apache.cayenne.exp.Expression;
+import org.apache.cayenne.exp.parser.ASTPath;
+import org.apache.cayenne.exp.property.BaseProperty;
+import org.apache.cayenne.map.JoinType;
+import org.apache.cayenne.map.ObjEntity;
+import org.apache.cayenne.reflect.ClassDescriptor;
+import org.apache.cayenne.util.Util;
+
+/**
+ * @since 4.2
+ */
+class CustomColumnSetExtractor implements ColumnExtractor {
+
+ private final TranslatorContext context;
+ private final Collection<BaseProperty<?>> columns;
+
+ CustomColumnSetExtractor(TranslatorContext context, Collection<BaseProperty<?>> columns) {
+ this.context = context;
+ this.columns = columns;
+ }
+
+ @Override
+ public void extract(String prefix) {
+ for (BaseProperty<?> property : columns) {
+ if (isFullObjectProp(property)) {
+ extractFullObject(prefix, property);
+ } else {
+ extractSimpleProperty(property);
+ }
+ }
+ }
+
+ private void extractSimpleProperty(BaseProperty<?> property) {
+ Node sqlNode = context.getQualifierTranslator().translate(property);
+ context.addResultNode(sqlNode, true, property, property.getAlias());
+ }
+
+ private boolean isFullObjectProp(BaseProperty<?> property) {
+ int expressionType = property.getExpression().getType();
+
+ // forbid direct selection of toMany relationships columns
+ if(property.getType() != null && (expressionType == Expression.OBJ_PATH || expressionType == Expression.DB_PATH)
+ && (Collection.class.isAssignableFrom(property.getType())
+ || Map.class.isAssignableFrom(property.getType()))) {
+ throw new CayenneRuntimeException("Can't directly select toMany relationship columns. " +
+ "Either select it with aggregate functions like count() or with flat() function to select full related objects.");
+ }
+
+ // evaluate ObjPath with Persistent type as toOne relations and use it as full object
+ return expressionType == Expression.FULL_OBJECT
+ || (property.getType() != null
+ && expressionType == Expression.OBJ_PATH
+ && Persistent.class.isAssignableFrom(property.getType()));
+ }
+
+ private void extractFullObject(String prefix, BaseProperty<?> property) {
+ prefix = calculatePrefix(prefix, property);
+ ensureJoin(prefix);
+
+ ObjEntity entity = context.getResolver().getObjEntity(property.getType());
+
+ ColumnExtractor extractor;
+ if(context.getMetadata().getPageSize() > 0) {
+ extractor = new IdColumnExtractor(context, entity);
+ } else {
+ ClassDescriptor descriptor = context.getResolver().getClassDescriptor(entity.getName());
+ extractor = new DescriptorColumnExtractor(context, descriptor);
+ }
+
+ int index = context.getResultNodeList().size();
+
+ // extract required columns of entity
+ extractor.extract(prefix);
+
+ // Reset data row key as ObjectResolver expects it to match attribute name.
+ // Maybe we should change resolver, as it seems cleaner to have path from root as prefix in data row key.
+ for(int i=index; i<context.getResultNodeList().size(); i++) {
+ context.getResultNodeList().get(i).setDataRowKey(null);
+ }
+ }
+
+ private String calculatePrefix(String prefix, BaseProperty<?> property) {
+ Expression propertyExpression = property.getExpression();
+ int expressionType = property.getExpression().getType();
+
+ if(expressionType == Expression.FULL_OBJECT && propertyExpression.getOperandCount() > 0) {
+ Object op = propertyExpression.getOperand(0);
+ if(op instanceof ASTPath) {
+ prefix = ((ASTPath) op).getPath();
+ }
+ } else if(propertyExpression instanceof ASTPath) {
+ prefix = ((ASTPath) propertyExpression).getPath();
+ }
+
+ return prefix;
+ }
+
+ private void ensureJoin(String prefix) {
+ // ensure all joins for given property
+ if(!Util.isEmptyString(prefix)) {
+ PathTranslationResult result = context.getPathTranslator().translatePath(context.getMetadata().getObjEntity(), prefix);
+ result.getDbRelationship().ifPresent(relationship
+ -> context.getTableTree().addJoinTable(result.getFinalPath(), relationship, JoinType.LEFT_OUTER));
+ }
+ }
+}
http://git-wip-us.apache.org/repos/asf/cayenne/blob/f6b2dac9/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/DataObjectMatchTranslator.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/DataObjectMatchTranslator.java b/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/DataObjectMatchTranslator.java
deleted file mode 100644
index 07f53f1..0000000
--- a/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/DataObjectMatchTranslator.java
+++ /dev/null
@@ -1,152 +0,0 @@
-/*****************************************************************
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- ****************************************************************/
-
-package org.apache.cayenne.access.translator.select;
-
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.Iterator;
-import java.util.Map;
-
-import org.apache.cayenne.CayenneRuntimeException;
-import org.apache.cayenne.ObjectId;
-import org.apache.cayenne.Persistent;
-import org.apache.cayenne.exp.Expression;
-import org.apache.cayenne.map.DbAttribute;
-import org.apache.cayenne.map.DbEntity;
-import org.apache.cayenne.map.DbJoin;
-import org.apache.cayenne.map.DbRelationship;
-
-/**
- */
-public class DataObjectMatchTranslator {
-
- protected Map<String, DbAttribute> attributes;
- protected Map<String, Object> values;
- protected String operation;
- protected Expression expression;
- protected DbRelationship relationship;
- protected String joinSplitAlias;
-
- public Expression getExpression() {
- return expression;
- }
-
- public void setExpression(Expression expression) {
- this.expression = expression;
- }
-
- public void reset() {
- attributes = null;
- values = null;
- operation = null;
- expression = null;
- relationship = null;
- }
-
- /**
- * Initializes itself to do translation of the match ending with a
- * DbRelationship.
- *
- * @since 3.0
- */
- public void setRelationship(DbRelationship rel, String joinSplitAlias) {
- this.relationship = rel;
- this.joinSplitAlias = joinSplitAlias;
- attributes = new HashMap<>(rel.getJoins().size() * 2);
-
- if (rel.isToMany() || !rel.isToPK()) {
-
- // match on target PK
- DbEntity ent = rel.getTargetEntity();
-
- // index by name
- for (DbAttribute pkAttr : ent.getPrimaryKeys()) {
- attributes.put(pkAttr.getName(), pkAttr);
- }
- } else {
-
- // match on this FK
- for (DbJoin join : rel.getJoins()) {
- // index by target name
- attributes.put(join.getTargetName(), join.getSource());
- }
- }
- }
-
- public void setDataObject(Persistent obj) {
- if (obj == null) {
- values = Collections.emptyMap();
- return;
- }
-
- setObjectId(obj.getObjectId());
- }
-
- /**
- * @since 1.2
- */
- public void setObjectId(ObjectId id) {
- if (id == null) {
- throw new CayenneRuntimeException(
- "Null ObjectId, probably an attempt to use TRANSIENT object as a query parameter.");
- } else if (id.isTemporary()) {
- throw new CayenneRuntimeException(
- "Temporary id, probably an attempt to use NEW object as a query parameter.");
- } else {
- values = id.getIdSnapshot();
- }
- }
-
- public Iterator<String> keys() {
- if (attributes == null) {
- throw new IllegalStateException("An attempt to use uninitialized DataObjectMatchTranslator: "
- + "[attributes: null, values: " + values + "]");
- }
-
- return attributes.keySet().iterator();
- }
-
- /**
- * @since 3.0
- */
- public String getJoinSplitAlias() {
- return joinSplitAlias;
- }
-
- public DbRelationship getRelationship() {
- return relationship;
- }
-
- public DbAttribute getAttribute(String key) {
- return attributes.get(key);
- }
-
- public Object getValue(String key) {
- return values.get(key);
- }
-
- public void setOperation(String operation) {
- this.operation = operation;
- }
-
- public String getOperation() {
- return operation;
- }
-}
http://git-wip-us.apache.org/repos/asf/cayenne/blob/f6b2dac9/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/DbEntityColumnExtractor.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/DbEntityColumnExtractor.java b/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/DbEntityColumnExtractor.java
new file mode 100644
index 0000000..3354fac
--- /dev/null
+++ b/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/DbEntityColumnExtractor.java
@@ -0,0 +1,45 @@
+/*****************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ ****************************************************************/
+
+package org.apache.cayenne.access.translator.select;
+
+import java.util.Objects;
+
+import org.apache.cayenne.map.DbAttribute;
+import org.apache.cayenne.map.DbEntity;
+
+/**
+ * @since 4.2
+ */
+class DbEntityColumnExtractor extends BaseColumnExtractor {
+
+ private final DbEntity dbEntity;
+
+ DbEntityColumnExtractor(TranslatorContext context) {
+ super(context);
+ this.dbEntity = Objects.requireNonNull(context.getMetadata().getDbEntity(), "No root entity");
+ }
+
+ @Override
+ public void extract(String prefix) {
+ for(DbAttribute attribute : dbEntity.getAttributes()) {
+ addDbAttribute(prefix, prefix, attribute);
+ }
+ }
+}
http://git-wip-us.apache.org/repos/asf/cayenne/blob/f6b2dac9/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/DbPathProcessor.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/DbPathProcessor.java b/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/DbPathProcessor.java
new file mode 100644
index 0000000..cccea12
--- /dev/null
+++ b/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/DbPathProcessor.java
@@ -0,0 +1,108 @@
+/*****************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ ****************************************************************/
+
+package org.apache.cayenne.access.translator.select;
+
+import org.apache.cayenne.map.DbAttribute;
+import org.apache.cayenne.map.DbEntity;
+import org.apache.cayenne.map.DbJoin;
+import org.apache.cayenne.map.DbRelationship;
+import org.apache.cayenne.map.JoinType;
+
+/**
+ * @since 4.2
+ */
+class DbPathProcessor extends PathProcessor<DbEntity> {
+
+ DbPathProcessor(TranslatorContext context, DbEntity entity, String parentPath) {
+ super(context, entity);
+ if(parentPath != null) {
+ currentDbPath.append(parentPath);
+ }
+ }
+
+ @Override
+ protected void processNormalAttribute(String next) {
+ DbAttribute dbAttribute = entity.getAttribute(next);
+ if(dbAttribute != null) {
+ processAttribute(dbAttribute);
+ return;
+ }
+
+ DbRelationship relationship = entity.getRelationship(next);
+ if(relationship != null) {
+ entity = relationship.getTargetEntity();
+ processRelationship(relationship);
+ return;
+ }
+
+ throw new IllegalStateException("Unable to resolve path: " + currentDbPath.toString() + "." + next);
+ }
+
+ @Override
+ protected void processAliasedAttribute(String next, String alias) {
+ DbRelationship relationship = entity.getRelationship(alias);
+ if(relationship == null) {
+ throw new IllegalStateException("Non-relationship aliased path part: " + alias);
+ }
+
+ processRelationship(relationship);
+ }
+
+ private void processAttribute(DbAttribute attribute) {
+ addAttribute(currentDbPath.toString(), attribute);
+ appendCurrentPath(attribute.getName());
+ }
+
+ private void processRelationship(DbRelationship relationship) {
+ if (lastComponent) {
+ // if this is a last relationship in the path, it needs special handling
+ processRelTermination(relationship);
+ } else {
+ appendCurrentPath(relationship.getName());
+ context.getTableTree().addJoinTable(currentDbPath.toString(), relationship, JoinType.LEFT_OUTER);
+ if(!relationship.isToMany()) {
+ String path = currentDbPath.toString();
+ for (DbAttribute attribute : relationship.getTargetEntity().getPrimaryKeys()) {
+ addAttribute(path, attribute);
+ }
+ }
+ }
+ }
+
+ protected void processRelTermination(DbRelationship rel) {
+ this.relationship = rel;
+ String path = currentDbPath.toString();
+ appendCurrentPath(rel.getName());
+
+ if (rel.isToMany() || !rel.isToPK()) {
+ // match on target PK
+ context.getTableTree().addJoinTable(currentDbPath.toString(), rel, JoinType.LEFT_OUTER);
+ path = currentDbPath.toString();
+ for(DbAttribute attribute : rel.getTargetEntity().getPrimaryKeys()) {
+ addAttribute(path, attribute);
+ }
+ } else {
+ for(DbJoin join : rel.getJoins()) {
+ addAttribute(path, join.getSource());
+ }
+ }
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/cayenne/blob/f6b2dac9/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/DefaultQuotingAppendable.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/DefaultQuotingAppendable.java b/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/DefaultQuotingAppendable.java
new file mode 100644
index 0000000..712cc5f
--- /dev/null
+++ b/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/DefaultQuotingAppendable.java
@@ -0,0 +1,47 @@
+/*****************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ ****************************************************************/
+
+package org.apache.cayenne.access.translator.select;
+
+import org.apache.cayenne.access.sqlbuilder.QuotingAppendable;
+import org.apache.cayenne.access.sqlbuilder.StringBuilderAppendable;
+
+/**
+ * @since 4.2
+ */
+public class DefaultQuotingAppendable extends StringBuilderAppendable {
+
+ private final TranslatorContext context;
+
+ public DefaultQuotingAppendable(TranslatorContext context) {
+ super();
+ this.context = context;
+ }
+
+ @Override
+ public QuotingAppendable appendQuoted(CharSequence content) {
+ context.getQuotingStrategy().quotedIdentifier(context.getRootDbEntity(), content, builder);
+ return this;
+ }
+
+ @Override
+ public TranslatorContext getContext() {
+ return context;
+ }
+}
[02/11] cayenne git commit: CAY-2465 New SelectTranslator
implementation
Posted by nt...@apache.org.
http://git-wip-us.apache.org/repos/asf/cayenne/blob/f6b2dac9/cayenne-server/src/test/java/org/apache/cayenne/access/translator/select/DefaultSelectTranslatorIT.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/test/java/org/apache/cayenne/access/translator/select/DefaultSelectTranslatorIT.java b/cayenne-server/src/test/java/org/apache/cayenne/access/translator/select/DefaultSelectTranslatorIT.java
index bb2248f..5e0db67 100644
--- a/cayenne-server/src/test/java/org/apache/cayenne/access/translator/select/DefaultSelectTranslatorIT.java
+++ b/cayenne-server/src/test/java/org/apache/cayenne/access/translator/select/DefaultSelectTranslatorIT.java
@@ -42,6 +42,9 @@ import org.apache.cayenne.unit.di.server.ServerCase;
import org.apache.cayenne.unit.di.server.UseServerRuntime;
import org.junit.Test;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Comparator;
import java.util.Date;
import java.util.List;
@@ -71,10 +74,10 @@ public class DefaultSelectTranslatorIT extends ServerCase {
@Test
public void testCreateSqlString1() throws Exception {
// query with qualifier and ordering
- SelectQuery<Artist> q = new SelectQuery<Artist>(Artist.class, ExpressionFactory.likeExp("artistName", "a%"));
+ SelectQuery<Artist> q = new SelectQuery<>(Artist.class, ExpressionFactory.likeExp("artistName", "a%"));
q.addOrdering("dateOfBirth", SortOrder.ASCENDING);
- DefaultSelectTranslator defaultSelectTranslator = new DefaultSelectTranslator(q, dataNode.getAdapter(), dataNode.getEntityResolver());
+ SelectTranslator defaultSelectTranslator = new DefaultSelectTranslator(q, dataNode.getAdapter(), dataNode.getEntityResolver());
String generatedSql = defaultSelectTranslator.getSql();
// do some simple assertions to make sure all parts are in
@@ -96,7 +99,7 @@ public class DefaultSelectTranslatorIT extends ServerCase {
final DbEntity entity = context.getEntityResolver().getDbEntity("ARTIST");
final DbEntity middleEntity = context.getEntityResolver().getDbEntity("ARTIST_GROUP");
- DefaultSelectTranslator transl = new DefaultSelectTranslator(q, dataNode.getAdapter(),
+ SelectTranslator transl = new DefaultSelectTranslator(q, dataNode.getAdapter(),
dataNode.getEntityResolver());
entity.setQualifier(ExpressionFactory.exp("ARTIST_NAME = \"123\""));
@@ -111,11 +114,11 @@ public class DefaultSelectTranslatorIT extends ServerCase {
assertTrue(generatedSql.startsWith("SELECT "));
assertTrue(generatedSql.indexOf(" FROM ") > 0);
if (generatedSql.contains("RTRIM")) {
- assertTrue(generatedSql.indexOf("ARTIST_NAME) = ") > generatedSql.indexOf("RTRIM("));
+ assertTrue(generatedSql.indexOf("ARTIST_NAME) =") > generatedSql.indexOf("RTRIM("));
} else if (generatedSql.contains("TRIM")) {
- assertTrue(generatedSql.indexOf("ARTIST_NAME) = ") > generatedSql.indexOf("TRIM("));
+ assertTrue(generatedSql.indexOf("ARTIST_NAME) =") > generatedSql.indexOf("TRIM("));
} else {
- assertTrue(generatedSql.indexOf("ARTIST_NAME = ") > 0);
+ assertTrue(generatedSql.indexOf("ARTIST_NAME =") > 0);
}
} finally {
entity.setQualifier(null);
@@ -132,7 +135,7 @@ public class DefaultSelectTranslatorIT extends ServerCase {
final DbEntity entity = context.getEntityResolver().getDbEntity("ARTIST");
final DbEntity middleEntity = context.getEntityResolver().getDbEntity("ARTIST_GROUP");
- DefaultSelectTranslator transl = new DefaultSelectTranslator(q, dataNode.getAdapter(),
+ SelectTranslator transl = new DefaultSelectTranslator(q, dataNode.getAdapter(),
dataNode.getEntityResolver());
entity.setQualifier(ExpressionFactory.exp("ARTIST_NAME = \"123\""));
@@ -147,11 +150,11 @@ public class DefaultSelectTranslatorIT extends ServerCase {
assertTrue(generatedSql.startsWith("SELECT "));
assertTrue(generatedSql.indexOf(" FROM ") > 0);
if (generatedSql.contains("RTRIM")) {
- assertTrue(generatedSql.indexOf("ARTIST_NAME) = ") > generatedSql.indexOf("RTRIM("));
+ assertTrue(generatedSql.indexOf("ARTIST_NAME) =") > generatedSql.indexOf("RTRIM("));
} else if (generatedSql.contains("TRIM")) {
- assertTrue(generatedSql.indexOf("ARTIST_NAME) = ") > generatedSql.indexOf("TRIM("));
+ assertTrue(generatedSql.indexOf("ARTIST_NAME) =") > generatedSql.indexOf("TRIM("));
} else {
- assertTrue(generatedSql.indexOf("ARTIST_NAME = ") > 0);
+ assertTrue(generatedSql.indexOf("ARTIST_NAME =") > 0);
}
} finally {
@@ -169,7 +172,7 @@ public class DefaultSelectTranslatorIT extends ServerCase {
final DbEntity entity = context.getEntityResolver().getDbEntity("ARTIST");
final DbEntity middleEntity = context.getEntityResolver().getDbEntity("ARTIST_GROUP");
- DefaultSelectTranslator transl = new DefaultSelectTranslator(q, dataNode.getAdapter(),
+ SelectTranslator transl = new DefaultSelectTranslator(q, dataNode.getAdapter(),
dataNode.getEntityResolver());
entity.setQualifier(ExpressionFactory.exp("ARTIST_NAME = \"123\""));
@@ -184,11 +187,11 @@ public class DefaultSelectTranslatorIT extends ServerCase {
assertTrue(generatedSql.startsWith("SELECT "));
assertTrue(generatedSql.indexOf(" FROM ") > 0);
if (generatedSql.contains("RTRIM")) {
- assertTrue(generatedSql.indexOf("ARTIST_NAME) = ") > generatedSql.indexOf("RTRIM("));
+ assertTrue(generatedSql.indexOf("ARTIST_NAME) =") > generatedSql.indexOf("RTRIM("));
} else if (generatedSql.contains("TRIM")) {
- assertTrue(generatedSql.indexOf("ARTIST_NAME) = ") > generatedSql.indexOf("TRIM("));
+ assertTrue(generatedSql.indexOf("ARTIST_NAME) =") > generatedSql.indexOf("TRIM("));
} else {
- assertTrue(generatedSql.indexOf("ARTIST_NAME = ") > 0);
+ assertTrue(generatedSql.indexOf("ARTIST_NAME =") > 0);
}
} finally {
@@ -200,13 +203,13 @@ public class DefaultSelectTranslatorIT extends ServerCase {
@Test
public void testDbEntityQualifier_RelatedMatch() throws Exception {
- SelectQuery<Artist> q = new SelectQuery(Painting.class,
+ SelectQuery<Painting> q = new SelectQuery<>(Painting.class,
ExpressionFactory.matchExp("toArtist.artistName", "foo"));
final DbEntity entity = context.getEntityResolver().getDbEntity("ARTIST");
final DbEntity middleEntity = context.getEntityResolver().getDbEntity("ARTIST_GROUP");
- DefaultSelectTranslator transl = new DefaultSelectTranslator(q, dataNode.getAdapter(),
+ SelectTranslator transl = new DefaultSelectTranslator(q, dataNode.getAdapter(),
dataNode.getEntityResolver());
entity.setQualifier(ExpressionFactory.exp("ARTIST_NAME = \"123\""));
@@ -221,11 +224,11 @@ public class DefaultSelectTranslatorIT extends ServerCase {
assertTrue(generatedSql.startsWith("SELECT "));
assertTrue(generatedSql.indexOf(" FROM ") > 0);
if (generatedSql.contains("RTRIM")) {
- assertTrue(generatedSql.indexOf("ARTIST_NAME) = ") > generatedSql.indexOf("RTRIM("));
+ assertTrue(generatedSql.indexOf("ARTIST_NAME) =") > generatedSql.indexOf("RTRIM("));
} else if (generatedSql.contains("TRIM")) {
- assertTrue(generatedSql.indexOf("ARTIST_NAME) = ") > generatedSql.indexOf("TRIM("));
+ assertTrue(generatedSql.indexOf("ARTIST_NAME) =") > generatedSql.indexOf("TRIM("));
} else {
- assertTrue(generatedSql.indexOf("ARTIST_NAME = ") > 0);
+ assertTrue(generatedSql.indexOf("ARTIST_NAME =") > 0);
}
} finally {
entity.setQualifier(null);
@@ -239,7 +242,7 @@ public class DefaultSelectTranslatorIT extends ServerCase {
@Test
public void testCreateSqlString2() throws Exception {
// query with "distinct" set
- SelectQuery q = new SelectQuery(Artist.class);
+ SelectQuery<Artist> q = new SelectQuery<>(Artist.class);
q.setDistinct(true);
String generatedSql = new DefaultSelectTranslator(q, dataNode.getAdapter(), dataNode.getEntityResolver())
@@ -258,7 +261,7 @@ public class DefaultSelectTranslatorIT extends ServerCase {
@Test
public void testCreateSqlString5() throws Exception {
// query with qualifier and ordering
- SelectQuery q = new SelectQuery(ArtistExhibit.class);
+ SelectQuery<ArtistExhibit> q = new SelectQuery<>(ArtistExhibit.class);
q.setQualifier(ExpressionFactory.likeExp("toArtist.artistName", "a%"));
q.andQualifier(ExpressionFactory.likeExp("toExhibit.toGallery.paintingArray.toArtist.artistName", "a%"));
@@ -290,7 +293,7 @@ public class DefaultSelectTranslatorIT extends ServerCase {
@Test
public void testCreateSqlString6() throws Exception {
// query with qualifier and ordering
- SelectQuery q = new SelectQuery(ArtistExhibit.class);
+ SelectQuery<ArtistExhibit> q = new SelectQuery<>(ArtistExhibit.class);
q.setQualifier(ExpressionFactory.likeExp("toArtist.artistName", "a%"));
q.andQualifier(ExpressionFactory.likeExp("toArtist.paintingArray.paintingTitle", "p%"));
@@ -318,7 +321,7 @@ public class DefaultSelectTranslatorIT extends ServerCase {
*/
@Test
public void testCreateSqlString7() throws Exception {
- SelectQuery q = new SelectQuery(Artist.class);
+ SelectQuery<Artist> q = new SelectQuery<>(Artist.class);
q.setQualifier(ExpressionFactory.greaterExp("dateOfBirth", new Date()));
q.andQualifier(ExpressionFactory.lessExp("dateOfBirth", new Date()));
@@ -351,7 +354,7 @@ public class DefaultSelectTranslatorIT extends ServerCase {
*/
@Test
public void testCreateSqlString8() throws Exception {
- SelectQuery q = new SelectQuery();
+ SelectQuery<?> q = new SelectQuery<>();
q.setRoot(Painting.class);
q.setQualifier(ExpressionFactory.greaterExp("toArtist.dateOfBirth", new Date()));
q.andQualifier(ExpressionFactory.lessExp("toArtist.dateOfBirth", new Date()));
@@ -379,7 +382,7 @@ public class DefaultSelectTranslatorIT extends ServerCase {
@Test
public void testCreateSqlString9() throws Exception {
// query for a compound ObjEntity with qualifier
- SelectQuery q = new SelectQuery(CompoundPainting.class, ExpressionFactory.likeExp("artistName", "a%"));
+ SelectQuery<CompoundPainting> q = new SelectQuery<>(CompoundPainting.class, ExpressionFactory.likeExp("artistName", "a%"));
String sql = new DefaultSelectTranslator(q, dataNode.getAdapter(), dataNode.getEntityResolver()).getSql();
@@ -431,10 +434,10 @@ public class DefaultSelectTranslatorIT extends ServerCase {
@Test
public void testCreateSqlString10() throws Exception {
// query with to-many joint prefetches
- SelectQuery q = new SelectQuery(Artist.class);
+ SelectQuery<Artist> q = new SelectQuery<>(Artist.class);
q.addPrefetch(Artist.PAINTING_ARRAY.joint());
- DefaultSelectTranslator transl = new DefaultSelectTranslator(q, dataNode.getAdapter(),
+ SelectTranslator transl = new DefaultSelectTranslator(q, dataNode.getAdapter(),
dataNode.getEntityResolver());
String sql = transl.getSql();
assertNotNull(sql);
@@ -449,32 +452,31 @@ public class DefaultSelectTranslatorIT extends ServerCase {
assertTrue(sql, sql.indexOf("PAINTING_ID") > 0);
// assert we have one join
- assertEquals(1, transl.joinStack.size());
+ assertTrue(transl.hasJoins());
}
@Test
public void testCreateSqlString11() throws Exception {
// query with joint prefetches and other joins
- SelectQuery q = new SelectQuery(Artist.class, ExpressionFactory.exp("paintingArray.paintingTitle = 'a'"));
+ SelectQuery<Artist> q = new SelectQuery<>(Artist.class, ExpressionFactory.exp("paintingArray.paintingTitle = 'a'"));
q.addPrefetch(Artist.PAINTING_ARRAY.joint());
- DefaultSelectTranslator transl = new DefaultSelectTranslator(q, dataNode.getAdapter(),
+ SelectTranslator transl = new DefaultSelectTranslator(q, dataNode.getAdapter(),
dataNode.getEntityResolver());
transl.getSql();
// assert we only have one join
- assertEquals(2, transl.joinStack.size());
assertTrue(transl.hasJoins());
}
@Test
public void testCreateSqlString12() throws Exception {
// query with to-one joint prefetches
- SelectQuery q = new SelectQuery(Painting.class);
+ SelectQuery<Painting> q = new SelectQuery<>(Painting.class);
q.addPrefetch(Painting.TO_ARTIST.joint());
- DefaultSelectTranslator transl = new DefaultSelectTranslator(q, dataNode.getAdapter(),
+ SelectTranslator transl = new DefaultSelectTranslator(q, dataNode.getAdapter(),
dataNode.getEntityResolver());
String sql = transl.getSql();
@@ -490,14 +492,13 @@ public class DefaultSelectTranslatorIT extends ServerCase {
assertTrue(sql, sql.indexOf("PAINTING_ID") > 0);
// assert we have one join
- assertEquals(1, transl.joinStack.size());
assertTrue(transl.hasJoins());
}
@Test
public void testCreateSqlString13() throws Exception {
// query with invalid joint prefetches
- SelectQuery q = new SelectQuery(Painting.class);
+ SelectQuery<Painting> q = new SelectQuery<>(Painting.class);
q.addPrefetch("invalid.invalid").setSemantics(PrefetchTreeNode.JOINT_PREFETCH_SEMANTICS);
try {
@@ -512,7 +513,7 @@ public class DefaultSelectTranslatorIT extends ServerCase {
public void testCreateSqlStringWithQuoteSqlIdentifiers() throws Exception {
try {
- SelectQuery q = new SelectQuery(Artist.class);
+ SelectQuery<Artist> q = new SelectQuery<>(Artist.class);
DbEntity entity = context.getEntityResolver().getDbEntity("ARTIST");
entity.getDataMap().setQuotingSQLIdentifiers(true);
q.addOrdering("dateOfBirth", SortOrder.ASCENDING);
@@ -549,7 +550,7 @@ public class DefaultSelectTranslatorIT extends ServerCase {
public void testCreateSqlStringWithQuoteSqlIdentifiers2() throws Exception {
try {
- SelectQuery q = new SelectQuery(Artist.class);
+ SelectQuery<Artist> q = new SelectQuery<>(Artist.class);
DbEntity entity = context.getEntityResolver().getDbEntity("ARTIST");
entity.getDataMap().setQuotingSQLIdentifiers(true);
q.setQualifier(ExpressionFactory.greaterExp("dateOfBirth", new Date()));
@@ -574,14 +575,12 @@ public class DefaultSelectTranslatorIT extends ServerCase {
int iWhere = s.indexOf(" WHERE ");
assertTrue(iWhere > iArtist);
- int dateOfBirth2 = s.indexOf(charStart + "t0" + charEnd + "." + charStart + "DATE_OF_BIRTH" + charEnd
- + " > ?");
+ int dateOfBirth2 = s.indexOf(charStart + "t0" + charEnd + "." + charStart + "DATE_OF_BIRTH" + charEnd + " > ?");
assertTrue(dateOfBirth2 > iWhere);
int iAnd = s.indexOf(" AND ");
assertTrue(iAnd > iWhere);
- int dateOfBirth3 = s.indexOf(charStart + "t0" + charEnd + "." + charStart + "DATE_OF_BIRTH" + charEnd
- + " < ?");
+ int dateOfBirth3 = s.indexOf(charStart + "t0" + charEnd + "." + charStart + "DATE_OF_BIRTH" + charEnd + " < ?");
assertTrue(dateOfBirth3 > iAnd);
} finally {
@@ -596,7 +595,7 @@ public class DefaultSelectTranslatorIT extends ServerCase {
// query with joint prefetches and other joins
// and with QuoteSqlIdentifiers = true
try {
- SelectQuery q = new SelectQuery(Artist.class, ExpressionFactory.exp("paintingArray.paintingTitle = 'a'"));
+ SelectQuery<Artist> q = new SelectQuery<>(Artist.class, ExpressionFactory.exp("paintingArray.paintingTitle = 'a'"));
q.addPrefetch(Artist.PAINTING_ARRAY.joint());
DbEntity entity = context.getEntityResolver().getDbEntity("ARTIST");
@@ -607,61 +606,60 @@ public class DefaultSelectTranslatorIT extends ServerCase {
String s = new DefaultSelectTranslator(q, dataNode.getAdapter(), dataNode.getEntityResolver()).getSql();
- assertTrue(s.startsWith("SELECT DISTINCT "));
+ assertTrue(s, s.startsWith("SELECT DISTINCT "));
int iFrom = s.indexOf(" FROM ");
- assertTrue(iFrom > 0);
+ assertTrue(s, iFrom > 0);
int artistName = s.indexOf(charStart + "t0" + charEnd + "." + charStart + "ARTIST_NAME" + charEnd);
- assertTrue(artistName > 0 && artistName < iFrom);
+ assertTrue(s, artistName > 0 && artistName < iFrom);
int artistId = s.indexOf(charStart + "t0" + charEnd + "." + charStart + "ARTIST_ID" + charEnd);
- assertTrue(artistId > 0 && artistId < iFrom);
+ assertTrue(s, artistId > 0 && artistId < iFrom);
int dateOfBirth = s.indexOf(charStart + "t0" + charEnd + "." + charStart + "DATE_OF_BIRTH" + charEnd);
- assertTrue(dateOfBirth > 0 && dateOfBirth < iFrom);
+ assertTrue(s, dateOfBirth > 0 && dateOfBirth < iFrom);
int estimatedPrice = s.indexOf(charStart + "t1" + charEnd + "." + charStart + "ESTIMATED_PRICE" + charEnd);
- assertTrue(estimatedPrice > 0 && estimatedPrice < iFrom);
+ assertTrue(s, estimatedPrice > 0 && estimatedPrice < iFrom);
int paintingDescription = s.indexOf(charStart + "t1" + charEnd + "." + charStart + "PAINTING_DESCRIPTION"
+ charEnd);
- assertTrue(paintingDescription > 0 && paintingDescription < iFrom);
+ assertTrue(s, paintingDescription > 0 && paintingDescription < iFrom);
int paintingTitle = s.indexOf(charStart + "t1" + charEnd + "." + charStart + "PAINTING_TITLE" + charEnd);
- assertTrue(paintingTitle > 0 && paintingTitle < iFrom);
+ assertTrue(s, paintingTitle > 0 && paintingTitle < iFrom);
int artistIdT1 = s.indexOf(charStart + "t1" + charEnd + "." + charStart + "ARTIST_ID" + charEnd);
- assertTrue(artistIdT1 > 0 && artistIdT1 < iFrom);
+ assertTrue(s, artistIdT1 > 0 && artistIdT1 < iFrom);
int galleryId = s.indexOf(charStart + "t1" + charEnd + "." + charStart + "GALLERY_ID" + charEnd);
- assertTrue(galleryId > 0 && galleryId < iFrom);
+ assertTrue(s, galleryId > 0 && galleryId < iFrom);
int paintingId = s.indexOf(charStart + "t1" + charEnd + "." + charStart + "PAINTING_ID" + charEnd);
- assertTrue(paintingId > 0 && paintingId < iFrom);
+ assertTrue(s, paintingId > 0 && paintingId < iFrom);
int iArtist = s.indexOf(charStart + "ARTIST" + charEnd + " " + charStart + "t0" + charEnd);
- assertTrue(iArtist > iFrom);
+ assertTrue(s, iArtist > iFrom);
int iLeftJoin = s.indexOf("LEFT JOIN");
- assertTrue(iLeftJoin > iFrom);
+ assertTrue(s, iLeftJoin > iFrom);
int iPainting = s.indexOf(charStart + "PAINTING" + charEnd + " " + charStart + "t1" + charEnd);
- assertTrue(iPainting > iLeftJoin);
+ assertTrue(s, iPainting > iLeftJoin);
int iOn = s.indexOf(" ON ");
- assertTrue(iOn > iLeftJoin);
+ assertTrue(s, iOn > iLeftJoin);
int iArtistId = s.indexOf(charStart + "t0" + charEnd + "." + charStart + "ARTIST_ID" + charEnd, iLeftJoin);
- assertTrue(iArtistId > iOn);
+ assertTrue(s, iArtistId > iOn);
int iArtistIdT1 = s
.indexOf(charStart + "t1" + charEnd + "." + charStart + "ARTIST_ID" + charEnd, iLeftJoin);
- assertTrue(iArtistIdT1 > iOn);
+ assertTrue(s, iArtistIdT1 > iOn);
int i = s.indexOf("=", iLeftJoin);
- assertTrue(iArtistIdT1 > i || iArtistId > i);
+ assertTrue(s, iArtistIdT1 > i || iArtistId > i);
int iJoin = s.indexOf("JOIN");
- assertTrue(iJoin > iLeftJoin);
+ assertTrue(s, iJoin > iLeftJoin);
int iPainting2 = s.indexOf(charStart + "PAINTING" + charEnd + " " + charStart + "t2" + charEnd);
- assertTrue(iPainting2 > iJoin);
+ assertTrue(s, iPainting2 > iJoin);
int iOn2 = s.indexOf(" ON ");
- assertTrue(iOn2 > iJoin);
+ assertTrue(s, iOn2 > iJoin);
int iArtistId2 = s.indexOf(charStart + "t0" + charEnd + "." + charStart + "ARTIST_ID" + charEnd, iJoin);
- assertTrue(iArtistId2 > iOn2);
+ assertTrue(s, iArtistId2 > iOn2);
int iArtistId2T2 = s.indexOf(charStart + "t2" + charEnd + "." + charStart + "ARTIST_ID" + charEnd, iJoin);
- assertTrue(iArtistId2T2 > iOn2);
+ assertTrue(s, iArtistId2T2 > iOn2);
int i2 = s.indexOf("=", iJoin);
- assertTrue(iArtistId2T2 > i2 || iArtistId2 > i2);
+ assertTrue(s, iArtistId2T2 > i2 || iArtistId2 > i2);
int iWhere = s.indexOf(" WHERE ");
- assertTrue(iWhere > iJoin);
+ assertTrue(s, iWhere > iJoin);
- int paintingTitle2 = s.indexOf(charStart + "t2" + charEnd + "." + charStart + "PAINTING_TITLE" + charEnd
- + " = ?");
- assertTrue(paintingTitle2 > iWhere);
+ int paintingTitle2 = s.indexOf(charStart + "t2" + charEnd + "." + charStart + "PAINTING_TITLE" + charEnd + " = ?");
+ assertTrue(s, paintingTitle2 > iWhere);
} finally {
DbEntity entity = context.getEntityResolver().getDbEntity("ARTIST");
@@ -675,7 +673,7 @@ public class DefaultSelectTranslatorIT extends ServerCase {
// query with to-one joint prefetches
// and with QuoteSqlIdentifiers = true
try {
- SelectQuery q = new SelectQuery(Painting.class);
+ SelectQuery<Painting> q = new SelectQuery<>(Painting.class);
q.addPrefetch(Painting.TO_ARTIST.joint());
DbEntity entity = context.getEntityResolver().getDbEntity("PAINTING");
@@ -686,45 +684,45 @@ public class DefaultSelectTranslatorIT extends ServerCase {
String s = new DefaultSelectTranslator(q, dataNode.getAdapter(), dataNode.getEntityResolver()).getSql();
- assertTrue(s.startsWith("SELECT "));
+ assertTrue(s, s.startsWith("SELECT "));
int iFrom = s.indexOf(" FROM ");
- assertTrue(iFrom > 0);
+ assertTrue(s, iFrom > 0);
int paintingDescription = s.indexOf(charStart + "t0" + charEnd + "." + charStart + "PAINTING_DESCRIPTION"
+ charEnd);
- assertTrue(paintingDescription > 0 && paintingDescription < iFrom);
+ assertTrue(s, paintingDescription > 0 && paintingDescription < iFrom);
int paintingTitle = s.indexOf(charStart + "t0" + charEnd + "." + charStart + "PAINTING_TITLE" + charEnd);
- assertTrue(paintingTitle > 0 && paintingTitle < iFrom);
+ assertTrue(s, paintingTitle > 0 && paintingTitle < iFrom);
int artistIdT1 = s.indexOf(charStart + "t0" + charEnd + "." + charStart + "ARTIST_ID" + charEnd);
- assertTrue(artistIdT1 > 0 && artistIdT1 < iFrom);
+ assertTrue(s, artistIdT1 > 0 && artistIdT1 < iFrom);
int estimatedPrice = s.indexOf(charStart + "t0" + charEnd + "." + charStart + "ESTIMATED_PRICE" + charEnd);
- assertTrue(estimatedPrice > 0 && estimatedPrice < iFrom);
+ assertTrue(s, estimatedPrice > 0 && estimatedPrice < iFrom);
int galleryId = s.indexOf(charStart + "t0" + charEnd + "." + charStart + "GALLERY_ID" + charEnd);
- assertTrue(galleryId > 0 && galleryId < iFrom);
+ assertTrue(s, galleryId > 0 && galleryId < iFrom);
int paintingId = s.indexOf(charStart + "t0" + charEnd + "." + charStart + "PAINTING_ID" + charEnd);
- assertTrue(paintingId > 0 && paintingId < iFrom);
+ assertTrue(s, paintingId > 0 && paintingId < iFrom);
int artistName = s.indexOf(charStart + "t1" + charEnd + "." + charStart + "ARTIST_NAME" + charEnd);
- assertTrue(artistName > 0 && artistName < iFrom);
+ assertTrue(s, artistName > 0 && artistName < iFrom);
int artistId = s.indexOf(charStart + "t1" + charEnd + "." + charStart + "ARTIST_ID" + charEnd);
- assertTrue(artistId > 0 && artistId < iFrom);
+ assertTrue(s, artistId > 0 && artistId < iFrom);
int dateOfBirth = s.indexOf(charStart + "t1" + charEnd + "." + charStart + "DATE_OF_BIRTH" + charEnd);
- assertTrue(dateOfBirth > 0 && dateOfBirth < iFrom);
+ assertTrue(s, dateOfBirth > 0 && dateOfBirth < iFrom);
int iPainting = s.indexOf(charStart + "PAINTING" + charEnd + " " + charStart + "t0" + charEnd);
- assertTrue(iPainting > iFrom);
+ assertTrue(s, iPainting > iFrom);
int iLeftJoin = s.indexOf("LEFT JOIN");
- assertTrue(iLeftJoin > iFrom);
+ assertTrue(s, iLeftJoin > iFrom);
int iArtist = s.indexOf(charStart + "ARTIST" + charEnd + " " + charStart + "t1" + charEnd);
- assertTrue(iArtist > iLeftJoin);
+ assertTrue(s, iArtist > iLeftJoin);
int iOn = s.indexOf(" ON ");
- assertTrue(iOn > iLeftJoin);
+ assertTrue(s, iOn > iLeftJoin);
int iArtistId = s.indexOf(charStart + "t0" + charEnd + "." + charStart + "ARTIST_ID" + charEnd, iLeftJoin);
- assertTrue(iArtistId > iOn);
+ assertTrue(s, iArtistId > iOn);
int iArtistIdT1 = s
.indexOf(charStart + "t1" + charEnd + "." + charStart + "ARTIST_ID" + charEnd, iLeftJoin);
- assertTrue(iArtistIdT1 > iOn);
+ assertTrue(s, iArtistIdT1 > iOn);
int i = s.indexOf("=", iLeftJoin);
- assertTrue(iArtistIdT1 > i || iArtistId > i);
+ assertTrue(s, iArtistIdT1 > i || iArtistId > i);
} finally {
DbEntity entity = context.getEntityResolver().getDbEntity("PAINTING");
@@ -737,16 +735,28 @@ public class DefaultSelectTranslatorIT extends ServerCase {
*/
@Test
public void testBuildResultColumns1() throws Exception {
- SelectQuery q = new SelectQuery(Painting.class);
- DefaultSelectTranslator tr = new DefaultSelectTranslator(q, dataNode.getAdapter(), dataNode.getEntityResolver());
+ SelectQuery<Painting> q = new SelectQuery<>(Painting.class);
+ SelectTranslator tr = new DefaultSelectTranslator(q, dataNode.getAdapter(), dataNode.getEntityResolver());
- List<?> columns = tr.buildResultColumns();
+ tr.getSql();
+
+ List<ColumnDescriptor> columns = Arrays.asList(tr.getResultColumns());
+ columns.sort(Comparator.comparing(ColumnDescriptor::getName));
- // all DbAttributes must be included
DbEntity entity = context.getEntityResolver().getDbEntity("PAINTING");
- for (final DbAttribute a : entity.getAttributes()) {
- ColumnDescriptor c = new ColumnDescriptor(a, "t0");
- assertTrue("No descriptor for " + a + ", columns: " + columns, columns.contains(c));
+ List<DbAttribute> attributes = new ArrayList<>(entity.getAttributes());
+ attributes.sort(Comparator.comparing(DbAttribute::getName));
+
+ // all DbAttributes must be included
+ assertEquals(attributes.size(), columns.size());
+
+ for(int i=0; i<attributes.size(); i++) {
+ DbAttribute attribute = attributes.get(i);
+ ColumnDescriptor descriptor = columns.get(i);
+ assertEquals(attribute, descriptor.getAttribute());
+ assertEquals(attribute.getName(), descriptor.getName());
+ assertEquals(attribute.getName(), descriptor.getDataRowKey());
+ assertEquals(attribute.getType(), descriptor.getJdbcType());
}
}
@@ -755,31 +765,32 @@ public class DefaultSelectTranslatorIT extends ServerCase {
*/
@Test
public void testBuildResultColumns2() throws Exception {
- SelectQuery q = new SelectQuery(Painting.class);
+ SelectQuery<Painting> q = new SelectQuery<>(Painting.class);
q.addPrefetch(Painting.TO_ARTIST.joint());
- DefaultSelectTranslator tr = new DefaultSelectTranslator(q, dataNode.getAdapter(), dataNode.getEntityResolver());
-
- List<?> columns = tr.buildResultColumns();
-
- // assert root entity columns
- DbEntity entity = context.getEntityResolver().getDbEntity("PAINTING");
- for (final DbAttribute a : entity.getAttributes()) {
- ColumnDescriptor c = new ColumnDescriptor(a, "t0");
- assertTrue("No descriptor for " + a + ", columns: " + columns, columns.contains(c));
- }
-
- // assert joined columns
- DbEntity joined = context.getEntityResolver().getDbEntity("ARTIST");
- for (final DbAttribute a : joined.getAttributes()) {
-
- // skip ARTIST PK, it is joined from painting
- if (Artist.ARTIST_ID_PK_COLUMN.equals(a.getName())) {
- continue;
+ SelectTranslator tr = new DefaultSelectTranslator(q, dataNode.getAdapter(), dataNode.getEntityResolver());
+
+ tr.getSql();
+
+ List<ColumnDescriptor> columns = Arrays.asList(tr.getResultColumns());
+ columns.sort(Comparator.comparing(ColumnDescriptor::getName));
+
+ DbEntity rootEntity = context.getEntityResolver().getDbEntity("PAINTING");
+ List<DbAttribute> attributes = new ArrayList<>(rootEntity.getAttributes());
+ DbEntity joinedEntity = context.getEntityResolver().getDbEntity("ARTIST");
+ attributes.addAll(joinedEntity.getAttributes());
+ attributes.sort(Comparator.comparing(DbAttribute::getName));
+
+ for(int i=0; i<attributes.size(); i++) {
+ DbAttribute attribute = attributes.get(i);
+ ColumnDescriptor descriptor = columns.get(i);
+ assertEquals(attribute, descriptor.getAttribute());
+ assertEquals(attribute.getName(), descriptor.getName());
+ if("ARTIST".equals(attribute.getEntity().getName())) {
+ assertEquals("toArtist." + attribute.getName(), descriptor.getDataRowKey());
+ } else {
+ assertEquals(attribute.getName(), descriptor.getDataRowKey());
}
-
- ColumnDescriptor c = new ColumnDescriptor(a, "t1");
- c.setDataRowKey("toArtist." + a.getName());
- assertTrue("No descriptor for " + a + ", columns: " + columns, columns.contains(c));
+ assertEquals(attribute.getType(), descriptor.getJdbcType());
}
}
http://git-wip-us.apache.org/repos/asf/cayenne/blob/f6b2dac9/cayenne-server/src/test/java/org/apache/cayenne/access/translator/select/DescriptorColumnExtractorTest.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/test/java/org/apache/cayenne/access/translator/select/DescriptorColumnExtractorTest.java b/cayenne-server/src/test/java/org/apache/cayenne/access/translator/select/DescriptorColumnExtractorTest.java
new file mode 100644
index 0000000..b0de2f29
--- /dev/null
+++ b/cayenne-server/src/test/java/org/apache/cayenne/access/translator/select/DescriptorColumnExtractorTest.java
@@ -0,0 +1,97 @@
+/*****************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ ****************************************************************/
+
+package org.apache.cayenne.access.translator.select;
+
+import java.sql.Types;
+
+import org.apache.cayenne.access.sqlbuilder.sqltree.ColumnNode;
+import org.apache.cayenne.map.DataMap;
+import org.apache.cayenne.map.DbEntity;
+import org.apache.cayenne.map.EntityResolver;
+import org.apache.cayenne.map.ObjAttribute;
+import org.apache.cayenne.map.ObjEntity;
+import org.junit.Test;
+
+import static org.hamcrest.CoreMatchers.instanceOf;
+import static org.junit.Assert.*;
+
+/**
+ * @since 4.2
+ */
+public class DescriptorColumnExtractorTest extends BaseColumnExtractorTest {
+
+ @Test
+ public void testExtractNoPrefix() {
+ DbEntity mockDbEntity = createMockDbEntity("mock");
+ TranslatableQueryWrapper wrapper = new MockQueryWrapperBuilder()
+ .withMetaData(new MockQueryMetadataBuilder()
+ .withDbEntity(mockDbEntity)
+ .build())
+ .build();
+
+ TranslatorContext context = new MockTranslatorContext(wrapper);
+
+ DataMap dataMap = new DataMap();
+ dataMap.addDbEntity(mockDbEntity);
+
+ ObjEntity entity = new ObjEntity();
+ entity.setName("mock");
+ entity.setDataMap(dataMap);
+ entity.setDbEntity(mockDbEntity);
+
+ ObjAttribute attribute = new ObjAttribute();
+ attribute.setName("not_name");
+ attribute.setDbAttributePath("name");
+ attribute.setType("my.type");
+ entity.addAttribute(attribute);
+
+ dataMap.addObjEntity(entity);
+
+ EntityResolver resolver = new EntityResolver();
+ resolver.addDataMap(dataMap);
+
+ DescriptorColumnExtractor extractor = new DescriptorColumnExtractor(context, resolver.getClassDescriptor("mock"));
+ extractor.extract();
+
+ assertEquals(2, context.getResultNodeList().size());
+
+ ResultNodeDescriptor descriptor0 = context.getResultNodeList().get(0);
+ ResultNodeDescriptor descriptor1 = context.getResultNodeList().get(1);
+
+ assertNull(descriptor0.getProperty());
+ assertNotNull(descriptor0.getNode());
+ assertThat(descriptor0.getNode(), instanceOf(ColumnNode.class));
+ assertFalse(descriptor0.isAggregate());
+ assertTrue(descriptor0.isInDataRow());
+ assertNotNull(descriptor0.getDbAttribute());
+ assertEquals("name", descriptor0.getDataRowKey());
+ assertEquals(Types.VARBINARY, descriptor0.getJdbcType());
+ assertEquals("my.type", descriptor0.getJavaType());
+
+ assertNull(descriptor1.getProperty());
+ assertNotNull(descriptor1.getNode());
+ assertThat(descriptor1.getNode(), instanceOf(ColumnNode.class));
+ assertFalse(descriptor1.isAggregate());
+ assertTrue(descriptor1.isInDataRow());
+ assertEquals("id", descriptor1.getDataRowKey());
+ assertNotNull(descriptor1.getDbAttribute());
+ assertEquals(Types.BIGINT, descriptor1.getJdbcType());
+ }
+}
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/cayenne/blob/f6b2dac9/cayenne-server/src/test/java/org/apache/cayenne/access/translator/select/DistinctStageTest.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/test/java/org/apache/cayenne/access/translator/select/DistinctStageTest.java b/cayenne-server/src/test/java/org/apache/cayenne/access/translator/select/DistinctStageTest.java
new file mode 100644
index 0000000..26ccfe4
--- /dev/null
+++ b/cayenne-server/src/test/java/org/apache/cayenne/access/translator/select/DistinctStageTest.java
@@ -0,0 +1,103 @@
+/*****************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ ****************************************************************/
+
+package org.apache.cayenne.access.translator.select;
+
+import java.sql.Types;
+
+import org.apache.cayenne.access.sqlbuilder.sqltree.ColumnNode;
+import org.apache.cayenne.access.sqlbuilder.sqltree.Node;
+import org.apache.cayenne.map.DbAttribute;
+import org.junit.Test;
+
+import static org.junit.Assert.*;
+
+/**
+ * @since 4.2
+ */
+public class DistinctStageTest {
+
+ @Test
+ public void isUnsupportedForDistinct() {
+ assertTrue(DistinctStage.isUnsupportedForDistinct(Types.BLOB));
+ assertTrue(DistinctStage.isUnsupportedForDistinct(Types.CLOB));
+ assertTrue(DistinctStage.isUnsupportedForDistinct(Types.NCLOB));
+ assertTrue(DistinctStage.isUnsupportedForDistinct(Types.LONGVARCHAR));
+ assertTrue(DistinctStage.isUnsupportedForDistinct(Types.LONGNVARCHAR));
+ assertTrue(DistinctStage.isUnsupportedForDistinct(Types.LONGVARBINARY));
+ assertFalse(DistinctStage.isUnsupportedForDistinct(Types.INTEGER));
+ assertFalse(DistinctStage.isUnsupportedForDistinct(Types.DATE));
+ assertFalse(DistinctStage.isUnsupportedForDistinct(Types.CHAR));
+ assertFalse(DistinctStage.isUnsupportedForDistinct(Types.DECIMAL));
+ assertFalse(DistinctStage.isUnsupportedForDistinct(Types.FLOAT));
+ assertFalse(DistinctStage.isUnsupportedForDistinct(Types.VARCHAR));
+ }
+
+ @Test
+ public void noSuppression() {
+ TranslatableQueryWrapper wrapper = new MockQueryWrapperBuilder().withDistinct(true).build();
+ TranslatorContext context = new MockTranslatorContext(wrapper);
+
+ assertFalse(context.isDistinctSuppression());
+
+ DistinctStage stage = new DistinctStage();
+ stage.perform(context);
+
+ assertFalse(context.isDistinctSuppression());
+ }
+
+ @Test
+ public void explicitSuppression() {
+ TranslatableQueryWrapper wrapper = new MockQueryWrapperBuilder()
+ .withDistinct(true)
+ .withMetaData(new MockQueryMetadataBuilder()
+ .withSuppressDistinct()
+ .build())
+ .build();
+ TranslatorContext context = new MockTranslatorContext(wrapper);
+
+ assertFalse(context.isDistinctSuppression());
+
+ DistinctStage stage = new DistinctStage();
+ stage.perform(context);
+
+ assertTrue(context.isDistinctSuppression());
+ }
+
+ @Test
+ public void suppressionByType() {
+ TranslatableQueryWrapper wrapper = new MockQueryWrapperBuilder()
+ .withDistinct(true)
+ .withMetaData(new MockQueryMetadataBuilder().build())
+ .build();
+ TranslatorContext context = new MockTranslatorContext(wrapper);
+
+ DbAttribute attribute = new DbAttribute();
+ attribute.setType(Types.LONGVARBINARY);
+ Node node = new ColumnNode("t0", "attr", null, attribute);
+ context.addResultNode(node);
+
+ assertFalse(context.isDistinctSuppression());
+
+ DistinctStage stage = new DistinctStage();
+ stage.perform(context);
+
+ assertTrue(context.isDistinctSuppression());
+ }
+}
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/cayenne/blob/f6b2dac9/cayenne-server/src/test/java/org/apache/cayenne/access/translator/select/GroupByStageTest.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/test/java/org/apache/cayenne/access/translator/select/GroupByStageTest.java b/cayenne-server/src/test/java/org/apache/cayenne/access/translator/select/GroupByStageTest.java
new file mode 100644
index 0000000..d739714
--- /dev/null
+++ b/cayenne-server/src/test/java/org/apache/cayenne/access/translator/select/GroupByStageTest.java
@@ -0,0 +1,86 @@
+/*****************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ ****************************************************************/
+
+package org.apache.cayenne.access.translator.select;
+
+import org.apache.cayenne.access.sqlbuilder.sqltree.ColumnNode;
+import org.apache.cayenne.access.sqlbuilder.sqltree.EmptyNode;
+import org.apache.cayenne.access.sqlbuilder.sqltree.GroupByNode;
+import org.apache.cayenne.access.sqlbuilder.sqltree.Node;
+import org.apache.cayenne.exp.Property;
+import org.junit.Before;
+import org.junit.Test;
+
+import static org.hamcrest.CoreMatchers.instanceOf;
+import static org.junit.Assert.*;
+
+/**
+ * @since 4.2
+ */
+public class GroupByStageTest {
+
+ private TranslatorContext context;
+
+ @Before
+ public void prepareContext() {
+ TranslatableQueryWrapper wrapper = new MockQueryWrapperBuilder().build();
+ context = new MockTranslatorContext(wrapper);
+ }
+
+ // no result columns
+ @Test
+ public void emptyContext() {
+ GroupByStage stage = new GroupByStage();
+ stage.perform(context);
+
+ Node node = context.getSelectBuilder().build();
+ assertEquals(0, node.getChildrenCount());
+ }
+
+ // result column but no aggregates
+ @Test
+ public void noAggregates() {
+ context.addResultNode(new ColumnNode("t0", "column", null, null));
+
+ GroupByStage stage = new GroupByStage();
+ stage.perform(context);
+
+ Node node = context.getSelectBuilder().build();
+ assertEquals(0, node.getChildrenCount());
+ }
+
+ // result column + aggregate
+ @Test
+ public void groupByWithAggregates() {
+ context.addResultNode(new ColumnNode("t0", "column", null, null));
+ context.addResultNode(new EmptyNode(), true, Property.COUNT, "count");
+
+ GroupByStage stage = new GroupByStage();
+ stage.perform(context);
+
+ Node node = context.getSelectBuilder().build();
+ assertEquals(1, node.getChildrenCount());
+ assertThat(node.getChild(0), instanceOf(GroupByNode.class));
+ assertEquals(1, node.getChild(0).getChildrenCount());
+ assertThat(node.getChild(0).getChild(0), instanceOf(ColumnNode.class));
+ ColumnNode columnNode = (ColumnNode)node.getChild(0).getChild(0);
+ assertEquals("t0", columnNode.getTable());
+ assertEquals("column", columnNode.getColumn());
+ }
+}
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/cayenne/blob/f6b2dac9/cayenne-server/src/test/java/org/apache/cayenne/access/translator/select/HavingTranslationStageTest.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/test/java/org/apache/cayenne/access/translator/select/HavingTranslationStageTest.java b/cayenne-server/src/test/java/org/apache/cayenne/access/translator/select/HavingTranslationStageTest.java
new file mode 100644
index 0000000..8d6160e
--- /dev/null
+++ b/cayenne-server/src/test/java/org/apache/cayenne/access/translator/select/HavingTranslationStageTest.java
@@ -0,0 +1,90 @@
+/*****************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ ****************************************************************/
+
+package org.apache.cayenne.access.translator.select;
+
+import org.apache.cayenne.access.sqlbuilder.sqltree.ColumnNode;
+import org.apache.cayenne.access.sqlbuilder.sqltree.HavingNode;
+import org.apache.cayenne.access.sqlbuilder.sqltree.Node;
+import org.apache.cayenne.access.sqlbuilder.sqltree.OpExpressionNode;
+import org.apache.cayenne.access.sqlbuilder.sqltree.ValueNode;
+import org.apache.cayenne.exp.ExpressionFactory;
+import org.apache.cayenne.map.DbAttribute;
+import org.apache.cayenne.map.DbEntity;
+import org.junit.Before;
+import org.junit.Test;
+
+import static org.hamcrest.CoreMatchers.instanceOf;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertThat;
+
+/**
+ * @since 4.2
+ */
+public class HavingTranslationStageTest {
+
+ private TranslatorContext context;
+
+ @Before
+ public void prepareContext() {
+ DbEntity dbEntity = new DbEntity();
+ dbEntity.setName("mock");
+ DbAttribute dbAttribute = new DbAttribute();
+ dbAttribute.setName("path");
+ dbEntity.addAttribute(dbAttribute);
+
+ TranslatableQueryWrapper wrapper = new MockQueryWrapperBuilder()
+ .withHavingQualifier(ExpressionFactory.greaterOrEqualDbExp("path", 10))
+ .withMetaData(new MockQueryMetadataBuilder()
+ .withDbEntity(dbEntity)
+ .build())
+ .build();
+ context = new MockTranslatorContext(wrapper);
+ }
+
+ @Test
+ public void perform() {
+ HavingTranslationStage stage = new HavingTranslationStage();
+ stage.perform(context);
+
+ Node select = context.getSelectBuilder().build();
+
+ // Content of "select" node:
+ //
+ // Having
+ // |
+ // OpExpression
+ // / \
+ // Column Value
+
+ assertEquals(1, select.getChildrenCount());
+ assertThat(select.getChild(0), instanceOf(HavingNode.class));
+ Node op = select.getChild(0).getChild(0);
+ assertThat(op, instanceOf(OpExpressionNode.class));
+ assertEquals(">=", ((OpExpressionNode)op).getOp());
+ assertEquals(2, op.getChildrenCount());
+ assertThat(op.getChild(0), instanceOf(ColumnNode.class));
+ assertThat(op.getChild(1), instanceOf(ValueNode.class));
+
+ ColumnNode columnNode = (ColumnNode)op.getChild(0);
+ ValueNode valueNode = (ValueNode)op.getChild(1);
+ assertEquals("path", columnNode.getColumn());
+ assertEquals(10, valueNode.getValue());
+ }
+}
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/cayenne/blob/f6b2dac9/cayenne-server/src/test/java/org/apache/cayenne/access/translator/select/IdColumnExtractorTest.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/test/java/org/apache/cayenne/access/translator/select/IdColumnExtractorTest.java b/cayenne-server/src/test/java/org/apache/cayenne/access/translator/select/IdColumnExtractorTest.java
new file mode 100644
index 0000000..28df71b
--- /dev/null
+++ b/cayenne-server/src/test/java/org/apache/cayenne/access/translator/select/IdColumnExtractorTest.java
@@ -0,0 +1,113 @@
+/*****************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ ****************************************************************/
+
+package org.apache.cayenne.access.translator.select;
+
+import org.apache.cayenne.access.sqlbuilder.sqltree.ColumnNode;
+import org.apache.cayenne.map.DataMap;
+import org.apache.cayenne.map.DbEntity;
+import org.apache.cayenne.map.DbRelationship;
+import org.apache.cayenne.map.JoinType;
+import org.apache.cayenne.map.ObjEntity;
+import org.junit.Test;
+
+import java.sql.Types;
+
+import static org.hamcrest.CoreMatchers.instanceOf;
+import static org.junit.Assert.*;
+
+public class IdColumnExtractorTest extends BaseColumnExtractorTest {
+
+ @Test
+ public void testExtractNoPrefix() {
+ DbEntity mockDbEntity = createMockDbEntity("mock");
+ TranslatableQueryWrapper wrapper = new MockQueryWrapperBuilder()
+ .withMetaData(new MockQueryMetadataBuilder()
+ .withDbEntity(mockDbEntity)
+ .build())
+ .build();
+ TranslatorContext context = new MockTranslatorContext(wrapper);
+
+ DataMap dataMap = new DataMap();
+ dataMap.addDbEntity(mockDbEntity);
+
+ ObjEntity entity = new ObjEntity();
+ entity.setDataMap(dataMap);
+ entity.setDbEntity(mockDbEntity);
+
+ IdColumnExtractor extractor = new IdColumnExtractor(context, entity);
+ extractor.extract();
+
+ assertEquals(1, context.getResultNodeList().size());
+
+ ResultNodeDescriptor descriptor0 = context.getResultNodeList().get(0);
+
+ assertNull(descriptor0.getProperty());
+ assertNotNull(descriptor0.getNode());
+ assertThat(descriptor0.getNode(), instanceOf(ColumnNode.class));
+ assertFalse(descriptor0.isAggregate());
+ assertTrue(descriptor0.isInDataRow());
+ assertEquals("id", descriptor0.getDataRowKey());
+ assertNotNull(descriptor0.getDbAttribute());
+ assertEquals(Types.BIGINT, descriptor0.getJdbcType());
+ }
+
+ @Test
+ public void testExtractWithPrefix() {
+ DbEntity mockDbEntity = createMockDbEntity("mock1");
+ DbEntity mock2DbEntity = createMockDbEntity("mock2");
+
+ TranslatableQueryWrapper wrapper = new MockQueryWrapperBuilder()
+ .withMetaData(new MockQueryMetadataBuilder()
+ .withDbEntity(mockDbEntity)
+ .build())
+ .build();
+ TranslatorContext context = new MockTranslatorContext(wrapper);
+
+ ObjEntity entity = new ObjEntity();
+ entity.setDbEntity(mockDbEntity);
+
+ DataMap dataMap = new DataMap();
+ dataMap.addDbEntity(mockDbEntity);
+ dataMap.addDbEntity(mock2DbEntity);
+ mockDbEntity.setDataMap(dataMap);
+ entity.setDataMap(dataMap);
+
+ DbRelationship relationship = new DbRelationship();
+ relationship.setSourceEntity(mockDbEntity);
+ relationship.setTargetEntityName("mock1");
+ context.getTableTree().addJoinTable("prefix", relationship, JoinType.INNER);
+
+ IdColumnExtractor extractor = new IdColumnExtractor(context, entity);
+ extractor.extract("prefix");
+
+ assertEquals(1, context.getResultNodeList().size());
+
+ ResultNodeDescriptor descriptor0 = context.getResultNodeList().get(0);
+
+ assertNull(descriptor0.getProperty());
+ assertNotNull(descriptor0.getNode());
+ assertThat(descriptor0.getNode(), instanceOf(ColumnNode.class));
+ assertFalse(descriptor0.isAggregate());
+ assertTrue(descriptor0.isInDataRow());
+ assertEquals("prefix.id", descriptor0.getDataRowKey());
+ assertNotNull(descriptor0.getDbAttribute());
+ assertEquals(Types.BIGINT, descriptor0.getJdbcType());
+ }
+}
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/cayenne/blob/f6b2dac9/cayenne-server/src/test/java/org/apache/cayenne/access/translator/select/LimitOffsetStageTest.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/test/java/org/apache/cayenne/access/translator/select/LimitOffsetStageTest.java b/cayenne-server/src/test/java/org/apache/cayenne/access/translator/select/LimitOffsetStageTest.java
new file mode 100644
index 0000000..3cd86d1
--- /dev/null
+++ b/cayenne-server/src/test/java/org/apache/cayenne/access/translator/select/LimitOffsetStageTest.java
@@ -0,0 +1,65 @@
+/*****************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ ****************************************************************/
+
+package org.apache.cayenne.access.translator.select;
+
+import org.apache.cayenne.access.sqlbuilder.sqltree.LimitOffsetNode;
+import org.apache.cayenne.access.sqlbuilder.sqltree.Node;
+import org.apache.cayenne.map.DbEntity;
+import org.junit.Before;
+import org.junit.Test;
+
+import static org.hamcrest.CoreMatchers.instanceOf;
+import static org.junit.Assert.*;
+
+/**
+ * @since 4.2
+ */
+public class LimitOffsetStageTest {
+
+ private TranslatorContext context;
+
+ @Before
+ public void prepareContext() {
+ DbEntity entity = new DbEntity();
+ entity.setName("mock");
+
+ TranslatableQueryWrapper wrapper = new MockQueryWrapperBuilder()
+ .withMetaData(new MockQueryMetadataBuilder()
+ .withDbEntity(entity)
+ .withLimitOffset(123, 321)
+ .build())
+ .build();
+ context = new MockTranslatorContext(wrapper);
+ }
+
+ @Test
+ public void perform() {
+ LimitOffsetStage stage = new LimitOffsetStage();
+ stage.perform(context);
+
+ Node select = context.getSelectBuilder().build();
+ Node child = select.getChild(0);
+ assertThat(child, instanceOf(LimitOffsetNode.class));
+
+ LimitOffsetNode limitOffsetNode = (LimitOffsetNode) child;
+ assertEquals(123, limitOffsetNode.getLimit());
+ assertEquals(321, limitOffsetNode.getOffset());
+ }
+}
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/cayenne/blob/f6b2dac9/cayenne-server/src/test/java/org/apache/cayenne/access/translator/select/MockQueryMetadataBuilder.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/test/java/org/apache/cayenne/access/translator/select/MockQueryMetadataBuilder.java b/cayenne-server/src/test/java/org/apache/cayenne/access/translator/select/MockQueryMetadataBuilder.java
new file mode 100644
index 0000000..e665381
--- /dev/null
+++ b/cayenne-server/src/test/java/org/apache/cayenne/access/translator/select/MockQueryMetadataBuilder.java
@@ -0,0 +1,100 @@
+/*****************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ ****************************************************************/
+
+package org.apache.cayenne.access.translator.select;
+
+import java.util.Collections;
+import java.util.Map;
+
+import org.apache.cayenne.map.DbEntity;
+import org.apache.cayenne.map.ObjEntity;
+import org.apache.cayenne.query.MockQueryMetadata;
+import org.apache.cayenne.query.QueryMetadata;
+
+/**
+ * @since 4.2
+ */
+class MockQueryMetadataBuilder {
+
+ private ObjEntity objEntity;
+
+ private DbEntity dbEntity;
+
+ private int limit;
+
+ private int offset;
+
+ private boolean suppressDistinct;
+
+ MockQueryMetadataBuilder withDbEntity(DbEntity entity) {
+ this.dbEntity = entity;
+ return this;
+ }
+
+ MockQueryMetadataBuilder withObjEntity(ObjEntity entity) {
+ this.objEntity = entity;
+ return this;
+ }
+
+ MockQueryMetadataBuilder withLimitOffset(int limit, int offset) {
+ this.limit = limit;
+ this.offset = offset;
+ return this;
+ }
+
+ MockQueryMetadataBuilder withSuppressDistinct() {
+ this.suppressDistinct = true;
+ return this;
+ }
+
+ QueryMetadata build() {
+ return new MockQueryMetadata() {
+
+ @Override
+ public ObjEntity getObjEntity() {
+ return objEntity;
+ }
+
+ @Override
+ public DbEntity getDbEntity() {
+ return dbEntity;
+ }
+
+ @Override
+ public int getFetchOffset() {
+ return offset;
+ }
+
+ @Override
+ public int getFetchLimit() {
+ return limit;
+ }
+
+ @Override
+ public Map<String, String> getPathSplitAliases() {
+ return Collections.emptyMap();
+ }
+
+ @Override
+ public boolean isSuppressingDistinct() {
+ return suppressDistinct;
+ }
+ };
+ }
+}
http://git-wip-us.apache.org/repos/asf/cayenne/blob/f6b2dac9/cayenne-server/src/test/java/org/apache/cayenne/access/translator/select/MockQueryWrapperBuilder.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/test/java/org/apache/cayenne/access/translator/select/MockQueryWrapperBuilder.java b/cayenne-server/src/test/java/org/apache/cayenne/access/translator/select/MockQueryWrapperBuilder.java
new file mode 100644
index 0000000..3c43204
--- /dev/null
+++ b/cayenne-server/src/test/java/org/apache/cayenne/access/translator/select/MockQueryWrapperBuilder.java
@@ -0,0 +1,137 @@
+/*****************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ ****************************************************************/
+
+package org.apache.cayenne.access.translator.select;
+
+import java.util.Collection;
+
+import org.apache.cayenne.exp.Expression;
+import org.apache.cayenne.exp.property.BaseProperty;
+import org.apache.cayenne.map.EntityResolver;
+import org.apache.cayenne.query.MockQueryMetadata;
+import org.apache.cayenne.query.Ordering;
+import org.apache.cayenne.query.PrefetchTreeNode;
+import org.apache.cayenne.query.QueryMetadata;
+import org.apache.cayenne.query.Select;
+
+/**
+ * @since 4.2
+ */
+class MockQueryWrapperBuilder {
+
+ private boolean distinct;
+
+ private QueryMetadata metaData;
+
+ private PrefetchTreeNode prefetchTreeNode;
+
+ private Expression qualifier;
+
+ private Collection<Ordering> orderings;
+
+ private Collection<BaseProperty<?>> columns;
+
+ private Expression havingQualifier;
+
+ private Select<?> mockSelect;
+
+ MockQueryWrapperBuilder withDistinct(boolean distinct) {
+ this.distinct = distinct;
+ return this;
+ }
+
+ MockQueryWrapperBuilder withMetaData(QueryMetadata metaData) {
+ this.metaData = metaData;
+ return this;
+ }
+
+ MockQueryWrapperBuilder withPrefetchTreeNode(PrefetchTreeNode prefetchTreeNode) {
+ this.prefetchTreeNode = prefetchTreeNode;
+ return this;
+ }
+
+ MockQueryWrapperBuilder withQualifier(Expression qualifier) {
+ this.qualifier = qualifier;
+ return this;
+ }
+
+ MockQueryWrapperBuilder withOrderings(Collection<Ordering> orderings) {
+ this.orderings = orderings;
+ return this;
+ }
+
+ MockQueryWrapperBuilder withColumns(Collection<BaseProperty<?>> columns) {
+ this.columns = columns;
+ return this;
+ }
+
+ MockQueryWrapperBuilder withHavingQualifier(Expression havingQualifier) {
+ this.havingQualifier = havingQualifier;
+ return this;
+ }
+
+ MockQueryWrapperBuilder withSelect(Select<?> select) {
+ this.mockSelect = select;
+ return this;
+ }
+
+ TranslatableQueryWrapper build() {
+ return new TranslatableQueryWrapper() {
+ @Override
+ public boolean isDistinct() {
+ return distinct;
+ }
+
+ @Override
+ public QueryMetadata getMetaData(EntityResolver resolver) {
+ return metaData != null ? metaData : new MockQueryMetadata();
+ }
+
+ @Override
+ public PrefetchTreeNode getPrefetchTree() {
+ return prefetchTreeNode;
+ }
+
+ @Override
+ public Expression getQualifier() {
+ return qualifier;
+ }
+
+ @Override
+ public Collection<Ordering> getOrderings() {
+ return orderings;
+ }
+
+ @Override
+ public Collection<BaseProperty<?>> getColumns() {
+ return columns;
+ }
+
+ @Override
+ public Expression getHavingQualifier() {
+ return havingQualifier;
+ }
+
+ @Override
+ public Select<?> unwrap() {
+ return mockSelect;
+ }
+ };
+ }
+}
http://git-wip-us.apache.org/repos/asf/cayenne/blob/f6b2dac9/cayenne-server/src/test/java/org/apache/cayenne/access/translator/select/MockTranslatorContext.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/test/java/org/apache/cayenne/access/translator/select/MockTranslatorContext.java b/cayenne-server/src/test/java/org/apache/cayenne/access/translator/select/MockTranslatorContext.java
new file mode 100644
index 0000000..b18f5cf
--- /dev/null
+++ b/cayenne-server/src/test/java/org/apache/cayenne/access/translator/select/MockTranslatorContext.java
@@ -0,0 +1,35 @@
+/*****************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ ****************************************************************/
+
+package org.apache.cayenne.access.translator.select;
+
+import org.apache.cayenne.dba.DbAdapter;
+import org.apache.cayenne.map.EntityResolver;
+
+import static org.mockito.Mockito.mock;
+
+public class MockTranslatorContext extends TranslatorContext {
+ MockTranslatorContext(TranslatableQueryWrapper query) {
+ super(query, mock(DbAdapter.class), null, null);
+ }
+
+ MockTranslatorContext(TranslatableQueryWrapper query, EntityResolver resolver) {
+ super(query, mock(DbAdapter.class), resolver, null);
+ }
+}
http://git-wip-us.apache.org/repos/asf/cayenne/blob/f6b2dac9/cayenne-server/src/test/java/org/apache/cayenne/access/translator/select/ObjPathProcessorIT.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/test/java/org/apache/cayenne/access/translator/select/ObjPathProcessorIT.java b/cayenne-server/src/test/java/org/apache/cayenne/access/translator/select/ObjPathProcessorIT.java
new file mode 100644
index 0000000..93f0141
--- /dev/null
+++ b/cayenne-server/src/test/java/org/apache/cayenne/access/translator/select/ObjPathProcessorIT.java
@@ -0,0 +1,92 @@
+/*****************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ ****************************************************************/
+
+package org.apache.cayenne.access.translator.select;
+
+import org.apache.cayenne.ObjectContext;
+import org.apache.cayenne.dba.DbAdapter;
+import org.apache.cayenne.di.Inject;
+import org.apache.cayenne.map.ObjEntity;
+import org.apache.cayenne.query.SelectQuery;
+import org.apache.cayenne.unit.di.server.CayenneProjects;
+import org.apache.cayenne.unit.di.server.ServerCase;
+import org.apache.cayenne.unit.di.server.UseServerRuntime;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mockito;
+
+import static org.junit.Assert.*;
+
+/**
+ * @since 4.2
+ */
+@UseServerRuntime(CayenneProjects.INHERITANCE_VERTICAL_PROJECT)
+public class ObjPathProcessorIT extends ServerCase {
+
+ @Inject
+ protected ObjectContext context;
+
+ private ObjPathProcessor pathProcessor;
+
+ @Before
+ public void prepareTranslationContext() {
+ TranslatorContext translatorContext = new TranslatorContext(
+ new SelectQueryWrapper(new SelectQuery<>()),
+ Mockito.mock(DbAdapter.class),
+ context.getEntityResolver(),
+ null
+ );
+ ObjEntity entity = context.getEntityResolver().getObjEntity("IvSub3");
+ pathProcessor = new ObjPathProcessor(translatorContext, entity, null);
+ }
+
+ @Test
+ public void testSimpleAttributePathTranslation() {
+ PathTranslationResult result = pathProcessor.process("name");
+ assertEquals(1, result.getDbAttributes().size());
+ assertEquals(1, result.getAttributePaths().size());
+
+ assertEquals("", result.getLastAttributePath());
+ assertEquals("NAME", result.getLastAttribute().getName());
+ }
+
+ @Test
+ public void testInheritedRelationshipPathTranslation() {
+ PathTranslationResult result = pathProcessor.process("ivRoot");
+ assertEquals(2, result.getDbAttributes().size());
+ assertEquals(2, result.getAttributePaths().size());
+
+ assertEquals("sub3", result.getAttributePaths().get(0));
+ assertEquals("ID", result.getDbAttributes().get(0).getName());
+
+ assertEquals("sub3", result.getAttributePaths().get(1));
+ assertEquals("IV_ROOT_ID", result.getDbAttributes().get(1).getName());
+ }
+
+ @Test
+ public void testFlattenedAttributePathTranslation() {
+ PathTranslationResult result = pathProcessor.process("ivRoot.discriminator");
+ assertEquals(1, result.getDbAttributes().size());
+ assertEquals(1, result.getAttributePaths().size());
+
+ assertEquals("sub3.ivRoot1", result.getAttributePaths().get(0));
+ assertEquals("DISCRIMINATOR", result.getDbAttributes().get(0).getName());
+ }
+
+}
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/cayenne/blob/f6b2dac9/cayenne-server/src/test/java/org/apache/cayenne/access/translator/select/ObjPathProcessorIT2.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/test/java/org/apache/cayenne/access/translator/select/ObjPathProcessorIT2.java b/cayenne-server/src/test/java/org/apache/cayenne/access/translator/select/ObjPathProcessorIT2.java
new file mode 100644
index 0000000..484472d
--- /dev/null
+++ b/cayenne-server/src/test/java/org/apache/cayenne/access/translator/select/ObjPathProcessorIT2.java
@@ -0,0 +1,91 @@
+/*****************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ ****************************************************************/
+
+package org.apache.cayenne.access.translator.select;
+
+import org.apache.cayenne.ObjectContext;
+import org.apache.cayenne.dba.DbAdapter;
+import org.apache.cayenne.di.Inject;
+import org.apache.cayenne.map.ObjEntity;
+import org.apache.cayenne.query.SelectQuery;
+import org.apache.cayenne.unit.di.server.CayenneProjects;
+import org.apache.cayenne.unit.di.server.ServerCase;
+import org.apache.cayenne.unit.di.server.UseServerRuntime;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mockito;
+
+import static org.junit.Assert.assertEquals;
+
+/**
+ * @since 4.2
+ */
+@UseServerRuntime(CayenneProjects.COMPOUND_PROJECT)
+public class ObjPathProcessorIT2 extends ServerCase {
+
+ @Inject
+ protected ObjectContext context;
+
+ private ObjPathProcessor pathProcessor;
+
+ @Before
+ public void prepareTranslationContext() {
+ TranslatorContext translatorContext = new TranslatorContext(
+ new SelectQueryWrapper(new SelectQuery<>()),
+ Mockito.mock(DbAdapter.class),
+ context.getEntityResolver(),
+ null
+ );
+ ObjEntity entity = context.getEntityResolver().getObjEntity("CompoundFkTestEntity");
+ pathProcessor = new ObjPathProcessor(translatorContext, entity, null);
+ }
+
+ @Test
+ public void testSimpleAttributePathTranslation() {
+ PathTranslationResult result = pathProcessor.process("name");
+ assertEquals(1, result.getDbAttributes().size());
+ assertEquals(1, result.getAttributePaths().size());
+
+ assertEquals("", result.getLastAttributePath());
+ assertEquals("NAME", result.getLastAttribute().getName());
+ }
+
+ @Test
+ public void testCompoundRelationshipPathTranslation() {
+ PathTranslationResult result = pathProcessor.process("toCompoundPk");
+ assertEquals(2, result.getDbAttributes().size());
+ assertEquals(2, result.getAttributePaths().size());
+
+ assertEquals("", result.getAttributePaths().get(0));
+ assertEquals("F_KEY1", result.getDbAttributes().get(0).getName());
+
+ assertEquals("", result.getAttributePaths().get(1));
+ assertEquals("F_KEY2", result.getDbAttributes().get(1).getName());
+ }
+
+ @Test
+ public void testCompoundRelationshipFlattenedPathTranslation() {
+ PathTranslationResult result = pathProcessor.process("toCompoundPk.name");
+
+ assertEquals(1, result.getDbAttributes().size());
+
+ assertEquals("toCompoundPk", result.getLastAttributePath());
+ assertEquals("NAME", result.getLastAttribute().getName());
+ }
+}
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/cayenne/blob/f6b2dac9/cayenne-server/src/test/java/org/apache/cayenne/access/translator/select/ObjPathProcessorIT3.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/test/java/org/apache/cayenne/access/translator/select/ObjPathProcessorIT3.java b/cayenne-server/src/test/java/org/apache/cayenne/access/translator/select/ObjPathProcessorIT3.java
new file mode 100644
index 0000000..8175777
--- /dev/null
+++ b/cayenne-server/src/test/java/org/apache/cayenne/access/translator/select/ObjPathProcessorIT3.java
@@ -0,0 +1,82 @@
+/*****************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ ****************************************************************/
+
+package org.apache.cayenne.access.translator.select;
+
+import org.apache.cayenne.ObjectContext;
+import org.apache.cayenne.dba.DbAdapter;
+import org.apache.cayenne.di.Inject;
+import org.apache.cayenne.map.ObjEntity;
+import org.apache.cayenne.query.SelectQuery;
+import org.apache.cayenne.unit.di.server.CayenneProjects;
+import org.apache.cayenne.unit.di.server.ServerCase;
+import org.apache.cayenne.unit.di.server.UseServerRuntime;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mockito;
+
+import static org.junit.Assert.assertEquals;
+
+/**
+ * @since 4.2
+ */
+@UseServerRuntime(CayenneProjects.RELATIONSHIPS_FLATTENED_PROJECT)
+public class ObjPathProcessorIT3 extends ServerCase {
+
+ @Inject
+ protected ObjectContext context;
+
+ private ObjPathProcessor pathProcessor;
+
+ @Before
+ public void prepareTranslationContext() {
+ TranslatorContext translatorContext = new TranslatorContext(
+ new SelectQueryWrapper(new SelectQuery<>()),
+ Mockito.mock(DbAdapter.class),
+ context.getEntityResolver(),
+ null
+ );
+ ObjEntity entity = context.getEntityResolver().getObjEntity("FlattenedTest5");
+ pathProcessor = new ObjPathProcessor(translatorContext, entity, null);
+ }
+
+ @Test
+ public void testSimpleAttributePathTranslation() {
+ PathTranslationResult result = pathProcessor.process("name");
+ assertEquals(1, result.getDbAttributes().size());
+ assertEquals(1, result.getAttributePaths().size());
+
+ assertEquals("", result.getLastAttributePath());
+ assertEquals("NAME", result.getLastAttribute().getName());
+ }
+
+ @Test
+ public void testFlattenedRelationshipPathTranslation() {
+ PathTranslationResult result = pathProcessor.process("toFT1");
+ assertEquals(2, result.getDbAttributes().size());
+ assertEquals(2, result.getAttributePaths().size());
+
+ assertEquals("complexJoin2", result.getAttributePaths().get(0));
+ assertEquals("PK", result.getDbAttributes().get(0).getName());
+
+ assertEquals("complexJoin2", result.getAttributePaths().get(1));
+ assertEquals("FT1_FK", result.getDbAttributes().get(1).getName());
+ }
+
+}
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/cayenne/blob/f6b2dac9/cayenne-server/src/test/java/org/apache/cayenne/access/translator/select/ObjPathProcessorIT4.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/test/java/org/apache/cayenne/access/translator/select/ObjPathProcessorIT4.java b/cayenne-server/src/test/java/org/apache/cayenne/access/translator/select/ObjPathProcessorIT4.java
new file mode 100644
index 0000000..e84d06f
--- /dev/null
+++ b/cayenne-server/src/test/java/org/apache/cayenne/access/translator/select/ObjPathProcessorIT4.java
@@ -0,0 +1,72 @@
+/*****************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ ****************************************************************/
+
+package org.apache.cayenne.access.translator.select;
+
+import org.apache.cayenne.ObjectContext;
+import org.apache.cayenne.dba.DbAdapter;
+import org.apache.cayenne.di.Inject;
+import org.apache.cayenne.map.ObjEntity;
+import org.apache.cayenne.query.SelectQuery;
+import org.apache.cayenne.unit.di.server.CayenneProjects;
+import org.apache.cayenne.unit.di.server.ServerCase;
+import org.apache.cayenne.unit.di.server.UseServerRuntime;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mockito;
+
+import static org.junit.Assert.assertEquals;
+
+/**
+ * @since 4.2
+ */
+@UseServerRuntime(CayenneProjects.TESTMAP_PROJECT)
+public class ObjPathProcessorIT4 extends ServerCase {
+
+ @Inject
+ protected ObjectContext context;
+
+ private ObjPathProcessor pathProcessor;
+
+ @Before
+ public void prepareTranslationContext() {
+ TranslatorContext translatorContext = new TranslatorContext(
+ new SelectQueryWrapper(new SelectQuery<>()),
+ Mockito.mock(DbAdapter.class),
+ context.getEntityResolver(),
+ null
+ );
+ ObjEntity entity = context.getEntityResolver().getObjEntity("CompoundPainting");
+ pathProcessor = new ObjPathProcessor(translatorContext, entity, null);
+ }
+
+ @Test
+ public void testSimpleAttributePathTranslation() {
+ PathTranslationResult result = pathProcessor.process("textReview");
+ assertEquals(2, result.getDbAttributes().size());
+ assertEquals(2, result.getAttributePaths().size());
+
+ assertEquals("toPaintingInfo", result.getAttributePaths().get(0));
+ assertEquals("PAINTING_ID", result.getDbAttributes().get(0).getName());
+
+ assertEquals("toPaintingInfo", result.getAttributePaths().get(1));
+ assertEquals("TEXT_REVIEW", result.getDbAttributes().get(1).getName());
+ }
+
+}
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/cayenne/blob/f6b2dac9/cayenne-server/src/test/java/org/apache/cayenne/access/translator/select/OrderingStageTest.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/test/java/org/apache/cayenne/access/translator/select/OrderingStageTest.java b/cayenne-server/src/test/java/org/apache/cayenne/access/translator/select/OrderingStageTest.java
new file mode 100644
index 0000000..6292052
--- /dev/null
+++ b/cayenne-server/src/test/java/org/apache/cayenne/access/translator/select/OrderingStageTest.java
@@ -0,0 +1,110 @@
+/*****************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ ****************************************************************/
+
+package org.apache.cayenne.access.translator.select;
+
+import java.util.Collections;
+
+import org.apache.cayenne.access.sqlbuilder.sqltree.ColumnNode;
+import org.apache.cayenne.access.sqlbuilder.sqltree.EmptyNode;
+import org.apache.cayenne.access.sqlbuilder.sqltree.Node;
+import org.apache.cayenne.access.sqlbuilder.sqltree.OrderByNode;
+import org.apache.cayenne.access.sqlbuilder.sqltree.TextNode;
+import org.apache.cayenne.map.DataMap;
+import org.apache.cayenne.map.DbAttribute;
+import org.apache.cayenne.map.DbEntity;
+import org.apache.cayenne.map.ObjAttribute;
+import org.apache.cayenne.map.ObjEntity;
+import org.apache.cayenne.query.Ordering;
+import org.junit.Before;
+import org.junit.Test;
+
+import static org.hamcrest.CoreMatchers.instanceOf;
+import static org.junit.Assert.*;
+
+/**
+ * @since 4.2
+ */
+public class OrderingStageTest {
+
+ private TranslatorContext context;
+
+ @Before
+ public void prepareContext() {
+ DbEntity dbEntity = new DbEntity();
+ dbEntity.setName("mock");
+ DbAttribute dbAttribute = new DbAttribute();
+ dbAttribute.setName("path");
+ dbEntity.addAttribute(dbAttribute);
+
+ ObjEntity objEntity = new ObjEntity();
+ objEntity.setName("mock");
+ objEntity.setDbEntity(dbEntity);
+
+ ObjAttribute objAttribute = new ObjAttribute();
+ objAttribute.setName("path");
+ objAttribute.setDbAttributePath("path");
+ objEntity.addAttribute(objAttribute);
+
+ DataMap dataMap = new DataMap();
+ dataMap.addObjEntity(objEntity);
+ dataMap.addDbEntity(dbEntity);
+
+ Ordering ordering = new Ordering("path");
+ ordering.setDescending();
+
+ TranslatableQueryWrapper wrapper = new MockQueryWrapperBuilder()
+ .withOrderings(Collections.singleton(ordering))
+ .withMetaData(new MockQueryMetadataBuilder()
+ .withDbEntity(dbEntity)
+ .withObjEntity(objEntity)
+ .build())
+ .build();
+ context = new MockTranslatorContext(wrapper);
+ }
+
+ @Test
+ public void perform() {
+ OrderingStage orderingStage = new OrderingStage();
+ orderingStage.perform(context);
+
+ Node select = context.getSelectBuilder().build();
+
+ // Content of "select" node:
+ //
+ // OrderBy
+ // |
+ // Empty
+ // / \
+ // Column "DESC"
+
+ Node child = select.getChild(0);
+ assertEquals(1, select.getChildrenCount());
+ assertThat(child, instanceOf(OrderByNode.class));
+ assertEquals(1, child.getChildrenCount());
+ assertThat(child.getChild(0), instanceOf(EmptyNode.class));
+ assertEquals(2, child.getChild(0).getChildrenCount());
+ assertThat(child.getChild(0).getChild(0), instanceOf(ColumnNode.class));
+ assertThat(child.getChild(0).getChild(1), instanceOf(TextNode.class));
+
+ ColumnNode columnNode = (ColumnNode)child.getChild(0).getChild(0);
+ assertEquals("path", columnNode.getColumn());
+ assertEquals("Node { DESC}", child.getChild(0).getChild(1).toString());
+ }
+}
\ No newline at end of file
[08/11] cayenne git commit: CAY-2465 New SelectTranslator
implementation
Posted by nt...@apache.org.
http://git-wip-us.apache.org/repos/asf/cayenne/blob/f6b2dac9/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/DefaultSelectTranslator.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/DefaultSelectTranslator.java b/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/DefaultSelectTranslator.java
index 4f1a10c..4107dbe 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/DefaultSelectTranslator.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/DefaultSelectTranslator.java
@@ -16,993 +16,117 @@
* specific language governing permissions and limitations
* under the License.
****************************************************************/
+
package org.apache.cayenne.access.translator.select;
-import org.apache.cayenne.CayenneRuntimeException;
-import org.apache.cayenne.Persistent;
+import java.util.Collections;
+import java.util.Map;
+import java.util.Objects;
+
import org.apache.cayenne.access.jdbc.ColumnDescriptor;
import org.apache.cayenne.access.translator.DbAttributeBinding;
import org.apache.cayenne.dba.DbAdapter;
-import org.apache.cayenne.dba.QuotingStrategy;
-import org.apache.cayenne.dba.TypesMapping;
-import org.apache.cayenne.exp.property.BaseProperty;
-import org.apache.cayenne.exp.Expression;
-import org.apache.cayenne.exp.ExpressionFactory;
-import org.apache.cayenne.exp.TraversalHelper;
-import org.apache.cayenne.exp.parser.ASTAggregateFunctionCall;
-import org.apache.cayenne.exp.parser.ASTDbPath;
-import org.apache.cayenne.map.DataMap;
-import org.apache.cayenne.map.DbAttribute;
-import org.apache.cayenne.map.DbEntity;
-import org.apache.cayenne.map.DbJoin;
-import org.apache.cayenne.map.DbRelationship;
import org.apache.cayenne.map.EntityResolver;
-import org.apache.cayenne.map.JoinType;
import org.apache.cayenne.map.ObjAttribute;
-import org.apache.cayenne.map.ObjEntity;
-import org.apache.cayenne.map.ObjRelationship;
-import org.apache.cayenne.map.PathComponent;
-import org.apache.cayenne.query.PrefetchProcessor;
-import org.apache.cayenne.query.PrefetchSelectQuery;
-import org.apache.cayenne.query.PrefetchTreeNode;
-import org.apache.cayenne.query.Query;
+import org.apache.cayenne.query.ColumnSelect;
+import org.apache.cayenne.query.ObjectSelect;
import org.apache.cayenne.query.SelectQuery;
-import org.apache.cayenne.reflect.ArcProperty;
-import org.apache.cayenne.reflect.AttributeProperty;
-import org.apache.cayenne.reflect.ClassDescriptor;
-import org.apache.cayenne.reflect.PropertyVisitor;
-import org.apache.cayenne.reflect.ToManyProperty;
-import org.apache.cayenne.reflect.ToOneProperty;
-import org.apache.cayenne.util.CayenneMapEntry;
-import org.apache.cayenne.util.EqualsBuilder;
-import org.apache.cayenne.util.HashCodeBuilder;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import java.sql.Types;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.Iterator;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
/**
- * @since 4.0
+ * Default translator of select queries ({@link SelectQuery}, {@link ObjectSelect} and {@link ColumnSelect}).
+ *
+ * @since 4.2
*/
-public class DefaultSelectTranslator extends QueryAssembler implements SelectTranslator {
-
- private static final Logger logger = LoggerFactory.getLogger(SelectTranslator.class);
-
- protected static final int[] UNSUPPORTED_DISTINCT_TYPES = { Types.BLOB, Types.CLOB, Types.NCLOB,
- Types.LONGVARBINARY, Types.LONGVARCHAR, Types.LONGNVARCHAR };
-
- protected static boolean isUnsupportedForDistinct(int type) {
- for (int unsupportedDistinctType : UNSUPPORTED_DISTINCT_TYPES) {
- if (unsupportedDistinctType == type) {
- return true;
- }
- }
-
- return false;
- }
-
- JoinStack joinStack;
- List<ColumnDescriptor> resultColumns;
- Map<ObjAttribute, ColumnDescriptor> attributeOverrides;
- Map<ColumnDescriptor, ObjAttribute> defaultAttributesByColumn;
-
- boolean suppressingDistinct;
-
- /**
- * If set to <code>true</code>, indicates that distinct select query is
- * required no matter what the original query settings where. This flag can
- * be set when joins are created using "to-many" relationships.
- */
- boolean forcingDistinct;
-
- /**
- * Does this SQL have any aggregate function
- */
- boolean haveAggregate;
- Map<ColumnDescriptor, List<DbAttributeBinding>> groupByColumns;
-
- /**
- * Callback for joins creation
- */
- AddJoinListener joinListener;
-
- JointPrefetchChecker jointPrefetchChecker = new JointPrefetchChecker();
-
-
- public DefaultSelectTranslator(Query query, DbAdapter adapter, EntityResolver entityResolver) {
- super(query, adapter, entityResolver);
- }
-
- protected JoinStack getJoinStack() {
- if (joinStack == null) {
- joinStack = createJoinStack();
- }
- return joinStack;
- }
-
- protected JoinStack createJoinStack() {
- return new JoinStack(getAdapter(), this);
- }
-
- @Override
- protected void doTranslate() {
-
- checkLimitAndJointPrefetch();
-
- DataMap dataMap = queryMetadata.getDataMap();
- JoinStack joins = getJoinStack();
-
- QuotingStrategy strategy = getAdapter().getQuotingStrategy();
- forcingDistinct = false;
-
- // build column list
- this.resultColumns = buildResultColumns();
-
- // build qualifier
- QualifierTranslator qualifierTranslator = adapter.getQualifierTranslator(this);
- StringBuilder whereQualifierBuffer = qualifierTranslator.appendPart(new StringBuilder());
-
- // build having qualifier
- Expression havingQualifier = getSelectQuery().getHavingQualifier();
- StringBuilder havingQualifierBuffer = null;
- if(havingQualifier != null) {
- haveAggregate = true;
- QualifierTranslator havingQualifierTranslator = adapter.getQualifierTranslator(this);
- havingQualifierTranslator.setQualifier(havingQualifier);
- havingQualifierBuffer = havingQualifierTranslator.appendPart(new StringBuilder());
- }
-
- if(!haveAggregate && groupByColumns != null) {
- // if no expression with aggregation function found
- // in select columns and there is no having clause
- groupByColumns.clear();
- }
-
- // build ORDER BY
- OrderingTranslator orderingTranslator = new OrderingTranslator(this);
- StringBuilder orderingBuffer = orderingTranslator.appendPart(new StringBuilder());
-
- // assemble
- StringBuilder queryBuf = new StringBuilder();
- queryBuf.append("SELECT ");
-
- // check if DISTINCT is appropriate
- // side effect: "suppressingDistinct" flag may end up being flipped here
- if (forcingDistinct || getSelectQuery().isDistinct()) {
- suppressingDistinct = queryMetadata.isSuppressingDistinct();
- if(!suppressingDistinct) {
- for (ColumnDescriptor column : resultColumns) {
- if (isUnsupportedForDistinct(column.getJdbcType())) {
- suppressingDistinct = true;
- break;
- }
- }
- }
-
- if (!suppressingDistinct) {
- queryBuf.append(buildDistinctStatement()).append(" ");
- }
- }
-
- // convert ColumnDescriptors to column names
- List<String> selectColumnExpList = new ArrayList<>();
- for (ColumnDescriptor column : resultColumns) {
- String fullName;
- if(column.isExpression()) {
- fullName = column.getName();
- } else {
- fullName = strategy.quotedIdentifier(dataMap, column.getNamePrefix(), column.getName());
- }
- selectColumnExpList.add(fullName);
- }
-
- // append any column expressions used in the order by if this query
- // uses the DISTINCT modifier
- if (forcingDistinct || getSelectQuery().isDistinct()) {
- List<String> orderByColumnList = orderingTranslator.getOrderByColumnList();
- for (String orderByColumnExp : orderByColumnList) {
- // Convert to ColumnDescriptors??
- if (!selectColumnExpList.contains(orderByColumnExp)) {
- selectColumnExpList.add(orderByColumnExp);
- }
- }
- }
-
- appendSelectColumns(queryBuf, selectColumnExpList);
-
- // append from clause
- queryBuf.append(" FROM ");
-
- // append tables and joins
- joins.appendRootWithQuoteSqlIdentifiers(queryBuf, getQueryMetadata().getDbEntity());
-
- joins.appendJoins(queryBuf);
- joins.appendQualifier(whereQualifierBuffer, whereQualifierBuffer.length() == 0);
-
- // append qualifier
- if (whereQualifierBuffer.length() > 0) {
- queryBuf.append(" WHERE ");
- queryBuf.append(whereQualifierBuffer);
- }
-
- if(groupByColumns != null && !groupByColumns.isEmpty()) {
- queryBuf.append(" GROUP BY ");
- appendGroupByColumns(queryBuf, groupByColumns);
- }
-
- // append HAVING qualifier
- if(havingQualifierBuffer != null && havingQualifierBuffer.length() > 0) {
- queryBuf.append(" HAVING ");
- queryBuf.append(havingQualifierBuffer);
- }
-
- // append prebuilt ordering
- if (orderingBuffer.length() > 0) {
- queryBuf.append(" ORDER BY ").append(orderingBuffer);
- }
-
- if (!isSuppressingDistinct()) {
- appendLimitAndOffsetClauses(queryBuf);
- }
-
- this.sql = queryBuf.toString();
- }
-
- /**
- * Warn user in case query uses both limit and joint prefetch, as we don't support this combination.
- */
- private void checkLimitAndJointPrefetch() {
- if(queryMetadata.getPrefetchTree() == null) {
- return;
- }
-
- if(queryMetadata.getFetchLimit() == 0 && queryMetadata.getFetchOffset() == 0) {
- return;
- }
-
- if(!jointPrefetchChecker.haveJointNode(queryMetadata.getPrefetchTree())) {
- return;
- }
-
- logger.warn("Query uses both limit and joint prefetch, this most probably will lead to incorrect result. " +
- "Either use disjointById prefetch or get full result set.");
- }
-
- /**
- * Allows subclasses to insert their own dialect of DISTINCT statement to
- * improve performance.
- *
- * @return string representing the DISTINCT statement
- * @since 4.0
- */
- protected String buildDistinctStatement() {
- return "DISTINCT";
- }
-
- /**
- * @since 3.1
- */
- protected void appendSelectColumns(StringBuilder buffer, List<String> selectColumnExpList) {
-
- // append columns (unroll the loop's first element)
- int columnCount = selectColumnExpList.size();
- buffer.append(selectColumnExpList.get(0));
-
- // assume there is at least 1 element
- for (int i = 1; i < columnCount; i++) {
- buffer.append(", ");
- buffer.append(selectColumnExpList.get(i));
- }
- }
-
- /**
- * Append columns to GROUP BY clause
- * @since 4.0
- */
- protected void appendGroupByColumns(StringBuilder buffer, Map<ColumnDescriptor, List<DbAttributeBinding>> groupByColumns) {
- Iterator<Map.Entry<ColumnDescriptor, List<DbAttributeBinding>>> it = groupByColumns.entrySet().iterator();
- Map.Entry<ColumnDescriptor, List<DbAttributeBinding>> entry = it.next();
- appendGroupByColumn(buffer, entry);
- while(it.hasNext()) {
- entry = it.next();
- buffer.append(", ");
- appendGroupByColumn(buffer, entry);
- }
- }
-
- /**
- * Append single column to GROUP BY clause
- * @since 4.0
- */
- protected void appendGroupByColumn(StringBuilder buffer, Map.Entry<ColumnDescriptor, List<DbAttributeBinding>> entry) {
- String fullName;
- if(entry.getKey().isExpression()) {
- fullName = entry.getKey().getDataRowKey();
- } else {
- QuotingStrategy strategy = getAdapter().getQuotingStrategy();
- fullName = strategy.quotedIdentifier(queryMetadata.getDataMap(),
- entry.getKey().getNamePrefix(), entry.getKey().getName());
- }
-
- buffer.append(fullName);
-
- if(entry.getKey().getDataRowKey().equals(entry.getKey().getName())) {
- for (DbAttributeBinding binding : entry.getValue()) {
- addToParamList(binding.getAttribute(), binding.getValue());
- }
- }
- }
-
- /**
- * Handles appending optional limit and offset clauses. This implementation
- * does nothing, deferring to subclasses to define the LIMIT/OFFSET clause
- * syntax.
- *
- * @since 3.0
- */
- protected void appendLimitAndOffsetClauses(StringBuilder buffer) {
-
- }
-
- @Override
- public String getCurrentAlias() {
- return getJoinStack().getCurrentAlias();
- }
-
- /**
- * Returns a list of ColumnDescriptors for the query columns.
- *
- * @since 1.2
- */
- public ColumnDescriptor[] getResultColumns() {
- if (resultColumns == null || resultColumns.isEmpty()) {
- return new ColumnDescriptor[0];
- }
-
- return resultColumns.toArray(new ColumnDescriptor[resultColumns.size()]);
- }
-
- /**
- * Returns a map of ColumnDescriptors keyed by ObjAttribute for columns that
- * may need to be reprocessed manually due to incompatible mappings along
- * the inheritance hierarchy.
- *
- * @since 1.2
- */
- public Map<ObjAttribute, ColumnDescriptor> getAttributeOverrides() {
- if (attributeOverrides != null) {
- return attributeOverrides;
- } else {
- return Collections.emptyMap();
- }
- }
-
- /**
- * Returns true if SelectTranslator determined that a query requiring
- * DISTINCT can't be run with DISTINCT keyword for internal reasons. If this
- * method returns true, DataNode may need to do in-memory distinct
- * filtering.
- *
- * @since 1.1
- */
- public boolean isSuppressingDistinct() {
- return suppressingDistinct;
- }
-
- private SelectQuery<?> getSelectQuery() {
- return (SelectQuery<?>) getQuery();
- }
-
- protected List<ColumnDescriptor> buildResultColumns() {
-
- this.defaultAttributesByColumn = new HashMap<>();
-
- List<ColumnDescriptor> columns = new ArrayList<>();
- SelectQuery<?> query = getSelectQuery();
-
- if(query.getColumns() != null && !query.getColumns().isEmpty()) {
- appendOverriddenColumns(columns, query);
- } else if (query.getRoot() instanceof DbEntity) {
- appendDbEntityColumns(columns, query);
- } else if (getQueryMetadata().getPageSize() > 0) {
- appendIdColumns(columns, queryMetadata.getClassDescriptor().getEntity());
- } else {
- appendQueryColumns(columns, query, queryMetadata.getClassDescriptor(), null);
- }
-
- return columns;
- }
-
- /**
- * If query contains explicit column list, use only them
- */
- <T> List<ColumnDescriptor> appendOverriddenColumns(List<ColumnDescriptor> columns, SelectQuery<T> query) {
- groupByColumns = new HashMap<>();
-
- QualifierTranslator qualifierTranslator = adapter.getQualifierTranslator(this);
- AccumulatingBindingListener bindingListener = new AccumulatingBindingListener();
- final String[] joinTableAliasForProperty = {null};
- // capture last alias for joined table, will use it to resolve object columns
- joinListener = () -> joinTableAliasForProperty[0] = getCurrentAlias();
- setAddBindingListener(bindingListener);
-
- for(BaseProperty<?> property : query.getColumns()) {
- joinTableAliasForProperty[0] = null;
- int expressionType = property.getExpression().getType();
-
- // forbid direct selection of toMany relationships columns
- if(property.getType() != null && (expressionType == Expression.OBJ_PATH || expressionType == Expression.DB_PATH)
- && (Collection.class.isAssignableFrom(property.getType())
- || Map.class.isAssignableFrom(property.getType()))) {
- throw new CayenneRuntimeException("Can't directly select toMany relationship columns. " +
- "Either select it with aggregate functions like count() or with flat() function to select full related objects.");
- }
-
- // evaluate ObjPath with Persistent type as toOne relations and use it as full object
- boolean objectProperty = expressionType == Expression.FULL_OBJECT
- || (property.getType() != null
- && expressionType == Expression.OBJ_PATH
- && Persistent.class.isAssignableFrom(property.getType()));
-
- // Qualifier Translator in case of Object Columns have side effect -
- // it will create required joins, that we catch with listener above.
- // And we force created join alias for all columns of Object we select.
- qualifierTranslator.setQualifier(property.getExpression());
- qualifierTranslator.setForceJoinForRelations(objectProperty);
- StringBuilder builder = qualifierTranslator.appendPart(new StringBuilder());
-
- if(objectProperty) {
- // If we want full object, use appendQueryColumns method, to fully process class descriptor
- List<ColumnDescriptor> classColumns = new ArrayList<>();
- ObjEntity entity = entityResolver.getObjEntity(property.getType());
- if(getQueryMetadata().getPageSize() > 0) {
- appendIdColumns(classColumns, entity);
- } else {
- ClassDescriptor classDescriptor = entityResolver.getClassDescriptor(entity.getName());
- appendQueryColumns(classColumns, query, classDescriptor, joinTableAliasForProperty[0]);
- }
- for(ColumnDescriptor descriptor : classColumns) {
- columns.add(descriptor);
- groupByColumns.put(descriptor, Collections.<DbAttributeBinding>emptyList());
- }
- } else {
- // This property will go as scalar value
- String alias = property.getAlias();
- if (alias != null) {
- builder.append(" AS ").append(alias);
- }
-
- int type = getJdbcTypeForProperty(property);
- ColumnDescriptor descriptor;
- if(property.getType() != null) {
- descriptor = new ColumnDescriptor(builder.toString(), type, property.getType().getCanonicalName());
- } else {
- descriptor = new ColumnDescriptor(builder.toString(), type);
- }
- descriptor.setDataRowKey(alias);
- descriptor.setIsExpression(true);
- columns.add(descriptor);
-
- if (isAggregate(property)) {
- haveAggregate = true;
- } else {
- groupByColumns.put(descriptor, bindingListener.getBindings());
- }
- bindingListener.reset();
- }
- }
-
- setAddBindingListener(null);
- qualifierTranslator.setForceJoinForRelations(false);
- joinListener = null;
-
- return columns;
- }
-
- private int getJdbcTypeForProperty(BaseProperty<?> property) {
- int expressionType = property.getExpression().getType();
- if(expressionType == Expression.OBJ_PATH) {
- // Scan obj path, stop as soon as DbAttribute found
- for (PathComponent<ObjAttribute, ObjRelationship> component :
- getQueryMetadata().getObjEntity().resolvePath(property.getExpression(), getPathAliases())) {
- if(component.getAttribute() != null) {
- Iterator<CayenneMapEntry> dbPathIterator = component.getAttribute().getDbPathIterator();
- while (dbPathIterator.hasNext()) {
- Object pathPart = dbPathIterator.next();
- if (pathPart instanceof DbAttribute) {
- return ((DbAttribute) pathPart).getType();
- }
- }
- }
- }
- } else if(expressionType == Expression.DB_PATH) {
- // Scan db path, stop as soon as DbAttribute found
- for (PathComponent<DbAttribute, DbRelationship> component :
- getQueryMetadata().getDbEntity().resolvePath(property.getExpression(), getPathAliases())) {
- if(component.getAttribute() != null) {
- return component.getAttribute().getType();
- }
- }
- }
- // NOTE: If no attribute found or expression have some other type
- // return JDBC type based on Java type of the property.
- // This can lead to incorrect behavior in case we deal with some custom type
- // backed by ExtendedType with logic based on correct JDBC type provided (luckily not very common case).
- // In general we can't map any meaningful type, as we don't know outcome of the expression in the property.
- // If this ever become a problem correct type can be provided at the query call time, using meta data.
- return TypesMapping.getSqlTypeByJava(property.getType());
- }
-
- private boolean isAggregate(BaseProperty<?> property) {
- final boolean[] isAggregate = new boolean[1];
- Expression exp = property.getExpression();
- exp.traverse(new TraversalHelper() {
- @Override
- public void startNode(Expression node, Expression parentNode) {
- if(node instanceof ASTAggregateFunctionCall) {
- isAggregate[0] = true;
- }
- }
- });
-
- return isAggregate[0];
- }
-
- <T> List<ColumnDescriptor> appendDbEntityColumns(List<ColumnDescriptor> columns, SelectQuery<T> query) {
-
- Set<ColumnTracker> attributes = new HashSet<>();
-
- DbEntity table = getQueryMetadata().getDbEntity();
- for (DbAttribute dba : table.getAttributes()) {
- appendColumn(columns, null, dba, attributes, null);
- }
-
- return columns;
- }
-
- /**
- * Appends columns needed for object SelectQuery to the provided columns
- * list.
- */
- <T> List<ColumnDescriptor> appendQueryColumns(final List<ColumnDescriptor> columns, SelectQuery<T> query, ClassDescriptor descriptor, final String tableAlias) {
-
- final Set<ColumnTracker> attributes = new HashSet<>();
-
- // fetched attributes include attributes that are either:
- //
- // * class properties
- // * PK
- // * FK used in relationship
- // * joined prefetch PK
- ObjEntity oe = descriptor.getEntity();
-
- PropertyVisitor visitor = new PropertyVisitor() {
-
- public boolean visitAttribute(AttributeProperty property) {
- ObjAttribute oa = property.getAttribute();
-
- resetJoinStack();
- Iterator<CayenneMapEntry> dbPathIterator = oa.getDbPathIterator();
- while (dbPathIterator.hasNext()) {
- Object pathPart = dbPathIterator.next();
-
- if (pathPart == null) {
- throw new CayenneRuntimeException("ObjAttribute has no component: %s", oa.getName());
- } else if (pathPart instanceof DbRelationship) {
- DbRelationship rel = (DbRelationship) pathPart;
- dbRelationshipAdded(rel, JoinType.LEFT_OUTER, null);
- } else if (pathPart instanceof DbAttribute) {
- DbAttribute dbAttr = (DbAttribute) pathPart;
-
- appendColumn(columns, oa, dbAttr, attributes, null, tableAlias);
- }
- }
- return true;
- }
-
- public boolean visitToMany(ToManyProperty property) {
- visitRelationship(property);
- return true;
- }
-
- public boolean visitToOne(ToOneProperty property) {
- visitRelationship(property);
-
- // add PKs for flattened tables in flattened path
- ObjRelationship rel = property.getRelationship();
- for(int i=0; i<rel.getDbRelationships().size() - 1; i++) {
- DbRelationship dbRel = rel.getDbRelationships().get(i);
- dbRelationshipAdded(dbRel, JoinType.LEFT_OUTER, null);
-
- // append path PK attributes
- for(DbAttribute dba : dbRel.getTargetEntity().getPrimaryKeys()) {
- appendColumn(columns, null, dba, attributes, dbRel.getName() + '.' + dba.getName());
- }
- }
-
- return true;
- }
-
- private void visitRelationship(ArcProperty property) {
- resetJoinStack();
-
- ObjRelationship rel = property.getRelationship();
- DbRelationship dbRel = rel.getDbRelationships().get(0);
-
- List<DbJoin> joins = dbRel.getJoins();
- for (DbJoin join : joins) {
- DbAttribute src = join.getSource();
- appendColumn(columns, null, src, attributes, null, tableAlias);
- }
- }
- };
-
- descriptor.visitAllProperties(visitor);
-
- // stack should be reset, because all root table attributes go with "t0"
- // table alias
- resetJoinStack();
-
- // add remaining needed attrs from DbEntity
- DbEntity table = oe.getDbEntity();
- for (DbAttribute dba : table.getPrimaryKeys()) {
- appendColumn(columns, null, dba, attributes, null, tableAlias);
- }
-
- // special handling of a disjoint query...
-
- if (query instanceof PrefetchSelectQuery) {
-
- // for each relationship path add PK of the target entity...
- for (String path : ((PrefetchSelectQuery<?>) query).getResultPaths()) {
-
- ASTDbPath pathExp = (ASTDbPath) oe.translateToDbPath(ExpressionFactory.exp(path));
-
- // add joins and find terminating element
-
- resetJoinStack();
-
- PathComponent<DbAttribute, DbRelationship> lastComponent = null;
- for (PathComponent<DbAttribute, DbRelationship> component : table
- .resolvePath(pathExp, getPathAliases())) {
-
- if (component.getRelationship() != null) {
- // do not invoke dbRelationshipAdded(), invoke
- // pushJoin() instead. This is to prevent
- // 'forcingDistinct' flipping to true, that will result
- // in unneeded extra processing and sometimes in invalid
- // results (see CAY-1979). Distinctness of each row is
- // guaranteed by the prefetch query semantics - we
- // include target ID in the result columns
- getJoinStack().pushJoin(component.getRelationship(), component.getJoinType(), null);
- }
-
- lastComponent = component;
- }
-
- // process terminating element
- if (lastComponent != null) {
-
- DbRelationship relationship = lastComponent.getRelationship();
-
- if (relationship != null) {
-
- String labelPrefix = pathExp.getPath();
- DbEntity targetEntity = relationship.getTargetEntity();
-
- for (DbAttribute pk : targetEntity.getPrimaryKeys()) {
-
- // note that we my select a source attribute, but
- // label it as
- // target for simplified snapshot processing
- appendColumn(columns, null, pk, attributes, labelPrefix + '.' + pk.getName());
- }
- }
- }
- }
- }
-
- // handle joint prefetches directly attached to this query...
- if (query.getPrefetchTree() != null) {
- // Set entity name, in case MixedConversionStrategy will be used to select objects from this query
- // Note: all prefetch nodes will point to query root, it is not a problem until select query can't
- // perform some sort of union or sub-queries.
- for(PrefetchTreeNode prefetch : query.getPrefetchTree().getChildren()) {
- prefetch.setEntityName(oe.getName());
- }
-
- for (PrefetchTreeNode prefetch : query.getPrefetchTree().adjacentJointNodes()) {
-
- // for each prefetch add all joins plus columns from the target
- // entity
- Expression prefetchExp = ExpressionFactory.exp(prefetch.getPath());
- ASTDbPath dbPrefetch = (ASTDbPath) oe.translateToDbPath(prefetchExp);
-
- resetJoinStack();
- DbRelationship r = null;
- for (PathComponent<DbAttribute, DbRelationship> component :
- table.resolvePath(dbPrefetch, getPathAliases())) {
- r = component.getRelationship();
- dbRelationshipAdded(r, JoinType.LEFT_OUTER, null);
- }
-
- if (r == null) {
- throw new CayenneRuntimeException("Invalid joint prefetch '%s' for entity: %s"
- , prefetch, oe.getName());
- }
-
- // add columns from the target entity, including those that are matched
- // against the FK of the source entity.
- // This is needed to determine whether optional relationships are null
-
- // go via target OE to make sure that Java types are mapped correctly...
- ObjRelationship targetRel = (ObjRelationship) prefetchExp.evaluate(oe);
- ObjEntity targetEntity = targetRel.getTargetEntity();
-
- String labelPrefix = dbPrefetch.getPath();
-
- PropertyVisitor prefetchVisitor = new PropertyVisitor() {
- public boolean visitAttribute(AttributeProperty property) {
- ObjAttribute oa = property.getAttribute();
- Iterator<CayenneMapEntry> dbPathIterator = oa.getDbPathIterator();
- while (dbPathIterator.hasNext()) {
- Object pathPart = dbPathIterator.next();
-
- if (pathPart == null) {
- throw new CayenneRuntimeException("ObjAttribute has no component: %s", oa.getName());
- } else if (pathPart instanceof DbRelationship) {
- DbRelationship rel = (DbRelationship) pathPart;
- dbRelationshipAdded(rel, JoinType.INNER, null);
- } else if (pathPart instanceof DbAttribute) {
- DbAttribute dbAttr = (DbAttribute) pathPart;
-
- appendColumn(columns, oa, dbAttr, attributes, labelPrefix + '.' + dbAttr.getName());
- }
- }
- return true;
- }
-
- public boolean visitToMany(ToManyProperty property) {
- return true;
- }
-
- public boolean visitToOne(ToOneProperty property) {
- return true;
- }
- };
-
- ClassDescriptor prefetchClassDescriptor = entityResolver.getClassDescriptor(targetEntity.getName());
- prefetchClassDescriptor.visitAllProperties(prefetchVisitor);
-
- // append remaining target attributes such as keys
- DbEntity targetDbEntity = r.getTargetEntity();
- for (DbAttribute attribute : targetDbEntity.getAttributes()) {
- appendColumn(columns, null, attribute, attributes, labelPrefix + '.' + attribute.getName());
- }
- }
- }
-
- return columns;
- }
-
- <T> List<ColumnDescriptor> appendIdColumns(final List<ColumnDescriptor> columns, ObjEntity objEntity) {
-
- Set<ColumnTracker> skipSet = new HashSet<>();
-
- DbEntity dbEntity = objEntity.getDbEntity();
- for (ObjAttribute attribute : objEntity.getPrimaryKeys()) {
-
- // synthetic objattributes can't reliably lookup their DbAttribute,
- // so do it manually..
- DbAttribute dbAttribute = dbEntity.getAttribute(attribute.getDbAttributeName());
- appendColumn(columns, attribute, dbAttribute, skipSet, null);
- }
-
- return columns;
- }
-
- private void appendColumn(List<ColumnDescriptor> columns, ObjAttribute objAttribute, DbAttribute attribute,
- Set<ColumnTracker> skipSet, String label) {
- appendColumn(columns, objAttribute, attribute, skipSet, label, null);
- }
-
- private void appendColumn(List<ColumnDescriptor> columns, ObjAttribute objAttribute, DbAttribute attribute,
- Set<ColumnTracker> skipSet, String label, String alias) {
-
- if(alias == null) {
- alias = getCurrentAlias();
- }
-
- if (skipSet.add(new ColumnTracker(alias, attribute))) {
-
- ColumnDescriptor column = (objAttribute != null) ? new ColumnDescriptor(objAttribute, attribute, alias)
- : new ColumnDescriptor(attribute, alias);
-
- if (label != null) {
- column.setDataRowKey(label);
- }
-
- columns.add(column);
-
- // TODO: andrus, 5/7/2006 - replace 'columns' collection with this map, as it is redundant
- defaultAttributesByColumn.put(column, objAttribute);
- } else if (objAttribute != null) {
-
- // record ObjAttribute override
- for (ColumnDescriptor column : columns) {
- if (attribute.getName().equals(column.getName())) {
-
- if (attributeOverrides == null) {
- attributeOverrides = new HashMap<>();
- }
-
- // kick out the original attribute
- ObjAttribute original = defaultAttributesByColumn.remove(column);
-
- if (original != null) {
- attributeOverrides.put(original, column);
- }
-
- attributeOverrides.put(objAttribute, column);
- column.setJavaClass(Void.TYPE.getName());
-
- break;
- }
- }
- }
- }
-
- /**
- * @since 3.0
- */
- @Override
- public void resetJoinStack() {
- getJoinStack().resetStack();
- }
-
- /**
- * @since 3.0
- */
- @Override
- public void dbRelationshipAdded(DbRelationship relationship, JoinType joinType, String joinSplitAlias) {
- if (relationship.isToMany()) {
- forcingDistinct = true;
- }
-
- getJoinStack().pushJoin(relationship, joinType, joinSplitAlias);
- if(joinListener != null) {
- joinListener.joinAdded();
- }
- }
-
- /**
- * Always returns true.
- */
- @Override
- public boolean supportsTableAliases() {
- return true;
- }
-
- @Override
- public String getAliasForExpression(Expression exp) {
- Collection<BaseProperty<?>> columns = getSelectQuery().getColumns();
- if(columns == null) {
- return null;
- }
- for(BaseProperty<?> property : columns) {
- if(property.getExpression().equals(exp)) {
- return property.getAlias();
- }
- }
-
- return null;
- }
-
- /**
- * @since 4.0
- */
- @Override
- public boolean hasJoins() {
- return joinStack != null && joinStack.size() > 0;
- }
-
- static final class ColumnTracker {
-
- private DbAttribute attribute;
- private String alias;
-
- ColumnTracker(String alias, DbAttribute attribute) {
- this.attribute = attribute;
- this.alias = alias;
- }
-
- @Override
- public boolean equals(Object object) {
- if (!(object instanceof ColumnTracker)) {
- return false;
- }
-
- ColumnTracker other = (ColumnTracker) object;
- return new EqualsBuilder().append(alias, other.alias).append(attribute, other.attribute).isEquals();
- }
-
- @Override
- public int hashCode() {
- return new HashCodeBuilder(31, 5).append(alias).append(attribute).toHashCode();
- }
-
- }
-
- static final class AccumulatingBindingListener implements AddBindingListener {
- private List<DbAttributeBinding> bindings = new ArrayList<>();
-
- @Override
- public void onAdd(DbAttributeBinding binding) {
- bindings.add(binding);
- }
-
- public void reset() {
- bindings.clear();
- }
-
- public List<DbAttributeBinding> getBindings() {
- return new ArrayList<>(bindings);
- }
- }
-
- interface AddJoinListener {
- void joinAdded();
- }
-
- private static class JointPrefetchChecker implements PrefetchProcessor {
- private boolean haveJointNode;
-
- public JointPrefetchChecker() {
- }
-
- public boolean haveJointNode(PrefetchTreeNode prefetchTree) {
- haveJointNode = false;
- prefetchTree.traverse(this);
- return haveJointNode;
- }
-
- @Override
- public boolean startPhantomPrefetch(PrefetchTreeNode node) {
- return true;
- }
-
- @Override
- public boolean startDisjointPrefetch(PrefetchTreeNode node) {
- return true;
- }
-
- @Override
- public boolean startDisjointByIdPrefetch(PrefetchTreeNode prefetchTreeNode) {
- return true;
+public class DefaultSelectTranslator implements SelectTranslator {
+
+ private static final TranslationStage[] TRANSLATION_STAGES = {
+ new ColumnExtractorStage(),
+ new PrefetchNodeStage(),
+ new OrderingStage(),
+ new QualifierTranslationStage(),
+ new HavingTranslationStage(),
+ new GroupByStage(),
+ new DistinctStage(),
+ new LimitOffsetStage(),
+ new ColumnDescriptorStage(),
+ new TableTreeStage(),
+ new SQLGenerationStage()
+ };
+
+ private final TranslatorContext context;
+
+ /**
+ * Constructor for the subquery case
+ */
+ DefaultSelectTranslator(TranslatableQueryWrapper query, TranslatorContext parentContext) {
+ Objects.requireNonNull(query, "Query is null");
+ Objects.requireNonNull(parentContext, "Parent context is null");
+ this.context = new TranslatorContext(query, parentContext.getAdapter(), parentContext.getResolver(), parentContext);
+ // skip SQL translation stage for nested translators, it should be performed by root context only
+ this.context.setSkipSQLGeneration(true);
+ }
+
+ /**
+ * Constructor for the root query case
+ */
+ DefaultSelectTranslator(TranslatableQueryWrapper query, DbAdapter adapter, EntityResolver entityResolver) {
+ Objects.requireNonNull(query, "Query is null");
+ Objects.requireNonNull(adapter, "DbAdapter is null");
+ Objects.requireNonNull(entityResolver, "EntityResolver is null");
+ this.context = new TranslatorContext(query, adapter, entityResolver, null);
+ }
+
+ public DefaultSelectTranslator(SelectQuery<?> query, DbAdapter adapter, EntityResolver entityResolver) {
+ this(new SelectQueryWrapper(query), adapter, entityResolver);
+ }
+
+ public DefaultSelectTranslator(ObjectSelect<?> query, DbAdapter adapter, EntityResolver entityResolver) {
+ this(new ObjectSelectWrapper(query), adapter, entityResolver);
+ }
+
+ public DefaultSelectTranslator(ColumnSelect<?> query, DbAdapter adapter, EntityResolver entityResolver) {
+ this(new ColumnSelectWrapper(query), adapter, entityResolver);
+ }
+
+ TranslatorContext getContext() {
+ return context;
+ }
+
+ void translate() {
+ for(TranslationStage stage : TRANSLATION_STAGES) {
+ stage.perform(context);
}
+ }
+
+ @Override
+ public String getSql() {
+ translate();
+ return context.getFinalSQL();
+ }
+
+ @Override
+ public DbAttributeBinding[] getBindings() {
+ return context.getBindings().toArray(new DbAttributeBinding[0]);
+ }
+
+ @Override
+ public Map<ObjAttribute, ColumnDescriptor> getAttributeOverrides() {
+ return Collections.emptyMap();
+ }
+
+ @Override
+ public ColumnDescriptor[] getResultColumns() {
+ return context.getColumnDescriptors().toArray(new ColumnDescriptor[0]);
+ }
+
+ @Override
+ public boolean isSuppressingDistinct() {
+ return context.isDistinctSuppression();
+ }
+
+ @Override
+ public boolean hasJoins() {
+ return context.getTableCount() > 1;
+ }
- @Override
- public boolean startJointPrefetch(PrefetchTreeNode node) {
- haveJointNode = true;
- return false;
- }
-
- @Override
- public boolean startUnknownPrefetch(PrefetchTreeNode node) {
- return true;
- }
-
- @Override
- public void finishPrefetch(PrefetchTreeNode node) {
- }
- }
}
http://git-wip-us.apache.org/repos/asf/cayenne/blob/f6b2dac9/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/DescriptorColumnExtractor.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/DescriptorColumnExtractor.java b/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/DescriptorColumnExtractor.java
new file mode 100644
index 0000000..5bcff0f
--- /dev/null
+++ b/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/DescriptorColumnExtractor.java
@@ -0,0 +1,140 @@
+/*****************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ ****************************************************************/
+
+package org.apache.cayenne.access.translator.select;
+
+import java.util.HashSet;
+import java.util.Set;
+
+import org.apache.cayenne.access.sqlbuilder.sqltree.Node;
+import org.apache.cayenne.map.DbAttribute;
+import org.apache.cayenne.map.DbEntity;
+import org.apache.cayenne.map.ObjAttribute;
+import org.apache.cayenne.map.ObjRelationship;
+import org.apache.cayenne.reflect.AttributeProperty;
+import org.apache.cayenne.reflect.ClassDescriptor;
+import org.apache.cayenne.reflect.PropertyVisitor;
+import org.apache.cayenne.reflect.ToManyProperty;
+import org.apache.cayenne.reflect.ToOneProperty;
+
+import static org.apache.cayenne.access.sqlbuilder.SQLBuilder.table;
+
+/**
+ * @since 4.2
+ */
+class DescriptorColumnExtractor extends BaseColumnExtractor implements PropertyVisitor {
+
+ private static final String PREFETCH_PREFIX = "p:";
+
+ private final ClassDescriptor descriptor;
+ private final PathTranslator pathTranslator;
+
+ private String prefix;
+ private Set<String> columnTracker = new HashSet<>();
+
+ DescriptorColumnExtractor(TranslatorContext context, ClassDescriptor descriptor) {
+ super(context);
+ this.descriptor = descriptor;
+ this.pathTranslator = context.getPathTranslator();
+ }
+
+ public void extract(String prefix) {
+ this.prefix = prefix;
+ String labelPrefix = prefix;
+ TranslatorContext.DescriptorType type = TranslatorContext.DescriptorType.OTHER;
+ if(prefix != null && prefix.length() > 2 && prefix.startsWith(PREFETCH_PREFIX)) {
+ type = TranslatorContext.DescriptorType.PREFETCH;
+ labelPrefix = prefix.substring(2);
+ } else if(descriptor.getEntity().getDbEntity() == context.getRootDbEntity()) {
+ type = TranslatorContext.DescriptorType.ROOT;
+ }
+ context.markDescriptorStart(type);
+
+ descriptor.visitAllProperties(this);
+
+ // add remaining needed attrs from DbEntity
+ DbEntity table = descriptor.getEntity().getDbEntity();
+ String alias = context.getTableTree().aliasForPath(prefix);
+ for (DbAttribute dba : table.getPrimaryKeys()) {
+ String columnUniqueName = alias + '.' + dba.getName();
+ if(columnTracker.add(columnUniqueName)) {
+ addDbAttribute(prefix, labelPrefix, dba);
+ }
+ }
+ context.markDescriptorEnd(type);
+ }
+
+ @Override
+ public boolean visitAttribute(AttributeProperty property) {
+ ObjAttribute oa = property.getAttribute();
+ PathTranslationResult result = pathTranslator.translatePath(oa.getEntity(), property.getName(), prefix);
+
+ int count = result.getDbAttributes().size();
+ for(int i=0; i<count; i++) {
+ ResultNodeDescriptor resultNodeDescriptor = processTranslationResult(result, i);
+ if(resultNodeDescriptor != null && i == count - 1) {
+ resultNodeDescriptor.setJavaType(oa.getType());
+ }
+ }
+
+ return true;
+ }
+
+ @Override
+ public boolean visitToOne(ToOneProperty property) {
+ ObjRelationship rel = property.getRelationship();
+ PathTranslationResult result = pathTranslator.translatePath(rel.getSourceEntity(), property.getName(), prefix);
+
+ int count = result.getDbAttributes().size();
+ for(int i=0; i<count; i++) {
+ processTranslationResult(result, i);
+ }
+
+ return true;
+ }
+
+ private ResultNodeDescriptor processTranslationResult(PathTranslationResult result, int i) {
+ String path = result.getAttributePaths().get(i);
+ String alias = context.getTableTree().aliasForPath(path);
+ DbAttribute attribute = result.getDbAttributes().get(i);
+
+ String columnUniqueName = alias + '.' + attribute.getName();
+ if(columnTracker.add(columnUniqueName)) {
+ String columnLabelPrefix = path;
+ if(columnLabelPrefix.length() > 2) {
+ if(columnLabelPrefix.startsWith(PREFETCH_PREFIX)) {
+ columnLabelPrefix = columnLabelPrefix.substring(2);
+ }
+ }
+ String attributeName = columnLabelPrefix.isEmpty()
+ ? attribute.getName()
+ : columnLabelPrefix + '.' + attribute.getName();
+
+ Node columnNode = table(alias).column(attribute).build();
+ return context.addResultNode(columnNode, attributeName).setDbAttribute(attribute);
+ }
+
+ return null;
+ }
+
+ @Override
+ public boolean visitToMany(ToManyProperty property) {
+ return true;
+ }
+}
http://git-wip-us.apache.org/repos/asf/cayenne/blob/f6b2dac9/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/DistinctStage.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/DistinctStage.java b/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/DistinctStage.java
new file mode 100644
index 0000000..5a5febc
--- /dev/null
+++ b/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/DistinctStage.java
@@ -0,0 +1,71 @@
+/*****************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ ****************************************************************/
+
+package org.apache.cayenne.access.translator.select;
+
+import java.sql.Types;
+
+/**
+ * @since 4.2
+ */
+class DistinctStage implements TranslationStage {
+
+ private static final int[] UNSUPPORTED_DISTINCT_TYPES = {
+ Types.BLOB,
+ Types.CLOB,
+ Types.NCLOB,
+ Types.LONGVARCHAR,
+ Types.LONGNVARCHAR,
+ Types.LONGVARBINARY
+ };
+
+ static boolean isUnsupportedForDistinct(int type) {
+ for (int unsupportedDistinctType : UNSUPPORTED_DISTINCT_TYPES) {
+ if (unsupportedDistinctType == type) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ @Override
+ public void perform(TranslatorContext context) {
+ // explicit suppressing of distinct
+ if(context.getMetadata().isSuppressingDistinct()) {
+ context.setDistinctSuppression(true);
+ return;
+ }
+
+ // query forcing distinct or query have joins (qualifier or prefetch)
+ if(!context.getQuery().isDistinct() && context.getTableTree().getNodeCount() <= 1) {
+ return;
+ }
+
+ // unsuitable jdbc type for distinct clause
+ for(ResultNodeDescriptor node : context.getResultNodeList()) {
+ // TODO: make it per adapter rather than one-for-all
+ if(isUnsupportedForDistinct(node.getJdbcType())) {
+ context.setDistinctSuppression(true);
+ return;
+ }
+ }
+ context.getSelectBuilder().distinct();
+ }
+}
http://git-wip-us.apache.org/repos/asf/cayenne/blob/f6b2dac9/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/GroupByStage.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/GroupByStage.java b/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/GroupByStage.java
new file mode 100644
index 0000000..db05011
--- /dev/null
+++ b/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/GroupByStage.java
@@ -0,0 +1,54 @@
+/*****************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ ****************************************************************/
+
+package org.apache.cayenne.access.translator.select;
+
+/**
+ * @since 4.2
+ */
+class GroupByStage implements TranslationStage {
+
+ @Override
+ public void perform(TranslatorContext context) {
+ if (!hasAggregate(context)) {
+ return;
+ }
+
+ for(ResultNodeDescriptor resultNode : context.getResultNodeList()) {
+ if(resultNode.isAggregate()) {
+ continue;
+ }
+ context.getSelectBuilder().groupBy(resultNode.getNode().deepCopy());
+ }
+ }
+
+ private boolean hasAggregate(TranslatorContext context) {
+ if(context.getQuery().getHavingQualifier() != null) {
+ return true;
+ }
+
+ for(ResultNodeDescriptor resultNode : context.getResultNodeList()) {
+ if(resultNode.isAggregate()) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+}
http://git-wip-us.apache.org/repos/asf/cayenne/blob/f6b2dac9/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/HavingTranslationStage.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/HavingTranslationStage.java b/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/HavingTranslationStage.java
new file mode 100644
index 0000000..e0fdd8b
--- /dev/null
+++ b/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/HavingTranslationStage.java
@@ -0,0 +1,40 @@
+/*****************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ ****************************************************************/
+
+package org.apache.cayenne.access.translator.select;
+
+import org.apache.cayenne.access.sqlbuilder.sqltree.Node;
+import org.apache.cayenne.exp.Expression;
+
+/**
+ * @since 4.2
+ */
+class HavingTranslationStage implements TranslationStage {
+
+ @Override
+ public void perform(TranslatorContext context) {
+ Expression expression = context.getQuery().getHavingQualifier();
+ QualifierTranslator translator = context.getQualifierTranslator();
+ Node qualifier = translator.translate(expression);
+ if(qualifier != null) {
+ context.getSelectBuilder().having(qualifier);
+ }
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/cayenne/blob/f6b2dac9/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/IdColumnExtractor.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/IdColumnExtractor.java b/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/IdColumnExtractor.java
new file mode 100644
index 0000000..a718a04
--- /dev/null
+++ b/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/IdColumnExtractor.java
@@ -0,0 +1,49 @@
+/*****************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ ****************************************************************/
+
+package org.apache.cayenne.access.translator.select;
+
+import org.apache.cayenne.map.DbAttribute;
+import org.apache.cayenne.map.DbEntity;
+import org.apache.cayenne.map.ObjEntity;
+
+/**
+ * @since 4.2
+ */
+class IdColumnExtractor extends BaseColumnExtractor {
+
+ private final DbEntity dbEntity;
+
+ IdColumnExtractor(TranslatorContext context, DbEntity dbEntity) {
+ super(context);
+ this.dbEntity = dbEntity;
+ }
+
+ IdColumnExtractor(TranslatorContext context, ObjEntity objEntity) {
+ this(context, objEntity.getDbEntity());
+ }
+
+ @Override
+ public void extract(String prefix) {
+ for (DbAttribute dba : dbEntity.getPrimaryKeys()) {
+ addDbAttribute(prefix, prefix, dba);
+ }
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/cayenne/blob/f6b2dac9/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/JoinStack.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/JoinStack.java b/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/JoinStack.java
deleted file mode 100644
index 11b57fc..0000000
--- a/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/JoinStack.java
+++ /dev/null
@@ -1,219 +0,0 @@
-/*****************************************************************
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- ****************************************************************/
-package org.apache.cayenne.access.translator.select;
-
-import java.util.List;
-import java.util.function.Function;
-
-import org.apache.cayenne.dba.DbAdapter;
-import org.apache.cayenne.dba.QuotingStrategy;
-import org.apache.cayenne.exp.Expression;
-import org.apache.cayenne.exp.parser.ASTDbPath;
-import org.apache.cayenne.exp.parser.ASTPath;
-import org.apache.cayenne.map.DbEntity;
-import org.apache.cayenne.map.DbJoin;
-import org.apache.cayenne.map.DbRelationship;
-import org.apache.cayenne.map.Entity;
-import org.apache.cayenne.map.JoinType;
-import org.apache.cayenne.map.ObjEntity;
-
-/**
- * Encapsulates join reuse/split logic used in SelectQuery processing. All
- * expression path's that exist in the query (in the qualifier, etc.) are
- * processed to produce a combined join tree.
- *
- * @since 3.0
- */
-public class JoinStack {
-
- protected JoinTreeNode rootNode;
- protected JoinTreeNode topNode;
- private QuotingStrategy quotingStrategy;
-
- private int aliasCounter;
-
- /**
- * Helper class to process DbEntity qualifiers
- */
- private QualifierTranslator qualifierTranslator;
-
- protected JoinStack(DbAdapter dbAdapter, QueryAssembler assembler) {
- this.rootNode = new JoinTreeNode(this);
- this.rootNode.setTargetTableAlias(newAlias());
-
- this.quotingStrategy = dbAdapter.getQuotingStrategy();
- this.qualifierTranslator = dbAdapter.getQualifierTranslator(assembler);
-
- resetStack();
- }
-
- String getCurrentAlias() {
- return topNode.getTargetTableAlias();
- }
-
- /**
- * Returns the number of configured joins.
- */
- protected int size() {
- // do not count root as a join
- return rootNode.size() - 1;
- }
-
- void appendRootWithQuoteSqlIdentifiers(StringBuilder out, DbEntity rootEntity) {
-
- out.append(quotingStrategy.quotedFullyQualifiedName(rootEntity));
- out.append(' ');
- out.append(quotingStrategy.quotedIdentifier(rootEntity, rootNode.getTargetTableAlias()));
- }
-
- /**
- * Appends all configured joins to the provided output object.
- */
- protected void appendJoins(StringBuilder out) {
-
- // skip root, recursively append its children
- for (JoinTreeNode child : rootNode.getChildren()) {
- appendJoinSubtree(out, child);
- }
- }
-
- protected void appendJoinSubtree(StringBuilder out, JoinTreeNode node) {
-
- DbRelationship relationship = node.getRelationship();
-
- DbEntity targetEntity = relationship.getTargetEntity();
- String srcAlias = node.getSourceTableAlias();
- String targetAlias = node.getTargetTableAlias();
-
- switch (node.getJoinType()) {
- case INNER:
- out.append(" JOIN");
- break;
- case LEFT_OUTER:
- out.append(" LEFT JOIN");
- break;
- default:
- throw new IllegalArgumentException("Unsupported join type: " + node.getJoinType());
- }
-
- out.append(' ');
- out.append(quotingStrategy.quotedFullyQualifiedName(targetEntity));
-
- out.append(' ');
- out.append(quotingStrategy.quotedIdentifier(targetEntity, targetAlias));
- out.append(" ON (");
-
- List<DbJoin> joins = relationship.getJoins();
- int len = joins.size();
- for (int i = 0; i < len; i++) {
- DbJoin join = joins.get(i);
- if (i > 0) {
- out.append(" AND ");
- }
-
- out.append(quotingStrategy.quotedIdentifier(relationship.getSourceEntity(), srcAlias, join.getSourceName()));
- out.append(" = ");
- out.append(quotingStrategy.quotedIdentifier(targetEntity, targetAlias, join.getTargetName()));
- }
-
- /*
- * Attaching root Db entity's qualifier
- */
- Expression dbQualifier = targetEntity.getQualifier();
- if (dbQualifier != null) {
- dbQualifier = dbQualifier.transform(new JoinedDbEntityQualifierTransformer(node));
-
- if (len > 0) {
- out.append(" AND ");
- }
- qualifierTranslator.setOut(out);
- qualifierTranslator.doAppendPart(dbQualifier);
- }
-
- out.append(')');
-
- for (JoinTreeNode child : node.getChildren()) {
- appendJoinSubtree(out, child);
- }
- }
-
- /**
- * Append join information to the qualifier - the part after "WHERE".
- */
- protected void appendQualifier(StringBuilder out, boolean firstQualifierElement) {
- // nothing as standard join is performed before "WHERE"
- }
-
- /**
- * Pops the stack all the way to the root node.
- */
- void resetStack() {
- topNode = rootNode;
- }
-
- /**
- * Finds or creates a JoinTreeNode for the given arguments and sets it as
- * the next current join.
- */
- void pushJoin(DbRelationship relationship, JoinType joinType, String alias) {
- topNode = topNode.findOrCreateChild(relationship, joinType, alias);
- }
-
- protected String newAlias() {
- return "t" + aliasCounter++;
- }
-
- /**
- * Class to translate *joined* DB Entity qualifiers annotation to *current*
- * Obj-entity qualifiers annotation This is done by changing all Obj-paths
- * to concatenated Db-paths to root entity and rejecting all original
- * Db-paths
- */
- class JoinedDbEntityQualifierTransformer implements Function<Object, Object> {
-
- StringBuilder pathToRoot;
-
- JoinedDbEntityQualifierTransformer(JoinTreeNode node) {
- pathToRoot = new StringBuilder();
- while (node != null && node.getRelationship() != null) {
- String relName = node.getRelationship().getName();
-
- /*
- * We must be in the same join as 'node', otherwise incorrect
- * join statement like JOIN t1 ... ON (t0.id=t1.id AND
- * t2.qualifier=0) could be generated
- */
- if (node.getJoinType() == JoinType.LEFT_OUTER) {
- relName += Entity.OUTER_JOIN_INDICATOR;
- }
- relName += ObjEntity.PATH_SEPARATOR;
-
- pathToRoot.insert(0, relName);
- node = node.getParent();
- }
- }
-
- public Object apply(Object input) {
- if (input instanceof ASTPath) {
- return new ASTDbPath(pathToRoot.toString() + ((ASTPath) input).getPath());
- }
- return input;
- }
- }
-}
http://git-wip-us.apache.org/repos/asf/cayenne/blob/f6b2dac9/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/JoinTreeNode.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/JoinTreeNode.java b/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/JoinTreeNode.java
deleted file mode 100644
index fc80fa3..0000000
--- a/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/JoinTreeNode.java
+++ /dev/null
@@ -1,145 +0,0 @@
-/*****************************************************************
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- ****************************************************************/
-package org.apache.cayenne.access.translator.select;
-
-import org.apache.cayenne.map.DbRelationship;
-import org.apache.cayenne.map.JoinType;
-import org.apache.cayenne.util.Util;
-
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.Collections;
-
-/**
- * @since 3.0
- */
-public final class JoinTreeNode {
-
- private String targetTableAlias;
- private String sourceTableAlias;
- private DbRelationship relationship;
- private String alias;
- private JoinType joinType;
- private Collection<JoinTreeNode> children;
- private JoinStack joinProcessor;
-
- /**
- * Parent join
- */
- private JoinTreeNode parent;
-
- JoinTreeNode(JoinStack joinProcessor) {
- this.joinProcessor = joinProcessor;
- }
-
- JoinTreeNode(JoinStack joinProcessor, DbRelationship relationship,
- JoinType joinType, String alias, JoinTreeNode parent) {
- this(joinProcessor);
- this.relationship = relationship;
- this.alias = alias;
- this.joinType = joinType;
- this.parent = parent;
- }
-
- int size() {
- int i = 1;
-
- if (children != null) {
- for (JoinTreeNode child : children) {
- i += child.size();
- }
- }
-
- return i;
- }
-
- public Collection<JoinTreeNode> getChildren() {
- return children != null ? children : Collections.<JoinTreeNode> emptyList();
- }
-
- JoinTreeNode findOrCreateChild(
- DbRelationship relationship,
- JoinType joinType,
- String alias) {
-
- if (children == null) {
- children = new ArrayList<>(4);
- }
- else {
- for (JoinTreeNode child : children) {
-
- if (child.equals(relationship, joinType, alias)) {
- return child;
- }
- }
- }
-
- JoinTreeNode child = new JoinTreeNode(
- joinProcessor,
- relationship,
- joinType,
- alias,
- this);
- child.setSourceTableAlias(this.targetTableAlias);
- child.setTargetTableAlias(joinProcessor.newAlias());
- children.add(child);
- return child;
- }
-
- private boolean equals(DbRelationship relationship, JoinType joinType, String alias) {
- return this.relationship == relationship
- && this.joinType == joinType
- && Util.nullSafeEquals(this.alias, alias);
- }
-
- public String getTargetTableAlias() {
- return targetTableAlias;
- }
-
- void setTargetTableAlias(String targetTableAlias) {
- this.targetTableAlias = targetTableAlias;
- }
-
- public String getSourceTableAlias() {
- return sourceTableAlias;
- }
-
- void setSourceTableAlias(String sourceTableAlias) {
- this.sourceTableAlias = sourceTableAlias;
- }
-
- public DbRelationship getRelationship() {
- return relationship;
- }
-
- String getAlias() {
- return alias;
- }
-
- public JoinType getJoinType() {
- return joinType;
- }
-
- /**
- * @return parent join
- */
- public JoinTreeNode getParent() {
- return parent;
- }
-}
http://git-wip-us.apache.org/repos/asf/cayenne/blob/f6b2dac9/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/LimitOffsetStage.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/LimitOffsetStage.java b/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/LimitOffsetStage.java
new file mode 100644
index 0000000..3d6faa3
--- /dev/null
+++ b/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/LimitOffsetStage.java
@@ -0,0 +1,37 @@
+/*****************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ ****************************************************************/
+
+package org.apache.cayenne.access.translator.select;
+
+/**
+ * @since 4.2
+ */
+class LimitOffsetStage implements TranslationStage {
+
+ @Override
+ public void perform(TranslatorContext context) {
+ if(context.isDistinctSuppression()) {
+ return;
+ }
+ int offset = context.getMetadata().getFetchOffset();
+ int limit = context.getMetadata().getFetchLimit();
+
+ context.getSelectBuilder().limitOffset(limit, offset);
+ }
+}
http://git-wip-us.apache.org/repos/asf/cayenne/blob/f6b2dac9/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/ObjPathProcessor.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/ObjPathProcessor.java b/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/ObjPathProcessor.java
new file mode 100644
index 0000000..111bfc4
--- /dev/null
+++ b/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/ObjPathProcessor.java
@@ -0,0 +1,132 @@
+/*****************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ ****************************************************************/
+
+package org.apache.cayenne.access.translator.select;
+
+import org.apache.cayenne.map.DbRelationship;
+import org.apache.cayenne.map.EmbeddedAttribute;
+import org.apache.cayenne.map.JoinType;
+import org.apache.cayenne.map.ObjAttribute;
+import org.apache.cayenne.map.ObjEntity;
+import org.apache.cayenne.map.ObjRelationship;
+
+/**
+ * @since 4.2
+ */
+class ObjPathProcessor extends PathProcessor<ObjEntity> {
+
+ private ObjAttribute attribute;
+ private EmbeddedAttribute embeddedAttribute;
+
+ ObjPathProcessor(TranslatorContext context, ObjEntity entity, String parentPath) {
+ super(context, entity);
+ if(parentPath != null) {
+ currentDbPath.append(parentPath);
+ }
+ }
+
+ ObjAttribute getAttribute() {
+ return attribute;
+ }
+
+ @Override
+ protected void processNormalAttribute(String next) {
+ attribute = fetchAttribute(next);
+ if(attribute != null) {
+ processAttribute(attribute);
+ return;
+ }
+
+ ObjRelationship relationship = entity.getRelationship(next);
+ if(relationship != null) {
+ processRelationship(relationship);
+ return;
+ }
+
+ throw new IllegalStateException("Unable to resolve path: " + currentDbPath.toString()
+ + " (unknown '" + next + "' component)");
+ }
+
+ @Override
+ protected void processAliasedAttribute(String next, String alias) {
+ ObjRelationship relationship = entity.getRelationship(alias);
+ if(relationship == null) {
+ throw new IllegalStateException("Non-relationship aliased path part: " + alias);
+ }
+
+ processRelationship(relationship);
+ }
+
+ protected ObjAttribute fetchAttribute(String name) {
+ if(embeddedAttribute != null) {
+ ObjAttribute attribute = embeddedAttribute.getAttribute(name);
+ embeddedAttribute = null;
+ return attribute;
+ } else {
+ return entity.getAttribute(name);
+ }
+ }
+
+ protected void processAttribute(ObjAttribute attribute) {
+ if(attribute instanceof EmbeddedAttribute) {
+ embeddedAttribute = (EmbeddedAttribute)attribute;
+ return;
+ }
+
+ PathTranslationResult result = context.getPathTranslator()
+ .translatePath(entity.getDbEntity(), attribute.getDbAttributePath(), currentDbPath.toString());
+ attributes.addAll(result.getDbAttributes());
+ attributePaths.addAll(result.getAttributePaths());
+ relationship = result.getDbRelationship().orElse(relationship);
+ currentDbPath.delete(0, currentDbPath.length());
+ currentDbPath.append(result.getFinalPath());
+ }
+
+ protected void processRelationship(ObjRelationship relationship) {
+ if (lastComponent) {
+ // if this is a last relationship in the path, it needs special handling
+ processRelTermination(relationship);
+ } else {
+ entity = relationship.getTargetEntity();
+ // find and add joins ....
+ int count = relationship.getDbRelationships().size();
+ for (int i=0; i<count; i++) {
+ DbRelationship dbRel = relationship.getDbRelationships().get(i);
+ appendCurrentPath(dbRel.getName());
+ context.getTableTree().addJoinTable(currentDbPath.toString(), dbRel,
+ isOuterJoin ? JoinType.LEFT_OUTER : JoinType.INNER);
+ }
+ }
+ }
+
+ protected void processRelTermination(ObjRelationship rel) {
+ String path = currentAlias != null ? currentAlias : rel.getDbRelationshipPath();
+ if(isOuterJoin) {
+ path += OUTER_JOIN_INDICATOR;
+ }
+ PathTranslationResult result = context.getPathTranslator()
+ .translatePath(entity.getDbEntity(), path, currentDbPath.toString());
+ attributes.addAll(result.getDbAttributes());
+ attributePaths.addAll(result.getAttributePaths());
+ relationship = result.getDbRelationship().orElse(relationship);
+ currentDbPath.delete(0, currentDbPath.length());
+ currentDbPath.append(result.getFinalPath());
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/cayenne/blob/f6b2dac9/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/ObjectSelectWrapper.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/ObjectSelectWrapper.java b/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/ObjectSelectWrapper.java
new file mode 100644
index 0000000..272bf83
--- /dev/null
+++ b/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/ObjectSelectWrapper.java
@@ -0,0 +1,84 @@
+/*****************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ ****************************************************************/
+
+package org.apache.cayenne.access.translator.select;
+
+import java.util.Collection;
+import java.util.Objects;
+
+import org.apache.cayenne.exp.Expression;
+import org.apache.cayenne.exp.property.BaseProperty;
+import org.apache.cayenne.map.EntityResolver;
+import org.apache.cayenne.query.ObjectSelect;
+import org.apache.cayenne.query.Ordering;
+import org.apache.cayenne.query.PrefetchTreeNode;
+import org.apache.cayenne.query.QueryMetadata;
+import org.apache.cayenne.query.Select;
+
+/**
+ * @since 4.2
+ */
+public class ObjectSelectWrapper implements TranslatableQueryWrapper {
+
+ private final ObjectSelect<?> selectQuery;
+
+ public ObjectSelectWrapper(ObjectSelect<?> selectQuery) {
+ this.selectQuery = Objects.requireNonNull(selectQuery);
+ }
+
+ @Override
+ public boolean isDistinct() {
+ return false;
+ }
+
+ @Override
+ public QueryMetadata getMetaData(EntityResolver resolver) {
+ return selectQuery.getMetaData(resolver);
+ }
+
+ @Override
+ public PrefetchTreeNode getPrefetchTree() {
+ return selectQuery.getPrefetches();
+ }
+
+ @Override
+ public Expression getQualifier() {
+ return selectQuery.getWhere();
+ }
+
+ @Override
+ public Collection<Ordering> getOrderings() {
+ return selectQuery.getOrderings();
+ }
+
+ @Override
+ public Collection<BaseProperty<?>> getColumns() {
+ return null;
+ }
+
+ @Override
+ public Expression getHavingQualifier() {
+ return null;
+ }
+
+ @Override
+ public Select<?> unwrap() {
+ return selectQuery;
+ }
+}
http://git-wip-us.apache.org/repos/asf/cayenne/blob/f6b2dac9/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/OrderingStage.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/OrderingStage.java b/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/OrderingStage.java
new file mode 100644
index 0000000..46b4919
--- /dev/null
+++ b/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/OrderingStage.java
@@ -0,0 +1,81 @@
+/*****************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ ****************************************************************/
+
+package org.apache.cayenne.access.translator.select;
+
+import org.apache.cayenne.access.sqlbuilder.NodeBuilder;
+import org.apache.cayenne.access.sqlbuilder.OrderingNodeBuilder;
+import org.apache.cayenne.access.sqlbuilder.sqltree.Node;
+import org.apache.cayenne.exp.Expression;
+import org.apache.cayenne.exp.parser.ASTAggregateFunctionCall;
+import org.apache.cayenne.query.Ordering;
+
+import static org.apache.cayenne.access.sqlbuilder.SQLBuilder.*;
+
+/**
+ * @since 4.2
+ */
+class OrderingStage implements TranslationStage {
+
+ @Override
+ public void perform(TranslatorContext context) {
+ if(context.getQuery().getOrderings() == null) {
+ return;
+ }
+
+ QualifierTranslator qualifierTranslator = context.getQualifierTranslator();
+ for(Ordering ordering : context.getQuery().getOrderings()) {
+ processOrdering(qualifierTranslator, context, ordering);
+ }
+ }
+
+ private void processOrdering(QualifierTranslator qualifierTranslator, TranslatorContext context, Ordering ordering) {
+ Expression exp = ordering.getSortSpec();
+ Node translatedNode = qualifierTranslator.translate(exp);
+
+ NodeBuilder nodeBuilder = node(translatedNode);
+ if(ordering.isCaseInsensitive()) {
+ nodeBuilder = function("UPPER", nodeBuilder);
+ }
+
+ // If query is DISTINCT then we need to add all ORDER BY clauses as result columns
+ if(shouldAddToResult(context, exp)) {
+ // TODO: need to check duplicates?
+ // need UPPER() function here too, as some DB expect exactly the same expression in select and in ordering
+ context.addResultNode(nodeBuilder.build().deepCopy());
+ }
+
+ OrderingNodeBuilder orderingNodeBuilder = order(nodeBuilder);
+ if(ordering.isDescending()) {
+ orderingNodeBuilder.desc();
+ }
+ context.getSelectBuilder().orderBy(orderingNodeBuilder);
+ }
+
+ private boolean shouldAddToResult(TranslatorContext context, Expression exp) {
+ if(context.isDistinctSuppression()) {
+ return false;
+ }
+ if(exp instanceof ASTAggregateFunctionCall) {
+ return false;
+ }
+ return true;
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/cayenne/blob/f6b2dac9/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/OrderingTranslator.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/OrderingTranslator.java b/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/OrderingTranslator.java
deleted file mode 100644
index 59602f0..0000000
--- a/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/OrderingTranslator.java
+++ /dev/null
@@ -1,127 +0,0 @@
-/*****************************************************************
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- ****************************************************************/
-
-package org.apache.cayenne.access.translator.select;
-
-import org.apache.cayenne.CayenneRuntimeException;
-import org.apache.cayenne.exp.Expression;
-import org.apache.cayenne.query.Ordering;
-import org.apache.cayenne.query.Query;
-import org.apache.cayenne.query.SelectQuery;
-
-import java.util.ArrayList;
-import java.util.Iterator;
-import java.util.List;
-
-/**
- * Translates query ordering to SQL.
- */
-public class OrderingTranslator extends QueryAssemblerHelper {
-
- protected List<String> orderByColumnList = new ArrayList<>();
-
- public OrderingTranslator(QueryAssembler queryAssembler) {
- super(queryAssembler);
- }
-
- /**
- * Translates query Ordering list to SQL ORDER BY clause. Ordering list is
- * obtained from <code>queryAssembler</code>'s query object. In a process of
- * building of ORDER BY clause, <code>queryAssembler</code> is notified when
- * a join needs to be added.
- *
- * @since 3.0
- */
- @Override
- protected void doAppendPart() {
-
- Query q = queryAssembler.getQuery();
-
- // only select queries can have ordering...
- if (q == null || !(q instanceof SelectQuery)) {
- return;
- }
-
- Iterator<Ordering> it = ((SelectQuery<?>) q).getOrderings().iterator();
-
- StringBuilder mainBuffer = this.out;
-
- try {
- while (it.hasNext()) {
- Ordering ord = it.next();
-
- // reset buffer to collect SQL for the single column, that we'll
- // be reusing
- this.out = new StringBuilder();
-
- if (ord.isCaseInsensitive()) {
- out.append("UPPER(");
- }
-
- Expression exp = ord.getSortSpec();
-
- if (exp.getType() == Expression.OBJ_PATH) {
- appendObjPath(exp);
- } else if (exp.getType() == Expression.DB_PATH) {
- appendDbPath(exp);
- } else if (exp.getType() == Expression.FUNCTION_CALL) {
- appendFunction(exp);
- } else {
- throw new CayenneRuntimeException("Unsupported ordering expression: %s", exp);
- }
-
- // Close UPPER() modifier
- if (ord.isCaseInsensitive()) {
- out.append(")");
- }
-
- String columnSQL = out.toString();
- mainBuffer.append(columnSQL);
- orderByColumnList.add(columnSQL);
-
- // "ASC" is a noop, omit it from the query
- if (!ord.isAscending()) {
- mainBuffer.append(" DESC");
- }
-
- if (it.hasNext()) {
- mainBuffer.append(", ");
- }
- }
- } finally {
- this.out = mainBuffer;
- }
- }
-
- protected void appendFunction(Expression exp) {
- QualifierTranslator qualifierTranslator = queryAssembler.getAdapter().getQualifierTranslator(queryAssembler);
- qualifierTranslator.setQualifier(exp);
- StringBuilder builder = qualifierTranslator.appendPart(new StringBuilder());
- out.append(builder.toString());
- }
-
- /**
- * Returns the column expressions (not Expressions) used in the order by
- * clause. E.g., in the case of an case-insensitive order by, an element of
- * the list would be <code>UPPER(<column reference>)</code>
- */
- public List<String> getOrderByColumnList() {
- return orderByColumnList;
- }
-}
http://git-wip-us.apache.org/repos/asf/cayenne/blob/f6b2dac9/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/PathComponents.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/PathComponents.java b/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/PathComponents.java
new file mode 100644
index 0000000..0d1d93c
--- /dev/null
+++ b/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/PathComponents.java
@@ -0,0 +1,95 @@
+/*****************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ ****************************************************************/
+
+package org.apache.cayenne.access.translator.select;
+
+/**
+ * @since 4.2
+ */
+class PathComponents {
+
+ private final String path;
+ private String parent;
+ private String[] pathComponents;
+
+ PathComponents(String path) {
+ this.path = path;
+ initPath();
+ }
+
+ int size() {
+ return pathComponents.length;
+ }
+
+ String getLast() {
+ return pathComponents[pathComponents.length - 1];
+ }
+
+ String getParent() {
+ return parent;
+ }
+
+ String[] getAll() {
+ return pathComponents;
+ }
+
+ String getPath() {
+ return path;
+ }
+
+ private void initPath() {
+ int count = 1;
+ int last = 0;
+
+ // quick scan to check for path separator in path
+ for(int i=0; i<path.length(); i++) {
+ if(path.charAt(i) == '.') {
+ count++;
+ last = i;
+ }
+ }
+
+ // fast path, simple path
+ if(count == 1) {
+ pathComponents = new String[]{path};
+ parent = "";
+ return;
+ }
+
+ // two parts path, can be fast too
+ pathComponents = new String[count];
+ parent = path.substring(0, last);
+ if(count == 2) {
+ pathComponents[0] = path.substring(0, last);
+ pathComponents[1] = path.substring(last + 1);
+ return;
+ }
+
+ // additional full scan
+ last = 0;
+ int idx = 0;
+ for(int i=0; i<path.length(); i++) {
+ if(path.charAt(i) == '.') {
+ pathComponents[idx++] = path.substring(last, i);
+ last = i + 1;
+ }
+ }
+ pathComponents[idx] = path.substring(last);
+ }
+}
[06/11] cayenne git commit: CAY-2465 New SelectTranslator
implementation
Posted by nt...@apache.org.
http://git-wip-us.apache.org/repos/asf/cayenne/blob/f6b2dac9/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/TableTree.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/TableTree.java b/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/TableTree.java
new file mode 100644
index 0000000..5082c4e
--- /dev/null
+++ b/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/TableTree.java
@@ -0,0 +1,109 @@
+/*****************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ ****************************************************************/
+
+package org.apache.cayenne.access.translator.select;
+
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+import org.apache.cayenne.CayenneRuntimeException;
+import org.apache.cayenne.map.DbEntity;
+import org.apache.cayenne.map.DbRelationship;
+import org.apache.cayenne.map.JoinType;
+import org.apache.cayenne.util.Util;
+
+/**
+ * @since 4.2
+ */
+class TableTree {
+ /**
+ * Tables mapped by db path it's spawned by.
+ * Can be following:
+ * - query root table
+ * - tables from flattened attributes (including all intermediate tables)
+ * - tables from attributes used in expressions (WHERE, HAVING, ORDER BY)
+ * - tables from prefetches
+ */
+ private final Map<String, TableTreeNode> tableNodes;
+ private final TableTree parentTree;
+
+ private TableTreeNode rootNode;
+
+ private int tableAliasSequence;
+
+ TableTree(DbEntity root, TableTree parentTree) {
+ this.tableAliasSequence = 0;
+ this.parentTree = parentTree;
+ this.tableNodes = new LinkedHashMap<>();
+ this.rootNode = new TableTreeNode(root, nextTableAlias());
+ }
+
+ void addJoinTable(String path, DbRelationship relationship, JoinType joinType) {
+ if (tableNodes.get(path) != null) {
+ return;
+ }
+
+ TableTreeNode node = new TableTreeNode(path, relationship, nextTableAlias(), joinType);
+ tableNodes.put(path, node);
+ }
+
+ String aliasForPath(String attributePath) {
+ if(Util.isEmptyString(attributePath)) {
+ return rootNode.getTableAlias();
+ }
+ TableTreeNode node = tableNodes.get(attributePath);
+ if (node == null) {
+ throw new CayenneRuntimeException("No table for attribute '%s' found", attributePath);
+ }
+ return node.getTableAlias();
+ }
+
+ String aliasForAttributePath(String attributePath) {
+ int lastSeparator = attributePath.lastIndexOf('.');
+ if (lastSeparator == -1) {
+ return rootNode.getTableAlias();
+ }
+ String table = attributePath.substring(0, lastSeparator);
+ return aliasForPath(table);
+ }
+
+ String nextTableAlias() {
+ // delegate actual generation to parent if any
+ if(parentTree != null) {
+ return parentTree.nextTableAlias();
+ }
+ return 't' + String.valueOf(tableAliasSequence++);
+ }
+
+ public int getNodeCount() {
+ return tableNodes.size() + 1;
+ }
+
+ public void visit(TableNodeVisitor visitor) {
+ visitor.visit(rootNode);
+ for(TableTreeNode node : tableNodes.values()) {
+ visitor.visit(node);
+ }
+ }
+
+ @FunctionalInterface
+ interface TableNodeVisitor {
+ void visit(TableTreeNode node);
+ }
+}
http://git-wip-us.apache.org/repos/asf/cayenne/blob/f6b2dac9/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/TableTreeNode.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/TableTreeNode.java b/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/TableTreeNode.java
new file mode 100644
index 0000000..f197b4b
--- /dev/null
+++ b/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/TableTreeNode.java
@@ -0,0 +1,75 @@
+/*****************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ ****************************************************************/
+
+package org.apache.cayenne.access.translator.select;
+
+import org.apache.cayenne.map.DbEntity;
+import org.apache.cayenne.map.DbRelationship;
+import org.apache.cayenne.map.JoinType;
+
+/**
+ * @since 4.2
+ */
+class TableTreeNode {
+
+ // path that spawned this node
+ private final PathComponents attributePath;
+ private final DbEntity entity;
+ private final String tableAlias;
+ private final JoinType joinType;
+
+ // relationship that connects this node with parent (or null if this is root)
+ private final DbRelationship relationship;
+
+ TableTreeNode(DbEntity entity, String tableAlias) {
+ this.attributePath = new PathComponents("");
+ this.entity = entity;
+ this.tableAlias = tableAlias;
+ this.relationship = null;
+ this.joinType = null;
+ }
+
+ TableTreeNode(String path, DbRelationship relationship, String tableAlias, JoinType joinType) {
+ this.attributePath = new PathComponents(path);
+ this.entity = relationship.getTargetEntity();
+ this.tableAlias = tableAlias;
+ this.relationship = relationship;
+ this.joinType = joinType;
+ }
+
+ public PathComponents getAttributePath() {
+ return attributePath;
+ }
+
+ public DbEntity getEntity() {
+ return entity;
+ }
+
+ public String getTableAlias() {
+ return tableAlias;
+ }
+
+ public JoinType getJoinType() {
+ return joinType;
+ }
+
+ public DbRelationship getRelationship() {
+ return relationship;
+ }
+}
http://git-wip-us.apache.org/repos/asf/cayenne/blob/f6b2dac9/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/TableTreeStage.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/TableTreeStage.java b/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/TableTreeStage.java
new file mode 100644
index 0000000..9ad1588
--- /dev/null
+++ b/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/TableTreeStage.java
@@ -0,0 +1,104 @@
+/*****************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ ****************************************************************/
+
+package org.apache.cayenne.access.translator.select;
+
+import java.util.List;
+
+import org.apache.cayenne.access.sqlbuilder.ExpressionNodeBuilder;
+import org.apache.cayenne.access.sqlbuilder.JoinNodeBuilder;
+import org.apache.cayenne.access.sqlbuilder.NodeBuilder;
+import org.apache.cayenne.access.sqlbuilder.sqltree.Node;
+import org.apache.cayenne.exp.Expression;
+import org.apache.cayenne.exp.parser.ASTDbPath;
+import org.apache.cayenne.exp.parser.ASTPath;
+import org.apache.cayenne.map.DbAttribute;
+import org.apache.cayenne.map.DbJoin;
+
+import static org.apache.cayenne.access.sqlbuilder.SQLBuilder.*;
+
+/**
+ * @since 4.2
+ */
+class TableTreeStage implements TranslationStage {
+
+ @Override
+ public void perform(TranslatorContext context) {
+ context.getTableTree().visit(node -> {
+ NodeBuilder tableNode = table(node.getEntity().getFullyQualifiedName()).as(node.getTableAlias());
+ if(node.getRelationship() != null) {
+ tableNode = getJoin(node, tableNode).on(getJoinExpression(context, node));
+ }
+
+ context.getSelectBuilder().from(tableNode);
+ });
+ }
+
+ private JoinNodeBuilder getJoin(TableTreeNode node, NodeBuilder table) {
+ switch (node.getJoinType()) {
+ case INNER:
+ return join(table);
+ case LEFT_OUTER:
+ return leftJoin(table);
+ default:
+ throw new IllegalArgumentException("Unsupported join type: " + node.getJoinType());
+ }
+ }
+
+ private NodeBuilder getJoinExpression(TranslatorContext context, TableTreeNode node) {
+ List<DbJoin> joins = node.getRelationship().getJoins();
+
+ ExpressionNodeBuilder expressionNodeBuilder = null;
+ String sourceAlias = context.getTableTree().aliasForPath(node.getAttributePath().getParent());
+ for (DbJoin dbJoin : joins) {
+ DbAttribute src = dbJoin.getSource();
+ DbAttribute dst = dbJoin.getTarget();
+ ExpressionNodeBuilder joinExp = table(sourceAlias).column(src)
+ .eq(table(node.getTableAlias()).column(dst));
+
+ if (expressionNodeBuilder != null) {
+ expressionNodeBuilder = expressionNodeBuilder.and(joinExp);
+ } else {
+ expressionNodeBuilder = joinExp;
+ }
+ }
+
+ expressionNodeBuilder = attachTargetQualifier(context, node, expressionNodeBuilder);
+
+ return expressionNodeBuilder;
+ }
+
+ private ExpressionNodeBuilder attachTargetQualifier(TranslatorContext context, TableTreeNode node, ExpressionNodeBuilder expressionNodeBuilder) {
+ Expression dbQualifier = node.getRelationship().getTargetEntity().getQualifier();
+ if (dbQualifier != null) {
+ String pathToRoot = node.getAttributePath().getPath();
+ dbQualifier = dbQualifier.transform(input -> input instanceof ASTPath
+ ? new ASTDbPath(pathToRoot + '.' + ((ASTPath) input).getPath())
+ : input
+ );
+ Node translatedQualifier = context.getQualifierTranslator().translate(dbQualifier);
+ if (expressionNodeBuilder != null) {
+ expressionNodeBuilder = expressionNodeBuilder.and(node(translatedQualifier));
+ } else {
+ expressionNodeBuilder = exp(node(translatedQualifier));
+ }
+ }
+ return expressionNodeBuilder;
+ }
+}
http://git-wip-us.apache.org/repos/asf/cayenne/blob/f6b2dac9/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/TranslatableQueryWrapper.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/TranslatableQueryWrapper.java b/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/TranslatableQueryWrapper.java
new file mode 100644
index 0000000..6bd2c41
--- /dev/null
+++ b/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/TranslatableQueryWrapper.java
@@ -0,0 +1,57 @@
+/*****************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ ****************************************************************/
+
+package org.apache.cayenne.access.translator.select;
+
+import java.util.Collection;
+
+import org.apache.cayenne.exp.Expression;
+import org.apache.cayenne.exp.Property;
+import org.apache.cayenne.exp.property.BaseProperty;
+import org.apache.cayenne.map.EntityResolver;
+import org.apache.cayenne.query.Ordering;
+import org.apache.cayenne.query.PrefetchTreeNode;
+import org.apache.cayenne.query.QueryMetadata;
+import org.apache.cayenne.query.Select;
+
+/**
+ * This interface allows transparently use different queries (namely SelectQuery, ObjectSelect and ColumnSelect)
+ * in translator and as subqueries.
+ *
+ * @since 4.2
+ */
+public interface TranslatableQueryWrapper {
+
+ boolean isDistinct();
+
+ QueryMetadata getMetaData(EntityResolver resolver);
+
+ PrefetchTreeNode getPrefetchTree();
+
+ Expression getQualifier();
+
+ Collection<Ordering> getOrderings();
+
+ Collection<BaseProperty<?>> getColumns();
+
+ Expression getHavingQualifier();
+
+ Select<?> unwrap();
+
+}
http://git-wip-us.apache.org/repos/asf/cayenne/blob/f6b2dac9/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/TranslationStage.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/TranslationStage.java b/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/TranslationStage.java
new file mode 100644
index 0000000..2518759
--- /dev/null
+++ b/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/TranslationStage.java
@@ -0,0 +1,27 @@
+/*****************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ ****************************************************************/
+
+package org.apache.cayenne.access.translator.select;
+
+/**
+ * @since 4.2
+ */
+interface TranslationStage {
+ void perform(TranslatorContext context);
+}
http://git-wip-us.apache.org/repos/asf/cayenne/blob/f6b2dac9/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/TranslatorContext.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/TranslatorContext.java b/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/TranslatorContext.java
new file mode 100644
index 0000000..ba03b72
--- /dev/null
+++ b/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/TranslatorContext.java
@@ -0,0 +1,245 @@
+/*****************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ ****************************************************************/
+
+package org.apache.cayenne.access.translator.select;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.LinkedList;
+import java.util.List;
+
+import org.apache.cayenne.access.jdbc.ColumnDescriptor;
+import org.apache.cayenne.access.sqlbuilder.SQLGenerationContext;
+import org.apache.cayenne.access.sqlbuilder.SelectBuilder;
+import org.apache.cayenne.access.sqlbuilder.SQLBuilder;
+import org.apache.cayenne.access.sqlbuilder.sqltree.Node;
+import org.apache.cayenne.access.translator.DbAttributeBinding;
+import org.apache.cayenne.dba.DbAdapter;
+import org.apache.cayenne.dba.QuotingStrategy;
+import org.apache.cayenne.exp.property.BaseProperty;
+import org.apache.cayenne.map.DbEntity;
+import org.apache.cayenne.map.EntityResolver;
+import org.apache.cayenne.query.QueryMetadata;
+
+/**
+ * Context that holds all data necessary for query translation as well as a result of that translation.
+ *
+ * @since 4.2
+ */
+public class TranslatorContext implements SQLGenerationContext {
+
+ private final TableTree tableTree;
+
+ /**
+ * Result columns, can be following:
+ * - root object attributes (including flattened)
+ * - root object additional db attributes (PKs and FKs)
+ * - flattened attributes additional PKs
+ * - prefetched objects attributes and additional db attributes (PKs and FKs)
+ * - order by expressions if query is distinct?
+ */
+ private final List<ColumnDescriptor> columnDescriptors;
+
+
+ /**
+ * Scalar values bindings in order of appearance in final SQL,
+ * may be should be filled by SQL node visitor.
+ * <p>
+ * Can be from expressions encountered in:
+ * - attributes
+ * - order by expressions
+ * - where expression (including qualifiers from all used DbEntities and ObjEntities)
+ */
+ private final Collection<DbAttributeBinding> bindings;
+
+ // Translated query
+ private final TranslatableQueryWrapper query;
+ private final QueryMetadata metadata;
+
+ private final EntityResolver resolver;
+ private final DbAdapter adapter;
+ private final QuotingStrategy quotingStrategy;
+
+ // Select builder that builds final SQL tree
+ private final SelectBuilder selectBuilder;
+ private final QualifierTranslator qualifierTranslator;
+ private final PathTranslator pathTranslator;
+ // Parent context will be not null in case of a nested query
+ private final TranslatorContext parentContext;
+ // List of SQL tree nodes that describe resulting rows of this query
+ private final List<ResultNodeDescriptor> resultNodeList;
+
+ // if true SQL generation stage will be skipped, needed for nested queries translation
+ private boolean skipSQLGeneration;
+ // translated SQL string
+ private String finalSQL;
+ // suppress DISTINCT clause
+ private boolean distinctSuppression;
+
+ // index of a last result node of a root entity
+ private int rootSegmentEnd;
+ // should current entity be linked to root object
+ // (prefetch entity should, while unrelated entity in a column select should't)
+ // this flag can be removed if logic that converts result row into an object tree allow random order of columns if a row.
+ private boolean appendResultToRoot;
+
+ TranslatorContext(TranslatableQueryWrapper query, DbAdapter adapter, EntityResolver resolver, TranslatorContext parentContext) {
+ this.query = query;
+ this.adapter = adapter;
+ this.resolver = resolver;
+ this.metadata = query.getMetaData(resolver);
+ this.parentContext = parentContext;
+ this.tableTree = new TableTree(metadata.getDbEntity(), parentContext == null ? null : parentContext.getTableTree());
+ this.columnDescriptors = new ArrayList<>();
+ this.bindings = new ArrayList<>(4);
+ this.selectBuilder = SQLBuilder.select();
+ this.pathTranslator = new PathTranslator(this);
+ this.qualifierTranslator = new QualifierTranslator(this);
+ this.quotingStrategy = adapter.getQuotingStrategy();
+ this.resultNodeList = new LinkedList<>();
+ }
+
+ /**
+ * Mark start of a new class descriptor, to be able to process result columns properly.
+ * @param type of a descriptor
+ * @see #addResultNode(Node, boolean, BaseProperty, String)
+ */
+ void markDescriptorStart(DescriptorType type) {
+ if(type == DescriptorType.PREFETCH) {
+ appendResultToRoot = true;
+ }
+ }
+
+ void markDescriptorEnd(DescriptorType type) {
+ if(type == DescriptorType.ROOT) {
+ rootSegmentEnd = resultNodeList.size() - 1;
+ } else if(type == DescriptorType.PREFETCH) {
+ appendResultToRoot = false;
+ }
+ }
+
+ SelectBuilder getSelectBuilder() {
+ return selectBuilder;
+ }
+
+ Collection<ColumnDescriptor> getColumnDescriptors() {
+ return columnDescriptors;
+ }
+
+ public Collection<DbAttributeBinding> getBindings() {
+ return bindings;
+ }
+
+ TableTree getTableTree() {
+ return tableTree;
+ }
+
+ QualifierTranslator getQualifierTranslator() {
+ return qualifierTranslator;
+ }
+
+ PathTranslator getPathTranslator() {
+ return pathTranslator;
+ }
+
+ int getTableCount() {
+ return tableTree.getNodeCount();
+ }
+
+ TranslatableQueryWrapper getQuery() {
+ return query;
+ }
+
+ QueryMetadata getMetadata() {
+ return metadata;
+ }
+
+ EntityResolver getResolver() {
+ return resolver;
+ }
+
+ public DbAdapter getAdapter() {
+ return adapter;
+ }
+
+ DbEntity getRootDbEntity() {
+ return metadata.getDbEntity();
+ }
+
+ public QuotingStrategy getQuotingStrategy() {
+ return quotingStrategy;
+ }
+
+ void setDistinctSuppression(boolean distinctSuppression) {
+ this.distinctSuppression = distinctSuppression;
+ }
+
+ boolean isDistinctSuppression() {
+ return distinctSuppression;
+ }
+
+ void setFinalSQL(String SQL) {
+ this.finalSQL = SQL;
+ }
+
+ String getFinalSQL() {
+ return finalSQL;
+ }
+
+ List<ResultNodeDescriptor> getResultNodeList() {
+ return resultNodeList;
+ }
+
+ ResultNodeDescriptor addResultNode(Node node) {
+ return addResultNode(node, false, null, null);
+ }
+
+ ResultNodeDescriptor addResultNode(Node node, String dataRowKey) {
+ return addResultNode(node, true, null, dataRowKey);
+ }
+
+ ResultNodeDescriptor addResultNode(Node node, boolean inDataRow, BaseProperty<?> property, String dataRowKey) {
+ ResultNodeDescriptor resultNode = new ResultNodeDescriptor(node, inDataRow, property, dataRowKey);
+ if(appendResultToRoot) {
+ resultNodeList.add(rootSegmentEnd + 1, resultNode);
+ } else {
+ resultNodeList.add(resultNode);
+ }
+ return resultNode;
+ }
+
+ TranslatorContext getParentContext() {
+ return parentContext;
+ }
+
+ void setSkipSQLGeneration(boolean skipSQLGeneration) {
+ this.skipSQLGeneration = skipSQLGeneration;
+ }
+
+ boolean isSkipSQLGeneration() {
+ return skipSQLGeneration;
+ }
+
+ enum DescriptorType {
+ ROOT,
+ PREFETCH,
+ OTHER
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/cayenne/blob/f6b2dac9/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/TrimmingQualifierTranslator.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/TrimmingQualifierTranslator.java b/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/TrimmingQualifierTranslator.java
deleted file mode 100644
index 31eb2c9..0000000
--- a/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/TrimmingQualifierTranslator.java
+++ /dev/null
@@ -1,92 +0,0 @@
-/*****************************************************************
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- ****************************************************************/
-
-package org.apache.cayenne.access.translator.select;
-
-import java.sql.Types;
-
-import org.apache.cayenne.exp.Expression;
-import org.apache.cayenne.map.DbAttribute;
-
-/**
- * QualifierTranslator that allows translation of qualifiers that perform
- * comparison with CHAR columns. Some databases require trimming the values for
- * this to work.
- *
- */
-public class TrimmingQualifierTranslator extends QualifierTranslator {
-
- protected String trimFunction;
-
- /**
- * Constructor for TrimmingQualifierTranslator.
- */
- public TrimmingQualifierTranslator(QueryAssembler queryAssembler, String trimFunction) {
- super(queryAssembler);
- this.trimFunction = trimFunction;
- }
-
- /**
- * Adds special handling of CHAR columns.
- */
- @Override
- protected void processColumn(DbAttribute dbAttr) {
- if (dbAttr.getType() == Types.CHAR) {
- out.append(trimFunction).append("(");
- super.processColumn(dbAttr);
- out.append(')');
- } else {
- super.processColumn(dbAttr);
- }
- }
-
- /**
- * Adds special handling of CHAR columns.
- */
- @Override
- protected void processColumnWithQuoteSqlIdentifiers(DbAttribute dbAttr, Expression pathExp) {
-
- if (dbAttr.getType() == Types.CHAR) {
- out.append(trimFunction).append("(");
- super.processColumnWithQuoteSqlIdentifiers(dbAttr, pathExp);
- out.append(')');
- } else {
- super.processColumnWithQuoteSqlIdentifiers(dbAttr, pathExp);
- }
- }
-
- /**
- * Returns the trimFunction.
- *
- * @return String
- */
- public String getTrimFunction() {
- return trimFunction;
- }
-
- /**
- * Sets the trimFunction.
- *
- * @param trimFunction
- * The trimFunction to set
- */
- public void setTrimFunction(String trimFunction) {
- this.trimFunction = trimFunction;
- }
-}
http://git-wip-us.apache.org/repos/asf/cayenne/blob/f6b2dac9/cayenne-server/src/main/java/org/apache/cayenne/dba/AutoAdapter.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/dba/AutoAdapter.java b/cayenne-server/src/main/java/org/apache/cayenne/dba/AutoAdapter.java
index 44f1b25..fffd1c1 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/dba/AutoAdapter.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/dba/AutoAdapter.java
@@ -21,10 +21,9 @@ package org.apache.cayenne.dba;
import org.apache.cayenne.CayenneRuntimeException;
import org.apache.cayenne.access.DataNode;
+import org.apache.cayenne.access.sqlbuilder.sqltree.Node;
import org.apache.cayenne.access.translator.ParameterBinding;
import org.apache.cayenne.access.translator.ejbql.EJBQLTranslatorFactory;
-import org.apache.cayenne.access.translator.select.QualifierTranslator;
-import org.apache.cayenne.access.translator.select.QueryAssembler;
import org.apache.cayenne.access.translator.select.SelectTranslator;
import org.apache.cayenne.access.types.ExtendedTypeMap;
import org.apache.cayenne.di.Provider;
@@ -40,6 +39,7 @@ import org.apache.cayenne.query.SelectQuery;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.Collection;
+import java.util.function.Function;
/**
* A DbAdapter that automatically detects the kind of database it is running on
@@ -111,8 +111,8 @@ public class AutoAdapter implements DbAdapter {
}
@Override
- public QualifierTranslator getQualifierTranslator(QueryAssembler queryAssembler) {
- return getAdapter().getQualifierTranslator(queryAssembler);
+ public Function<Node, Node> getSqlTreeProcessor() {
+ return getAdapter().getSqlTreeProcessor();
}
@Override
http://git-wip-us.apache.org/repos/asf/cayenne/blob/f6b2dac9/cayenne-server/src/main/java/org/apache/cayenne/dba/DbAdapter.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/dba/DbAdapter.java b/cayenne-server/src/main/java/org/apache/cayenne/dba/DbAdapter.java
index ae8073e..629ef13 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/dba/DbAdapter.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/dba/DbAdapter.java
@@ -19,10 +19,9 @@
package org.apache.cayenne.dba;
import org.apache.cayenne.access.DataNode;
+import org.apache.cayenne.access.sqlbuilder.sqltree.Node;
import org.apache.cayenne.access.translator.ParameterBinding;
import org.apache.cayenne.access.translator.ejbql.EJBQLTranslatorFactory;
-import org.apache.cayenne.access.translator.select.QualifierTranslator;
-import org.apache.cayenne.access.translator.select.QueryAssembler;
import org.apache.cayenne.access.translator.select.SelectTranslator;
import org.apache.cayenne.access.types.ExtendedTypeMap;
import org.apache.cayenne.map.DbAttribute;
@@ -36,6 +35,7 @@ import org.apache.cayenne.query.SelectQuery;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.Collection;
+import java.util.function.Function;
/**
* A Cayenne extension point that abstracts the differences between specifics of
@@ -60,7 +60,11 @@ public interface DbAdapter {
*/
SelectTranslator getSelectTranslator(SelectQuery<?> query, EntityResolver entityResolver);
- QualifierTranslator getQualifierTranslator(QueryAssembler queryAssembler);
+ /**
+ * @since 4.2
+ * @return {@link Function} that can adjust SQL tree to specific database flavour
+ */
+ Function<Node, Node> getSqlTreeProcessor();
/**
* Returns an instance of SQLAction that should handle the query.
http://git-wip-us.apache.org/repos/asf/cayenne/blob/f6b2dac9/cayenne-server/src/main/java/org/apache/cayenne/dba/DefaultQuotingStrategy.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/dba/DefaultQuotingStrategy.java b/cayenne-server/src/main/java/org/apache/cayenne/dba/DefaultQuotingStrategy.java
index e961f07..f7122ff 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/dba/DefaultQuotingStrategy.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/dba/DefaultQuotingStrategy.java
@@ -18,6 +18,9 @@
****************************************************************/
package org.apache.cayenne.dba;
+import java.io.IOException;
+
+import org.apache.cayenne.CayenneRuntimeException;
import org.apache.cayenne.map.DataMap;
import org.apache.cayenne.map.DbAttribute;
import org.apache.cayenne.map.DbEntity;
@@ -29,8 +32,8 @@ import org.apache.cayenne.map.Entity;
*/
public class DefaultQuotingStrategy implements QuotingStrategy {
- private String endQuote;
- private String startQuote;
+ private final String endQuote;
+ private final String startQuote;
public DefaultQuotingStrategy(String startQuote, String endQuote) {
this.startQuote = startQuote;
@@ -59,37 +62,44 @@ public class DefaultQuotingStrategy implements QuotingStrategy {
return quotedIdentifier(dataMap, join.getTargetName());
}
+ /**
+ * @since 4.2
+ */
@Override
- public String quotedIdentifier(Entity entity, String... identifierParts) {
- return quotedIdentifier(entity.getDataMap(), identifierParts);
+ public void quotedIdentifier(DataMap dataMap, CharSequence identifier, Appendable appender) {
+ if (identifier == null) {
+ return;
+ }
+
+ boolean quoting = dataMap != null && dataMap.isQuotingSQLIdentifiers();
+ try {
+ if (quoting) {
+ appender.append(startQuote).append(identifier).append(endQuote);
+ } else {
+ appender.append(identifier);
+ }
+ } catch (IOException ex) {
+ throw new CayenneRuntimeException("Failed to append quoted identifier", ex);
+ }
}
@Override
public String quotedIdentifier(DataMap dataMap, String... identifierParts) {
-
- String startQuote, endQuote;
-
- if (dataMap != null && dataMap.isQuotingSQLIdentifiers()) {
- startQuote = this.startQuote;
- endQuote = this.endQuote;
- } else {
- startQuote = "";
- endQuote = "";
- }
-
+ boolean quoting = dataMap != null && dataMap.isQuotingSQLIdentifiers();
StringBuilder buffer = new StringBuilder();
for (String part : identifierParts) {
-
if (part == null) {
continue;
}
-
if (buffer.length() > 0) {
buffer.append('.');
}
-
- buffer.append(startQuote).append(part).append(endQuote);
+ if(quoting) {
+ buffer.append(startQuote).append(part).append(endQuote);
+ } else {
+ buffer.append(part);
+ }
}
return buffer.toString();
http://git-wip-us.apache.org/repos/asf/cayenne/blob/f6b2dac9/cayenne-server/src/main/java/org/apache/cayenne/dba/JdbcAdapter.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/dba/JdbcAdapter.java b/cayenne-server/src/main/java/org/apache/cayenne/dba/JdbcAdapter.java
index 4db2b38..db3c5ad 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/dba/JdbcAdapter.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/dba/JdbcAdapter.java
@@ -21,14 +21,13 @@ package org.apache.cayenne.dba;
import org.apache.cayenne.CayenneRuntimeException;
import org.apache.cayenne.access.DataNode;
+import org.apache.cayenne.access.sqlbuilder.sqltree.Node;
import org.apache.cayenne.access.translator.ParameterBinding;
import org.apache.cayenne.access.translator.batch.BatchTranslatorFactory;
import org.apache.cayenne.access.translator.ejbql.EJBQLTranslatorFactory;
import org.apache.cayenne.access.translator.ejbql.JdbcEJBQLTranslatorFactory;
-import org.apache.cayenne.access.translator.select.DefaultSelectTranslator;
-import org.apache.cayenne.access.translator.select.QualifierTranslator;
-import org.apache.cayenne.access.translator.select.QueryAssembler;
import org.apache.cayenne.access.translator.select.SelectTranslator;
+import org.apache.cayenne.access.translator.select.DefaultSelectTranslator;
import org.apache.cayenne.access.types.ExtendedType;
import org.apache.cayenne.access.types.ExtendedTypeFactory;
import org.apache.cayenne.access.types.ExtendedTypeMap;
@@ -52,12 +51,12 @@ import org.apache.cayenne.util.Util;
import java.net.URL;
import java.sql.PreparedStatement;
-import java.sql.SQLException;
import java.sql.Types;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
+import java.util.function.Function;
/**
* A generic DbAdapter implementation. Can be used as a default adapter or as a
@@ -516,16 +515,6 @@ public class JdbcAdapter implements DbAdapter {
}
/**
- * Creates and returns a default implementation of a qualifier translator.
- */
- @Override
- public QualifierTranslator getQualifierTranslator(QueryAssembler queryAssembler) {
- QualifierTranslator translator = new QualifierTranslator(queryAssembler);
- translator.setCaseInsensitive(caseInsensitiveCollations);
- return translator;
- }
-
- /**
* Uses JdbcActionBuilder to create the right action.
*
* @since 1.2
@@ -541,9 +530,13 @@ public class JdbcAdapter implements DbAdapter {
}
@Override
+ public Function<Node, Node> getSqlTreeProcessor() {
+ return Function.identity();
+ }
+
@SuppressWarnings("unchecked")
- public void bindParameter(PreparedStatement statement, ParameterBinding binding)
- throws SQLException, Exception {
+ @Override
+ public void bindParameter(PreparedStatement statement, ParameterBinding binding) throws Exception {
if (binding.getValue() == null) {
statement.setNull(binding.getStatementPosition(), binding.getJdbcType());
http://git-wip-us.apache.org/repos/asf/cayenne/blob/f6b2dac9/cayenne-server/src/main/java/org/apache/cayenne/dba/QuotingStrategy.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/dba/QuotingStrategy.java b/cayenne-server/src/main/java/org/apache/cayenne/dba/QuotingStrategy.java
index a94dd72..4f4dfa3 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/dba/QuotingStrategy.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/dba/QuotingStrategy.java
@@ -31,7 +31,7 @@ public interface QuotingStrategy {
/**
* Builds a fully qualified name from catalog, schema, name parts of
- * DbEntity, inclosing them in quotations according to this strategy
+ * DbEntity, enclosing them in quotations according to this strategy
* algorithm. Analog of "quotedIdentifier(entity.getCatalog(),
* entity.getSchema(), entity.getName())".
*
@@ -58,10 +58,26 @@ public interface QuotingStrategy {
/**
* @since 4.0
*/
- String quotedIdentifier(Entity entity, String... identifierParts);
+ default String quotedIdentifier(Entity entity, String... identifierParts) {
+ return quotedIdentifier(entity.getDataMap(), identifierParts);
+ }
/**
* @since 4.0
*/
String quotedIdentifier(DataMap dataMap, String... identifierParts);
+
+ /**
+ * Append quoted identifier to provided appender
+ * @since 4.2
+ */
+ default void quotedIdentifier(Entity entity, CharSequence identifier, Appendable appender) {
+ quotedIdentifier(entity.getDataMap(), identifier, appender);
+ }
+
+ /**
+ * Append quoted identifier to provided appender
+ * @since 4.2
+ */
+ void quotedIdentifier(DataMap dataMap, CharSequence identifier, Appendable appender);
}
http://git-wip-us.apache.org/repos/asf/cayenne/blob/f6b2dac9/cayenne-server/src/main/java/org/apache/cayenne/dba/db2/DB2ActionBuilder.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/dba/db2/DB2ActionBuilder.java b/cayenne-server/src/main/java/org/apache/cayenne/dba/db2/DB2ActionBuilder.java
index c9a7d8c..f22b8f2 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/dba/db2/DB2ActionBuilder.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/dba/db2/DB2ActionBuilder.java
@@ -22,6 +22,7 @@ import org.apache.cayenne.access.DataNode;
import org.apache.cayenne.dba.JdbcActionBuilder;
import org.apache.cayenne.query.ProcedureQuery;
import org.apache.cayenne.query.SQLAction;
+import org.apache.cayenne.query.SelectQuery;
/**
* @since 3.1
@@ -36,4 +37,9 @@ public class DB2ActionBuilder extends JdbcActionBuilder {
public SQLAction procedureAction(ProcedureQuery query) {
return new DB2ProcedureAction(query, dataNode);
}
+
+ @Override
+ public <T> SQLAction objectSelectAction(SelectQuery<T> query) {
+ return new DB2SelectAction(query, dataNode);
+ }
}
http://git-wip-us.apache.org/repos/asf/cayenne/blob/f6b2dac9/cayenne-server/src/main/java/org/apache/cayenne/dba/db2/DB2Adapter.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/dba/db2/DB2Adapter.java b/cayenne-server/src/main/java/org/apache/cayenne/dba/db2/DB2Adapter.java
index c6df839..d76d62c 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/dba/db2/DB2Adapter.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/dba/db2/DB2Adapter.java
@@ -22,14 +22,14 @@ package org.apache.cayenne.dba.db2;
import java.sql.PreparedStatement;
import java.sql.Types;
import java.util.List;
+import java.util.function.Function;
import org.apache.cayenne.CayenneRuntimeException;
import org.apache.cayenne.access.DataNode;
+import org.apache.cayenne.access.sqlbuilder.sqltree.Node;
import org.apache.cayenne.access.translator.ParameterBinding;
import org.apache.cayenne.access.translator.ejbql.EJBQLTranslatorFactory;
import org.apache.cayenne.access.translator.ejbql.JdbcEJBQLTranslatorFactory;
-import org.apache.cayenne.access.translator.select.QualifierTranslator;
-import org.apache.cayenne.access.translator.select.QueryAssembler;
import org.apache.cayenne.access.types.BooleanType;
import org.apache.cayenne.access.types.ByteArrayType;
import org.apache.cayenne.access.types.CharType;
@@ -145,13 +145,11 @@ public class DB2Adapter extends JdbcAdapter {
}
/**
- * Returns a trimming translator.
+ * @since 4.2
*/
@Override
- public QualifierTranslator getQualifierTranslator(QueryAssembler queryAssembler) {
- QualifierTranslator translator = new DB2QualifierTranslator(queryAssembler, TRIM_FUNCTION);
- translator.setCaseInsensitive(caseInsensitiveCollations);
- return translator;
+ public Function<Node, Node> getSqlTreeProcessor() {
+ return new DB2SQLTreeProcessor();
}
/**
http://git-wip-us.apache.org/repos/asf/cayenne/blob/f6b2dac9/cayenne-server/src/main/java/org/apache/cayenne/dba/db2/DB2QualifierTranslator.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/dba/db2/DB2QualifierTranslator.java b/cayenne-server/src/main/java/org/apache/cayenne/dba/db2/DB2QualifierTranslator.java
deleted file mode 100644
index 99e019f..0000000
--- a/cayenne-server/src/main/java/org/apache/cayenne/dba/db2/DB2QualifierTranslator.java
+++ /dev/null
@@ -1,174 +0,0 @@
-/*****************************************************************
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- ****************************************************************/
-
-package org.apache.cayenne.dba.db2;
-
-import java.io.IOException;
-import java.sql.Types;
-
-import org.apache.cayenne.CayenneRuntimeException;
-import org.apache.cayenne.access.translator.select.QueryAssembler;
-import org.apache.cayenne.access.translator.select.TrimmingQualifierTranslator;
-import org.apache.cayenne.dba.TypesMapping;
-import org.apache.cayenne.exp.Expression;
-import org.apache.cayenne.exp.parser.ASTEqual;
-import org.apache.cayenne.exp.parser.ASTExtract;
-import org.apache.cayenne.exp.parser.ASTFunctionCall;
-import org.apache.cayenne.exp.parser.ASTNotEqual;
-import org.apache.cayenne.exp.parser.SimpleNode;
-import org.apache.cayenne.map.DbAttribute;
-
-public class DB2QualifierTranslator extends TrimmingQualifierTranslator {
-
- public DB2QualifierTranslator(QueryAssembler queryAssembler, String trimFunction) {
- super(queryAssembler, trimFunction);
- }
-
- @Override
- protected void appendLiteralDirect(Object val, DbAttribute attr, Expression parentExpression) throws IOException {
-
- boolean castNeeded = false;
-
- if (parentExpression != null) {
- int type = parentExpression.getType();
-
- castNeeded = attr != null
- && (type == Expression.LIKE || type == Expression.LIKE_IGNORE_CASE || type == Expression.NOT_LIKE || type == Expression.NOT_LIKE_IGNORE_CASE);
- }
-
- if (castNeeded) {
- out.append("CAST (");
- }
-
- super.appendLiteralDirect(val, attr, parentExpression);
-
- if (castNeeded) {
- int jdbcType = attr.getType();
- int len = attr.getMaxLength();
-
- // determine CAST type
-
- // LIKE on CHAR may produce unpredictible results
- // LIKE on LONVARCHAR doesn't seem to be supported
- if (jdbcType == Types.CHAR || jdbcType == Types.LONGVARCHAR) {
- jdbcType = Types.VARCHAR;
-
- // length is required for VARCHAR
- if (len <= 0) {
- len = 254;
- }
- }
-
- out.append(" AS ");
- String[] types = queryAssembler.getAdapter().externalTypesForJdbcType(jdbcType);
-
- if (types == null || types.length == 0) {
- throw new CayenneRuntimeException("Can't find database type for JDBC type '%s'"
- , TypesMapping.getSqlNameByType(jdbcType));
- }
-
- out.append(types[0]);
- if (len > 0 && queryAssembler.getAdapter().typeSupportsLength(jdbcType)) {
- out.append("(");
- out.append(String.valueOf(len));
- out.append(")");
- }
-
- out.append(")");
- }
- }
-
- @Override
- protected void processColumnWithQuoteSqlIdentifiers(DbAttribute dbAttr, Expression pathExp) {
-
- SimpleNode parent = null;
- if (pathExp instanceof SimpleNode) {
- parent = (SimpleNode) ((SimpleNode) pathExp).jjtGetParent();
- }
-
- // problem in db2 : Comparisons between CLOB and CLOB are not supported.
- // we need do it by casting the Clob to VARCHAR.
- if (parent != null && (parent instanceof ASTEqual || parent instanceof ASTNotEqual)
- && dbAttr.getType() == Types.CLOB && parent.getOperandCount() == 2
- && parent.getOperand(1) instanceof String) {
- Integer size = parent.getOperand(1).toString().length() + 1;
-
- out.append("CAST(");
- super.processColumnWithQuoteSqlIdentifiers(dbAttr, pathExp);
- out.append(" AS VARCHAR(" + size + "))");
- } else {
- super.processColumnWithQuoteSqlIdentifiers(dbAttr, pathExp);
- }
- }
-
- /**
- * @since 4.0
- */
- @Override
- protected void appendFunction(ASTFunctionCall functionExpression) {
- if("SUBSTRING".equals(functionExpression.getFunctionName())) {
- out.append("SUBSTR");
- } else if(!"CONCAT".equals(functionExpression.getFunctionName())) {
- super.appendFunction(functionExpression);
- }
- }
-
- /**
- * @since 4.0
- */
- @Override
- protected void appendFunctionArgDivider(ASTFunctionCall functionExpression) {
- if("CONCAT".equals(functionExpression.getFunctionName())) {
- out.append(" || ");
- } else {
- super.appendFunctionArgDivider(functionExpression);
- }
- }
-
- /**
- * @since 4.0
- */
- @Override
- protected void clearLastFunctionArgDivider(ASTFunctionCall functionExpression) {
- if("CONCAT".equals(functionExpression.getFunctionName())) {
- out.delete(out.length() - " || ".length(), out.length());
- } else {
- super.clearLastFunctionArgDivider(functionExpression);
- }
- }
-
- /**
- * @since 4.0
- */
- @Override
- protected void appendExtractFunction(ASTExtract functionExpression) {
- switch (functionExpression.getPart()) {
- case DAY_OF_MONTH:
- out.append("DAY");
- break;
- case DAY_OF_WEEK:
- case DAY_OF_YEAR:
- // db2 variants are without '_'
- out.append(functionExpression.getPart().name().replace("_", ""));
- break;
- default:
- appendFunction(functionExpression);
- }
- }
-}
http://git-wip-us.apache.org/repos/asf/cayenne/blob/f6b2dac9/cayenne-server/src/main/java/org/apache/cayenne/dba/db2/DB2SQLTreeProcessor.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/dba/db2/DB2SQLTreeProcessor.java b/cayenne-server/src/main/java/org/apache/cayenne/dba/db2/DB2SQLTreeProcessor.java
new file mode 100644
index 0000000..7b31a2c
--- /dev/null
+++ b/cayenne-server/src/main/java/org/apache/cayenne/dba/db2/DB2SQLTreeProcessor.java
@@ -0,0 +1,85 @@
+/*****************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ ****************************************************************/
+
+package org.apache.cayenne.dba.db2;
+
+import org.apache.cayenne.access.sqlbuilder.sqltree.ColumnNode;
+import org.apache.cayenne.access.sqlbuilder.sqltree.OpExpressionNode;
+import org.apache.cayenne.access.sqlbuilder.sqltree.FunctionNode;
+import org.apache.cayenne.access.sqlbuilder.sqltree.LimitOffsetNode;
+import org.apache.cayenne.access.sqlbuilder.sqltree.Node;
+import org.apache.cayenne.access.sqlbuilder.sqltree.ValueNode;
+import org.apache.cayenne.access.translator.select.BaseSQLTreeProcessor;
+import org.apache.cayenne.access.sqlbuilder.sqltree.TrimmingColumnNode;
+import org.apache.cayenne.dba.derby.sqltree.DerbyValueNode;
+import org.apache.cayenne.dba.mysql.sqltree.MysqlLimitOffsetNode;
+
+/**
+ * @since 4.2
+ */
+public class DB2SQLTreeProcessor extends BaseSQLTreeProcessor {
+
+
+ @Override
+ protected void onLimitOffsetNode(Node parent, LimitOffsetNode child, int index) {
+ Node replacement = new MysqlLimitOffsetNode(child.getLimit(), child.getOffset());
+ replaceChild(parent, index, replacement, false);
+ }
+
+ @Override
+ protected void onColumnNode(Node parent, ColumnNode child, int index) {
+ TrimmingColumnNode replacement = new TrimmingColumnNode(child);
+ replaceChild(parent, index, replacement);
+ }
+
+ @Override
+ protected void onValueNode(Node parent, ValueNode child, int index) {
+ Node replacement = new DerbyValueNode(child.getValue(), child.isArray(), child.getAttribute());
+ replaceChild(parent, index, replacement, false);
+ }
+
+ @Override
+ protected void onFunctionNode(Node parent, FunctionNode child, int index) {
+ Node replacement = getReplacementForFunction(child);
+ if(replacement != null) {
+ replaceChild(parent, index, replacement);
+ }
+ }
+
+ private Node getReplacementForFunction(FunctionNode child) {
+ switch (child.getFunctionName()) {
+ case "SUBSTRING":
+ return new FunctionNode("SUBSTR", child.getAlias(), true);
+ case "DAY_OF_MONTH":
+ return new FunctionNode("DAY", child.getAlias(), true);
+ case "DAY_OF_WEEK":
+ return new FunctionNode("DAYOFWEEK", child.getAlias(), true);
+ case "DAY_OF_YEAR":
+ return new FunctionNode("DAYOFYEAR", child.getAlias(), true);
+ case "CURRENT_DATE":
+ case "CURRENT_TIME":
+ case "CURRENT_TIMESTAMP":
+ return new FunctionNode(child.getFunctionName(), child.getAlias(), false);
+ case "CONCAT":
+ return new OpExpressionNode("||");
+ }
+ return null;
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/cayenne/blob/f6b2dac9/cayenne-server/src/main/java/org/apache/cayenne/dba/db2/DB2SelectAction.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/dba/db2/DB2SelectAction.java b/cayenne-server/src/main/java/org/apache/cayenne/dba/db2/DB2SelectAction.java
new file mode 100644
index 0000000..d0f8fd1
--- /dev/null
+++ b/cayenne-server/src/main/java/org/apache/cayenne/dba/db2/DB2SelectAction.java
@@ -0,0 +1,39 @@
+/*****************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ ****************************************************************/
+
+package org.apache.cayenne.dba.db2;
+
+import org.apache.cayenne.access.DataNode;
+import org.apache.cayenne.access.jdbc.SelectAction;
+import org.apache.cayenne.query.SelectQuery;
+
+/**
+ * @since 4.1
+ */
+public class DB2SelectAction extends SelectAction {
+
+ public DB2SelectAction(SelectQuery<?> query, DataNode dataNode) {
+ super(query, dataNode);
+ }
+
+ @Override
+ protected int getInMemoryOffset(int queryOffset) {
+ return 0;
+ }
+}
http://git-wip-us.apache.org/repos/asf/cayenne/blob/f6b2dac9/cayenne-server/src/main/java/org/apache/cayenne/dba/derby/DerbyActionBuilder.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/dba/derby/DerbyActionBuilder.java b/cayenne-server/src/main/java/org/apache/cayenne/dba/derby/DerbyActionBuilder.java
new file mode 100644
index 0000000..b0b67ff
--- /dev/null
+++ b/cayenne-server/src/main/java/org/apache/cayenne/dba/derby/DerbyActionBuilder.java
@@ -0,0 +1,40 @@
+/*****************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ ****************************************************************/
+
+package org.apache.cayenne.dba.derby;
+
+import org.apache.cayenne.access.DataNode;
+import org.apache.cayenne.dba.JdbcActionBuilder;
+import org.apache.cayenne.query.SQLAction;
+import org.apache.cayenne.query.SelectQuery;
+
+/**
+ * @since 4.1
+ */
+public class DerbyActionBuilder extends JdbcActionBuilder {
+
+ public DerbyActionBuilder(DataNode dataNode) {
+ super(dataNode);
+ }
+
+ @Override
+ public <T> SQLAction objectSelectAction(SelectQuery<T> query) {
+ return new DerbySelectAction(query, dataNode);
+ }
+}
http://git-wip-us.apache.org/repos/asf/cayenne/blob/f6b2dac9/cayenne-server/src/main/java/org/apache/cayenne/dba/derby/DerbyAdapter.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/dba/derby/DerbyAdapter.java b/cayenne-server/src/main/java/org/apache/cayenne/dba/derby/DerbyAdapter.java
index 7dd23a3..da19f09 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/dba/derby/DerbyAdapter.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/dba/derby/DerbyAdapter.java
@@ -20,11 +20,11 @@
package org.apache.cayenne.dba.derby;
import org.apache.cayenne.CayenneRuntimeException;
+import org.apache.cayenne.access.DataNode;
+import org.apache.cayenne.access.sqlbuilder.sqltree.Node;
import org.apache.cayenne.access.translator.ParameterBinding;
import org.apache.cayenne.access.translator.ejbql.EJBQLTranslatorFactory;
import org.apache.cayenne.access.translator.ejbql.JdbcEJBQLTranslatorFactory;
-import org.apache.cayenne.access.translator.select.QualifierTranslator;
-import org.apache.cayenne.access.translator.select.QueryAssembler;
import org.apache.cayenne.access.types.ByteType;
import org.apache.cayenne.access.types.CharType;
import org.apache.cayenne.access.types.ExtendedType;
@@ -38,13 +38,15 @@ import org.apache.cayenne.dba.JdbcAdapter;
import org.apache.cayenne.dba.PkGenerator;
import org.apache.cayenne.di.Inject;
import org.apache.cayenne.map.DbAttribute;
-import org.apache.cayenne.map.DbEntity;
+import org.apache.cayenne.query.Query;
+import org.apache.cayenne.query.SQLAction;
import org.apache.cayenne.resource.ResourceLocator;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.sql.Types;
import java.util.List;
+import java.util.function.Function;
/**
* DbAdapter implementation for the <a href="http://db.apache.org/derby/"> Derby RDBMS
@@ -91,6 +93,11 @@ public class DerbyAdapter extends JdbcAdapter {
return new DerbyPkGenerator(this);
}
+ @Override
+ public SQLAction getAction(Query query, DataNode node) {
+ return query.createSQLAction(new DerbyActionBuilder(node));
+ }
+
/**
* Installs appropriate ExtendedTypes as converters for passing values between JDBC
* and Java layers.
@@ -163,15 +170,11 @@ public class DerbyAdapter extends JdbcAdapter {
}
/**
- * Returns a trimming translator.
+ * @since 4.2
*/
@Override
- public QualifierTranslator getQualifierTranslator(QueryAssembler queryAssembler) {
- QualifierTranslator translator = new DerbyQualifierTranslator(
- queryAssembler,
- "RTRIM");
- translator.setCaseInsensitive(caseInsensitiveCollations);
- return translator;
+ public Function<Node, Node> getSqlTreeProcessor() {
+ return new DerbySQLTreeProcessor();
}
/**
http://git-wip-us.apache.org/repos/asf/cayenne/blob/f6b2dac9/cayenne-server/src/main/java/org/apache/cayenne/dba/derby/DerbyQualifierTranslator.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/dba/derby/DerbyQualifierTranslator.java b/cayenne-server/src/main/java/org/apache/cayenne/dba/derby/DerbyQualifierTranslator.java
deleted file mode 100644
index 4b4cc51..0000000
--- a/cayenne-server/src/main/java/org/apache/cayenne/dba/derby/DerbyQualifierTranslator.java
+++ /dev/null
@@ -1,141 +0,0 @@
-/*****************************************************************
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- ****************************************************************/
-package org.apache.cayenne.dba.derby;
-
-import java.io.IOException;
-import java.sql.Types;
-
-import org.apache.cayenne.CayenneRuntimeException;
-import org.apache.cayenne.access.translator.select.QueryAssembler;
-import org.apache.cayenne.access.translator.select.TrimmingQualifierTranslator;
-import org.apache.cayenne.exp.Expression;
-import org.apache.cayenne.exp.parser.ASTEqual;
-import org.apache.cayenne.exp.parser.ASTExtract;
-import org.apache.cayenne.exp.parser.ASTFunctionCall;
-import org.apache.cayenne.exp.parser.ASTNotEqual;
-import org.apache.cayenne.exp.parser.SimpleNode;
-import org.apache.cayenne.map.DbAttribute;
-
-public class DerbyQualifierTranslator extends TrimmingQualifierTranslator {
-
- public DerbyQualifierTranslator(QueryAssembler queryAssembler, String trimFunction) {
- super(queryAssembler, trimFunction);
- }
-
- @Override
- protected void processColumnWithQuoteSqlIdentifiers(DbAttribute dbAttr, Expression pathExp) {
-
- SimpleNode parent = null;
- if (pathExp instanceof SimpleNode) {
- parent = (SimpleNode) ((SimpleNode) pathExp).jjtGetParent();
- }
-
- // problem in derby : Comparisons between 'CLOB (UCS_BASIC)' and 'CLOB
- // (UCS_BASIC)' are not supported.
- // we need do it by casting the Clob to VARCHAR.
- if (parent != null && (parent instanceof ASTEqual || parent instanceof ASTNotEqual)
- && dbAttr.getType() == Types.CLOB && parent.getOperandCount() == 2
- && parent.getOperand(1) instanceof String) {
- Integer size = parent.getOperand(1).toString().length() + 1;
-
- out.append("CAST(");
- super.processColumnWithQuoteSqlIdentifiers(dbAttr, pathExp);
- out.append(" AS VARCHAR(").append(size).append("))");
- } else {
- super.processColumnWithQuoteSqlIdentifiers(dbAttr, pathExp);
- }
- }
-
- /**
- * @since 4.0
- */
- @Override
- protected void appendFunction(ASTFunctionCall functionExpression) {
- if("SUBSTRING".equals(functionExpression.getFunctionName())) {
- out.append("SUBSTR");
- } else if("CONCAT".equals(functionExpression.getFunctionName())) {
- out.append("");
- } else {
- super.appendFunction(functionExpression);
- }
- }
-
- /**
- * A little bit ugly code that wraps String scalars to CAST(? AS VARCHAR(length))
- * because otherwise derby don't know what type will be at the placeholder and
- * use LONG VARCHAR that isn't comparable what leads to statement preparation failure.
- *
- * @since 4.0
- */
- protected void appendFunctionArg(Object value, ASTFunctionCall functionExpression) throws IOException {
- if("CONCAT".equals(functionExpression.getFunctionName())) {
- if(value instanceof String) {
- out.append("CAST(");
- }
- super.appendFunctionArg(value, functionExpression);
- if(value instanceof String) {
- clearLastFunctionArgDivider(functionExpression);
- out.append(" AS VARCHAR(").append(((String)value).length()).append("))");
- appendFunctionArgDivider(functionExpression);
- }
- } else {
- super.appendFunctionArg(value, functionExpression);
- }
- }
-
- /**
- * @since 4.0
- */
- @Override
- protected void appendFunctionArgDivider(ASTFunctionCall functionExpression) {
- if("CONCAT".equals(functionExpression.getFunctionName())) {
- out.append(" || ");
- } else {
- super.appendFunctionArgDivider(functionExpression);
- }
- }
-
- /**
- * @since 4.0
- */
- @Override
- protected void clearLastFunctionArgDivider(ASTFunctionCall functionExpression) {
- if("CONCAT".equals(functionExpression.getFunctionName())) {
- out.delete(out.length() - 4, out.length());
- } else {
- super.clearLastFunctionArgDivider(functionExpression);
- }
- }
-
- @Override
- protected void appendExtractFunction(ASTExtract functionExpression) {
- switch (functionExpression.getPart()) {
- case DAY_OF_MONTH:
- out.append("DAY");
- break;
- case DAY_OF_WEEK:
- case DAY_OF_YEAR:
- case WEEK:
- throw new CayenneRuntimeException("Function %s() is unsupported in Derby."
- , functionExpression.getPartCamelCaseName());
- default:
- super.appendExtractFunction(functionExpression);
- }
- }
-}
http://git-wip-us.apache.org/repos/asf/cayenne/blob/f6b2dac9/cayenne-server/src/main/java/org/apache/cayenne/dba/derby/DerbySQLTreeProcessor.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/dba/derby/DerbySQLTreeProcessor.java b/cayenne-server/src/main/java/org/apache/cayenne/dba/derby/DerbySQLTreeProcessor.java
new file mode 100644
index 0000000..b00212c
--- /dev/null
+++ b/cayenne-server/src/main/java/org/apache/cayenne/dba/derby/DerbySQLTreeProcessor.java
@@ -0,0 +1,81 @@
+/*****************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ ****************************************************************/
+
+package org.apache.cayenne.dba.derby;
+
+import org.apache.cayenne.CayenneRuntimeException;
+import org.apache.cayenne.access.sqlbuilder.sqltree.ColumnNode;
+import org.apache.cayenne.access.sqlbuilder.sqltree.FunctionNode;
+import org.apache.cayenne.access.sqlbuilder.sqltree.LimitOffsetNode;
+import org.apache.cayenne.access.sqlbuilder.sqltree.Node;
+import org.apache.cayenne.access.sqlbuilder.sqltree.OffsetFetchNextNode;
+import org.apache.cayenne.access.sqlbuilder.sqltree.OpExpressionNode;
+import org.apache.cayenne.access.sqlbuilder.sqltree.ValueNode;
+import org.apache.cayenne.access.translator.select.BaseSQLTreeProcessor;
+import org.apache.cayenne.access.sqlbuilder.sqltree.TrimmingColumnNode;
+import org.apache.cayenne.dba.derby.sqltree.DerbyValueNode;
+
+/**
+ * @since 4.2
+ */
+public class DerbySQLTreeProcessor extends BaseSQLTreeProcessor {
+
+ @Override
+ protected void onLimitOffsetNode(Node parent, LimitOffsetNode child, int index) {
+ replaceChild(parent, index, new OffsetFetchNextNode(child), false);
+ }
+
+ @Override
+ protected void onColumnNode(Node parent, ColumnNode child, int index) {
+ replaceChild(parent, index, new TrimmingColumnNode(child));
+ }
+
+ @Override
+ protected void onValueNode(Node parent, ValueNode child, int index) {
+ replaceChild(parent, index, new DerbyValueNode(child.getValue(), child.isArray(), child.getAttribute()));
+ }
+
+ @Override
+ protected void onFunctionNode(Node parent, FunctionNode child, int index) {
+ Node replacement = getReplacementForFunction(child);
+ if(replacement != null) {
+ replaceChild(parent, index, replacement);
+ }
+ }
+
+ private Node getReplacementForFunction(FunctionNode child) {
+ switch (child.getFunctionName()) {
+ case "SUBSTRING":
+ return new FunctionNode("SUBSTR", child.getAlias(), true);
+ case "DAY_OF_MONTH":
+ return new FunctionNode("DAY", child.getAlias(), true);
+ case "WEEK":
+ case "DAY_OF_WEEK":
+ case "DAY_OF_YEAR":
+ throw new CayenneRuntimeException("Function %s() is unsupported in Derby.", child.getFunctionName());
+ case "CURRENT_DATE":
+ case "CURRENT_TIME":
+ case "CURRENT_TIMESTAMP":
+ return new FunctionNode(child.getFunctionName(), child.getAlias(), false);
+ case "CONCAT":
+ return new OpExpressionNode("||");
+ }
+ return null;
+ }
+}
http://git-wip-us.apache.org/repos/asf/cayenne/blob/f6b2dac9/cayenne-server/src/main/java/org/apache/cayenne/dba/derby/DerbySelectAction.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/dba/derby/DerbySelectAction.java b/cayenne-server/src/main/java/org/apache/cayenne/dba/derby/DerbySelectAction.java
new file mode 100644
index 0000000..6a1c921
--- /dev/null
+++ b/cayenne-server/src/main/java/org/apache/cayenne/dba/derby/DerbySelectAction.java
@@ -0,0 +1,39 @@
+/*****************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ ****************************************************************/
+
+package org.apache.cayenne.dba.derby;
+
+import org.apache.cayenne.access.DataNode;
+import org.apache.cayenne.access.jdbc.SelectAction;
+import org.apache.cayenne.query.SelectQuery;
+
+/**
+ * @since 4.1
+ */
+public class DerbySelectAction extends SelectAction {
+
+ public DerbySelectAction(SelectQuery<?> query, DataNode dataNode) {
+ super(query, dataNode);
+ }
+
+ @Override
+ protected int getInMemoryOffset(int queryOffset) {
+ return 0;
+ }
+}
http://git-wip-us.apache.org/repos/asf/cayenne/blob/f6b2dac9/cayenne-server/src/main/java/org/apache/cayenne/dba/derby/sqltree/DerbyValueNode.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/dba/derby/sqltree/DerbyValueNode.java b/cayenne-server/src/main/java/org/apache/cayenne/dba/derby/sqltree/DerbyValueNode.java
new file mode 100644
index 0000000..cb8348e
--- /dev/null
+++ b/cayenne-server/src/main/java/org/apache/cayenne/dba/derby/sqltree/DerbyValueNode.java
@@ -0,0 +1,51 @@
+/*****************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ ****************************************************************/
+
+package org.apache.cayenne.dba.derby.sqltree;
+
+import java.sql.Types;
+
+import org.apache.cayenne.access.sqlbuilder.QuotingAppendable;
+import org.apache.cayenne.access.sqlbuilder.sqltree.Node;
+import org.apache.cayenne.access.sqlbuilder.sqltree.ValueNode;
+import org.apache.cayenne.map.DbAttribute;
+
+/**
+ * @since 4.2
+ */
+public class DerbyValueNode extends ValueNode {
+
+ public DerbyValueNode(Object value, boolean isArray, DbAttribute attribute) {
+ super(value, isArray, attribute);
+ }
+
+ protected void appendStringValue(QuotingAppendable buffer, CharSequence value) {
+ if(getAttribute() == null || (getAttribute() != null && getAttribute().getType() == Types.CLOB)) {
+ buffer.append(" CAST(? AS VARCHAR(").append(value.length()).append("))");
+ } else {
+ buffer.append(" ?");
+ }
+ addValueBinding(buffer, value);
+ }
+
+ @Override
+ public Node copy() {
+ return new DerbyValueNode(getValue(), isArray(), getAttribute());
+ }
+}
http://git-wip-us.apache.org/repos/asf/cayenne/blob/f6b2dac9/cayenne-server/src/main/java/org/apache/cayenne/dba/firebird/FirebirdActionBuilder.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/dba/firebird/FirebirdActionBuilder.java b/cayenne-server/src/main/java/org/apache/cayenne/dba/firebird/FirebirdActionBuilder.java
new file mode 100644
index 0000000..92f606c
--- /dev/null
+++ b/cayenne-server/src/main/java/org/apache/cayenne/dba/firebird/FirebirdActionBuilder.java
@@ -0,0 +1,41 @@
+/*****************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ ****************************************************************/
+
+package org.apache.cayenne.dba.firebird;
+
+import org.apache.cayenne.access.DataNode;
+import org.apache.cayenne.dba.JdbcActionBuilder;
+import org.apache.cayenne.query.SQLAction;
+import org.apache.cayenne.query.SelectQuery;
+
+/**
+ * @since 4.1
+ */
+public class FirebirdActionBuilder extends JdbcActionBuilder {
+
+ public FirebirdActionBuilder(DataNode dataNode) {
+ super(dataNode);
+ }
+
+
+ @Override
+ public <T> SQLAction objectSelectAction(SelectQuery<T> query) {
+ return new FirebirdSelectAction(query, dataNode);
+ }
+}
http://git-wip-us.apache.org/repos/asf/cayenne/blob/f6b2dac9/cayenne-server/src/main/java/org/apache/cayenne/dba/firebird/FirebirdAdapter.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/dba/firebird/FirebirdAdapter.java b/cayenne-server/src/main/java/org/apache/cayenne/dba/firebird/FirebirdAdapter.java
index a878be2..8a5e0b0 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/dba/firebird/FirebirdAdapter.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/dba/firebird/FirebirdAdapter.java
@@ -20,9 +20,9 @@
package org.apache.cayenne.dba.firebird;
import org.apache.cayenne.CayenneRuntimeException;
+import org.apache.cayenne.access.DataNode;
+import org.apache.cayenne.access.sqlbuilder.sqltree.Node;
import org.apache.cayenne.access.translator.ejbql.EJBQLTranslatorFactory;
-import org.apache.cayenne.access.translator.select.QualifierTranslator;
-import org.apache.cayenne.access.translator.select.QueryAssembler;
import org.apache.cayenne.access.types.ByteArrayType;
import org.apache.cayenne.access.types.CharType;
import org.apache.cayenne.access.types.ExtendedType;
@@ -34,9 +34,12 @@ import org.apache.cayenne.configuration.RuntimeProperties;
import org.apache.cayenne.dba.JdbcAdapter;
import org.apache.cayenne.di.Inject;
import org.apache.cayenne.map.DbAttribute;
+import org.apache.cayenne.query.Query;
+import org.apache.cayenne.query.SQLAction;
import org.apache.cayenne.resource.ResourceLocator;
import java.util.List;
+import java.util.function.Function;
/**
* DbAdapter implementation for <a href="http://www.firebirdsql.org">FirebirdSQL
@@ -105,14 +108,22 @@ public class FirebirdAdapter extends JdbcAdapter {
sqlBuffer.append(column.isMandatory() ? " NOT NULL" : "");
}
-
+
+ /**
+ * @since 4.2
+ */
@Override
- public QualifierTranslator getQualifierTranslator(QueryAssembler queryAssembler) {
- return new FirebirdQualifierTranslator(queryAssembler);
+ public Function<Node, Node> getSqlTreeProcessor() {
+ return new FirebirdSQLTreeProcessor();
}
@Override
public EJBQLTranslatorFactory getEjbqlTranslatorFactory() {
return new FirebirdEJBQLTranslatorFactory();
}
+
+ @Override
+ public SQLAction getAction(Query query, DataNode node) {
+ return query.createSQLAction(new FirebirdActionBuilder(node));
+ }
}
http://git-wip-us.apache.org/repos/asf/cayenne/blob/f6b2dac9/cayenne-server/src/main/java/org/apache/cayenne/dba/firebird/FirebirdQualifierTranslator.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/dba/firebird/FirebirdQualifierTranslator.java b/cayenne-server/src/main/java/org/apache/cayenne/dba/firebird/FirebirdQualifierTranslator.java
deleted file mode 100644
index eae42f7..0000000
--- a/cayenne-server/src/main/java/org/apache/cayenne/dba/firebird/FirebirdQualifierTranslator.java
+++ /dev/null
@@ -1,181 +0,0 @@
-/*****************************************************************
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- ****************************************************************/
-
-package org.apache.cayenne.dba.firebird;
-
-import org.apache.cayenne.access.translator.select.QualifierTranslator;
-import org.apache.cayenne.access.translator.select.QueryAssembler;
-import org.apache.cayenne.dba.oracle.OracleQualifierTranslator;
-import org.apache.cayenne.exp.Expression;
-import org.apache.cayenne.exp.parser.ASTExtract;
-import org.apache.cayenne.exp.parser.ASTFunctionCall;
-
-import java.io.IOException;
-
-public class FirebirdQualifierTranslator extends QualifierTranslator {
-
- private int substringArg = 0;
-
- public FirebirdQualifierTranslator(QueryAssembler queryAssembler) {
- super(queryAssembler);
- }
-
- @Override
- protected void doAppendPart(Expression rootNode) {
- if (rootNode == null) {
- return;
- }
- // IN statements with more than 1500 values are denied in Firebird
- // so we need to split one big statement on few smaller ones
- rootNode = rootNode.transform(new OracleQualifierTranslator.INTrimmer());
- rootNode.traverse(this);
- }
-
- /**
- * @since 4.0
- */
- @Override
- protected void appendFunction(ASTFunctionCall functionExpression) {
- switch (functionExpression.getFunctionName()) {
- case "CONCAT":
- // noop
- break;
- case "LENGTH":
- out.append("CHAR_LENGTH");
- break;
- case "LOCATE":
- out.append("POSITION");
- break;
- case "SUBSTRING":
- substringArg = 0;
- default:
- super.appendFunction(functionExpression);
- }
- }
-
- /**
- * A little bit ugly code that wraps String scalars to CAST(? AS VARCHAR(length))
- * because otherwise derby don't know what type will be at the placeholder and
- * use LONG VARCHAR that isn't comparable what leads to statement preparation failure.
- *
- * @since 4.0
- */
- protected void appendFunctionArg(Object value, ASTFunctionCall functionExpression) throws IOException {
- if("CONCAT".equals(functionExpression.getFunctionName())) {
- if(value instanceof String) {
- out.append("CAST(");
- }
- super.appendFunctionArg(value, functionExpression);
- if(value instanceof String) {
- clearLastFunctionArgDivider(functionExpression);
- out.append(" AS VARCHAR(").append(((String)value).length()).append("))");
- appendFunctionArgDivider(functionExpression);
- }
- } else if("SUBSTRING".equals(functionExpression.getFunctionName())) {
- out.append("CAST(");
- super.appendFunctionArg(value, functionExpression);
- clearLastFunctionArgDivider(functionExpression);
- substringArg--;
- out.append(" AS INTEGER)");
- appendFunctionArgDivider(functionExpression);
- } else {
- super.appendFunctionArg(value, functionExpression);
- }
- }
-
- /**
- * @since 4.0
- */
- @Override
- protected void appendFunctionArgDivider(ASTFunctionCall functionExpression) {
- switch (functionExpression.getFunctionName()) {
- case "CONCAT":
- out.append(" || ");
- break;
- case "SUBSTRING":
- switch (substringArg++) {
- case 0:
- out.append(" FROM ");
- break;
- case 1:
- out.append(" FOR ");
- break;
- }
- break;
- default:
- super.appendFunctionArgDivider(functionExpression);
- }
- }
-
- /**
- * @since 4.0
- */
- @Override
- protected void clearLastFunctionArgDivider(ASTFunctionCall functionExpression) {
- switch (functionExpression.getFunctionName()) {
- case "CONCAT":
- out.delete(out.length() - 4, out.length());
- break;
- case "SUBSTRING":
- if(substringArg == 1) {
- out.delete(out.length() - " FROM ".length(), out.length());
- } else if(substringArg == 2) {
- out.delete(out.length() - " FOR ".length(), out.length());
- }
- break;
- default:
- super.clearLastFunctionArgDivider(functionExpression);
- }
- if(functionExpression instanceof ASTExtract) {
- out.append(")");
- }
- }
-
- @Override
- protected boolean parenthesisNeeded(Expression node, Expression parentNode) {
- if (node.getType() == Expression.FUNCTION_CALL) {
- if (node instanceof ASTExtract) {
- return false;
- }
- }
-
- return super.parenthesisNeeded(node, parentNode);
- }
-
-
- @Override
- protected void appendExtractFunction(ASTExtract functionExpression) {
- out.append("EXTRACT(");
- switch (functionExpression.getPart()) {
- case DAY_OF_MONTH:
- out.append("DAY");
- break;
- case DAY_OF_WEEK:
- out.append("WEEKDAY");
- break;
- case DAY_OF_YEAR:
- out.append("YEARDAY");
- break;
- default:
- out.append(functionExpression.getPartCamelCaseName());
- }
-
- out.append(" FROM ");
- }
-}