diff --git a/RELEASE-NOTES.txt b/RELEASE-NOTES.txt index 9062574576..55962f5cf1 100644 --- a/RELEASE-NOTES.txt +++ b/RELEASE-NOTES.txt @@ -21,6 +21,7 @@ CAY-2897 Add no-op default implementations to the GraphChangeHandler interface CAY-2905 Upgrade Gradle to 8.14 CAY-2908 Review and upgrade dependencies CAY-2916 SelectById: unify logic for null/empty values +CAY-2917 Add Qualifier Joins Before Depenent Table Joins Bug Fixes: diff --git a/cayenne/src/main/java/org/apache/cayenne/access/translator/select/DefaultSelectTranslator.java b/cayenne/src/main/java/org/apache/cayenne/access/translator/select/DefaultSelectTranslator.java index e0cc7c1d87..d221fb029f 100644 --- a/cayenne/src/main/java/org/apache/cayenne/access/translator/select/DefaultSelectTranslator.java +++ b/cayenne/src/main/java/org/apache/cayenne/access/translator/select/DefaultSelectTranslator.java @@ -38,10 +38,10 @@ public class DefaultSelectTranslator implements SelectTranslator { private static final TranslationStage[] TRANSLATION_STAGES = { + new QualifierTranslationStage(), new ColumnExtractorStage(), new PrefetchNodeStage(), new OrderingStage(), - new QualifierTranslationStage(), new HavingTranslationStage(), new OrderingGroupByStage(), new GroupByStage(), diff --git a/cayenne/src/test/java/org/apache/cayenne/access/translator/select/DefaultSelectTranslatorIT.java b/cayenne/src/test/java/org/apache/cayenne/access/translator/select/DefaultSelectTranslatorIT.java index 4bd1d92e7c..e9cc08a503 100644 --- a/cayenne/src/test/java/org/apache/cayenne/access/translator/select/DefaultSelectTranslatorIT.java +++ b/cayenne/src/test/java/org/apache/cayenne/access/translator/select/DefaultSelectTranslatorIT.java @@ -615,50 +615,50 @@ public void testCreateSqlStringWithQuoteSqlIdentifiers3() throws Exception { assertTrue(s, artistId > 0 && artistId < iFrom); int dateOfBirth = s.indexOf(charStart + "t0" + charEnd + "." + charStart + "DATE_OF_BIRTH" + charEnd); assertTrue(s, dateOfBirth > 0 && dateOfBirth < iFrom); - int estimatedPrice = s.indexOf(charStart + "t1" + charEnd + "." + charStart + "ESTIMATED_PRICE" + charEnd); + int estimatedPrice = s.indexOf(charStart + "t2" + charEnd + "." + charStart + "ESTIMATED_PRICE" + charEnd); assertTrue(s, estimatedPrice > 0 && estimatedPrice < iFrom); - int paintingDescription = s.indexOf(charStart + "t1" + charEnd + "." + charStart + "PAINTING_DESCRIPTION" + int paintingDescription = s.indexOf(charStart + "t2" + charEnd + "." + charStart + "PAINTING_DESCRIPTION" + charEnd); assertTrue(s, paintingDescription > 0 && paintingDescription < iFrom); - int paintingTitle = s.indexOf(charStart + "t1" + charEnd + "." + charStart + "PAINTING_TITLE" + charEnd); + int paintingTitle = s.indexOf(charStart + "t2" + charEnd + "." + charStart + "PAINTING_TITLE" + charEnd); assertTrue(s, paintingTitle > 0 && paintingTitle < iFrom); - int artistIdT1 = s.indexOf(charStart + "t1" + charEnd + "." + charStart + "ARTIST_ID" + charEnd); - assertTrue(s, artistIdT1 > 0 && artistIdT1 < iFrom); - int galleryId = s.indexOf(charStart + "t1" + charEnd + "." + charStart + "GALLERY_ID" + charEnd); + int artistIdT2 = s.indexOf(charStart + "t2" + charEnd + "." + charStart + "ARTIST_ID" + charEnd); + assertTrue(s, artistIdT2 > 0 && artistIdT2 < iFrom); + int galleryId = s.indexOf(charStart + "t2" + charEnd + "." + charStart + "GALLERY_ID" + charEnd); assertTrue(s, galleryId > 0 && galleryId < iFrom); - int paintingId = s.indexOf(charStart + "t1" + charEnd + "." + charStart + "PAINTING_ID" + charEnd); + int paintingId = s.indexOf(charStart + "t2" + charEnd + "." + charStart + "PAINTING_ID" + charEnd); assertTrue(s, paintingId > 0 && paintingId < iFrom); int iArtist = s.indexOf(charStart + "ARTIST" + charEnd + " " + charStart + "t0" + charEnd); assertTrue(s, iArtist > iFrom); - int iLeftJoin = s.indexOf("LEFT JOIN"); - assertTrue(s, iLeftJoin > iFrom); - int iPainting = s.indexOf(charStart + "PAINTING" + charEnd + " " + charStart + "t1" + charEnd); - assertTrue(s, iPainting > iLeftJoin); - int iOn = s.indexOf(" ON "); - assertTrue(s, iOn > iLeftJoin); - int iArtistId = s.indexOf(charStart + "t0" + charEnd + "." + charStart + "ARTIST_ID" + charEnd, iLeftJoin); - assertTrue(s, iArtistId > iOn); - int iArtistIdT1 = s - .indexOf(charStart + "t1" + charEnd + "." + charStart + "ARTIST_ID" + charEnd, iLeftJoin); - assertTrue(s, iArtistIdT1 > iOn); - int i = s.indexOf("=", iLeftJoin); - assertTrue(s, iArtistIdT1 > i || iArtistId > i); int iJoin = s.indexOf("JOIN"); - assertTrue(s, iJoin > iLeftJoin); - int iPainting2 = s.indexOf(charStart + "PAINTING" + charEnd + " " + charStart + "t2" + charEnd); + assertTrue(s, iJoin > iFrom); + int iPainting2 = s.indexOf(charStart + "PAINTING" + charEnd + " " + charStart + "t1" + charEnd); assertTrue(s, iPainting2 > iJoin); - int iOn2 = s.indexOf(" ON "); + int iOn2 = s.indexOf(" ON ", iJoin); assertTrue(s, iOn2 > iJoin); int iArtistId2 = s.indexOf(charStart + "t0" + charEnd + "." + charStart + "ARTIST_ID" + charEnd, iJoin); assertTrue(s, iArtistId2 > iOn2); - int iArtistId2T2 = s.indexOf(charStart + "t2" + charEnd + "." + charStart + "ARTIST_ID" + charEnd, iJoin); - assertTrue(s, iArtistId2T2 > iOn2); + int iArtistId2T1 = s.indexOf(charStart + "t1" + charEnd + "." + charStart + "ARTIST_ID" + charEnd, iJoin); + assertTrue(s, iArtistId2T1 > iOn2); int i2 = s.indexOf("=", iJoin); - assertTrue(s, iArtistId2T2 > i2 || iArtistId2 > i2); + assertTrue(s, iArtistId2T1 > i2 || iArtistId2 > i2); + int iLeftJoin = s.indexOf("LEFT JOIN"); + assertTrue(s, iLeftJoin > iJoin); + int iPainting = s.indexOf(charStart + "PAINTING" + charEnd + " " + charStart + "t2" + charEnd); + assertTrue(s, iPainting > iLeftJoin); + int iOn = s.indexOf(" ON ", iLeftJoin); + assertTrue(s, iOn > iLeftJoin); + int iArtistId = s.indexOf(charStart + "t0" + charEnd + "." + charStart + "ARTIST_ID" + charEnd, iLeftJoin); + assertTrue(s, iArtistId > iOn); + int iArtistIdT2 = s + .indexOf(charStart + "t2" + charEnd + "." + charStart + "ARTIST_ID" + charEnd, iLeftJoin); + assertTrue(s, iArtistIdT2 > iOn); + int i = s.indexOf("=", iLeftJoin); + assertTrue(s, iArtistIdT2 > i || iArtistId > i); int iWhere = s.indexOf(" WHERE "); - assertTrue(s, iWhere > iJoin); + assertTrue(s, iWhere > iLeftJoin); - int paintingTitle2 = s.indexOf(charStart + "t2" + charEnd + "." + charStart + "PAINTING_TITLE" + charEnd + " = ?"); + int paintingTitle2 = s.indexOf(charStart + "t1" + charEnd + "." + charStart + "PAINTING_TITLE" + charEnd + " = ?"); assertTrue(s, paintingTitle2 > iWhere); } finally { diff --git a/cayenne/src/test/java/org/apache/cayenne/access/translator/select/VerticalInheritanceJoinOrderIT.java b/cayenne/src/test/java/org/apache/cayenne/access/translator/select/VerticalInheritanceJoinOrderIT.java new file mode 100644 index 0000000000..aecb6ba764 --- /dev/null +++ b/cayenne/src/test/java/org/apache/cayenne/access/translator/select/VerticalInheritanceJoinOrderIT.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 + * + * https://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.DataNode; +import org.apache.cayenne.dba.DbAdapter; +import org.apache.cayenne.di.Inject; +import org.apache.cayenne.map.EntityResolver; +import org.apache.cayenne.query.ObjectSelect; +import org.apache.cayenne.testdo.inheritance_vertical.IvBase; +import org.apache.cayenne.testdo.inheritance_vertical.IvOther; +import org.apache.cayenne.unit.di.runtime.CayenneProjects; +import org.apache.cayenne.unit.di.runtime.RuntimeCase; +import org.apache.cayenne.unit.di.runtime.UseCayenneRuntime; +import org.junit.Test; + +import static org.junit.Assert.*; + +/** + * @since 5.0 + */ +@UseCayenneRuntime(CayenneProjects.INHERITANCE_VERTICAL_PROJECT) +public class VerticalInheritanceJoinOrderIT extends RuntimeCase { + + @Inject + private DataNode dataNode; + + /** + * Tests that INNER joins from WHERE clause relationships are added before + * LEFT OUTER joins from vertical inheritance child tables in the generated SQL. + */ + @Test + public void testQualifierJoinsBeforeDependentTableLeftJoins() { + ObjectSelect q = ObjectSelect.query(IvBase.class) + .where(IvBase.OTHERS.dot(IvOther.NAME).eq("test")) + .and(IvBase.OTHERS.dot(IvOther.BASE).dot(IvBase.NAME).eq("test2")); + + String sql = translateToSql(q); + + assertTrue("Expected INNER JOIN to IV_OTHER, got: " + sql, sql.contains("JOIN IV_OTHER")); + assertTrue("Expected LEFT JOIN to IV_IMPL, got: " + sql, sql.contains("LEFT JOIN IV_IMPL")); + + int iLeftJoin = sql.indexOf("LEFT JOIN IV_IMPL"); + int iInnerJoinOther = sql.indexOf("JOIN IV_OTHER"); + assertTrue("INNER JOIN to IV_OTHER should precede LEFT JOIN, got: " + sql, + iInnerJoinOther < iLeftJoin); + + int iInnerJoinBase = sql.indexOf("JOIN IV_BASE", iInnerJoinOther); + if (iInnerJoinBase > 0) { + assertTrue("INNER JOIN to IV_BASE should precede LEFT JOIN, got: " + sql, + iInnerJoinBase < iLeftJoin); + } + } + + private String translateToSql(ObjectSelect query) { + DbAdapter adapter = dataNode.getAdapter(); + EntityResolver resolver = dataNode.getEntityResolver(); + return new DefaultSelectTranslator(query, adapter, resolver).getSql(); + } +}