From 4e9b6e11da0de6f1248d6878ec5fbe402905f44a Mon Sep 17 00:00:00 2001 From: Mark Paluch Date: Thu, 28 Feb 2019 13:57:09 +0100 Subject: [PATCH 1/4] DATAJDBC-335 - Prepare issue 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 fe60487ec8..43db4e7280 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ org.springframework.data spring-data-relational-parent - 1.1.0.BUILD-SNAPSHOT + 2.2.0.DATAJDBC-335-SNAPSHOT pom Spring Data Relational Parent diff --git a/spring-data-jdbc-distribution/pom.xml b/spring-data-jdbc-distribution/pom.xml index 1753776a73..6817da4185 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 + 2.2.0.DATAJDBC-335-SNAPSHOT ../pom.xml diff --git a/spring-data-jdbc/pom.xml b/spring-data-jdbc/pom.xml index 9458230173..f9155913f8 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 + 2.2.0.DATAJDBC-335-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 + 2.2.0.DATAJDBC-335-SNAPSHOT diff --git a/spring-data-relational/pom.xml b/spring-data-relational/pom.xml index 11e7db737c..6c93650ac5 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 + 2.2.0.DATAJDBC-335-SNAPSHOT Spring Data Relational Spring Data Relational support @@ -13,7 +13,7 @@ org.springframework.data spring-data-relational-parent - 1.1.0.BUILD-SNAPSHOT + 2.2.0.DATAJDBC-335-SNAPSHOT From e3b1b7717f463d67c71200adfe55ac76f2346fdb Mon Sep 17 00:00:00 2001 From: Mark Paluch Date: Thu, 28 Feb 2019 14:31:21 +0100 Subject: [PATCH 2/4] DATAJDBC-335 - Add DSL and renderer for Insert, Update, and Delete. We now provide Statement builders for Insert, Update, and Delete statements. Table myTable = SQL.table("mytable"); Insert insert = StatementBuilder.insert().into(myTable).values(SQL.bindMarker()).build(); Update update = StatementBuilder.update(table).set(myTable.column("foo").set(SQL.bindMarker())).build(); Delete delete = StatementBuilder.delete().from(table).where(myTable.column("foo").isEqualTo(SQL.literal("bar"))).build(); --- .../core/sql/AbstractImportValidator.java | 66 ++++++ .../data/relational/core/sql/AssignValue.java | 78 +++++++ .../data/relational/core/sql/Assignment.java | 23 ++ .../data/relational/core/sql/Assignments.java | 42 ++++ .../data/relational/core/sql/Column.java | 14 ++ .../relational/core/sql/DefaultDelete.java | 75 +++++++ .../core/sql/DefaultDeleteBuilder.java | 94 ++++++++ .../relational/core/sql/DefaultInsert.java | 80 +++++++ .../core/sql/DefaultInsertBuilder.java | 139 ++++++++++++ .../relational/core/sql/DefaultSelect.java | 2 +- .../relational/core/sql/DefaultUpdate.java | 84 ++++++++ .../core/sql/DefaultUpdateBuilder.java | 127 +++++++++++ .../data/relational/core/sql/Delete.java | 42 ++++ .../relational/core/sql/DeleteBuilder.java | 90 ++++++++ .../relational/core/sql/DeleteValidator.java | 43 ++++ .../data/relational/core/sql/Insert.java | 43 ++++ .../relational/core/sql/InsertBuilder.java | 201 ++++++++++++++++++ .../data/relational/core/sql/Into.java | 52 +++++ .../data/relational/core/sql/Literal.java | 55 +++++ .../relational/core/sql/NumericLiteral.java | 41 ++++ .../data/relational/core/sql/SQL.java | 40 ++++ .../relational/core/sql/SelectBuilder.java | 1 + .../relational/core/sql/SelectValidator.java | 24 +-- .../relational/core/sql/StatementBuilder.java | 75 ++++++- .../relational/core/sql/StringLiteral.java | 50 +++++ .../data/relational/core/sql/Update.java | 43 ++++ .../relational/core/sql/UpdateBuilder.java | 117 ++++++++++ .../data/relational/core/sql/Values.java | 52 +++++ .../core/sql/render/AssignmentVisitor.java | 95 +++++++++ .../core/sql/render/ColumnVisitor.java | 73 +++++++ .../sql/render/DeleteStatementVisitor.java | 103 +++++++++ .../core/sql/render/ExpressionVisitor.java | 3 + .../sql/render/InsertStatementVisitor.java | 123 +++++++++++ .../core/sql/render/IntoClauseVisitor.java | 68 ++++++ .../relational/core/sql/render/Renderer.java | 62 ++++++ .../core/sql/render/SqlRenderer.java | 106 +++++++-- .../sql/render/UpdateStatementVisitor.java | 123 +++++++++++ .../core/sql/render/ValuesVisitor.java | 90 ++++++++ .../relational/core/sql/CapturingVisitor.java | 39 ++++ .../core/sql/DeleteBuilderUnitTests.java | 48 +++++ .../core/sql/DeleteValidatorUnitTests.java | 43 ++++ .../core/sql/InsertBuilderUnitTests.java | 46 ++++ .../core/sql/SelectBuilderUnitTests.java | 26 +-- .../core/sql/UpdateBuilderUnitTests.java | 62 ++++++ .../render/ConditionRendererUnitTests.java | 24 +-- .../sql/render/DeleteRendererUnitTests.java | 63 ++++++ .../sql/render/InsertRendererUnitTests.java | 63 ++++++ ...ests.java => SelectRendererUnitTests.java} | 44 ++-- .../sql/render/UpdateRendererUnitTests.java | 67 ++++++ 49 files changed, 3065 insertions(+), 99 deletions(-) create mode 100644 spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/AbstractImportValidator.java create mode 100644 spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/AssignValue.java create mode 100644 spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/Assignment.java create mode 100644 spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/Assignments.java create mode 100644 spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/DefaultDelete.java create mode 100644 spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/DefaultDeleteBuilder.java create mode 100644 spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/DefaultInsert.java create mode 100644 spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/DefaultInsertBuilder.java create mode 100644 spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/DefaultUpdate.java create mode 100644 spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/DefaultUpdateBuilder.java create mode 100644 spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/Delete.java create mode 100644 spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/DeleteBuilder.java create mode 100644 spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/DeleteValidator.java create mode 100644 spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/Insert.java create mode 100644 spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/InsertBuilder.java create mode 100644 spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/Into.java create mode 100644 spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/Literal.java create mode 100644 spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/NumericLiteral.java create mode 100644 spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/StringLiteral.java create mode 100644 spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/Update.java create mode 100644 spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/UpdateBuilder.java create mode 100644 spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/Values.java create mode 100644 spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/render/AssignmentVisitor.java create mode 100644 spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/render/ColumnVisitor.java create mode 100644 spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/render/DeleteStatementVisitor.java create mode 100644 spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/render/InsertStatementVisitor.java create mode 100644 spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/render/IntoClauseVisitor.java create mode 100644 spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/render/Renderer.java create mode 100644 spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/render/UpdateStatementVisitor.java create mode 100644 spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/render/ValuesVisitor.java create mode 100644 spring-data-relational/src/test/java/org/springframework/data/relational/core/sql/CapturingVisitor.java create mode 100644 spring-data-relational/src/test/java/org/springframework/data/relational/core/sql/DeleteBuilderUnitTests.java create mode 100644 spring-data-relational/src/test/java/org/springframework/data/relational/core/sql/DeleteValidatorUnitTests.java create mode 100644 spring-data-relational/src/test/java/org/springframework/data/relational/core/sql/InsertBuilderUnitTests.java create mode 100644 spring-data-relational/src/test/java/org/springframework/data/relational/core/sql/UpdateBuilderUnitTests.java create mode 100644 spring-data-relational/src/test/java/org/springframework/data/relational/core/sql/render/DeleteRendererUnitTests.java create mode 100644 spring-data-relational/src/test/java/org/springframework/data/relational/core/sql/render/InsertRendererUnitTests.java rename spring-data-relational/src/test/java/org/springframework/data/relational/core/sql/render/{SqlRendererUnitTests.java => SelectRendererUnitTests.java} (80%) create mode 100644 spring-data-relational/src/test/java/org/springframework/data/relational/core/sql/render/UpdateRendererUnitTests.java diff --git a/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/AbstractImportValidator.java b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/AbstractImportValidator.java new file mode 100644 index 0000000000..1a56627a59 --- /dev/null +++ b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/AbstractImportValidator.java @@ -0,0 +1,66 @@ +/* + * 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.relational.core.sql; + +import java.util.HashSet; +import java.util.Set; + +/** + * Validator for statements to import columns. + * + * @author Mark Paluch + * @since 1.1 + */ +abstract class AbstractImportValidator implements Visitor { + + Set requiredByWhere = new HashSet<>(); + Set
from = new HashSet<>(); + Visitable parent; + + /* + * (non-Javadoc) + * @see org.springframework.data.relational.core.sql.Visitor#enter(org.springframework.data.relational.core.sql.Visitable) + */ + @Override + public void enter(Visitable segment) { + + if (segment instanceof Table && parent instanceof From) { + from.add((Table) segment); + } + + if (segment instanceof Where) { + + segment.visit(item -> { + + if (item instanceof Table) { + requiredByWhere.add((Table) item); + } + }); + } + + if (segment instanceof Join || segment instanceof OrderByField || segment instanceof From + || segment instanceof Select || segment instanceof Where || segment instanceof SimpleFunction) { + parent = segment; + } + } + + /* + * (non-Javadoc) + * @see org.springframework.data.relational.core.sql.Visitor#leave(org.springframework.data.relational.core.sql.Visitable) + */ + @Override + public void leave(Visitable segment) {} +} diff --git a/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/AssignValue.java b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/AssignValue.java new file mode 100644 index 0000000000..8ded7e5309 --- /dev/null +++ b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/AssignValue.java @@ -0,0 +1,78 @@ +/* + * 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.relational.core.sql; + +import org.springframework.util.Assert; + +/** + * Assign a {@link Expression} to a {@link Column}. + *

+ * Results in a rendered assignment: {@code = } (e.g. {@code col = 'foo'}. + * + * @author Mark Paluch + * @since 1.1 + */ +public class AssignValue extends AbstractSegment implements Assignment { + + private final Column column; + private final Expression value; + + private AssignValue(Column column, Expression value) { + super(column, value); + this.column = column; + this.value = value; + } + + /** + * Creates a {@link AssignValue value} assignment to a {@link Column} given an {@link Expression}. + * + * @param target target column, must not be {@literal null}. + * @param value assignment value, must not be {@literal null}. + * @return the {@link AssignValue}. + */ + public static AssignValue create(Column target, Expression value) { + + Assert.notNull(target, "Target column must not be null!"); + Assert.notNull(value, "Value must not be null!"); + + return new AssignValue(target, value); + } + + /** + * @return the target {@link Column}. + */ + public Column getColumn() { + return column; + } + + /** + * @return the value to assign. + */ + public Expression getValue() { + return value; + } + + /* + * (non-Javadoc) + * @see java.lang.Object#toString() + */ + @Override + public String toString() { + + StringBuilder builder = new StringBuilder(32); + return builder.append(this.column).append(" = ").append(this.value).toString(); + } +} diff --git a/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/Assignment.java b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/Assignment.java new file mode 100644 index 0000000000..2a932bcf32 --- /dev/null +++ b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/Assignment.java @@ -0,0 +1,23 @@ +/* + * 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.relational.core.sql; + +/** + * Update assignment to a {@link Column}. + * + * @author Mark Paluch + */ +public interface Assignment extends Segment {} diff --git a/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/Assignments.java b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/Assignments.java new file mode 100644 index 0000000000..968f25af61 --- /dev/null +++ b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/Assignments.java @@ -0,0 +1,42 @@ +/* + * 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.relational.core.sql; + +/** + * Factory for common {@link Assignment}s. + * + * @author Mark Paluch + * @since 1.1 + * @see SQL + * @see Expressions + * @see Functions + */ +public abstract class Assignments { + + /** + * Creates a {@link AssignValue value} assignment to a {@link Column} given an {@link Expression}. + * + * @param target target column, must not be {@literal null}. + * @param value assignment value, must not be {@literal null}. + * @return the {@link AssignValue}. + */ + public static AssignValue value(Column target, Expression value) { + return AssignValue.create(target, value); + } + + // Utility constructor. + private Assignments() {} +} 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 356a8e83b4..72f354a16e 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 @@ -200,6 +200,20 @@ public Condition isNotNull() { return isNull().not(); } + // ------------------------------------------------------------------------- + // Methods for Assignment creation. + // ------------------------------------------------------------------------- + + /** + * Creates a value {@link AssignValue assignment}. + * + * @param value the value to assign. + * @return the {@link AssignValue} assignment. + */ + public AssignValue set(Expression value) { + return Assignments.value(this, value); + } + /* * (non-Javadoc) * @see org.springframework.data.relational.core.sql.Named#getName() diff --git a/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/DefaultDelete.java b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/DefaultDelete.java new file mode 100644 index 0000000000..6d84c45402 --- /dev/null +++ b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/DefaultDelete.java @@ -0,0 +1,75 @@ +/* + * 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.relational.core.sql; + +import org.springframework.lang.Nullable; +import org.springframework.util.Assert; + +/** + * Default {@link Delete} implementation. + * + * @author Mark Paluch + * @since 1.1 + */ +class DefaultDelete implements Delete { + + private final From from; + private final @Nullable Where where; + + DefaultDelete(Table table, @Nullable Condition where) { + + this.from = new From(table); + this.where = where != null ? new Where(where) : null; + } + + /* + * (non-Javadoc) + * @see org.springframework.data.relational.core.sql.Visitable#visit(org.springframework.data.relational.core.sql.Visitor) + */ + @Override + public void visit(Visitor visitor) { + + Assert.notNull(visitor, "Visitor must not be null!"); + + visitor.enter(this); + + from.visit(visitor); + + if (where != null) { + where.visit(visitor); + } + + visitor.leave(this); + } + + /* + * (non-Javadoc) + * @see java.lang.Object#toString() + */ + @Override + public String toString() { + + StringBuilder builder = new StringBuilder(); + + builder.append("DELETE ").append(this.from); + + if (this.where != null) { + builder.append(' ').append(this.where); + } + + return builder.toString(); + } +} diff --git a/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/DefaultDeleteBuilder.java b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/DefaultDeleteBuilder.java new file mode 100644 index 0000000000..8d350ccc07 --- /dev/null +++ b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/DefaultDeleteBuilder.java @@ -0,0 +1,94 @@ +/* + * 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.relational.core.sql; + +import org.springframework.lang.Nullable; +import org.springframework.util.Assert; + +/** + * Default {@link SelectBuilder} implementation. + * + * @author Mark Paluch + * @since 1.1 + */ +class DefaultDeleteBuilder implements DeleteBuilder, DeleteBuilder.DeleteWhereAndOr, DeleteBuilder.DeleteWhere { + + private Table from; + private @Nullable Condition where; + + /* + * (non-Javadoc) + * @see org.springframework.data.relational.core.sql.DeleteBuilder#from(org.springframework.data.relational.core.sql.Table) + */ + @Override + public DeleteWhere from(Table table) { + + Assert.notNull(table, "Table must not be null!"); + + this.from = table; + return this; + } + + /* + * (non-Javadoc) + * @see org.springframework.data.relational.core.sql.DeleteBuilder.DeleteWhere#where(org.springframework.data.relational.core.sql.Condition) + */ + @Override + public DeleteWhereAndOr where(Condition condition) { + + Assert.notNull(condition, "Where Condition must not be null!"); + this.where = condition; + return this; + } + + /* + * (non-Javadoc) + * @see org.springframework.data.relational.core.sql.DeleteBuilder.DeleteWhereAndOr#and(org.springframework.data.relational.core.sql.Condition) + */ + @Override + public DeleteWhereAndOr and(Condition condition) { + + Assert.notNull(condition, "Condition must not be null!"); + this.where = this.where.and(condition); + return this; + } + + /* + * (non-Javadoc) + * @see org.springframework.data.relational.core.sql.DeleteBuilder.DeleteWhereAndOr#or(org.springframework.data.relational.core.sql.Condition) + */ + @Override + public DeleteWhereAndOr or(Condition condition) { + + Assert.notNull(condition, "Condition must not be null!"); + this.where = this.where.or(condition); + return this; + } + + /* + * (non-Javadoc) + * @see org.springframework.data.relational.core.sql.DeleteBuilder.BuildDelete#build() + */ + @Override + public Delete build() { + + DefaultDelete delete = new DefaultDelete(this.from, this.where); + + DeleteValidator.validate(delete); + + return delete; + } +} 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 new file mode 100644 index 0000000000..0f0715e7b1 --- /dev/null +++ b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/DefaultInsert.java @@ -0,0 +1,80 @@ +/* + * 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.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; + +/** + * Default {@link Insert} implementation. + * + * @author Mark Paluch + * @since 1.1 + */ +class DefaultInsert implements Insert { + + private final Into into; + private final List columns; + private final Values values; + + DefaultInsert(@Nullable Table into, List columns, List values) { + this.into = new Into(into); + this.columns = new ArrayList<>(columns); + this.values = new Values(new ArrayList<>(values)); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.relational.core.sql.Visitable#visit(org.springframework.data.relational.core.sql.Visitor) + */ + @Override + public void visit(Visitor visitor) { + + Assert.notNull(visitor, "Visitor must not be null!"); + + visitor.enter(this); + + into.visit(visitor); + columns.forEach(it -> it.visit(visitor)); + values.visit(visitor); + + visitor.leave(this); + } + + /* + * (non-Javadoc) + * @see java.lang.Object#toString() + */ + @Override + public String toString() { + + StringBuilder builder = new StringBuilder(); + + builder.append("INSERT ").append(this.into); + + if (!this.columns.isEmpty()) { + builder.append(" (").append(StringUtils.collectionToDelimitedString(this.columns, ", ")).append(")"); + } + + builder.append(" ").append(this.values); + + return builder.toString(); + } +} 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 new file mode 100644 index 0000000000..af7f96dc2e --- /dev/null +++ b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/DefaultInsertBuilder.java @@ -0,0 +1,139 @@ +/* + * 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.relational.core.sql; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.List; + +import org.springframework.util.Assert; + +/** + * Default {@link InsertBuilder} implementation. + * + * @author Mark Paluch + * @since 1.1 + */ +class DefaultInsertBuilder + implements InsertBuilder, InsertBuilder.InsertIntoColumnsAndValuesWithBuild, InsertBuilder.InsertValuesWithBuild { + + private Table into; + private List columns = new ArrayList<>(); + private List values = new ArrayList<>(); + + /* + * (non-Javadoc) + * @see org.springframework.data.relational.core.sql.InsertBuilder#into(org.springframework.data.relational.core.sql.Table) + */ + @Override + public InsertIntoColumnsAndValues into(Table table) { + + Assert.notNull(table, "Insert Into Table must not be null!"); + + this.into = table; + return this; + } + + /* + * (non-Javadoc) + * @see org.springframework.data.relational.core.sql.InsertBuilder.InsertIntoColumnsAndValues#column(org.springframework.data.relational.core.sql.Column) + */ + @Override + public InsertIntoColumnsAndValuesWithBuild column(Column column) { + + Assert.notNull(column, "Column must not be null!"); + + this.columns.add(column); + + return this; + } + + /* + * (non-Javadoc) + * @see org.springframework.data.relational.core.sql.InsertBuilder.InsertIntoColumnsAndValues#columns(org.springframework.data.relational.core.sql.Column[]) + */ + @Override + public InsertIntoColumnsAndValuesWithBuild columns(Column... columns) { + + Assert.notNull(columns, "Columns must not be null!"); + + return columns(Arrays.asList(columns)); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.relational.core.sql.InsertBuilder.InsertIntoColumnsAndValues#columns(java.util.Collection) + */ + @Override + public InsertIntoColumnsAndValuesWithBuild columns(Collection columns) { + + Assert.notNull(columns, "Columns must not be null!"); + + this.columns.addAll(columns); + + return this; + } + + /* + * (non-Javadoc) + * @see org.springframework.data.relational.core.sql.InsertBuilder.InsertIntoColumnsAndValuesWithBuild#value(org.springframework.data.relational.core.sql.Expression) + */ + @Override + public InsertValuesWithBuild value(Expression value) { + + Assert.notNull(value, "Value must not be null!"); + + this.values.add(value); + + return this; + } + + /* + * (non-Javadoc) + * @see org.springframework.data.relational.core.sql.InsertBuilder.InsertIntoColumnsAndValuesWithBuild#values(org.springframework.data.relational.core.sql.Expression[]) + */ + @Override + public InsertValuesWithBuild values(Expression... values) { + + Assert.notNull(values, "Values must not be null!"); + + return values(Arrays.asList(values)); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.relational.core.sql.InsertBuilder.InsertIntoColumnsAndValuesWithBuild#values(java.util.Collection) + */ + @Override + public InsertValuesWithBuild values(Collection values) { + + Assert.notNull(values, "Values must not be null!"); + + this.values.addAll(values); + + return this; + } + + /* + * (non-Javadoc) + * @see org.springframework.data.relational.core.sql.InsertBuilder.BuildInsert#build() + */ + @Override + public Insert build() { + return new DefaultInsert(this.into, this.columns, this.values); + } +} diff --git a/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/DefaultSelect.java b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/DefaultSelect.java index 6bd8f8d5af..5b5816257e 100644 --- a/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/DefaultSelect.java +++ b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/DefaultSelect.java @@ -44,7 +44,7 @@ class DefaultSelect implements Select { this.distinct = distinct; this.selectList = new SelectList(new ArrayList<>(selectList)); - this.from = new From(from); + this.from = new From(new ArrayList<>(from)); this.limit = limit; this.offset = offset; this.joins = new ArrayList<>(joins); diff --git a/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/DefaultUpdate.java b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/DefaultUpdate.java new file mode 100644 index 0000000000..74ba30f2e6 --- /dev/null +++ b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/DefaultUpdate.java @@ -0,0 +1,84 @@ +/* + * 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.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; + +/** + * Default {@link Update} implementation. + * + * @author Mark Paluch + * @since 1.1 + */ +class DefaultUpdate implements Update { + + private final Table table; + private final List assignments; + private final @Nullable Where where; + + DefaultUpdate(Table table, List assignments, @Nullable Condition where) { + this.table = table; + this.assignments = new ArrayList<>(assignments); + this.where = where != null ? new Where(where) : null; + } + + /* + * (non-Javadoc) + * @see org.springframework.data.relational.core.sql.Visitable#visit(org.springframework.data.relational.core.sql.Visitor) + */ + @Override + public void visit(Visitor visitor) { + + Assert.notNull(visitor, "Visitor must not be null!"); + + visitor.enter(this); + + this.table.visit(visitor); + this.assignments.forEach(it -> it.visit(visitor)); + + if (this.where != null) { + this.where.visit(visitor); + } + + visitor.leave(this); + } + + /* + * (non-Javadoc) + * @see java.lang.Object#toString() + */ + @Override + public String toString() { + + StringBuilder builder = new StringBuilder(32); + builder.append("UPDATE ").append(table); + + if (!assignments.isEmpty()) { + builder.append(" SET ").append(StringUtils.collectionToDelimitedString(this.assignments, ", ")); + } + + if (this.where != null) { + builder.append(" WHERE ").append(this.where); + } + + return builder.toString(); + } +} diff --git a/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/DefaultUpdateBuilder.java b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/DefaultUpdateBuilder.java new file mode 100644 index 0000000000..8418765cf0 --- /dev/null +++ b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/DefaultUpdateBuilder.java @@ -0,0 +1,127 @@ +/* + * 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.relational.core.sql; + +import java.util.ArrayList; +import java.util.List; + +import org.springframework.data.relational.core.sql.UpdateBuilder.UpdateAssign; +import org.springframework.data.relational.core.sql.UpdateBuilder.UpdateAssignAnd; +import org.springframework.data.relational.core.sql.UpdateBuilder.UpdateWhere; +import org.springframework.data.relational.core.sql.UpdateBuilder.UpdateWhereAndOr; +import org.springframework.lang.Nullable; +import org.springframework.util.Assert; + +/** + * Default {@link UpdateBuilder} implementation. + * + * @author Mark Paluch + * @since 1.1 + */ +class DefaultUpdateBuilder implements UpdateBuilder, UpdateWhere, UpdateWhereAndOr, UpdateAssign, UpdateAssignAnd { + + private Table table; + private List assignments = new ArrayList<>(); + private @Nullable Condition where; + + /* + * (non-Javadoc) + * @see org.springframework.data.relational.core.sql.UpdateBuilder#table(org.springframework.data.relational.core.sql.Table) + */ + @Override + public UpdateAssign table(Table table) { + + Assert.notNull(table, "Table must not be null!"); + + this.table = table; + + return this; + } + + /* + * (non-Javadoc) + * @see org.springframework.data.relational.core.sql.UpdateBuilder.UpdateAssign#set(org.springframework.data.relational.core.sql.Assignment) + */ + @Override + public DefaultUpdateBuilder set(Assignment assignment) { + + Assert.notNull(assignment, "Assignment must not be null!"); + + this.assignments.add(assignment); + + return this; + } + + /* + * (non-Javadoc) + * @see org.springframework.data.relational.core.sql.UpdateBuilder.UpdateAssignAnd#and(org.springframework.data.relational.core.sql.Assignment) + */ + @Override + public DefaultUpdateBuilder and(Assignment assignment) { + return set(assignment); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.relational.core.sql.UpdateBuilder.UpdateWhere#where(org.springframework.data.relational.core.sql.Condition) + */ + @Override + public UpdateWhereAndOr where(Condition condition) { + + Assert.notNull(condition, "Condition must not be null!"); + + this.where = condition; + + return this; + } + + /* + * (non-Javadoc) + * @see org.springframework.data.relational.core.sql.UpdateBuilder.UpdateWhereAndOr#and(org.springframework.data.relational.core.sql.Condition) + */ + @Override + public UpdateWhereAndOr and(Condition condition) { + + Assert.notNull(condition, "Condition must not be null!"); + + this.where = this.where.and(condition); + + return this; + } + + /* + * (non-Javadoc) + * @see org.springframework.data.relational.core.sql.UpdateBuilder.UpdateWhereAndOr#or(org.springframework.data.relational.core.sql.Condition) + */ + @Override + public UpdateWhereAndOr or(Condition condition) { + + Assert.notNull(condition, "Condition must not be null!"); + + this.where = this.where.and(condition); + + return this; + } + + /* + * (non-Javadoc) + * @see org.springframework.data.relational.core.sql.UpdateBuilder.BuildUpdate#build() + */ + @Override + public Update build() { + return new DefaultUpdate(this.table, this.assignments, this.where); + } +} diff --git a/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/Delete.java b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/Delete.java new file mode 100644 index 0000000000..da85aa2b4d --- /dev/null +++ b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/Delete.java @@ -0,0 +1,42 @@ +/* + * 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.relational.core.sql; + +/** + * AST for a {@code DELETE} statement. Visiting order: + *

    + *
  1. Self
  2. + *
  3. {@link Table FROM tables} clause
  4. + *
  5. {@link Where WHERE} condition
  6. + *
+ * + * @author Mark Paluch + * @since 1.1 + * @see StatementBuilder + * @see DeleteBuilder + * @see SQL + */ +public interface Delete extends Segment, Visitable { + + /** + * Creates a new {@link DeleteBuilder}. + * + * @return a new {@link DeleteBuilder}. + */ + static DeleteBuilder builder() { + return new DefaultDeleteBuilder(); + } +} diff --git a/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/DeleteBuilder.java b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/DeleteBuilder.java new file mode 100644 index 0000000000..d98576fc3e --- /dev/null +++ b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/DeleteBuilder.java @@ -0,0 +1,90 @@ +/* + * 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.relational.core.sql; + +/** + * Entry point to construct a {@link Delete} statement. + * + * @author Mark Paluch + * @since 1.1 + * @see StatementBuilder + */ +public interface DeleteBuilder { + + /** + * Declare a {@link Table} for {@code DELETE FROM}. + * + * @param table the table to {@code DELETE FROM} must not be {@literal null}. + * @return {@code this} builder. + * @see From + * @see SQL#table(String) + */ + DeleteWhere from(Table table); + + /** + * Interface exposing {@code WHERE} methods. + */ + interface DeleteWhere extends BuildDelete { + + /** + * Apply a {@code WHERE} clause. + * + * @param condition the {@code WHERE} condition. + * @return {@code this} builder. + * @see Where + * @see Condition + */ + DeleteWhereAndOr where(Condition condition); + } + + /** + * Interface exposing {@code AND}/{@code OR} combinator methods for {@code WHERE} {@link Condition}s. + */ + interface DeleteWhereAndOr extends BuildDelete { + + /** + * Combine the previous {@code WHERE} {@link Condition} using {@code AND}. + * + * @param condition the condition, must not be {@literal null}. + * @return {@code this} builder. + * @see Condition#and(Condition) + */ + DeleteWhereAndOr and(Condition condition); + + /** + * Combine the previous {@code WHERE} {@link Condition} using {@code OR}. + * + * @param condition the condition, must not be {@literal null}. + * @return {@code this} builder. + * @see Condition#or(Condition) + */ + DeleteWhereAndOr or(Condition condition); + } + + /** + * Interface exposing the {@link Delete} build method. + */ + interface BuildDelete { + + /** + * Build the {@link Delete} statement and verify basic relationship constraints such as all referenced columns have + * a {@code FROM} table import. + * + * @return the build and immutable {@link Delete} statement. + */ + Delete build(); + } +} diff --git a/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/DeleteValidator.java b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/DeleteValidator.java new file mode 100644 index 0000000000..bf07e0e2fb --- /dev/null +++ b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/DeleteValidator.java @@ -0,0 +1,43 @@ +/* + * 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.relational.core.sql; + +/** + * Validator for {@link Delete} statements. + *

+ * Validates that all {@link Column}s using a table qualifier have a table import from the {@code FROM} clause. + * + * @author Mark Paluch + * @since 1.1 + */ +class DeleteValidator extends AbstractImportValidator { + + public static void validate(Delete select) { + new DeleteValidator().doValidate(select); + } + + private void doValidate(Delete select) { + + select.visit(this); + + for (Table table : requiredByWhere) { + if (!from.contains(table)) { + throw new IllegalStateException( + String.format("Required table [%s] by a WHERE predicate not imported by FROM %s", table, from)); + } + } + } +} diff --git a/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/Insert.java b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/Insert.java new file mode 100644 index 0000000000..cf3e1c7d21 --- /dev/null +++ b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/Insert.java @@ -0,0 +1,43 @@ +/* + * 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.relational.core.sql; + +/** + * AST for a {@code INSERT} statement. Visiting order: + *

    + *
  1. Self
  2. + *
  3. {@link Into INTO table} clause
  4. + *
  5. {@link Column columns}
  6. + *
  7. {@link Values VALUEs}
  8. + *
+ * + * @author Mark Paluch + * @since 1.1 + * @see StatementBuilder + * @see InsertBuilder + * @see SQL + */ +public interface Insert extends Segment, Visitable { + + /** + * Creates a new {@link InsertBuilder}. + * + * @return a new {@link InsertBuilder}. + */ + static InsertBuilder builder() { + return new DefaultInsertBuilder(); + } +} 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 new file mode 100644 index 0000000000..f20c8e62df --- /dev/null +++ b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/InsertBuilder.java @@ -0,0 +1,201 @@ +/* + * 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.relational.core.sql; + +import java.util.Collection; + +/** + * Entry point to construct an {@link Insert} statement. + * + * @author Mark Paluch + * @since 1.1 + * @see StatementBuilder + */ +public interface InsertBuilder { + + /** + * Declare a {@link Table} to {@code INSERT INTO}. + * + * @param table the table to {@code INSERT INTO} must not be {@literal null}. + * @return {@code this} builder. + * @see Into + * @see SQL#table(String) + */ + InsertIntoColumnsAndValues into(Table table); + + /** + * Interface exposing {@code WHERE} methods. + */ + interface InsertIntoColumnsAndValues extends InsertValues { + + /** + * Add a {@link Column} to the {@code INTO} column list. Calling this method multiple times will add the + * {@link Column} multiple times. + * + * @param column the column. + * @return {@code this} builder. + * @see Column + */ + InsertIntoColumnsAndValuesWithBuild column(Column column); + + /** + * Add a one or more {@link Column} to the {@code INTO} column list. Calling this method multiple times will add the + * {@link Column} multiple times. + * + * @param columns the columns. + * @return {@code this} builder. + * @see Column + */ + InsertIntoColumnsAndValuesWithBuild columns(Column... columns); + + /** + * Add a one or more {@link Column} to the {@code INTO} column list. Calling this method multiple times will add the + * {@link Column} multiple times. + * + * @param columns the columns. + * @return {@code this} builder. + * @see Column + */ + InsertIntoColumnsAndValuesWithBuild columns(Collection columns); + } + + /** + * Interface exposing {@code value} methods to add values to the {@code INSERT} statement and the build method. + */ + interface InsertIntoColumnsAndValuesWithBuild extends InsertIntoColumnsAndValues, InsertValues, BuildInsert { + + /** + * Add a {@link Expression value} to the {@code VALUES} list. Calling this method multiple times will add a + * {@link Expression value} multiple times. + * + * @param value the value to use. + * @return {@code this} builder. + * @see Column + */ + @Override + InsertValuesWithBuild value(Expression value); + + /** + * Add one or more {@link Expression values} to the {@code VALUES} list. Calling this method multiple times will add + * a {@link Expression values} multiple times. + * + * @param values the values. + * @return {@code this} builder. + * @see Column + */ + @Override + InsertValuesWithBuild values(Expression... values); + + /** + * Add one or more {@link Expression values} to the {@code VALUES} list. Calling this method multiple times will add + * a {@link Expression values} multiple times. + * + * @param values the values. + * @return {@code this} builder. + * @see Column + */ + @Override + InsertValuesWithBuild values(Collection values); + } + + /** + * Interface exposing {@code value} methods to add values to the {@code INSERT} statement and the build method. + */ + interface InsertValuesWithBuild extends InsertValues, BuildInsert { + + /** + * Add a {@link Expression value} to the {@code VALUES} list. Calling this method multiple times will add a + * {@link Expression value} multiple times. + * + * @param value the value to use. + * @return {@code this} builder. + * @see Column + */ + @Override + InsertValuesWithBuild value(Expression value); + + /** + * Add one or more {@link Expression values} to the {@code VALUES} list. Calling this method multiple times will add + * a {@link Expression values} multiple times. + * + * @param values the values. + * @return {@code this} builder. + * @see Column + */ + @Override + InsertValuesWithBuild values(Expression... values); + + /** + * Add one or more {@link Expression values} to the {@code VALUES} list. Calling this method multiple times will add + * a {@link Expression values} multiple times. + * + * @param values the values. + * @return {@code this} builder. + * @see Column + */ + @Override + InsertValuesWithBuild values(Collection values); + } + + /** + * Interface exposing {@code value} methods to add values to the {@code INSERT} statement. + */ + interface InsertValues { + + /** + * Add a {@link Expression value} to the {@code VALUES} list. Calling this method multiple times will add a + * {@link Expression value} multiple times. + * + * @param value the value to use. + * @return {@code this} builder. + * @see Column + */ + InsertValuesWithBuild value(Expression value); + + /** + * Add one or more {@link Expression values} to the {@code VALUES} list. Calling this method multiple times will add + * a {@link Expression values} multiple times. + * + * @param values the values. + * @return {@code this} builder. + * @see Column + */ + InsertValuesWithBuild values(Expression... values); + + /** + * Add one or more {@link Expression values} to the {@code VALUES} list. Calling this method multiple times will add + * a {@link Expression values} multiple times. + * + * @param values the values. + * @return {@code this} builder. + * @see Column + */ + InsertValuesWithBuild values(Collection values); + } + + /** + * Interface exposing the {@link Insert} build method. + */ + interface BuildInsert { + + /** + * Build the {@link Insert} statement. + * + * @return the build and immutable {@link Insert} statement. + */ + Insert build(); + } +} diff --git a/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/Into.java b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/Into.java new file mode 100644 index 0000000000..9758a91e82 --- /dev/null +++ b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/Into.java @@ -0,0 +1,52 @@ +/* + * 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.relational.core.sql; + +import java.util.Arrays; +import java.util.List; + +import org.springframework.util.StringUtils; + +/** + * {@code INTO} clause. + * + * @author Mark Paluch + * @since 1.1 + */ +public class Into extends AbstractSegment { + + private final List
tables; + + Into(Table... tables) { + this(Arrays.asList(tables)); + } + + Into(List
tables) { + + super(tables.toArray(new Table[] {})); + + this.tables = tables; + } + + /* + * (non-Javadoc) + * @see java.lang.Object#toString() + */ + @Override + public String toString() { + return "INTO " + StringUtils.collectionToDelimitedString(tables, ", "); + } +} diff --git a/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/Literal.java b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/Literal.java new file mode 100644 index 0000000000..f874dc9fb8 --- /dev/null +++ b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/Literal.java @@ -0,0 +1,55 @@ +/* + * 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.relational.core.sql; + +import org.springframework.lang.Nullable; + +/** + * Represents a literal. + * + * @author Mark Paluch + * @since 1.1 + */ +public class Literal extends AbstractSegment implements Expression { + + private @Nullable T content; + + Literal(@Nullable T content) { + this.content = content; + } + + /** + * @return the content of the literal. + */ + @Nullable + public T getContent() { + return content; + } + + /* + * (non-Javadoc) + * @see java.lang.Object#toString() + */ + @Override + public String toString() { + + if (this.content == null) { + return "NULL"; + } + + return content.toString(); + } +} diff --git a/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/NumericLiteral.java b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/NumericLiteral.java new file mode 100644 index 0000000000..d09d93d66a --- /dev/null +++ b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/NumericLiteral.java @@ -0,0 +1,41 @@ +/* + * 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.relational.core.sql; + +import org.springframework.lang.Nullable; + +/** + * Represents a {@link Number} literal. + * + * @author Mark Paluch + * @since 1.1 + */ +public class NumericLiteral extends Literal { + + NumericLiteral(@Nullable Number content) { + super(content); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.relational.core.sql.Literal#getContent() + */ + @Override + @Nullable + public Number getContent() { + return super.getContent(); + } +} diff --git a/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/SQL.java b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/SQL.java index 0f45a8637e..c01954f5a5 100644 --- a/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/SQL.java +++ b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/SQL.java @@ -16,6 +16,7 @@ package org.springframework.data.relational.core.sql; import org.springframework.data.relational.core.sql.BindMarker.NamedBindMarker; +import org.springframework.lang.Nullable; import org.springframework.util.Assert; /** @@ -77,6 +78,45 @@ public static BindMarker bindMarker(String name) { return new NamedBindMarker(name); } + /** + * Creates a new {@link StringLiteral} from the {@code content}. + * + * @param content the literal content. + * @return a new {@link StringLiteral}. + */ + public static StringLiteral literalOf(@Nullable CharSequence content) { + return new StringLiteral(content); + } + + /** + * Creates a new {@link NumericLiteral} from the {@code content}. + * + * @param content the literal content. + * @return a new {@link NumericLiteral}. + */ + public static NumericLiteral literalOf(@Nullable Number content) { + return new NumericLiteral(content); + } + + /** + * Creates a new {@link Literal} from the {@code content}. + * + * @param content the literal content. + * @return a new {@link Literal}. + */ + public static Literal literalOf(@Nullable T content) { + return new Literal<>(content); + } + + /** + * Creates a new {@code NULL} {@link Literal}. + * + * @return a new {@link Literal}. + */ + public static Literal nullLiteral() { + return new Literal<>(null); + } + // Utility constructor. private SQL() {} } 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 b1ea5ceb94..f4ef3c8e61 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 @@ -22,6 +22,7 @@ * * @author Mark Paluch * @since 1.1 + * @see StatementBuilder */ public interface SelectBuilder { 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 e1f9257818..d8254c9374 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 @@ -27,18 +27,14 @@ * @author Mark Paluch * @since 1.1 */ -class SelectValidator implements Visitor { +class SelectValidator extends AbstractImportValidator { private int selectFieldCount; private Set
requiredBySelect = new HashSet<>(); - private Set
requiredByWhere = new HashSet<>(); private Set
requiredByOrderBy = new HashSet<>(); - private Set
from = new HashSet<>(); private Set
join = new HashSet<>(); - private Visitable parent; - public static void validate(Select select) { new SelectValidator().doValidate(select); } @@ -80,6 +76,8 @@ private void doValidate(Select select) { @Override public void enter(Visitable segment) { + super.enter(segment); + if (segment instanceof AsteriskFromTable && parent instanceof Select) { Table table = ((AsteriskFromTable) segment).getTable(); @@ -97,10 +95,6 @@ public void enter(Visitable segment) { } } - if (segment instanceof Table && parent instanceof From) { - from.add((Table) segment); - } - if (segment instanceof Column && parent instanceof OrderByField) { Table table = ((Column) segment).getTable(); @@ -123,17 +117,5 @@ public void enter(Visitable segment) { } }); } - - if (segment instanceof Join || segment instanceof OrderByField || segment instanceof From - || segment instanceof Select || segment instanceof Where || segment instanceof SimpleFunction) { - parent = segment; - } } - - /* - * (non-Javadoc) - * @see org.springframework.data.relational.core.sql.Visitor#leave(org.springframework.data.relational.core.sql.Visitable) - */ - @Override - public void leave(Visitable segment) {} } diff --git a/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/StatementBuilder.java b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/StatementBuilder.java index 9116886048..bd209a46f4 100644 --- a/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/StatementBuilder.java +++ b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/StatementBuilder.java @@ -15,8 +15,12 @@ */ package org.springframework.data.relational.core.sql; +import static org.springframework.data.relational.core.sql.UpdateBuilder.*; + import java.util.Collection; +import org.springframework.data.relational.core.sql.DeleteBuilder.DeleteWhere; +import org.springframework.data.relational.core.sql.InsertBuilder.InsertIntoColumnsAndValues; import org.springframework.data.relational.core.sql.SelectBuilder.SelectAndFrom; /** @@ -32,7 +36,7 @@ public abstract class StatementBuilder { /** - * Creates a new {@link SelectBuilder} by specifying a {@code SELECT} column. + * Creates a new {@link SelectBuilder} and includes the {@code SELECT} columns. * * @param expression the select list expression. * @return the {@link SelectBuilder} containing {@link Expression}. @@ -43,7 +47,7 @@ public static SelectAndFrom select(Expression expression) { } /** - * Creates a new {@link SelectBuilder} by specifying one or more {@code SELECT} columns. + * Creates a new {@link SelectBuilder} and includes one or more {@code SELECT} columns. * * @param expressions the select list expressions. * @return the {@link SelectBuilder} containing {@link Expression}s. @@ -54,10 +58,10 @@ public static SelectAndFrom select(Expression... expressions) { } /** - * Include one or more {@link Expression}s in the select list. + * Creates a new {@link SelectBuilder} and includes one or more {@link Expression}s in the select list. * * @param expressions the expressions to include. - * @return {@code this} builder. + * @return the {@link SelectBuilder} containing {@link Expression}s. * @see Table#columns(String...) */ public static SelectAndFrom select(Collection expressions) { @@ -74,7 +78,68 @@ public static SelectBuilder select() { return Select.builder(); } - private StatementBuilder() { + /** + * Creates a new {@link InsertBuilder} and declare the {@link Table} to insert into. + * + * @param table the table to insert into. + * @return the new {@link InsertBuilder}. + * @see Table#create(String) + */ + public static InsertIntoColumnsAndValues insert(Table table) { + return insert().into(table); + } + /** + * Creates a new {@link InsertBuilder}. + * + * @return the new {@link InsertBuilder}. + * @see InsertBuilder + */ + public static InsertBuilder insert() { + return Insert.builder(); + } + + /** + * Creates a new {@link UpdateBuilder} and declare the {@link Table} for the update. + * + * @param table the table for the update. + * @return the new {@link UpdateBuilder}. + * @see Table#create(String) + */ + public static UpdateAssign update(Table table) { + return update().table(table); } + + /** + * Creates a new {@link UpdateBuilder}. + * + * @return the new {@link UpdateBuilder}. + * @see UpdateBuilder + */ + public static UpdateBuilder update() { + return Update.builder(); + } + + /** + * Creates a new {@link DeleteBuilder} and declares the {@link Table} to delete from. + * + * @param table the table to delete from. + * @return {@code this} builder. + * @see Table#columns(String...) + */ + public static DeleteWhere delete(Table table) { + return delete().from(table); + } + + /** + * Creates a new {@link DeleteBuilder}. + * + * @return the new {@link DeleteBuilder}. + * @see DeleteBuilder + */ + public static DeleteBuilder delete() { + return Delete.builder(); + } + + private StatementBuilder() {} } diff --git a/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/StringLiteral.java b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/StringLiteral.java new file mode 100644 index 0000000000..ab2e05b1c6 --- /dev/null +++ b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/StringLiteral.java @@ -0,0 +1,50 @@ +/* + * 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.relational.core.sql; + +import org.springframework.lang.Nullable; + +/** + * Represents a {@link CharSequence} literal. + * + * @author Mark Paluch + * @since 1.1 + */ +public class StringLiteral extends Literal { + + StringLiteral(@Nullable CharSequence content) { + super(content); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.relational.core.sql.Literal#getContent() + */ + @Override + @Nullable + public CharSequence getContent() { + return super.getContent(); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.relational.core.sql.Literal#toString() + */ + @Override + public String toString() { + return "'" + super.toString() + "'"; + } +} diff --git a/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/Update.java b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/Update.java new file mode 100644 index 0000000000..3289011b6d --- /dev/null +++ b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/Update.java @@ -0,0 +1,43 @@ +/* + * 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.relational.core.sql; + +/** + * AST for aa {@code UPDATE} statement. Visiting order: + *
    + *
  1. Self
  2. + *
  3. {@link Table table}
  4. + *
  5. {@link Assignments assignments}
  6. + *
  7. {@link Where WHERE} condition
  8. + *
+ * + * @author Mark Paluch + * @since 1.1 + * @see StatementBuilder + * @see SelectBuilder + * @see SQL + */ +public interface Update extends Segment, Visitable { + + /** + * Creates a new {@link UpdateBuilder}. + * + * @return a new {@link UpdateBuilder}. + */ + static UpdateBuilder builder() { + return new DefaultUpdateBuilder(); + } +} diff --git a/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/UpdateBuilder.java b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/UpdateBuilder.java new file mode 100644 index 0000000000..5588741898 --- /dev/null +++ b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/UpdateBuilder.java @@ -0,0 +1,117 @@ +/* + * 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.relational.core.sql; + +/** + * Entry point to construct an {@link Update} statement. + * + * @author Mark Paluch + * @since 1.1 + * @see StatementBuilder + */ +public interface UpdateBuilder { + + /** + * Configure the {@link Table} to which the update is applied. + * + * @param count the top count. + * @return {@code this} {@link SelectBuilder}. + */ + UpdateAssign table(Table table); + + /** + * Interface exposing {@code SET} methods. + */ + interface UpdateAssign { + + /** + * Apply a {@link Assignment SET assignment}. + * + * @param assignment a single {@link Assignment column assignment}. + * @return {@code this} builder. + * @see Assignment + */ + UpdateAssignAnd set(Assignment assignment); + } + + /** + * Interface exposing {@code SET} methods. + */ + interface UpdateAssignAnd extends UpdateWhere { + + /** + * Apply a {@link Assignment SET assignment}. + * + * @param assignment a single {@link Assignment column assignment}. + * @return {@code this} builder. + * @see Assignment + */ + UpdateAssignAnd and(Assignment assignment); + } + + /** + * Interface exposing {@code WHERE} methods. + */ + interface UpdateWhere extends BuildUpdate { + + /** + * Apply a {@code WHERE} clause. + * + * @param condition the {@code WHERE} condition. + * @return {@code this} builder. + * @see Where + * @see Condition + */ + UpdateWhereAndOr where(Condition condition); + } + + /** + * Interface exposing {@code AND}/{@code OR} combinator methods for {@code WHERE} {@link Condition}s. + */ + interface UpdateWhereAndOr extends BuildUpdate { + + /** + * Combine the previous {@code WHERE} {@link Condition} using {@code AND}. + * + * @param condition the condition, must not be {@literal null}. + * @return {@code this} builder. + * @see Condition#and(Condition) + */ + UpdateWhereAndOr and(Condition condition); + + /** + * Combine the previous {@code WHERE} {@link Condition} using {@code OR}. + * + * @param condition the condition, must not be {@literal null}. + * @return {@code this} builder. + * @see Condition#or(Condition) + */ + UpdateWhereAndOr or(Condition condition); + } + + /** + * Interface exposing the {@link Update} build method. + */ + interface BuildUpdate { + + /** + * Build the {@link Update}. + * + * @return the build and immutable {@link Update} statement. + */ + Update build(); + } +} diff --git a/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/Values.java b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/Values.java new file mode 100644 index 0000000000..f857aafc37 --- /dev/null +++ b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/Values.java @@ -0,0 +1,52 @@ +/* + * 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.relational.core.sql; + +import java.util.Arrays; +import java.util.List; + +import org.springframework.util.StringUtils; + +/** + * {@code VALUES} clause. + * + * @author Mark Paluch + * @since 1.1 + */ +public class Values extends AbstractSegment { + + private final List tables; + + Values(Expression... tables) { + this(Arrays.asList(tables)); + } + + Values(List expressions) { + + super(expressions.toArray(new Expression[0])); + + this.tables = expressions; + } + + /* + * (non-Javadoc) + * @see java.lang.Object#toString() + */ + @Override + public String toString() { + return "VALUES(" + StringUtils.collectionToDelimitedString(tables, ", ") + ")"; + } +} diff --git a/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/render/AssignmentVisitor.java b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/render/AssignmentVisitor.java new file mode 100644 index 0000000000..01df5efcb6 --- /dev/null +++ b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/render/AssignmentVisitor.java @@ -0,0 +1,95 @@ +/* + * 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.relational.core.sql.render; + +import org.springframework.data.relational.core.sql.Assignment; +import org.springframework.data.relational.core.sql.Column; +import org.springframework.data.relational.core.sql.Expression; +import org.springframework.data.relational.core.sql.Visitable; + +/** + * {@link org.springframework.data.relational.core.sql.Visitor} rendering {@link Assignment}. Uses a + * {@link RenderTarget} to call back for render results. + * + * @author Mark Paluch + * @since 1.1 + * @see Assignment + */ +class AssignmentVisitor extends TypedSubtreeVisitor { + + private final ColumnVisitor columnVisitor; + private final ExpressionVisitor expressionVisitor; + private final RenderTarget target; + private final StringBuilder part = new StringBuilder(); + + AssignmentVisitor(RenderContext context, RenderTarget target) { + this.columnVisitor = new ColumnVisitor(context, part::append); + this.expressionVisitor = new ExpressionVisitor(context); + this.target = target; + } + + /* + * (non-Javadoc) + * @see org.springframework.data.relational.core.sql.render.FilteredSubtreeVisitor#enterNested(org.springframework.data.relational.core.sql.Visitable) + */ + @Override + Delegation enterNested(Visitable segment) { + + if (segment instanceof Column) { + return Delegation.delegateTo(columnVisitor); + } + + if (segment instanceof Expression) { + return Delegation.delegateTo(expressionVisitor); + } + + throw new IllegalStateException("Cannot provide visitor for " + segment); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.relational.core.sql.render.FilteredSubtreeVisitor#leaveNested(org.springframework.data.relational.core.sql.Visitable) + */ + @Override + Delegation leaveNested(Visitable segment) { + + if (segment instanceof Column) { + if (part.length() != 0) { + part.append(" = "); + } + return super.leaveNested(segment); + } + + if (segment instanceof Expression) { + part.append(expressionVisitor.getRenderedPart()); + } + + return super.leaveNested(segment); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.relational.core.sql.render.FilteredSubtreeVisitor#leaveMatched(org.springframework.data.relational.core.sql.Visitable) + */ + @Override + Delegation leaveMatched(Assignment segment) { + + target.onRendered(new StringBuilder(part)); + part.setLength(0); + + return super.leaveMatched(segment); + } +} diff --git a/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/render/ColumnVisitor.java b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/render/ColumnVisitor.java new file mode 100644 index 0000000000..814a38b196 --- /dev/null +++ b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/render/ColumnVisitor.java @@ -0,0 +1,73 @@ +/* + * 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.relational.core.sql.render; + +import org.springframework.data.relational.core.sql.Column; +import org.springframework.data.relational.core.sql.Table; +import org.springframework.data.relational.core.sql.Visitable; +import org.springframework.lang.Nullable; + +/** + * Renderer for {@link Column}s. + * + * @author Mark Paluch + * @since 1.1 + */ +class ColumnVisitor extends TypedSubtreeVisitor { + + private final RenderContext context; + private final RenderTarget target; + + private @Nullable String tableName; + + ColumnVisitor(RenderContext context, RenderTarget target) { + this.context = context; + this.target = target; + } + + /* + * (non-Javadoc) + * @see org.springframework.data.relational.core.sql.render.TypedSubtreeVisitor#leaveMatched(org.springframework.data.relational.core.sql.Visitable) + */ + @Override + Delegation leaveMatched(Column segment) { + + String column = context.getNamingStrategy().getName(segment); + StringBuilder builder = new StringBuilder( + tableName != null ? tableName.length() + column.length() : column.length()); + if (tableName != null) { + builder.append(tableName); + } + builder.append(column); + + target.onRendered(builder); + return super.leaveMatched(segment); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.relational.core.sql.render.TypedSubtreeVisitor#leaveNested(org.springframework.data.relational.core.sql.Visitable) + */ + @Override + Delegation leaveNested(Visitable segment) { + + if (segment instanceof Table) { + tableName = context.getNamingStrategy().getReferenceName((Table) segment) + '.'; + } + + return super.leaveNested(segment); + } +} diff --git a/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/render/DeleteStatementVisitor.java b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/render/DeleteStatementVisitor.java new file mode 100644 index 0000000000..dc49288866 --- /dev/null +++ b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/render/DeleteStatementVisitor.java @@ -0,0 +1,103 @@ +/* + * 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.relational.core.sql.render; + +import org.springframework.data.relational.core.sql.Delete; +import org.springframework.data.relational.core.sql.From; +import org.springframework.data.relational.core.sql.Visitable; +import org.springframework.data.relational.core.sql.Where; + +/** + * {@link PartRenderer} for {@link Delete} statements. + * + * @author Mark Paluch + * @since 1.1 + */ +class DeleteStatementVisitor extends DelegatingVisitor implements PartRenderer { + + private StringBuilder builder = new StringBuilder(); + private StringBuilder from = new StringBuilder(); + private StringBuilder where = new StringBuilder(); + + private FromClauseVisitor fromClauseVisitor; + private WhereClauseVisitor whereClauseVisitor; + + DeleteStatementVisitor(RenderContext context) { + + this.fromClauseVisitor = new FromClauseVisitor(context, it -> { + + if (from.length() != 0) { + from.append(", "); + } + + from.append(it); + }); + + this.whereClauseVisitor = new WhereClauseVisitor(context, where::append); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.relational.core.sql.render.DelegatingVisitor#doEnter(org.springframework.data.relational.core.sql.Visitable) + */ + @Override + public Delegation doEnter(Visitable segment) { + + if (segment instanceof From) { + return Delegation.delegateTo(fromClauseVisitor); + } + + if (segment instanceof Where) { + return Delegation.delegateTo(whereClauseVisitor); + } + + return Delegation.retain(); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.relational.core.sql.render.DelegatingVisitor#doLeave(org.springframework.data.relational.core.sql.Visitable) + */ + @Override + public Delegation doLeave(Visitable segment) { + + if (segment instanceof Delete) { + + builder.append("DELETE "); + + if (from.length() != 0) { + builder.append("FROM ").append(from); + } + + if (where.length() != 0) { + builder.append(" WHERE ").append(where); + } + + return Delegation.leave(); + } + + return Delegation.retain(); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.relational.core.sql.render.PartRenderer#getRenderedPart() + */ + @Override + public CharSequence getRenderedPart() { + return builder; + } +} diff --git a/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/render/ExpressionVisitor.java b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/render/ExpressionVisitor.java index 604190e079..751e580cea 100644 --- a/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/render/ExpressionVisitor.java +++ b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/render/ExpressionVisitor.java @@ -19,6 +19,7 @@ import org.springframework.data.relational.core.sql.Column; import org.springframework.data.relational.core.sql.Condition; import org.springframework.data.relational.core.sql.Expression; +import org.springframework.data.relational.core.sql.Literal; import org.springframework.data.relational.core.sql.Named; import org.springframework.data.relational.core.sql.SubselectExpression; import org.springframework.data.relational.core.sql.Visitable; @@ -71,6 +72,8 @@ Delegation enterMatched(Expression segment) { } else { value = segment.toString(); } + } else if (segment instanceof Literal) { + value = segment.toString(); } return Delegation.retain(); 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 new file mode 100644 index 0000000000..8951b18440 --- /dev/null +++ b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/render/InsertStatementVisitor.java @@ -0,0 +1,123 @@ +/* + * 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.relational.core.sql.render; + +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; + +/** + * {@link PartRenderer} for {@link Insert} statements. + * + * @author Mark Paluch + * @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 IntoClauseVisitor intoClauseVisitor; + private ColumnVisitor columnVisitor; + private ValuesVisitor valuesVisitor; + + InsertStatementVisitor(RenderContext context) { + + this.intoClauseVisitor = new IntoClauseVisitor(context, it -> { + + if (into.length() != 0) { + into.append(", "); + } + + into.append(it); + }); + + this.columnVisitor = new ColumnVisitor(context, it -> { + + if (columns.length() != 0) { + columns.append(", "); + } + + columns.append(it); + }); + + this.valuesVisitor = new ValuesVisitor(context, values::append); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.relational.core.sql.render.DelegatingVisitor#doEnter(org.springframework.data.relational.core.sql.Visitable) + */ + @Override + public Delegation doEnter(Visitable segment) { + + if (segment instanceof Into) { + return Delegation.delegateTo(this.intoClauseVisitor); + } + + if (segment instanceof Column) { + return Delegation.delegateTo(this.columnVisitor); + } + + if (segment instanceof Values) { + return Delegation.delegateTo(this.valuesVisitor); + } + + return Delegation.retain(); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.relational.core.sql.render.DelegatingVisitor#doLeave(org.springframework.data.relational.core.sql.Visitable) + */ + @Override + public Delegation doLeave(Visitable segment) { + + if (segment instanceof Insert) { + + builder.append("INSERT"); + + if (into.length() != 0) { + builder.append(" INTO ").append(into); + } + + if (columns.length() != 0) { + builder.append(" (").append(columns).append(")"); + } + + if (values.length() != 0) { + builder.append(" VALUES(").append(values).append(")"); + } + + return Delegation.leave(); + } + + return Delegation.retain(); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.relational.core.sql.render.PartRenderer#getRenderedPart() + */ + @Override + public CharSequence getRenderedPart() { + return builder; + } +} diff --git a/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/render/IntoClauseVisitor.java b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/render/IntoClauseVisitor.java new file mode 100644 index 0000000000..855c1825ce --- /dev/null +++ b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/render/IntoClauseVisitor.java @@ -0,0 +1,68 @@ +/* + * 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.relational.core.sql.render; + +import org.springframework.data.relational.core.sql.Into; +import org.springframework.data.relational.core.sql.Visitable; + +/** + * Renderer for {@link Into}. Uses a {@link RenderTarget} to call back for render results. + * + * @author Mark Paluch + * @since 1.1 + */ +class IntoClauseVisitor extends TypedSubtreeVisitor { + + private final FromTableVisitor visitor; + private final RenderTarget parent; + private final StringBuilder builder = new StringBuilder(); + private boolean first = true; + + IntoClauseVisitor(RenderContext context, RenderTarget parent) { + + this.visitor = new FromTableVisitor(context, it -> { + + if (first) { + first = false; + } else { + builder.append(", "); + } + + builder.append(it); + }); + + this.parent = parent; + } + + /* + * (non-Javadoc) + * @see org.springframework.data.relational.core.sql.render.TypedSubtreeVisitor#enterNested(org.springframework.data.relational.core.sql.Visitable) + */ + @Override + Delegation enterNested(Visitable segment) { + return Delegation.delegateTo(visitor); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.relational.core.sql.render.TypedSubtreeVisitor#leaveMatched(org.springframework.data.relational.core.sql.Visitable) + */ + @Override + Delegation leaveMatched(Into segment) { + parent.onRendered(builder); + return super.leaveMatched(segment); + } +} diff --git a/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/render/Renderer.java b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/render/Renderer.java new file mode 100644 index 0000000000..7a7466d7a9 --- /dev/null +++ b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/render/Renderer.java @@ -0,0 +1,62 @@ +/* + * 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.relational.core.sql.render; + +import org.springframework.data.relational.core.sql.Delete; +import org.springframework.data.relational.core.sql.Insert; +import org.springframework.data.relational.core.sql.Select; +import org.springframework.data.relational.core.sql.Update; + +/** + * SQL renderer for {@link Select} and {@link Delete} statements. + * + * @author Mark Paluch + * @since 1.1 + */ +public interface Renderer { + + /** + * Render the {@link Select} AST into a SQL statement. + * + * @param select the statement to render, must not be {@literal null}. + * @return the rendered statement. + */ + String render(Select select); + + /** + * Render the {@link Insert} AST into a SQL statement. + * + * @param insert the statement to render, must not be {@literal null}. + * @return the rendered statement. + */ + String render(Insert insert); + + /** + * Render the {@link Update} AST into a SQL statement. + * + * @param update the statement to render, must not be {@literal null}. + * @return the rendered statement. + */ + String render(Update update); + + /** + * Render the {@link Delete} AST into a SQL statement. + * + * @param delete the statement to render, must not be {@literal null}. + * @return the rendered statement. + */ + String render(Delete delete); +} 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 b114173c7f..e62d5512a7 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 @@ -15,49 +15,48 @@ */ package org.springframework.data.relational.core.sql.render; +import org.springframework.data.relational.core.sql.Delete; +import org.springframework.data.relational.core.sql.Insert; import org.springframework.data.relational.core.sql.Select; +import org.springframework.data.relational.core.sql.Update; import org.springframework.util.Assert; /** - * Naive SQL renderer that does not consider dialect specifics. This class is to evaluate requirements of a SQL - * renderer. + * SQL renderer for {@link Select} and {@link Delete} statements. * * @author Mark Paluch * @author Jens Schauder * @since 1.1 + * @see RenderContext */ -public class SqlRenderer { +public class SqlRenderer implements Renderer { - private final Select select; private final RenderContext context; - private SqlRenderer(Select select, RenderContext context) { - this.context = context; + private SqlRenderer(RenderContext context) { - Assert.notNull(select, "Select must not be null!"); + Assert.notNull(context, "RenderContext must not be null!"); - this.select = select; + this.context = context; } /** * Creates a new {@link SqlRenderer}. * - * @param select must not be {@literal null}. * @return the renderer. */ - public static SqlRenderer create(Select select) { - return new SqlRenderer(select, new SimpleRenderContext(NamingStrategies.asIs())); + public static SqlRenderer create() { + return new SqlRenderer(new SimpleRenderContext(NamingStrategies.asIs())); } /** * Creates a new {@link SqlRenderer} using a {@link RenderContext}. * - * @param select must not be {@literal null}. * @param context must not be {@literal null}. * @return the renderer. */ - public static SqlRenderer create(Select select, RenderContext context) { - return new SqlRenderer(select, context); + public static SqlRenderer create(RenderContext context) { + return new SqlRenderer(context); } /** @@ -66,8 +65,38 @@ public static SqlRenderer create(Select select, RenderContext context) { * @param select must not be {@literal null}. * @return the rendered statement. */ - public static String render(Select select) { - return create(select).render(); + 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. + * + * @param update must not be {@literal null}. + * @return the rendered statement. + */ + public static String toString(Update update) { + return create().render(update); + } + + /** + * Renders a {@link Delete} statement into its SQL representation. + * + * @param delete must not be {@literal null}. + * @return the rendered statement. + */ + public static String toString(Delete delete) { + return create().render(delete); } /** @@ -75,11 +104,54 @@ public static String render(Select select) { * * @return the rendered statement. */ - public String render() { + @Override + public String render(Select select) { SelectStatementVisitor visitor = new SelectStatementVisitor(context); select.visit(visitor); return visitor.getRenderedPart().toString(); } + + /** + * Render the {@link Insert} AST into a SQL statement. + * + * @return the rendered statement. + */ + @Override + public String render(Insert insert) { + + InsertStatementVisitor visitor = new InsertStatementVisitor(context); + insert.visit(visitor); + + return visitor.getRenderedPart().toString(); + } + + /** + * Render the {@link Update} AST into a SQL statement. + * + * @return the rendered statement. + */ + @Override + public String render(Update update) { + + UpdateStatementVisitor visitor = new UpdateStatementVisitor(context); + update.visit(visitor); + + return visitor.getRenderedPart().toString(); + } + + /** + * Render the {@link Delete} AST into a SQL statement. + * + * @return the rendered statement. + */ + @Override + public String render(Delete delete) { + + DeleteStatementVisitor visitor = new DeleteStatementVisitor(context); + delete.visit(visitor); + + return visitor.getRenderedPart().toString(); + } } diff --git a/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/render/UpdateStatementVisitor.java b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/render/UpdateStatementVisitor.java new file mode 100644 index 0000000000..125e4c1cb1 --- /dev/null +++ b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/render/UpdateStatementVisitor.java @@ -0,0 +1,123 @@ +/* + * 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.relational.core.sql.render; + +import org.springframework.data.relational.core.sql.Assignment; +import org.springframework.data.relational.core.sql.Table; +import org.springframework.data.relational.core.sql.Update; +import org.springframework.data.relational.core.sql.Visitable; +import org.springframework.data.relational.core.sql.Where; + +/** + * {@link PartRenderer} for {@link Update} statements. + * + * @author Mark Paluch + * @since 1.1 + */ +class UpdateStatementVisitor extends DelegatingVisitor implements PartRenderer { + + private StringBuilder builder = new StringBuilder(); + private StringBuilder table = new StringBuilder(); + private StringBuilder assignments = new StringBuilder(); + private StringBuilder where = new StringBuilder(); + + private FromTableVisitor tableVisitor; + private AssignmentVisitor assignmentVisitor; + private WhereClauseVisitor whereClauseVisitor; + + UpdateStatementVisitor(RenderContext context) { + + this.tableVisitor = new FromTableVisitor(context, it -> { + + if (table.length() != 0) { + table.append(", "); + } + + table.append(it); + }); + + this.assignmentVisitor = new AssignmentVisitor(context, it -> { + + if (assignments.length() != 0) { + assignments.append(", "); + } + + assignments.append(it); + }); + + this.whereClauseVisitor = new WhereClauseVisitor(context, where::append); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.relational.core.sql.render.DelegatingVisitor#doEnter(org.springframework.data.relational.core.sql.Visitable) + */ + @Override + public Delegation doEnter(Visitable segment) { + + if (segment instanceof Table) { + return Delegation.delegateTo(this.tableVisitor); + } + + if (segment instanceof Assignment) { + return Delegation.delegateTo(this.assignmentVisitor); + } + + if (segment instanceof Where) { + return Delegation.delegateTo(this.whereClauseVisitor); + } + + return Delegation.retain(); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.relational.core.sql.render.DelegatingVisitor#doLeave(org.springframework.data.relational.core.sql.Visitable) + */ + @Override + public Delegation doLeave(Visitable segment) { + + if (segment instanceof Update) { + + builder.append("UPDATE"); + + if (table.length() != 0) { + builder.append(" ").append(table); + } + + if (assignments.length() != 0) { + builder.append(" SET ").append(assignments); + } + + if (where.length() != 0) { + builder.append(" WHERE ").append(where); + } + + return Delegation.leave(); + } + + return Delegation.retain(); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.relational.core.sql.render.PartRenderer#getRenderedPart() + */ + @Override + public CharSequence getRenderedPart() { + return builder; + } +} diff --git a/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/render/ValuesVisitor.java b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/render/ValuesVisitor.java new file mode 100644 index 0000000000..501d17e40e --- /dev/null +++ b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/render/ValuesVisitor.java @@ -0,0 +1,90 @@ +/* + * 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.relational.core.sql.render; + +import org.springframework.data.relational.core.sql.Expression; +import org.springframework.data.relational.core.sql.Values; +import org.springframework.data.relational.core.sql.Visitable; +import org.springframework.lang.Nullable; + +/** + * Renderer for {@link Values}. Uses a {@link RenderTarget} to call back for render results. + * + * @author Mark Paluch + * @since 1.1 + */ +class ValuesVisitor extends TypedSubtreeVisitor { + + private final RenderTarget parent; + private final StringBuilder builder = new StringBuilder(); + private final RenderContext context; + + private @Nullable ExpressionVisitor current; + private boolean first = true; + + ValuesVisitor(RenderContext context, RenderTarget parent) { + + this.context = context; + this.parent = parent; + } + + /* + * (non-Javadoc) + * @see org.springframework.data.relational.core.sql.render.TypedSubtreeVisitor#enterNested(org.springframework.data.relational.core.sql.Visitable) + */ + @Override + Delegation enterNested(Visitable segment) { + + if (segment instanceof Expression) { + this.current = new ExpressionVisitor(context); + return Delegation.delegateTo(this.current); + } + + return super.enterNested(segment); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.relational.core.sql.render.TypedSubtreeVisitor#leaveNested(org.springframework.data.relational.core.sql.Visitable) + */ + @Override + Delegation leaveNested(Visitable segment) { + + if (this.current != null) { + + if (first) { + first = false; + } else { + builder.append(", "); + } + + builder.append(this.current.getRenderedPart()); + this.current = null; + } + + return super.leaveNested(segment); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.relational.core.sql.render.TypedSubtreeVisitor#leaveMatched(org.springframework.data.relational.core.sql.Visitable) + */ + @Override + Delegation leaveMatched(Values segment) { + parent.onRendered(builder); + return super.leaveMatched(segment); + } +} diff --git a/spring-data-relational/src/test/java/org/springframework/data/relational/core/sql/CapturingVisitor.java b/spring-data-relational/src/test/java/org/springframework/data/relational/core/sql/CapturingVisitor.java new file mode 100644 index 0000000000..6d9abe8e59 --- /dev/null +++ b/spring-data-relational/src/test/java/org/springframework/data/relational/core/sql/CapturingVisitor.java @@ -0,0 +1,39 @@ +/* + * 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.relational.core.sql; + +import java.util.ArrayList; +import java.util.List; + +/** + * @author Mark Paluch + */ +class CapturingVisitor implements Visitor { + + final List enter = new ArrayList<>(); + + @Override + public void enter(Visitable segment) { + enter.add(segment); + } + + @Override + public void leave(Visitable segment) { + leave.add(segment); + } + + final List leave = new ArrayList<>(); +} diff --git a/spring-data-relational/src/test/java/org/springframework/data/relational/core/sql/DeleteBuilderUnitTests.java b/spring-data-relational/src/test/java/org/springframework/data/relational/core/sql/DeleteBuilderUnitTests.java new file mode 100644 index 0000000000..4e13278fe4 --- /dev/null +++ b/spring-data-relational/src/test/java/org/springframework/data/relational/core/sql/DeleteBuilderUnitTests.java @@ -0,0 +1,48 @@ +/* + * 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.relational.core.sql; + +import static org.assertj.core.api.Assertions.*; + +import org.junit.Test; + +/** + * Unit tests for {@link DeleteBuilder}. + * + * @author Mark Paluch + */ +public class DeleteBuilderUnitTests { + + @Test // DATAJDBC-335 + public void simpleDelete() { + + DeleteBuilder builder = StatementBuilder.delete(); + + Table table = SQL.table("mytable"); + Column foo = table.column("foo"); + Column bar = table.column("bar"); + + Delete delete = builder.from(table).where(foo.isEqualTo(bar)).build(); + + CapturingVisitor visitor = new CapturingVisitor(); + delete.visit(visitor); + + assertThat(visitor.enter).containsSequence(new From(table), table, new Where(foo.isEqualTo(bar)), + foo.isEqualTo(bar), foo, table, bar, table); + + assertThat(delete.toString()).isEqualTo("DELETE FROM mytable WHERE mytable.foo = mytable.bar"); + } +} diff --git a/spring-data-relational/src/test/java/org/springframework/data/relational/core/sql/DeleteValidatorUnitTests.java b/spring-data-relational/src/test/java/org/springframework/data/relational/core/sql/DeleteValidatorUnitTests.java new file mode 100644 index 0000000000..934135963e --- /dev/null +++ b/spring-data-relational/src/test/java/org/springframework/data/relational/core/sql/DeleteValidatorUnitTests.java @@ -0,0 +1,43 @@ +/* + * 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.relational.core.sql; + +import static org.assertj.core.api.Assertions.*; + +import org.junit.Test; + +/** + * Unit tests for {@link SelectValidator}. + * + * @author Mark Paluch + */ +public class DeleteValidatorUnitTests { + + @Test // DATAJDBC-335 + public void shouldReportMissingTableForDeleteViaWhere() { + + Column column = SQL.table("table").column("foo"); + Table bar = SQL.table("bar"); + + assertThatThrownBy(() -> { + StatementBuilder.delete() // + .from(bar) // + .where(new SimpleCondition(column, "=", "foo")) // + .build(); + }).isInstanceOf(IllegalStateException.class) + .hasMessageContaining("Required table [table] by a WHERE predicate not imported by FROM [bar]"); + } +} diff --git a/spring-data-relational/src/test/java/org/springframework/data/relational/core/sql/InsertBuilderUnitTests.java b/spring-data-relational/src/test/java/org/springframework/data/relational/core/sql/InsertBuilderUnitTests.java new file mode 100644 index 0000000000..2ffc4bf8fd --- /dev/null +++ b/spring-data-relational/src/test/java/org/springframework/data/relational/core/sql/InsertBuilderUnitTests.java @@ -0,0 +1,46 @@ +/* + * 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.relational.core.sql; + +import static org.assertj.core.api.Assertions.*; + +import org.junit.Test; + +/** + * Unit tests for {@link InsertBuilder}. + * + * @author Mark Paluch + */ +public class InsertBuilderUnitTests { + + @Test // DATAJDBC-335 + public void shouldCreateSimpleInsert() { + + Table table = SQL.table("mytable"); + Column foo = table.column("foo"); + Column bar = table.column("bar"); + + Insert insert = StatementBuilder.insert().into(table).column(foo).column(bar).value(SQL.bindMarker()).build(); + + CapturingVisitor visitor = new CapturingVisitor(); + insert.visit(visitor); + + assertThat(visitor.enter).containsSequence(insert, new Into(table), table, foo, table, bar, table, + new Values(SQL.bindMarker())); + + assertThat(insert.toString()).isEqualTo("INSERT INTO mytable (mytable.foo, mytable.bar) VALUES(?)"); + } +} diff --git a/spring-data-relational/src/test/java/org/springframework/data/relational/core/sql/SelectBuilderUnitTests.java b/spring-data-relational/src/test/java/org/springframework/data/relational/core/sql/SelectBuilderUnitTests.java index 4a446310cf..bdf5f8a1c1 100644 --- a/spring-data-relational/src/test/java/org/springframework/data/relational/core/sql/SelectBuilderUnitTests.java +++ b/spring-data-relational/src/test/java/org/springframework/data/relational/core/sql/SelectBuilderUnitTests.java @@ -42,7 +42,7 @@ public void simpleSelect() { Select select = builder.select(foo, bar).from(table).build(); - CapturingSelectVisitor visitor = new CapturingSelectVisitor(); + CapturingVisitor visitor = new CapturingVisitor(); select.visit(visitor); assertThat(visitor.enter).containsSequence(foo, table, bar, table, new From(table), table); @@ -58,7 +58,7 @@ public void selectTop() { Select select = builder.top(10).select(foo).from(table).build(); - CapturingSelectVisitor visitor = new CapturingSelectVisitor(); + CapturingVisitor visitor = new CapturingVisitor(); select.visit(visitor); assertThat(visitor.enter).containsSequence(foo, table, new From(table), table); @@ -78,7 +78,7 @@ public void moreAdvancedSelect() { Select select = builder.select(foo, bar).from(table1, table2).build(); - CapturingSelectVisitor visitor = new CapturingSelectVisitor(); + CapturingVisitor visitor = new CapturingVisitor(); select.visit(visitor); assertThat(visitor.enter).containsSequence(foo, table1, bar, table2, new From(table1, table2), table1, table2); @@ -96,7 +96,7 @@ public void orderBy() { OrderByField orderByField = OrderByField.from(foo).asc(); Select select = builder.select(foo).from(table).orderBy(orderByField).build(); - CapturingSelectVisitor visitor = new CapturingSelectVisitor(); + CapturingVisitor visitor = new CapturingVisitor(); select.visit(visitor); assertThat(visitor.enter).containsSequence(foo, table, new From(table), table, orderByField, foo); @@ -118,7 +118,7 @@ public void joins() { .and(SQL.column("tenant", employee)).equals(SQL.column("tenant", department)) .orderBy(OrderByField.from(name).asc()).build(); - CapturingSelectVisitor visitor = new CapturingSelectVisitor(); + CapturingVisitor visitor = new CapturingVisitor(); select.visit(visitor); assertThat(visitor.enter).filteredOn(Join.class::isInstance).hasSize(1); @@ -131,20 +131,4 @@ public void joins() { assertThat(join.getType()).isEqualTo(JoinType.JOIN); } - static class CapturingSelectVisitor implements Visitor { - - final List enter = new ArrayList<>(); - - @Override - public void enter(Visitable segment) { - enter.add(segment); - } - - @Override - public void leave(Visitable segment) { - leave.add(segment); - } - - final List leave = new ArrayList<>(); - } } diff --git a/spring-data-relational/src/test/java/org/springframework/data/relational/core/sql/UpdateBuilderUnitTests.java b/spring-data-relational/src/test/java/org/springframework/data/relational/core/sql/UpdateBuilderUnitTests.java new file mode 100644 index 0000000000..36054eecf6 --- /dev/null +++ b/spring-data-relational/src/test/java/org/springframework/data/relational/core/sql/UpdateBuilderUnitTests.java @@ -0,0 +1,62 @@ +/* + * 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.relational.core.sql; + +import static org.assertj.core.api.Assertions.*; + +import org.junit.Test; + +/** + * Unit tests for {@link UpdateBuilder}. + * + * @author Mark Paluch + */ +public class UpdateBuilderUnitTests { + + @Test // DATAJDBC-335 + public void shouldCreateSimpleUpdate() { + + Table table = SQL.table("mytable"); + Column column = table.column("foo"); + + Update update = StatementBuilder.update(table).set(column.set(SQL.bindMarker())).build(); + + CapturingVisitor visitor = new CapturingVisitor(); + update.visit(visitor); + + assertThat(visitor.enter).containsSequence(update, table, Assignments.value(column, SQL.bindMarker()), column, + table, SQL.bindMarker()); + + assertThat(update.toString()).isEqualTo("UPDATE mytable SET mytable.foo = ?"); + } + + @Test // DATAJDBC-335 + public void shouldCreateUpdateWIthCondition() { + + Table table = SQL.table("mytable"); + Column column = table.column("foo"); + + Update update = StatementBuilder.update(table).set(column.set(SQL.bindMarker())).where(column.isNull()).build(); + + CapturingVisitor visitor = new CapturingVisitor(); + update.visit(visitor); + + assertThat(visitor.enter).containsSequence(update, table, Assignments.value(column, SQL.bindMarker()), column, + table, SQL.bindMarker(), column.isNull()); + + assertThat(update.toString()).isEqualTo("UPDATE mytable SET mytable.foo = ? WHERE mytable.foo IS NULL"); + } +} diff --git a/spring-data-relational/src/test/java/org/springframework/data/relational/core/sql/render/ConditionRendererUnitTests.java b/spring-data-relational/src/test/java/org/springframework/data/relational/core/sql/render/ConditionRendererUnitTests.java index edf3dc80e7..21b82f849b 100644 --- a/spring-data-relational/src/test/java/org/springframework/data/relational/core/sql/render/ConditionRendererUnitTests.java +++ b/spring-data-relational/src/test/java/org/springframework/data/relational/core/sql/render/ConditionRendererUnitTests.java @@ -38,7 +38,7 @@ public class ConditionRendererUnitTests { public void shouldRenderEquals() { String sql = SqlRenderer - .render(StatementBuilder.select(left).from(table).where(left.isEqualTo(right)).build()); + .toString(StatementBuilder.select(left).from(table).where(left.isEqualTo(right)).build()); assertThat(sql).endsWith("WHERE my_table.left = my_table.right"); } @@ -47,11 +47,11 @@ public void shouldRenderEquals() { public void shouldRenderNotEquals() { String sql = SqlRenderer - .render(StatementBuilder.select(left).from(table).where(left.isNotEqualTo(right)).build()); + .toString(StatementBuilder.select(left).from(table).where(left.isNotEqualTo(right)).build()); assertThat(sql).endsWith("WHERE my_table.left != my_table.right"); - sql = SqlRenderer.render(StatementBuilder.select(left).from(table).where(left.isEqualTo(right).not()).build()); + sql = SqlRenderer.toString(StatementBuilder.select(left).from(table).where(left.isEqualTo(right).not()).build()); assertThat(sql).endsWith("WHERE my_table.left != my_table.right"); } @@ -59,7 +59,7 @@ public void shouldRenderNotEquals() { @Test // DATAJDBC-309 public void shouldRenderIsLess() { - String sql = SqlRenderer.render(StatementBuilder.select(left).from(table).where(left.isLess(right)).build()); + String sql = SqlRenderer.toString(StatementBuilder.select(left).from(table).where(left.isLess(right)).build()); assertThat(sql).endsWith("WHERE my_table.left < my_table.right"); } @@ -68,7 +68,7 @@ public void shouldRenderIsLess() { public void shouldRenderIsLessOrEqualTo() { String sql = SqlRenderer - .render(StatementBuilder.select(left).from(table).where(left.isLessOrEqualTo(right)).build()); + .toString(StatementBuilder.select(left).from(table).where(left.isLessOrEqualTo(right)).build()); assertThat(sql).endsWith("WHERE my_table.left <= my_table.right"); } @@ -77,7 +77,7 @@ public void shouldRenderIsLessOrEqualTo() { public void shouldRenderIsGreater() { String sql = SqlRenderer - .render(StatementBuilder.select(left).from(table).where(left.isGreater(right)).build()); + .toString(StatementBuilder.select(left).from(table).where(left.isGreater(right)).build()); assertThat(sql).endsWith("WHERE my_table.left > my_table.right"); } @@ -86,7 +86,7 @@ public void shouldRenderIsGreater() { public void shouldRenderIsGreaterOrEqualTo() { String sql = SqlRenderer - .render(StatementBuilder.select(left).from(table).where(left.isGreaterOrEqualTo(right)).build()); + .toString(StatementBuilder.select(left).from(table).where(left.isGreaterOrEqualTo(right)).build()); assertThat(sql).endsWith("WHERE my_table.left >= my_table.right"); } @@ -94,7 +94,7 @@ public void shouldRenderIsGreaterOrEqualTo() { @Test // DATAJDBC-309 public void shouldRenderIn() { - String sql = SqlRenderer.render(StatementBuilder.select(left).from(table).where(left.in(right)).build()); + String sql = SqlRenderer.toString(StatementBuilder.select(left).from(table).where(left.in(right)).build()); assertThat(sql).endsWith("WHERE my_table.left IN (my_table.right)"); } @@ -102,7 +102,7 @@ public void shouldRenderIn() { @Test // DATAJDBC-309 public void shouldRenderLike() { - String sql = SqlRenderer.render(StatementBuilder.select(left).from(table).where(left.like(right)).build()); + String sql = SqlRenderer.toString(StatementBuilder.select(left).from(table).where(left.like(right)).build()); assertThat(sql).endsWith("WHERE my_table.left LIKE my_table.right"); } @@ -110,7 +110,7 @@ public void shouldRenderLike() { @Test // DATAJDBC-309 public void shouldRenderIsNull() { - String sql = SqlRenderer.render(StatementBuilder.select(left).from(table).where(left.isNull()).build()); + String sql = SqlRenderer.toString(StatementBuilder.select(left).from(table).where(left.isNull()).build()); assertThat(sql).endsWith("WHERE my_table.left IS NULL"); } @@ -118,11 +118,11 @@ public void shouldRenderIsNull() { @Test // DATAJDBC-309 public void shouldRenderIsNotNull() { - String sql = SqlRenderer.render(StatementBuilder.select(left).from(table).where(left.isNotNull()).build()); + String sql = SqlRenderer.toString(StatementBuilder.select(left).from(table).where(left.isNotNull()).build()); assertThat(sql).endsWith("WHERE my_table.left IS NOT NULL"); - sql = SqlRenderer.render(StatementBuilder.select(left).from(table).where(left.isNull().not()).build()); + sql = SqlRenderer.toString(StatementBuilder.select(left).from(table).where(left.isNull().not()).build()); assertThat(sql).endsWith("WHERE my_table.left IS NOT NULL"); } diff --git a/spring-data-relational/src/test/java/org/springframework/data/relational/core/sql/render/DeleteRendererUnitTests.java b/spring-data-relational/src/test/java/org/springframework/data/relational/core/sql/render/DeleteRendererUnitTests.java new file mode 100644 index 0000000000..ddaf1b0915 --- /dev/null +++ b/spring-data-relational/src/test/java/org/springframework/data/relational/core/sql/render/DeleteRendererUnitTests.java @@ -0,0 +1,63 @@ +/* + * 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.relational.core.sql.render; + +import static org.assertj.core.api.Assertions.*; + +import org.junit.Test; + +import org.springframework.data.relational.core.sql.Delete; +import org.springframework.data.relational.core.sql.SQL; +import org.springframework.data.relational.core.sql.Table; + +/** + * Unit tests for {@link SqlRenderer}. + * + * @author Mark Paluch + */ +public class DeleteRendererUnitTests { + + @Test // DATAJDBC-335 + public void shouldRenderWithoutWhere() { + + Table bar = SQL.table("bar"); + + Delete delete = Delete.builder().from(bar).build(); + + assertThat(SqlRenderer.toString(delete)).isEqualTo("DELETE FROM bar"); + } + + @Test // DATAJDBC-335 + public void shouldRenderWithCondition() { + + Table table = Table.create("bar"); + + Delete delete = Delete.builder().from(table).where(table.column("foo").isEqualTo(table.column("baz"))) + .and(table.column("doe").isNull()).build(); + + assertThat(SqlRenderer.toString(delete)).isEqualTo("DELETE FROM bar WHERE bar.foo = bar.baz AND bar.doe IS NULL"); + } + + @Test // DATAJDBC-335 + public void shouldConsiderTableAlias() { + + Table table = Table.create("bar").as("my_bar"); + + Delete delete = Delete.builder().from(table).where(table.column("foo").isEqualTo(table.column("baz"))).build(); + + assertThat(SqlRenderer.toString(delete)).isEqualTo("DELETE FROM bar AS my_bar WHERE my_bar.foo = my_bar.baz"); + } +} 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 new file mode 100644 index 0000000000..1b7c14d78c --- /dev/null +++ b/spring-data-relational/src/test/java/org/springframework/data/relational/core/sql/render/InsertRendererUnitTests.java @@ -0,0 +1,63 @@ +/* + * 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.relational.core.sql.render; + +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; + +/** + * Unit tests for {@link SqlRenderer}. + * + * @author Mark Paluch + */ +public class InsertRendererUnitTests { + + @Test // DATAJDBC-335 + public void shouldRenderInsert() { + + Table bar = SQL.table("bar"); + + Insert insert = Insert.builder().into(bar).values(SQL.bindMarker()).build(); + + assertThat(SqlRenderer.toString(insert)).isEqualTo("INSERT INTO bar VALUES(?)"); + } + + @Test // DATAJDBC-335 + public void shouldRenderInsertColumn() { + + Table bar = SQL.table("bar"); + + Insert insert = Insert.builder().into(bar).column(bar.column("foo")).values(SQL.bindMarker()).build(); + + assertThat(SqlRenderer.toString(insert)).isEqualTo("INSERT INTO bar (bar.foo) VALUES(?)"); + } + + @Test // DATAJDBC-335 + public void shouldRenderInsertMultipleColumns() { + + Table bar = SQL.table("bar"); + + 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 (bar.foo, bar.baz) VALUES(?, 'foo')"); + } +} diff --git a/spring-data-relational/src/test/java/org/springframework/data/relational/core/sql/render/SqlRendererUnitTests.java b/spring-data-relational/src/test/java/org/springframework/data/relational/core/sql/render/SelectRendererUnitTests.java similarity index 80% rename from spring-data-relational/src/test/java/org/springframework/data/relational/core/sql/render/SqlRendererUnitTests.java rename to spring-data-relational/src/test/java/org/springframework/data/relational/core/sql/render/SelectRendererUnitTests.java index 42920236b5..c38fd26067 100644 --- a/spring-data-relational/src/test/java/org/springframework/data/relational/core/sql/render/SqlRendererUnitTests.java +++ b/spring-data-relational/src/test/java/org/springframework/data/relational/core/sql/render/SelectRendererUnitTests.java @@ -34,7 +34,7 @@ * @author Mark Paluch * @author Jens Schauder */ -public class SqlRendererUnitTests { +public class SelectRendererUnitTests { @Test // DATAJDBC-309 public void shouldRenderSingleColumn() { @@ -44,7 +44,7 @@ public void shouldRenderSingleColumn() { Select select = Select.builder().select(foo).from(bar).build(); - assertThat(SqlRenderer.render(select)).isEqualTo("SELECT bar.foo FROM bar"); + assertThat(SqlRenderer.toString(select)).isEqualTo("SELECT bar.foo FROM bar"); } @Test // DATAJDBC-309 @@ -54,7 +54,7 @@ public void shouldRenderAliasedColumnAndFrom() { Select select = Select.builder().select(table.column("foo").as("my_foo")).from(table).build(); - assertThat(SqlRenderer.render(select)).isEqualTo("SELECT my_bar.foo AS my_foo FROM bar AS my_bar"); + assertThat(SqlRenderer.toString(select)).isEqualTo("SELECT my_bar.foo AS my_foo FROM bar AS my_bar"); } @Test // DATAJDBC-309 @@ -66,7 +66,7 @@ public void shouldRenderMultipleColumnsFromTables() { Select select = Select.builder().select(table1.column("col1")).select(table2.column("col2")).from(table1) .from(table2).build(); - assertThat(SqlRenderer.render(select)).isEqualTo("SELECT table1.col1, table2.col2 FROM table1, table2"); + assertThat(SqlRenderer.toString(select)).isEqualTo("SELECT table1.col1, table2.col2 FROM table1, table2"); } @Test // DATAJDBC-309 @@ -78,7 +78,7 @@ public void shouldRenderDistinct() { Select select = Select.builder().distinct().select(foo, bar).from(table).build(); - assertThat(SqlRenderer.render(select)).isEqualTo("SELECT DISTINCT bar.foo, bar.bar FROM bar"); + assertThat(SqlRenderer.toString(select)).isEqualTo("SELECT DISTINCT bar.foo, bar.bar FROM bar"); } @Test // DATAJDBC-309 @@ -90,7 +90,7 @@ public void shouldRenderCountFunction() { Select select = Select.builder().select(Functions.count(foo), bar).from(table).build(); - assertThat(SqlRenderer.render(select)).isEqualTo("SELECT COUNT(bar.foo), bar.bar FROM bar"); + assertThat(SqlRenderer.toString(select)).isEqualTo("SELECT COUNT(bar.foo), bar.bar FROM bar"); } @Test // DATAJDBC-309 @@ -103,7 +103,7 @@ public void shouldRenderSimpleJoin() { .join(department).on(employee.column("department_id")).equals(department.column("id")) // .build(); - assertThat(SqlRenderer.render(select)).isEqualTo("SELECT employee.id, department.name FROM employee " + assertThat(SqlRenderer.toString(select)).isEqualTo("SELECT employee.id, department.name FROM employee " + "JOIN department ON employee.department_id = department.id"); } @@ -118,7 +118,7 @@ public void shouldRenderSimpleJoinWithAnd() { .and(employee.column("tenant")).equals(department.column("tenant")) // .build(); - assertThat(SqlRenderer.render(select)).isEqualTo("SELECT employee.id, department.name FROM employee " + assertThat(SqlRenderer.toString(select)).isEqualTo("SELECT employee.id, department.name FROM employee " + "JOIN department ON employee.department_id = department.id " + "AND employee.tenant = department.tenant"); } @@ -135,7 +135,7 @@ public void shouldRenderMultipleJoinWithAnd() { .join(tenant).on(tenant.column("tenant_id")).equals(department.column("tenant")) // .build(); - assertThat(SqlRenderer.render(select)).isEqualTo("SELECT employee.id, department.name FROM employee " + assertThat(SqlRenderer.toString(select)).isEqualTo("SELECT employee.id, department.name FROM employee " + "JOIN department ON employee.department_id = department.id " + "AND employee.tenant = department.tenant " + "JOIN tenant AS tenant_base ON tenant_base.tenant_id = department.tenant"); } @@ -148,7 +148,7 @@ public void shouldRenderOrderByName() { Select select = Select.builder().select(column).from(employee).orderBy(OrderByField.from(column).asc()).build(); - assertThat(SqlRenderer.render(select)) + assertThat(SqlRenderer.toString(select)) .isEqualTo("SELECT emp.name AS emp_name FROM employee AS emp ORDER BY emp_name ASC"); } @@ -160,7 +160,7 @@ public void shouldRenderOrderLimitOffset() { Select select = Select.builder().select(bar).from("foo").limitOffset(10, 20).build(); - assertThat(SqlRenderer.render(select)).isEqualTo("SELECT foo.bar FROM foo LIMIT 10 OFFSET 20"); + assertThat(SqlRenderer.toString(select)).isEqualTo("SELECT foo.bar FROM foo LIMIT 10 OFFSET 20"); } @Test // DATAJDBC-309 @@ -171,7 +171,7 @@ public void shouldRenderIsNull() { Select select = Select.builder().select(bar).from(table).where(Conditions.isNull(bar)).build(); - assertThat(SqlRenderer.render(select)).isEqualTo("SELECT foo.bar FROM foo WHERE foo.bar IS NULL"); + assertThat(SqlRenderer.toString(select)).isEqualTo("SELECT foo.bar FROM foo WHERE foo.bar IS NULL"); } @Test // DATAJDBC-309 @@ -182,7 +182,7 @@ public void shouldRenderNotNull() { Select select = Select.builder().select(bar).from(table).where(Conditions.isNull(bar).not()).build(); - assertThat(SqlRenderer.render(select)).isEqualTo("SELECT foo.bar FROM foo WHERE foo.bar IS NOT NULL"); + assertThat(SqlRenderer.toString(select)).isEqualTo("SELECT foo.bar FROM foo WHERE foo.bar IS NOT NULL"); } @Test // DATAJDBC-309 @@ -194,7 +194,7 @@ public void shouldRenderEqualityCondition() { Select select = Select.builder().select(bar).from(table).where(Conditions.isEqual(bar, SQL.bindMarker(":name"))) .build(); - assertThat(SqlRenderer.render(select)).isEqualTo("SELECT foo.bar FROM foo WHERE foo.bar = :name"); + assertThat(SqlRenderer.toString(select)).isEqualTo("SELECT foo.bar FROM foo WHERE foo.bar = :name"); } @Test // DATAJDBC-309 @@ -207,7 +207,7 @@ public void shouldRendersAndOrConditionWithProperParentheses() { Select select = Select.builder().select(bar).from(table).where(Conditions.isEqual(bar, SQL.bindMarker(":name")) .or(Conditions.isEqual(bar, SQL.bindMarker(":name2"))).and(Conditions.isNull(baz))).build(); - assertThat(SqlRenderer.render(select)) + assertThat(SqlRenderer.toString(select)) .isEqualTo("SELECT foo.bar FROM foo WHERE foo.bar = :name OR foo.bar = :name2 AND foo.baz IS NULL"); } @@ -219,7 +219,7 @@ public void shouldInWithNamedParameter() { Select select = Select.builder().select(bar).from(table).where(Conditions.in(bar, SQL.bindMarker(":name"))).build(); - assertThat(SqlRenderer.render(select)).isEqualTo("SELECT foo.bar FROM foo WHERE foo.bar IN (:name)"); + assertThat(SqlRenderer.toString(select)).isEqualTo("SELECT foo.bar FROM foo WHERE foo.bar IN (:name)"); } @Test // DATAJDBC-309 @@ -231,7 +231,7 @@ public void shouldInWithNamedParameters() { Select select = Select.builder().select(bar).from(table) .where(Conditions.in(bar, SQL.bindMarker(":name"), SQL.bindMarker(":name2"))).build(); - assertThat(SqlRenderer.render(select)).isEqualTo("SELECT foo.bar FROM foo WHERE foo.bar IN (:name, :name2)"); + assertThat(SqlRenderer.toString(select)).isEqualTo("SELECT foo.bar FROM foo WHERE foo.bar IN (:name, :name2)"); } @Test // DATAJDBC-309 @@ -247,7 +247,7 @@ public void shouldRenderInSubselect() { Select select = Select.builder().select(bar).from(foo).where(Conditions.in(bar, subselect)).build(); - assertThat(SqlRenderer.render(select)) + assertThat(SqlRenderer.toString(select)) .isEqualTo("SELECT foo.bar FROM foo WHERE foo.bar IN (SELECT floo.bah FROM floo)"); } @@ -260,14 +260,14 @@ public void shouldConsiderNamingStrategy() { Select select = Select.builder().select(bar).from(foo).where(bar.isEqualTo(baz)).build(); - String upper = SqlRenderer.create(select, new SimpleRenderContext(NamingStrategies.toUpper())).render(); + String upper = SqlRenderer.create(new SimpleRenderContext(NamingStrategies.toUpper())).render(select); assertThat(upper).isEqualTo("SELECT FOO.BAR FROM FOO WHERE FOO.BAR = FOO.BAZ"); - String lower = SqlRenderer.create(select, new SimpleRenderContext(NamingStrategies.toLower())).render(); + String lower = SqlRenderer.create(new SimpleRenderContext(NamingStrategies.toLower())).render(select); assertThat(lower).isEqualTo("SELECT foo.bar FROM foo WHERE foo.bar = foo.baz"); - String mapped = SqlRenderer - .create(select, new SimpleRenderContext(NamingStrategies.mapWith(StringUtils::uncapitalize))).render(); + String mapped = SqlRenderer.create(new SimpleRenderContext(NamingStrategies.mapWith(StringUtils::uncapitalize))) + .render(select); assertThat(mapped).isEqualTo("SELECT foo.baR FROM foo WHERE foo.baR = foo.baZ"); } diff --git a/spring-data-relational/src/test/java/org/springframework/data/relational/core/sql/render/UpdateRendererUnitTests.java b/spring-data-relational/src/test/java/org/springframework/data/relational/core/sql/render/UpdateRendererUnitTests.java new file mode 100644 index 0000000000..3f054475ea --- /dev/null +++ b/spring-data-relational/src/test/java/org/springframework/data/relational/core/sql/render/UpdateRendererUnitTests.java @@ -0,0 +1,67 @@ +/* + * 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.relational.core.sql.render; + +import static org.assertj.core.api.Assertions.*; + +import org.junit.Test; + +import org.springframework.data.relational.core.sql.Column; +import org.springframework.data.relational.core.sql.SQL; +import org.springframework.data.relational.core.sql.StatementBuilder; +import org.springframework.data.relational.core.sql.Table; +import org.springframework.data.relational.core.sql.Update; + +/** + * Unit tests for {@link SqlRenderer}. + * + * @author Mark Paluch + */ +public class UpdateRendererUnitTests { + + @Test // DATAJDBC-335 + public void shouldRenderSimpleUpdate() { + + Table table = SQL.table("mytable"); + Column column = table.column("foo"); + + Update update = StatementBuilder.update(table).set(column.set(SQL.bindMarker())).build(); + + assertThat(SqlRenderer.toString(update)).isEqualTo("UPDATE mytable SET mytable.foo = ?"); + } + + @Test // DATAJDBC-335 + public void shouldRenderUpdateWithLiteral() { + + Table table = SQL.table("mytable"); + Column column = table.column("foo"); + + Update update = StatementBuilder.update(table).set(column.set(SQL.literalOf(20))).build(); + + assertThat(SqlRenderer.toString(update)).isEqualTo("UPDATE mytable SET mytable.foo = 20"); + } + + @Test // DATAJDBC-335 + public void shouldCreateUpdateWIthCondition() { + + Table table = SQL.table("mytable"); + Column column = table.column("foo"); + + Update update = StatementBuilder.update(table).set(column.set(SQL.bindMarker())).where(column.isNull()).build(); + + assertThat(SqlRenderer.toString(update)).isEqualTo("UPDATE mytable SET mytable.foo = ? WHERE mytable.foo IS NULL"); + } +} From 098ef0315c790f2d0f5e310e9adba85350d39ead Mon Sep 17 00:00:00 2001 From: Jens Schauder Date: Mon, 4 Mar 2019 14:49:02 +0100 Subject: [PATCH 3/4] DATAJDBC-335 - Polishing. Formatting and one additional test. --- .../core/sql/render/DeleteRendererUnitTests.java | 8 +++++--- .../core/sql/render/SelectRendererUnitTests.java | 5 +++-- .../core/sql/render/UpdateRendererUnitTests.java | 15 +++++++++++++++ 3 files changed, 23 insertions(+), 5 deletions(-) diff --git a/spring-data-relational/src/test/java/org/springframework/data/relational/core/sql/render/DeleteRendererUnitTests.java b/spring-data-relational/src/test/java/org/springframework/data/relational/core/sql/render/DeleteRendererUnitTests.java index ddaf1b0915..54926caa96 100644 --- a/spring-data-relational/src/test/java/org/springframework/data/relational/core/sql/render/DeleteRendererUnitTests.java +++ b/spring-data-relational/src/test/java/org/springframework/data/relational/core/sql/render/DeleteRendererUnitTests.java @@ -18,7 +18,6 @@ import static org.assertj.core.api.Assertions.*; import org.junit.Test; - import org.springframework.data.relational.core.sql.Delete; import org.springframework.data.relational.core.sql.SQL; import org.springframework.data.relational.core.sql.Table; @@ -45,7 +44,8 @@ public void shouldRenderWithCondition() { Table table = Table.create("bar"); - Delete delete = Delete.builder().from(table).where(table.column("foo").isEqualTo(table.column("baz"))) + Delete delete = Delete.builder().from(table) // + .where(table.column("foo").isEqualTo(table.column("baz"))) // .and(table.column("doe").isNull()).build(); assertThat(SqlRenderer.toString(delete)).isEqualTo("DELETE FROM bar WHERE bar.foo = bar.baz AND bar.doe IS NULL"); @@ -56,7 +56,9 @@ public void shouldConsiderTableAlias() { Table table = Table.create("bar").as("my_bar"); - Delete delete = Delete.builder().from(table).where(table.column("foo").isEqualTo(table.column("baz"))).build(); + Delete delete = Delete.builder().from(table) // + .where(table.column("foo").isEqualTo(table.column("baz"))) // + .build(); assertThat(SqlRenderer.toString(delete)).isEqualTo("DELETE FROM bar AS my_bar WHERE my_bar.foo = my_bar.baz"); } 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 c38fd26067..cf161f5ae3 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 @@ -118,8 +118,9 @@ public void shouldRenderSimpleJoinWithAnd() { .and(employee.column("tenant")).equals(department.column("tenant")) // .build(); - assertThat(SqlRenderer.toString(select)).isEqualTo("SELECT employee.id, department.name FROM employee " - + "JOIN department ON employee.department_id = department.id " + "AND employee.tenant = department.tenant"); + assertThat(SqlRenderer.toString(select)).isEqualTo("SELECT employee.id, department.name FROM employee " // + + "JOIN department ON employee.department_id = department.id " // + + "AND employee.tenant = department.tenant"); } @Test // DATAJDBC-309 diff --git a/spring-data-relational/src/test/java/org/springframework/data/relational/core/sql/render/UpdateRendererUnitTests.java b/spring-data-relational/src/test/java/org/springframework/data/relational/core/sql/render/UpdateRendererUnitTests.java index 3f054475ea..7a05f4caaf 100644 --- a/spring-data-relational/src/test/java/org/springframework/data/relational/core/sql/render/UpdateRendererUnitTests.java +++ b/spring-data-relational/src/test/java/org/springframework/data/relational/core/sql/render/UpdateRendererUnitTests.java @@ -43,6 +43,21 @@ public void shouldRenderSimpleUpdate() { assertThat(SqlRenderer.toString(update)).isEqualTo("UPDATE mytable SET mytable.foo = ?"); } + @Test // DATAJDBC-335 + public void shouldRenderMultipleColumnUpdate() { + + Table table = SQL.table("mytable"); + Column foo = table.column("foo"); + Column bar = table.column("bar"); + + Update update = StatementBuilder.update(table) // + .set(foo.set(SQL.bindMarker())) // + .and(bar.set(SQL.bindMarker())) // + .build(); + + assertThat(SqlRenderer.toString(update)).isEqualTo("UPDATE mytable SET mytable.foo = ?, mytable.bar = ?"); + } + @Test // DATAJDBC-335 public void shouldRenderUpdateWithLiteral() { From 33472b73efae065b8aa702b566a0e94ce3e0c5a6 Mon Sep 17 00:00:00 2001 From: Jens Schauder Date: Mon, 4 Mar 2019 14:51:37 +0100 Subject: [PATCH 4/4] DATAJDBC-335 - Address review comments. --- .../core/sql/AbstractImportValidator.java | 51 ++++++++++++++++--- .../data/relational/core/sql/AssignValue.java | 4 +- .../relational/core/sql/DefaultUpdate.java | 8 +-- .../core/sql/DefaultUpdateBuilder.java | 42 ++++++++++----- .../relational/core/sql/DeleteValidator.java | 10 +++- .../relational/core/sql/SelectValidator.java | 37 +++++++++++--- .../relational/core/sql/UpdateBuilder.java | 23 +++++---- .../core/sql/DeleteValidatorUnitTests.java | 16 +++++- .../core/sql/SelectValidatorUnitTests.java | 18 +++++++ .../core/sql/UpdateBuilderUnitTests.java | 2 +- .../sql/render/UpdateRendererUnitTests.java | 3 +- 11 files changed, 168 insertions(+), 46 deletions(-) diff --git a/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/AbstractImportValidator.java b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/AbstractImportValidator.java index 1a56627a59..12c41a7d88 100644 --- a/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/AbstractImportValidator.java +++ b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/AbstractImportValidator.java @@ -18,6 +18,8 @@ import java.util.HashSet; import java.util.Set; +import org.springframework.lang.Nullable; + /** * Validator for statements to import columns. * @@ -42,13 +44,7 @@ public void enter(Visitable segment) { } if (segment instanceof Where) { - - segment.visit(item -> { - - if (item instanceof Table) { - requiredByWhere.add((Table) item); - } - }); + segment.visit(new SubselectFilteringWhereVisitor()); } if (segment instanceof Join || segment instanceof OrderByField || segment instanceof From @@ -63,4 +59,45 @@ public void enter(Visitable segment) { */ @Override public void leave(Visitable segment) {} + + /** + * {@link Visitor} that skips sub-{@link Select} and collects columns within a {@link Where} clause. + */ + class SubselectFilteringWhereVisitor implements Visitor { + + private @Nullable Select selectFilter; + + /* + * (non-Javadoc) + * @see org.springframework.data.relational.core.sql.Visitor#enter(org.springframework.data.relational.core.sql.Visitable) + */ + @Override + public void enter(Visitable segment) { + + if (selectFilter != null) { + return; + } + + if (segment instanceof Select) { + this.selectFilter = (Select) segment; + return; + } + + if (segment instanceof Table) { + requiredByWhere.add((Table) segment); + } + } + + /* + * (non-Javadoc) + * @see org.springframework.data.relational.core.sql.Visitor#leave(org.springframework.data.relational.core.sql.Visitable) + */ + @Override + public void leave(Visitable segment) { + + if (this.selectFilter == segment) { + this.selectFilter = null; + } + } + } } diff --git a/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/AssignValue.java b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/AssignValue.java index 8ded7e5309..8304bf56ad 100644 --- a/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/AssignValue.java +++ b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/AssignValue.java @@ -65,14 +65,14 @@ public Expression getValue() { return value; } - /* + /* * (non-Javadoc) * @see java.lang.Object#toString() */ @Override public String toString() { - StringBuilder builder = new StringBuilder(32); + StringBuilder builder = new StringBuilder(); return builder.append(this.column).append(" = ").append(this.value).toString(); } } diff --git a/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/DefaultUpdate.java b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/DefaultUpdate.java index 74ba30f2e6..135fdffded 100644 --- a/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/DefaultUpdate.java +++ b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/DefaultUpdate.java @@ -40,7 +40,7 @@ class DefaultUpdate implements Update { this.where = where != null ? new Where(where) : null; } - /* + /* * (non-Javadoc) * @see org.springframework.data.relational.core.sql.Visitable#visit(org.springframework.data.relational.core.sql.Visitor) */ @@ -61,14 +61,14 @@ public void visit(Visitor visitor) { visitor.leave(this); } - /* + /* * (non-Javadoc) * @see java.lang.Object#toString() */ @Override public String toString() { - StringBuilder builder = new StringBuilder(32); + StringBuilder builder = new StringBuilder(); builder.append("UPDATE ").append(table); if (!assignments.isEmpty()) { @@ -76,7 +76,7 @@ public String toString() { } if (this.where != null) { - builder.append(" WHERE ").append(this.where); + builder.append(" ").append(this.where); } return builder.toString(); diff --git a/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/DefaultUpdateBuilder.java b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/DefaultUpdateBuilder.java index 8418765cf0..8fcbb3c333 100644 --- a/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/DefaultUpdateBuilder.java +++ b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/DefaultUpdateBuilder.java @@ -16,10 +16,11 @@ package org.springframework.data.relational.core.sql; import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; import java.util.List; import org.springframework.data.relational.core.sql.UpdateBuilder.UpdateAssign; -import org.springframework.data.relational.core.sql.UpdateBuilder.UpdateAssignAnd; import org.springframework.data.relational.core.sql.UpdateBuilder.UpdateWhere; import org.springframework.data.relational.core.sql.UpdateBuilder.UpdateWhereAndOr; import org.springframework.lang.Nullable; @@ -31,13 +32,13 @@ * @author Mark Paluch * @since 1.1 */ -class DefaultUpdateBuilder implements UpdateBuilder, UpdateWhere, UpdateWhereAndOr, UpdateAssign, UpdateAssignAnd { +class DefaultUpdateBuilder implements UpdateBuilder, UpdateWhere, UpdateWhereAndOr, UpdateAssign { private Table table; private List assignments = new ArrayList<>(); private @Nullable Condition where; - /* + /* * (non-Javadoc) * @see org.springframework.data.relational.core.sql.UpdateBuilder#table(org.springframework.data.relational.core.sql.Table) */ @@ -51,7 +52,7 @@ public UpdateAssign table(Table table) { return this; } - /* + /* * (non-Javadoc) * @see org.springframework.data.relational.core.sql.UpdateBuilder.UpdateAssign#set(org.springframework.data.relational.core.sql.Assignment) */ @@ -65,16 +66,33 @@ public DefaultUpdateBuilder set(Assignment assignment) { return this; } - /* + /* * (non-Javadoc) - * @see org.springframework.data.relational.core.sql.UpdateBuilder.UpdateAssignAnd#and(org.springframework.data.relational.core.sql.Assignment) + * @see org.springframework.data.relational.core.sql.UpdateBuilder.UpdateAssign#set(org.springframework.data.relational.core.sql.Assignment...) */ @Override - public DefaultUpdateBuilder and(Assignment assignment) { - return set(assignment); + public UpdateWhere set(Assignment... assignments) { + + Assert.notNull(assignments, "Assignment must not be null!"); + + return set(Arrays.asList(assignments)); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.relational.core.sql.UpdateBuilder.UpdateAssign#set(java.util.Collection) + */ + @Override + public UpdateWhere set(Collection assignments) { + + Assert.notNull(assignments, "Assignment must not be null!"); + + this.assignments.addAll(assignments); + + return this; } - /* + /* * (non-Javadoc) * @see org.springframework.data.relational.core.sql.UpdateBuilder.UpdateWhere#where(org.springframework.data.relational.core.sql.Condition) */ @@ -88,7 +106,7 @@ public UpdateWhereAndOr where(Condition condition) { return this; } - /* + /* * (non-Javadoc) * @see org.springframework.data.relational.core.sql.UpdateBuilder.UpdateWhereAndOr#and(org.springframework.data.relational.core.sql.Condition) */ @@ -102,7 +120,7 @@ public UpdateWhereAndOr and(Condition condition) { return this; } - /* + /* * (non-Javadoc) * @see org.springframework.data.relational.core.sql.UpdateBuilder.UpdateWhereAndOr#or(org.springframework.data.relational.core.sql.Condition) */ @@ -116,7 +134,7 @@ public UpdateWhereAndOr or(Condition condition) { return this; } - /* + /* * (non-Javadoc) * @see org.springframework.data.relational.core.sql.UpdateBuilder.BuildUpdate#build() */ diff --git a/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/DeleteValidator.java b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/DeleteValidator.java index bf07e0e2fb..51e327727c 100644 --- a/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/DeleteValidator.java +++ b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/DeleteValidator.java @@ -25,8 +25,14 @@ */ class DeleteValidator extends AbstractImportValidator { - public static void validate(Delete select) { - new DeleteValidator().doValidate(select); + /** + * Validates a {@link Delete} statement. + * + * @param delete the {@link Delete} statement. + * @throws IllegalStateException if the statement is not valid. + */ + public static void validate(Delete delete) { + new DeleteValidator().doValidate(delete); } private void doValidate(Delete select) { 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 d8254c9374..5a199bf32b 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 @@ -17,6 +17,7 @@ import java.util.HashSet; import java.util.Set; +import java.util.Stack; /** * Validator for {@link Select} statements. @@ -29,12 +30,20 @@ */ class SelectValidator extends AbstractImportValidator { + private final Stack
requiredBySelect = new HashSet<>(); private Set
requiredByOrderBy = new HashSet<>(); private Set
join = new HashSet<>(); + /** + * Validates a {@link Select} statement. + * + * @param select the {@link Select} statement. + * @throws IllegalStateException if the statement is not valid. + */ public static void validate(Select select) { new SelectValidator().doValidate(select); } @@ -76,6 +85,14 @@ private void doValidate(Select select) { @Override public void enter(Visitable segment) { + if (segment instanceof Select) { + selects.push((Select) segment); + } + + if (selects.size() > 1) { + return; + } + super.enter(segment); if (segment instanceof AsteriskFromTable && parent instanceof Select) { @@ -107,15 +124,23 @@ public void enter(Visitable segment) { if (segment instanceof Table && parent instanceof Join) { join.add((Table) segment); } + } - if (segment instanceof Where) { + /* + * (non-Javadoc) + * @see org.springframework.data.relational.core.sql.AbstractImportValidator#leave(org.springframework.data.relational.core.sql.Visitable) + */ + @Override + public void leave(Visitable segment) { - segment.visit(item -> { + if (segment instanceof Select) { + selects.remove(segment); + } - if (item instanceof Table) { - requiredByWhere.add((Table) item); - } - }); + if (selects.size() > 1) { + return; } + + super.leave(segment); } } diff --git a/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/UpdateBuilder.java b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/UpdateBuilder.java index 5588741898..c2b0674e32 100644 --- a/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/UpdateBuilder.java +++ b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/UpdateBuilder.java @@ -15,6 +15,8 @@ */ package org.springframework.data.relational.core.sql; +import java.util.Collection; + /** * Entry point to construct an {@link Update} statement. * @@ -44,22 +46,25 @@ interface UpdateAssign { * @return {@code this} builder. * @see Assignment */ - UpdateAssignAnd set(Assignment assignment); - } + UpdateWhere set(Assignment assignment); - /** - * Interface exposing {@code SET} methods. - */ - interface UpdateAssignAnd extends UpdateWhere { + /** + * Apply one or more {@link Assignment SET assignments}. + * + * @param assignments the {@link Assignment column assignments}. + * @return {@code this} builder. + * @see Assignment + */ + UpdateWhere set(Assignment... assignments); /** - * Apply a {@link Assignment SET assignment}. + * Apply one or more {@link Assignment SET assignments}. * - * @param assignment a single {@link Assignment column assignment}. + * @param assignments the {@link Assignment column assignments}. * @return {@code this} builder. * @see Assignment */ - UpdateAssignAnd and(Assignment assignment); + UpdateWhere set(Collection assignments); } /** diff --git a/spring-data-relational/src/test/java/org/springframework/data/relational/core/sql/DeleteValidatorUnitTests.java b/spring-data-relational/src/test/java/org/springframework/data/relational/core/sql/DeleteValidatorUnitTests.java index 934135963e..883a782245 100644 --- a/spring-data-relational/src/test/java/org/springframework/data/relational/core/sql/DeleteValidatorUnitTests.java +++ b/spring-data-relational/src/test/java/org/springframework/data/relational/core/sql/DeleteValidatorUnitTests.java @@ -20,7 +20,7 @@ import org.junit.Test; /** - * Unit tests for {@link SelectValidator}. + * Unit tests for {@link DeleteValidator}. * * @author Mark Paluch */ @@ -40,4 +40,18 @@ public void shouldReportMissingTableForDeleteViaWhere() { }).isInstanceOf(IllegalStateException.class) .hasMessageContaining("Required table [table] by a WHERE predicate not imported by FROM [bar]"); } + + @Test // DATAJDBC-335 + public void shouldIgnoreImportsFromSubselectsInWhereClause() { + + Table foo = SQL.table("foo"); + Column bar = foo.column("bar"); + + Table floo = SQL.table("floo"); + Column bah = floo.column("bah"); + + Select subselect = Select.builder().select(bah).from(floo).build(); + + assertThat(Delete.builder().from(foo).where(Conditions.in(bar, subselect)).build()).isNotNull(); + } } diff --git a/spring-data-relational/src/test/java/org/springframework/data/relational/core/sql/SelectValidatorUnitTests.java b/spring-data-relational/src/test/java/org/springframework/data/relational/core/sql/SelectValidatorUnitTests.java index d30525bb58..273a190779 100644 --- a/spring-data-relational/src/test/java/org/springframework/data/relational/core/sql/SelectValidatorUnitTests.java +++ b/spring-data-relational/src/test/java/org/springframework/data/relational/core/sql/SelectValidatorUnitTests.java @@ -88,4 +88,22 @@ public void shouldReportMissingTableViaWhere() { }).isInstanceOf(IllegalStateException.class) .hasMessageContaining("Required table [table] by a WHERE predicate not imported by FROM [bar] or JOIN []"); } + + @Test // DATAJDBC-309 + public void shouldIgnoreImportsFromSubselectsInWhereClause() { + + Table foo = SQL.table("foo"); + Column bar = foo.column("bar"); + + Table floo = SQL.table("floo"); + Column bah = floo.column("bah"); + + Select subselect = Select.builder().select(bah).from(floo).build(); + + assertThatThrownBy(() -> { + Select.builder().select(bah).from(foo).where(Conditions.in(bar, subselect)).build(); + }).isInstanceOf(IllegalStateException.class) + .hasMessageContaining("Required table [floo] by a SELECT column not imported by FROM [foo] or JOIN []"); + } + } diff --git a/spring-data-relational/src/test/java/org/springframework/data/relational/core/sql/UpdateBuilderUnitTests.java b/spring-data-relational/src/test/java/org/springframework/data/relational/core/sql/UpdateBuilderUnitTests.java index 36054eecf6..b59a5a1b81 100644 --- a/spring-data-relational/src/test/java/org/springframework/data/relational/core/sql/UpdateBuilderUnitTests.java +++ b/spring-data-relational/src/test/java/org/springframework/data/relational/core/sql/UpdateBuilderUnitTests.java @@ -55,7 +55,7 @@ public void shouldCreateUpdateWIthCondition() { update.visit(visitor); assertThat(visitor.enter).containsSequence(update, table, Assignments.value(column, SQL.bindMarker()), column, - table, SQL.bindMarker(), column.isNull()); + table, SQL.bindMarker(), new Where(column.isNull())); assertThat(update.toString()).isEqualTo("UPDATE mytable SET mytable.foo = ? WHERE mytable.foo IS NULL"); } diff --git a/spring-data-relational/src/test/java/org/springframework/data/relational/core/sql/render/UpdateRendererUnitTests.java b/spring-data-relational/src/test/java/org/springframework/data/relational/core/sql/render/UpdateRendererUnitTests.java index 7a05f4caaf..1f51f92652 100644 --- a/spring-data-relational/src/test/java/org/springframework/data/relational/core/sql/render/UpdateRendererUnitTests.java +++ b/spring-data-relational/src/test/java/org/springframework/data/relational/core/sql/render/UpdateRendererUnitTests.java @@ -51,8 +51,7 @@ public void shouldRenderMultipleColumnUpdate() { Column bar = table.column("bar"); Update update = StatementBuilder.update(table) // - .set(foo.set(SQL.bindMarker())) // - .and(bar.set(SQL.bindMarker())) // + .set(foo.set(SQL.bindMarker()), bar.set(SQL.bindMarker())) // .build(); assertThat(SqlRenderer.toString(update)).isEqualTo("UPDATE mytable SET mytable.foo = ?, mytable.bar = ?");