diff --git a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/SqlGenerator.java b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/SqlGenerator.java index 06bfaf0a52..5ecca36559 100644 --- a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/SqlGenerator.java +++ b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/SqlGenerator.java @@ -50,6 +50,7 @@ * @author Tyler Van Gorder * @author Milan Milanov * @author Myeonghyeon Lee + * @author Mikhail Polivakha */ class SqlGenerator { @@ -92,12 +93,14 @@ class SqlGenerator { SqlGenerator(RelationalMappingContext mappingContext, JdbcConverter converter, RelationalPersistentEntity entity, Dialect dialect) { + final RenderContextFactory renderContextFactory = new RenderContextFactory(dialect); + this.mappingContext = mappingContext; this.entity = entity; this.sqlContext = new SqlContext(entity); - this.sqlRenderer = SqlRenderer.create(new RenderContextFactory(dialect).createRenderContext()); + this.sqlRenderer = SqlRenderer.create(renderContextFactory.createRenderContext()); this.columns = new Columns(entity, mappingContext, converter); - this.renderContext = new RenderContextFactory(dialect).createRenderContext(); + this.renderContext = renderContextFactory.createRenderContext(); } /** 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 52d848a8f0..c7c339c75c 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 @@ -80,6 +80,7 @@ * @author Tyler Van Gorder * @author Clemens Hahn * @author Milan Milanov + * @author Mikhail Polivakha */ @ContextConfiguration @Transactional @@ -887,6 +888,12 @@ public void saveAndLoadDateTimeWithMicrosecondPrecision() { assertThat(loaded.testTime).isEqualTo(entity.testTime); } + @Test // DATAJDBC-557 + public void insertWithIdOnly() { + WithIdOnly entity = new WithIdOnly(); + assertThat(template.save(entity).id).isNotNull(); + } + private void saveAndUpdateAggregateWithVersion(VersionedAggregate aggregate, Function toConcreteNumber) { saveAndUpdateAggregateWithVersion(aggregate, toConcreteNumber, 0); @@ -1254,6 +1261,11 @@ static class WithLocalDateTime { LocalDateTime testTime; } + @Table + class WithIdOnly { + @Id Long id; + } + @Configuration @Import(TestConfiguration.class) static class Config { diff --git a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/convert/DefaultDataAccessStrategyUnitTests.java b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/convert/DefaultDataAccessStrategyUnitTests.java index 48464a0f68..0b1af5627b 100644 --- a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/convert/DefaultDataAccessStrategyUnitTests.java +++ b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/convert/DefaultDataAccessStrategyUnitTests.java @@ -42,6 +42,7 @@ import org.springframework.data.mapping.PersistentPropertyPath; import org.springframework.data.relational.core.dialect.Dialect; import org.springframework.data.relational.core.dialect.HsqlDbDialect; +import org.springframework.data.relational.core.dialect.PostgresDialect; import org.springframework.data.relational.core.mapping.RelationalMappingContext; import org.springframework.data.relational.core.mapping.RelationalPersistentProperty; import org.springframework.data.relational.core.sql.SqlIdentifier; @@ -59,6 +60,7 @@ * @author Myeonghyeon Lee * @author Myat Min * @author Radim Tlusty + * @author Mikhail Polivakha */ public class DefaultDataAccessStrategyUnitTests { @@ -219,7 +221,7 @@ public void insertWithUndefinedIdRetrievesGeneratedKeys() { assertThat(generatedId).isEqualTo(GENERATED_ID); - verify(namedJdbcOperations).update(eq("INSERT INTO \"DUMMY_ENTITY\" VALUES ()"), + verify(namedJdbcOperations).update(eq("INSERT INTO \"DUMMY_ENTITY\"" + HsqlDbDialect.INSTANCE.getSqlInsertWithDefaultValues().getDefaultInsertPart()), paramSourceCaptor.capture(), any(KeyHolder.class)); } diff --git a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/convert/SqlGeneratorUnitTests.java b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/convert/SqlGeneratorUnitTests.java index 5e9f696103..5ebef03516 100644 --- a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/convert/SqlGeneratorUnitTests.java +++ b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/convert/SqlGeneratorUnitTests.java @@ -39,6 +39,8 @@ import org.springframework.data.mapping.PersistentPropertyPath; import org.springframework.data.relational.core.dialect.AnsiDialect; import org.springframework.data.relational.core.dialect.Dialect; +import org.springframework.data.relational.core.dialect.PostgresDialect; +import org.springframework.data.relational.core.dialect.SqlServerDialect; import org.springframework.data.relational.core.mapping.Column; import org.springframework.data.relational.core.mapping.NamingStrategy; import org.springframework.data.relational.core.mapping.PersistentPropertyPathExtension; @@ -61,6 +63,7 @@ * @author Tom Hombergs * @author Milan Milanov * @author Myeonghyeon Lee + * @author Mikhail Polivakha */ class SqlGeneratorUnitTests { @@ -391,13 +394,22 @@ void updateWithVersion() { } @Test // DATAJDBC-264 - void getInsertForEmptyColumnList() { + public void getInsertForEmptyColumnListPostgres() { - SqlGenerator sqlGenerator = createSqlGenerator(IdOnlyEntity.class); + SqlGenerator sqlGenerator = createSqlGenerator(IdOnlyEntity.class, PostgresDialect.INSTANCE); - String insert = sqlGenerator.getInsert(emptySet()); + String insertSqlStatement = sqlGenerator.getInsert(emptySet()); + + assertThat(insertSqlStatement).endsWith(" VALUES (DEFAULT) "); + } + + @Test //DATAJDBC-557 + void gerInsertForEmptyColumnListMsSqlServer() { + SqlGenerator sqlGenerator = createSqlGenerator(IdOnlyEntity.class, SqlServerDialect.INSTANCE); + + String insertSqlStatement = sqlGenerator.getInsert(emptySet()); - assertThat(insert).endsWith("()"); + assertThat(insertSqlStatement).endsWith(" DEFAULT VALUES "); } @Test // DATAJDBC-334 diff --git a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/config/AbstractJdbcConfigurationIntegrationTests.java b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/config/AbstractJdbcConfigurationIntegrationTests.java index 91e166f3c2..4a0d56ea5a 100644 --- a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/config/AbstractJdbcConfigurationIntegrationTests.java +++ b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/config/AbstractJdbcConfigurationIntegrationTests.java @@ -38,6 +38,7 @@ import org.springframework.data.jdbc.core.convert.JdbcCustomConversions; import org.springframework.data.jdbc.core.mapping.JdbcMappingContext; import org.springframework.data.relational.core.dialect.Dialect; +import org.springframework.data.relational.core.dialect.InsertWithDefaultValues; import org.springframework.data.relational.core.dialect.LimitClause; import org.springframework.data.relational.core.dialect.LockClause; import org.springframework.data.relational.core.sql.render.SelectRenderContext; @@ -49,6 +50,7 @@ * Integration tests for {@link AbstractJdbcConfiguration}. * * @author Oliver Drotbohm + * @author Mikhail Polivakha */ public class AbstractJdbcConfigurationIntegrationTests { @@ -167,6 +169,11 @@ public SelectRenderContext getSelectContext() { public Collection getConverters() { return asList(BooleanToNumberConverter.INSTANCE, NumberToBooleanConverter.INSTANCE); } + + @Override + public InsertWithDefaultValues getSqlInsertWithDefaultValues() { + return null; + } } @WritingConverter diff --git a/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.core/JdbcAggregateTemplateIntegrationTests-db2.sql b/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.core/JdbcAggregateTemplateIntegrationTests-db2.sql index 7b2b8d63e5..1d58605c7a 100644 --- a/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.core/JdbcAggregateTemplateIntegrationTests-db2.sql +++ b/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.core/JdbcAggregateTemplateIntegrationTests-db2.sql @@ -37,6 +37,8 @@ DROP TABLE WITH_READ_ONLY; DROP TABLE VERSIONED_AGGREGATE; DROP TABLE WITH_LOCAL_DATE_TIME; +DROP TABLE WITH_ID_ONLY; + CREATE TABLE LEGO_SET ( "id1" BIGINT GENERATED BY DEFAULT AS IDENTITY (START WITH 1) PRIMARY KEY, @@ -350,4 +352,9 @@ CREATE TABLE WITH_LOCAL_DATE_TIME ( ID BIGINT NOT NULL PRIMARY KEY, TEST_TIME TIMESTAMP(9) +); + +CREATE TABLE WITH_ID_ONLY +( + ID BIGINT GENERATED BY DEFAULT AS IDENTITY (START WITH 1) PRIMARY KEY ); \ No newline at end of file diff --git a/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.core/JdbcAggregateTemplateIntegrationTests-h2.sql b/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.core/JdbcAggregateTemplateIntegrationTests-h2.sql index 5a1f6002d9..2773ba72b5 100644 --- a/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.core/JdbcAggregateTemplateIntegrationTests-h2.sql +++ b/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.core/JdbcAggregateTemplateIntegrationTests-h2.sql @@ -321,4 +321,9 @@ CREATE TABLE WITH_LOCAL_DATE_TIME ( ID BIGINT PRIMARY KEY, TEST_TIME TIMESTAMP(9) WITHOUT TIME ZONE +); + +CREATE TABLE WITH_ID_ONLY +( + ID SERIAL PRIMARY KEY ); \ No newline at end of file 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 d0846a0897..6d04923f17 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 @@ -323,4 +323,9 @@ CREATE TABLE WITH_LOCAL_DATE_TIME ( ID BIGINT PRIMARY KEY, TEST_TIME TIMESTAMP(9) -); \ No newline at end of file +); + +CREATE TABLE WITH_ID_ONLY +( + ID BIGINT GENERATED BY DEFAULT AS IDENTITY (START WITH 1) PRIMARY KEY +) \ No newline at end of file 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 31f000fc7c..8d72641aa7 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 @@ -296,4 +296,9 @@ CREATE TABLE WITH_LOCAL_DATE_TIME ( ID BIGINT PRIMARY KEY, TEST_TIME TIMESTAMP(6) +); + +CREATE TABLE WITH_ID_ONLY +( + ID BIGINT AUTO_INCREMENT PRIMARY KEY ); \ No newline at end of file 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 ba982ac9ec..e6352bf257 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 @@ -324,4 +324,11 @@ CREATE TABLE WITH_LOCAL_DATE_TIME ( ID BIGINT PRIMARY KEY, TEST_TIME datetime2(7) +); + +DROP TABLE IF EXISTS WITH_ID_ONLY; + +CREATE TABLE WITH_ID_ONLY +( + ID BIGINT IDENTITY PRIMARY KEY ); \ 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 4df794b78a..9720136459 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 @@ -301,4 +301,9 @@ CREATE TABLE WITH_LOCAL_DATE_TIME ( ID BIGINT PRIMARY KEY, TEST_TIME TIMESTAMP(6) +); + +CREATE TABLE WITH_ID_ONLY +( + ID BIGINT AUTO_INCREMENT PRIMARY KEY ); \ No newline at end of file diff --git a/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.core/JdbcAggregateTemplateIntegrationTests-oracle.sql b/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.core/JdbcAggregateTemplateIntegrationTests-oracle.sql index b1a97093b0..cf36153817 100644 --- a/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.core/JdbcAggregateTemplateIntegrationTests-oracle.sql +++ b/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.core/JdbcAggregateTemplateIntegrationTests-oracle.sql @@ -28,6 +28,7 @@ DROP TABLE NO_ID_MAP_CHAIN4 CASCADE CONSTRAINTS PURGE; DROP TABLE VERSIONED_AGGREGATE CASCADE CONSTRAINTS PURGE; DROP TABLE WITH_READ_ONLY CASCADE CONSTRAINTS PURGE; DROP TABLE WITH_LOCAL_DATE_TIME CASCADE CONSTRAINTS PURGE; +DROP TABLE WITH_ID_ONLY CASCADE CONSTRAINTS PURGE; CREATE TABLE LEGO_SET ( @@ -332,4 +333,9 @@ CREATE TABLE WITH_LOCAL_DATE_TIME ( ID NUMBER PRIMARY KEY, TEST_TIME TIMESTAMP(9) +); + +CREATE TABLE WITH_ID_ONLY +( + ID NUMBER GENERATED by default on null as IDENTITY PRIMARY KEY ); \ 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 8a8c7f11e6..c21ec744bf 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 @@ -12,6 +12,7 @@ DROP TABLE CHAIN2; DROP TABLE CHAIN1; DROP TABLE CHAIN0; DROP TABLE WITH_READ_ONLY; +DROP TABLE WITH_ID_ONLY; CREATE TABLE LEGO_SET ( @@ -335,4 +336,9 @@ CREATE TABLE WITH_LOCAL_DATE_TIME ( ID BIGINT PRIMARY KEY, TEST_TIME TIMESTAMP(9) WITHOUT TIME ZONE +); + +CREATE TABLE WITH_ID_ONLY +( + ID SERIAL PRIMARY KEY ); \ No newline at end of file diff --git a/spring-data-relational/src/main/java/org/springframework/data/relational/core/dialect/Dialect.java b/spring-data-relational/src/main/java/org/springframework/data/relational/core/dialect/Dialect.java index d12e54e19e..c66fe9970b 100644 --- a/spring-data-relational/src/main/java/org/springframework/data/relational/core/dialect/Dialect.java +++ b/spring-data-relational/src/main/java/org/springframework/data/relational/core/dialect/Dialect.java @@ -32,6 +32,7 @@ * @author Jens Schauder * @author Myeonghyeon Lee * @author Christoph Strobl + * @author Mikhail Polivakha * @since 1.1 */ public interface Dialect { @@ -109,4 +110,13 @@ default Collection getConverters() { default Set> simpleTypes() { return Collections.emptySet(); } + + /** + * @return an appropriate {@link InsertWithDefaultValues } for that specific dialect. + * for most of the Dialects the default implementation will be valid, but, for + * example, in case of {@link SqlServerDialect} it is not + */ + default InsertWithDefaultValues getSqlInsertWithDefaultValues() { + return new InsertWithDefaultValues() {}; + } } diff --git a/spring-data-relational/src/main/java/org/springframework/data/relational/core/dialect/InsertWithDefaultValues.java b/spring-data-relational/src/main/java/org/springframework/data/relational/core/dialect/InsertWithDefaultValues.java new file mode 100644 index 0000000000..4710ab796d --- /dev/null +++ b/spring-data-relational/src/main/java/org/springframework/data/relational/core/dialect/InsertWithDefaultValues.java @@ -0,0 +1,17 @@ +package org.springframework.data.relational.core.dialect; + +import org.springframework.data.relational.core.mapping.InsertDefaultValues; + +/** + * This interface aggregates information about an Insert with default values statement. + * @author Mikhail Polivakha + */ +public interface InsertWithDefaultValues { + + /** + * @return the part of the sql statement, that follows after INSERT INTO table + */ + default String getDefaultInsertPart() { + return InsertDefaultValues.DEFAULT.getDefaultInsertPart(); + } +} \ No newline at end of file diff --git a/spring-data-relational/src/main/java/org/springframework/data/relational/core/dialect/RenderContextFactory.java b/spring-data-relational/src/main/java/org/springframework/data/relational/core/dialect/RenderContextFactory.java index e8a1dadb2d..b35014a5ae 100644 --- a/spring-data-relational/src/main/java/org/springframework/data/relational/core/dialect/RenderContextFactory.java +++ b/spring-data-relational/src/main/java/org/springframework/data/relational/core/dialect/RenderContextFactory.java @@ -16,6 +16,7 @@ package org.springframework.data.relational.core.dialect; import org.springframework.data.relational.core.sql.IdentifierProcessing; +import org.springframework.data.relational.core.sql.render.InsertRenderContext; import org.springframework.data.relational.core.sql.render.NamingStrategies; import org.springframework.data.relational.core.sql.render.RenderContext; import org.springframework.data.relational.core.sql.render.RenderNamingStrategy; @@ -26,6 +27,7 @@ * Factory for {@link RenderContext} based on {@link Dialect}. * * @author Mark Paluch + * @author Mikhail Polivakha * @since 1.1 */ public class RenderContextFactory { @@ -65,9 +67,9 @@ public void setNamingStrategy(RenderNamingStrategy namingStrategy) { */ public RenderContext createRenderContext() { - SelectRenderContext select = dialect.getSelectContext(); + SelectRenderContext selectRenderContext = dialect.getSelectContext(); - return new DialectRenderContext(namingStrategy, dialect.getIdentifierProcessing(), select); + return new DialectRenderContext(namingStrategy, dialect, selectRenderContext); } /** @@ -76,17 +78,18 @@ public RenderContext createRenderContext() { static class DialectRenderContext implements RenderContext { private final RenderNamingStrategy renderNamingStrategy; - private final IdentifierProcessing identifierProcessing; private final SelectRenderContext selectRenderContext; + private final Dialect renderingDialect; - DialectRenderContext(RenderNamingStrategy renderNamingStrategy, IdentifierProcessing identifierProcessing, SelectRenderContext selectRenderContext) { + DialectRenderContext(RenderNamingStrategy renderNamingStrategy, Dialect renderingDialect, SelectRenderContext selectRenderContext) { Assert.notNull(renderNamingStrategy, "RenderNamingStrategy must not be null"); - Assert.notNull(identifierProcessing, "IdentifierProcessing must not be null"); + Assert.notNull(renderingDialect, "renderingDialect must not be null"); + Assert.notNull(renderingDialect.getIdentifierProcessing(), "IdentifierProcessing of renderingDialect must not be null"); Assert.notNull(selectRenderContext, "SelectRenderContext must not be null"); this.renderNamingStrategy = renderNamingStrategy; - this.identifierProcessing = identifierProcessing; + this.renderingDialect = renderingDialect; this.selectRenderContext = selectRenderContext; } @@ -105,7 +108,7 @@ public RenderNamingStrategy getNamingStrategy() { */ @Override public IdentifierProcessing getIdentifierProcessing() { - return identifierProcessing; + return renderingDialect.getIdentifierProcessing(); } /* @@ -113,8 +116,18 @@ public IdentifierProcessing getIdentifierProcessing() { * @see org.springframework.data.relational.core.sql.render.RenderContext#getSelect() */ @Override - public SelectRenderContext getSelect() { + public SelectRenderContext getSelectRenderContext() { return selectRenderContext; } + + @Override + public InsertRenderContext getInsertRenderContext() { + return new InsertRenderContext() { + @Override + public String getInsertDefaultValuesPartSQL() { + return renderingDialect.getSqlInsertWithDefaultValues().getDefaultInsertPart(); + } + }; + } } -} +} \ No newline at end of file diff --git a/spring-data-relational/src/main/java/org/springframework/data/relational/core/dialect/SqlServerDialect.java b/spring-data-relational/src/main/java/org/springframework/data/relational/core/dialect/SqlServerDialect.java index c3b47653da..cf3bcbbc8f 100644 --- a/spring-data-relational/src/main/java/org/springframework/data/relational/core/dialect/SqlServerDialect.java +++ b/spring-data-relational/src/main/java/org/springframework/data/relational/core/dialect/SqlServerDialect.java @@ -15,6 +15,7 @@ */ package org.springframework.data.relational.core.dialect; +import org.springframework.data.relational.core.mapping.InsertDefaultValues; import org.springframework.data.relational.core.sql.IdentifierProcessing; import org.springframework.data.relational.core.sql.LockOptions; import org.springframework.data.relational.core.sql.render.SelectRenderContext; @@ -26,6 +27,7 @@ * @author Mark Paluch * @author Myeonghyeon Lee * @author Jens Schauder + * @author Mikhail Polivakha * @since 1.1 */ public class SqlServerDialect extends AbstractDialect { @@ -150,4 +152,14 @@ public SelectRenderContext getSelectContext() { public IdentifierProcessing getIdentifierProcessing() { return IdentifierProcessing.NONE; } + + @Override + public InsertWithDefaultValues getSqlInsertWithDefaultValues() { + return new InsertWithDefaultValues() { + @Override + public String getDefaultInsertPart() { + return InsertDefaultValues.MS_SQL_SERVER.getDefaultInsertPart(); + } + }; + } } diff --git a/spring-data-relational/src/main/java/org/springframework/data/relational/core/mapping/InsertDefaultValues.java b/spring-data-relational/src/main/java/org/springframework/data/relational/core/mapping/InsertDefaultValues.java new file mode 100644 index 0000000000..8a9299edff --- /dev/null +++ b/spring-data-relational/src/main/java/org/springframework/data/relational/core/mapping/InsertDefaultValues.java @@ -0,0 +1,28 @@ +package org.springframework.data.relational.core.mapping; + +import org.springframework.data.relational.core.dialect.Dialect; +import org.springframework.data.relational.core.dialect.InsertWithDefaultValues; + +/** + * In the scope of Insert with default values SQL statement, for example + * INSERT INTO SCHEMA.TABLE VALUES (DEFAULT) + * this enum represents the default values part in different {@link Dialect}s + * + * @author Mikhail Polivakha + * @see InsertWithDefaultValues + */ +public enum InsertDefaultValues { + + DEFAULT(" VALUES (DEFAULT) "), + MS_SQL_SERVER(" DEFAULT VALUES "); + + private final String defaultInsertPart; + + InsertDefaultValues(String defaultInsertPart) { + this.defaultInsertPart = defaultInsertPart; + } + + public String getDefaultInsertPart() { + return defaultInsertPart; + } +} \ No newline at end of file diff --git a/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/DefaultInsert.java b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/DefaultInsert.java index 9e8c73ff8c..1e89988bcc 100644 --- a/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/DefaultInsert.java +++ b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/DefaultInsert.java @@ -15,13 +15,13 @@ */ package org.springframework.data.relational.core.sql; -import java.util.ArrayList; -import java.util.List; - import org.springframework.lang.Nullable; import org.springframework.util.Assert; import org.springframework.util.StringUtils; +import java.util.ArrayList; +import java.util.List; + /** * Default {@link Insert} implementation. * 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 702cc0767b..d008ba8539 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 @@ -15,14 +15,14 @@ */ package org.springframework.data.relational.core.sql; +import org.springframework.lang.Nullable; +import org.springframework.util.Assert; + import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.List; -import org.springframework.lang.Nullable; -import org.springframework.util.Assert; - /** * Default {@link InsertBuilder} implementation. * @@ -33,8 +33,8 @@ class DefaultInsertBuilder implements InsertBuilder, InsertBuilder.InsertIntoColumnsAndValuesWithBuild, InsertBuilder.InsertValuesWithBuild { private @Nullable Table into; - private List columns = new ArrayList<>(); - private List values = new ArrayList<>(); + private final List columns = new ArrayList<>(); + private final List values = new ArrayList<>(); /* * (non-Javadoc) diff --git a/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/render/InsertRenderContext.java b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/render/InsertRenderContext.java new file mode 100644 index 0000000000..6824d8c76d --- /dev/null +++ b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/render/InsertRenderContext.java @@ -0,0 +1,18 @@ +package org.springframework.data.relational.core.sql.render; + +import org.springframework.data.relational.core.mapping.InsertDefaultValues; +import org.springframework.data.relational.core.sql.Insert; + +/** + * This interface encapsulates the details about how to + * process {@link Insert} SQL statement + * + * @see RenderContext + * @author Mikhail Polivakha + */ +public interface InsertRenderContext { + + default String getInsertDefaultValuesPartSQL() { + return InsertDefaultValues.DEFAULT.getDefaultInsertPart(); + } +} \ No newline at end of file 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 e775b5a37c..75cf44074f 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 @@ -15,51 +15,42 @@ */ package org.springframework.data.relational.core.sql.render; +import org.jetbrains.annotations.NotNull; import org.springframework.data.relational.core.sql.Column; import org.springframework.data.relational.core.sql.Insert; import org.springframework.data.relational.core.sql.Into; import org.springframework.data.relational.core.sql.Values; import org.springframework.data.relational.core.sql.Visitable; +import org.springframework.util.Assert; /** * {@link PartRenderer} for {@link Insert} statements. * * @author Mark Paluch * @author Jens Schauder + * @author Mikhail Polivakha * @since 1.1 */ class InsertStatementVisitor extends DelegatingVisitor implements PartRenderer { - private StringBuilder builder = new StringBuilder(); - private StringBuilder into = new StringBuilder(); - private StringBuilder columns = new StringBuilder(); - private StringBuilder values = new StringBuilder(); + private final StringBuilder builder = new StringBuilder(); + private final StringBuilder into = new StringBuilder(); + private final StringBuilder columns = new StringBuilder(); + private final StringBuilder values = new StringBuilder(); - private IntoClauseVisitor intoClauseVisitor; - private ColumnVisitor columnVisitor; - private ValuesVisitor valuesVisitor; + private final IntoClauseVisitor intoClauseVisitor; + private final ColumnVisitor columnVisitor; + private final ValuesVisitor valuesVisitor; + private final RenderContext renderContext; - InsertStatementVisitor(RenderContext context) { + InsertStatementVisitor(RenderContext renderContext) { - this.intoClauseVisitor = new IntoClauseVisitor(context, it -> { + Assert.notNull(renderContext, "renderContext must not be null!"); - if (into.length() != 0) { - into.append(", "); - } - - into.append(it); - }); - - this.columnVisitor = new ColumnVisitor(context, false, it -> { - - if (columns.length() != 0) { - columns.append(", "); - } - - columns.append(it); - }); - - this.valuesVisitor = new ValuesVisitor(context, values::append); + this.renderContext = renderContext; + this.intoClauseVisitor = createIntoClauseVisitor(renderContext); + this.columnVisitor = createColumnVisitor(renderContext); + this.valuesVisitor = new ValuesVisitor(renderContext, values::append); } /* @@ -97,11 +88,9 @@ public Delegation doLeave(Visitable segment) { builder.append(" INTO ").append(into); - if (columns.length() != 0) { - builder.append(" (").append(columns).append(")"); - } + addInsertColumnsIfPresent(); - builder.append(" VALUES (").append(values).append(")"); + addInsertValuesIfPresentElseDefault(); return Delegation.leave(); } @@ -109,6 +98,24 @@ public Delegation doLeave(Visitable segment) { return Delegation.retain(); } + private void addInsertValuesIfPresentElseDefault() { + if (values.length() != 0) { + builder.append(" VALUES (").append(values).append(")"); + } else { + addInsertWithDefaultValuesToBuilder(); + } + } + + private void addInsertColumnsIfPresent() { + if (columns.length() != 0) { + builder.append(" (").append(columns).append(")"); + } + } + + private void addInsertWithDefaultValuesToBuilder() { + builder.append(renderContext.getInsertRenderContext().getInsertDefaultValuesPartSQL()); + } + /* * (non-Javadoc) * @see org.springframework.data.relational.core.sql.render.PartRenderer#getRenderedPart() @@ -117,4 +124,28 @@ public Delegation doLeave(Visitable segment) { public CharSequence getRenderedPart() { return builder; } + + @NotNull + private ColumnVisitor createColumnVisitor(RenderContext context) { + return new ColumnVisitor(context, false, it -> { + + if (columns.length() != 0) { + columns.append(", "); + } + + columns.append(it); + }); + } + + @NotNull + private IntoClauseVisitor createIntoClauseVisitor(RenderContext context) { + return new IntoClauseVisitor(context, it -> { + + if (into.length() != 0) { + into.append(", "); + } + + into.append(it); + }); + } } diff --git a/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/render/RenderContext.java b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/render/RenderContext.java index c11b68d499..2d40ef5188 100644 --- a/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/render/RenderContext.java +++ b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/render/RenderContext.java @@ -21,6 +21,7 @@ * Render context providing {@link RenderNamingStrategy} and other resources that are required during rendering. * * @author Mark Paluch + * @author Mikhail Polivakha * @since 1.1 */ public interface RenderContext { @@ -43,5 +44,10 @@ public interface RenderContext { /** * @return the {@link SelectRenderContext}. */ - SelectRenderContext getSelect(); + SelectRenderContext getSelectRenderContext(); + + /** + * @return the {@link InsertRenderContext} + */ + InsertRenderContext getInsertRenderContext(); } diff --git a/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/render/SelectStatementVisitor.java b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/render/SelectStatementVisitor.java index 5f743c5a9a..3b545d3c3b 100644 --- a/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/render/SelectStatementVisitor.java +++ b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/render/SelectStatementVisitor.java @@ -50,7 +50,7 @@ class SelectStatementVisitor extends DelegatingVisitor implements PartRenderer { SelectStatementVisitor(RenderContext context) { this.context = context; - this.selectRenderContext = context.getSelect(); + this.selectRenderContext = context.getSelectRenderContext(); this.selectListVisitor = new SelectListVisitor(context, selectList::append); this.orderByClauseVisitor = new OrderByClauseVisitor(context); this.fromClauseVisitor = new FromClauseVisitor(context, it -> { diff --git a/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/render/SimpleRenderContext.java b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/render/SimpleRenderContext.java index 9f4d942f61..3886d5c4ae 100644 --- a/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/render/SimpleRenderContext.java +++ b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/render/SimpleRenderContext.java @@ -37,10 +37,15 @@ public IdentifierProcessing getIdentifierProcessing() { } @Override - public SelectRenderContext getSelect() { + public SelectRenderContext getSelectRenderContext() { return DefaultSelectRenderContext.INSTANCE; } + @Override + public InsertRenderContext getInsertRenderContext() { + return new InsertRenderContext() {}; + } + public RenderNamingStrategy getNamingStrategy() { return this.namingStrategy; } diff --git a/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/render/SqlRenderer.java b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/render/SqlRenderer.java index 1a0ffa7f24..9c93fe6852 100644 --- a/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/render/SqlRenderer.java +++ b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/render/SqlRenderer.java @@ -69,16 +69,6 @@ public static String toString(Select select) { return create().render(select); } - /** - * Renders a {@link Insert} statement into its SQL representation. - * - * @param insert must not be {@literal null}. - * @return the rendered statement. - */ - public static String toString(Insert insert) { - return create().render(insert); - } - /** * Renders a {@link Update} statement into its SQL representation. * @@ -120,10 +110,8 @@ public String render(Select select) { */ @Override public String render(Insert insert) { - InsertStatementVisitor visitor = new InsertStatementVisitor(context); insert.visit(visitor); - return visitor.getRenderedPart().toString(); } 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 976c990635..0feff5742b 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 @@ -15,18 +15,20 @@ */ package org.springframework.data.relational.core.sql.render; -import static org.assertj.core.api.Assertions.*; - import org.junit.jupiter.api.Test; +import org.springframework.data.relational.core.dialect.InsertWithDefaultValues; import org.springframework.data.relational.core.sql.Insert; import org.springframework.data.relational.core.sql.SQL; import org.springframework.data.relational.core.sql.Table; +import static org.assertj.core.api.Assertions.assertThat; + /** * Unit tests for {@link SqlRenderer}. * * @author Mark Paluch * @author Jens Schauder + * @author Mikhail Polivakha */ public class InsertRendererUnitTests { @@ -37,7 +39,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.create().render(insert)).isEqualTo("INSERT INTO bar VALUES (?)"); } @Test // DATAJDBC-335 @@ -47,7 +49,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.create().render(insert)).isEqualTo("INSERT INTO bar (foo) VALUES (?)"); } @Test // DATAJDBC-335 @@ -58,17 +60,16 @@ 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.create().render(insert)).isEqualTo("INSERT INTO bar (foo, baz) VALUES (?, 'foo')"); } @Test // DATAJDBC-340 public void shouldRenderInsertWithZeroColumns() { - Table bar = SQL.table("bar"); + Table bar = Table.create("bar"); Insert insert = Insert.builder().into(bar).build(); - assertThat(SqlRenderer.toString(insert)).isEqualTo("INSERT INTO bar VALUES ()"); + assertThat(SqlRenderer.create().render(insert)).contains(new InsertWithDefaultValues(){}.getDefaultInsertPart()); } - }