From 03a8f98c0c75fe884140773fc6334afa76a99140 Mon Sep 17 00:00:00 2001 From: Jens Schauder Date: Tue, 19 Sep 2023 13:15:56 +0200 Subject: [PATCH] Support Single Query Loading for aggregates with more than one collection. Closes #1448 --- ...SingleQueryFallbackDataAccessStrategy.java | 13 +- ...JdbcAggregateTemplateIntegrationTests.java | 133 ++++++++++++++++++ ...cAggregateTemplateIntegrationTests-db2.sql | 33 ++++- ...bcAggregateTemplateIntegrationTests-h2.sql | 28 +++- ...AggregateTemplateIntegrationTests-hsql.sql | 28 +++- ...regateTemplateIntegrationTests-mariadb.sql | 28 +++- ...ggregateTemplateIntegrationTests-mssql.sql | 33 ++++- ...ggregateTemplateIntegrationTests-mysql.sql | 28 +++- ...gregateTemplateIntegrationTests-oracle.sql | 33 ++++- ...egateTemplateIntegrationTests-postgres.sql | 33 ++++- .../SingleQuerySqlGenerator.java | 70 ++++++--- .../sqlgeneration/AliasFactoryUnitTests.java | 18 ++- .../SingleQuerySqlGeneratorUnitTests.java | 8 +- 13 files changed, 448 insertions(+), 38 deletions(-) diff --git a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/SingleQueryFallbackDataAccessStrategy.java b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/SingleQueryFallbackDataAccessStrategy.java index 9628588f7a..2b03127ace 100644 --- a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/SingleQueryFallbackDataAccessStrategy.java +++ b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/SingleQueryFallbackDataAccessStrategy.java @@ -29,6 +29,7 @@ * Query Loading. * * @author Mark Paluch + * @author Jens Schauder * @since 3.2 */ class SingleQueryFallbackDataAccessStrategy extends DelegatingDataAccessStrategy { @@ -120,23 +121,25 @@ private boolean isSingleSelectQuerySupported(Class entityType) { private boolean entityQualifiesForSingleQueryLoading(Class entityType) { - boolean referenceFound = false; for (PersistentPropertyPath path : converter.getMappingContext() .findPersistentPropertyPaths(entityType, __ -> true)) { RelationalPersistentProperty property = path.getLeafProperty(); if (property.isEntity()) { + // single references are currently not supported + if (!(property.isMap() || property.isCollectionLike())) { + return false; + } + // embedded entities are currently not supported if (property.isEmbedded()) { return false; } - // only a single reference is currently supported - if (referenceFound) { + // nested references are currently not supported + if (path.getLength() > 1) { return false; } - - referenceFound = true; } } return true; diff --git a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/AbstractJdbcAggregateTemplateIntegrationTests.java b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/AbstractJdbcAggregateTemplateIntegrationTests.java index 858a69a791..20bc95686d 100644 --- a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/AbstractJdbcAggregateTemplateIntegrationTests.java +++ b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/AbstractJdbcAggregateTemplateIntegrationTests.java @@ -22,6 +22,8 @@ import static org.springframework.data.jdbc.testing.TestConfiguration.*; import static org.springframework.data.jdbc.testing.TestDatabaseFeatures.Feature.*; +import java.sql.ResultSet; +import java.sql.SQLException; import java.time.LocalDateTime; import java.util.ArrayList; import java.util.Collections; @@ -37,6 +39,7 @@ import java.util.stream.IntStream; import org.assertj.core.api.SoftAssertions; +import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.ApplicationEventPublisher; @@ -44,6 +47,7 @@ import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; import org.springframework.dao.IncorrectResultSizeDataAccessException; +import org.springframework.dao.DataAccessException; import org.springframework.dao.IncorrectUpdateSemanticsDataAccessException; import org.springframework.dao.OptimisticLockingFailureException; import org.springframework.data.annotation.Id; @@ -70,6 +74,7 @@ import org.springframework.data.relational.core.query.Criteria; import org.springframework.data.relational.core.query.CriteriaDefinition; import org.springframework.data.relational.core.query.Query; +import org.springframework.jdbc.core.ResultSetExtractor; import org.springframework.jdbc.core.namedparam.NamedParameterJdbcOperations; import org.springframework.test.context.ActiveProfiles; import org.springframework.test.context.ContextConfiguration; @@ -95,6 +100,7 @@ abstract class AbstractJdbcAggregateTemplateIntegrationTests { @Autowired JdbcAggregateOperations template; @Autowired NamedParameterJdbcOperations jdbcTemplate; @Autowired RelationalMappingContext mappingContext; + @Autowired NamedParameterJdbcOperations jdbc; LegoSet legoSet = createLegoSet("Star Destroyer"); @@ -1202,6 +1208,115 @@ void readEnumArray() { assertThat(template.findById(entity.id, EnumArrayOwner.class).digits).isEqualTo(new Color[] { Color.BLUE }); } + @Test // GH-1448 + void multipleCollections() { + + MultipleCollections aggregate = new MultipleCollections(); + aggregate.name = "aggregate"; + + aggregate.listElements.add(new ListElement("one")); + aggregate.listElements.add(new ListElement("two")); + aggregate.listElements.add(new ListElement("three")); + + aggregate.setElements.add(new SetElement("one")); + aggregate.setElements.add(new SetElement("two")); + + aggregate.mapElements.put("alpha", new MapElement("one")); + aggregate.mapElements.put("beta", new MapElement("two")); + aggregate.mapElements.put("gamma", new MapElement("three")); + aggregate.mapElements.put("delta", new MapElement("four")); + + template.save(aggregate); + + MultipleCollections reloaded = template.findById(aggregate.id, MultipleCollections.class); + + assertSoftly(softly -> { + + softly.assertThat(reloaded.name).isEqualTo(aggregate.name); + + softly.assertThat(reloaded.listElements).containsExactly(aggregate.listElements.get(0), + aggregate.listElements.get(1), aggregate.listElements.get(2)); + + softly.assertThat(reloaded.setElements) + .containsExactlyInAnyOrder(aggregate.setElements.toArray(new SetElement[0])); + + softly.assertThat(reloaded.mapElements.get("alpha")).isEqualTo(new MapElement("one")); + softly.assertThat(reloaded.mapElements.get("beta")).isEqualTo(new MapElement("two")); + softly.assertThat(reloaded.mapElements.get("gamma")).isEqualTo(new MapElement("three")); + softly.assertThat(reloaded.mapElements.get("delta")).isEqualTo(new MapElement("four")); + }); + } + + @Test // GH-1448 + void multipleCollectionsWithEmptySet() { + + MultipleCollections aggregate = new MultipleCollections(); + aggregate.name = "aggregate"; + + aggregate.listElements.add(new ListElement("one")); + aggregate.listElements.add(new ListElement("two")); + aggregate.listElements.add(new ListElement("three")); + + aggregate.mapElements.put("alpha", new MapElement("one")); + aggregate.mapElements.put("beta", new MapElement("two")); + aggregate.mapElements.put("gamma", new MapElement("three")); + aggregate.mapElements.put("delta", new MapElement("four")); + + template.save(aggregate); + + MultipleCollections reloaded = template.findById(aggregate.id, MultipleCollections.class); + + assertSoftly(softly -> { + + softly.assertThat(reloaded.name).isEqualTo(aggregate.name); + + softly.assertThat(reloaded.listElements).containsExactly(aggregate.listElements.get(0), + aggregate.listElements.get(1), aggregate.listElements.get(2)); + + softly.assertThat(reloaded.setElements) + .containsExactlyInAnyOrder(aggregate.setElements.toArray(new SetElement[0])); + + softly.assertThat(reloaded.mapElements.get("alpha")).isEqualTo(new MapElement("one")); + softly.assertThat(reloaded.mapElements.get("beta")).isEqualTo(new MapElement("two")); + softly.assertThat(reloaded.mapElements.get("gamma")).isEqualTo(new MapElement("three")); + softly.assertThat(reloaded.mapElements.get("delta")).isEqualTo(new MapElement("four")); + }); + } + + @Test // GH-1448 + void multipleCollectionsWithEmptyList() { + + MultipleCollections aggregate = new MultipleCollections(); + aggregate.name = "aggregate"; + + aggregate.setElements.add(new SetElement("one")); + aggregate.setElements.add(new SetElement("two")); + + aggregate.mapElements.put("alpha", new MapElement("one")); + aggregate.mapElements.put("beta", new MapElement("two")); + aggregate.mapElements.put("gamma", new MapElement("three")); + aggregate.mapElements.put("delta", new MapElement("four")); + + template.save(aggregate); + + MultipleCollections reloaded = template.findById(aggregate.id, MultipleCollections.class); + + assertSoftly(softly -> { + + softly.assertThat(reloaded.name).isEqualTo(aggregate.name); + + softly.assertThat(reloaded.listElements).containsExactly(); + + softly.assertThat(reloaded.setElements) + .containsExactlyInAnyOrder(aggregate.setElements.toArray(new SetElement[0])); + + softly.assertThat(reloaded.mapElements.get("alpha")).isEqualTo(new MapElement("one")); + softly.assertThat(reloaded.mapElements.get("beta")).isEqualTo(new MapElement("two")); + softly.assertThat(reloaded.mapElements.get("gamma")).isEqualTo(new MapElement("three")); + softly.assertThat(reloaded.mapElements.get("delta")).isEqualTo(new MapElement("four")); + }); + } + private void saveAndUpdateAggregateWithVersion(VersionedAggregate aggregate, Function toConcreteNumber) { saveAndUpdateAggregateWithVersion(aggregate, toConcreteNumber, 0); @@ -1932,6 +2047,24 @@ static class WithInsertOnly { @InsertOnlyProperty String insertOnly; } + @Table + static class MultipleCollections { + @Id Long id; + String name; + List listElements = new ArrayList<>(); + Set setElements = new HashSet<>(); + Map mapElements = new HashMap<>(); + } + + record ListElement(String name) { + } + + record SetElement(String name) { + } + + record MapElement(String name) { + } + @Configuration @Import(TestConfiguration.class) static class Config { diff --git a/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.core/JdbcAggregateTemplateIntegrationTests-db2.sql b/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.core/JdbcAggregateTemplateIntegrationTests-db2.sql index f086a03b5c..2abc57c279 100644 --- a/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.core/JdbcAggregateTemplateIntegrationTests-db2.sql +++ b/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.core/JdbcAggregateTemplateIntegrationTests-db2.sql @@ -42,6 +42,11 @@ DROP TABLE WITH_ID_ONLY; DROP TABLE WITH_INSERT_ONLY; +DROP TABLE MULTIPLE_COLLECTIONS; +DROP TABLE MAP_ELEMENT; +DROP TABLE LIST_ELEMENT; +DROP TABLE SET_ELEMENT; + CREATE TABLE LEGO_SET ( "id1" BIGINT GENERATED BY DEFAULT AS IDENTITY (START WITH 1) PRIMARY KEY, @@ -373,4 +378,30 @@ CREATE TABLE WITH_INSERT_ONLY ( ID BIGINT GENERATED BY DEFAULT AS IDENTITY (START WITH 1) PRIMARY KEY, INSERT_ONLY VARCHAR(100) -); \ No newline at end of file +); + +CREATE TABLE MULTIPLE_COLLECTIONS +( + ID BIGINT GENERATED BY DEFAULT AS IDENTITY (START WITH 1) PRIMARY KEY, + NAME VARCHAR(100) +); + +CREATE TABLE SET_ELEMENT +( + MULTIPLE_COLLECTIONS BIGINT, + NAME VARCHAR(100) +); + +CREATE TABLE LIST_ELEMENT +( + MULTIPLE_COLLECTIONS BIGINT, + MULTIPLE_COLLECTIONS_KEY INT, + NAME VARCHAR(100) +); + +CREATE TABLE MAP_ELEMENT +( + MULTIPLE_COLLECTIONS BIGINT, + MULTIPLE_COLLECTIONS_KEY VARCHAR(10), + NAME VARCHAR(100) +); diff --git a/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.core/JdbcAggregateTemplateIntegrationTests-h2.sql b/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.core/JdbcAggregateTemplateIntegrationTests-h2.sql index a6e5eabad7..1b1da1a1ec 100644 --- a/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.core/JdbcAggregateTemplateIntegrationTests-h2.sql +++ b/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.core/JdbcAggregateTemplateIntegrationTests-h2.sql @@ -340,4 +340,30 @@ CREATE TABLE WITH_INSERT_ONLY ( ID SERIAL PRIMARY KEY, INSERT_ONLY VARCHAR(100) -); \ No newline at end of file +); + +CREATE TABLE MULTIPLE_COLLECTIONS +( + ID SERIAL PRIMARY KEY, + NAME VARCHAR(100) +); + +CREATE TABLE SET_ELEMENT +( + MULTIPLE_COLLECTIONS BIGINT, + NAME VARCHAR(100) +); + +CREATE TABLE LIST_ELEMENT +( + MULTIPLE_COLLECTIONS BIGINT, + MULTIPLE_COLLECTIONS_KEY INT, + NAME VARCHAR(100) +); + +CREATE TABLE MAP_ELEMENT +( + MULTIPLE_COLLECTIONS BIGINT, + MULTIPLE_COLLECTIONS_KEY VARCHAR(10), + NAME VARCHAR(100) +); diff --git a/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.core/JdbcAggregateTemplateIntegrationTests-hsql.sql b/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.core/JdbcAggregateTemplateIntegrationTests-hsql.sql index dc73899207..b74e716ebd 100644 --- a/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.core/JdbcAggregateTemplateIntegrationTests-hsql.sql +++ b/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.core/JdbcAggregateTemplateIntegrationTests-hsql.sql @@ -341,4 +341,30 @@ CREATE TABLE WITH_INSERT_ONLY CREATE TABLE WITH_ID_ONLY ( ID BIGINT GENERATED BY DEFAULT AS IDENTITY (START WITH 1) PRIMARY KEY -) \ No newline at end of file +); + +CREATE TABLE MULTIPLE_COLLECTIONS +( + ID BIGINT GENERATED BY DEFAULT AS IDENTITY (START WITH 1) PRIMARY KEY, + NAME VARCHAR(100) +); + +CREATE TABLE SET_ELEMENT +( + MULTIPLE_COLLECTIONS BIGINT, + NAME VARCHAR(100) +); + +CREATE TABLE LIST_ELEMENT +( + MULTIPLE_COLLECTIONS BIGINT, + MULTIPLE_COLLECTIONS_KEY INT, + NAME VARCHAR(100) +); + +CREATE TABLE MAP_ELEMENT +( + MULTIPLE_COLLECTIONS BIGINT, + MULTIPLE_COLLECTIONS_KEY VARCHAR(10), + NAME VARCHAR(100) +); diff --git a/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.core/JdbcAggregateTemplateIntegrationTests-mariadb.sql b/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.core/JdbcAggregateTemplateIntegrationTests-mariadb.sql index 4258e7b438..29f0140d86 100644 --- a/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.core/JdbcAggregateTemplateIntegrationTests-mariadb.sql +++ b/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.core/JdbcAggregateTemplateIntegrationTests-mariadb.sql @@ -314,4 +314,30 @@ CREATE TABLE WITH_INSERT_ONLY ( ID BIGINT AUTO_INCREMENT PRIMARY KEY, INSERT_ONLY VARCHAR(100) -); \ No newline at end of file +); + +CREATE TABLE MULTIPLE_COLLECTIONS +( + ID BIGINT AUTO_INCREMENT PRIMARY KEY, + NAME VARCHAR(100) +); + +CREATE TABLE SET_ELEMENT +( + MULTIPLE_COLLECTIONS BIGINT, + NAME VARCHAR(100) +); + +CREATE TABLE LIST_ELEMENT +( + MULTIPLE_COLLECTIONS BIGINT, + MULTIPLE_COLLECTIONS_KEY INT, + NAME VARCHAR(100) +); + +CREATE TABLE MAP_ELEMENT +( + MULTIPLE_COLLECTIONS BIGINT, + MULTIPLE_COLLECTIONS_KEY VARCHAR(10), + NAME VARCHAR(100) +); diff --git a/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.core/JdbcAggregateTemplateIntegrationTests-mssql.sql b/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.core/JdbcAggregateTemplateIntegrationTests-mssql.sql index e9a378f49b..cacd54ed6c 100644 --- a/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.core/JdbcAggregateTemplateIntegrationTests-mssql.sql +++ b/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.core/JdbcAggregateTemplateIntegrationTests-mssql.sql @@ -347,4 +347,35 @@ CREATE TABLE WITH_INSERT_ONLY ( ID BIGINT IDENTITY PRIMARY KEY, INSERT_ONLY VARCHAR(100) -); \ No newline at end of file +); + +DROP TABLE MULTIPLE_COLLECTIONS; +DROP TABLE MAP_ELEMENT; +DROP TABLE LIST_ELEMENT; +DROP TABLE SET_ELEMENT; + +CREATE TABLE MULTIPLE_COLLECTIONS +( + ID BIGINT IDENTITY PRIMARY KEY, + NAME VARCHAR(100) +); + +CREATE TABLE SET_ELEMENT +( + MULTIPLE_COLLECTIONS BIGINT, + NAME VARCHAR(100) +); + +CREATE TABLE LIST_ELEMENT +( + MULTIPLE_COLLECTIONS BIGINT, + MULTIPLE_COLLECTIONS_KEY INT, + NAME VARCHAR(100) +); + +CREATE TABLE MAP_ELEMENT +( + MULTIPLE_COLLECTIONS BIGINT, + MULTIPLE_COLLECTIONS_KEY VARCHAR(10), + NAME VARCHAR(100) +); diff --git a/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.core/JdbcAggregateTemplateIntegrationTests-mysql.sql b/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.core/JdbcAggregateTemplateIntegrationTests-mysql.sql index 40e32f1692..3efb07a000 100644 --- a/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.core/JdbcAggregateTemplateIntegrationTests-mysql.sql +++ b/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.core/JdbcAggregateTemplateIntegrationTests-mysql.sql @@ -319,4 +319,30 @@ CREATE TABLE WITH_INSERT_ONLY ( ID BIGINT AUTO_INCREMENT PRIMARY KEY, INSERT_ONLY VARCHAR(100) -); \ No newline at end of file +); + +CREATE TABLE MULTIPLE_COLLECTIONS +( + ID BIGINT AUTO_INCREMENT PRIMARY KEY, + NAME VARCHAR(100) +); + +CREATE TABLE SET_ELEMENT +( + MULTIPLE_COLLECTIONS BIGINT, + NAME VARCHAR(100) +); + +CREATE TABLE LIST_ELEMENT +( + MULTIPLE_COLLECTIONS BIGINT, + MULTIPLE_COLLECTIONS_KEY INT, + NAME VARCHAR(100) +); + +CREATE TABLE MAP_ELEMENT +( + MULTIPLE_COLLECTIONS BIGINT, + MULTIPLE_COLLECTIONS_KEY VARCHAR(10), + NAME VARCHAR(100) +); diff --git a/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.core/JdbcAggregateTemplateIntegrationTests-oracle.sql b/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.core/JdbcAggregateTemplateIntegrationTests-oracle.sql index 5a5c5baf40..c4255de27e 100644 --- a/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.core/JdbcAggregateTemplateIntegrationTests-oracle.sql +++ b/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.core/JdbcAggregateTemplateIntegrationTests-oracle.sql @@ -32,6 +32,11 @@ DROP TABLE WITH_LOCAL_DATE_TIME CASCADE CONSTRAINTS PURGE; DROP TABLE WITH_ID_ONLY CASCADE CONSTRAINTS PURGE; DROP TABLE WITH_INSERT_ONLY CASCADE CONSTRAINTS PURGE; +DROP TABLE MULTIPLE_COLLECTIONS CASCADE CONSTRAINTS PURGE; +DROP TABLE MAP_ELEMENT CASCADE CONSTRAINTS PURGE; +DROP TABLE LIST_ELEMENT CASCADE CONSTRAINTS PURGE; +DROP TABLE SET_ELEMENT CASCADE CONSTRAINTS PURGE; + CREATE TABLE LEGO_SET ( "id1" NUMBER GENERATED by default on null as IDENTITY PRIMARY KEY, @@ -354,4 +359,30 @@ CREATE TABLE WITH_INSERT_ONLY ( ID NUMBER GENERATED by default on null as IDENTITY PRIMARY KEY, INSERT_ONLY VARCHAR(100) -); \ No newline at end of file +); + +CREATE TABLE MULTIPLE_COLLECTIONS +( + ID NUMBER GENERATED by default on null as IDENTITY PRIMARY KEY, + NAME VARCHAR(100) +); + +CREATE TABLE SET_ELEMENT +( + MULTIPLE_COLLECTIONS NUMBER, + NAME VARCHAR(100) +); + +CREATE TABLE LIST_ELEMENT +( + MULTIPLE_COLLECTIONS NUMBER, + MULTIPLE_COLLECTIONS_KEY INT, + NAME VARCHAR(100) +); + +CREATE TABLE MAP_ELEMENT +( + MULTIPLE_COLLECTIONS NUMBER, + MULTIPLE_COLLECTIONS_KEY VARCHAR(10), + NAME VARCHAR(100) +); diff --git a/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.core/JdbcAggregateTemplateIntegrationTests-postgres.sql b/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.core/JdbcAggregateTemplateIntegrationTests-postgres.sql index d43b5750b1..e401412e59 100644 --- a/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.core/JdbcAggregateTemplateIntegrationTests-postgres.sql +++ b/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.core/JdbcAggregateTemplateIntegrationTests-postgres.sql @@ -35,6 +35,11 @@ DROP TABLE WITH_LOCAL_DATE_TIME; DROP TABLE WITH_ID_ONLY; DROP TABLE WITH_INSERT_ONLY; +DROP TABLE MULTIPLE_COLLECTIONS; +DROP TABLE MAP_ELEMENT; +DROP TABLE LIST_ELEMENT; +DROP TABLE SET_ELEMENT; + CREATE TABLE LEGO_SET ( "id1" SERIAL PRIMARY KEY, @@ -376,4 +381,30 @@ CREATE TABLE WITH_INSERT_ONLY ( ID SERIAL PRIMARY KEY, INSERT_ONLY VARCHAR(100) -); \ No newline at end of file +); + +CREATE TABLE MULTIPLE_COLLECTIONS +( + ID SERIAL PRIMARY KEY, + NAME VARCHAR(100) +); + +CREATE TABLE SET_ELEMENT +( + MULTIPLE_COLLECTIONS BIGINT, + NAME VARCHAR(100) +); + +CREATE TABLE LIST_ELEMENT +( + MULTIPLE_COLLECTIONS BIGINT, + MULTIPLE_COLLECTIONS_KEY INT, + NAME VARCHAR(100) +); + +CREATE TABLE MAP_ELEMENT +( + MULTIPLE_COLLECTIONS BIGINT, + MULTIPLE_COLLECTIONS_KEY VARCHAR(10), + NAME VARCHAR(100) +); diff --git a/spring-data-relational/src/main/java/org/springframework/data/relational/core/sqlgeneration/SingleQuerySqlGenerator.java b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sqlgeneration/SingleQuerySqlGenerator.java index 9326a55f1f..949dbc6aa0 100644 --- a/spring-data-relational/src/main/java/org/springframework/data/relational/core/sqlgeneration/SingleQuerySqlGenerator.java +++ b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sqlgeneration/SingleQuerySqlGenerator.java @@ -187,16 +187,12 @@ private QueryMeta createInlineQuery(AggregatePath basePath, @Nullable Condition backReferenceAlias = aliases.getBackReferenceAlias(basePath); columns.add(table.column(basePath.getTableInfo().reverseColumnInfo().name()).as(backReferenceAlias)); - if (basePath.isQualified()) { + keyAlias = aliases.getKeyAlias(basePath); + Expression keyExpression = basePath.isQualified() + ? table.column(basePath.getTableInfo().qualifierColumnInfo().name()).as(keyAlias) + : createRowNumberExpression(basePath, table, keyAlias); + columns.add(keyExpression); - keyAlias = aliases.getKeyAlias(basePath); - columns.add(table.column(basePath.getTableInfo().qualifierColumnInfo().name()).as(keyAlias)); - } else { - - String alias = aliases.getColumnAlias(basePath); - columns.add(new AliasedExpression(just("1"), alias)); - columnAliases.add(just(alias)); - } } String id = null; @@ -215,8 +211,7 @@ private QueryMeta createInlineQuery(AggregatePath basePath, @Nullable Condition SelectBuilder.BuildSelect buildSelect = condition != null ? select.where(condition) : select; - InlineQuery inlineQuery = InlineQuery.create(buildSelect.build(false), - aliases.getTableAlias(context.getAggregatePath(entity))); + InlineQuery inlineQuery = InlineQuery.create(buildSelect.build(false), aliases.getTableAlias(basePath)); return QueryMeta.of(basePath, inlineQuery, columnAliases, just(id), just(backReferenceAlias), just(keyAlias), just(rowNumberAlias), just(rowCountAlias)); } @@ -252,6 +247,7 @@ private SelectBuilder.SelectJoin applyJoins(AggregatePath rootPath, List inlineQueries, SelectBuilder.SelectJoin select) { - SelectBuilder.SelectWhereAndOr selectWhere = null; - for (QueryMeta queryMeta : inlineQueries) { + SelectBuilder.SelectWhere selectWhere = (SelectBuilder.SelectWhere) select; + + Condition joins = null; + + for (int left = 0; left < inlineQueries.size(); left++) { - AggregatePath path = queryMeta.basePath; - Expression childRowNumber = just(aliases.getRowNumberAlias(path)); - Condition pseudoJoinCondition = Conditions.isNull(childRowNumber) - .or(Conditions.isEqual(childRowNumber, Expressions.just(aliases.getRowNumberAlias(rootPath)))) - .or(Conditions.isGreater(childRowNumber, Expressions.just(aliases.getRowCountAlias(rootPath)))); + QueryMeta leftQueryMeta = inlineQueries.get(left); + AggregatePath leftPath = leftQueryMeta.basePath; + Expression leftRowNumber = just(aliases.getRowNumberAlias(leftPath)); + Expression leftRowCount = just(aliases.getRowCountAlias(leftPath)); - selectWhere = ((SelectBuilder.SelectWhere) select).where(pseudoJoinCondition); + for (int right = left + 1; right < inlineQueries.size(); right++) { + + QueryMeta rightQueryMeta = inlineQueries.get(right); + AggregatePath rightPath = rightQueryMeta.basePath; + Expression rightRowNumber = just(aliases.getRowNumberAlias(rightPath)); + Expression rightRowCount = just(aliases.getRowCountAlias(rightPath)); + + System.out.println("joining: " + leftPath + " and " + rightPath); + + Condition mutualJoin = Conditions.isEqual(leftRowNumber, rightRowNumber).or(Conditions.isNull(leftRowNumber)) + .or(Conditions.isNull(rightRowNumber)) + .or(Conditions.nest(Conditions.isGreater(leftRowNumber, rightRowCount) + .and(Conditions.isEqual(rightRowNumber, SQL.literalOf(1))))) + .or(Conditions.nest(Conditions.isGreater(rightRowNumber, leftRowCount) + .and(Conditions.isEqual(leftRowNumber, SQL.literalOf(1))))); + + mutualJoin = Conditions.nest(mutualJoin); + + if (joins == null) { + joins = mutualJoin; + } else { + joins = joins.and(mutualJoin); + } + } } + // for (QueryMeta queryMeta : inlineQueries) { + // + // AggregatePath path = queryMeta.basePath; + // Expression childRowNumber = just(aliases.getRowNumberAlias(path)); + // Condition pseudoJoinCondition = Conditions.isNull(childRowNumber) + // .or(Conditions.isEqual(childRowNumber, Expressions.just(aliases.getRowNumberAlias(rootPath)))) + // .or(Conditions.isGreater(childRowNumber, Expressions.just(aliases.getRowCountAlias(rootPath)))); + // + selectWhere = (SelectBuilder.SelectWhere) selectWhere.where(joins); + // } + return selectWhere == null ? (SelectBuilder.SelectOrdered) select : selectWhere; } diff --git a/spring-data-relational/src/test/java/org/springframework/data/relational/core/sqlgeneration/AliasFactoryUnitTests.java b/spring-data-relational/src/test/java/org/springframework/data/relational/core/sqlgeneration/AliasFactoryUnitTests.java index 5f0f988769..840fa2ff32 100644 --- a/spring-data-relational/src/test/java/org/springframework/data/relational/core/sqlgeneration/AliasFactoryUnitTests.java +++ b/spring-data-relational/src/test/java/org/springframework/data/relational/core/sqlgeneration/AliasFactoryUnitTests.java @@ -53,7 +53,7 @@ void aliasSimpleProperty() { } @Test - void nameGetsSanatized() { + void nameGetsSanitized() { String alias = aliasFactory.getColumnAlias( context.getAggregatePath( context.getPersistentPropertyPath("evil", DummyEntity.class))); @@ -136,6 +136,21 @@ void testKeyAlias() { } } + @Nested + class TableAlias { + @Test // GH-1448 + void tableAliasIsDifferentForDifferentPathsToSameEntity() { + + String alias = aliasFactory.getTableAlias( + context.getAggregatePath(context.getPersistentPropertyPath("dummy", Reference.class))); + + String alias2 = aliasFactory.getTableAlias( + context.getAggregatePath(context.getPersistentPropertyPath("dummy2", Reference.class))); + + assertThat(alias).isNotEqualTo(alias2); + } + } + static class DummyEntity { String name; @@ -144,5 +159,6 @@ static class DummyEntity { static class Reference { DummyEntity dummy; + DummyEntity dummy2; } } diff --git a/spring-data-relational/src/test/java/org/springframework/data/relational/core/sqlgeneration/SingleQuerySqlGeneratorUnitTests.java b/spring-data-relational/src/test/java/org/springframework/data/relational/core/sqlgeneration/SingleQuerySqlGeneratorUnitTests.java index ade6e0dad1..2f8a865989 100644 --- a/spring-data-relational/src/test/java/org/springframework/data/relational/core/sqlgeneration/SingleQuerySqlGeneratorUnitTests.java +++ b/spring-data-relational/src/test/java/org/springframework/data/relational/core/sqlgeneration/SingleQuerySqlGeneratorUnitTests.java @@ -161,11 +161,9 @@ void createSelectForFindById() { func("coalesce", col(trivialsRowNumber), lit(1))), // col(backref), // col(keyAlias) // - ).extractWhereClause() // - .doesNotContainIgnoringCase("and") // - .containsIgnoringCase(trivialsRowNumber + " is null") // - .containsIgnoringCase(trivialsRowNumber + " = " + rootRowNumber) // - .containsIgnoringCase(trivialsRowNumber + " > " + rootCount); + ) + .extractWhereClause() // + .isEqualTo(""); baseSelect.hasInlineViewSelectingFrom("\"single_reference_aggregate\"") // .hasExactlyColumns( // lit(1).as(rnAlias()), lit(1).as(rootCount), //