From ce32223c17f8fd6cd302d86dbf9e9ca5bb2b7255 Mon Sep 17 00:00:00 2001 From: Jens Schauder Date: Thu, 14 Mar 2019 13:21:15 +0100 Subject: [PATCH 1/3] DATAJDBC-340 - Prepare branch --- pom.xml | 2 +- spring-data-jdbc-distribution/pom.xml | 2 +- spring-data-jdbc/pom.xml | 4 ++-- spring-data-relational/pom.xml | 4 ++-- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/pom.xml b/pom.xml index 680dddf736..563ccf1284 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ org.springframework.data spring-data-relational-parent - 1.1.0.BUILD-SNAPSHOT + 1.1.0.DATAJDBC-340-SNAPSHOT pom Spring Data Relational Parent diff --git a/spring-data-jdbc-distribution/pom.xml b/spring-data-jdbc-distribution/pom.xml index f6d4373844..08fe7e38ea 100644 --- a/spring-data-jdbc-distribution/pom.xml +++ b/spring-data-jdbc-distribution/pom.xml @@ -14,7 +14,7 @@ org.springframework.data spring-data-relational-parent - 1.1.0.BUILD-SNAPSHOT + 1.1.0.DATAJDBC-340-SNAPSHOT ../pom.xml diff --git a/spring-data-jdbc/pom.xml b/spring-data-jdbc/pom.xml index cfb1e9443b..baa35dffb8 100644 --- a/spring-data-jdbc/pom.xml +++ b/spring-data-jdbc/pom.xml @@ -5,7 +5,7 @@ 4.0.0 spring-data-jdbc - 1.1.0.BUILD-SNAPSHOT + 1.1.0.DATAJDBC-340-SNAPSHOT Spring Data JDBC Spring Data module for JDBC repositories. @@ -14,7 +14,7 @@ org.springframework.data spring-data-relational-parent - 1.1.0.BUILD-SNAPSHOT + 1.1.0.DATAJDBC-340-SNAPSHOT diff --git a/spring-data-relational/pom.xml b/spring-data-relational/pom.xml index ba0ffea143..11717415c2 100644 --- a/spring-data-relational/pom.xml +++ b/spring-data-relational/pom.xml @@ -5,7 +5,7 @@ 4.0.0 spring-data-relational - 1.1.0.BUILD-SNAPSHOT + 1.1.0.DATAJDBC-340-SNAPSHOT Spring Data Relational Spring Data Relational support @@ -13,7 +13,7 @@ org.springframework.data spring-data-relational-parent - 1.1.0.BUILD-SNAPSHOT + 1.1.0.DATAJDBC-340-SNAPSHOT From 94cbf8613c491d5fad11e9982cc3306e0ac64e49 Mon Sep 17 00:00:00 2001 From: Jens Schauder Date: Thu, 14 Mar 2019 14:07:11 +0100 Subject: [PATCH 2/3] DATAJDBC-340 - Using new SQL generation DSL. SqlGenerator basically got rewritten using the new SQL rendering API. The SQL rendering API got some minor improvements in the process. The whole generation process is now based on paths instead of properties which makes the actual logic of the construciton much more obvious. --- .../core/PersistentPropertyPathExtension.java | 300 +++++++++++++ .../data/jdbc/core/SelectBuilder.java | 335 -------------- .../data/jdbc/core/SqlContext.java | 59 +++ .../data/jdbc/core/SqlGenerator.java | 423 ++++++++++-------- .../core/DefaultJdbcInterpreterUnitTests.java | 4 +- ...JdbcAggregateTemplateIntegrationTests.java | 68 ++- .../core/JdbcIdentifierBuilderUnitTests.java | 23 +- .../MyBatisDataAccessStrategyUnitTests.java | 12 +- ...sistentPropertyPathExtensionUnitTests.java | 174 +++++++ ...ils.java => PropertyPathTestingUtils.java} | 2 +- .../jdbc/core/SelectBuilderUnitTests.java | 89 ---- ...orContextBasedNamingStrategyUnitTests.java | 32 +- .../core/SqlGeneratorEmbeddedUnitTests.java | 252 +++++++---- ...GeneratorFixedNamingStrategyUnitTests.java | 39 +- .../data/jdbc/core/SqlGeneratorUnitTests.java | 293 +++++++++--- .../src/test/resources/logback.xml | 4 +- ...AggregateTemplateIntegrationTests-hsql.sql | 95 +++- ...regateTemplateIntegrationTests-mariadb.sql | 89 +++- ...ggregateTemplateIntegrationTests-mssql.sql | 91 +++- ...ggregateTemplateIntegrationTests-mysql.sql | 89 +++- ...egateTemplateIntegrationTests-postgres.sql | 100 ++++- .../data/relational/core/sql/Column.java | 11 + .../data/relational/core/sql/Conditions.java | 4 +- .../core/sql/DefaultInsertBuilder.java | 2 +- .../core/sql/DefaultSelectBuilder.java | 32 +- .../data/relational/core/sql/Expressions.java | 3 +- .../data/relational/core/sql/Functions.java | 3 +- .../relational/core/sql/InsertBuilder.java | 3 +- .../relational/core/sql/SelectBuilder.java | 11 + .../relational/core/sql/SelectValidator.java | 8 +- .../sql/render/InsertStatementVisitor.java | 9 +- .../core/sql/render/SelectListVisitor.java | 19 +- .../sql/render/InsertRendererUnitTests.java | 19 +- .../sql/render/SelectRendererUnitTests.java | 73 ++- 34 files changed, 1886 insertions(+), 884 deletions(-) create mode 100644 spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/PersistentPropertyPathExtension.java delete mode 100644 spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/SelectBuilder.java create mode 100644 spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/SqlContext.java create mode 100644 spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/PersistentPropertyPathExtensionUnitTests.java rename spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/{PropertyPathUtils.java => PropertyPathTestingUtils.java} (97%) delete mode 100644 spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/SelectBuilderUnitTests.java diff --git a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/PersistentPropertyPathExtension.java b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/PersistentPropertyPathExtension.java new file mode 100644 index 0000000000..32154adf1e --- /dev/null +++ b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/PersistentPropertyPathExtension.java @@ -0,0 +1,300 @@ +/* + * Copyright 2019 the original author or authors. + * + * Licensed 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.springframework.data.jdbc.core; + +import java.util.Objects; + +import org.springframework.data.mapping.PersistentPropertyPath; +import org.springframework.data.relational.core.mapping.RelationalMappingContext; +import org.springframework.data.relational.core.mapping.RelationalPersistentEntity; +import org.springframework.data.relational.core.mapping.RelationalPersistentProperty; +import org.springframework.lang.Nullable; +import org.springframework.util.Assert; + +/** + * A wrapper around a {@link org.springframework.data.mapping.PersistentPropertyPath} for making common operations + * available used in SQL generation. + * + * @author Jens Schauder + * @since 1.1 + */ +class PersistentPropertyPathExtension { + + private final RelationalPersistentEntity entity; + private final PersistentPropertyPath path; + private final RelationalMappingContext context; + + PersistentPropertyPathExtension(RelationalMappingContext context, RelationalPersistentEntity entity) { + + Assert.notNull(context, "Context must not be null."); + Assert.notNull(entity, "Entity must not be null."); + + this.context = context; + this.entity = entity; + this.path = null; + } + + PersistentPropertyPathExtension(RelationalMappingContext context, + PersistentPropertyPath path) { + + Assert.notNull(context, "Context must not be null."); + Assert.notNull(path, "Path must not be null."); + Assert.isTrue(!path.isEmpty(), "Path must not be empty."); + + this.context = context; + this.entity = Objects.requireNonNull(path.getBaseProperty()).getOwner(); + this.path = path; + } + + /** + * Returns {@literal true} exactly when the path is non empty and the leaf property an embedded one. + * + * @return if the leaf property is embedded. + */ + boolean isEmbedded() { + return path != null && path.getRequiredLeafProperty().isEmbedded(); + } + + /** + * Returns the path that has the same beginning but is one segment shorter than this path. + * + * @return the parent path. Guaranteed to be not {@literal null}. + * @throws IllegalStateException when called on an empty path. + */ + PersistentPropertyPathExtension getParentPath() { + + if (path == null) { + throw new IllegalStateException("The parent path of a root path is not defined."); + } + + if (path.getLength() == 1) { + return new PersistentPropertyPathExtension(context, entity); + } + + return new PersistentPropertyPathExtension(context, path.getParentPath()); + } + + /** + * Returns {@literal true} if there are multiple values for this path, i.e. if the path contains at least one element + * that is a collection and array or a map. + * + * @return {@literal true} if the path contains a multivalued element. + */ + boolean isMultiValued() { + + return path != null && // + (path.getRequiredLeafProperty().isCollectionLike() // + || path.getRequiredLeafProperty().isQualified() // + || getParentPath().isMultiValued() // + ); + } + + /** + * The {@link RelationalPersistentEntity} associated with the leaf of this path. + * + * @return Might return {@literal null} when called on a path that does not represent an entity. + */ + @Nullable + RelationalPersistentEntity getLeafEntity() { + return path == null ? entity : context.getPersistentEntity(path.getRequiredLeafProperty().getActualType()); + } + + /** + * @return {@literal true} when this is an empty path or the path references an entity. + */ + boolean isEntity() { + return path == null || path.getRequiredLeafProperty().isEntity(); + } + + /** + * @return {@literal true} when this is references a {@link java.util.List} or {@link java.util.Map}. + */ + boolean isQualified() { + return path != null && path.getRequiredLeafProperty().isQualified(); + } + + /** + * @return {@literal true} when this is references a {@link java.util.Collection} or an array. + */ + boolean isCollectionLike() { + return path != null && path.getRequiredLeafProperty().isCollectionLike(); + } + + /** + * The name of the column used to reference the id in the parent table. + * + * @throws IllegalStateException when called on an empty path. + */ + String getReverseColumnName() { + + return path.getRequiredLeafProperty().getReverseColumnName(); + } + + /** + * The alias used in select for the column used to reference the id in the parent table. + * + * @throws IllegalStateException when called on an empty path. + */ + String getReverseColumnNameAlias() { + + return prefixWithTableAlias(getReverseColumnName()); + } + + /** + * The name of the column used to represent this property in the database. + * + * @throws IllegalStateException when called on an empty path. + */ + String getColumnName() { + + return assembleColumnName(path.getRequiredLeafProperty().getColumnName()); + } + + /** + * The alias for the column used to represent this property in the database. + * + * @throws IllegalStateException when called on an empty path. + */ + String getColumnAlias() { + + return prefixWithTableAlias(getColumnName()); + } + + /** + * @return {@literal true} if this path represents an entity which has an Id attribute. + */ + boolean hasIdProperty() { + + RelationalPersistentEntity leafEntity = getLeafEntity(); + return leafEntity != null && leafEntity.hasIdProperty(); + } + + PersistentPropertyPathExtension getIdDefiningParentPath() { + + PersistentPropertyPathExtension parent = getParentPath(); + if (parent.path == null) { + return parent; + } + if (parent.isEmbedded()) { + return getParentPath().getIdDefiningParentPath(); + } + return parent; + } + + /** + * The name of the table this path is tied to or of the longest ancestor path that is actually tied to a table. + * + * @return the name of the table. Guaranteed to be not {@literal null}. + */ + String getTableName() { + return getTableOwningAncestor().getRequiredLeafEntity().getTableName(); + } + + /** + * The alias used for the table on which this path is based. + * + * @return a table alias, {@literal null} if the table owning path is the empty path. + */ + @Nullable + String getTableAlias() { + + PersistentPropertyPathExtension tableOwner = getTableOwningAncestor(); + if (tableOwner.path == null) { + return null; + } + + return tableOwner.assembleTableAlias(); + } + + /** + * The column name of the id column of the ancestor path that represents an actual table. + */ + String getIdColumnName() { + return getTableOwningAncestor().getRequiredLeafEntity().getIdColumn(); + } + + /** + * If the table owning ancestor has an id the column name of that id property is returned. Otherwise the reverse + * column is returned. + */ + String getEffectiveIdColumnName() { + + PersistentPropertyPathExtension owner = getTableOwningAncestor(); + return owner.path == null ? owner.getRequiredLeafEntity().getIdColumn() : owner.getReverseColumnName(); + } + + /** + * The length of the path. + */ + int getLength() { + return path == null ? 0 : path.getLength(); + } + + /** + * Finds and returns the longest path with ich identical or an ancestor to the current path and maps directly to a + * table. + * + * @return a path. Guaranteed to be not {@literal null}. + */ + private PersistentPropertyPathExtension getTableOwningAncestor() { + + if (isEntity() && !isEmbedded()) { + return this; + } + return getParentPath().getTableOwningAncestor(); + } + + private String assembleTableAlias() { + + RelationalPersistentProperty leafProperty = path.getRequiredLeafProperty(); + String prefix = isEmbedded() ? leafProperty.getEmbeddedPrefix() : leafProperty.getName(); + + if (path.getLength() == 1) { + Assert.notNull(prefix, "Prefix mus not be null."); + return prefix; + } + + PersistentPropertyPathExtension parentPath = getParentPath(); + return parentPath.isEmbedded() ? parentPath.assembleTableAlias() + prefix + : parentPath.assembleTableAlias() + "_" + prefix; + } + + private String assembleColumnName(String suffix) { + + if (path.getLength() <= 1) { + return suffix; + } + PersistentPropertyPath parentPath = path.getParentPath(); + RelationalPersistentProperty parentLeaf = parentPath.getRequiredLeafProperty(); + if (!parentLeaf.isEmbedded()) { + return suffix; + } + String embeddedPrefix = parentLeaf.getEmbeddedPrefix(); + return getParentPath().assembleColumnName(embeddedPrefix + suffix); + } + + private RelationalPersistentEntity getRequiredLeafEntity() { + return path == null ? entity : context.getRequiredPersistentEntity(path.getRequiredLeafProperty().getActualType()); + } + + + private String prefixWithTableAlias(String columnName) { + + String tableAlias = getTableAlias(); + return tableAlias == null ? columnName : tableAlias + "_" + columnName; + } + +} diff --git a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/SelectBuilder.java b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/SelectBuilder.java deleted file mode 100644 index 7dcc11e61a..0000000000 --- a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/SelectBuilder.java +++ /dev/null @@ -1,335 +0,0 @@ -/* - * Copyright 2017-2019 the original author or authors. - * - * Licensed 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.springframework.data.jdbc.core; - -import lombok.Builder; - -import java.util.ArrayList; -import java.util.List; -import java.util.function.Function; -import java.util.stream.Collectors; - -/** - * Builder for creating Select-statements. Not intended for general purpose, but only for the needs of the - * {@link JdbcAggregateTemplate}. - * - * @author Jens Schauder - */ -class SelectBuilder { - - private final List columns = new ArrayList<>(); - private final String tableName; - private final List joins = new ArrayList<>(); - private final List conditions = new ArrayList<>(); - - /** - * Creates a {@link SelectBuilder} using the given table name. - * - * @param tableName the table name. Must not be {@code null}. - */ - SelectBuilder(String tableName) { - - this.tableName = tableName; - } - - /** - * Adds a column to the select list. - * - * @param columnSpec a function that specifies the column to add. The passed in {@link Column.ColumnBuilder} allows to - * specify details like alias and the source table. Must not be {@code null}. - * @return {@code this}. - */ - SelectBuilder column(Function columnSpec) { - - columns.add(columnSpec.apply(Column.builder()).build()); - return this; - } - - /** - * Adds a where clause to the select - * - * @param whereSpec a function specifying the details of the where clause by manipulating the passed in - * {@link WhereConditionBuilder}. Must not be {@code null}. - * @return {@code this}. - */ - SelectBuilder where(Function whereSpec) { - - conditions.add(whereSpec.apply(new WhereConditionBuilder()).build()); - return this; - } - - /** - * Adds a join to the select. - * - * @param joinSpec a function specifying the details of the join by manipulating the passed in - * {@link Join.JoinBuilder}. Must not be {@code null}. - * @return {@code this}. - */ - SelectBuilder join(Function joinSpec) { - - joins.add(joinSpec.apply(Join.builder()).build()); - return this; - } - - /** - * Builds the actual SQL statement. - * - * @return a SQL statement. Guaranteed to be not {@code null}. - */ - String build() { - - return selectFrom() + joinClause() + whereClause(); - } - - private String whereClause() { - - if (conditions.isEmpty()) { - return ""; - } - - return conditions.stream() // - .map(WhereCondition::toSql) // - .collect(Collectors.joining("AND", " WHERE ", "") // - ); - } - - private String joinClause() { - return joins.stream().map(j -> joinTable(j) + joinConditions(j)).collect(Collectors.joining(" ")); - } - - private String joinTable(Join j) { - return String.format("%s JOIN %s AS %s", j.outerJoinModifier(), j.table, j.as); - } - - private String joinConditions(Join j) { - - return j.conditions.stream() // - .map(w -> String.format("%s %s %s", w.fromExpression, w.operation, w.toExpression)) // - .collect(Collectors.joining(" AND ", " ON ", "")); - } - - private String selectFrom() { - - return columns.stream() // - .map(Column::columnDefinition) // - .collect(Collectors.joining(", ", "SELECT ", " FROM " + tableName)); - } - - static class WhereConditionBuilder { - - private String fromTable; - private String fromColumn; - - private String operation = "="; - private String expression; - - WhereConditionBuilder() {} - - WhereConditionBuilder eq() { - - this.operation = "="; - return this; - } - - public WhereConditionBuilder in() { - - this.operation = "in"; - return this; - } - - WhereConditionBuilder tableAlias(String fromTable) { - - this.fromTable = fromTable; - return this; - } - - WhereConditionBuilder column(String fromColumn) { - - this.fromColumn = fromColumn; - return this; - } - - WhereConditionBuilder variable(String var) { - - this.expression = ":" + var; - return this; - } - - WhereCondition build() { - return new WhereCondition(fromTable + "." + fromColumn, operation, expression); - } - - } - - static class Join { - - private final String table; - private final String as; - private final Outer outer; - private final List conditions = new ArrayList<>(); - - Join(String table, String as, List conditions, Outer outer) { - - this.table = table; - this.as = as; - this.outer = outer; - this.conditions.addAll(conditions); - } - - static JoinBuilder builder() { - return new JoinBuilder(); - } - - private String outerJoinModifier() { - - switch (outer) { - case NONE: - return ""; - default: - return String.format(" %s OUTER", outer.name()); - - } - } - - public static class JoinBuilder { - - private String table; - private String as; - private List conditions = new ArrayList<>(); - private Outer outer = Outer.NONE; - - JoinBuilder() {} - - public Join.JoinBuilder table(String table) { - - this.table = table; - return this; - } - - public Join.JoinBuilder as(String as) { - - this.as = as; - return this; - } - - WhereConditionBuilder where(String column) { - return new WhereConditionBuilder(this, column); - } - - private JoinBuilder where(WhereCondition condition) { - - conditions.add(condition); - return this; - } - - Join build() { - return new Join(table, as, conditions, outer); - } - - public String toString() { - return "org.springframework.data.jdbc.core.SelectBuilder.Join.JoinBuilder(table=" + this.table + ", as=" - + this.as + ")"; - } - - JoinBuilder rightOuter() { - - outer = Outer.RIGHT; - return this; - } - - JoinBuilder leftOuter() { - outer = Outer.LEFT; - return this; - } - - static class WhereConditionBuilder { - - private final JoinBuilder joinBuilder; - private final String fromColumn; - - private String operation = "="; - - WhereConditionBuilder(JoinBuilder joinBuilder, String column) { - - this.joinBuilder = joinBuilder; - this.fromColumn = column; - } - - WhereConditionBuilder eq() { - operation = "="; - return this; - } - - JoinBuilder column(String table, String column) { - - return joinBuilder.where(new WhereCondition( // - joinBuilder.as + "." + fromColumn, // - operation, // - table + "." + column // - )); - - } - - } - - } - - private enum Outer { - NONE, RIGHT, LEFT - } - } - - static class WhereCondition { - - private final String operation; - private final String fromExpression; - private final String toExpression; - - WhereCondition(String fromExpression, String operation, String toExpression) { - - this.fromExpression = fromExpression; - this.toExpression = toExpression; - this.operation = operation; - } - - String toSql() { - - if (operation.equals("in")) { - return String.format("%s %s(%s)", fromExpression, operation, toExpression); - } - - return String.format("%s %s %s", fromExpression, operation, toExpression); - } - } - - @Builder - static class Column { - - private final String tableAlias; - private final String column; - private final String as; - - String columnDefinition() { - StringBuilder b = new StringBuilder(); - if (tableAlias != null) - b.append(tableAlias).append('.'); - b.append(column); - if (as != null) - b.append(" AS ").append(as); - return b.toString(); - } - } -} diff --git a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/SqlContext.java b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/SqlContext.java new file mode 100644 index 0000000000..33346a99a8 --- /dev/null +++ b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/SqlContext.java @@ -0,0 +1,59 @@ +/* + * Copyright 2019 the original author or authors. + * + * Licensed 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.springframework.data.jdbc.core; + +import org.springframework.data.relational.core.mapping.RelationalPersistentEntity; +import org.springframework.data.relational.core.sql.Column; +import org.springframework.data.relational.core.sql.SQL; +import org.springframework.data.relational.core.sql.Table; + +/** + * Utility to get from path to SQL DSL elements. + * + * @author Jens Schauder + * @since 1.1 + */ +class SqlContext { + + private final RelationalPersistentEntity entity; + + SqlContext(RelationalPersistentEntity entity) { + this.entity = entity; + } + + Column getIdColumn() { + return getTable().column(entity.getIdColumn()); + } + + Table getTable(PersistentPropertyPathExtension path) { + + String tableAlias = path.getTableAlias(); + Table table = SQL.table(path.getTableName()); + return tableAlias == null ? table : table.as(tableAlias); + } + + Table getTable() { + return SQL.table(entity.getTableName()); + } + + Column getColumn(PersistentPropertyPathExtension path) { + return getTable(path).column(path.getColumnName()).as(path.getColumnAlias()); + } + + Column getReverseColumn(PersistentPropertyPathExtension path) { + return getTable(path).column(path.getReverseColumnName()).as(path.getReverseColumnNameAlias()); + } +} diff --git a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/SqlGenerator.java b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/SqlGenerator.java index 4a28b59ded..958a7f16db 100644 --- a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/SqlGenerator.java +++ b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/SqlGenerator.java @@ -15,13 +15,15 @@ */ package org.springframework.data.jdbc.core; +import lombok.Value; + import java.util.ArrayList; -import java.util.Collection; import java.util.HashSet; import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Set; +import java.util.function.Function; import java.util.regex.Pattern; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -33,6 +35,8 @@ import org.springframework.data.relational.core.mapping.RelationalMappingContext; import org.springframework.data.relational.core.mapping.RelationalPersistentEntity; import org.springframework.data.relational.core.mapping.RelationalPersistentProperty; +import org.springframework.data.relational.core.sql.*; +import org.springframework.data.relational.core.sql.render.SqlRenderer; import org.springframework.data.util.Lazy; import org.springframework.data.util.StreamUtils; import org.springframework.lang.Nullable; @@ -49,12 +53,12 @@ class SqlGenerator { private final RelationalPersistentEntity entity; - private final RelationalMappingContext context; + private final RelationalMappingContext mappingContext; private final List columnNames = new ArrayList<>(); private final List nonIdColumnNames = new ArrayList<>(); private final Set readOnlyColumnNames = new HashSet<>(); - private final Lazy findOneSql = Lazy.of(this::createFindOneSelectSql); + private final Lazy findOneSql = Lazy.of(this::createFindOneSql); private final Lazy findAllSql = Lazy.of(this::createFindAllSql); private final Lazy findAllInListSql = Lazy.of(this::createFindAllInListSql); @@ -68,13 +72,15 @@ class SqlGenerator { private final SqlGeneratorSource sqlGeneratorSource; private final Pattern parameterPattern = Pattern.compile("\\W"); + private final SqlContext sqlContext; - SqlGenerator(RelationalMappingContext context, RelationalPersistentEntity entity, + SqlGenerator(RelationalMappingContext mappingContext, RelationalPersistentEntity entity, SqlGeneratorSource sqlGeneratorSource) { - this.context = context; + this.mappingContext = mappingContext; this.entity = entity; this.sqlGeneratorSource = sqlGeneratorSource; + this.sqlContext = new SqlContext(entity); initColumnNames(entity, ""); } @@ -109,7 +115,8 @@ private void initEmbeddedColumnNames(RelationalPersistentProperty property, Stri final String embeddedPrefix = property.getEmbeddedPrefix(); - final RelationalPersistentEntity embeddedEntity = context.getRequiredPersistentEntity(property.getColumnType()); + final RelationalPersistentEntity embeddedEntity = mappingContext + .getRequiredPersistentEntity(property.getColumnType()); initColumnNames(embeddedEntity, prefix + embeddedPrefix); } @@ -150,14 +157,20 @@ String getFindAllByProperty(String columnName, @Nullable String keyColumn, boole Assert.isTrue(keyColumn != null || !ordered, "If the SQL statement should be ordered a keyColumn to order by must be provided."); - String baseSelect = (keyColumn != null) // - ? createSelectBuilder().column(cb -> cb.tableAlias(entity.getTableName()).column(keyColumn).as(keyColumn)) - .build() - : getFindAll(); + SelectBuilder.SelectWhere baseSelect = createBaseSelect(keyColumn); + + Table table = Table.create(entity.getTableName()); + SelectBuilder.SelectWhereAndOr withWhereClause = baseSelect + .where(table.column(columnName).isEqualTo(SQL.bindMarker(":" + columnName))); - String orderBy = ordered ? " ORDER BY " + keyColumn : ""; + SelectBuilder.BuildSelect select; + if (ordered) { + select = withWhereClause.orderBy(table.column(keyColumn).as(keyColumn)); + } else { + select = withWhereClause; + } - return String.format("%s WHERE %s = :%s%s", baseSelect, columnName, columnName, orderBy); + return render(select); } String getExists() { @@ -188,277 +201,311 @@ String getDeleteByList() { return deleteByListSql.get(); } - private String createFindOneSelectSql() { + private String createFindOneSql() { - return createSelectBuilder() // - .where(wb -> wb.tableAlias(entity.getTableName()).column(entity.getIdColumn()).eq().variable("id")) // - .build(); + SelectBuilder.SelectWhereAndOr withCondition = createBaseSelect() + .where(sqlContext.getIdColumn().isEqualTo(SQL.bindMarker(":id"))); + + return render(withCondition); } - private SelectBuilder createSelectBuilder() { + private Stream getColumnNameStream(String prefix) { - SelectBuilder builder = new SelectBuilder(entity.getTableName()); - addColumnsForSimpleProperties(entity, "", "", entity, builder); - addColumnsForEmbeddedProperties(entity, "", "", entity, builder); - addColumnsAndJoinsForOneToOneReferences(entity, "", "", entity, builder); + return StreamUtils.createStreamFromIterator(entity.iterator()) // + .flatMap(p -> getColumnNameStream(p, prefix)); + } + + private Stream getColumnNameStream(RelationalPersistentProperty p, String prefix) { - return builder; + if (p.isEntity()) { + return sqlGeneratorSource.getSqlGenerator(p.getType()).getColumnNameStream(prefix + p.getColumnName() + "_"); + } else { + return Stream.of(prefix + p.getColumnName()); + } } - /** - * Adds the columns to the provided {@link SelectBuilder} representing simple properties, including those from - * one-to-one relationships. - * - * @param rootEntity the root entity for which to add the columns. - * @param builder The {@link SelectBuilder} to be modified. - */ - private void addColumnsAndJoinsForOneToOneReferences(RelationalPersistentEntity entity, String prefix, - String tableAlias, RelationalPersistentEntity rootEntity, SelectBuilder builder) { - - for (RelationalPersistentProperty property : entity) { - if (!property.isEntity() // - || property.isEmbedded() // - || Collection.class.isAssignableFrom(property.getType()) // - || Map.class.isAssignableFrom(property.getType()) // - ) { - continue; - } + private String createFindAllSql() { + return render(createBaseSelect()); + } - final RelationalPersistentEntity refEntity = context.getRequiredPersistentEntity(property.getActualType()); - final String joinAlias; - - if (tableAlias.isEmpty()) { - if (prefix.isEmpty()) { - joinAlias = property.getName(); - } else { - joinAlias = prefix + property.getName(); - } - } else { - if (prefix.isEmpty()) { - joinAlias = tableAlias + "_" + property.getName(); - } else { - joinAlias = tableAlias + "_" + prefix + property.getName(); - } - } + private SelectBuilder.SelectWhere createBaseSelect() { - // final String joinAlias = tableAlias.isEmpty() ? property.getName() : tableAlias + "_" + property.getName(); - builder.join(jb -> jb.leftOuter().table(refEntity.getTableName()).as(joinAlias) // - .where(property.getReverseColumnName()).eq().column(rootEntity.getTableName(), rootEntity.getIdColumn())); + return createBaseSelect(null); + } - addColumnsForSimpleProperties(refEntity, "", joinAlias, refEntity, builder); - addColumnsForEmbeddedProperties(refEntity, "", joinAlias, refEntity, builder); - addColumnsAndJoinsForOneToOneReferences(refEntity, "", joinAlias, refEntity, builder); + private SelectBuilder.SelectWhere createBaseSelect(@Nullable String keyColumn) { - // if the referenced property doesn't have an id, include the back reference in the select list. - // this enables determining if the referenced entity is present or null. - if (!refEntity.hasIdProperty()) { + Table table = SQL.table(entity.getTableName()); - builder.column( // - cb -> cb.tableAlias(joinAlias) // - .column(property.getReverseColumnName()) // - .as(joinAlias + "_" + property.getReverseColumnName()) // - ); + List columnExpressions = new ArrayList<>(); + + List joinTables = new ArrayList<>(); + for (PersistentPropertyPath path : mappingContext + .findPersistentPropertyPaths(entity.getType(), p -> true)) { + + PersistentPropertyPathExtension extPath = new PersistentPropertyPathExtension(mappingContext, path); + + // add a join if necessary + Join join = getJoin(extPath); + if (join != null) { + joinTables.add(join); } - } - } - private void addColumnsForEmbeddedProperties(RelationalPersistentEntity currentEntity, String prefix, - String tableAlias, RelationalPersistentEntity rootEntity, SelectBuilder builder) { - for (RelationalPersistentProperty property : currentEntity) { - if (!property.isEmbedded()) { - continue; + Column column = getColumn(extPath); + if (column != null) { + columnExpressions.add(column); } + } - final String embeddedPrefix = prefix + property.getEmbeddedPrefix(); - final RelationalPersistentEntity embeddedEntity = context - .getRequiredPersistentEntity(property.getColumnType()); + if (keyColumn != null) { + columnExpressions.add(table.column(keyColumn).as(keyColumn)); + } + + SelectBuilder.SelectAndFrom selectBuilder = StatementBuilder.select(columnExpressions); + + SelectBuilder.SelectJoin baseSelect = selectBuilder.from(table); - addColumnsForSimpleProperties(embeddedEntity, embeddedPrefix, tableAlias, rootEntity, builder); - addColumnsForEmbeddedProperties(embeddedEntity, embeddedPrefix, tableAlias, rootEntity, builder); - addColumnsAndJoinsForOneToOneReferences(embeddedEntity, embeddedPrefix, tableAlias, rootEntity, builder); + for (Join join : joinTables) { + baseSelect = baseSelect.leftOuterJoin(join.joinTable).on(join.joinColumn).equals(join.parentId); } + + return (SelectBuilder.SelectWhere) baseSelect; } - private void addColumnsForSimpleProperties(RelationalPersistentEntity currentEntity, String prefix, - String tableAlias, RelationalPersistentEntity rootEntity, SelectBuilder builder) { + @Nullable + Column getColumn(PersistentPropertyPathExtension path) { + + // an embedded itself doesn't give an column, its members will though. + // if there is a collection or map on the path it won't get selected at all, but it will get loaded with a separate + // select + // only the parent path is considered in order to handle arrays that get stored as BINARY properly + if (path.isEmbedded() || path.getParentPath().isMultiValued()) { + return null; + } + + if (path.isEntity()) { - for (RelationalPersistentProperty property : currentEntity) { + // Simple entities without id include there backreference as an synthetic id in order to distinguish null entities + // from entities with only null values. - if (property.isEntity()) { - continue; + if (path.isQualified() // + || path.isCollectionLike() // + || path.hasIdProperty() // + ) { + return null; } - final String column = prefix + property.getColumnName(); - final String as = tableAlias.isEmpty() ? column : tableAlias + "_" + column; + return sqlContext.getReverseColumn(path); - builder.column(cb -> cb // - .tableAlias(tableAlias.isEmpty() ? rootEntity.getTableName() : tableAlias) // - .column(column) // - .as(as)); } - } - private Stream getColumnNameStream(String prefix) { + return sqlContext.getColumn(path); - return StreamUtils.createStreamFromIterator(entity.iterator()) // - .flatMap(p -> getColumnNameStream(p, prefix)); } - private Stream getColumnNameStream(RelationalPersistentProperty p, String prefix) { + @Nullable + Join getJoin(PersistentPropertyPathExtension path) { - if (p.isEntity()) { - return sqlGeneratorSource.getSqlGenerator(p.getType()).getColumnNameStream(prefix + p.getColumnName() + "_"); - } else { - return Stream.of(prefix + p.getColumnName()); + if (!path.isEntity() || path.isEmbedded() || path.isMultiValued()) { + return null; } - } - private String createFindAllSql() { - return createSelectBuilder().build(); + Table currentTable = sqlContext.getTable(path); + + PersistentPropertyPathExtension idDefiningParentPath = path.getIdDefiningParentPath(); + Table parentTable = sqlContext.getTable(idDefiningParentPath); + + return new Join( // + currentTable, // + currentTable.column(path.getReverseColumnName()), // + parentTable.column(idDefiningParentPath.getIdColumnName()) // + ); } private String createFindAllInListSql() { - return createSelectBuilder() // - .where(wb -> wb.tableAlias(entity.getTableName()).column(entity.getIdColumn()).in().variable("ids")) // - .build(); + SelectBuilder.SelectWhereAndOr withCondition = createBaseSelect() + .where(sqlContext.getIdColumn().in(SQL.bindMarker(":ids"))); + + return render(withCondition); + } + + private String render(SelectBuilder.BuildSelect select) { + return SqlRenderer.create().render(select.build()); + } + + private String render(InsertBuilder.BuildInsert insert) { + return SqlRenderer.create().render(insert.build()); + } + + private String render(DeleteBuilder.BuildDelete delete) { + return SqlRenderer.create().render(delete.build()); + } + + private String render(UpdateBuilder.BuildUpdate update) { + return SqlRenderer.create().render(update.build()); } private String createExistsSql() { - return String.format("SELECT COUNT(*) FROM %s WHERE %s = :id", entity.getTableName(), entity.getIdColumn()); + + Table table = sqlContext.getTable(); + Column idColumn = table.column(entity.getIdColumn()); + + SelectBuilder.BuildSelect select = StatementBuilder // + .select(Functions.count(idColumn)) // + .from(table) // + .where(idColumn.isEqualTo(SQL.bindMarker(":id"))); + + return render(select); } private String createCountSql() { - return String.format("select count(*) from %s", entity.getTableName()); + + Table table = SQL.table(entity.getTableName()); + + SelectBuilder.BuildSelect select = StatementBuilder // + .select(Functions.count(Expressions.asterisk())) // + .from(table); + + return render(select); } private String createInsertSql(Set additionalColumns) { - String insertTemplate = "INSERT INTO %s (%s) VALUES (%s)"; + Table table = SQL.table(entity.getTableName()); LinkedHashSet columnNamesForInsert = new LinkedHashSet<>(nonIdColumnNames); columnNamesForInsert.addAll(additionalColumns); columnNamesForInsert.removeIf(readOnlyColumnNames::contains); - String tableColumns = String.join(", ", columnNamesForInsert); + InsertBuilder.InsertIntoColumnsAndValuesWithBuild insert = Insert.builder().into(table); - String parameterNames = columnNamesForInsert.stream()// - .map(this::columnNameToParameterName) - .map(n -> String.format(":%s", n))// - .collect(Collectors.joining(", ")); + for (String cn : columnNamesForInsert) { + insert = insert.column(table.column(cn)); + } + + InsertBuilder.InsertValuesWithBuild insertWithValues = null; + for (String cn : columnNamesForInsert) { + insertWithValues = (insertWithValues == null ? insert : insertWithValues) + .values(SQL.bindMarker(":" + columnNameToParameterName(cn))); + } - return String.format(insertTemplate, entity.getTableName(), tableColumns, parameterNames); + return render(insertWithValues == null ? insert : insertWithValues); } private String createUpdateSql() { - String updateTemplate = "UPDATE %s SET %s WHERE %s = :%s"; + Table table = SQL.table(entity.getTableName()); - String setClause = columnNames.stream() // + List assignments = columnNames.stream() // .filter(s -> !s.equals(entity.getIdColumn())) // .filter(s -> !readOnlyColumnNames.contains(s)) // - .map(n -> String.format("%s = :%s", n, columnNameToParameterName(n))) // - .collect(Collectors.joining(", ")); - - return String.format( // - updateTemplate, // - entity.getTableName(), // - setClause, // - entity.getIdColumn(), // - columnNameToParameterName(entity.getIdColumn()) // - ); + .map(columnName -> Assignments.value( // + table.column(columnName), // + SQL.bindMarker(":" + columnNameToParameterName(columnName)))) // + .collect(Collectors.toList()); + + UpdateBuilder.UpdateWhereAndOr update = Update.builder() // + .table(table) // + .set(assignments) // + .where(table.column(entity.getIdColumn()) + .isEqualTo(SQL.bindMarker(":" + columnNameToParameterName(entity.getIdColumn())))) // + ; + + return render(update); } private String createDeleteSql() { - return String.format("DELETE FROM %s WHERE %s = :id", entity.getTableName(), entity.getIdColumn()); + + Table table = SQL.table(entity.getTableName()); + + DeleteBuilder.DeleteWhereAndOr delete = Delete.builder().from(table) + .where(table.column(entity.getIdColumn()).isEqualTo(SQL.bindMarker(":id"))); + + return render(delete); } String createDeleteAllSql(@Nullable PersistentPropertyPath path) { - if (path == null) { - return String.format("DELETE FROM %s", entity.getTableName()); - } - - RelationalPersistentEntity entityToDelete = context - .getRequiredPersistentEntity(path.getRequiredLeafProperty().getActualType()); + Table table = SQL.table(entity.getTableName()); - final String innerMostCondition1 = createInnerMostCondition("%s IS NOT NULL", path); - String condition = cascadeConditions(innerMostCondition1, getSubPath(path)); + DeleteBuilder.DeleteWhere deleteAll = Delete.builder().from(table); - return String.format("DELETE FROM %s WHERE %s", entityToDelete.getTableName(), condition); + if (path == null) { + return render(deleteAll); + } + return createDeleteByPathAndCriteria(new PersistentPropertyPathExtension(mappingContext, path), Column::isNotNull); } private String createDeleteByListSql() { - return String.format("DELETE FROM %s WHERE %s IN (:ids)", entity.getTableName(), entity.getIdColumn()); - } - String createDeleteByPath(PersistentPropertyPath path) { + Table table = SQL.table(entity.getTableName()); - RelationalPersistentEntity entityToDelete = context - .getRequiredPersistentEntity(path.getRequiredLeafProperty().getActualType()); + DeleteBuilder.DeleteWhereAndOr delete = Delete.builder() // + .from(table) // + .where(table.column(entity.getIdColumn()).in(SQL.bindMarker(":ids"))); - final String innerMostCondition = createInnerMostCondition("%s = :rootId", path); - String condition = cascadeConditions(innerMostCondition, getSubPath(path)); + return render(delete); + } - return String.format("DELETE FROM %s WHERE %s", entityToDelete.getTableName(), condition); + String createDeleteByPath(PersistentPropertyPath path) { + return createDeleteByPathAndCriteria(new PersistentPropertyPathExtension(mappingContext, path), + filterColumn -> filterColumn.isEqualTo(SQL.bindMarker(":rootId"))); } - private String createInnerMostCondition(String template, PersistentPropertyPath path) { - PersistentPropertyPath currentPath = path; - while (!currentPath.getParentPath().isEmpty() - && !currentPath.getParentPath().getRequiredLeafProperty().isEmbedded()) { - currentPath = currentPath.getParentPath(); - } + private String createDeleteByPathAndCriteria(PersistentPropertyPathExtension path, + Function rootCondition) { - RelationalPersistentProperty property = currentPath.getRequiredLeafProperty(); - return String.format(template, property.getReverseColumnName()); - } + Table table = SQL.table(path.getTableName()); - private PersistentPropertyPath getSubPath( - PersistentPropertyPath path) { + DeleteBuilder.DeleteWhere delete = Delete.builder() // + .from(table); - int pathLength = path.getLength(); + DeleteBuilder.DeleteWhereAndOr deleteWithWhere; - PersistentPropertyPath ancestor = path; + Column filterColumn = table.column(path.getReverseColumnName()); - int embeddedDepth = 0; - while (!ancestor.getParentPath().isEmpty() && ancestor.getParentPath().getRequiredLeafProperty().isEmbedded()) { - embeddedDepth++; - ancestor = ancestor.getParentPath(); - } + if (path.getLength() == 1) { - ancestor = path; + deleteWithWhere = delete // + .where(rootCondition.apply(filterColumn)); + } else { - for (int i = pathLength - 1 + embeddedDepth; i > 0; i--) { - ancestor = ancestor.getParentPath(); + Condition condition = getSubselectCondition(path, rootCondition, filterColumn); + deleteWithWhere = delete.where(condition); } - - return path.getExtensionForBaseOf(ancestor); + return render(deleteWithWhere); } - private String cascadeConditions(String innerCondition, PersistentPropertyPath path) { + private Condition getSubselectCondition(PersistentPropertyPathExtension path, + Function rootCondition, Column filterColumn) { - if (path.getLength() == 0) { - return innerCondition; - } + PersistentPropertyPathExtension parentPath = path.getParentPath(); - PersistentPropertyPath rootPath = path; - while (rootPath.getLength() > 1) { - rootPath = rootPath.getParentPath(); - } + Table subSelectTable = SQL.table(parentPath.getTableName()); + Column idColumn = subSelectTable.column(parentPath.getIdColumnName()); + Column selectFilterColumn = subSelectTable.column(parentPath.getEffectiveIdColumnName()); - RelationalPersistentEntity entity = context - .getRequiredPersistentEntity(rootPath.getBaseProperty().getOwner().getTypeInformation()); - RelationalPersistentProperty property = path.getRequiredLeafProperty(); + Condition innerCondition = parentPath.getLength() == 1 ? rootCondition.apply(selectFilterColumn) + : getSubselectCondition(parentPath, rootCondition, selectFilterColumn); - return String.format("%s IN (SELECT %s FROM %s WHERE %s)", // - property.getReverseColumnName(), // - entity.getIdColumn(), // - entity.getTableName(), innerCondition // - ); + Select select = Select.builder() // + .select(idColumn) // + .from(subSelectTable) // + .where(innerCondition).build(); + + return filterColumn.in(select); } - private String columnNameToParameterName(String columnName){ + private String columnNameToParameterName(String columnName) { return parameterPattern.matcher(columnName).replaceAll(""); } + + @Value + class Join { + Table joinTable; + Column joinColumn; + Column parentId; + } + } diff --git a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/DefaultJdbcInterpreterUnitTests.java b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/DefaultJdbcInterpreterUnitTests.java index 0e089c6bc1..11efc3ffb8 100644 --- a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/DefaultJdbcInterpreterUnitTests.java +++ b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/DefaultJdbcInterpreterUnitTests.java @@ -21,7 +21,6 @@ import org.junit.Test; import org.mockito.ArgumentCaptor; - import org.springframework.data.annotation.Id; import org.springframework.data.jdbc.core.mapping.JdbcMappingContext; import org.springframework.data.relational.core.conversion.DbAction.Insert; @@ -55,7 +54,7 @@ public String getReverseColumnName(RelationalPersistentProperty property) { Element element = new Element(); InsertRoot containerInsert = new InsertRoot<>(container); - Insert insert = new Insert<>(element, PropertyPathUtils.toPath("element", Container.class, context), + Insert insert = new Insert<>(element, PropertyPathTestingUtils.toPath("element", Container.class, context), containerInsert); @Test // DATAJDBC-145 @@ -104,6 +103,7 @@ public void generatedIdOfParentGetsPassedOnAsAdditionalParameter() { .containsExactly(tuple(BACK_REFERENCE, CONTAINER_ID, Long.class)); } + @SuppressWarnings("unused") static class Container { @Id Long id; diff --git a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/JdbcAggregateTemplateIntegrationTests.java b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/JdbcAggregateTemplateIntegrationTests.java index 6bab2e0c02..dcbb0855d8 100644 --- a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/JdbcAggregateTemplateIntegrationTests.java +++ b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/JdbcAggregateTemplateIntegrationTests.java @@ -20,6 +20,8 @@ import lombok.Data; +import java.sql.ResultSet; +import java.sql.SQLException; import java.util.ArrayList; import java.util.Arrays; import java.util.HashSet; @@ -31,7 +33,6 @@ import org.junit.ClassRule; import org.junit.Rule; import org.junit.Test; - import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.ApplicationEventPublisher; import org.springframework.context.annotation.Bean; @@ -44,6 +45,9 @@ import org.springframework.data.relational.core.mapping.Column; import org.springframework.data.relational.core.mapping.RelationalMappingContext; import org.springframework.data.relational.core.mapping.Table; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.jdbc.core.RowMapper; +import org.springframework.jdbc.core.namedparam.NamedParameterJdbcOperations; import org.springframework.test.annotation.IfProfileValue; import org.springframework.test.annotation.ProfileValueSourceConfiguration; import org.springframework.test.annotation.ProfileValueUtils; @@ -67,7 +71,8 @@ public class JdbcAggregateTemplateIntegrationTests { @ClassRule public static final SpringClassRule classRule = new SpringClassRule(); @Rule public SpringMethodRule methodRule = new SpringMethodRule(); @Autowired JdbcAggregateOperations template; - + @Autowired + NamedParameterJdbcOperations jdbcTemplate; LegoSet legoSet = createLegoSet(); @Test // DATAJDBC-112 @@ -422,6 +427,7 @@ public void saveAndLoadAnEntityWithSet() { @Test // DATAJDBC-327 public void saveAndLoadAnEntityWithByteArray() { + ByteArrayOwner owner = new ByteArrayOwner(); owner.binaryData = new byte[] { 1, 23, 42 }; @@ -434,6 +440,35 @@ public void saveAndLoadAnEntityWithByteArray() { assertThat(reloaded.binaryData).isEqualTo(new byte[] { 1, 23, 42 }); } + @Test + public void saveAndLoadLongChain() { + + Chain4 chain4 = new Chain4(); + chain4.fourValue = "omega"; + chain4.chain3 = new Chain3(); + chain4.chain3.threeValue = "delta"; + chain4.chain3.chain2 = new Chain2(); + chain4.chain3.chain2.twoValue = "gamma"; + chain4.chain3.chain2.chain1 = new Chain1(); + chain4.chain3.chain2.chain1.oneValue = "beta"; + chain4.chain3.chain2.chain1.chain0 = new Chain0(); + chain4.chain3.chain2.chain1.chain0.zeroValue = "alpha"; + + template.save(chain4); + + Chain4 reloaded = template.findById(chain4.four, Chain4.class); + + assertThat(reloaded).isNotNull(); + + assertThat(reloaded.four).isEqualTo(chain4.four); + assertThat(reloaded.chain3.chain2.chain1.chain0.zeroValue).isEqualTo(chain4.chain3.chain2.chain1.chain0.zeroValue); + + template.delete(chain4, Chain4.class); + + String countSelect = "SELECT COUNT(*) FROM %s"; + jdbcTemplate.queryForObject(String.format(countSelect, "CHAIN0"),emptyMap(), Long.class); + } + private static void assumeNot(String dbProfileName) { Assume.assumeTrue("true" @@ -537,4 +572,33 @@ JdbcAggregateOperations operations(ApplicationEventPublisher publisher, Relation return new JdbcAggregateTemplate(publisher, context, converter, dataAccessStrategy); } } + + static class Chain0 { + @Id Long zero; + String zeroValue; + } + + static class Chain1 { + @Id Long one; + String oneValue; + Chain0 chain0; + } + + static class Chain2 { + @Id Long two; + String twoValue; + Chain1 chain1; + } + + static class Chain3 { + @Id Long three; + String threeValue; + Chain2 chain2; + } + + static class Chain4 { + @Id Long four; + String fourValue; + Chain3 chain3; + } } diff --git a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/JdbcIdentifierBuilderUnitTests.java b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/JdbcIdentifierBuilderUnitTests.java index 13ec82a143..90db074d64 100644 --- a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/JdbcIdentifierBuilderUnitTests.java +++ b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/JdbcIdentifierBuilderUnitTests.java @@ -16,7 +16,7 @@ package org.springframework.data.jdbc.core; import static org.assertj.core.api.Assertions.*; -import static org.springframework.data.jdbc.core.PropertyPathUtils.*; +import static org.springframework.data.jdbc.core.PropertyPathTestingUtils.*; import java.util.List; import java.util.Map; @@ -24,7 +24,6 @@ import org.jetbrains.annotations.NotNull; import org.junit.Test; - import org.springframework.data.annotation.Id; import org.springframework.data.jdbc.core.convert.JdbcIdentifierBuilder; import org.springframework.data.jdbc.core.mapping.JdbcMappingContext; @@ -49,8 +48,8 @@ public void parametersWithPropertyKeysUseTheParentPropertyJdbcType() { assertThat(identifier.getParts()) // .extracting("name", "value", "targetType") // .containsExactly( // - tuple("dummy_entity", "eins", UUID.class) // - ); + tuple("dummy_entity", "eins", UUID.class) // + ); } @Test // DATAJDBC-326 @@ -66,9 +65,9 @@ public void qualifiersForMaps() { assertThat(identifier.getParts()) // .extracting("name", "value", "targetType") // .containsExactlyInAnyOrder( // - tuple("dummy_entity", "parent-eins", UUID.class), // - tuple("dummy_entity_key", "map-key-eins", String.class) // - ); + tuple("dummy_entity", "parent-eins", UUID.class), // + tuple("dummy_entity_key", "map-key-eins", String.class) // + ); } @Test // DATAJDBC-326 @@ -84,9 +83,9 @@ public void qualifiersForLists() { assertThat(identifier.getParts()) // .extracting("name", "value", "targetType") // .containsExactlyInAnyOrder( // - tuple("dummy_entity", "parent-eins", UUID.class), // - tuple("dummy_entity_key", "list-index-eins", Integer.class) // - ); + tuple("dummy_entity", "parent-eins", UUID.class), // + tuple("dummy_entity_key", "list-index-eins", Integer.class) // + ); } @Test // DATAJDBC-326 @@ -99,8 +98,8 @@ public void backreferenceAcrossEmbeddable() { assertThat(identifier.getParts()) // .extracting("name", "value", "targetType") // .containsExactly( // - tuple("embeddable", "parent-eins", UUID.class) // - ); + tuple("embeddable", "parent-eins", UUID.class) // + ); } @NotNull diff --git a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/MyBatisDataAccessStrategyUnitTests.java b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/MyBatisDataAccessStrategyUnitTests.java index b4fa8263f6..7e5ccab554 100644 --- a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/MyBatisDataAccessStrategyUnitTests.java +++ b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/MyBatisDataAccessStrategyUnitTests.java @@ -47,11 +47,7 @@ public class MyBatisDataAccessStrategyUnitTests { MyBatisDataAccessStrategy accessStrategy = new MyBatisDataAccessStrategy(session); - PersistentPropertyPath path(String path, Class source) { - - RelationalMappingContext context = this.context; - return PropertyPathUtils.toPath(path, source, context); - } + PersistentPropertyPath path = PropertyPathTestingUtils.toPath("one.two", DummyEntity.class, context); @Before public void before() { @@ -128,7 +124,7 @@ public void delete() { @Test // DATAJDBC-123 public void deleteAllByPath() { - accessStrategy.deleteAll(path("one.two", DummyEntity.class)); + accessStrategy.deleteAll(path); verify(session).delete( eq("org.springframework.data.jdbc.core.MyBatisDataAccessStrategyUnitTests$DummyEntityMapper.deleteAll-one-two"), @@ -174,7 +170,7 @@ public void deleteAllByType() { @Test // DATAJDBC-123 public void deleteByPath() { - accessStrategy.delete("rootid", path("one.two", DummyEntity.class)); + accessStrategy.delete("rootid", path); verify(session).delete( eq("org.springframework.data.jdbc.core.MyBatisDataAccessStrategyUnitTests$DummyEntityMapper.delete-one-two"), @@ -334,10 +330,12 @@ public void count() { ); } + @SuppressWarnings("unused") private static class DummyEntity { ChildOne one; } + @SuppressWarnings("unused") private static class ChildOne { ChildTwo two; } diff --git a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/PersistentPropertyPathExtensionUnitTests.java b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/PersistentPropertyPathExtensionUnitTests.java new file mode 100644 index 0000000000..6e9f48dced --- /dev/null +++ b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/PersistentPropertyPathExtensionUnitTests.java @@ -0,0 +1,174 @@ +/* + * Copyright 2019 the original author or authors. + * + * Licensed 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.springframework.data.jdbc.core; + +import java.util.List; + +import org.assertj.core.api.SoftAssertions; +import org.jetbrains.annotations.NotNull; +import org.junit.Test; +import org.springframework.data.jdbc.core.mapping.JdbcMappingContext; +import org.springframework.data.mapping.PersistentPropertyPath; +import org.springframework.data.relational.core.mapping.Embedded; +import org.springframework.data.relational.core.mapping.RelationalPersistentEntity; +import org.springframework.data.relational.core.mapping.RelationalPersistentProperty; + +/** + * @author Jens Schauder + */ +public class PersistentPropertyPathExtensionUnitTests { + + JdbcMappingContext context = new JdbcMappingContext(); + private RelationalPersistentEntity entity = context.getRequiredPersistentEntity(DummyEntity.class); + + @Test + public void isEmbedded() { + + SoftAssertions.assertSoftly(softly -> { + + softly.assertThat(extPath(entity).isEmbedded()).isFalse(); + softly.assertThat(extPath("second").isEmbedded()).isFalse(); + softly.assertThat(extPath("second.third").isEmbedded()).isTrue(); + }); + } + + @Test + public void isMultiValued() { + + SoftAssertions.assertSoftly(softly -> { + + softly.assertThat(extPath(entity).isMultiValued()).isFalse(); + softly.assertThat(extPath("second").isMultiValued()).isFalse(); + softly.assertThat(extPath("second.third").isMultiValued()).isFalse(); + softly.assertThat(extPath("secondList.third").isMultiValued()).isTrue(); + softly.assertThat(extPath("secondList").isMultiValued()).isTrue(); + }); + } + + @Test + public void leafEntity() { + + RelationalPersistentEntity second = context.getRequiredPersistentEntity(Second.class); + RelationalPersistentEntity third = context.getRequiredPersistentEntity(Third.class); + + SoftAssertions.assertSoftly(softly -> { + + softly.assertThat(extPath(entity).getLeafEntity()).isEqualTo(entity); + softly.assertThat(extPath("second").getLeafEntity()).isEqualTo(second); + softly.assertThat(extPath("second.third").getLeafEntity()).isEqualTo(third); + softly.assertThat(extPath("secondList.third").getLeafEntity()).isEqualTo(third); + softly.assertThat(extPath("secondList").getLeafEntity()).isEqualTo(second); + }); + } + + @Test + public void isEntity() { + + SoftAssertions.assertSoftly(softly -> { + + softly.assertThat(extPath(entity).isEntity()).isTrue(); + String path = "second"; + softly.assertThat(extPath(path).isEntity()).isTrue(); + softly.assertThat(extPath("second.third").isEntity()).isTrue(); + softly.assertThat(extPath("second.third.value").isEntity()).isFalse(); + softly.assertThat(extPath("secondList.third").isEntity()).isTrue(); + softly.assertThat(extPath("secondList.third.value").isEntity()).isFalse(); + softly.assertThat(extPath("secondList").isEntity()).isTrue(); + }); + } + + @Test + public void getTableName() { + + SoftAssertions.assertSoftly(softly -> { + + softly.assertThat(extPath(entity).getTableName()).isEqualTo("dummy_entity"); + softly.assertThat(extPath("second").getTableName()).isEqualTo("second"); + softly.assertThat(extPath("second.third").getTableName()).isEqualTo("second"); + softly.assertThat(extPath("second.third.value").getTableName()).isEqualTo("second"); + softly.assertThat(extPath("secondList.third").getTableName()).isEqualTo("second"); + softly.assertThat(extPath("secondList.third.value").getTableName()).isEqualTo("second"); + softly.assertThat(extPath("secondList").getTableName()).isEqualTo("second"); + }); + } + + @Test + public void getTableAlias() { + + SoftAssertions.assertSoftly(softly -> { + + softly.assertThat(extPath(entity).getTableAlias()).isEqualTo(null); + softly.assertThat(extPath("second").getTableAlias()).isEqualTo("second"); + softly.assertThat(extPath("second.third").getTableAlias()).isEqualTo("second"); + softly.assertThat(extPath("second.third.value").getTableAlias()).isEqualTo("second"); + softly.assertThat(extPath("second.third2").getTableAlias()).isEqualTo("second_third2"); + softly.assertThat(extPath("second.third2.value").getTableAlias()).isEqualTo("second_third2"); + softly.assertThat(extPath("secondList.third").getTableAlias()).isEqualTo("secondList"); + softly.assertThat(extPath("secondList.third.value").getTableAlias()).isEqualTo("secondList"); + softly.assertThat(extPath("secondList.third2").getTableAlias()).isEqualTo("secondList_third2"); + softly.assertThat(extPath("secondList.third2.value").getTableAlias()).isEqualTo("secondList_third2"); + softly.assertThat(extPath("secondList").getTableAlias()).isEqualTo("secondList"); + softly.assertThat(extPath("second2.third2").getTableAlias()).isEqualTo("secthird2"); + }); + } + + @Test + public void getColumnName() { + + SoftAssertions.assertSoftly(softly -> { + + softly.assertThat(extPath("second.third.value").getColumnName()).isEqualTo("thrdvalue"); + softly.assertThat(extPath("second.third2.value").getColumnName()).isEqualTo("value"); + softly.assertThat(extPath("secondList.third.value").getColumnName()).isEqualTo("thrdvalue"); + softly.assertThat(extPath("secondList.third2.value").getColumnName()).isEqualTo("value"); + softly.assertThat(extPath("second2.third.value").getColumnName()).isEqualTo("secthrdvalue"); + softly.assertThat(extPath("second2.third2.value").getColumnName()).isEqualTo("value"); + }); + } + + @NotNull + private PersistentPropertyPathExtension extPath(RelationalPersistentEntity entity) { + return new PersistentPropertyPathExtension(context, entity); + } + + @NotNull + private PersistentPropertyPathExtension extPath(String path) { + return new PersistentPropertyPathExtension(context, createSimplePath(path)); + } + + PersistentPropertyPath createSimplePath(String path) { + return PropertyPathTestingUtils.toPath(path, DummyEntity.class, context); + } + + @SuppressWarnings("unused") + static class DummyEntity { + Second second; + List secondList; + @Embedded("sec") Second second2; + } + + @SuppressWarnings("unused") + static class Second { + @Embedded("thrd") Third third; + Third third2; + } + + @SuppressWarnings("unused") + static class Third { + String value; + } + +} diff --git a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/PropertyPathUtils.java b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/PropertyPathTestingUtils.java similarity index 97% rename from spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/PropertyPathUtils.java rename to spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/PropertyPathTestingUtils.java index 6db1a82031..0dcf7f4210 100644 --- a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/PropertyPathUtils.java +++ b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/PropertyPathTestingUtils.java @@ -29,7 +29,7 @@ * @author Jens Schauder */ @UtilityClass -class PropertyPathUtils { +class PropertyPathTestingUtils { static PersistentPropertyPath toPath(String path, Class source, RelationalMappingContext context) { diff --git a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/SelectBuilderUnitTests.java b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/SelectBuilderUnitTests.java deleted file mode 100644 index 9b3e044565..0000000000 --- a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/SelectBuilderUnitTests.java +++ /dev/null @@ -1,89 +0,0 @@ -/* - * Copyright 2017-2019 the original author or authors. - * - * Licensed 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.springframework.data.jdbc.core; - -import static org.assertj.core.api.Assertions.*; - -import org.junit.Test; - -/** - * Unit tests for the {@link SelectBuilder}. - * - * @author Jens Schauder - */ -public class SelectBuilderUnitTests { - - @Test // DATAJDBC-112 - public void simplestSelect() { - - String sql = new SelectBuilder("mytable") // - .column(cb -> cb.tableAlias("mytable").column("mycolumn").as("myalias")) // - .build(); - - assertThat(sql).isEqualTo("SELECT mytable.mycolumn AS myalias FROM mytable"); - } - - @Test // DATAJDBC-112 - public void columnWithoutTableAlias() { - - String sql = new SelectBuilder("mytable") // - .column(cb -> cb.column("mycolumn").as("myalias")) // - .build(); - - assertThat(sql).isEqualTo("SELECT mycolumn AS myalias FROM mytable"); - } - - @Test // DATAJDBC-112 - public void whereClause() { - - String sql = new SelectBuilder("mytable") // - .column(cb -> cb.tableAlias("mytable").column("mycolumn").as("myalias")) // - .where(cb -> cb.tableAlias("mytable").column("mycolumn").eq().variable("var")).build(); - - assertThat(sql).isEqualTo("SELECT mytable.mycolumn AS myalias FROM mytable WHERE mytable.mycolumn = :var"); - } - - @Test // DATAJDBC-112 - public void multipleColumnsSelect() { - - String sql = new SelectBuilder("mytable") // - .column(cb -> cb.tableAlias("mytable").column("one").as("oneAlias")) // - .column(cb -> cb.tableAlias("mytable").column("two").as("twoAlias")) // - .build(); - - assertThat(sql).isEqualTo("SELECT mytable.one AS oneAlias, mytable.two AS twoAlias FROM mytable"); - } - - @Test // DATAJDBC-112 - public void join() { - String sql = new SelectBuilder("mytable") // - .column(cb -> cb.tableAlias("mytable").column("mycolumn").as("myalias")) // - .join(jb -> jb.table("other").as("o").where("oid").eq().column("mytable", "id")).build(); - - assertThat(sql).isEqualTo("SELECT mytable.mycolumn AS myalias FROM mytable JOIN other AS o ON o.oid = mytable.id"); - } - - @Test // DATAJDBC-112 - public void outerJoin() { - String sql = new SelectBuilder("mytable") // - .column(cb -> cb.tableAlias("mytable").column("mycolumn").as("myalias")) // - .join(jb -> jb.rightOuter().table("other").as("o").where("oid").eq().column("mytable", "id")).build(); - - assertThat(sql) - .isEqualTo("SELECT mytable.mycolumn AS myalias FROM mytable RIGHT OUTER JOIN other AS o ON o.oid = mytable.id"); - } - -} diff --git a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/SqlGeneratorContextBasedNamingStrategyUnitTests.java b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/SqlGeneratorContextBasedNamingStrategyUnitTests.java index 3e435e2b6f..03522ba15b 100644 --- a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/SqlGeneratorContextBasedNamingStrategyUnitTests.java +++ b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/SqlGeneratorContextBasedNamingStrategyUnitTests.java @@ -84,9 +84,10 @@ public void cascadingDeleteFirstLevel() { SqlGenerator sqlGenerator = configureSqlGenerator(contextualNamingStrategy); - String sql = sqlGenerator.createDeleteByPath(getPath("ref", DummyEntity.class)); + String sql = sqlGenerator.createDeleteByPath(getPath("ref")); - assertThat(sql).isEqualTo("DELETE FROM " + user + ".referenced_entity WHERE " + "dummy_entity = :rootId"); + assertThat(sql).isEqualTo( + "DELETE FROM " + user + ".referenced_entity WHERE " + user + ".referenced_entity.dummy_entity = :rootId"); }); } @@ -97,13 +98,13 @@ public void cascadingDeleteAllSecondLevel() { SqlGenerator sqlGenerator = configureSqlGenerator(contextualNamingStrategy); - String sql = sqlGenerator.createDeleteByPath(getPath("ref.further", DummyEntity.class)); + String sql = sqlGenerator.createDeleteByPath(getPath("ref.further")); assertThat(sql).isEqualTo( // "DELETE FROM " + user + ".second_level_referenced_entity " // - + "WHERE " + "referenced_entity IN " // - + "(SELECT l1id FROM " + user + ".referenced_entity " // - + "WHERE " + "dummy_entity = :rootId)"); + + "WHERE " + user + ".second_level_referenced_entity.referenced_entity IN " // + + "(SELECT " + user + ".referenced_entity.l1id FROM " + user + ".referenced_entity " // + + "WHERE " + user + ".referenced_entity.dummy_entity = :rootId)"); }); } @@ -127,10 +128,10 @@ public void cascadingDeleteAllFirstLevel() { SqlGenerator sqlGenerator = configureSqlGenerator(contextualNamingStrategy); - String sql = sqlGenerator.createDeleteAllSql(getPath("ref", DummyEntity.class)); + String sql = sqlGenerator.createDeleteAllSql(getPath("ref")); assertThat(sql).isEqualTo( // - "DELETE FROM " + user + ".referenced_entity WHERE " + "dummy_entity IS NOT NULL"); + "DELETE FROM " + user + ".referenced_entity WHERE " + user + ".referenced_entity.dummy_entity IS NOT NULL"); }); } @@ -141,18 +142,18 @@ public void cascadingDeleteSecondLevel() { SqlGenerator sqlGenerator = configureSqlGenerator(contextualNamingStrategy); - String sql = sqlGenerator.createDeleteAllSql(getPath("ref.further", DummyEntity.class)); + String sql = sqlGenerator.createDeleteAllSql(getPath("ref.further")); assertThat(sql).isEqualTo( // "DELETE FROM " + user + ".second_level_referenced_entity " // - + "WHERE " + "referenced_entity IN " // - + "(SELECT l1id FROM " + user + ".referenced_entity " // - + "WHERE " + "dummy_entity IS NOT NULL)"); + + "WHERE " + user + ".second_level_referenced_entity.referenced_entity IN " // + + "(SELECT " + user + ".referenced_entity.l1id FROM " + user + ".referenced_entity " // + + "WHERE " + user + ".referenced_entity.dummy_entity IS NOT NULL)"); }); } - private PersistentPropertyPath getPath(String path, Class baseType) { - return PersistentPropertyPathTestUtils.getPath(this.context, path, baseType); + private PersistentPropertyPath getPath(String path) { + return PersistentPropertyPathTestUtils.getPath(this.context, path, DummyEntity.class); } /** @@ -214,6 +215,7 @@ private SqlGenerator configureSqlGenerator(NamingStrategy namingStrategy) { return new SqlGenerator(context, persistentEntity, new SqlGeneratorSource(context)); } + @SuppressWarnings("unused") static class DummyEntity { @Id Long id; @@ -221,6 +223,7 @@ static class DummyEntity { ReferencedEntity ref; } + @SuppressWarnings("unused") static class ReferencedEntity { @Id Long l1id; @@ -228,6 +231,7 @@ static class ReferencedEntity { SecondLevelReferencedEntity further; } + @SuppressWarnings("unused") static class SecondLevelReferencedEntity { @Id Long l2id; diff --git a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/SqlGeneratorEmbeddedUnitTests.java b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/SqlGeneratorEmbeddedUnitTests.java index e3af893f33..67a20dcdef 100644 --- a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/SqlGeneratorEmbeddedUnitTests.java +++ b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/SqlGeneratorEmbeddedUnitTests.java @@ -16,9 +16,11 @@ package org.springframework.data.jdbc.core; import static java.util.Collections.*; +import static org.assertj.core.api.Assertions.*; import org.assertj.core.api.SoftAssertions; import org.junit.Before; +import org.junit.Ignore; import org.junit.Test; import org.springframework.data.annotation.Id; import org.springframework.data.jdbc.core.mapping.JdbcMappingContext; @@ -26,6 +28,7 @@ import org.springframework.data.relational.core.mapping.Embedded; import org.springframework.data.relational.core.mapping.RelationalMappingContext; import org.springframework.data.relational.core.mapping.RelationalPersistentEntity; +import org.springframework.data.relational.core.sql.Aliased; /** * Unit tests for the {@link SqlGenerator} in a context of the {@link Embedded} annotation. @@ -34,6 +37,7 @@ */ public class SqlGeneratorEmbeddedUnitTests { + private RelationalMappingContext context = new JdbcMappingContext(); private SqlGenerator sqlGenerator; @Before @@ -42,7 +46,6 @@ public void setUp() { } SqlGenerator createSqlGenerator(Class type) { - RelationalMappingContext context = new JdbcMappingContext(); RelationalPersistentEntity persistentEntity = context.getRequiredPersistentEntity(type); return new SqlGenerator(context, persistentEntity, new SqlGeneratorSource(context)); } @@ -52,44 +55,31 @@ public void findOne() { final String sql = sqlGenerator.getFindOne(); SoftAssertions softAssertions = new SoftAssertions(); - softAssertions.assertThat(sql) - .startsWith("SELECT") - .contains("dummy_entity.id1 AS id1") - .contains("dummy_entity.test AS test") - .contains("dummy_entity.attr1 AS attr1") - .contains("dummy_entity.attr2 AS attr2") - .contains("dummy_entity.prefix2_attr1 AS prefix2_attr1") - .contains("dummy_entity.prefix2_attr2 AS prefix2_attr2") - .contains("dummy_entity.prefix_test AS prefix_test") - .contains("dummy_entity.prefix_attr1 AS prefix_attr1") - .contains("dummy_entity.prefix_attr2 AS prefix_attr2") + softAssertions.assertThat(sql).startsWith("SELECT").contains("dummy_entity.id1 AS id1") + .contains("dummy_entity.test AS test").contains("dummy_entity.attr1 AS attr1") + .contains("dummy_entity.attr2 AS attr2").contains("dummy_entity.prefix2_attr1 AS prefix2_attr1") + .contains("dummy_entity.prefix2_attr2 AS prefix2_attr2").contains("dummy_entity.prefix_test AS prefix_test") + .contains("dummy_entity.prefix_attr1 AS prefix_attr1").contains("dummy_entity.prefix_attr2 AS prefix_attr2") .contains("dummy_entity.prefix_prefix2_attr1 AS prefix_prefix2_attr1") - .contains("dummy_entity.prefix_prefix2_attr2 AS prefix_prefix2_attr2") - .contains("WHERE dummy_entity.id1 = :id") + .contains("dummy_entity.prefix_prefix2_attr2 AS prefix_prefix2_attr2").contains("WHERE dummy_entity.id1 = :id") .doesNotContain("JOIN").doesNotContain("embeddable"); softAssertions.assertAll(); } @Test // DATAJDBC-111 - public void findAll() { - final String sql = sqlGenerator.getFindAll(); - - SoftAssertions softAssertions = new SoftAssertions(); - softAssertions.assertThat(sql) - .startsWith("SELECT") - .contains("dummy_entity.id1 AS id1") - .contains("dummy_entity.test AS test") - .contains("dummy_entity.attr1 AS attr1") - .contains("dummy_entity.attr2 AS attr2") - .contains("dummy_entity.prefix2_attr1 AS prefix2_attr1") - .contains("dummy_entity.prefix2_attr2 AS prefix2_attr2") - .contains("dummy_entity.prefix_test AS prefix_test") - .contains("dummy_entity.prefix_attr1 AS prefix_attr1") - .contains("dummy_entity.prefix_attr2 AS prefix_attr2") - .contains("dummy_entity.prefix_prefix2_attr1 AS prefix_prefix2_attr1") - .contains("dummy_entity.prefix_prefix2_attr2 AS prefix_prefix2_attr2") - .doesNotContain("JOIN").doesNotContain("embeddable"); - softAssertions.assertAll(); + public void findAll() { + final String sql = sqlGenerator.getFindAll(); + + SoftAssertions softAssertions = new SoftAssertions(); + softAssertions.assertThat(sql).startsWith("SELECT").contains("dummy_entity.id1 AS id1") + .contains("dummy_entity.test AS test").contains("dummy_entity.attr1 AS attr1") + .contains("dummy_entity.attr2 AS attr2").contains("dummy_entity.prefix2_attr1 AS prefix2_attr1") + .contains("dummy_entity.prefix2_attr2 AS prefix2_attr2").contains("dummy_entity.prefix_test AS prefix_test") + .contains("dummy_entity.prefix_attr1 AS prefix_attr1").contains("dummy_entity.prefix_attr2 AS prefix_attr2") + .contains("dummy_entity.prefix_prefix2_attr1 AS prefix_prefix2_attr1") + .contains("dummy_entity.prefix_prefix2_attr2 AS prefix_prefix2_attr2").doesNotContain("JOIN") + .doesNotContain("embeddable"); + softAssertions.assertAll(); } @Test // DATAJDBC-111 @@ -97,21 +87,14 @@ public void findAllInList() { final String sql = sqlGenerator.getFindAllInList(); SoftAssertions softAssertions = new SoftAssertions(); - softAssertions.assertThat(sql) - .startsWith("SELECT") - .contains("dummy_entity.id1 AS id1") - .contains("dummy_entity.test AS test") - .contains("dummy_entity.attr1 AS attr1") - .contains("dummy_entity.attr2 AS attr2") - .contains("dummy_entity.prefix2_attr1 AS prefix2_attr1") - .contains("dummy_entity.prefix2_attr2 AS prefix2_attr2") - .contains("dummy_entity.prefix_test AS prefix_test") - .contains("dummy_entity.prefix_attr1 AS prefix_attr1") - .contains("dummy_entity.prefix_attr2 AS prefix_attr2") + softAssertions.assertThat(sql).startsWith("SELECT").contains("dummy_entity.id1 AS id1") + .contains("dummy_entity.test AS test").contains("dummy_entity.attr1 AS attr1") + .contains("dummy_entity.attr2 AS attr2").contains("dummy_entity.prefix2_attr1 AS prefix2_attr1") + .contains("dummy_entity.prefix2_attr2 AS prefix2_attr2").contains("dummy_entity.prefix_test AS prefix_test") + .contains("dummy_entity.prefix_attr1 AS prefix_attr1").contains("dummy_entity.prefix_attr2 AS prefix_attr2") .contains("dummy_entity.prefix_prefix2_attr1 AS prefix_prefix2_attr1") .contains("dummy_entity.prefix_prefix2_attr2 AS prefix_prefix2_attr2") - .contains("WHERE dummy_entity.id1 in(:ids)") - .doesNotContain("JOIN").doesNotContain("embeddable"); + .contains("WHERE dummy_entity.id1 IN (:ids)").doesNotContain("JOIN").doesNotContain("embeddable"); softAssertions.assertAll(); } @@ -120,18 +103,9 @@ public void insert() { final String sql = sqlGenerator.getInsert(emptySet()); SoftAssertions softAssertions = new SoftAssertions(); - softAssertions.assertThat(sql) - .startsWith("INSERT INTO") - .contains("dummy_entity") - .contains(":test") - .contains(":attr1") - .contains(":attr2") - .contains(":prefix2_attr1") - .contains(":prefix2_attr2") - .contains(":prefix_test") - .contains(":prefix_attr1") - .contains(":prefix_attr2") - .contains(":prefix_prefix2_attr1") + softAssertions.assertThat(sql).startsWith("INSERT INTO").contains("dummy_entity").contains(":test") + .contains(":attr1").contains(":attr2").contains(":prefix2_attr1").contains(":prefix2_attr2") + .contains(":prefix_test").contains(":prefix_attr1").contains(":prefix_attr2").contains(":prefix_prefix2_attr1") .contains(":prefix_prefix2_attr2"); softAssertions.assertAll(); } @@ -141,48 +115,166 @@ public void update() { final String sql = sqlGenerator.getUpdate(); SoftAssertions softAssertions = new SoftAssertions(); - softAssertions.assertThat(sql) - .startsWith("UPDATE") - .contains("dummy_entity") - .contains("test = :test") - .contains("attr1 = :attr1") - .contains("attr2 = :attr2") - .contains("prefix2_attr1 = :prefix2_attr1") - .contains("prefix2_attr2 = :prefix2_attr2") - .contains("prefix_test = :prefix_test") - .contains("prefix_attr1 = :prefix_attr1") - .contains("prefix_attr2 = :prefix_attr2") + softAssertions.assertThat(sql).startsWith("UPDATE").contains("dummy_entity").contains("test = :test") + .contains("attr1 = :attr1").contains("attr2 = :attr2").contains("prefix2_attr1 = :prefix2_attr1") + .contains("prefix2_attr2 = :prefix2_attr2").contains("prefix_test = :prefix_test") + .contains("prefix_attr1 = :prefix_attr1").contains("prefix_attr2 = :prefix_attr2") .contains("prefix_prefix2_attr1 = :prefix_prefix2_attr1") .contains("prefix_prefix2_attr2 = :prefix_prefix2_attr2"); softAssertions.assertAll(); } + @Test // DATAJDBC-340 + @Ignore // this is just broken right now + public void deleteByPath() { + + final String sql = sqlGenerator + .createDeleteByPath(PropertyPathTestingUtils.toPath("embedded.other", DummyEntity2.class, context)); + + assertThat(sql).containsSequence("DELETE FROM other_entity", // + "WHERE", // + "embedded_with_reference IN (", // + "SELECT ", // + "id ", // + "FROM", // + "dummy_entity2", // + "WHERE", // + "embedded_with_reference = :rootId"); + } + + @Test // DATAJDBC-340 + public void noJoinForEmbedded() { + + SqlGenerator.Join join = generateJoin("embeddable", DummyEntity.class); + + assertThat(join).isNull(); + } + + @Test // DATAJDBC-340 + public void columnForEmbeddedProperty() { + + assertThat(generatedColumn("embeddable.test", DummyEntity.class)) // + .extracting(c -> c.getName(), c -> c.getTable().getName(), c -> getAlias(c.getTable()), this::getAlias) + .containsExactly("test", "dummy_entity", null, "test"); + } + + @Test // DATAJDBC-340 + public void noColumnForEmbedded() { + + assertThat(generatedColumn("embeddable", DummyEntity.class)) // + .isNull(); + } + + @Test // DATAJDBC-340 + public void noJoinForPrefixedEmbedded() { + + SqlGenerator.Join join = generateJoin("prefixedEmbeddable", DummyEntity.class); + + assertThat(join).isNull(); + } + + @Test // DATAJDBC-340 + public void columnForPrefixedEmbeddedProperty() { + + assertThat(generatedColumn("prefixedEmbeddable.test", DummyEntity.class)) // + .extracting(c -> c.getName(), c -> c.getTable().getName(), c -> getAlias(c.getTable()), this::getAlias) + .containsExactly("prefix_test", "dummy_entity", null, "prefix_test"); + } + + @Test // DATAJDBC-340 + public void noJoinForCascadedEmbedded() { + + SqlGenerator.Join join = generateJoin("embeddable.embeddable", DummyEntity.class); + + assertThat(join).isNull(); + } + + @Test // DATAJDBC-340 + public void columnForCascadedEmbeddedProperty() { + + assertThat(generatedColumn("embeddable.embeddable.attr1", DummyEntity.class)) // + .extracting(c -> c.getName(), c -> c.getTable().getName(), c -> getAlias(c.getTable()), this::getAlias) + .containsExactly("attr1", "dummy_entity", null, "attr1"); + } + + @Test // DATAJDBC-340 + public void joinForEmbeddedWithReference() { + + SqlGenerator.Join join = generateJoin("embedded.other", DummyEntity2.class); + + SoftAssertions.assertSoftly(softly -> { + + softly.assertThat(join.getJoinTable().getName()).isEqualTo("other_entity"); + softly.assertThat(join.getJoinColumn().getTable()).isEqualTo(join.getJoinTable()); + softly.assertThat(join.getJoinColumn().getName()).isEqualTo("embedded_with_reference"); + softly.assertThat(join.getParentId().getName()).isEqualTo("id"); + softly.assertThat(join.getParentId().getTable().getName()).isEqualTo("dummy_entity2"); + }); + } + + @Test // DATAJDBC-340 + public void columnForEmbeddedWithReferenceProperty() { + + assertThat(generatedColumn("embedded.other.value", DummyEntity2.class)) // + .extracting(c -> c.getName(), c -> c.getTable().getName(), c -> getAlias(c.getTable()), this::getAlias) + .containsExactly("value", "other_entity", "prefix_other", "prefix_other_value"); + } + + private SqlGenerator.Join generateJoin(String path, Class type) { + return createSqlGenerator(type) + .getJoin(new PersistentPropertyPathExtension(context, PropertyPathTestingUtils.toPath(path, type, context))); + } + + private String getAlias(Object maybeAliased) { + + if (maybeAliased instanceof Aliased) { + return ((Aliased) maybeAliased).getAlias(); + } + return null; + } + + private org.springframework.data.relational.core.sql.Column generatedColumn(String path, Class type) { + return createSqlGenerator(type) + .getColumn(new PersistentPropertyPathExtension(context, PropertyPathTestingUtils.toPath(path, type, context))); + } + @SuppressWarnings("unused") static class DummyEntity { - @Column("id1") - @Id - Long id; + @Column("id1") @Id Long id; - @Embedded("prefix_") - CascadedEmbedded prefixedEmbeddable; + @Embedded("prefix_") CascadedEmbedded prefixedEmbeddable; - @Embedded - CascadedEmbedded embeddable; + @Embedded CascadedEmbedded embeddable; } @SuppressWarnings("unused") - static class CascadedEmbedded - { + static class CascadedEmbedded { String test; @Embedded("prefix2_") Embeddable prefixedEmbeddable; @Embedded Embeddable embeddable; } @SuppressWarnings("unused") - static class Embeddable - { + static class Embeddable { Long attr1; String attr2; } + + @SuppressWarnings("unused") + static class DummyEntity2 { + + @Id Long id; + + @Embedded("prefix_") EmbeddedWithReference embedded; + } + + static class EmbeddedWithReference { + OtherEntity other; + } + + static class OtherEntity { + String value; + } + } diff --git a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/SqlGeneratorFixedNamingStrategyUnitTests.java b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/SqlGeneratorFixedNamingStrategyUnitTests.java index 5fbc08eccf..b8a87d1913 100644 --- a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/SqlGeneratorFixedNamingStrategyUnitTests.java +++ b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/SqlGeneratorFixedNamingStrategyUnitTests.java @@ -112,10 +112,10 @@ public void cascadingDeleteFirstLevel() { SqlGenerator sqlGenerator = configureSqlGenerator(fixedCustomTablePrefixStrategy); - String sql = sqlGenerator.createDeleteByPath(getPath("ref", DummyEntity.class)); + String sql = sqlGenerator.createDeleteByPath(getPath("ref")); - assertThat(sql).isEqualTo( - "DELETE FROM FixedCustomSchema.FixedCustomTablePrefix_ReferencedEntity " + "WHERE dummy_entity = :rootId"); + assertThat(sql).isEqualTo("DELETE FROM FixedCustomSchema.FixedCustomTablePrefix_ReferencedEntity " + + "WHERE FixedCustomSchema.FixedCustomTablePrefix_ReferencedEntity.dummy_entity = :rootId"); } @Test // DATAJDBC-107 @@ -123,11 +123,13 @@ public void cascadingDeleteAllSecondLevel() { SqlGenerator sqlGenerator = configureSqlGenerator(fixedCustomTablePrefixStrategy); - String sql = sqlGenerator.createDeleteByPath(getPath("ref.further", DummyEntity.class)); + String sql = sqlGenerator.createDeleteByPath(getPath("ref.further")); assertThat(sql).isEqualTo("DELETE FROM FixedCustomSchema.FixedCustomTablePrefix_SecondLevelReferencedEntity " - + "WHERE referenced_entity IN " + "(SELECT FixedCustomPropertyPrefix_l1id " - + "FROM FixedCustomSchema.FixedCustomTablePrefix_ReferencedEntity " + "WHERE dummy_entity = :rootId)"); + + "WHERE FixedCustomSchema.FixedCustomTablePrefix_SecondLevelReferencedEntity.referenced_entity IN " + + "(SELECT FixedCustomSchema.FixedCustomTablePrefix_ReferencedEntity.FixedCustomPropertyPrefix_l1id " + + "FROM FixedCustomSchema.FixedCustomTablePrefix_ReferencedEntity " + + "WHERE FixedCustomSchema.FixedCustomTablePrefix_ReferencedEntity.dummy_entity = :rootId)"); } @Test // DATAJDBC-107 @@ -145,10 +147,10 @@ public void cascadingDeleteAllFirstLevel() { SqlGenerator sqlGenerator = configureSqlGenerator(fixedCustomTablePrefixStrategy); - String sql = sqlGenerator.createDeleteAllSql(getPath("ref", DummyEntity.class)); + String sql = sqlGenerator.createDeleteAllSql(getPath("ref")); - assertThat(sql).isEqualTo( - "DELETE FROM FixedCustomSchema.FixedCustomTablePrefix_ReferencedEntity " + "WHERE dummy_entity IS NOT NULL"); + assertThat(sql).isEqualTo("DELETE FROM FixedCustomSchema.FixedCustomTablePrefix_ReferencedEntity " + + "WHERE FixedCustomSchema.FixedCustomTablePrefix_ReferencedEntity.dummy_entity IS NOT NULL"); } @Test // DATAJDBC-107 @@ -156,11 +158,13 @@ public void cascadingDeleteSecondLevel() { SqlGenerator sqlGenerator = configureSqlGenerator(fixedCustomTablePrefixStrategy); - String sql = sqlGenerator.createDeleteAllSql(getPath("ref.further", DummyEntity.class)); + String sql = sqlGenerator.createDeleteAllSql(getPath("ref.further")); assertThat(sql).isEqualTo("DELETE FROM FixedCustomSchema.FixedCustomTablePrefix_SecondLevelReferencedEntity " - + "WHERE referenced_entity IN " + "(SELECT FixedCustomPropertyPrefix_l1id " - + "FROM FixedCustomSchema.FixedCustomTablePrefix_ReferencedEntity " + "WHERE dummy_entity IS NOT NULL)"); + + "WHERE FixedCustomSchema.FixedCustomTablePrefix_SecondLevelReferencedEntity.referenced_entity IN " + + "(SELECT FixedCustomSchema.FixedCustomTablePrefix_ReferencedEntity.FixedCustomPropertyPrefix_l1id " + + "FROM FixedCustomSchema.FixedCustomTablePrefix_ReferencedEntity " + + "WHERE FixedCustomSchema.FixedCustomTablePrefix_ReferencedEntity.dummy_entity IS NOT NULL)"); } @Test // DATAJDBC-113 @@ -171,17 +175,15 @@ public void deleteByList() { String sql = sqlGenerator.getDeleteByList(); assertThat(sql).isEqualTo( - "DELETE FROM FixedCustomSchema.FixedCustomTablePrefix_DummyEntity WHERE FixedCustomPropertyPrefix_id IN (:ids)"); + "DELETE FROM FixedCustomSchema.FixedCustomTablePrefix_DummyEntity WHERE FixedCustomSchema.FixedCustomTablePrefix_DummyEntity.FixedCustomPropertyPrefix_id IN (:ids)"); } - private PersistentPropertyPath getPath(String path, Class baseType) { - return PersistentPropertyPathTestUtils.getPath(context, path, baseType); + private PersistentPropertyPath getPath(String path) { + return PersistentPropertyPathTestUtils.getPath(context, path, DummyEntity.class); } /** * Plug in a custom {@link NamingStrategy} for this test case. - * - * @param namingStrategy */ private SqlGenerator configureSqlGenerator(NamingStrategy namingStrategy) { @@ -190,6 +192,7 @@ private SqlGenerator configureSqlGenerator(NamingStrategy namingStrategy) { return new SqlGenerator(context, persistentEntity, new SqlGeneratorSource(context)); } + @SuppressWarnings("unused") static class DummyEntity { @Id Long id; @@ -197,6 +200,7 @@ static class DummyEntity { ReferencedEntity ref; } + @SuppressWarnings("unused") static class ReferencedEntity { @Id Long l1id; @@ -204,6 +208,7 @@ static class ReferencedEntity { SecondLevelReferencedEntity further; } + @SuppressWarnings("unused") static class SecondLevelReferencedEntity { @Id Long l2id; diff --git a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/SqlGeneratorUnitTests.java b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/SqlGeneratorUnitTests.java index 64c613ef7f..b2db59898b 100644 --- a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/SqlGeneratorUnitTests.java +++ b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/SqlGeneratorUnitTests.java @@ -35,6 +35,8 @@ import org.springframework.data.relational.core.mapping.RelationalMappingContext; import org.springframework.data.relational.core.mapping.RelationalPersistentEntity; import org.springframework.data.relational.core.mapping.RelationalPersistentProperty; +import org.springframework.data.relational.core.sql.Aliased; +import org.springframework.data.relational.core.sql.Table; /** * Unit tests for the {@link SqlGenerator}. @@ -46,19 +48,17 @@ */ public class SqlGeneratorUnitTests { - private SqlGenerator sqlGenerator; - private RelationalMappingContext context = new JdbcMappingContext(); + SqlGenerator sqlGenerator; + NamingStrategy namingStrategy = new PrefixingNamingStrategy(); + RelationalMappingContext context = new JdbcMappingContext(namingStrategy); @Before public void setUp() { - this.sqlGenerator = createSqlGenerator(DummyEntity.class); } SqlGenerator createSqlGenerator(Class type) { - NamingStrategy namingStrategy = new PrefixingNamingStrategy(); - RelationalMappingContext context = new JdbcMappingContext(namingStrategy); RelationalPersistentEntity persistentEntity = context.getRequiredPersistentEntity(type); return new SqlGenerator(context, persistentEntity, new SqlGeneratorSource(context)); @@ -87,18 +87,18 @@ public void findOne() { @Test // DATAJDBC-112 public void cascadingDeleteFirstLevel() { - String sql = sqlGenerator.createDeleteByPath(getPath("ref")); + String sql = sqlGenerator.createDeleteByPath(getPath("ref", DummyEntity.class)); - assertThat(sql).isEqualTo("DELETE FROM referenced_entity WHERE dummy_entity = :rootId"); + assertThat(sql).isEqualTo("DELETE FROM referenced_entity WHERE referenced_entity.dummy_entity = :rootId"); } @Test // DATAJDBC-112 - public void cascadingDeleteAllSecondLevel() { + public void cascadingDeleteByPathSecondLevel() { - String sql = sqlGenerator.createDeleteByPath(getPath("ref.further")); + String sql = sqlGenerator.createDeleteByPath(getPath("ref.further", DummyEntity.class)); assertThat(sql).isEqualTo( - "DELETE FROM second_level_referenced_entity WHERE referenced_entity IN (SELECT x_l1id FROM referenced_entity WHERE dummy_entity = :rootId)"); + "DELETE FROM second_level_referenced_entity WHERE second_level_referenced_entity.referenced_entity IN (SELECT referenced_entity.x_l1id FROM referenced_entity WHERE referenced_entity.dummy_entity = :rootId)"); } @Test // DATAJDBC-112 @@ -112,34 +112,34 @@ public void deleteAll() { @Test // DATAJDBC-112 public void cascadingDeleteAllFirstLevel() { - String sql = sqlGenerator.createDeleteAllSql(getPath("ref")); + String sql = sqlGenerator.createDeleteAllSql(getPath("ref", DummyEntity.class)); - assertThat(sql).isEqualTo("DELETE FROM referenced_entity WHERE dummy_entity IS NOT NULL"); + assertThat(sql).isEqualTo("DELETE FROM referenced_entity WHERE referenced_entity.dummy_entity IS NOT NULL"); } @Test // DATAJDBC-112 - public void cascadingDeleteSecondLevel() { + public void cascadingDeleteAllSecondLevel() { - String sql = sqlGenerator.createDeleteAllSql(getPath("ref.further")); + String sql = sqlGenerator.createDeleteAllSql(getPath("ref.further", DummyEntity.class)); assertThat(sql).isEqualTo( - "DELETE FROM second_level_referenced_entity WHERE referenced_entity IN (SELECT x_l1id FROM referenced_entity WHERE dummy_entity IS NOT NULL)"); + "DELETE FROM second_level_referenced_entity WHERE second_level_referenced_entity.referenced_entity IN (SELECT referenced_entity.x_l1id FROM referenced_entity WHERE referenced_entity.dummy_entity IS NOT NULL)"); } @Test // DATAJDBC-227 public void deleteAllMap() { - String sql = sqlGenerator.createDeleteAllSql(getPath("mappedElements")); + String sql = sqlGenerator.createDeleteAllSql(getPath("mappedElements", DummyEntity.class)); - assertThat(sql).isEqualTo("DELETE FROM element WHERE dummy_entity IS NOT NULL"); + assertThat(sql).isEqualTo("DELETE FROM element WHERE element.dummy_entity IS NOT NULL"); } @Test // DATAJDBC-227 public void deleteMapByPath() { - String sql = sqlGenerator.createDeleteByPath(getPath("mappedElements")); + String sql = sqlGenerator.createDeleteByPath(getPath("mappedElements", DummyEntity.class)); - assertThat(sql).isEqualTo("DELETE FROM element WHERE dummy_entity = :rootId"); + assertThat(sql).isEqualTo("DELETE FROM element WHERE element.dummy_entity = :rootId"); } @Test // DATAJDBC-131, DATAJDBC-111 @@ -148,14 +148,18 @@ public void findAllByProperty() { // this would get called when ListParent is the element type of a Set String sql = sqlGenerator.getFindAllByProperty("back-ref", null, false); - assertThat(sql).isEqualTo("SELECT dummy_entity.id1 AS id1, dummy_entity.x_name AS x_name, " // - + "dummy_entity.x_other AS x_other, " // - + "ref.x_l1id AS ref_x_l1id, ref.x_content AS ref_x_content, " - + "ref_further.x_l2id AS ref_further_x_l2id, ref_further.x_something AS ref_further_x_something " // - + "FROM dummy_entity " // - + "LEFT OUTER JOIN referenced_entity AS ref ON ref.dummy_entity = dummy_entity.id1 " // - + "LEFT OUTER JOIN second_level_referenced_entity AS ref_further ON ref_further.referenced_entity = referenced_entity.x_l1id " // - + "WHERE back-ref = :back-ref"); + assertThat(sql).contains("SELECT", // + "dummy_entity.id1 AS id1", // + "dummy_entity.x_name AS x_name", // + "dummy_entity.x_other AS x_other", // + "ref.x_l1id AS ref_x_l1id", // + "ref.x_content AS ref_x_content", // + "ref_further.x_l2id AS ref_further_x_l2id", // + "ref_further.x_something AS ref_further_x_something", // + "FROM dummy_entity ", // + "LEFT OUTER JOIN referenced_entity AS ref ON ref.dummy_entity = dummy_entity.id1", // + "LEFT OUTER JOIN second_level_referenced_entity AS ref_further ON ref_further.referenced_entity = ref.x_l1id", // + "WHERE dummy_entity.back-ref = :back-ref"); } @Test // DATAJDBC-131, DATAJDBC-111 @@ -170,9 +174,9 @@ public void findAllByPropertyWithKey() { + "ref_further.x_l2id AS ref_further_x_l2id, ref_further.x_something AS ref_further_x_something, " // + "dummy_entity.key-column AS key-column " // + "FROM dummy_entity " // - + "LEFT OUTER JOIN referenced_entity AS ref ON ref.dummy_entity = dummy_entity.id1 " // - + "LEFT OUTER JOIN second_level_referenced_entity AS ref_further ON ref_further.referenced_entity = referenced_entity.x_l1id " // - + "WHERE back-ref = :back-ref"); + + "LEFT OUTER JOIN referenced_entity AS ref ON ref.dummy_entity = dummy_entity.id1 " // + + "LEFT OUTER JOIN second_level_referenced_entity AS ref_further ON ref_further.referenced_entity = ref.x_l1id " // + + "WHERE dummy_entity.back-ref = :back-ref"); } @Test(expected = IllegalArgumentException.class) // DATAJDBC-130 @@ -192,9 +196,9 @@ public void findAllByPropertyWithKeyOrdered() { + "ref_further.x_l2id AS ref_further_x_l2id, ref_further.x_something AS ref_further_x_something, " // + "dummy_entity.key-column AS key-column " // + "FROM dummy_entity " // - + "LEFT OUTER JOIN referenced_entity AS ref ON ref.dummy_entity = dummy_entity.id1 " // - + "LEFT OUTER JOIN second_level_referenced_entity AS ref_further ON ref_further.referenced_entity = referenced_entity.x_l1id " // - + "WHERE back-ref = :back-ref " + "ORDER BY key-column"); + + "LEFT OUTER JOIN referenced_entity AS ref ON ref.dummy_entity = dummy_entity.id1 " // + + "LEFT OUTER JOIN second_level_referenced_entity AS ref_further ON ref_further.referenced_entity = ref.x_l1id " // + + "WHERE dummy_entity.back-ref = :back-ref " + "ORDER BY key-column"); } @Test // DATAJDBC-264 @@ -214,9 +218,8 @@ public void getInsertForQuotedColumnName() { String insert = sqlGenerator.getInsert(emptySet()); - assertThat(insert).isEqualTo("INSERT INTO entity_with_quoted_column_name " + - "(\"test_@123\") " + - "VALUES (:test_123)"); + assertThat(insert) + .isEqualTo("INSERT INTO entity_with_quoted_column_name " + "(\"test_@123\") " + "VALUES (:test_123)"); } @Test // DATAJDBC-266 @@ -249,7 +252,7 @@ public void readOnlyPropertyExcludedFromQuery_when_generateUpdateSql() { assertThat(sqlGenerator.getUpdate()).isEqualToIgnoringCase( // "UPDATE entity_with_read_only_property " // + "SET x_name = :x_name " // - + "WHERE x_id = :x_id" // + + "WHERE entity_with_read_only_property.x_id = :x_id" // ); } @@ -260,9 +263,8 @@ public void getUpdateForQuotedColumnName() { String update = sqlGenerator.getUpdate(); - assertThat(update).isEqualTo("UPDATE entity_with_quoted_column_name " + - "SET \"test_@123\" = :test_123 " + - "WHERE \"test_@id\" = :test_id"); + assertThat(update).isEqualTo("UPDATE entity_with_quoted_column_name " + "SET \"test_@123\" = :test_123 " + + "WHERE entity_with_quoted_column_name.\"test_@id\" = :test_id"); } @Test // DATAJDBC-324 @@ -294,13 +296,13 @@ public void readOnlyPropertyIncludedIntoQuery_when_generateFindAllByPropertySql( assertThat(sqlGenerator.getFindAllByProperty("back-ref", "key-column", true)).isEqualToIgnoringCase( // "SELECT " // - + "entity_with_read_only_property.x_id AS x_id, " // - + "entity_with_read_only_property.x_name AS x_name, " // - + "entity_with_read_only_property.x_read_only_value AS x_read_only_value, " // - + "entity_with_read_only_property.key-column AS key-column " // - + "FROM entity_with_read_only_property " // - + "WHERE back-ref = :back-ref " // - + "ORDER BY key-column" // + + "entity_with_read_only_property.x_id AS x_id, " // + + "entity_with_read_only_property.x_name AS x_name, " // + + "entity_with_read_only_property.x_read_only_value AS x_read_only_value, " // + + "entity_with_read_only_property.key-column AS key-column " // + + "FROM entity_with_read_only_property " // + + "WHERE entity_with_read_only_property.back-ref = :back-ref " // + + "ORDER BY key-column" // ); } @@ -311,11 +313,11 @@ public void readOnlyPropertyIncludedIntoQuery_when_generateFindAllInListSql() { assertThat(sqlGenerator.getFindAllInList()).isEqualToIgnoringCase( // "SELECT " // - + "entity_with_read_only_property.x_id AS x_id, " // - + "entity_with_read_only_property.x_name AS x_name, " // - + "entity_with_read_only_property.x_read_only_value AS x_read_only_value " // - + "FROM entity_with_read_only_property " // - + "WHERE entity_with_read_only_property.x_id in(:ids)" // + + "entity_with_read_only_property.x_id AS x_id, " // + + "entity_with_read_only_property.x_name AS x_name, " // + + "entity_with_read_only_property.x_read_only_value AS x_read_only_value " // + + "FROM entity_with_read_only_property " // + + "WHERE entity_with_read_only_property.x_id IN (:ids)" // ); } @@ -326,23 +328,158 @@ public void readOnlyPropertyIncludedIntoQuery_when_generateFindOneSql() { assertThat(sqlGenerator.getFindOne()).isEqualToIgnoringCase( // "SELECT " // - + "entity_with_read_only_property.x_id AS x_id, " // - + "entity_with_read_only_property.x_name AS x_name, " // - + "entity_with_read_only_property.x_read_only_value AS x_read_only_value " // - + "FROM entity_with_read_only_property " // - + "WHERE entity_with_read_only_property.x_id = :id" // + + "entity_with_read_only_property.x_id AS x_id, " // + + "entity_with_read_only_property.x_name AS x_name, " // + + "entity_with_read_only_property.x_read_only_value AS x_read_only_value " // + + "FROM entity_with_read_only_property " // + + "WHERE entity_with_read_only_property.x_id = :id" // ); } - private PersistentPropertyPath getPath(String path) { - return PersistentPropertyPathTestUtils.getPath(context, path, DummyEntity.class); + @Test // DATAJDBC-340 + public void deletingLongChain() { + + assertThat( + createSqlGenerator(Chain4.class).createDeleteByPath(getPath("chain3.chain2.chain1.chain0", Chain4.class))) // + .isEqualTo("DELETE FROM chain0 " + // + "WHERE chain0.chain1 IN (" + // + "SELECT chain1.x_one " + // + "FROM chain1 " + // + "WHERE chain1.chain2 IN (" + // + "SELECT chain2.x_two " + // + "FROM chain2 " + // + "WHERE chain2.chain3 IN (" + // + "SELECT chain3.x_three " + // + "FROM chain3 " + // + "WHERE chain3.chain4 = :rootId" + // + ")))"); + } + + @Test // DATAJDBC-340 + public void noJoinForSimpleColumn() { + assertThat(generateJoin("id", DummyEntity.class)).isNull(); + } + + @Test // DATAJDBC-340 + public void joinForSimpleReference() { + + SoftAssertions.assertSoftly(softly -> { + + SqlGenerator.Join join = generateJoin("ref", DummyEntity.class); + softly.assertThat(join.getJoinTable().getName()).isEqualTo("referenced_entity"); + softly.assertThat(join.getJoinColumn().getTable()).isEqualTo(join.getJoinTable()); + softly.assertThat(join.getJoinColumn().getName()).isEqualTo("dummy_entity"); + softly.assertThat(join.getParentId().getName()).isEqualTo("id1"); + softly.assertThat(join.getParentId().getTable().getName()).isEqualTo("dummy_entity"); + }); + } + + @Test // DATAJDBC-340 + public void noJoinForCollectionReference() { + + SqlGenerator.Join join = generateJoin("elements", DummyEntity.class); + + assertThat(join).isNull(); + + } + + @Test // DATAJDBC-340 + public void noJoinForMappedReference() { + + SqlGenerator.Join join = generateJoin("mappedElements", DummyEntity.class); + + assertThat(join).isNull(); + } + + @Test // DATAJDBC-340 + public void joinForSecondLevelReference() { + + SoftAssertions.assertSoftly(softly -> { + + SqlGenerator.Join join = generateJoin("ref.further", DummyEntity.class); + softly.assertThat(join.getJoinTable().getName()).isEqualTo("second_level_referenced_entity"); + softly.assertThat(join.getJoinColumn().getTable()).isEqualTo(join.getJoinTable()); + softly.assertThat(join.getJoinColumn().getName()).isEqualTo("referenced_entity"); + softly.assertThat(join.getParentId().getName()).isEqualTo("x_l1id"); + softly.assertThat(join.getParentId().getTable().getName()).isEqualTo("referenced_entity"); + }); + } + + @Test // DATAJDBC-340 + public void joinForOneToOneWithoutId() { + + SoftAssertions.assertSoftly(softly -> { + + SqlGenerator.Join join = generateJoin("child", ParentOfNoIdChild.class); + Table joinTable = join.getJoinTable(); + softly.assertThat(joinTable.getName()).isEqualTo("no_id_child"); + softly.assertThat(joinTable).isInstanceOf(Aliased.class); + softly.assertThat(((Aliased) joinTable).getAlias()).isEqualTo("child"); + softly.assertThat(join.getJoinColumn().getTable()).isEqualTo(joinTable); + softly.assertThat(join.getJoinColumn().getName()).isEqualTo("parent_of_no_id_child"); + softly.assertThat(join.getParentId().getName()).isEqualTo("x_id"); + softly.assertThat(join.getParentId().getTable().getName()).isEqualTo("parent_of_no_id_child"); + + }); + } + + private SqlGenerator.Join generateJoin(String path, Class type) { + return createSqlGenerator(type) + .getJoin(new PersistentPropertyPathExtension(context, PropertyPathTestingUtils.toPath(path, type, context))); + } + + @Test // DATAJDBC-340 + public void simpleColumn() { + + assertThat(generatedColumn("id", DummyEntity.class)) // + .extracting(c -> c.getName(), c -> c.getTable().getName(), c -> getAlias(c.getTable()), this::getAlias) + .containsExactly("id1", "dummy_entity", null, "id1"); + } + + @Test // DATAJDBC-340 + public void columnForIndirectProperty() { + + assertThat(generatedColumn("ref.l1id", DummyEntity.class)) // + .extracting(c -> c.getName(), c -> c.getTable().getName(), c -> getAlias(c.getTable()), this::getAlias) // + .containsExactly("x_l1id", "referenced_entity", "ref", "ref_x_l1id"); + } + + @Test // DATAJDBC-340 + public void noColumnForReferencedEntity() { + + assertThat(generatedColumn("ref", DummyEntity.class)).isNull(); + } + + @Test // DATAJDBC-340 + public void columnForReferencedEntityWithoutId() { + + assertThat(generatedColumn("child", ParentOfNoIdChild.class)) // + .extracting(c -> c.getName(), c -> c.getTable().getName(), c -> getAlias(c.getTable()), this::getAlias) // + .containsExactly("parent_of_no_id_child", "no_id_child", "child", "child_parent_of_no_id_child"); + } + + private String getAlias(Object maybeAliased) { + + if (maybeAliased instanceof Aliased) { + return ((Aliased) maybeAliased).getAlias(); + } + return null; + } + + private org.springframework.data.relational.core.sql.Column generatedColumn(String path, Class type) { + + return createSqlGenerator(type) + .getColumn(new PersistentPropertyPathExtension(context, PropertyPathTestingUtils.toPath(path, type, context))); + } + + private PersistentPropertyPath getPath(String path, Class baseType) { + return PersistentPropertyPathTestUtils.getPath(context, path, baseType); } @SuppressWarnings("unused") static class DummyEntity { - @Column("id1") - @Id Long id; + @Column("id1") @Id Long id; String name; ReferencedEntity ref; Set elements; @@ -412,4 +549,38 @@ static class EntityWithQuotedColumnName { @Id @Column("\"test_@id\"") Long id; @Column("\"test_@123\"") String name; } + + @SuppressWarnings("unused") + static class Chain0 { + @Id Long zero; + String zeroValue; + } + + @SuppressWarnings("unused") + static class Chain1 { + @Id Long one; + String oneValue; + Chain0 chain0; + } + + @SuppressWarnings("unused") + static class Chain2 { + @Id Long two; + String twoValue; + Chain1 chain1; + } + + @SuppressWarnings("unused") + static class Chain3 { + @Id Long three; + String threeValue; + Chain2 chain2; + } + + @SuppressWarnings("unused") + static class Chain4 { + @Id Long four; + String fourValue; + Chain3 chain3; + } } diff --git a/spring-data-jdbc/src/test/resources/logback.xml b/spring-data-jdbc/src/test/resources/logback.xml index 6da1bab099..f1bfdbaf39 100644 --- a/spring-data-jdbc/src/test/resources/logback.xml +++ b/spring-data-jdbc/src/test/resources/logback.xml @@ -7,8 +7,8 @@ - - + + 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 1699ad6c50..43e6772c4e 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 @@ -1,20 +1,93 @@ -CREATE TABLE LEGO_SET ( id1 BIGINT GENERATED BY DEFAULT AS IDENTITY(START WITH 1) PRIMARY KEY, NAME VARCHAR(30)); -CREATE TABLE MANUAL ( id2 BIGINT GENERATED BY DEFAULT AS IDENTITY(START WITH 1) PRIMARY KEY, LEGO_SET BIGINT, ALTERNATIVE BIGINT, CONTENT VARCHAR(2000)); +CREATE TABLE LEGO_SET +( + id1 BIGINT GENERATED BY DEFAULT AS IDENTITY (START WITH 1) PRIMARY KEY, + NAME VARCHAR(30) +); +CREATE TABLE MANUAL +( + id2 BIGINT GENERATED BY DEFAULT AS IDENTITY (START WITH 1) PRIMARY KEY, + LEGO_SET BIGINT, + ALTERNATIVE BIGINT, + CONTENT VARCHAR(2000) +); -ALTER TABLE MANUAL ADD FOREIGN KEY (LEGO_SET) -REFERENCES LEGO_SET(id1); +ALTER TABLE MANUAL + ADD FOREIGN KEY (LEGO_SET) + REFERENCES LEGO_SET (id1); -CREATE TABLE ONE_TO_ONE_PARENT ( id3 BIGINT GENERATED BY DEFAULT AS IDENTITY(START WITH 1) PRIMARY KEY, content VARCHAR(30)); -CREATE TABLE Child_No_Id (ONE_TO_ONE_PARENT INTEGER PRIMARY KEY, content VARCHAR(30)); +CREATE TABLE ONE_TO_ONE_PARENT +( + id3 BIGINT GENERATED BY DEFAULT AS IDENTITY (START WITH 1) PRIMARY KEY, + content VARCHAR(30) +); +CREATE TABLE Child_No_Id +( + ONE_TO_ONE_PARENT INTEGER PRIMARY KEY, + content VARCHAR(30) +); -CREATE TABLE LIST_PARENT ( id4 BIGINT GENERATED BY DEFAULT AS IDENTITY ( START WITH 1 ) PRIMARY KEY, NAME VARCHAR(100)); -CREATE TABLE ELEMENT_NO_ID ( content VARCHAR(100), LIST_PARENT_KEY BIGINT, LIST_PARENT BIGINT); +CREATE TABLE LIST_PARENT +( + id4 BIGINT GENERATED BY DEFAULT AS IDENTITY ( START WITH 1 ) PRIMARY KEY, + NAME VARCHAR(100) +); +CREATE TABLE ELEMENT_NO_ID +( + content VARCHAR(100), + LIST_PARENT_KEY BIGINT, + LIST_PARENT BIGINT +); ALTER TABLE ELEMENT_NO_ID ADD FOREIGN KEY (LIST_PARENT) - REFERENCES LIST_PARENT(id4); + REFERENCES LIST_PARENT (id4); +CREATE TABLE ARRAY_OWNER +( + ID BIGINT GENERATED BY DEFAULT AS IDENTITY (START WITH 1) PRIMARY KEY, + DIGITS VARCHAR(20) ARRAY[10] NOT NULL, + MULTIDIMENSIONAL VARCHAR(20) ARRAY[10] NULL +); -CREATE TABLE ARRAY_OWNER (ID BIGINT GENERATED BY DEFAULT AS IDENTITY (START WITH 1) PRIMARY KEY, DIGITS VARCHAR(20) ARRAY[10] NOT NULL, MULTIDIMENSIONAL VARCHAR(20) ARRAY[10] NULL); +CREATE TABLE BYTE_ARRAY_OWNER +( + ID BIGINT GENERATED BY DEFAULT AS IDENTITY (START WITH 1) PRIMARY KEY, + BINARY_DATA VARBINARY(20) NOT NULL +); -CREATE TABLE BYTE_ARRAY_OWNER (ID BIGINT GENERATED BY DEFAULT AS IDENTITY (START WITH 1) PRIMARY KEY, BINARY_DATA VARBINARY(20) NOT NULL); +CREATE TABLE CHAIN4 +( + FOUR BIGINT GENERATED BY DEFAULT AS IDENTITY (START WITH 40) PRIMARY KEY, + FOUR_VALUE VARCHAR(20) +); +CREATE TABLE CHAIN3 +( + THREE BIGINT GENERATED BY DEFAULT AS IDENTITY (START WITH 30) PRIMARY KEY, + THREE_VALUE VARCHAR(20), + CHAIN4 BIGINT, + FOREIGN KEY (CHAIN4) REFERENCES CHAIN4 (FOUR) +); + +CREATE TABLE CHAIN2 +( + TWO BIGINT GENERATED BY DEFAULT AS IDENTITY (START WITH 20) PRIMARY KEY, + TWO_VALUE VARCHAR(20), + CHAIN3 BIGINT, + FOREIGN KEY (CHAIN3) REFERENCES CHAIN3 (THREE) +); + +CREATE TABLE CHAIN1 +( + ONE BIGINT GENERATED BY DEFAULT AS IDENTITY (START WITH 10) PRIMARY KEY, + ONE_VALUE VARCHAR(20), + CHAIN2 BIGINT, + FOREIGN KEY (CHAIN2) REFERENCES CHAIN2 (TWO) +); + +CREATE TABLE CHAIN0 +( + ZERO BIGINT GENERATED BY DEFAULT AS IDENTITY (START WITH 0) PRIMARY KEY, + ZERO_VALUE VARCHAR(20), + CHAIN1 BIGINT, + FOREIGN KEY (CHAIN1) REFERENCES CHAIN1 (ONE) +); 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 38957bcc61..cccddb9777 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 @@ -1,13 +1,84 @@ -CREATE TABLE LEGO_SET ( id1 BIGINT AUTO_INCREMENT PRIMARY KEY, NAME VARCHAR(30)); -CREATE TABLE MANUAL ( id2 BIGINT AUTO_INCREMENT PRIMARY KEY, LEGO_SET BIGINT, ALTERNATIVE BIGINT, CONTENT VARCHAR(2000)); +CREATE TABLE LEGO_SET +( + id1 BIGINT AUTO_INCREMENT PRIMARY KEY, + NAME VARCHAR(30) +); +CREATE TABLE MANUAL +( + id2 BIGINT AUTO_INCREMENT PRIMARY KEY, + LEGO_SET BIGINT, + ALTERNATIVE BIGINT, + CONTENT VARCHAR(2000) +); -ALTER TABLE MANUAL ADD FOREIGN KEY (LEGO_SET) -REFERENCES LEGO_SET(id1); +ALTER TABLE MANUAL + ADD FOREIGN KEY (LEGO_SET) + REFERENCES LEGO_SET (id1); -CREATE TABLE ONE_TO_ONE_PARENT ( id3 BIGINT AUTO_INCREMENT PRIMARY KEY, content VARCHAR(30)); -CREATE TABLE Child_No_Id (ONE_TO_ONE_PARENT INTEGER PRIMARY KEY, content VARCHAR(30)); +CREATE TABLE ONE_TO_ONE_PARENT +( + id3 BIGINT AUTO_INCREMENT PRIMARY KEY, + content VARCHAR(30) +); +CREATE TABLE Child_No_Id +( + ONE_TO_ONE_PARENT INTEGER PRIMARY KEY, + content VARCHAR(30) +); -CREATE TABLE LIST_PARENT ( id4 BIGINT AUTO_INCREMENT PRIMARY KEY, NAME VARCHAR(100)); -CREATE TABLE element_no_id ( content VARCHAR(100), LIST_PARENT_key BIGINT, LIST_PARENT BIGINT); +CREATE TABLE LIST_PARENT +( + id4 BIGINT AUTO_INCREMENT PRIMARY KEY, + NAME VARCHAR(100) +); +CREATE TABLE element_no_id +( + content VARCHAR(100), + LIST_PARENT_key BIGINT, + LIST_PARENT BIGINT +); -CREATE TABLE BYTE_ARRAY_OWNER (ID BIGINT AUTO_INCREMENT PRIMARY KEY, BINARY_DATA VARBINARY(20) NOT NULL) \ No newline at end of file +CREATE TABLE BYTE_ARRAY_OWNER +( + ID BIGINT AUTO_INCREMENT PRIMARY KEY, + BINARY_DATA VARBINARY(20) NOT NULL +); + +CREATE TABLE CHAIN4 +( + FOUR BIGINT AUTO_INCREMENT PRIMARY KEY, + FOUR_VALUE VARCHAR(20) +); + + +CREATE TABLE CHAIN3 +( + THREE BIGINT AUTO_INCREMENT PRIMARY KEY, + THREE_VALUE VARCHAR(20), + CHAIN4 BIGINT, + FOREIGN KEY (CHAIN4) REFERENCES CHAIN4(FOUR) +); + +CREATE TABLE CHAIN2 +( + TWO BIGINT AUTO_INCREMENT PRIMARY KEY, + TWO_VALUE VARCHAR(20), + CHAIN3 BIGINT, + FOREIGN KEY (CHAIN3) REFERENCES CHAIN3(THREE) +); + +CREATE TABLE CHAIN1 +( + ONE BIGINT AUTO_INCREMENT PRIMARY KEY, + ONE_VALUE VARCHAR(20), + CHAIN2 BIGINT, + FOREIGN KEY (CHAIN2) REFERENCES CHAIN2(TWO) +); + +CREATE TABLE CHAIN0 +( + ZERO BIGINT AUTO_INCREMENT PRIMARY KEY, + ZERO_VALUE VARCHAR(20), + CHAIN1 BIGINT, + FOREIGN KEY (CHAIN1) REFERENCES CHAIN1(ONE) +); 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 b507634c35..ff06116d2e 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 @@ -1,18 +1,93 @@ DROP TABLE IF EXISTS MANUAL; DROP TABLE IF EXISTS LEGO_SET; -CREATE TABLE LEGO_SET ( id1 BIGINT IDENTITY PRIMARY KEY, NAME VARCHAR(30)); -CREATE TABLE MANUAL ( id2 BIGINT IDENTITY PRIMARY KEY, LEGO_SET BIGINT, ALTERNATIVE BIGINT, CONTENT VARCHAR(2000)); -ALTER TABLE MANUAL ADD FOREIGN KEY (LEGO_SET) REFERENCES LEGO_SET(id1); +CREATE TABLE LEGO_SET +( + id1 BIGINT IDENTITY PRIMARY KEY, + NAME VARCHAR(30) +); +CREATE TABLE MANUAL +( + id2 BIGINT IDENTITY PRIMARY KEY, + LEGO_SET BIGINT, + ALTERNATIVE BIGINT, + CONTENT VARCHAR(2000) +); +ALTER TABLE MANUAL + ADD FOREIGN KEY (LEGO_SET) REFERENCES LEGO_SET (id1); DROP TABLE IF EXISTS Child_No_Id; DROP TABLE IF EXISTS ONE_TO_ONE_PARENT; -CREATE TABLE ONE_TO_ONE_PARENT ( id3 BIGINT IDENTITY PRIMARY KEY, content VARCHAR(30)); -CREATE TABLE Child_No_Id (ONE_TO_ONE_PARENT BIGINT PRIMARY KEY, content VARCHAR(30)); +CREATE TABLE ONE_TO_ONE_PARENT +( + id3 BIGINT IDENTITY PRIMARY KEY, + content VARCHAR(30) +); +CREATE TABLE Child_No_Id +( + ONE_TO_ONE_PARENT BIGINT PRIMARY KEY, + content VARCHAR(30) +); DROP TABLE IF EXISTS element_no_id; DROP TABLE IF EXISTS LIST_PARENT; -CREATE TABLE LIST_PARENT ( id4 BIGINT IDENTITY PRIMARY KEY, NAME VARCHAR(100)); -CREATE TABLE element_no_id ( content VARCHAR(100), LIST_PARENT_key BIGINT, LIST_PARENT BIGINT); +CREATE TABLE LIST_PARENT +( + id4 BIGINT IDENTITY PRIMARY KEY, + NAME VARCHAR(100) +); +CREATE TABLE element_no_id +( + content VARCHAR(100), + LIST_PARENT_key BIGINT, + LIST_PARENT BIGINT +); DROP TABLE IF EXISTS BYTE_ARRAY_OWNER; -CREATE TABLE BYTE_ARRAY_OWNER (ID BIGINT IDENTITY PRIMARY KEY, BINARY_DATA VARBINARY(20) NOT NULL) \ No newline at end of file +CREATE TABLE BYTE_ARRAY_OWNER +( + ID BIGINT IDENTITY PRIMARY KEY, + BINARY_DATA VARBINARY(20) NOT NULL +); + +DROP TABLE IF EXISTS CHAIN4; +CREATE TABLE CHAIN4 +( + FOUR BIGINT IDENTITY PRIMARY KEY, + FOUR_VALUE VARCHAR(20) +); + +DROP TABLE IF EXISTS CHAIN3; +CREATE TABLE CHAIN3 +( + THREE BIGINT IDENTITY PRIMARY KEY, + THREE_VALUE VARCHAR(20), + CHAIN4 BIGINT, + FOREIGN KEY (CHAIN4) REFERENCES CHAIN4 (FOUR) +); + +DROP TABLE IF EXISTS CHAIN2; +CREATE TABLE CHAIN2 +( + TWO BIGINT IDENTITY PRIMARY KEY, + TWO_VALUE VARCHAR(20), + CHAIN3 BIGINT, + FOREIGN KEY (CHAIN3) REFERENCES CHAIN3 (THREE) +); + +DROP TABLE IF EXISTS CHAIN1; +CREATE TABLE CHAIN1 +( + ONE BIGINT IDENTITY PRIMARY KEY, + ONE_VALUE VARCHAR(20), + CHAIN2 BIGINT, + FOREIGN KEY (CHAIN2) REFERENCES CHAIN2 (TWO) +); + +DROP TABLE IF EXISTS CHAIN0; +CREATE TABLE CHAIN0 +( + ZERO BIGINT IDENTITY PRIMARY KEY, + ZERO_VALUE VARCHAR(20), + CHAIN1 BIGINT, + FOREIGN KEY (CHAIN1) REFERENCES CHAIN1 (ONE) +); \ No newline at end of file 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 38957bcc61..3000a7a0a8 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 @@ -1,13 +1,84 @@ -CREATE TABLE LEGO_SET ( id1 BIGINT AUTO_INCREMENT PRIMARY KEY, NAME VARCHAR(30)); -CREATE TABLE MANUAL ( id2 BIGINT AUTO_INCREMENT PRIMARY KEY, LEGO_SET BIGINT, ALTERNATIVE BIGINT, CONTENT VARCHAR(2000)); +CREATE TABLE LEGO_SET +( + id1 BIGINT AUTO_INCREMENT PRIMARY KEY, + NAME VARCHAR(30) +); +CREATE TABLE MANUAL +( + id2 BIGINT AUTO_INCREMENT PRIMARY KEY, + LEGO_SET BIGINT, + ALTERNATIVE BIGINT, + CONTENT VARCHAR(2000) +); -ALTER TABLE MANUAL ADD FOREIGN KEY (LEGO_SET) -REFERENCES LEGO_SET(id1); +ALTER TABLE MANUAL + ADD FOREIGN KEY (LEGO_SET) + REFERENCES LEGO_SET (id1); -CREATE TABLE ONE_TO_ONE_PARENT ( id3 BIGINT AUTO_INCREMENT PRIMARY KEY, content VARCHAR(30)); -CREATE TABLE Child_No_Id (ONE_TO_ONE_PARENT INTEGER PRIMARY KEY, content VARCHAR(30)); +CREATE TABLE ONE_TO_ONE_PARENT +( + id3 BIGINT AUTO_INCREMENT PRIMARY KEY, + content VARCHAR(30) +); +CREATE TABLE Child_No_Id +( + ONE_TO_ONE_PARENT INTEGER PRIMARY KEY, + content VARCHAR(30) +); -CREATE TABLE LIST_PARENT ( id4 BIGINT AUTO_INCREMENT PRIMARY KEY, NAME VARCHAR(100)); -CREATE TABLE element_no_id ( content VARCHAR(100), LIST_PARENT_key BIGINT, LIST_PARENT BIGINT); +CREATE TABLE LIST_PARENT +( + id4 BIGINT AUTO_INCREMENT PRIMARY KEY, + NAME VARCHAR(100) +); +CREATE TABLE element_no_id +( + content VARCHAR(100), + LIST_PARENT_key BIGINT, + LIST_PARENT BIGINT +); -CREATE TABLE BYTE_ARRAY_OWNER (ID BIGINT AUTO_INCREMENT PRIMARY KEY, BINARY_DATA VARBINARY(20) NOT NULL) \ No newline at end of file +CREATE TABLE BYTE_ARRAY_OWNER +( + ID BIGINT AUTO_INCREMENT PRIMARY KEY, + BINARY_DATA VARBINARY(20) NOT NULL +); + +CREATE TABLE CHAIN4 +( + FOUR BIGINT AUTO_INCREMENT PRIMARY KEY, + FOUR_VALUE VARCHAR(20) +); + + +CREATE TABLE CHAIN3 +( + THREE BIGINT AUTO_INCREMENT PRIMARY KEY, + THREE_VALUE VARCHAR(20), + CHAIN4 BIGINT, + FOREIGN KEY (CHAIN4) REFERENCES CHAIN4(FOUR) +); + +CREATE TABLE CHAIN2 +( + TWO BIGINT AUTO_INCREMENT PRIMARY KEY, + TWO_VALUE VARCHAR(20), + CHAIN3 BIGINT, + FOREIGN KEY (CHAIN3) REFERENCES CHAIN3(THREE) +); + +CREATE TABLE CHAIN1 +( + ONE BIGINT AUTO_INCREMENT PRIMARY KEY, + ONE_VALUE VARCHAR(20), + CHAIN2 BIGINT, + FOREIGN KEY (CHAIN2) REFERENCES CHAIN2(TWO) +); + +CREATE TABLE CHAIN0 +( + ZERO BIGINT AUTO_INCREMENT PRIMARY KEY, + ZERO_VALUE VARCHAR(20), + CHAIN1 BIGINT, + FOREIGN KEY (CHAIN1) REFERENCES CHAIN1(ONE) +); \ No newline at end of file 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 4f39362347..f996800a45 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 @@ -6,19 +6,99 @@ DROP TABLE LIST_PARENT; DROP TABLE element_no_id; DROP TABLE ARRAY_OWNER; DROP TABLE BYTE_ARRAY_OWNER; +DROP TABLE CHAIN4; +DROP TABLE CHAIN3; +DROP TABLE CHAIN2; +DROP TABLE CHAIN1; +DROP TABLE CHAIN0; -CREATE TABLE LEGO_SET ( id1 SERIAL PRIMARY KEY, NAME VARCHAR(30)); -CREATE TABLE MANUAL ( id2 SERIAL PRIMARY KEY, LEGO_SET BIGINT, ALTERNATIVE BIGINT, CONTENT VARCHAR(2000)); +CREATE TABLE LEGO_SET +( + id1 SERIAL PRIMARY KEY, + NAME VARCHAR(30) +); +CREATE TABLE MANUAL +( + id2 SERIAL PRIMARY KEY, + LEGO_SET BIGINT, + ALTERNATIVE BIGINT, + CONTENT VARCHAR(2000) +); -ALTER TABLE MANUAL ADD FOREIGN KEY (LEGO_SET) -REFERENCES LEGO_SET(id1); +ALTER TABLE MANUAL + ADD FOREIGN KEY (LEGO_SET) + REFERENCES LEGO_SET (id1); -CREATE TABLE ONE_TO_ONE_PARENT ( id3 SERIAL PRIMARY KEY, content VARCHAR(30)); -CREATE TABLE Child_No_Id (ONE_TO_ONE_PARENT INTEGER PRIMARY KEY, content VARCHAR(30)); +CREATE TABLE ONE_TO_ONE_PARENT +( + id3 SERIAL PRIMARY KEY, + content VARCHAR(30) +); +CREATE TABLE Child_No_Id +( + ONE_TO_ONE_PARENT INTEGER PRIMARY KEY, + content VARCHAR(30) +); -CREATE TABLE LIST_PARENT ( id4 SERIAL PRIMARY KEY, NAME VARCHAR(100)); -CREATE TABLE element_no_id ( content VARCHAR(100), LIST_PARENT_key BIGINT, LIST_PARENT INTEGER); +CREATE TABLE LIST_PARENT +( + id4 SERIAL PRIMARY KEY, + NAME VARCHAR(100) +); +CREATE TABLE element_no_id +( + content VARCHAR(100), + LIST_PARENT_key BIGINT, + LIST_PARENT INTEGER +); -CREATE TABLE ARRAY_OWNER (ID SERIAL PRIMARY KEY, DIGITS VARCHAR(20)[10], MULTIDIMENSIONAL VARCHAR(20)[10][10]); +CREATE TABLE ARRAY_OWNER +( + ID SERIAL PRIMARY KEY, + DIGITS VARCHAR(20)[10], + MULTIDIMENSIONAL VARCHAR(20)[10][10] +); -CREATE TABLE BYTE_ARRAY_OWNER (ID SERIAL PRIMARY KEY, BINARY_DATA BYTEA NOT NULL) \ No newline at end of file +CREATE TABLE BYTE_ARRAY_OWNER +( + ID SERIAL PRIMARY KEY, + BINARY_DATA BYTEA NOT NULL +); + +CREATE TABLE CHAIN4 +( + FOUR SERIAL PRIMARY KEY, + FOUR_VALUE VARCHAR(20) +); + +CREATE TABLE CHAIN3 +( + THREE SERIAL PRIMARY KEY, + THREE_VALUE VARCHAR(20), + CHAIN4 BIGINT, + FOREIGN KEY (CHAIN4) REFERENCES CHAIN4 (FOUR) +); + +CREATE TABLE CHAIN2 +( + TWO SERIAL PRIMARY KEY, + TWO_VALUE VARCHAR(20), + CHAIN3 BIGINT, + FOREIGN KEY (CHAIN3) REFERENCES CHAIN3 (THREE) +); + +CREATE TABLE CHAIN1 +( + ONE SERIAL PRIMARY KEY, + ONE_VALUE VARCHAR(20), + CHAIN2 BIGINT, + FOREIGN KEY (CHAIN2) REFERENCES CHAIN2 (TWO) +); + +CREATE TABLE CHAIN0 +( + ZERO SERIAL PRIMARY KEY, + ZERO_VALUE VARCHAR(20), + CHAIN1 BIGINT, + FOREIGN KEY (CHAIN1) REFERENCES CHAIN1 (ONE) +); \ No newline at end of file diff --git a/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/Column.java b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/Column.java index 1dc41820e5..97e71b2a6d 100644 --- a/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/Column.java +++ b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/Column.java @@ -24,6 +24,7 @@ * Renders to: {@code } or {@code .}. * * @author Mark Paluch + * @author Jens Schauder * @since 1.1 */ public class Column extends AbstractSegment implements Expression, Named { @@ -182,6 +183,16 @@ public In in(Expression... expression) { return Conditions.in(this, expression); } + /** + * Creates a new {@link In} {@link Condition} given a subselects. + * + * @param subselect right side of the comparison. + * @return the {@link In} condition. + */ + public In in(Select subselect) { + return Conditions.in(this, subselect); + } + /** * Creates a {@code IS NULL} condition. * diff --git a/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/Conditions.java b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/Conditions.java index a3c7a2f15e..ea71b0c63c 100644 --- a/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/Conditions.java +++ b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/Conditions.java @@ -139,7 +139,7 @@ public static Like like(Expression leftColumnOrExpression, Expression rightColum * @param arg IN argument. * @return the {@link In} condition. */ - public static Condition in(Expression columnOrExpression, Expression arg) { + public static In in(Expression columnOrExpression, Expression arg) { Assert.notNull(columnOrExpression, "Comparison column or expression must not be null"); Assert.notNull(arg, "Expression argument must not be null"); @@ -184,7 +184,7 @@ public static In in(Expression columnOrExpression, Expression... expressions) { * @param subselect the subselect. * @return the {@link In} condition. */ - public static Condition in(Column column, Select subselect) { + public static In in(Column column, Select subselect) { Assert.notNull(column, "Column must not be null"); Assert.notNull(subselect, "Subselect must not be null"); diff --git a/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/DefaultInsertBuilder.java b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/DefaultInsertBuilder.java index f477f762ef..f263796f83 100644 --- a/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/DefaultInsertBuilder.java +++ b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/DefaultInsertBuilder.java @@ -40,7 +40,7 @@ class DefaultInsertBuilder * @see org.springframework.data.relational.core.sql.InsertBuilder#into(org.springframework.data.relational.core.sql.Table) */ @Override - public InsertIntoColumnsAndValues into(Table table) { + public InsertIntoColumnsAndValuesWithBuild into(Table table) { Assert.notNull(table, "Insert Into Table must not be null!"); diff --git a/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/DefaultSelectBuilder.java b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/DefaultSelectBuilder.java index 84383b8795..9571dd07c8 100644 --- a/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/DefaultSelectBuilder.java +++ b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/DefaultSelectBuilder.java @@ -30,6 +30,7 @@ * Default {@link SelectBuilder} implementation. * * @author Mark Paluch + * @author Jens Schauder * @since 1.1 */ class DefaultSelectBuilder implements SelectBuilder, SelectAndFrom, SelectFromAndJoin, SelectWhereAndOr { @@ -249,6 +250,15 @@ public SelectOn join(Table table) { return new JoinBuilder(table, this); } + /* + * (non-Javadoc) + * @see org.springframework.data.relational.core.sql.SelectBuilder.SelectJoin#join(org.springframework.data.relational.core.sql.Table) + */ + @Override + public SelectOn leftOuterJoin(Table table) { + return new JoinBuilder(table, this, JoinType.LEFT_OUTER_JOIN); + } + public DefaultSelectBuilder join(Join join) { this.joins.add(join); @@ -273,13 +283,21 @@ static class JoinBuilder implements SelectOn, SelectOnConditionComparison, Selec private final Table table; private final DefaultSelectBuilder selectBuilder; + private final JoinType joinType; private Expression from; private Expression to; private @Nullable Condition condition; - JoinBuilder(Table table, DefaultSelectBuilder selectBuilder) { + + JoinBuilder(Table table, DefaultSelectBuilder selectBuilder, JoinType joinType) { this.table = table; this.selectBuilder = selectBuilder; + + this.joinType = joinType; + } + + JoinBuilder(Table table, DefaultSelectBuilder selectBuilder) { + this(table, selectBuilder, JoinType.JOIN); } /* @@ -328,7 +346,7 @@ private void finishCondition() { private Join finishJoin() { finishCondition(); - return new Join(JoinType.JOIN, table, condition); + return new Join(joinType, table, condition); } /* @@ -391,6 +409,16 @@ public SelectOn join(Table table) { return selectBuilder.join(table); } + /* + * (non-Javadoc) + * @see org.springframework.data.relational.core.sql.SelectBuilder.SelectJoin#leftOuterJoin(org.springframework.data.relational.core.sql.Table) + */ + @Override + public SelectOn leftOuterJoin(Table table) { + selectBuilder.join(finishJoin()); + return selectBuilder.leftOuterJoin(table); + } + /* * (non-Javadoc) * @see org.springframework.data.relational.core.sql.SelectBuilder.SelectFromAndJoinCondition#limitOffset(long, long) diff --git a/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/Expressions.java b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/Expressions.java index 431141e6e1..bd14a272af 100644 --- a/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/Expressions.java +++ b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/Expressions.java @@ -19,6 +19,7 @@ * Factory for common {@link Expression}s. * * @author Mark Paluch + * @author Jens Schauder * @since 1.1 * @see SQL * @see Conditions @@ -55,7 +56,7 @@ public static Expression asterisk(Table table) { // Utility constructor. private Expressions() {} - static class SimpleExpression extends AbstractSegment implements Expression { + static public class SimpleExpression extends AbstractSegment implements Expression { private final String expression; diff --git a/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/Functions.java b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/Functions.java index 5fd7699b19..fb49426a41 100644 --- a/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/Functions.java +++ b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/Functions.java @@ -25,6 +25,7 @@ * Factory for common {@link Expression function expressions}. * * @author Mark Paluch + * @author Jens Schauder * @since 1.1 * @see SQL * @see Expressions @@ -38,7 +39,7 @@ public class Functions { * @param columns columns to apply count, must not be {@literal null}. * @return the new {@link SimpleFunction count function} for {@code columns}. */ - public static SimpleFunction count(Column... columns) { + public static SimpleFunction count(Expression... columns) { Assert.notNull(columns, "Columns must not be null!"); Assert.notEmpty(columns, "Columns must contains at least one column"); diff --git a/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/InsertBuilder.java b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/InsertBuilder.java index f76e39657a..3a718b49ab 100644 --- a/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/InsertBuilder.java +++ b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/InsertBuilder.java @@ -21,6 +21,7 @@ * Entry point to construct an {@link Insert} statement. * * @author Mark Paluch + * @author Jens Schauder * @since 1.1 * @see StatementBuilder */ @@ -34,7 +35,7 @@ public interface InsertBuilder { * @see Into * @see SQL#table(String) */ - InsertIntoColumnsAndValues into(Table table); + InsertIntoColumnsAndValuesWithBuild into(Table table); /** * Interface exposing {@code WHERE} methods. diff --git a/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/SelectBuilder.java b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/SelectBuilder.java index 5ee62bc524..a658b16520 100644 --- a/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/SelectBuilder.java +++ b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/SelectBuilder.java @@ -21,6 +21,7 @@ * Entry point to construct a {@link Select} statement. * * @author Mark Paluch + * @author Jens Schauder * @since 1.1 * @see StatementBuilder */ @@ -460,6 +461,16 @@ interface SelectJoin extends BuildSelect { * @see SQL#table(String) */ SelectOn join(Table table); + + /** + * Declare a {@code LEFT OUTER JOIN} {@link Table}. + * + * @param table name of the table, must not be {@literal null}. + * @return {@code this} builder. + * @see Join + * @see SQL#table(String) + */ + SelectOn leftOuterJoin(Table table); } /** diff --git a/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/SelectValidator.java b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/SelectValidator.java index 3cf74fd0d6..da6a3d61ee 100644 --- a/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/SelectValidator.java +++ b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/SelectValidator.java @@ -26,6 +26,7 @@ * {@code JOIN} clause. * * @author Mark Paluch + * @author Jens Schauder * @since 1.1 */ class SelectValidator extends AbstractImportValidator { @@ -93,18 +94,18 @@ public void enter(Visitable segment) { return; } - super.enter(segment); + if (segment instanceof Expression && parent instanceof Select) { + selectFieldCount++; + } if (segment instanceof AsteriskFromTable && parent instanceof Select) { Table table = ((AsteriskFromTable) segment).getTable(); requiredBySelect.add(table); - selectFieldCount++; } if (segment instanceof Column && (parent instanceof Select || parent instanceof SimpleFunction)) { - selectFieldCount++; Table table = ((Column) segment).getTable(); if (table != null) { @@ -124,6 +125,7 @@ public void enter(Visitable segment) { if (segment instanceof Table && parent instanceof Join) { join.add((Table) segment); } + super.enter(segment); } /* diff --git a/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/render/InsertStatementVisitor.java b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/render/InsertStatementVisitor.java index 5ba4375dc8..2f8af6d086 100644 --- a/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/render/InsertStatementVisitor.java +++ b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/render/InsertStatementVisitor.java @@ -25,6 +25,7 @@ * {@link PartRenderer} for {@link Insert} statements. * * @author Mark Paluch + * @author Jens Schauder * @since 1.1 */ class InsertStatementVisitor extends DelegatingVisitor implements PartRenderer { @@ -94,17 +95,13 @@ public Delegation doLeave(Visitable segment) { builder.append("INSERT"); - if (into.length() != 0) { - builder.append(" INTO ").append(into); - } + builder.append(" INTO ").append(into); if (columns.length() != 0) { builder.append(" (").append(columns).append(")"); } - if (values.length() != 0) { - builder.append(" VALUES(").append(values).append(")"); - } + builder.append(" VALUES (").append(values).append(")"); return Delegation.leave(); } diff --git a/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/render/SelectListVisitor.java b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/render/SelectListVisitor.java index c98fcd0d01..56a72788b8 100644 --- a/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/render/SelectListVisitor.java +++ b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/render/SelectListVisitor.java @@ -16,7 +16,9 @@ package org.springframework.data.relational.core.sql.render; import org.springframework.data.relational.core.sql.Aliased; +import org.springframework.data.relational.core.sql.AsteriskFromTable; import org.springframework.data.relational.core.sql.Column; +import org.springframework.data.relational.core.sql.Expression; import org.springframework.data.relational.core.sql.SelectList; import org.springframework.data.relational.core.sql.SimpleFunction; import org.springframework.data.relational.core.sql.Table; @@ -38,6 +40,7 @@ class SelectListVisitor extends TypedSubtreeVisitor implements PartR private boolean insideFunction = false; // this is hackery and should be fix with a proper visitor for // subelements. + SelectListVisitor(RenderContext context, RenderTarget target) { this.context = context; this.target = target; @@ -57,8 +60,6 @@ Delegation enterNested(Visitable segment) { if (segment instanceof SimpleFunction) { builder.append(((SimpleFunction) segment).getFunctionName()).append("("); insideFunction = true; - } else { - insideFunction = false; } return super.enterNested(segment); @@ -87,14 +88,26 @@ Delegation leaveNested(Visitable segment) { } if (segment instanceof SimpleFunction) { + builder.append(")"); + if (segment instanceof Aliased) { + builder.append(" AS ").append(((Aliased) segment).getAlias()); + } + + insideFunction = false; requiresComma = true; } else if (segment instanceof Column) { + builder.append(context.getNamingStrategy().getName((Column) segment)); - if (segment instanceof Aliased) { + if (segment instanceof Aliased && !insideFunction) { builder.append(" AS ").append(((Aliased) segment).getAlias()); } requiresComma = true; + } else if (segment instanceof AsteriskFromTable) { + // the toString of AsteriskFromTable includes the table name, which would cause it to appear twice. + builder.append("*"); + } else if (segment instanceof Expression) { + builder.append(segment.toString()); } return super.leaveNested(segment); diff --git a/spring-data-relational/src/test/java/org/springframework/data/relational/core/sql/render/InsertRendererUnitTests.java b/spring-data-relational/src/test/java/org/springframework/data/relational/core/sql/render/InsertRendererUnitTests.java index c09d1c757a..6b99fc9099 100644 --- a/spring-data-relational/src/test/java/org/springframework/data/relational/core/sql/render/InsertRendererUnitTests.java +++ b/spring-data-relational/src/test/java/org/springframework/data/relational/core/sql/render/InsertRendererUnitTests.java @@ -18,7 +18,6 @@ import static org.assertj.core.api.Assertions.*; import org.junit.Test; - import org.springframework.data.relational.core.sql.Insert; import org.springframework.data.relational.core.sql.SQL; import org.springframework.data.relational.core.sql.Table; @@ -27,6 +26,7 @@ * Unit tests for {@link SqlRenderer}. * * @author Mark Paluch + * @author Jens Schauder */ public class InsertRendererUnitTests { @@ -37,7 +37,7 @@ public void shouldRenderInsert() { Insert insert = Insert.builder().into(bar).values(SQL.bindMarker()).build(); - assertThat(SqlRenderer.toString(insert)).isEqualTo("INSERT INTO bar VALUES(?)"); + assertThat(SqlRenderer.toString(insert)).isEqualTo("INSERT INTO bar VALUES (?)"); } @Test // DATAJDBC-335 @@ -47,7 +47,7 @@ public void shouldRenderInsertColumn() { Insert insert = Insert.builder().into(bar).column(bar.column("foo")).values(SQL.bindMarker()).build(); - assertThat(SqlRenderer.toString(insert)).isEqualTo("INSERT INTO bar (foo) VALUES(?)"); + assertThat(SqlRenderer.toString(insert)).isEqualTo("INSERT INTO bar (foo) VALUES (?)"); } @Test // DATAJDBC-335 @@ -58,6 +58,17 @@ public void shouldRenderInsertMultipleColumns() { Insert insert = Insert.builder().into(bar).columns(bar.columns("foo", "baz")).value(SQL.bindMarker()) .value(SQL.literalOf("foo")).build(); - assertThat(SqlRenderer.toString(insert)).isEqualTo("INSERT INTO bar (foo, baz) VALUES(?, 'foo')"); + assertThat(SqlRenderer.toString(insert)).isEqualTo("INSERT INTO bar (foo, baz) VALUES (?, 'foo')"); + } + + @Test // DATAJDBC-340 + public void shouldRenderInsertWithZeroColumns() { + + Table bar = SQL.table("bar"); + + Insert insert = Insert.builder().into(bar).build(); + + assertThat(SqlRenderer.toString(insert)).isEqualTo("INSERT INTO bar VALUES ()"); } + } diff --git a/spring-data-relational/src/test/java/org/springframework/data/relational/core/sql/render/SelectRendererUnitTests.java b/spring-data-relational/src/test/java/org/springframework/data/relational/core/sql/render/SelectRendererUnitTests.java index 2cb98b0560..f61b17dc59 100644 --- a/spring-data-relational/src/test/java/org/springframework/data/relational/core/sql/render/SelectRendererUnitTests.java +++ b/spring-data-relational/src/test/java/org/springframework/data/relational/core/sql/render/SelectRendererUnitTests.java @@ -17,10 +17,11 @@ import static org.assertj.core.api.Assertions.*; +import org.junit.Ignore; import org.junit.Test; - import org.springframework.data.relational.core.sql.Column; import org.springframework.data.relational.core.sql.Conditions; +import org.springframework.data.relational.core.sql.Expressions; import org.springframework.data.relational.core.sql.Functions; import org.springframework.data.relational.core.sql.OrderByField; import org.springframework.data.relational.core.sql.SQL; @@ -93,6 +94,17 @@ public void shouldRenderCountFunction() { assertThat(SqlRenderer.toString(select)).isEqualTo("SELECT COUNT(bar.foo), bar.bar FROM bar"); } + @Test // DATAJDBC-340 + public void shouldRenderCountFunctionWithAliasedColumn() { + + Table table = SQL.table("bar"); + Column foo = table.column("foo").as("foo_bar"); + + Select select = Select.builder().select(Functions.count(foo), foo).from(table).build(); + + assertThat(SqlRenderer.toString(select)).isEqualTo("SELECT COUNT(bar.foo), bar.foo AS foo_bar FROM bar"); + } + @Test // DATAJDBC-309 public void shouldRenderSimpleJoin() { @@ -107,6 +119,21 @@ public void shouldRenderSimpleJoin() { + "JOIN department ON employee.department_id = department.id"); } + @Test // DATAJDBC-340 + public void shouldRenderOuterJoin() { + + Table employee = SQL.table("employee"); + Table department = SQL.table("department"); + + Select select = Select.builder().select(employee.column("id"), department.column("name")) // + .from(employee) // + .leftOuterJoin(department).on(employee.column("department_id")).equals(department.column("id")) // + .build(); + + assertThat(SqlRenderer.toString(select)).isEqualTo("SELECT employee.id, department.name FROM employee " + + "LEFT OUTER JOIN department ON employee.department_id = department.id"); + } + @Test // DATAJDBC-309 public void shouldRenderSimpleJoinWithAnd() { @@ -119,7 +146,7 @@ public void shouldRenderSimpleJoinWithAnd() { .build(); assertThat(SqlRenderer.toString(select)).isEqualTo("SELECT employee.id, department.name FROM employee " // - + "JOIN department ON employee.department_id = department.id " // + + "JOIN department ON employee.department_id = department.id " // + "AND employee.tenant = department.tenant"); } @@ -246,7 +273,7 @@ public void shouldRenderInSubselect() { Select subselect = Select.builder().select(bah).from(floo).build(); - Select select = Select.builder().select(bar).from(foo).where(Conditions.in(bar, subselect)).build(); + Select select = Select.builder().select(bar).from(foo).where(bar.in(subselect)).build(); assertThat(SqlRenderer.toString(select)) .isEqualTo("SELECT foo.bar FROM foo WHERE foo.bar IN (SELECT floo.bah FROM floo)"); @@ -272,4 +299,44 @@ public void shouldConsiderNamingStrategy() { assertThat(mapped).isEqualTo("SELECT foo.baR FROM foo WHERE foo.baR = foo.baZ"); } + @Test // DATAJDBC-340 + public void shouldRenderCountStar() { + + Select select = Select.builder() // + .select(Functions.count(Expressions.asterisk())) // + .from(SQL.table("foo")) // + .build(); + + String rendered = SqlRenderer.toString(select); + + assertThat(rendered).isEqualTo("SELECT COUNT(*) FROM foo"); + } + + @Test // DATAJDBC-340 + public void shouldRenderCountTableStar() { + + Table foo = SQL.table("foo"); + Select select = Select.builder() // + .select(Functions.count(foo.asterisk())) // + .from(foo) // + .build(); + + String rendered = SqlRenderer.toString(select); + + assertThat(rendered).isEqualTo("SELECT COUNT(foo.*) FROM foo"); + } + + @Test // DATAJDBC-340 + public void shouldRenderFunctionWithAlias() { + + Table foo = SQL.table("foo"); + Select select = Select.builder() // + .select(Functions.count(foo.asterisk()).as("counter")) // + .from(foo) // + .build(); + + String rendered = SqlRenderer.toString(select); + + assertThat(rendered).isEqualTo("SELECT COUNT(foo.*) AS counter FROM foo"); + } } From 5b73e40a469d578f869d7c29296de1a9e1749269 Mon Sep 17 00:00:00 2001 From: Mark Paluch Date: Mon, 8 Apr 2019 12:36:53 +0200 Subject: [PATCH 3/3] DATAJDBC-340 - Polishing. Encapsulate column caches in Columns type. Relax RelationalMappingContext to MappingContext with appropriate generics. Remove unused SQL generator source. Cache table and Id column objects. Simplify assertions. Consistently use naming pattern for named parameters. Migrate http URLs to https. --- mvnw | 2 +- mvnw.cmd | 4 +- .../core/PersistentPropertyPathExtension.java | 40 +- .../data/jdbc/core/SqlContext.java | 15 +- .../data/jdbc/core/SqlGenerator.java | 364 +++++++++++------- .../data/jdbc/core/SqlGeneratorSource.java | 4 +- ...sistentPropertyPathExtensionUnitTests.java | 2 +- ...orContextBasedNamingStrategyUnitTests.java | 2 +- .../core/SqlGeneratorEmbeddedUnitTests.java | 2 +- ...GeneratorFixedNamingStrategyUnitTests.java | 2 +- .../data/jdbc/core/SqlGeneratorUnitTests.java | 20 +- 11 files changed, 264 insertions(+), 193 deletions(-) diff --git a/mvnw b/mvnw index 5551fde8e7..8b9da3b8b6 100755 --- a/mvnw +++ b/mvnw @@ -8,7 +8,7 @@ # "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 +# 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 diff --git a/mvnw.cmd b/mvnw.cmd index e5cfb0ae9e..b3f995811c 100755 --- a/mvnw.cmd +++ b/mvnw.cmd @@ -7,7 +7,7 @@ @REM "License"); you may not use this file except in compliance @REM with the License. You may obtain a copy of the License at @REM -@REM http://www.apache.org/licenses/LICENSE-2.0 +@REM https://www.apache.org/licenses/LICENSE-2.0 @REM @REM Unless required by applicable law or agreed to in writing, @REM software distributed under the License is distributed on an @@ -122,7 +122,7 @@ set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain set DOWNLOAD_URL="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.4.2/maven-wrapper-0.4.2.jar" FOR /F "tokens=1,2 delims==" %%A IN (%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties) DO ( - IF "%%A"=="wrapperUrl" SET DOWNLOAD_URL=%%B + IF "%%A"=="wrapperUrl" SET DOWNLOAD_URL=%%B ) @REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central diff --git a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/PersistentPropertyPathExtension.java b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/PersistentPropertyPathExtension.java index 32154adf1e..a9a5e757c1 100644 --- a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/PersistentPropertyPathExtension.java +++ b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/PersistentPropertyPathExtension.java @@ -5,7 +5,7 @@ * 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 + * 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, @@ -15,10 +15,8 @@ */ package org.springframework.data.jdbc.core; -import java.util.Objects; - import org.springframework.data.mapping.PersistentPropertyPath; -import org.springframework.data.relational.core.mapping.RelationalMappingContext; +import org.springframework.data.mapping.context.MappingContext; import org.springframework.data.relational.core.mapping.RelationalPersistentEntity; import org.springframework.data.relational.core.mapping.RelationalPersistentProperty; import org.springframework.lang.Nullable; @@ -34,10 +32,11 @@ class PersistentPropertyPathExtension { private final RelationalPersistentEntity entity; - private final PersistentPropertyPath path; - private final RelationalMappingContext context; + private final @Nullable PersistentPropertyPath path; + private final MappingContext, RelationalPersistentProperty> context; - PersistentPropertyPathExtension(RelationalMappingContext context, RelationalPersistentEntity entity) { + PersistentPropertyPathExtension(MappingContext, RelationalPersistentProperty> context, + RelationalPersistentEntity entity) { Assert.notNull(context, "Context must not be null."); Assert.notNull(entity, "Entity must not be null."); @@ -47,15 +46,15 @@ class PersistentPropertyPathExtension { this.path = null; } - PersistentPropertyPathExtension(RelationalMappingContext context, + PersistentPropertyPathExtension(MappingContext, RelationalPersistentProperty> context, PersistentPropertyPath path) { Assert.notNull(context, "Context must not be null."); Assert.notNull(path, "Path must not be null."); - Assert.isTrue(!path.isEmpty(), "Path must not be empty."); + Assert.notNull(path.getBaseProperty(), "Path must not be empty."); this.context = context; - this.entity = Objects.requireNonNull(path.getBaseProperty()).getOwner(); + this.entity = path.getBaseProperty().getOwner(); this.path = path; } @@ -140,6 +139,8 @@ boolean isCollectionLike() { */ String getReverseColumnName() { + Assert.state(path != null, "Path is null"); + return path.getRequiredLeafProperty().getReverseColumnName(); } @@ -160,6 +161,8 @@ String getReverseColumnNameAlias() { */ String getColumnName() { + Assert.state(path != null, "Path is null"); + return assembleColumnName(path.getRequiredLeafProperty().getColumnName()); } @@ -212,11 +215,9 @@ String getTableName() { String getTableAlias() { PersistentPropertyPathExtension tableOwner = getTableOwningAncestor(); - if (tableOwner.path == null) { - return null; - } - return tableOwner.assembleTableAlias(); + return tableOwner.path == null ? null : tableOwner.assembleTableAlias(); + } /** @@ -251,14 +252,13 @@ int getLength() { */ private PersistentPropertyPathExtension getTableOwningAncestor() { - if (isEntity() && !isEmbedded()) { - return this; - } - return getParentPath().getTableOwningAncestor(); + return isEntity() && !isEmbedded() ? this : getParentPath().getTableOwningAncestor(); } private String assembleTableAlias() { + Assert.state(path != null, "Path is null"); + RelationalPersistentProperty leafProperty = path.getRequiredLeafProperty(); String prefix = isEmbedded() ? leafProperty.getEmbeddedPrefix() : leafProperty.getName(); @@ -274,6 +274,8 @@ private String assembleTableAlias() { private String assembleColumnName(String suffix) { + Assert.state(path != null, "Path is null"); + if (path.getLength() <= 1) { return suffix; } @@ -286,11 +288,11 @@ private String assembleColumnName(String suffix) { return getParentPath().assembleColumnName(embeddedPrefix + suffix); } + @SuppressWarnings("unchecked") private RelationalPersistentEntity getRequiredLeafEntity() { return path == null ? entity : context.getRequiredPersistentEntity(path.getRequiredLeafProperty().getActualType()); } - private String prefixWithTableAlias(String columnName) { String tableAlias = getTableAlias(); diff --git a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/SqlContext.java b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/SqlContext.java index 33346a99a8..aabb3707f8 100644 --- a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/SqlContext.java +++ b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/SqlContext.java @@ -5,7 +5,7 @@ * 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 + * 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, @@ -24,18 +24,25 @@ * Utility to get from path to SQL DSL elements. * * @author Jens Schauder + * @author Mark Paluch * @since 1.1 */ class SqlContext { private final RelationalPersistentEntity entity; + private final Table table; SqlContext(RelationalPersistentEntity entity) { this.entity = entity; + this.table = SQL.table(entity.getTableName()); } Column getIdColumn() { - return getTable().column(entity.getIdColumn()); + return table.column(entity.getIdColumn()); + } + + Table getTable() { + return table; } Table getTable(PersistentPropertyPathExtension path) { @@ -45,10 +52,6 @@ Table getTable(PersistentPropertyPathExtension path) { return tableAlias == null ? table : table.as(tableAlias); } - Table getTable() { - return SQL.table(entity.getTableName()); - } - Column getColumn(PersistentPropertyPathExtension path) { return getTable(path).column(path.getColumnName()).as(path.getColumnAlias()); } diff --git a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/SqlGenerator.java b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/SqlGenerator.java index 958a7f16db..bfa8fe67be 100644 --- a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/SqlGenerator.java +++ b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/SqlGenerator.java @@ -18,6 +18,8 @@ import lombok.Value; import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; import java.util.HashSet; import java.util.LinkedHashSet; import java.util.List; @@ -26,19 +28,35 @@ import java.util.function.Function; import java.util.regex.Pattern; import java.util.stream.Collectors; -import java.util.stream.Stream; import org.springframework.data.annotation.ReadOnlyProperty; import org.springframework.data.jdbc.repository.support.SimpleJdbcRepository; import org.springframework.data.mapping.PersistentPropertyPath; import org.springframework.data.mapping.PropertyHandler; +import org.springframework.data.mapping.context.MappingContext; import org.springframework.data.relational.core.mapping.RelationalMappingContext; import org.springframework.data.relational.core.mapping.RelationalPersistentEntity; import org.springframework.data.relational.core.mapping.RelationalPersistentProperty; -import org.springframework.data.relational.core.sql.*; +import org.springframework.data.relational.core.sql.AssignValue; +import org.springframework.data.relational.core.sql.Assignments; +import org.springframework.data.relational.core.sql.BindMarker; +import org.springframework.data.relational.core.sql.Column; +import org.springframework.data.relational.core.sql.Condition; +import org.springframework.data.relational.core.sql.Delete; +import org.springframework.data.relational.core.sql.DeleteBuilder; +import org.springframework.data.relational.core.sql.Expression; +import org.springframework.data.relational.core.sql.Expressions; +import org.springframework.data.relational.core.sql.Functions; +import org.springframework.data.relational.core.sql.Insert; +import org.springframework.data.relational.core.sql.InsertBuilder; +import org.springframework.data.relational.core.sql.SQL; +import org.springframework.data.relational.core.sql.Select; +import org.springframework.data.relational.core.sql.SelectBuilder; +import org.springframework.data.relational.core.sql.StatementBuilder; +import org.springframework.data.relational.core.sql.Table; +import org.springframework.data.relational.core.sql.Update; import org.springframework.data.relational.core.sql.render.SqlRenderer; import org.springframework.data.util.Lazy; -import org.springframework.data.util.StreamUtils; import org.springframework.lang.Nullable; import org.springframework.util.Assert; @@ -49,14 +67,17 @@ * @author Yoichi Imai * @author Bastian Wilhelm * @author Oleksandr Kucher + * @author Mark Paluch */ class SqlGenerator { + private static final Pattern parameterPattern = Pattern.compile("\\W"); + private final RelationalPersistentEntity entity; - private final RelationalMappingContext mappingContext; - private final List columnNames = new ArrayList<>(); - private final List nonIdColumnNames = new ArrayList<>(); - private final Set readOnlyColumnNames = new HashSet<>(); + private final MappingContext, RelationalPersistentProperty> mappingContext; + + private final SqlContext sqlContext; + private final Columns columns; private final Lazy findOneSql = Lazy.of(this::createFindOneSql); private final Lazy findAllSql = Lazy.of(this::createFindAllSql); @@ -69,56 +90,19 @@ class SqlGenerator { private final Lazy deleteByIdSql = Lazy.of(this::createDeleteSql); private final Lazy deleteByListSql = Lazy.of(this::createDeleteByListSql); - private final SqlGeneratorSource sqlGeneratorSource; - private final Pattern parameterPattern = Pattern.compile("\\W"); - private final SqlContext sqlContext; - - SqlGenerator(RelationalMappingContext mappingContext, RelationalPersistentEntity entity, - SqlGeneratorSource sqlGeneratorSource) { + /** + * Create a new {@link SqlGenerator} given {@link RelationalMappingContext} and {@link RelationalPersistentEntity}. + * + * @param mappingContext must not be {@literal null}. + * @param entity must not be {@literal null}. + */ + SqlGenerator(RelationalMappingContext mappingContext, RelationalPersistentEntity entity) { this.mappingContext = mappingContext; this.entity = entity; - this.sqlGeneratorSource = sqlGeneratorSource; this.sqlContext = new SqlContext(entity); - initColumnNames(entity, ""); - } - - private void initColumnNames(RelationalPersistentEntity entity, String prefix) { - - entity.doWithProperties((PropertyHandler) property -> { - - // the referencing column of referenced entity is expected to be on the other side of the relation - if (!property.isEntity()) { - initSimpleColumnName(property, prefix); - } else if (property.isEmbedded()) { - initEmbeddedColumnNames(property, prefix); - } - }); - } - - private void initSimpleColumnName(RelationalPersistentProperty property, String prefix) { - - String columnName = prefix + property.getColumnName(); - - columnNames.add(columnName); - - if (!entity.isIdProperty(property)) { - nonIdColumnNames.add(columnName); - } - if (property.isAnnotationPresent(ReadOnlyProperty.class)) { - readOnlyColumnNames.add(columnName); - } - } - - private void initEmbeddedColumnNames(RelationalPersistentProperty property, String prefix) { - - final String embeddedPrefix = property.getEmbeddedPrefix(); - - final RelationalPersistentEntity embeddedEntity = mappingContext - .getRequiredPersistentEntity(property.getColumnType()); - - initColumnNames(embeddedEntity, prefix + embeddedPrefix); + this.columns = new Columns(entity, mappingContext); } /** @@ -157,17 +141,18 @@ String getFindAllByProperty(String columnName, @Nullable String keyColumn, boole Assert.isTrue(keyColumn != null || !ordered, "If the SQL statement should be ordered a keyColumn to order by must be provided."); - SelectBuilder.SelectWhere baseSelect = createBaseSelect(keyColumn); + SelectBuilder.SelectWhere builder = selectBuilder( + keyColumn == null ? Collections.emptyList() : Collections.singleton(keyColumn)); - Table table = Table.create(entity.getTableName()); - SelectBuilder.SelectWhereAndOr withWhereClause = baseSelect - .where(table.column(columnName).isEqualTo(SQL.bindMarker(":" + columnName))); + Table table = getTable(); + SelectBuilder.SelectWhereAndOr withWhereClause = builder + .where(table.column(columnName).isEqualTo(getBindMarker(columnName))); - SelectBuilder.BuildSelect select; + Select select; if (ordered) { - select = withWhereClause.orderBy(table.column(keyColumn).as(keyColumn)); + select = withWhereClause.orderBy(table.column(keyColumn).as(keyColumn)).build(); } else { - select = withWhereClause; + select = withWhereClause.build(); } return render(select); @@ -203,39 +188,23 @@ String getDeleteByList() { private String createFindOneSql() { - SelectBuilder.SelectWhereAndOr withCondition = createBaseSelect() - .where(sqlContext.getIdColumn().isEqualTo(SQL.bindMarker(":id"))); - - return render(withCondition); - } - - private Stream getColumnNameStream(String prefix) { - - return StreamUtils.createStreamFromIterator(entity.iterator()) // - .flatMap(p -> getColumnNameStream(p, prefix)); - } + Select select = selectBuilder().where(getIdColumn().isEqualTo(getBindMarker("id"))) // + .build(); - private Stream getColumnNameStream(RelationalPersistentProperty p, String prefix) { - - if (p.isEntity()) { - return sqlGeneratorSource.getSqlGenerator(p.getType()).getColumnNameStream(prefix + p.getColumnName() + "_"); - } else { - return Stream.of(prefix + p.getColumnName()); - } + return render(select); } private String createFindAllSql() { - return render(createBaseSelect()); + return render(selectBuilder().build()); } - private SelectBuilder.SelectWhere createBaseSelect() { - - return createBaseSelect(null); + private SelectBuilder.SelectWhere selectBuilder() { + return selectBuilder(Collections.emptyList()); } - private SelectBuilder.SelectWhere createBaseSelect(@Nullable String keyColumn) { + private SelectBuilder.SelectWhere selectBuilder(Collection keyColumns) { - Table table = SQL.table(entity.getTableName()); + Table table = getTable(); List columnExpressions = new ArrayList<>(); @@ -257,12 +226,11 @@ private SelectBuilder.SelectWhere createBaseSelect(@Nullable String keyColumn) { } } - if (keyColumn != null) { + for (String keyColumn : keyColumns) { columnExpressions.add(table.column(keyColumn).as(keyColumn)); } SelectBuilder.SelectAndFrom selectBuilder = StatementBuilder.select(columnExpressions); - SelectBuilder.SelectJoin baseSelect = selectBuilder.from(table); for (Join join : joinTables) { @@ -296,11 +264,9 @@ Column getColumn(PersistentPropertyPathExtension path) { } return sqlContext.getReverseColumn(path); - } return sqlContext.getColumn(path); - } @Nullable @@ -324,59 +290,42 @@ Join getJoin(PersistentPropertyPathExtension path) { private String createFindAllInListSql() { - SelectBuilder.SelectWhereAndOr withCondition = createBaseSelect() - .where(sqlContext.getIdColumn().in(SQL.bindMarker(":ids"))); - - return render(withCondition); - } - - private String render(SelectBuilder.BuildSelect select) { - return SqlRenderer.create().render(select.build()); - } - - private String render(InsertBuilder.BuildInsert insert) { - return SqlRenderer.create().render(insert.build()); - } - - private String render(DeleteBuilder.BuildDelete delete) { - return SqlRenderer.create().render(delete.build()); - } + Select select = selectBuilder().where(getIdColumn().in(getBindMarker("ids"))).build(); - private String render(UpdateBuilder.BuildUpdate update) { - return SqlRenderer.create().render(update.build()); + return render(select); } private String createExistsSql() { - Table table = sqlContext.getTable(); - Column idColumn = table.column(entity.getIdColumn()); + Table table = getTable(); - SelectBuilder.BuildSelect select = StatementBuilder // - .select(Functions.count(idColumn)) // + Select select = StatementBuilder // + .select(Functions.count(getIdColumn())) // .from(table) // - .where(idColumn.isEqualTo(SQL.bindMarker(":id"))); + .where(getIdColumn().isEqualTo(getBindMarker("id"))) // + .build(); return render(select); } private String createCountSql() { - Table table = SQL.table(entity.getTableName()); + Table table = getTable(); - SelectBuilder.BuildSelect select = StatementBuilder // + Select select = StatementBuilder // .select(Functions.count(Expressions.asterisk())) // - .from(table); + .from(table) // + .build(); return render(select); } private String createInsertSql(Set additionalColumns) { - Table table = SQL.table(entity.getTableName()); + Table table = getTable(); - LinkedHashSet columnNamesForInsert = new LinkedHashSet<>(nonIdColumnNames); + Set columnNamesForInsert = new LinkedHashSet<>(columns.getInsertableColumns()); columnNamesForInsert.addAll(additionalColumns); - columnNamesForInsert.removeIf(readOnlyColumnNames::contains); InsertBuilder.InsertIntoColumnsAndValuesWithBuild insert = Insert.builder().into(table); @@ -386,71 +335,70 @@ private String createInsertSql(Set additionalColumns) { InsertBuilder.InsertValuesWithBuild insertWithValues = null; for (String cn : columnNamesForInsert) { - insertWithValues = (insertWithValues == null ? insert : insertWithValues) - .values(SQL.bindMarker(":" + columnNameToParameterName(cn))); + insertWithValues = (insertWithValues == null ? insert : insertWithValues).values(getBindMarker(cn)); } - return render(insertWithValues == null ? insert : insertWithValues); + return render(insertWithValues == null ? insert.build() : insertWithValues.build()); } private String createUpdateSql() { - Table table = SQL.table(entity.getTableName()); + Table table = getTable(); - List assignments = columnNames.stream() // - .filter(s -> !s.equals(entity.getIdColumn())) // - .filter(s -> !readOnlyColumnNames.contains(s)) // + List assignments = columns.getUpdateableColumns() // + .stream() // .map(columnName -> Assignments.value( // table.column(columnName), // - SQL.bindMarker(":" + columnNameToParameterName(columnName)))) // + getBindMarker(columnName))) // .collect(Collectors.toList()); - UpdateBuilder.UpdateWhereAndOr update = Update.builder() // + Update update = Update.builder() // .table(table) // .set(assignments) // - .where(table.column(entity.getIdColumn()) - .isEqualTo(SQL.bindMarker(":" + columnNameToParameterName(entity.getIdColumn())))) // - ; + .where(getIdColumn().isEqualTo(getBindMarker(entity.getIdColumn()))) // + .build(); return render(update); } private String createDeleteSql() { - Table table = SQL.table(entity.getTableName()); + Table table = getTable(); - DeleteBuilder.DeleteWhereAndOr delete = Delete.builder().from(table) - .where(table.column(entity.getIdColumn()).isEqualTo(SQL.bindMarker(":id"))); + Delete delete = Delete.builder().from(table).where(getIdColumn().isEqualTo(SQL.bindMarker(":id"))) // + .build(); return render(delete); } String createDeleteAllSql(@Nullable PersistentPropertyPath path) { - Table table = SQL.table(entity.getTableName()); + Table table = getTable(); DeleteBuilder.DeleteWhere deleteAll = Delete.builder().from(table); if (path == null) { - return render(deleteAll); + return render(deleteAll.build()); } + return createDeleteByPathAndCriteria(new PersistentPropertyPathExtension(mappingContext, path), Column::isNotNull); } private String createDeleteByListSql() { - Table table = SQL.table(entity.getTableName()); + Table table = getTable(); - DeleteBuilder.DeleteWhereAndOr delete = Delete.builder() // + Delete delete = Delete.builder() // .from(table) // - .where(table.column(entity.getIdColumn()).in(SQL.bindMarker(":ids"))); + .where(getIdColumn().in(getBindMarker("ids"))) // + .build(); return render(delete); } String createDeleteByPath(PersistentPropertyPath path) { return createDeleteByPathAndCriteria(new PersistentPropertyPathExtension(mappingContext, path), - filterColumn -> filterColumn.isEqualTo(SQL.bindMarker(":rootId"))); + filterColumn -> filterColumn.isEqualTo(getBindMarker("rootId"))); } private String createDeleteByPathAndCriteria(PersistentPropertyPathExtension path, @@ -458,26 +406,27 @@ private String createDeleteByPathAndCriteria(PersistentPropertyPathExtension pat Table table = SQL.table(path.getTableName()); - DeleteBuilder.DeleteWhere delete = Delete.builder() // + DeleteBuilder.DeleteWhere builder = Delete.builder() // .from(table); - - DeleteBuilder.DeleteWhereAndOr deleteWithWhere; + Delete delete; Column filterColumn = table.column(path.getReverseColumnName()); if (path.getLength() == 1) { - deleteWithWhere = delete // - .where(rootCondition.apply(filterColumn)); + delete = builder // + .where(rootCondition.apply(filterColumn)) // + .build(); } else { Condition condition = getSubselectCondition(path, rootCondition, filterColumn); - deleteWithWhere = delete.where(condition); + delete = builder.where(condition).build(); } - return render(deleteWithWhere); + + return render(delete); } - private Condition getSubselectCondition(PersistentPropertyPathExtension path, + private static Condition getSubselectCondition(PersistentPropertyPathExtension path, Function rootCondition, Column filterColumn) { PersistentPropertyPathExtension parentPath = path.getParentPath(); @@ -497,15 +446,132 @@ private Condition getSubselectCondition(PersistentPropertyPathExtension path, return filterColumn.in(select); } - private String columnNameToParameterName(String columnName) { - return parameterPattern.matcher(columnName).replaceAll(""); + private String render(Select select) { + return SqlRenderer.create().render(select); + } + + private String render(Insert insert) { + return SqlRenderer.create().render(insert); + } + + private String render(Update update) { + return SqlRenderer.create().render(update); + } + + private String render(Delete delete) { + return SqlRenderer.create().render(delete); + } + + private Table getTable() { + return sqlContext.getTable(); + } + + private Column getIdColumn() { + return sqlContext.getIdColumn(); + } + + private static BindMarker getBindMarker(String columnName) { + return SQL.bindMarker(":" + parameterPattern.matcher(columnName).replaceAll("")); } + /** + * Value object representing a {@code JOIN} association. + */ @Value - class Join { + static class Join { Table joinTable; Column joinColumn; Column parentId; } + /** + * Value object encapsulating column name caches. + * + * @author Mark Paluch + */ + static class Columns { + + private final MappingContext, RelationalPersistentProperty> mappingContext; + + private final List columnNames = new ArrayList<>(); + private final List idColumnNames = new ArrayList<>(); + private final List nonIdColumnNames = new ArrayList<>(); + private final Set readOnlyColumnNames = new HashSet<>(); + private final Set insertableColumns; + private final Set updateableColumns; + + Columns(RelationalPersistentEntity entity, + MappingContext, RelationalPersistentProperty> mappingContext) { + + this.mappingContext = mappingContext; + + populateColumnNameCache(entity, ""); + + Set insertable = new LinkedHashSet<>(nonIdColumnNames); + insertable.removeAll(readOnlyColumnNames); + + this.insertableColumns = Collections.unmodifiableSet(insertable); + + Set updateable = new LinkedHashSet<>(columnNames); + + updateable.removeAll(idColumnNames); + updateable.removeAll(readOnlyColumnNames); + + this.updateableColumns = Collections.unmodifiableSet(updateable); + } + + private void populateColumnNameCache(RelationalPersistentEntity entity, String prefix) { + + entity.doWithProperties((PropertyHandler) property -> { + + // the referencing column of referenced entity is expected to be on the other side of the relation + if (!property.isEntity()) { + initSimpleColumnName(property, prefix); + } else if (property.isEmbedded()) { + initEmbeddedColumnNames(property, prefix); + } + }); + } + + private void initSimpleColumnName(RelationalPersistentProperty property, String prefix) { + + String columnName = prefix + property.getColumnName(); + + columnNames.add(columnName); + + if (!property.getOwner().isIdProperty(property)) { + nonIdColumnNames.add(columnName); + } else { + idColumnNames.add(columnName); + } + + if (!property.isWritable() || property.isAnnotationPresent(ReadOnlyProperty.class)) { + readOnlyColumnNames.add(columnName); + } + } + + private void initEmbeddedColumnNames(RelationalPersistentProperty property, String prefix) { + + String embeddedPrefix = property.getEmbeddedPrefix(); + + RelationalPersistentEntity embeddedEntity = mappingContext + .getRequiredPersistentEntity(property.getColumnType()); + + populateColumnNameCache(embeddedEntity, prefix + embeddedPrefix); + } + + /** + * @return Column names that can be used for {@code INSERT}. + */ + Set getInsertableColumns() { + return insertableColumns; + } + + /** + * @return Column names that can be used for {@code UPDATE}. + */ + Set getUpdateableColumns() { + return updateableColumns; + } + } } diff --git a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/SqlGeneratorSource.java b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/SqlGeneratorSource.java index b66273a9e5..732d9807ec 100644 --- a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/SqlGeneratorSource.java +++ b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/SqlGeneratorSource.java @@ -17,7 +17,6 @@ import lombok.RequiredArgsConstructor; -import java.util.HashMap; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; @@ -38,7 +37,6 @@ public class SqlGeneratorSource { SqlGenerator getSqlGenerator(Class domainType) { return sqlGeneratorCache.computeIfAbsent(domainType, - t -> new SqlGenerator(context, context.getRequiredPersistentEntity(t), this)); - + t -> new SqlGenerator(context, context.getRequiredPersistentEntity(t))); } } diff --git a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/PersistentPropertyPathExtensionUnitTests.java b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/PersistentPropertyPathExtensionUnitTests.java index 6e9f48dced..27350c3ef4 100644 --- a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/PersistentPropertyPathExtensionUnitTests.java +++ b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/PersistentPropertyPathExtensionUnitTests.java @@ -5,7 +5,7 @@ * 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 + * 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, diff --git a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/SqlGeneratorContextBasedNamingStrategyUnitTests.java b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/SqlGeneratorContextBasedNamingStrategyUnitTests.java index 03522ba15b..4bf0a59788 100644 --- a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/SqlGeneratorContextBasedNamingStrategyUnitTests.java +++ b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/SqlGeneratorContextBasedNamingStrategyUnitTests.java @@ -212,7 +212,7 @@ private SqlGenerator configureSqlGenerator(NamingStrategy namingStrategy) { RelationalMappingContext context = new JdbcMappingContext(namingStrategy); RelationalPersistentEntity persistentEntity = context.getRequiredPersistentEntity(DummyEntity.class); - return new SqlGenerator(context, persistentEntity, new SqlGeneratorSource(context)); + return new SqlGenerator(context, persistentEntity); } @SuppressWarnings("unused") diff --git a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/SqlGeneratorEmbeddedUnitTests.java b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/SqlGeneratorEmbeddedUnitTests.java index 67a20dcdef..10184c76c9 100644 --- a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/SqlGeneratorEmbeddedUnitTests.java +++ b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/SqlGeneratorEmbeddedUnitTests.java @@ -47,7 +47,7 @@ public void setUp() { SqlGenerator createSqlGenerator(Class type) { RelationalPersistentEntity persistentEntity = context.getRequiredPersistentEntity(type); - return new SqlGenerator(context, persistentEntity, new SqlGeneratorSource(context)); + return new SqlGenerator(context, persistentEntity); } @Test // DATAJDBC-111 diff --git a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/SqlGeneratorFixedNamingStrategyUnitTests.java b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/SqlGeneratorFixedNamingStrategyUnitTests.java index b8a87d1913..2dd17b916c 100644 --- a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/SqlGeneratorFixedNamingStrategyUnitTests.java +++ b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/SqlGeneratorFixedNamingStrategyUnitTests.java @@ -189,7 +189,7 @@ private SqlGenerator configureSqlGenerator(NamingStrategy namingStrategy) { RelationalMappingContext context = new JdbcMappingContext(namingStrategy); RelationalPersistentEntity persistentEntity = context.getRequiredPersistentEntity(DummyEntity.class); - return new SqlGenerator(context, persistentEntity, new SqlGeneratorSource(context)); + return new SqlGenerator(context, persistentEntity); } @SuppressWarnings("unused") diff --git a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/SqlGeneratorUnitTests.java b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/SqlGeneratorUnitTests.java index b2db59898b..a7e6e064b9 100644 --- a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/SqlGeneratorUnitTests.java +++ b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/SqlGeneratorUnitTests.java @@ -24,6 +24,7 @@ import org.assertj.core.api.SoftAssertions; import org.junit.Before; import org.junit.Test; + import org.springframework.data.annotation.Id; import org.springframework.data.annotation.ReadOnlyProperty; import org.springframework.data.jdbc.core.mapping.AggregateReference; @@ -45,6 +46,7 @@ * @author Greg Turnquist * @author Oleksandr Kucher * @author Bastian Wilhelm + * @author Mark Paluch */ public class SqlGeneratorUnitTests { @@ -61,7 +63,7 @@ SqlGenerator createSqlGenerator(Class type) { RelationalPersistentEntity persistentEntity = context.getRequiredPersistentEntity(type); - return new SqlGenerator(context, persistentEntity, new SqlGeneratorSource(context)); + return new SqlGenerator(context, persistentEntity); } @Test // DATAJDBC-112 @@ -146,7 +148,7 @@ public void deleteMapByPath() { public void findAllByProperty() { // this would get called when ListParent is the element type of a Set - String sql = sqlGenerator.getFindAllByProperty("back-ref", null, false); + String sql = sqlGenerator.getFindAllByProperty("backref", null, false); assertThat(sql).contains("SELECT", // "dummy_entity.id1 AS id1", // @@ -159,14 +161,14 @@ public void findAllByProperty() { "FROM dummy_entity ", // "LEFT OUTER JOIN referenced_entity AS ref ON ref.dummy_entity = dummy_entity.id1", // "LEFT OUTER JOIN second_level_referenced_entity AS ref_further ON ref_further.referenced_entity = ref.x_l1id", // - "WHERE dummy_entity.back-ref = :back-ref"); + "WHERE dummy_entity.backref = :backref"); } @Test // DATAJDBC-131, DATAJDBC-111 public void findAllByPropertyWithKey() { // this would get called when ListParent is th element type of a Map - String sql = sqlGenerator.getFindAllByProperty("back-ref", "key-column", false); + String sql = sqlGenerator.getFindAllByProperty("backref", "key-column", false); assertThat(sql).isEqualTo("SELECT dummy_entity.id1 AS id1, dummy_entity.x_name AS x_name, " // + "dummy_entity.x_other AS x_other, " // @@ -176,7 +178,7 @@ public void findAllByPropertyWithKey() { + "FROM dummy_entity " // + "LEFT OUTER JOIN referenced_entity AS ref ON ref.dummy_entity = dummy_entity.id1 " // + "LEFT OUTER JOIN second_level_referenced_entity AS ref_further ON ref_further.referenced_entity = ref.x_l1id " // - + "WHERE dummy_entity.back-ref = :back-ref"); + + "WHERE dummy_entity.backref = :backref"); } @Test(expected = IllegalArgumentException.class) // DATAJDBC-130 @@ -188,7 +190,7 @@ public void findAllByPropertyOrderedWithoutKey() { public void findAllByPropertyWithKeyOrdered() { // this would get called when ListParent is th element type of a Map - String sql = sqlGenerator.getFindAllByProperty("back-ref", "key-column", true); + String sql = sqlGenerator.getFindAllByProperty("backref", "key-column", true); assertThat(sql).isEqualTo("SELECT dummy_entity.id1 AS id1, dummy_entity.x_name AS x_name, " // + "dummy_entity.x_other AS x_other, " // @@ -198,7 +200,7 @@ public void findAllByPropertyWithKeyOrdered() { + "FROM dummy_entity " // + "LEFT OUTER JOIN referenced_entity AS ref ON ref.dummy_entity = dummy_entity.id1 " // + "LEFT OUTER JOIN second_level_referenced_entity AS ref_further ON ref_further.referenced_entity = ref.x_l1id " // - + "WHERE dummy_entity.back-ref = :back-ref " + "ORDER BY key-column"); + + "WHERE dummy_entity.backref = :backref " + "ORDER BY key-column"); } @Test // DATAJDBC-264 @@ -294,14 +296,14 @@ public void readOnlyPropertyIncludedIntoQuery_when_generateFindAllByPropertySql( final SqlGenerator sqlGenerator = createSqlGenerator(EntityWithReadOnlyProperty.class); - assertThat(sqlGenerator.getFindAllByProperty("back-ref", "key-column", true)).isEqualToIgnoringCase( // + assertThat(sqlGenerator.getFindAllByProperty("backref", "key-column", true)).isEqualToIgnoringCase( // "SELECT " // + "entity_with_read_only_property.x_id AS x_id, " // + "entity_with_read_only_property.x_name AS x_name, " // + "entity_with_read_only_property.x_read_only_value AS x_read_only_value, " // + "entity_with_read_only_property.key-column AS key-column " // + "FROM entity_with_read_only_property " // - + "WHERE entity_with_read_only_property.back-ref = :back-ref " // + + "WHERE entity_with_read_only_property.backref = :backref " // + "ORDER BY key-column" // ); }