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 06bfaf0a..5ecca365 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 @@ import java.util.stream.Collectors; * @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 52d848a8..c7c339c7 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 @@ import org.springframework.transaction.annotation.Transactional; * @author Tyler Van Gorder * @author Clemens Hahn * @author Milan Milanov + * @author Mikhail Polivakha */ @ContextConfiguration @Transactional @@ -887,6 +888,12 @@ public class JdbcAggregateTemplateIntegrationTests { 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 @@ public class JdbcAggregateTemplateIntegrationTests { 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/SqlGeneratorUnitTests.java b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/convert/SqlGeneratorUnitTests.java index 5e9f6961..5b0fa054 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.jdbc.core.mapping.PersistentPropertyPathTestUtil 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 @@ import org.springframework.data.relational.core.sql.Table; * @author Tom Hombergs * @author Milan Milanov * @author Myeonghyeon Lee + * @author Mikhail Polivakha */ class SqlGeneratorUnitTests { @@ -391,13 +394,22 @@ class SqlGeneratorUnitTests { } @Test // DATAJDBC-264 - void getInsertForEmptyColumnList() { + 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(insert).endsWith("()"); + assertThat(insertSqlStatement).endsWith(" VALUES (DEFAULT) "); + } + + @Test //DATAJDBC-557 + void gerInsertForEmptyColumnListMsSqlServer() { + SqlGenerator sqlGenerator = createSqlGenerator(IdOnlyEntity.class, SqlServerDialect.INSTANCE); + + String insertSqlStatement = sqlGenerator.getInsert(emptySet()); + + assertThat(insertSqlStatement).endsWith(" DEFAULT VALUES "); } @Test // DATAJDBC-334 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 7b2b8d63..1d58605c 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 5a1f6002..2773ba72 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 d0846a08..6d04923f 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 31f000fc..8d72641a 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 ba982ac9..e6352bf2 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 4df794b7..97201364 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 b1a97093..cf361538 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 8a8c7f11..c21ec744 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 d12e54e1..c66fe997 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 @@ import org.springframework.data.relational.core.sql.render.SelectRenderContext; * @author Jens Schauder * @author Myeonghyeon Lee * @author Christoph Strobl + * @author Mikhail Polivakha * @since 1.1 */ public interface Dialect { @@ -109,4 +110,13 @@ public interface Dialect { 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 00000000..4710ab79 --- /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 e8a1dadb..b35014a5 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 @@ import org.springframework.util.Assert; * 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 class RenderContextFactory { */ 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 class RenderContextFactory { 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 class RenderContextFactory { */ @Override public IdentifierProcessing getIdentifierProcessing() { - return identifierProcessing; + return renderingDialect.getIdentifierProcessing(); } /* @@ -113,8 +116,18 @@ public class RenderContextFactory { * @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 c3b47653..cf3bcbbc 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 @@ import org.springframework.data.util.Lazy; * @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 class SqlServerDialect extends AbstractDialect { 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 00000000..8a9299ed --- /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/render/InsertRenderContext.java b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/render/InsertRenderContext.java new file mode 100644 index 00000000..6824d8c7 --- /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 e775b5a3..75cf4407 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 @@ class InsertStatementVisitor extends DelegatingVisitor implements PartRenderer { 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 @@ class InsertStatementVisitor extends DelegatingVisitor implements PartRenderer { 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 @@ class InsertStatementVisitor extends DelegatingVisitor implements PartRenderer { 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 c11b68d4..2d40ef51 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 @@ import org.springframework.data.relational.core.sql.IdentifierProcessing; * 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 5f743c5a..3b545d3c 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 9f4d942f..3886d5c4 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 @@ final class SimpleRenderContext implements RenderContext { } @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 1a0ffa7f..9c93fe68 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 class SqlRenderer implements Renderer { 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 class SqlRenderer implements Renderer { */ @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 976c9906..c0fee3d9 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 @@ -68,7 +68,7 @@ public class InsertRendererUnitTests { Insert insert = Insert.builder().into(bar).build(); - assertThat(SqlRenderer.toString(insert)).isEqualTo("INSERT INTO bar VALUES ()"); + assertThat(SqlRenderer.toString(insert)).isEqualTo("INSERT INTO bar VALUES (DEFAULT)"); } }