diff --git a/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/AbstractSegment.java b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/AbstractSegment.java
index 490da7b1..33070905 100644
--- a/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/AbstractSegment.java
+++ b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/AbstractSegment.java
@@ -21,15 +21,20 @@ import org.springframework.util.Assert;
* Abstract implementation to support {@link Segment} implementations.
*
* @author Mark Paluch
+ * @since 1.1
*/
abstract class AbstractSegment implements Segment {
private final Segment[] children;
- protected AbstractSegment(Segment ... children) {
+ protected AbstractSegment(Segment... children) {
this.children = children;
}
+ /*
+ * (non-Javadoc)
+ * @see org.springframework.data.relational.core.sql.Visitable#visit(org.springframework.data.relational.core.sql.Visitor)
+ */
@Override
public void visit(Visitor visitor) {
diff --git a/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/Aliased.java b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/Aliased.java
index 07826bba..829177fa 100644
--- a/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/Aliased.java
+++ b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/Aliased.java
@@ -19,6 +19,7 @@ package org.springframework.data.relational.core.sql;
* Aliased element exposing an {@link #getAlias() alias}.
*
* @author Mark Paluch
+ * @since 1.1
*/
public interface Aliased {
diff --git a/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/AliasedExpression.java b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/AliasedExpression.java
index 6d2b48a8..1c926741 100644
--- a/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/AliasedExpression.java
+++ b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/AliasedExpression.java
@@ -19,8 +19,9 @@ package org.springframework.data.relational.core.sql;
* An expression with an alias.
*
* @author Jens Schauder
+ * @since 1.1
*/
-public class AliasedExpression extends AbstractSegment implements Aliased, Expression {
+class AliasedExpression extends AbstractSegment implements Aliased, Expression {
private final Expression expression;
private final String alias;
@@ -33,11 +34,19 @@ public class AliasedExpression extends AbstractSegment implements Aliased, Expre
this.alias = alias;
}
+ /*
+ * (non-Javadoc)
+ * @see org.springframework.data.relational.core.sql.Aliased#getAlias()
+ */
@Override
public String getAlias() {
return alias;
}
+ /*
+ * (non-Javadoc)
+ * @see java.lang.Object#toString()
+ */
@Override
public String toString() {
return expression.toString() + " AS " + alias;
diff --git a/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/AndCondition.java b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/AndCondition.java
index a0201d8c..4447eed1 100644
--- a/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/AndCondition.java
+++ b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/AndCondition.java
@@ -19,6 +19,7 @@ package org.springframework.data.relational.core.sql;
* {@link Condition} representing an {@code AND} relation between two {@link Condition}s.
*
* @author Mark Paluch
+ * @since 1.1
* @see Condition#and(Condition)
*/
public class AndCondition extends MultipleCondition {
@@ -26,5 +27,4 @@ public class AndCondition extends MultipleCondition {
AndCondition(Condition... conditions) {
super(" AND ", conditions);
}
-
}
diff --git a/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/AsteriskFromTable.java b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/AsteriskFromTable.java
index 7dc5cd87..f2803ade 100644
--- a/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/AsteriskFromTable.java
+++ b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/AsteriskFromTable.java
@@ -15,14 +15,19 @@
*/
package org.springframework.data.relational.core.sql;
-import org.springframework.util.Assert;
-
/**
* {@link Segment} to select all columns from a {@link Table}.
*
- * * Renders to: {@code .*} as in {@code SELECT .* FROM …}.
+ * * Renders to: {@code
+ *
+
+ * .*} as in {@code SELECT
+ *
+
+ * .* FROM …}.
*
* @author Mark Paluch
+ * @since 1.1
* @see Table#asterisk()
*/
public class AsteriskFromTable extends AbstractSegment implements Expression {
@@ -45,6 +50,10 @@ public class AsteriskFromTable extends AbstractSegment implements Expression {
return table;
}
+ /*
+ * (non-Javadoc)
+ * @see java.lang.Object#toString()
+ */
@Override
public String toString() {
diff --git a/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/BindMarker.java b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/BindMarker.java
index 5f14d0cb..16957cde 100644
--- a/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/BindMarker.java
+++ b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/BindMarker.java
@@ -21,6 +21,7 @@ import org.springframework.lang.Nullable;
* Bind marker/parameter placeholder used to construct prepared statements with parameter substitution.
*
* @author Mark Paluch
+ * @since 1.1
*/
public class BindMarker extends AbstractSegment implements Expression {
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 dccf97ee..356a8e83 100644
--- a/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/Column.java
+++ b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/Column.java
@@ -24,6 +24,7 @@ import org.springframework.util.Assert;
* Renders to: {@code } or {@code .}.
*
* @author Mark Paluch
+ * @since 1.1
*/
public class Column extends AbstractSegment implements Expression, Named {
@@ -72,7 +73,7 @@ public class Column extends AbstractSegment implements Expression, Named {
}
/**
- * Create a new aliased {@link Column}.
+ * Creates a new aliased {@link Column}.
*
* @param alias column alias name, must not {@literal null} or empty.
* @return the aliased {@link Column}.
@@ -85,7 +86,7 @@ public class Column extends AbstractSegment implements Expression, Named {
}
/**
- * Create a new {@link Column} associated with a {@link Table}.
+ * Creates a new {@link Column} associated with a {@link Table}.
*
* @param table the table, must not be {@literal null}.
* @return a new {@link Column} associated with {@link Table}.
@@ -97,6 +98,108 @@ public class Column extends AbstractSegment implements Expression, Named {
return new Column(name, table);
}
+ // -------------------------------------------------------------------------
+ // Methods for Condition creation.
+ // -------------------------------------------------------------------------
+
+ /**
+ * Creates a {@code =} (equals) {@link Condition}.
+ *
+ * @param expression right side of the comparison.
+ * @return the {@link Comparison} condition.
+ */
+ public Comparison isEqualTo(Expression expression) {
+ return Conditions.isEqual(this, expression);
+ }
+
+ /**
+ * Creates a {@code !=} (not equals) {@link Condition}.
+ *
+ * @param expression right side of the comparison.
+ * @return the {@link Comparison} condition.
+ */
+ public Comparison isNotEqualTo(Expression expression) {
+ return Conditions.isNotEqual(this, expression);
+ }
+
+ /**
+ * Creates a {@code <} (less) {@link Condition} {@link Condition}.
+ *
+ * @param expression right side of the comparison.
+ * @return the {@link Comparison} condition.
+ */
+ public Comparison isLess(Expression expression) {
+ return Conditions.isLess(this, expression);
+ }
+
+ /**
+ * CCreates a {@code <=} (greater ) {@link Condition} {@link Condition}.
+ *
+ * @param expression right side of the comparison.
+ * @return the {@link Comparison} condition.
+ */
+ public Comparison isLessOrEqualTo(Expression expression) {
+ return Conditions.isLessOrEqualTo(this, expression);
+ }
+
+ /**
+ * Creates a {@code !=} (not equals) {@link Condition}.
+ *
+ * @param expression right side of the comparison.
+ * @return the {@link Comparison} condition.
+ */
+ public Comparison isGreater(Expression expression) {
+ return Conditions.isGreater(this, expression);
+ }
+
+ /**
+ * Creates a {@code <=} (greater or equal to) {@link Condition} {@link Condition}.
+ *
+ * @param expression right side of the comparison.
+ * @return the {@link Comparison} condition.
+ */
+ public Comparison isGreaterOrEqualTo(Expression expression) {
+ return Conditions.isGreaterOrEqualTo(this, expression);
+ }
+
+ /**
+ * Creates a {@code LIKE} {@link Condition}.
+ *
+ * @param expression right side of the comparison.
+ * @return the {@link Like} condition.
+ */
+ public Like like(Expression expression) {
+ return Conditions.like(this, expression);
+ }
+
+ /**
+ * Creates a new {@link In} {@link Condition} given right {@link Expression}s.
+ *
+ * @param expression right side of the comparison.
+ * @return the {@link In} condition.
+ */
+ public In in(Expression... expression) {
+ return Conditions.in(this, expression);
+ }
+
+ /**
+ * Creates a {@code IS NULL} condition.
+ *
+ * @return the {@link IsNull} condition.
+ */
+ public IsNull isNull() {
+ return Conditions.isNull(this);
+ }
+
+ /**
+ * Creates a {@code IS NOT NULL} condition.
+ *
+ * @return the {@link Condition} condition.
+ */
+ public Condition isNotNull() {
+ return isNull().not();
+ }
+
/*
* (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/Comparison.java b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/Comparison.java
new file mode 100644
index 00000000..a5641c23
--- /dev/null
+++ b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/Comparison.java
@@ -0,0 +1,100 @@
+/*
+ * 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;
+
+/**
+ * Comparing {@link Condition} comparing two {@link Expression}s.
+ *
+ * Results in a rendered condition: {@code } (e.g. {@code col = 'predicate'}.
+ *
+ * @author Mark Paluch
+ * @since 1.1
+ */
+public class Comparison extends AbstractSegment implements Condition {
+
+ private final Expression left;
+ private final String comparator;
+ private final Expression right;
+
+ private Comparison(Expression left, String comparator, Expression right) {
+
+ super(left, right);
+
+ this.left = left;
+ this.comparator = comparator;
+ this.right = right;
+ }
+
+ /**
+ * Creates a new {@link Comparison} {@link Condition} given two {@link Expression}s.
+ *
+ * @param leftColumnOrExpression the left {@link Expression}.
+ * @param comparator the comparator.
+ * @param rightColumnOrExpression the right {@link Expression}.
+ * @return the {@link Comparison} condition.
+ */
+ public static Comparison create(Expression leftColumnOrExpression, String comparator,
+ Expression rightColumnOrExpression) {
+
+ Assert.notNull(leftColumnOrExpression, "Left expression must not be null!");
+ Assert.notNull(comparator, "Comparator must not be null!");
+ Assert.notNull(rightColumnOrExpression, "Right expression must not be null!");
+
+ return new Comparison(leftColumnOrExpression, comparator, rightColumnOrExpression);
+ }
+
+ @Override
+ public Condition not() {
+
+ if ("=".equals(comparator)) {
+ return new Comparison(left, "!=", right);
+ }
+
+ if ("!=".equals(comparator)) {
+ return new Comparison(left, "=", right);
+ }
+
+ return new Not(this);
+ }
+
+ /**
+ * @return the left {@link Expression}.
+ */
+ public Expression getLeft() {
+ return left;
+ }
+
+ /**
+ * @return the comparator.
+ */
+ public String getComparator() {
+ return comparator;
+ }
+
+ /**
+ * @return the right {@link Expression}.
+ */
+ public Expression getRight() {
+ return right;
+ }
+
+ @Override
+ public String toString() {
+ return left.toString() + " " + comparator + " " + right.toString();
+ }
+}
diff --git a/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/Condition.java b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/Condition.java
index 88341993..b7799392 100644
--- a/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/Condition.java
+++ b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/Condition.java
@@ -19,6 +19,8 @@ package org.springframework.data.relational.core.sql;
* AST {@link Segment} for a condition.
*
* @author Mark Paluch
+ * @author Jens Schauder
+ * @since 1.1
* @see Conditions
*/
public interface Condition extends Segment {
@@ -43,6 +45,11 @@ public interface Condition extends Segment {
return new OrCondition(this, other);
}
+ /**
+ * Creates a {@link Condition} that negates this {@link Condition}.
+ *
+ * @return the negated {@link Condition}.
+ */
default Condition not() {
return new Not(this);
}
diff --git a/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/Conditions.java b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/Conditions.java
index 3e16c13d..bee428a9 100644
--- a/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/Conditions.java
+++ b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/Conditions.java
@@ -15,23 +15,24 @@
*/
package org.springframework.data.relational.core.sql;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+
+import org.springframework.util.Assert;
+
/**
* Factory for common {@link Condition}s.
*
* @author Mark Paluch
+ * @author Jens Schauder
+ * @since 1.1
* @see SQL
* @see Expressions
* @see Functions
*/
public abstract class Conditions {
- /**
- * @return a new {@link Equals} condition.
- */
- public static Equals equals(Expression left, Expression right) {
- return Equals.create(left, right);
- }
-
/**
* Creates a plain {@code sql} {@link Condition}.
*
@@ -42,20 +43,153 @@ public abstract class Conditions {
return new ConstantCondition(sql);
}
- // Utility constructor.
- private Conditions() {
+ /**
+ * Creates a {@code IS NULL} condition.
+ *
+ * @param expression the expression to check for nullability, must not be {@literal null}.
+ * @return the {@code IS NULL} condition.
+ */
+ public static IsNull isNull(Expression expression) {
+ return IsNull.create(expression);
}
- public static Condition isNull(Expression expression) {
- return new IsNull(expression);
+ /**
+ * Creates a {@code =} (equals) {@link Condition}.
+ *
+ * @param leftColumnOrExpression left side of the comparison.
+ * @param rightColumnOrExpression right side of the comparison.
+ * @return the {@link Comparison} condition.
+ */
+ public static Comparison isEqual(Expression leftColumnOrExpression, Expression rightColumnOrExpression) {
+ return Comparison.create(leftColumnOrExpression, "=", rightColumnOrExpression);
}
- public static Condition isEqual(Column bar, Expression param) {
- return new Equals(bar, param);
+ /**
+ * Creates a {@code !=} (not equals) {@link Condition}.
+ *
+ * @param leftColumnOrExpression left side of the comparison.
+ * @param rightColumnOrExpression right side of the comparison.
+ * @return the {@link Comparison} condition.
+ */
+ public static Comparison isNotEqual(Expression leftColumnOrExpression, Expression rightColumnOrExpression) {
+ return Comparison.create(leftColumnOrExpression, "!=", rightColumnOrExpression);
}
- public static Condition in(Column bar, Expression subselectExpression) {
- return new In(bar, subselectExpression);
+ /**
+ * Creates a {@code <} (less) {@link Condition} comparing {@code left} is less than {@code right}.
+ *
+ * @param leftColumnOrExpression left side of the comparison.
+ * @param rightColumnOrExpression right side of the comparison.
+ * @return the {@link Comparison} condition.
+ */
+ public static Comparison isLess(Expression leftColumnOrExpression, Expression rightColumnOrExpression) {
+ return Comparison.create(leftColumnOrExpression, "<", rightColumnOrExpression);
+ }
+
+ /**
+ * Creates a {@code <=} (less or equal to) {@link Condition} comparing {@code left} is less than or equal to
+ * {@code right}.
+ *
+ * @param leftColumnOrExpression left side of the comparison.
+ * @param rightColumnOrExpression right side of the comparison.
+ * @return the {@link Comparison} condition.
+ */
+ public static Comparison isLessOrEqualTo(Expression leftColumnOrExpression, Expression rightColumnOrExpression) {
+ return Comparison.create(leftColumnOrExpression, "<=", rightColumnOrExpression);
+ }
+
+ /**
+ * Creates a {@code <=} (greater ) {@link Condition} comparing {@code left} is greater than {@code right}.
+ *
+ * @param leftColumnOrExpression left side of the comparison.
+ * @param rightColumnOrExpression right side of the comparison.
+ * @return the {@link Comparison} condition.
+ */
+ public static Comparison isGreater(Expression leftColumnOrExpression, Expression rightColumnOrExpression) {
+ return Comparison.create(leftColumnOrExpression, ">", rightColumnOrExpression);
+ }
+
+ /**
+ * Creates a {@code <=} (greater or equal to) {@link Condition} comparing {@code left} is greater than or equal to
+ * {@code right}.
+ *
+ * @param leftColumnOrExpression left side of the comparison.
+ * @param rightColumnOrExpression right side of the comparison.
+ * @return the {@link Comparison} condition.
+ */
+ public static Comparison isGreaterOrEqualTo(Expression leftColumnOrExpression, Expression rightColumnOrExpression) {
+ return Comparison.create(leftColumnOrExpression, ">=", rightColumnOrExpression);
+ }
+
+ /**
+ * Creates a {@code LIKE} {@link Condition}.
+ *
+ * @param leftColumnOrExpression left side of the comparison.
+ * @param rightColumnOrExpression right side of the comparison.
+ * @return the {@link Comparison} condition.
+ */
+ public static Like like(Expression leftColumnOrExpression, Expression rightColumnOrExpression) {
+ return Like.create(leftColumnOrExpression, rightColumnOrExpression);
+ }
+
+ /**
+ * Creates a {@code IN} {@link Condition clause}.
+ *
+ * @param columnOrExpression left side of the comparison.
+ * @param arg IN argument.
+ * @return the {@link In} condition.
+ */
+ public static Condition in(Expression columnOrExpression, Expression arg) {
+
+ Assert.notNull(columnOrExpression, "Comparison column or expression must not be null");
+ Assert.notNull(arg, "Expression argument must not be null");
+
+ return In.create(columnOrExpression, arg);
+ }
+
+ /**
+ * Creates a new {@link In} {@link Condition} given left and right {@link Expression}s.
+ *
+ * @param columnOrExpression left hand side of the {@link Condition} must not be {@literal null}.
+ * @param expressions right hand side (collection {@link Expression}) must not be {@literal null}.
+ * @return the {@link In} {@link Condition}.
+ */
+ public static Condition in(Expression columnOrExpression, Collection extends Expression> expressions) {
+
+ Assert.notNull(columnOrExpression, "Comparison column or expression must not be null");
+ Assert.notNull(expressions, "Expression argument must not be null");
+
+ return In.create(columnOrExpression, new ArrayList<>(expressions));
+ }
+
+ /**
+ * Creates a new {@link In} {@link Condition} given left and right {@link Expression}s.
+ *
+ * @param columnOrExpression left hand side of the {@link Condition} must not be {@literal null}.
+ * @param expressions right hand side (collection {@link Expression}) must not be {@literal null}.
+ * @return the {@link In} {@link Condition}.
+ */
+ public static In in(Expression columnOrExpression, Expression... expressions) {
+
+ Assert.notNull(columnOrExpression, "Comparison column or expression must not be null");
+ Assert.notNull(expressions, "Expression argument must not be null");
+
+ return In.create(columnOrExpression, Arrays.asList(expressions));
+ }
+
+ /**
+ * Creates a {@code IN} {@link Condition clause} for a {@link Select subselect}.
+ *
+ * @param column the column to compare.
+ * @param subselect the subselect.
+ * @return the {@link In} condition.
+ */
+ public static Condition in(Column column, Select subselect) {
+
+ Assert.notNull(column, "Column must not be null");
+ Assert.notNull(subselect, "Subselect must not be null");
+
+ return in(column, new SubselectExpression(subselect));
}
static class ConstantCondition extends AbstractSegment implements Condition {
@@ -71,8 +205,7 @@ public abstract class Conditions {
return condition;
}
}
+
+ // Utility constructor.
+ private Conditions() {}
}
-
-
-
-
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 50f6507b..6bd8f8d5 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
@@ -26,11 +26,12 @@ import org.springframework.util.Assert;
* Default {@link Select} implementation.
*
* @author Mark Paluch
+ * @since 1.1
*/
class DefaultSelect implements Select {
private final boolean distinct;
- private final List selectList;
+ private final SelectList selectList;
private final From from;
private final long limit;
private final long offset;
@@ -39,10 +40,10 @@ class DefaultSelect implements Select {
private final List orderBy;
DefaultSelect(boolean distinct, List selectList, List from, long limit, long offset,
- List joins, @Nullable Condition where, List orderBy) {
+ List joins, @Nullable Condition where, List orderBy) {
this.distinct = distinct;
- this.selectList = new ArrayList<>(selectList);
+ this.selectList = new SelectList(new ArrayList<>(selectList));
this.from = new From(from);
this.limit = limit;
this.offset = offset;
@@ -85,7 +86,7 @@ class DefaultSelect implements Select {
visitor.enter(this);
- selectList.forEach(it -> it.visit(visitor));
+ selectList.visit(visitor);
from.visit(visitor);
joins.forEach(it -> it.visit(visitor));
diff --git a/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/DefaultSelectBuilder.java b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/DefaultSelectBuilder.java
index 15dfb3f6..b5434f1b 100644
--- a/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/DefaultSelectBuilder.java
+++ b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/DefaultSelectBuilder.java
@@ -24,11 +24,13 @@ import org.springframework.data.relational.core.sql.Join.JoinType;
import org.springframework.data.relational.core.sql.SelectBuilder.SelectAndFrom;
import org.springframework.data.relational.core.sql.SelectBuilder.SelectFromAndJoin;
import org.springframework.data.relational.core.sql.SelectBuilder.SelectWhereAndOr;
+import org.springframework.lang.Nullable;
/**
* Default {@link SelectBuilder} implementation.
*
* @author Mark Paluch
+ * @since 1.1
*/
class DefaultSelectBuilder implements SelectBuilder, SelectAndFrom, SelectFromAndJoin, SelectWhereAndOr {
@@ -38,7 +40,7 @@ class DefaultSelectBuilder implements SelectBuilder, SelectAndFrom, SelectFromAn
private long limit = -1;
private long offset = -1;
private List joins = new ArrayList<>();
- private Condition where;
+ private @Nullable Condition where;
private List orderBy = new ArrayList<>();
/*
@@ -273,7 +275,7 @@ class DefaultSelectBuilder implements SelectBuilder, SelectAndFrom, SelectFromAn
private final DefaultSelectBuilder selectBuilder;
private Expression from;
private Expression to;
- private Condition condition;
+ private @Nullable Condition condition;
JoinBuilder(Table table, DefaultSelectBuilder selectBuilder) {
this.table = table;
@@ -314,12 +316,12 @@ class DefaultSelectBuilder implements SelectBuilder, SelectAndFrom, SelectFromAn
}
private void finishCondition() {
- Equals equals = Equals.create(from, to);
+ Comparison comparison = Comparison.create(from, "=", to);
if (condition == null) {
- condition = equals;
+ condition = comparison;
} else {
- condition = condition.and(equals);
+ condition = condition.and(comparison);
}
}
diff --git a/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/Expression.java b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/Expression.java
index 63a8b917..07e083ce 100644
--- a/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/Expression.java
+++ b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/Expression.java
@@ -19,9 +19,8 @@ package org.springframework.data.relational.core.sql;
* Expression that can be used in select lists.
*
* @author Mark Paluch
+ * @since 1.1
* @see SQL
* @see Expressions
*/
-public interface Expression extends Segment {
-
-}
+public interface Expression extends Segment {}
diff --git a/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/Expressions.java b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/Expressions.java
index 580d6d4f..be56122a 100644
--- a/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/Expressions.java
+++ b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/Expressions.java
@@ -19,6 +19,7 @@ package org.springframework.data.relational.core.sql;
* Factory for common {@link Expression}s.
*
* @author Mark Paluch
+ * @since 1.1
* @see SQL
* @see Conditions
* @see Functions
@@ -52,8 +53,7 @@ public abstract class Expressions {
}
// Utility constructor.
- private Expressions() {
- }
+ private Expressions() {}
static class SimpleExpression extends AbstractSegment implements Expression {
@@ -69,7 +69,3 @@ public abstract class Expressions {
}
}
}
-
-
-
-
diff --git a/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/From.java b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/From.java
index 777aeda0..6c6bfc40 100644
--- a/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/From.java
+++ b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/From.java
@@ -16,16 +16,15 @@
package org.springframework.data.relational.core.sql;
import java.util.Arrays;
-import java.util.Collections;
import java.util.List;
-import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
/**
* {@code FROM} clause.
*
* @author Mark Paluch
+ * @since 1.1
*/
public class From extends AbstractSegment {
@@ -37,7 +36,7 @@ public class From extends AbstractSegment {
From(List tables) {
- super(tables.toArray(new Table[]{}));
+ super(tables.toArray(new Table[] {}));
this.tables = tables;
}
diff --git a/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/Functions.java b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/Functions.java
index 6edf7ae2..651b5455 100644
--- a/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/Functions.java
+++ b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/Functions.java
@@ -25,6 +25,7 @@ import org.springframework.util.Assert;
* Factory for common {@link Expression function expressions}.
*
* @author Mark Paluch
+ * @since 1.1
* @see SQL
* @see Expressions
* @see Functions
@@ -42,7 +43,7 @@ public class Functions {
Assert.notNull(columns, "Columns must not be null!");
Assert.notEmpty(columns, "Columns must contains at least one column");
- return new SimpleFunction("COUNT", Arrays.asList(columns));
+ return SimpleFunction.create("COUNT", Arrays.asList(columns));
}
/**
@@ -55,7 +56,7 @@ public class Functions {
Assert.notNull(columns, "Columns must not be null!");
- return new SimpleFunction("COUNT", new ArrayList<>(columns));
+ return SimpleFunction.create("COUNT", new ArrayList<>(columns));
}
// Utility constructor.
diff --git a/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/In.java b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/In.java
index a66c3d81..23d72823 100644
--- a/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/In.java
+++ b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/In.java
@@ -15,24 +15,99 @@
*/
package org.springframework.data.relational.core.sql;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+
+import org.springframework.util.Assert;
+import org.springframework.util.StringUtils;
+
/**
+ * {@code IN} {@link Condition} clause.
+ *
* @author Jens Schauder
+ * @author Mark Paluch
+ * @since 1.1
*/
public class In extends AbstractSegment implements Condition {
private final Expression left;
- private final Expression right;
+ private final Collection expressions;
- public In(Expression left, Expression right) {
+ private In(Expression left, Collection expressions) {
- super(left, right);
+ super(toArray(left, expressions));
this.left = left;
- this.right = right;
+ this.expressions = expressions;
}
+ private static Segment[] toArray(Expression expression, Collection expressions) {
+
+ Segment[] segments = new Segment[1 + expressions.size()];
+ segments[0] = expression;
+
+ int index = 1;
+
+ for (Expression e : expressions) {
+ segments[index++] = e;
+ }
+
+ return segments;
+ }
+
+ /**
+ * Creates a new {@link In} {@link Condition} given left and right {@link Expression}s.
+ *
+ * @param columnOrExpression left hand side of the {@link Condition} must not be {@literal null}.
+ * @param arg right hand side (collection {@link Expression}) must not be {@literal null}.
+ * @return the {@link In} {@link Condition}.
+ */
+ public static In create(Expression columnOrExpression, Expression arg) {
+
+ Assert.notNull(columnOrExpression, "Comparison column or expression must not be null");
+ Assert.notNull(arg, "Expression argument must not be null");
+
+ return new In(columnOrExpression, Collections.singletonList(arg));
+ }
+
+ /**
+ * Creates a new {@link In} {@link Condition} given left and right {@link Expression}s.
+ *
+ * @param columnOrExpression left hand side of the {@link Condition} must not be {@literal null}.
+ * @param expressions right hand side (collection {@link Expression}) must not be {@literal null}.
+ * @return the {@link In} {@link Condition}.
+ */
+ public static In create(Expression columnOrExpression, Collection extends Expression> expressions) {
+
+ Assert.notNull(columnOrExpression, "Comparison column or expression must not be null");
+ Assert.notNull(expressions, "Expression argument must not be null");
+
+ return new In(columnOrExpression, new ArrayList<>(expressions));
+ }
+
+ /**
+ * Creates a new {@link In} {@link Condition} given left and right {@link Expression}s.
+ *
+ * @param columnOrExpression left hand side of the {@link Condition} must not be {@literal null}.
+ * @param expressions right hand side (collection {@link Expression}) must not be {@literal null}.
+ * @return the {@link In} {@link Condition}.
+ */
+ public static In create(Expression columnOrExpression, Expression... expressions) {
+
+ Assert.notNull(columnOrExpression, "Comparison column or expression must not be null");
+ Assert.notNull(expressions, "Expression argument must not be null");
+
+ return new In(columnOrExpression, Arrays.asList(expressions));
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see java.lang.Object#toString()
+ */
@Override
public String toString() {
- return left + " IN " + right;
+ return left + " IN (" + StringUtils.collectionToDelimitedString(expressions, ", ") + ")";
}
}
diff --git a/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/IsNull.java b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/IsNull.java
index 88e7d29c..99d950c6 100644
--- a/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/IsNull.java
+++ b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/IsNull.java
@@ -15,16 +15,24 @@
*/
package org.springframework.data.relational.core.sql;
+import org.springframework.util.Assert;
+
/**
+ * {@code IS NULL} {@link Condition}.
+ *
* @author Jens Schauder
+ * @since 1.1
*/
public class IsNull extends AbstractSegment implements Condition {
private final Expression expression;
-
private final boolean negated;
- public IsNull(Expression expression, boolean negated) {
+ private IsNull(Expression expression) {
+ this(expression, false);
+ }
+
+ private IsNull(Expression expression, boolean negated) {
super(expression);
@@ -32,21 +40,38 @@ public class IsNull extends AbstractSegment implements Condition {
this.negated = negated;
}
- public IsNull(Expression expression) {
- this(expression, false);
+ /**
+ * Creates a new {@link IsNull} expression.
+ *
+ * @param expression must not be {@literal null}.
+ * @return
+ */
+ public static IsNull create(Expression expression) {
+
+ Assert.notNull(expression, "Expression must not be null");
+
+ return new IsNull(expression);
}
+ /*
+ * (non-Javadoc)
+ * @see org.springframework.data.relational.core.sql.Condition#not()
+ */
@Override
public Condition not() {
return new IsNull(expression, !negated);
}
+ public boolean isNegated() {
+ return negated;
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see java.lang.Object#toString()
+ */
@Override
public String toString() {
return expression + (negated ? " IS NOT NULL" : " IS NULL");
}
-
- public boolean isNegated() {
- return negated;
- }
}
diff --git a/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/Join.java b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/Join.java
index 58c6b98a..867d29e2 100644
--- a/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/Join.java
+++ b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/Join.java
@@ -19,10 +19,12 @@ package org.springframework.data.relational.core.sql;
* {@link Segment} for a {@code JOIN} declaration.
*
* Renders to: {@code JOIN
- *
+ *
+
* ON }.
*
* @author Mark Paluch
+ * @since 1.1
*/
public class Join extends AbstractSegment {
diff --git a/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/Equals.java b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/Like.java
similarity index 56%
rename from spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/Equals.java
rename to spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/Like.java
index 5d077625..1a6c3ab3 100644
--- a/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/Equals.java
+++ b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/Like.java
@@ -18,18 +18,19 @@ package org.springframework.data.relational.core.sql;
import org.springframework.util.Assert;
/**
- * Equals to {@link Condition} comparing two {@link Expression}s.
+ * LIKE {@link Condition} comparing two {@link Expression}s.
*
- * Results in a rendered condition: {@code = }.
+ * Results in a rendered condition: {@code LIKE }.
*
* @author Mark Paluch
+ * @since 1.1
*/
-public class Equals extends AbstractSegment implements Condition {
+public class Like extends AbstractSegment implements Condition {
private final Expression left;
private final Expression right;
- Equals(Expression left, Expression right) {
+ private Like(Expression left, Expression right) {
super(left, right);
@@ -38,18 +39,18 @@ public class Equals extends AbstractSegment implements Condition {
}
/**
- * Creates a new {@link Equals} {@link Condition} given two {@link Expression}s.
+ * Creates a new {@link Like} {@link Condition} given two {@link Expression}s.
*
- * @param left the left {@link Expression}.
- * @param right the right {@link Expression}.
- * @return the {@link Equals} condition.
+ * @param leftColumnOrExpression the left {@link Expression}.
+ * @param rightColumnOrExpression the right {@link Expression}.
+ * @return the {@link Like} condition.
*/
- public static Equals create(Expression left, Expression right) {
+ public static Like create(Expression leftColumnOrExpression, Expression rightColumnOrExpression) {
- Assert.notNull(left, "Left expression must not be null!");
- Assert.notNull(right, "Right expression must not be null!");
+ Assert.notNull(leftColumnOrExpression, "Left expression must not be null!");
+ Assert.notNull(rightColumnOrExpression, "Right expression must not be null!");
- return new Equals(left, right);
+ return new Like(leftColumnOrExpression, rightColumnOrExpression);
}
/**
@@ -68,6 +69,6 @@ public class Equals extends AbstractSegment implements Condition {
@Override
public String toString() {
- return left.toString() + " = " + right.toString();
+ return left.toString() + " LIKE " + right.toString();
}
}
diff --git a/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/MultipleCondition.java b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/MultipleCondition.java
index 107e8dbb..82077288 100644
--- a/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/MultipleCondition.java
+++ b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/MultipleCondition.java
@@ -19,10 +19,11 @@ import java.util.Arrays;
import java.util.List;
import java.util.StringJoiner;
-import org.springframework.util.Assert;
-
/**
+ * Wrapper for multiple {@link Condition}s.
+ *
* @author Jens Schauder
+ * @since 1.1
*/
public abstract class MultipleCondition extends AbstractSegment implements Condition {
diff --git a/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/Named.java b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/Named.java
index a205e10b..44f7449c 100644
--- a/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/Named.java
+++ b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/Named.java
@@ -19,6 +19,7 @@ package org.springframework.data.relational.core.sql;
* Named element exposing a {@link #getName() name}.
*
* @author Mark Paluch
+ * @since 1.1
*/
public interface Named {
diff --git a/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/Not.java b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/Not.java
index 993d5972..a691b6dd 100644
--- a/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/Not.java
+++ b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/Not.java
@@ -17,23 +17,32 @@ package org.springframework.data.relational.core.sql;
/**
* @author Jens Schauder
+ * @since 1.1
*/
public class Not extends AbstractSegment implements Condition {
private final Condition condition;
- public Not(Condition condition) {
+ Not(Condition condition) {
super(condition);
this.condition = condition;
}
+ /*
+ * (non-Javadoc)
+ * @see org.springframework.data.relational.core.sql.Condition#not()
+ */
@Override
public Condition not() {
return condition;
}
+ /*
+ * (non-Javadoc)
+ * @see java.lang.Object#toString()
+ */
@Override
public String toString() {
return "NOT " + condition.toString();
diff --git a/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/OrCondition.java b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/OrCondition.java
index 7886d978..94e872fa 100644
--- a/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/OrCondition.java
+++ b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/OrCondition.java
@@ -19,6 +19,7 @@ package org.springframework.data.relational.core.sql;
* {@link Condition} representing an {@code OR} relation between two {@link Condition}s.
*
* @author Mark Paluch
+ * @since 1.1
* @see Condition#or(Condition)
*/
public class OrCondition extends MultipleCondition {
diff --git a/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/OrderByField.java b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/OrderByField.java
index 187f9c16..ae09b5fa 100644
--- a/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/OrderByField.java
+++ b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/OrderByField.java
@@ -22,7 +22,10 @@ import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
/**
+ * Represents a field in the {@code ORDER BY} clause.
+ *
* @author Mark Paluch
+ * @since 1.1
*/
public class OrderByField extends AbstractSegment {
@@ -30,7 +33,7 @@ public class OrderByField extends AbstractSegment {
private final @Nullable Sort.Direction direction;
private final Sort.NullHandling nullHandling;
- OrderByField(Expression expression, Direction direction, NullHandling nullHandling) {
+ private OrderByField(Expression expression, @Nullable Direction direction, NullHandling nullHandling) {
super(expression);
Assert.notNull(expression, "Order by expression must not be null");
@@ -41,18 +44,42 @@ public class OrderByField extends AbstractSegment {
this.nullHandling = nullHandling;
}
+ /**
+ * Creates a new {@link OrderByField} from a {@link Column} applying default ordering.
+ *
+ * @param column must not be {@literal null}.
+ * @return the {@link OrderByField}.
+ */
public static OrderByField from(Column column) {
return new OrderByField(column, null, NullHandling.NATIVE);
}
+ /**
+ * Creates a new {@link OrderByField} from a the current one using ascending sorting.
+ *
+ * @return the new {@link OrderByField} with ascending sorting.
+ * @see #desc()
+ */
public OrderByField asc() {
- return new OrderByField(expression, Direction.ASC, NullHandling.NATIVE);
+ return new OrderByField(expression, Direction.ASC, nullHandling);
}
+ /**
+ * Creates a new {@link OrderByField} from a the current one using descending sorting.
+ *
+ * @return the new {@link OrderByField} with descending sorting.
+ * @see #asc()
+ */
public OrderByField desc() {
- return new OrderByField(expression, Direction.DESC, NullHandling.NATIVE);
+ return new OrderByField(expression, Direction.DESC, nullHandling);
}
+ /**
+ * Creates a new {@link OrderByField} with {@link NullHandling} applied.
+ *
+ * @param nullHandling must not be {@literal null}.
+ * @return the new {@link OrderByField} with {@link NullHandling} applied.
+ */
public OrderByField withNullHandling(NullHandling nullHandling) {
return new OrderByField(expression, direction, nullHandling);
}
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 369727fe..0f45a863 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,53 +16,24 @@
package org.springframework.data.relational.core.sql;
import org.springframework.data.relational.core.sql.BindMarker.NamedBindMarker;
-import org.springframework.data.relational.core.sql.SelectBuilder.SelectAndFrom;
import org.springframework.util.Assert;
/**
- * Utility to create SQL {@link Segment}s. Typically used as entry point to the Query Builder AST.
- * Objects and dependent objects created by the Query AST are immutable except for builders.
- * The Query Builder API is intended for framework usage to produce SQL required for framework operations.
+ * Utility to create SQL {@link Segment}s. Typically used as entry point to the Statement Builder. Objects and dependent
+ * objects created by the Query AST are immutable except for builders.
+ *
+ * The Statement Builder API is intended for framework usage to produce SQL required for framework operations.
*
* @author Mark Paluch
+ * @author Jens Schauder
+ * @since 1.1
* @see Expressions
* @see Conditions
* @see Functions
+ * @see StatementBuilder
*/
public abstract class SQL {
- /**
- * Creates a new {@link SelectBuilder} by specifying a {@code SELECT} column.
- *
- * @param expression the select list expression.
- * @return the {@link SelectBuilder} containing {@link Expression}.
- * @see SelectBuilder#select(Expression)
- */
- public static SelectAndFrom newSelect(Expression expression) {
- return Select.builder().select(expression);
- }
-
- /**
- * Creates a new {@link SelectBuilder} by specifying one or more {@code SELECT} columns.
- *
- * @param expressions the select list expressions.
- * @return the {@link SelectBuilder} containing {@link Expression}s.
- * @see SelectBuilder#select(Expression...)
- */
- public static SelectAndFrom newSelect(Expression... expressions) {
- return Select.builder().select(expressions);
- }
-
- /**
- * Creates a new {@link SelectBuilder}.
- *
- * @return the new {@link SelectBuilder}.
- * @see SelectBuilder
- */
- public static SelectBuilder select() {
- return Select.builder();
- }
-
/**
* Creates a new {@link Column} associated with a source {@link Table}.
*
@@ -107,6 +78,5 @@ public abstract class SQL {
}
// Utility constructor.
- private SQL() {
- }
+ private SQL() {}
}
diff --git a/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/Segment.java b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/Segment.java
index 14ec424d..138d7cf0 100644
--- a/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/Segment.java
+++ b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/Segment.java
@@ -16,9 +16,11 @@
package org.springframework.data.relational.core.sql;
/**
- * Supertype of all Abstract Syntax Tree (AST) segments. Segments are typically immutable and mutator methods return new instances instead of changing the called instance.
+ * Supertype of all Abstract Syntax Tree (AST) segments. Segments are typically immutable and mutator methods return new
+ * instances instead of changing the called instance.
*
* @author Mark Paluch
+ * @since 1.1
*/
public interface Segment extends Visitable {
@@ -28,8 +30,7 @@ public interface Segment extends Visitable {
* Equality is typically given if the {@link #toString()} representation matches.
*
* @param other the reference object with which to compare.
- * @return {@literal true} if this object is the same as the {@code other}
- * argument; {@literal false} otherwise.
+ * @return {@literal true} if this object is the same as the {@code other} argument; {@literal false} otherwise.
*/
@Override
boolean equals(Object other);
@@ -37,7 +38,8 @@ public interface Segment extends Visitable {
/**
* Generate a hash code from this{@link Segment}.
*
- * Hashcode typically derives from the {@link #toString()} representation so two {@link Segment}s yield the same {@link #hashCode()} if their {@link #toString()} representation matches.
+ * Hashcode typically derives from the {@link #toString()} representation so two {@link Segment}s yield the same
+ * {@link #hashCode()} if their {@link #toString()} representation matches.
*
* @return a hash code value for this object.
*/
@@ -47,7 +49,9 @@ public interface Segment extends Visitable {
/**
* Return a SQL string representation of this {@link Segment}.
*
- * The representation is intended for debugging purposes and an approximation to the generated SQL. While it might work in the context of a specific dialect, you should not that the {@link #toString()} representation works across multiple databases.
+ * The representation is intended for debugging purposes and an approximation to the generated SQL. While it might
+ * work in the context of a specific dialect, you should not that the {@link #toString()} representation works across
+ * multiple databases.
*
* @return a SQL string representation of this {@link Segment}.
*/
diff --git a/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/Select.java b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/Select.java
index d225364a..a45c01b8 100644
--- a/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/Select.java
+++ b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/Select.java
@@ -18,11 +18,10 @@ package org.springframework.data.relational.core.sql;
import java.util.OptionalLong;
/**
- * AST for a {@code SELECT} statement.
- * Visiting order:
+ * AST for a {@code SELECT} statement. Visiting order:
*
* - Self
- * - {@link Column SELECT columns}
+ * - {@link Column SELECT columns}
* - {@link Table FROM tables} clause
* - {@link Join JOINs}
* - {@link Condition WHERE} condition
@@ -30,12 +29,13 @@ import java.util.OptionalLong;
*
*
* @author Mark Paluch
+ * @since 1.1
+ * @see StatementBuilder
* @see SelectBuilder
* @see SQL
*/
public interface Select extends Segment, Visitable {
-
/**
* Creates a new {@link SelectBuilder}.
*
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 4825584a..b1ea5ceb 100644
--- a/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/SelectBuilder.java
+++ b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/SelectBuilder.java
@@ -21,6 +21,7 @@ import java.util.Collection;
* Entry point to construct a {@link Select} statement.
*
* @author Mark Paluch
+ * @since 1.1
*/
public interface SelectBuilder {
@@ -67,12 +68,13 @@ public interface SelectBuilder {
SelectAndFrom distinct();
/**
- * Builder exposing {@code select} and {@code from} methods.
+ * Builder exposing {@code SELECT} and {@code FROM} methods.
*/
interface SelectAndFrom extends SelectFrom {
/**
- * Include a {@link Expression} in the select list. Multiple calls to this or other {@code select} methods keep adding items to the select list and do not replace previously contained items.
+ * Include a {@link Expression} in the select list. Multiple calls to this or other {@code select} methods keep
+ * adding items to the select list and do not replace previously contained items.
*
* @param expression the expression to include.
* @return {@code this} builder.
@@ -81,7 +83,8 @@ public interface SelectBuilder {
SelectFrom select(Expression expression);
/**
- * Include one or more {@link Expression}s in the select list. Multiple calls to this or other {@code select} methods keep adding items to the select list and do not replace previously contained items.
+ * Include one or more {@link Expression}s in the select list. Multiple calls to this or other {@code select}
+ * methods keep adding items to the select list and do not replace previously contained items.
*
* @param expressions the expressions to include.
* @return {@code this} builder.
@@ -90,7 +93,8 @@ public interface SelectBuilder {
SelectFrom select(Expression... expressions);
/**
- * Include one or more {@link Expression}s in the select list. Multiple calls to this or other {@code select} methods keep adding items to the select list and do not replace previously contained items.
+ * Include one or more {@link Expression}s in the select list. Multiple calls to this or other {@code select}
+ * methods keep adding items to the select list and do not replace previously contained items.
*
* @param expressions the expressions to include.
* @return {@code this} builder.
@@ -106,8 +110,8 @@ public interface SelectBuilder {
SelectAndFrom distinct();
/**
- * Declare a {@link Table} to {@code SELECT … FROM}.
- * Multiple calls to this or other {@code from} methods keep adding items to the select list and do not replace previously contained items.
+ * Declare a {@link Table} to {@code SELECT … FROM}. Multiple calls to this or other {@code from} methods keep
+ * adding items to the select list and do not replace previously contained items.
*
* @param table the table to {@code SELECT … FROM} must not be {@literal null}.
* @return {@code this} builder.
@@ -118,8 +122,8 @@ public interface SelectBuilder {
SelectFromAndJoin from(Table table);
/**
- * Declare one or more {@link Table}s to {@code SELECT … FROM}.
- * Multiple calls to this or other {@code from} methods keep adding items to the select list and do not replace previously contained items.
+ * Declare one or more {@link Table}s to {@code SELECT … FROM}. Multiple calls to this or other {@code from} methods
+ * keep adding items to the select list and do not replace previously contained items.
*
* @param tables the tables to {@code SELECT … FROM} must not be {@literal null}.
* @return {@code this} builder.
@@ -130,8 +134,8 @@ public interface SelectBuilder {
SelectFromAndJoin from(Table... tables);
/**
- * Declare one or more {@link Table}s to {@code SELECT … FROM}.
- * Multiple calls to this or other {@code from} methods keep adding items to the select list and do not replace previously contained items.
+ * Declare one or more {@link Table}s to {@code SELECT … FROM}. Multiple calls to this or other {@code from} methods
+ * keep adding items to the select list and do not replace previously contained items.
*
* @param tables the tables to {@code SELECT … FROM} must not be {@literal null}.
* @return {@code this} builder.
@@ -143,13 +147,13 @@ public interface SelectBuilder {
}
/**
- * Builder exposing {@code from} methods.
+ * Builder exposing {@code FROM} methods.
*/
interface SelectFrom extends BuildSelect {
/**
- * Declare a {@link Table} to {@code SELECT … FROM}.
- * Multiple calls to this or other {@code from} methods keep adding items to the select list and do not replace previously contained items.
+ * Declare a {@link Table} to {@code SELECT … FROM}. Multiple calls to this or other {@code from} methods keep
+ * adding items to the select list and do not replace previously contained items.
*
* @param table the table name to {@code SELECT … FROM} must not be {@literal null} or empty.
* @return {@code this} builder.
@@ -159,8 +163,8 @@ public interface SelectBuilder {
SelectFromAndOrderBy from(String table);
/**
- * Declare a {@link Table} to {@code SELECT … FROM}.
- * Multiple calls to this or other {@code from} methods keep adding items to the select list and do not replace previously contained items.
+ * Declare a {@link Table} to {@code SELECT … FROM}. Multiple calls to this or other {@code from} methods keep
+ * adding items to the select list and do not replace previously contained items.
*
* @param table the table to {@code SELECT … FROM} must not be {@literal null}.
* @return {@code this} builder.
@@ -170,8 +174,8 @@ public interface SelectBuilder {
SelectFromAndOrderBy from(Table table);
/**
- * Declare one or more {@link Table}s to {@code SELECT … FROM}.
- * Multiple calls to this or other {@code from} methods keep adding items to the select list and do not replace previously contained items.
+ * Declare one or more {@link Table}s to {@code SELECT … FROM}. Multiple calls to this or other {@code from} methods
+ * keep adding items to the select list and do not replace previously contained items.
*
* @param tables the tables to {@code SELECT … FROM} must not be {@literal null}.
* @return {@code this} builder.
@@ -181,8 +185,8 @@ public interface SelectBuilder {
SelectFromAndOrderBy from(Table... tables);
/**
- * Declare one or more {@link Table}s to {@code SELECT … FROM}.
- * Multiple calls to this or other {@code from} methods keep adding items to the select list and do not replace previously contained items.
+ * Declare one or more {@link Table}s to {@code SELECT … FROM}. Multiple calls to this or other {@code from} methods
+ * keep adding items to the select list and do not replace previously contained items.
*
* @param tables the tables to {@code SELECT … FROM} must not be {@literal null}.
* @return {@code this} builder.
@@ -193,7 +197,7 @@ public interface SelectBuilder {
}
/**
- * Builder exposing {@code from} and {@code order by} methods.
+ * Builder exposing {@code FROM}, {@code JOIN}, {@code WHERE} and {@code LIMIT/OFFSET} methods.
*/
interface SelectFromAndOrderBy extends SelectFrom, SelectOrdered, SelectLimitOffset, BuildSelect {
@@ -228,11 +232,14 @@ public interface SelectBuilder {
SelectFromAndOrderBy orderBy(Collection extends OrderByField> orderByFields);
}
+ /**
+ * Builder exposing {@code FROM}, {@code JOIN}, {@code WHERE} and {@code LIMIT/OFFSET} methods.
+ */
interface SelectFromAndJoin extends SelectFromAndOrderBy, BuildSelect, SelectJoin, SelectWhere, SelectLimitOffset {
/**
- * Declare a {@link Table} to {@code SELECT … FROM}.
- * Multiple calls to this or other {@code from} methods keep adding items to the select list and do not replace previously contained items.
+ * Declare a {@link Table} to {@code SELECT … FROM}. Multiple calls to this or other {@code from} methods keep
+ * adding items to the select list and do not replace previously contained items.
*
* @param table the table to {@code SELECT … FROM} must not be {@literal null}.
* @return {@code this} builder.
@@ -243,8 +250,8 @@ public interface SelectBuilder {
SelectFromAndJoin from(Table table);
/**
- * Declare one or more {@link Table}s to {@code SELECT … FROM}.
- * Multiple calls to this or other {@code from} methods keep adding items to the select list and do not replace previously contained items.
+ * Declare one or more {@link Table}s to {@code SELECT … FROM}. Multiple calls to this or other {@code from} methods
+ * keep adding items to the select list and do not replace previously contained items.
*
* @param tables the tables to {@code SELECT … FROM} must not be {@literal null}.
* @return {@code this} builder.
@@ -255,8 +262,8 @@ public interface SelectBuilder {
SelectFromAndJoin from(Table... tables);
/**
- * Declare one or more {@link Table}s to {@code SELECT … FROM}.
- * Multiple calls to this or other {@code from} methods keep adding items to the select list and do not replace previously contained items.
+ * Declare one or more {@link Table}s to {@code SELECT … FROM}. Multiple calls to this or other {@code from} methods
+ * keep adding items to the select list and do not replace previously contained items.
*
* @param tables the tables to {@code SELECT … FROM} must not be {@literal null}.
* @return {@code this} builder.
@@ -267,8 +274,8 @@ public interface SelectBuilder {
SelectFromAndJoin from(Collection extends Table> tables);
/**
- * Apply {@code limit} and {@code offset} parameters to the select statement.
- * To read the first 20 rows from start use {@code limitOffset(20, 0)}. to read the next 20 use {@code limitOffset(20, 20)}.
+ * Apply {@code limit} and {@code offset} parameters to the select statement. To read the first 20 rows from start
+ * use {@code limitOffset(20, 0)}. to read the next 20 use {@code limitOffset(20, 20)}.
*
* @param limit rows to read.
* @param offset row offset, zero-based.
@@ -294,13 +301,14 @@ public interface SelectBuilder {
}
/**
- * Builder exposing join/where/and {@code JOIN … ON} continuation methods.
+ * Builder exposing {@code FROM}, {@code WHERE}, {@code LIMIT/OFFSET}, and JOIN {@code AND} continuation methods.
*/
- interface SelectFromAndJoinCondition extends BuildSelect, SelectJoin, SelectWhere, SelectOnCondition, SelectLimitOffset {
+ interface SelectFromAndJoinCondition
+ extends BuildSelect, SelectJoin, SelectWhere, SelectOnCondition, SelectLimitOffset {
/**
- * Apply {@code limit} and {@code offset} parameters to the select statement.
- * To read the first 20 rows from start use {@code limitOffset(20, 0)}. to read the next 20 use {@code limitOffset(20, 20)}.
+ * Apply {@code limit} and {@code offset} parameters to the select statement. To read the first 20 rows from start
+ * use {@code limitOffset(20, 0)}. to read the next 20 use {@code limitOffset(20, 20)}.
*
* @param limit rows to read.
* @param offset row offset, zero-based.
@@ -331,8 +339,8 @@ public interface SelectBuilder {
interface SelectLimitOffset {
/**
- * Apply {@code limit} and {@code offset} parameters to the select statement.
- * To read the first 20 rows from start use {@code limitOffset(20, 0)}. to read the next 20 use {@code limitOffset(20, 20)}.
+ * Apply {@code limit} and {@code offset} parameters to the select statement. To read the first 20 rows from start
+ * use {@code limitOffset(20, 0)}. to read the next 20 use {@code limitOffset(20, 20)}.
*
* @param limit rows to read.
* @param offset row offset, zero-based.
@@ -404,7 +412,7 @@ public interface SelectBuilder {
}
/**
- * Interface exposing {@code AND}/{@code OR} combinatior methods for {@code WHERE} {@link Condition}s.
+ * Interface exposing {@code AND}/{@code OR} combinator methods for {@code WHERE} {@link Condition}s.
*/
interface SelectWhereAndOr extends SelectOrdered, BuildSelect {
@@ -458,7 +466,6 @@ public interface SelectBuilder {
*/
interface SelectOn {
-
/**
* Declare the source column in the {@code JOIN}.
*
@@ -505,7 +512,8 @@ public interface SelectBuilder {
interface BuildSelect {
/**
- * Build the {@link Select} statement and verify basic relationship constraints such as all referenced columns have a {@code FROM} or {@code JOIN} table import.
+ * Build the {@link Select} statement and verify basic relationship constraints such as all referenced columns have
+ * a {@code FROM} or {@code JOIN} table import.
*
* @return the build and immutable {@link Select} statement.
*/
diff --git a/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/SelectList.java b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/SelectList.java
new file mode 100644
index 00000000..1e8681ce
--- /dev/null
+++ b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/SelectList.java
@@ -0,0 +1,45 @@
+/*
+ * 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.List;
+
+import org.springframework.util.StringUtils;
+
+/**
+ * Value object representing the select list (selected columns, functions).
+ *
+ * @author Mark Paluch
+ * @since 1.1
+ */
+public class SelectList extends AbstractSegment {
+
+ private final List selectList;
+
+ SelectList(List selectList) {
+ super(selectList.toArray(new Expression[0]));
+ this.selectList = selectList;
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see java.lang.Object#toString()
+ */
+ @Override
+ public String toString() {
+ return StringUtils.collectionToDelimitedString(selectList, ", ");
+ }
+}
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 f760b019..e1f92578 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
@@ -19,7 +19,13 @@ import java.util.HashSet;
import java.util.Set;
/**
+ * Validator for {@link Select} statements.
+ *
+ * Validates that all {@link Column}s using a table qualifier have a table import from either the {@code FROM} or
+ * {@code JOIN} clause.
+ *
* @author Mark Paluch
+ * @since 1.1
*/
class SelectValidator implements Visitor {
@@ -81,8 +87,7 @@ class SelectValidator implements Visitor {
selectFieldCount++;
}
- if (segment instanceof Column
- && (parent instanceof Select || parent instanceof SimpleFunction)) {
+ if (segment instanceof Column && (parent instanceof Select || parent instanceof SimpleFunction)) {
selectFieldCount++;
Table table = ((Column) segment).getTable();
diff --git a/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/SimpleCondition.java b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/SimpleCondition.java
index 6a0d7853..517029ed 100644
--- a/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/SimpleCondition.java
+++ b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/SimpleCondition.java
@@ -15,12 +15,11 @@
*/
package org.springframework.data.relational.core.sql;
-import org.springframework.util.Assert;
-
/**
* Simple condition consisting of {@link Expression}, {@code comparator} and {@code predicate}.
*
* @author Mark Paluch
+ * @since 1.1
*/
public class SimpleCondition extends AbstractSegment implements Condition {
diff --git a/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/SimpleFunction.java b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/SimpleFunction.java
index b7ac738c..c5c7d31a 100644
--- a/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/SimpleFunction.java
+++ b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/SimpleFunction.java
@@ -24,20 +24,36 @@ import org.springframework.util.StringUtils;
* Simple function accepting one or more {@link Expression}s.
*
* @author Mark Paluch
+ * @since 1.1
*/
public class SimpleFunction extends AbstractSegment implements Expression {
private String functionName;
private List expressions;
- SimpleFunction(String functionName, List expressions) {
+ private SimpleFunction(String functionName, List expressions) {
- super(expressions.toArray(new Expression[]{}));
+ super(expressions.toArray(new Expression[0]));
this.functionName = functionName;
this.expressions = expressions;
}
+ /**
+ * Creates a new {@link SimpleFunction} given {@code functionName} and {@link List} of {@link Expression}s.
+ *
+ * @param functionName must not be {@literal null}.
+ * @param expressions zero or many {@link Expression}s, must not be {@literal null}.
+ * @return
+ */
+ public static SimpleFunction create(String functionName, List expressions) {
+
+ Assert.hasText(functionName, "Function name must not be null or empty");
+ Assert.notNull(expressions, "Expressions name must not be null");
+
+ return new SimpleFunction(functionName, expressions);
+ }
+
/**
* Expose this function result under a column {@code alias}.
*
@@ -79,6 +95,10 @@ public class SimpleFunction extends AbstractSegment implements Expression {
this.alias = alias;
}
+ /*
+ * (non-Javadoc)
+ * @see org.springframework.data.relational.core.sql.Aliased#getAlias()
+ */
@Override
public String getAlias() {
return alias;
diff --git a/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/SimpleSegment.java b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/SimpleSegment.java
index ba705787..58a37a37 100644
--- a/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/SimpleSegment.java
+++ b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/SimpleSegment.java
@@ -17,12 +17,13 @@ package org.springframework.data.relational.core.sql;
/**
* @author Mark Paluch
+ * @since 1.1
*/
public class SimpleSegment extends AbstractSegment {
private final String sql;
- public SimpleSegment(String sql) {
+ SimpleSegment(String sql) {
this.sql = sql;
}
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
new file mode 100644
index 00000000..91168860
--- /dev/null
+++ b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/StatementBuilder.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.Collection;
+
+import org.springframework.data.relational.core.sql.SelectBuilder.SelectAndFrom;
+
+/**
+ * Entrypoint to build SQL statements.
+ *
+ * @author Mark Paluch
+ * @since 1.1
+ * @see SQL
+ * @see Expressions
+ * @see Conditions
+ * @see Functions
+ */
+public abstract class StatementBuilder {
+
+ /**
+ * Creates a new {@link SelectBuilder} by specifying a {@code SELECT} column.
+ *
+ * @param expression the select list expression.
+ * @return the {@link SelectBuilder} containing {@link Expression}.
+ * @see SelectBuilder#select(Expression)
+ */
+ public static SelectAndFrom select(Expression expression) {
+ return Select.builder().select(expression);
+ }
+
+ /**
+ * Creates a new {@link SelectBuilder} by specifying one or more {@code SELECT} columns.
+ *
+ * @param expressions the select list expressions.
+ * @return the {@link SelectBuilder} containing {@link Expression}s.
+ * @see SelectBuilder#select(Expression...)
+ */
+ public static SelectAndFrom select(Expression... expressions) {
+ return Select.builder().select(expressions);
+ }
+
+ /**
+ * Include one or more {@link Expression}s in the select list.
+ *
+ * @param expressions the expressions to include.
+ * @return {@code this} builder.
+ * @see Table#columns(String...)
+ */
+ public static SelectAndFrom select(Collection extends Expression> expressions) {
+ return Select.builder().select(expressions);
+ }
+
+ /**
+ * Creates a new {@link SelectBuilder}.
+ *
+ * @return the new {@link SelectBuilder}.
+ * @see SelectBuilder
+ */
+ public static SelectBuilder select() {
+ return Select.builder();
+ }
+
+ private StatementBuilder() {
+
+ }
+}
diff --git a/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/SubselectExpression.java b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/SubselectExpression.java
index 7671ba89..1b5b61ed 100644
--- a/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/SubselectExpression.java
+++ b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/SubselectExpression.java
@@ -16,19 +16,26 @@
package org.springframework.data.relational.core.sql;
/**
+ * Wrapper for a {@link Select} query to be used as subselect.
+ *
* @author Jens Schauder
+ * @since 1.1
*/
public class SubselectExpression extends AbstractSegment implements Expression {
private final Select subselect;
- public SubselectExpression(Select subselect) {
+ SubselectExpression(Select subselect) {
super(subselect);
this.subselect = subselect;
}
+ /*
+ * (non-Javadoc)
+ * @see java.lang.Object#toString()
+ */
@Override
public String toString() {
return "(" + subselect.toString() + ")";
diff --git a/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/Table.java b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/Table.java
index aaa7fc6c..357cc5d6 100644
--- a/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/Table.java
+++ b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/Table.java
@@ -16,6 +16,8 @@
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;
@@ -27,6 +29,7 @@ import org.springframework.util.Assert;
* Renders to: {@code } or {@code AS }.
*
* @author Mark Paluch
+ * @since 1.1
*/
public class Table extends AbstractSegment {
@@ -66,7 +69,7 @@ public class Table extends AbstractSegment {
}
/**
- * Create a new {@link Table} aliased to {@code alias}.
+ * Creates a new {@link Table} aliased to {@code alias}.
*
* @param alias must not be {@literal null} or empty.
* @return the new {@link Table} using the {@code alias}.
@@ -79,7 +82,7 @@ public class Table extends AbstractSegment {
}
/**
- * Create a new {@link Column} associated with this {@link Table}.
+ * Creates a new {@link Column} associated with this {@link Table}.
*
* Note: This {@link Table} does not track column creation and there is no possibility to enumerate all
* {@link Column}s that were created for this table.
@@ -95,7 +98,7 @@ public class Table extends AbstractSegment {
}
/**
- * Create a {@link List} of {@link Column}s associated with this {@link Table}.
+ * Creates a {@link List} of {@link Column}s associated with this {@link Table}.
*
* Note: This {@link Table} does not track column creation and there is no possibility to enumerate all
* {@link Column}s that were created for this table.
@@ -107,6 +110,22 @@ public class Table extends AbstractSegment {
Assert.notNull(names, "Names must not be null");
+ return columns(Arrays.asList(names));
+ }
+
+ /**
+ * Creates a {@link List} of {@link Column}s associated with this {@link Table}.
+ *
+ * Note: This {@link Table} does not track column creation and there is no possibility to enumerate all
+ * {@link Column}s that were created for this table.
+ *
+ * @param names column names, must not be {@literal null} or empty.
+ * @return a new {@link List} of {@link Column}s associated with this {@link Table}.
+ */
+ public List columns(Collection names) {
+
+ Assert.notNull(names, "Names must not be null");
+
List columns = new ArrayList<>();
for (String name : names) {
columns.add(column(name));
@@ -117,7 +136,8 @@ public class Table extends AbstractSegment {
/**
* Creates a {@link AsteriskFromTable} maker selecting all columns from this {@link Table} (e.g. {@code SELECT
- *
+ *
+
* .*}.
*
* @return the select all marker for this {@link Table}.
diff --git a/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/Visitable.java b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/Visitable.java
index 2ac7f989..4ebb993e 100644
--- a/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/Visitable.java
+++ b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/Visitable.java
@@ -21,12 +21,13 @@ import org.springframework.util.Assert;
* Interface for implementations that wish to be visited by a {@link Visitor}.
*
* @author Mark Paluch
+ * @since 1.1
* @see Visitor
*/
public interface Visitable {
/**
- * Accept a {@link Visitor} visiting this {@link Segment} and its nested {@link Segment}s if applicable.
+ * Accept a {@link Visitor} visiting this {@link Visitable} and its nested {@link Visitable}s if applicable.
*
* @param visitor the visitor to notify, must not be {@literal null}.
*/
diff --git a/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/Visitor.java b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/Visitor.java
index 6d6eff53..0b6d9591 100644
--- a/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/Visitor.java
+++ b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/Visitor.java
@@ -16,26 +16,26 @@
package org.springframework.data.relational.core.sql;
/**
- * AST {@link Segment} visitor. Visitor methods get called by segments on entering a {@link Segment}, their child {@link Segment}s and on leaving the {@link Segment}.
+ * AST {@link Segment} visitor. Visitor methods get called by segments on entering a {@link Visitable}, their child
+ * {@link Visitable}s and on leaving the {@link Visitable}.
*
* @author Mark Paluch
+ * @since 1.1
*/
@FunctionalInterface
public interface Visitor {
/**
- * Enter a {@link Segment}.
+ * Enter a {@link Visitable}.
*
* @param segment the segment to visit.
*/
void enter(Visitable segment);
/**
- * Leave a {@link Segment}.
+ * Leave a {@link Visitable}.
*
* @param segment the visited segment.
*/
- default void leave(Visitable segment) {
-
- }
+ default void leave(Visitable segment) {}
}
diff --git a/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/Where.java b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/Where.java
index 42cc266f..e6d0b34d 100644
--- a/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/Where.java
+++ b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/Where.java
@@ -15,12 +15,11 @@
*/
package org.springframework.data.relational.core.sql;
-import org.springframework.util.Assert;
-
/**
* {@code Where} clause.
*
* @author Mark Paluch
+ * @since 1.1
*/
public class Where extends AbstractSegment {
@@ -33,6 +32,10 @@ public class Where extends AbstractSegment {
this.condition = condition;
}
+ /*
+ * (non-Javadoc)
+ * @see java.lang.Object#toString()
+ */
@Override
public String toString() {
return "WHERE " + condition.toString();
diff --git a/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/package-info.java b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/package-info.java
index 62f340aa..a6801860 100644
--- a/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/package-info.java
+++ b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/package-info.java
@@ -1,24 +1,16 @@
-/*
- * 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.
- */
/**
- * Query Builder AST. Use {@link org.springframework.data.relational.core.sql.SQL} as entry point to create SQL objects. Objects and dependent objects created by the Query AST are immutable except for builders.
- * The Query Builder API is intended for framework usage to produce SQL required for framework operations.
+ * Statement Builder implementation. Use {@link org.springframework.data.relational.core.sql.StatementBuilder} to create
+ * statements and {@link org.springframework.data.relational.core.sql.SQL} to create SQL objects. Objects and dependent
+ * objects created by the Statement Builder are immutable except for builders.
+ *
+ * The Statement Builder API is intended for framework usage to produce SQL required for framework operations.
+ *
+ * @since 1.1
*/
@NonNullApi
+@NonNullFields
package org.springframework.data.relational.core.sql;
import org.springframework.lang.NonNullApi;
+import org.springframework.lang.NonNullFields;
diff --git a/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/render/ComparisonVisitor.java b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/render/ComparisonVisitor.java
new file mode 100644
index 00000000..cd5f882e
--- /dev/null
+++ b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/render/ComparisonVisitor.java
@@ -0,0 +1,100 @@
+/*
+ * 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.Comparison;
+import org.springframework.data.relational.core.sql.Condition;
+import org.springframework.data.relational.core.sql.Expression;
+import org.springframework.data.relational.core.sql.Visitable;
+import org.springframework.lang.Nullable;
+
+/**
+ * {@link org.springframework.data.relational.core.sql.Visitor} rendering comparison {@link Condition}. Uses a
+ * {@link RenderTarget} to call back for render results.
+ *
+ * @author Mark Paluch
+ * @author Jens Schauder
+ * @since 1.1
+ * @see Comparison
+ */
+class ComparisonVisitor extends FilteredSubtreeVisitor {
+
+ private final RenderContext context;
+ private final Comparison condition;
+ private final RenderTarget target;
+ private final StringBuilder part = new StringBuilder();
+ private @Nullable PartRenderer current;
+
+ ComparisonVisitor(RenderContext context, Comparison condition, RenderTarget target) {
+ super(it -> it == condition);
+ this.condition = condition;
+ this.target = target;
+ this.context = context;
+ }
+
+ /*
+ * (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 Expression) {
+ ExpressionVisitor visitor = new ExpressionVisitor(context);
+ current = visitor;
+ return Delegation.delegateTo(visitor);
+ }
+
+ if (segment instanceof Condition) {
+ ConditionVisitor visitor = new ConditionVisitor(context);
+ current = visitor;
+ return Delegation.delegateTo(visitor);
+ }
+
+ 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 (current != null) {
+ if (part.length() != 0) {
+ part.append(' ').append(condition.getComparator()).append(' ');
+ }
+
+ part.append(current.getRenderedPart());
+ current = null;
+ }
+
+ 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(Visitable segment) {
+
+ target.onRendered(part);
+
+ return super.leaveMatched(segment);
+ }
+}
diff --git a/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/render/ConditionVisitor.java b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/render/ConditionVisitor.java
new file mode 100644
index 00000000..a6b22eec
--- /dev/null
+++ b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/render/ConditionVisitor.java
@@ -0,0 +1,100 @@
+/*
+ * 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.AndCondition;
+import org.springframework.data.relational.core.sql.Comparison;
+import org.springframework.data.relational.core.sql.Condition;
+import org.springframework.data.relational.core.sql.In;
+import org.springframework.data.relational.core.sql.IsNull;
+import org.springframework.data.relational.core.sql.Like;
+import org.springframework.data.relational.core.sql.OrCondition;
+import org.springframework.lang.Nullable;
+
+/**
+ * {@link org.springframework.data.relational.core.sql.Visitor} delegating {@link Condition} rendering to condition
+ * {@link org.springframework.data.relational.core.sql.Visitor}s.
+ *
+ * @author Mark Paluch
+ * @author Jens Schauder
+ * @since 1.1
+ * @see AndCondition
+ * @see OrCondition
+ * @see IsNull
+ * @see Comparison
+ * @see Like
+ * @see In
+ */
+class ConditionVisitor extends TypedSubtreeVisitor implements PartRenderer {
+
+ private final RenderContext context;
+ private StringBuilder builder = new StringBuilder();
+
+ ConditionVisitor(RenderContext context) {
+ this.context = context;
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see org.springframework.data.relational.core.sql.render.TypedSubtreeVisitor#enterMatched(org.springframework.data.relational.core.sql.Visitable)
+ */
+ @Override
+ Delegation enterMatched(Condition segment) {
+
+ DelegatingVisitor visitor = getDelegation(segment);
+
+ return visitor != null ? Delegation.delegateTo(visitor) : Delegation.retain();
+ }
+
+ @Nullable
+ private DelegatingVisitor getDelegation(Condition segment) {
+
+ if (segment instanceof AndCondition) {
+ return new MultiConcatConditionVisitor(context, (AndCondition) segment, builder::append);
+ }
+
+ if (segment instanceof OrCondition) {
+ return new MultiConcatConditionVisitor(context, (OrCondition) segment, builder::append);
+ }
+
+ if (segment instanceof IsNull) {
+ return new IsNullVisitor(context, builder::append);
+ }
+
+ if (segment instanceof Comparison) {
+ return new ComparisonVisitor(context, (Comparison) segment, builder::append);
+ }
+
+ if (segment instanceof Like) {
+ return new LikeVisitor((Like) segment, context, builder::append);
+ }
+
+ if (segment instanceof In) {
+ return new InVisitor(context, builder::append);
+ }
+
+ return null;
+ }
+
+ /*
+ * (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/DelegatingVisitor.java b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/render/DelegatingVisitor.java
new file mode 100644
index 00000000..a04be55c
--- /dev/null
+++ b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/render/DelegatingVisitor.java
@@ -0,0 +1,198 @@
+/*
+ * 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 java.util.Stack;
+
+import org.springframework.data.relational.core.sql.Visitable;
+import org.springframework.data.relational.core.sql.Visitor;
+import org.springframework.lang.Nullable;
+import org.springframework.util.Assert;
+
+/**
+ * Abstract base class for delegating {@link Visitor} implementations. This class implements a delegation pattern using
+ * visitors. A delegating {@link Visitor} can implement {@link #doEnter(Visitable)} and {@link #doLeave(Visitable)}
+ * methods to provide its functionality.
+ *
+ * Delegation
Typically, a {@link Visitor} is scoped to a single responsibility. If a {@link Visitor segment}
+ * requires {@link #doEnter(Visitable) processing} that is not directly implemented by the visitor itself, the current
+ * {@link Visitor} can delegate processing to a {@link DelegatingVisitor delegate}. Once a delegation is installed, the
+ * {@link DelegatingVisitor delegate} is used as {@link Visitor} for the current and all subsequent items until it
+ * {@link #doLeave(Visitable) signals} that it is no longer responsible.
+ *
+ * Nested visitors are required to properly signal once they are no longer responsible for a {@link Visitor segment} to
+ * step back from the delegation. Otherwise, parents are no longer involved in the visitation.
+ *
+ * Delegation is recursive and limited by the stack size.
+ *
+ * @author Mark Paluch
+ * @since 1.1
+ * @see FilteredSubtreeVisitor
+ * @see TypedSubtreeVisitor
+ */
+abstract class DelegatingVisitor implements Visitor {
+
+ private Stack delegation = new Stack<>();
+
+ /**
+ * Invoked for a {@link Visitable segment} when entering the segment.
+ *
+ * This method can signal whether it is responsible for handling the {@link Visitor segment} or whether the segment
+ * requires delegation to a sub-{@link Visitor}. When delegating to a sub-{@link Visitor}, {@link #doEnter(Visitable)}
+ * is called on the {@link DelegatingVisitor delegate}.
+ *
+ * @param segment must not be {@literal null}.
+ * @return
+ */
+ @Nullable
+ public abstract Delegation doEnter(Visitable segment);
+
+ /*
+ * (non-Javadoc)
+ * @see org.springframework.data.relational.core.sql.Visitor#enter(org.springframework.data.relational.core.sql.Visitable)
+ */
+ @Override
+ public final void enter(Visitable segment) {
+
+ if (delegation.isEmpty()) {
+
+ Delegation visitor = doEnter(segment);
+ Assert.notNull(visitor,
+ () -> String.format("Visitor must not be null. Caused by %s.doEnter(…)", getClass().getName()));
+ Assert.state(!visitor.isLeave(),
+ () -> String.format("Delegation indicates leave. Caused by %s.doEnter(…)", getClass().getName()));
+
+ if (visitor.isDelegate()) {
+ delegation.push(visitor.getDelegate());
+ visitor.getDelegate().enter(segment);
+ }
+ } else {
+ delegation.peek().enter(segment);
+ }
+ }
+
+ /**
+ * Invoked for a {@link Visitable segment} when leaving the segment.
+ *
+ * This method can signal whether this {@link Visitor} should remain responsible for handling subsequent
+ * {@link Visitor segments} or whether it should step back from delegation. When stepping back from delegation,
+ * {@link #doLeave(Visitable)} is called on the {@link DelegatingVisitor parent delegate}.
+ *
+ * @param segment must not be {@literal null}.
+ * @return
+ */
+ public abstract Delegation doLeave(Visitable segment);
+
+ /*
+ * (non-Javadoc)
+ * @see org.springframework.data.relational.core.sql.Visitor#leave(org.springframework.data.relational.core.sql.Visitable)
+ */
+ public final void leave(Visitable segment) {
+ doLeave0(segment);
+ }
+
+ private Delegation doLeave0(Visitable segment) {
+
+ if (delegation.isEmpty()) {
+ return doLeave(segment);
+ } else {
+
+ DelegatingVisitor visitor = delegation.peek();
+ while (visitor != null) {
+
+ Delegation result = visitor.doLeave0(segment);
+ Assert.notNull(visitor,
+ () -> String.format("Visitor must not be null. Caused by %s.doLeave(…)", getClass().getName()));
+
+ if (visitor == this) {
+ if (result.isLeave()) {
+ return delegation.isEmpty() ? Delegation.leave() : Delegation.retain();
+ }
+ return Delegation.retain();
+ }
+
+ if (result.isRetain()) {
+ return result;
+ }
+
+ if (result.isLeave()) {
+
+ if (!delegation.isEmpty()) {
+ delegation.pop();
+ }
+
+ if (!delegation.isEmpty()) {
+ visitor = delegation.peek();
+ } else {
+ visitor = this;
+ }
+ }
+ }
+ }
+
+ return Delegation.leave();
+ }
+
+ /**
+ * Value object to control delegation.
+ */
+ static class Delegation {
+
+ private static Delegation RETAIN = new Delegation(true, false, null);
+ private static Delegation LEAVE = new Delegation(false, true, null);
+
+ private final boolean retain;
+ private final boolean leave;
+
+ private final @Nullable DelegatingVisitor delegate;
+
+ private Delegation(boolean retain, boolean leave, @Nullable DelegatingVisitor delegate) {
+ this.retain = retain;
+ this.leave = leave;
+ this.delegate = delegate;
+ }
+
+ public static Delegation retain() {
+ return RETAIN;
+ }
+
+ public static Delegation leave() {
+ return LEAVE;
+ }
+
+ public static Delegation delegateTo(DelegatingVisitor visitor) {
+ return new Delegation(false, false, visitor);
+ }
+
+ boolean isDelegate() {
+ return delegate != null;
+ }
+
+ boolean isRetain() {
+ return retain;
+ }
+
+ boolean isLeave() {
+ return leave;
+ }
+
+ DelegatingVisitor getDelegate() {
+
+ Assert.state(isDelegate(), "No delegate available");
+ return delegate;
+ }
+ }
+}
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
new file mode 100644
index 00000000..604190e0
--- /dev/null
+++ b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/render/ExpressionVisitor.java
@@ -0,0 +1,118 @@
+/*
+ * 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.BindMarker;
+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.Named;
+import org.springframework.data.relational.core.sql.SubselectExpression;
+import org.springframework.data.relational.core.sql.Visitable;
+import org.springframework.lang.Nullable;
+
+/**
+ * {@link PartRenderer} for {@link Expression}s.
+ *
+ * @author Mark Paluch
+ * @author Jens Schauder
+ * @since 1.1
+ * @see Column
+ * @see SubselectExpression
+ */
+class ExpressionVisitor extends TypedSubtreeVisitor implements PartRenderer {
+
+ private final RenderContext context;
+
+ private CharSequence value = "";
+ private @Nullable PartRenderer partRenderer;
+
+ ExpressionVisitor(RenderContext context) {
+ this.context = context;
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see org.springframework.data.relational.core.sql.render.TypedSubtreeVisitor#enterMatched(org.springframework.data.relational.core.sql.Visitable)
+ */
+ @Override
+ Delegation enterMatched(Expression segment) {
+
+ if (segment instanceof SubselectExpression) {
+
+ SelectStatementVisitor visitor = new SelectStatementVisitor(context);
+ partRenderer = visitor;
+ return Delegation.delegateTo(visitor);
+ }
+
+ if (segment instanceof Column) {
+
+ RenderNamingStrategy namingStrategy = context.getNamingStrategy();
+ Column column = (Column) segment;
+
+ value = namingStrategy.getReferenceName(column.getTable()) + "." + namingStrategy.getReferenceName(column);
+ } else if (segment instanceof BindMarker) {
+
+ if (segment instanceof Named) {
+ value = ((Named) segment).getName();
+ } else {
+ value = segment.toString();
+ }
+ }
+
+ return Delegation.retain();
+ }
+
+ /*
+ * (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 Condition) {
+ ConditionVisitor visitor = new ConditionVisitor(context);
+ partRenderer = visitor;
+ return Delegation.delegateTo(visitor);
+ }
+
+ return super.enterNested(segment);
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see org.springframework.data.relational.core.sql.render.TypedSubtreeVisitor#leaveMatched(org.springframework.data.relational.core.sql.Visitable)
+ */
+ @Override
+ Delegation leaveMatched(Expression segment) {
+
+ if (partRenderer != null) {
+ value = partRenderer.getRenderedPart();
+ partRenderer = null;
+ }
+
+ return super.leaveMatched(segment);
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see org.springframework.data.relational.core.sql.render.PartRenderer#getRenderedPart()
+ */
+ @Override
+ public CharSequence getRenderedPart() {
+ return value;
+ }
+}
diff --git a/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/render/FilteredSingleConditionRenderSupport.java b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/render/FilteredSingleConditionRenderSupport.java
new file mode 100644
index 00000000..99f9b5df
--- /dev/null
+++ b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/render/FilteredSingleConditionRenderSupport.java
@@ -0,0 +1,96 @@
+/*
+ * 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 java.util.function.Predicate;
+
+import org.springframework.data.relational.core.sql.Condition;
+import org.springframework.data.relational.core.sql.Expression;
+import org.springframework.data.relational.core.sql.Visitable;
+import org.springframework.util.Assert;
+
+/**
+ * Support class for {@link FilteredSubtreeVisitor filtering visitors} that want to render a single {@link Condition}
+ * and delegate nested {@link Expression} and {@link Condition} rendering.
+ *
+ * @author Mark Paluch
+ * @since 1.1
+ */
+abstract class FilteredSingleConditionRenderSupport extends FilteredSubtreeVisitor {
+
+ private final RenderContext context;
+ private PartRenderer current;
+
+ /**
+ * Creates a new {@link FilteredSingleConditionRenderSupport} given the filter {@link Predicate}.
+ *
+ * @param context
+ * @param filter filter predicate to identify when to {@link #enterMatched(Visitable)
+ * enter}/{@link #leaveMatched(Visitable) leave} the {@link Visitable segment} that this visitor is
+ * responsible for.
+ */
+ FilteredSingleConditionRenderSupport(RenderContext context, Predicate filter) {
+ super(filter);
+ this.context = context;
+ }
+
+ /*
+ * (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 Expression) {
+ ExpressionVisitor visitor = new ExpressionVisitor(context);
+ current = visitor;
+ return Delegation.delegateTo(visitor);
+ }
+
+ if (segment instanceof Condition) {
+ ConditionVisitor visitor = new ConditionVisitor(context);
+ current = visitor;
+ return Delegation.delegateTo(visitor);
+ }
+
+ throw new IllegalStateException("Cannot provide visitor for " + segment);
+ }
+
+ /**
+ * Returns whether rendering was delegated to a {@link ExpressionVisitor} or {@link ConditionVisitor}.
+ *
+ * @return {@literal true} when rendering was delegated to a {@link ExpressionVisitor} or {@link ConditionVisitor}.
+ */
+ protected boolean hasDelegatedRendering() {
+ return current != null;
+ }
+
+ /**
+ * Consumes the delegated rendering part. Call {@link #hasDelegatedRendering()} to check whether rendering was
+ * actually delegated. Consumption releases the delegated rendered.
+ *
+ * @return the delegated rendered part.
+ * @throws IllegalStateException if rendering was not delegate.
+ */
+ protected CharSequence consumeRenderedPart() {
+
+ Assert.state(hasDelegatedRendering(), "Rendering not delegated. Cannot consume delegated rendering part.");
+
+ PartRenderer current = this.current;
+ this.current = null;
+ return current.getRenderedPart();
+ }
+}
diff --git a/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/render/FilteredSubtreeVisitor.java b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/render/FilteredSubtreeVisitor.java
new file mode 100644
index 00000000..6b370616
--- /dev/null
+++ b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/render/FilteredSubtreeVisitor.java
@@ -0,0 +1,146 @@
+/*
+ * 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 java.util.function.Predicate;
+
+import org.springframework.data.relational.core.sql.Visitable;
+import org.springframework.data.relational.core.sql.Visitor;
+import org.springframework.lang.Nullable;
+
+/**
+ * Filtering {@link DelegatingVisitor visitor} applying a {@link Predicate filter}. Typically used as base class for
+ * {@link Visitor visitors} that wish to apply hierarchical processing based on a well-defined entry {@link Visitor
+ * segment}.
+ *
+ * Filtering is a three-way process:
+ *
+ * - Ignores elements that do not match the filter {@link Predicate}.
+ * - {@link #enterMatched(Visitable) enter}/{@link #leaveMatched(Visitable) leave} matched callbacks for the
+ * {@link Visitable segment} that matches the {@link Predicate}.
+ * - {@link #enterNested(Visitable) enter}/{@link #leaveNested(Visitable) leave} nested callbacks for direct/nested
+ * children of the matched {@link Visitable} until {@link #leaveMatched(Visitable) leaving the matched}
+ * {@link Visitable}.
+ *
+ *
+ * @author Mark Paluch
+ * @see TypedSubtreeVisitor
+ * @since 1.1
+ */
+abstract class FilteredSubtreeVisitor extends DelegatingVisitor {
+
+ private final Predicate filter;
+
+ private @Nullable Visitable currentSegment;
+
+ /**
+ * Creates a new {@link FilteredSubtreeVisitor} given the filter {@link Predicate}.
+ *
+ * @param filter filter predicate to identify when to {@link #enterMatched(Visitable)
+ * enter}/{@link #leaveMatched(Visitable) leave} the {@link Visitable segment} that this visitor is
+ * responsible for.
+ */
+ FilteredSubtreeVisitor(Predicate filter) {
+ this.filter = filter;
+ }
+
+ /**
+ * {@link Visitor#enter(Visitable) Enter} callback for a {@link Visitable} that this {@link Visitor} is responsible
+ * for. The default implementation retains delegation by default.
+ *
+ * @param segment the segment, must not be {@literal null}.
+ * @return delegation options. Can be either {@link Delegation#retain()} or
+ * {@link Delegation#delegateTo(DelegatingVisitor)}.
+ * @see Delegation#retain()
+ */
+ Delegation enterMatched(Visitable segment) {
+ return Delegation.retain();
+ }
+
+ /**
+ * {@link Visitor#enter(Visitable) Enter} callback for a nested {@link Visitable}. The default implementation retains
+ * delegation by default.
+ *
+ * @param segment the segment, must not be {@literal null}.
+ * @return delegation options. Can be either {@link Delegation#retain()} or
+ * {@link Delegation#delegateTo(DelegatingVisitor)}.
+ * @see Delegation#retain()
+ */
+ Delegation enterNested(Visitable segment) {
+ return Delegation.retain();
+ }
+
+ /**
+ * {@link Visitor#leave(Visitable) Leave} callback for the matched {@link Visitable}. The default implementation steps
+ * back from delegation by default.
+ *
+ * @param segment the segment, must not be {@literal null}.
+ * @return delegation options. Can be either {@link Delegation#retain()} or {@link Delegation#leave()}.
+ * @see Delegation#leave()
+ */
+ Delegation leaveMatched(Visitable segment) {
+ return Delegation.leave();
+ }
+
+ /**
+ * {@link Visitor#leave(Visitable) Leave} callback for a nested {@link Visitable}. The default implementation retains
+ * delegation by default.
+ *
+ * @param segment the segment, must not be {@literal null}.
+ * @return delegation options. Can be either {@link Delegation#retain()} or {@link Delegation#leave()}.
+ * @see Delegation#retain()
+ */
+ Delegation leaveNested(Visitable segment) {
+ return Delegation.retain();
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see org.springframework.data.relational.core.sql.render.DelegatingVisitor#doEnter(org.springframework.data.relational.core.sql.Visitable)
+ */
+ @Override
+ public final Delegation doEnter(Visitable segment) {
+
+ if (currentSegment == null) {
+
+ if (filter.test(segment)) {
+ currentSegment = segment;
+ return enterMatched(segment);
+ }
+ } else {
+ return enterNested(segment);
+ }
+
+ return Delegation.retain();
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see org.springframework.data.relational.core.sql.render.DelegatingVisitor#doLeave(org.springframework.data.relational.core.sql.Visitable)
+ */
+ @Override
+ public final Delegation doLeave(Visitable segment) {
+
+ if (currentSegment == null) {
+ return Delegation.leave();
+ } else if (segment == currentSegment) {
+ currentSegment = null;
+ return leaveMatched(segment);
+ } else {
+ return leaveNested(segment);
+ }
+ }
+}
diff --git a/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/render/FromClauseVisitor.java b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/render/FromClauseVisitor.java
new file mode 100644
index 00000000..2a334ac4
--- /dev/null
+++ b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/render/FromClauseVisitor.java
@@ -0,0 +1,69 @@
+/*
+ * 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.From;
+import org.springframework.data.relational.core.sql.Visitable;
+
+/**
+ * Renderer for {@link From}. Uses a {@link RenderTarget} to call back for render results.
+ *
+ * @author Mark Paluch
+ * @author Jens Schauder
+ * @since 1.1
+ */
+class FromClauseVisitor extends TypedSubtreeVisitor {
+
+ private final FromTableVisitor visitor;
+ private final RenderTarget parent;
+ private final StringBuilder builder = new StringBuilder();
+ private boolean first = true;
+
+ FromClauseVisitor(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(From segment) {
+ parent.onRendered(builder);
+ return super.leaveMatched(segment);
+ }
+}
diff --git a/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/render/FromTableVisitor.java b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/render/FromTableVisitor.java
new file mode 100644
index 00000000..7870c6cf
--- /dev/null
+++ b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/render/FromTableVisitor.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright 2019 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.springframework.data.relational.core.sql.render;
+
+import org.springframework.data.relational.core.sql.Aliased;
+import org.springframework.data.relational.core.sql.From;
+import org.springframework.data.relational.core.sql.Table;
+
+/**
+ * Renderer for {@link Table} used within a {@link From} clause. Uses a {@link RenderTarget} to call back for render
+ * results.
+ *
+ * @author Mark Paluch
+ * @author Jens Schauder
+ * @since 1.1
+ */
+class FromTableVisitor extends TypedSubtreeVisitor {
+
+ private final RenderContext context;
+ private final RenderTarget parent;
+
+ FromTableVisitor(RenderContext context, RenderTarget parent) {
+ super();
+ this.context = context;
+ this.parent = parent;
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see org.springframework.data.relational.core.sql.render.TypedSubtreeVisitor#enterMatched(org.springframework.data.relational.core.sql.Visitable)
+ */
+ @Override
+ Delegation enterMatched(Table segment) {
+
+ StringBuilder builder = new StringBuilder();
+
+ builder.append(context.getNamingStrategy().getName(segment));
+ if (segment instanceof Aliased) {
+ builder.append(" AS ").append(((Aliased) segment).getAlias());
+ }
+
+ parent.onRendered(builder);
+
+ return super.enterMatched(segment);
+ }
+}
diff --git a/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/render/InVisitor.java b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/render/InVisitor.java
new file mode 100644
index 00000000..3992f07f
--- /dev/null
+++ b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/render/InVisitor.java
@@ -0,0 +1,77 @@
+/*
+ * 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.In;
+import org.springframework.data.relational.core.sql.Visitable;
+
+/**
+ * Renderer for {@link In}. Uses a {@link RenderTarget} to call back for render results.
+ *
+ * @author Mark Paluch
+ * @author Jens Schauder
+ * @since 1.1
+ */
+class InVisitor extends TypedSingleConditionRenderSupport {
+
+ private final RenderTarget target;
+ private final StringBuilder part = new StringBuilder();
+ private boolean needsComma = false;
+
+ InVisitor(RenderContext context, RenderTarget target) {
+ super(context);
+ this.target = target;
+ }
+
+ /*
+ * (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 (hasDelegatedRendering()) {
+ CharSequence renderedPart = consumeRenderedPart();
+
+ if (needsComma) {
+ part.append(", ");
+ }
+
+ if (part.length() == 0) {
+ part.append(renderedPart);
+ part.append(" IN (");
+ } else {
+ part.append(renderedPart);
+ needsComma = true;
+ }
+ }
+
+ 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(In segment) {
+
+ part.append(")");
+ target.onRendered(part);
+
+ return super.leaveMatched(segment);
+ }
+}
diff --git a/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/render/IsNullVisitor.java b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/render/IsNullVisitor.java
new file mode 100644
index 00000000..4a551154
--- /dev/null
+++ b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/render/IsNullVisitor.java
@@ -0,0 +1,69 @@
+/*
+ * 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.IsNull;
+import org.springframework.data.relational.core.sql.Visitable;
+
+/**
+ * Renderer for {@link IsNull}. Uses a {@link RenderTarget} to call back for render results.
+ *
+ * @author Mark Paluch
+ * @author Jens Schauder
+ * @since 1.1
+ */
+class IsNullVisitor extends TypedSingleConditionRenderSupport {
+
+ private final RenderTarget target;
+ private final StringBuilder part = new StringBuilder();
+
+ IsNullVisitor(RenderContext context, RenderTarget target) {
+ super(context);
+ this.target = target;
+ }
+
+ /*
+ * (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 (hasDelegatedRendering()) {
+ part.append(consumeRenderedPart());
+ }
+
+ 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(IsNull segment) {
+
+ if (segment.isNegated()) {
+ part.append(" IS NOT NULL");
+ } else {
+ part.append(" IS NULL");
+ }
+
+ target.onRendered(part);
+
+ return super.leaveMatched(segment);
+ }
+}
diff --git a/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/render/JoinVisitor.java b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/render/JoinVisitor.java
new file mode 100644
index 00000000..c174c35d
--- /dev/null
+++ b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/render/JoinVisitor.java
@@ -0,0 +1,104 @@
+/*
+ * 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.Aliased;
+import org.springframework.data.relational.core.sql.Condition;
+import org.springframework.data.relational.core.sql.Join;
+import org.springframework.data.relational.core.sql.Table;
+import org.springframework.data.relational.core.sql.Visitable;
+
+/**
+ * Renderer for {@link Join} segments. Uses a {@link RenderTarget} to call back for render results.
+ *
+ * @author Mark Paluch
+ * @author Jens Schauder
+ * @since 1.1
+ */
+class JoinVisitor extends TypedSubtreeVisitor {
+
+ private final RenderContext context;
+ private final RenderTarget parent;
+ private final StringBuilder joinClause = new StringBuilder();
+ private boolean inCondition = false;
+ private boolean hasSeenCondition = false;
+
+ JoinVisitor(RenderContext context, RenderTarget parent) {
+ this.context = context;
+ this.parent = parent;
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see org.springframework.data.relational.core.sql.render.TypedSubtreeVisitor#enterMatched(org.springframework.data.relational.core.sql.Visitable)
+ */
+ @Override
+ Delegation enterMatched(Join segment) {
+
+ joinClause.append(segment.getType().getSql()).append(' ');
+
+ return super.enterMatched(segment);
+ }
+
+ /*
+ * (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 Table && !inCondition) {
+ joinClause.append(context.getNamingStrategy().getName(((Table) segment)));
+ if (segment instanceof Aliased) {
+ joinClause.append(" AS ").append(((Aliased) segment).getAlias());
+ }
+ } else if (segment instanceof Condition) {
+
+ // TODO: Use proper delegation for condition rendering.
+ inCondition = true;
+ if (!hasSeenCondition) {
+ hasSeenCondition = true;
+ joinClause.append(" ON ");
+ joinClause.append(segment);
+ }
+ }
+
+ 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 (segment instanceof Condition) {
+ inCondition = false;
+ }
+ 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(Join segment) {
+ parent.onRendered(joinClause);
+ return super.leaveMatched(segment);
+ }
+}
diff --git a/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/render/LikeVisitor.java b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/render/LikeVisitor.java
new file mode 100644
index 00000000..2ed8ef0d
--- /dev/null
+++ b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/render/LikeVisitor.java
@@ -0,0 +1,97 @@
+/*
+ * 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.Condition;
+import org.springframework.data.relational.core.sql.Expression;
+import org.springframework.data.relational.core.sql.Like;
+import org.springframework.data.relational.core.sql.Visitable;
+import org.springframework.lang.Nullable;
+
+/**
+ * {@link org.springframework.data.relational.core.sql.Visitor} rendering comparison {@link Condition}. Uses a
+ * {@link RenderTarget} to call back for render results.
+ *
+ * @author Mark Paluch
+ * @see Like
+ * @since 1.1
+ */
+class LikeVisitor extends FilteredSubtreeVisitor {
+
+ private final RenderContext context;
+ private final RenderTarget target;
+ private final StringBuilder part = new StringBuilder();
+ private @Nullable PartRenderer current;
+
+ LikeVisitor(Like condition, RenderContext context, RenderTarget target) {
+ super(it -> it == condition);
+ this.context = 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 Expression) {
+ ExpressionVisitor visitor = new ExpressionVisitor(context);
+ current = visitor;
+ return Delegation.delegateTo(visitor);
+ }
+
+ if (segment instanceof Condition) {
+ ConditionVisitor visitor = new ConditionVisitor(context);
+ current = visitor;
+ return Delegation.delegateTo(visitor);
+ }
+
+ 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 (current != null) {
+ if (part.length() != 0) {
+ part.append(" LIKE ");
+ }
+
+ part.append(current.getRenderedPart());
+ current = null;
+ }
+
+ 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(Visitable segment) {
+
+ target.onRendered(part);
+
+ return super.leaveMatched(segment);
+ }
+}
diff --git a/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/render/MultiConcatConditionVisitor.java b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/render/MultiConcatConditionVisitor.java
new file mode 100644
index 00000000..288725cc
--- /dev/null
+++ b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/render/MultiConcatConditionVisitor.java
@@ -0,0 +1,77 @@
+/*
+ * 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.AndCondition;
+import org.springframework.data.relational.core.sql.OrCondition;
+import org.springframework.data.relational.core.sql.Visitable;
+
+/**
+ * Renderer for {@link AndCondition} and {@link OrCondition}. Uses a {@link RenderTarget} to call back for render
+ * results.
+ *
+ * @author Mark Paluch
+ * @author Jens Schauder
+ * @since 1.1
+ */
+class MultiConcatConditionVisitor extends FilteredSingleConditionRenderSupport {
+
+ private final RenderTarget target;
+ private final String concat;
+ private final StringBuilder part = new StringBuilder();
+
+ MultiConcatConditionVisitor(RenderContext context, AndCondition condition, RenderTarget target) {
+ super(context, it -> it == condition);
+ this.target = target;
+ this.concat = " AND ";
+ }
+
+ MultiConcatConditionVisitor(RenderContext context, OrCondition condition, RenderTarget target) {
+ super(context, it -> it == condition);
+ this.target = target;
+ this.concat = " OR ";
+ }
+
+ /*
+ * (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 (hasDelegatedRendering()) {
+ if (part.length() != 0) {
+ part.append(concat);
+ }
+
+ part.append(consumeRenderedPart());
+ }
+
+ 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(Visitable segment) {
+
+ target.onRendered(part);
+
+ return super.leaveMatched(segment);
+ }
+}
diff --git a/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/render/NamingStrategies.java b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/render/NamingStrategies.java
new file mode 100644
index 00000000..32650a83
--- /dev/null
+++ b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/render/NamingStrategies.java
@@ -0,0 +1,143 @@
+/*
+ * 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 lombok.RequiredArgsConstructor;
+
+import java.util.Locale;
+import java.util.function.Function;
+
+import org.springframework.data.relational.core.sql.Column;
+import org.springframework.data.relational.core.sql.Table;
+import org.springframework.util.Assert;
+
+/**
+ * Factory for {@link RenderNamingStrategy} objects.
+ *
+ * @author Mark Paluch
+ * @since 1.1
+ */
+public abstract class NamingStrategies {
+
+ private NamingStrategies() {}
+
+ /**
+ * Creates a as-is {@link RenderNamingStrategy} that preserves {@link Column} and {@link Table} names as they were
+ * expressed during their declaration.
+ *
+ * @return as-is {@link RenderNamingStrategy}.
+ */
+ public static RenderNamingStrategy asIs() {
+ return AsIs.INSTANCE;
+ }
+
+ /**
+ * Creates a mapping {@link RenderNamingStrategy} that applies a {@link Function mapping function} to {@link Column}
+ * and {@link Table} names.
+ *
+ * @param mappingFunction the mapping {@link Function}, must not be {@literal null}.
+ * @return the mapping {@link RenderNamingStrategy}.
+ */
+ public static RenderNamingStrategy mapWith(Function mappingFunction) {
+ return AsIs.INSTANCE.map(mappingFunction);
+ }
+
+ /**
+ * Creates a mapping {@link RenderNamingStrategy} that converts {@link Column} and {@link Table} names to upper case
+ * using the default {@link Locale}.
+ *
+ * @return upper-casing {@link RenderNamingStrategy}.
+ * @see String#toUpperCase()
+ * @see Locale
+ */
+ public static RenderNamingStrategy toUpper() {
+ return toUpper(Locale.getDefault());
+ }
+
+ /**
+ * Creates a mapping {@link RenderNamingStrategy} that converts {@link Column} and {@link Table} names to upper case
+ * using the given {@link Locale}.
+ *
+ * @param locale the locale to use.
+ * @return upper-casing {@link RenderNamingStrategy}.
+ * @see String#toUpperCase(Locale)
+ */
+ public static RenderNamingStrategy toUpper(Locale locale) {
+
+ Assert.notNull(locale, "Locale must not be null");
+
+ return AsIs.INSTANCE.map(it -> it.toUpperCase(locale));
+ }
+
+ /**
+ * Creates a mapping {@link RenderNamingStrategy} that converts {@link Column} and {@link Table} names to lower case
+ * using the default {@link Locale}.
+ *
+ * @return lower-casing {@link RenderNamingStrategy}.
+ * @see String#toLowerCase()
+ * @see Locale
+ */
+ public static RenderNamingStrategy toLower() {
+ return toLower(Locale.getDefault());
+ }
+
+ /**
+ * Creates a mapping {@link RenderNamingStrategy} that converts {@link Column} and {@link Table} names to lower case
+ * using the given {@link Locale}.
+ *
+ * @param locale the locale to use.
+ * @return lower-casing {@link RenderNamingStrategy}.
+ * @see String#toLowerCase(Locale)
+ * @see Locale
+ */
+ public static RenderNamingStrategy toLower(Locale locale) {
+
+ Assert.notNull(locale, "Locale must not be null");
+
+ return AsIs.INSTANCE.map(it -> it.toLowerCase(locale));
+ }
+
+ enum AsIs implements RenderNamingStrategy {
+ INSTANCE;
+ }
+
+ @RequiredArgsConstructor
+ static class DelegatingRenderNamingStrategy implements RenderNamingStrategy {
+
+ private final RenderNamingStrategy delegate;
+ private final Function mappingFunction;
+
+ @Override
+ public String getName(Column column) {
+ return mappingFunction.apply(delegate.getName(column));
+ }
+
+ @Override
+ public String getReferenceName(Column column) {
+ return mappingFunction.apply(delegate.getReferenceName(column));
+ }
+
+ @Override
+ public String getName(Table table) {
+ return mappingFunction.apply(delegate.getName(table));
+ }
+
+ @Override
+ public String getReferenceName(Table table) {
+ return mappingFunction.apply(delegate.getReferenceName(table));
+ }
+ }
+}
diff --git a/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/render/OrderByClauseVisitor.java b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/render/OrderByClauseVisitor.java
new file mode 100644
index 00000000..d7573c07
--- /dev/null
+++ b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/render/OrderByClauseVisitor.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.render;
+
+import org.springframework.data.relational.core.sql.Column;
+import org.springframework.data.relational.core.sql.OrderByField;
+import org.springframework.data.relational.core.sql.Visitable;
+
+/**
+ * {@link PartRenderer} for {@link OrderByField}s.
+ *
+ * @author Mark Paluch
+ * @author Jens Schauder
+ * @since 1.1
+ */
+class OrderByClauseVisitor extends TypedSubtreeVisitor implements PartRenderer {
+
+ private final RenderContext context;
+
+ private final StringBuilder builder = new StringBuilder();
+ private boolean first = true;
+
+ OrderByClauseVisitor(RenderContext context) {
+ this.context = context;
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see org.springframework.data.relational.core.sql.render.TypedSubtreeVisitor#enterMatched(org.springframework.data.relational.core.sql.Visitable)
+ */
+ @Override
+ Delegation enterMatched(OrderByField segment) {
+
+ if (!first) {
+ builder.append(", ");
+ }
+ first = false;
+
+ return super.enterMatched(segment);
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see org.springframework.data.relational.core.sql.render.TypedSubtreeVisitor#leaveMatched(org.springframework.data.relational.core.sql.Visitable)
+ */
+ @Override
+ Delegation leaveMatched(OrderByField segment) {
+
+ OrderByField field = segment;
+
+ if (field.getDirection() != null) {
+ builder.append(" ") //
+ .append(field.getDirection());
+ }
+
+ return Delegation.leave();
+ }
+
+ /*
+ * (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 Column) {
+ builder.append(context.getNamingStrategy().getReferenceName(((Column) segment)));
+ }
+
+ return super.leaveNested(segment);
+ }
+
+ /*
+ * (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/PartRenderer.java b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/render/PartRenderer.java
new file mode 100644
index 00000000..15963631
--- /dev/null
+++ b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/render/PartRenderer.java
@@ -0,0 +1,34 @@
+/*
+ * 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.Visitor;
+
+/**
+ * {@link Visitor} that renders a specific partial clause or expression.
+ *
+ * @author Mark Paluch
+ * @since 1.1
+ */
+interface PartRenderer extends Visitor {
+
+ /**
+ * Returns the rendered part.
+ *
+ * @return the rendered part.
+ */
+ CharSequence getRenderedPart();
+}
diff --git a/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/render/RenderContext.java b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/render/RenderContext.java
new file mode 100644
index 00000000..161c6059
--- /dev/null
+++ b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/render/RenderContext.java
@@ -0,0 +1,32 @@
+/*
+ * 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;
+
+/**
+ * Render context providing {@link RenderNamingStrategy} and other resources that are required during rendering.
+ *
+ * @author Mark Paluch
+ * @since 1.1
+ */
+public interface RenderContext {
+
+ /**
+ * Returns the configured {@link RenderNamingStrategy}.
+ *
+ * @return the {@link RenderNamingStrategy}.
+ */
+ RenderNamingStrategy getNamingStrategy();
+}
diff --git a/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/render/RenderNamingStrategy.java b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/render/RenderNamingStrategy.java
new file mode 100644
index 00000000..b6182e25
--- /dev/null
+++ b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/render/RenderNamingStrategy.java
@@ -0,0 +1,91 @@
+/*
+ * 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 java.util.function.Function;
+
+import org.springframework.data.relational.core.sql.Column;
+import org.springframework.data.relational.core.sql.Table;
+import org.springframework.data.relational.core.sql.render.NamingStrategies.DelegatingRenderNamingStrategy;
+import org.springframework.util.Assert;
+
+/**
+ * Naming strategy for SQL rendering.
+ *
+ * @author Mark Paluch
+ * @see NamingStrategies
+ * @since 1.1
+ */
+public interface RenderNamingStrategy {
+
+ /**
+ * Return the {@link Column#getName() column name}.
+ *
+ * @param column the column.
+ * @return the {@link Column#getName() column name}.
+ * @see Column#getName()
+ */
+ default String getName(Column column) {
+ return column.getName();
+ }
+
+ /**
+ * Return the {@link Column#getName() column reference name}.
+ *
+ * @param column the column.
+ * @return the {@link Column#getName() column reference name}.
+ * @see Column#getReferenceName() ()
+ */
+ default String getReferenceName(Column column) {
+ return column.getReferenceName();
+ }
+
+ /**
+ * Return the {@link Table#getName() table name}.
+ *
+ * @param table the table.
+ * @return the {@link Table#getName() table name}.
+ * @see Table#getName()
+ */
+ default String getName(Table table) {
+ return table.getName();
+ }
+
+ /**
+ * Return the {@link Table#getReferenceName() table reference name}.
+ *
+ * @param table the table.
+ * @return the {@link Table#getReferenceName() table name}.
+ * @see Table#getReferenceName()
+ */
+ default String getReferenceName(Table table) {
+ return table.getReferenceName();
+ }
+
+ /**
+ * Applies a {@link Function mapping function} after retrieving the object (column name, column reference name, …)
+ * name.
+ *
+ * @param mappingFunction the function that maps an object name.
+ * @return a new {@link RenderNamingStrategy} applying {@link Function mapping function}.
+ */
+ default RenderNamingStrategy map(Function mappingFunction) {
+
+ Assert.notNull(mappingFunction, "Mapping function must not be null!");
+
+ return new DelegatingRenderNamingStrategy(this, mappingFunction);
+ }
+}
diff --git a/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/render/RenderTarget.java b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/render/RenderTarget.java
new file mode 100644
index 00000000..91e21efd
--- /dev/null
+++ b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/render/RenderTarget.java
@@ -0,0 +1,37 @@
+/*
+ * 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.Visitor;
+
+/**
+ * Callback interface for {@link Visitor visitors} that wish to notify a render target when they are complete with
+ * rendering.
+ *
+ * @author Mark Paluch
+ * @since 1.1
+ */
+@FunctionalInterface
+interface RenderTarget {
+
+ /**
+ * Callback method that is invoked once the rendering for a part or expression is finished. When called multiple
+ * times, it's the responsibility of the implementor to ensure proper concatenation of render results.
+ *
+ * @param sequence the rendered part or expression.
+ */
+ void onRendered(CharSequence sequence);
+}
diff --git a/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/render/SelectListVisitor.java b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/render/SelectListVisitor.java
new file mode 100644
index 00000000..00432d52
--- /dev/null
+++ b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/render/SelectListVisitor.java
@@ -0,0 +1,111 @@
+/*
+ * 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.Aliased;
+import org.springframework.data.relational.core.sql.Column;
+import org.springframework.data.relational.core.sql.SelectList;
+import org.springframework.data.relational.core.sql.SimpleFunction;
+import org.springframework.data.relational.core.sql.Table;
+import org.springframework.data.relational.core.sql.Visitable;
+
+/**
+ * {@link PartRenderer} for {@link SelectList}s.
+ *
+ * @author Mark Paluch
+ * @author Jens Schauder
+ * @since 1.1
+ */
+class SelectListVisitor extends TypedSubtreeVisitor implements PartRenderer {
+
+ private final RenderContext context;
+ private final StringBuilder builder = new StringBuilder();
+ private final RenderTarget target;
+ private boolean requiresComma = false;
+ private boolean insideFunction = false; // this is hackery and should be fix with a proper visitor for
+ // subelements.
+
+ SelectListVisitor(RenderContext context, RenderTarget target) {
+ this.context = context;
+ this.target = target;
+ }
+
+ /*
+ * (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 (requiresComma) {
+ builder.append(", ");
+ requiresComma = false;
+ }
+ if (segment instanceof SimpleFunction) {
+ builder.append(((SimpleFunction) segment).getFunctionName()).append("(");
+ insideFunction = true;
+ } else {
+ insideFunction = false;
+ }
+
+ return super.enterNested(segment);
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see org.springframework.data.relational.core.sql.render.TypedSubtreeVisitor#leaveMatched(org.springframework.data.relational.core.sql.Visitable)
+ */
+ @Override
+ Delegation leaveMatched(SelectList segment) {
+
+ 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) {
+ builder.append(context.getNamingStrategy().getReferenceName((Table) segment)).append('.');
+ }
+
+ if (segment instanceof SimpleFunction) {
+ builder.append(")");
+ requiresComma = true;
+ } else if (segment instanceof Column) {
+ builder.append(context.getNamingStrategy().getName((Column) segment));
+ if (segment instanceof Aliased) {
+ builder.append(" AS ").append(((Aliased) segment).getAlias());
+ }
+ requiresComma = true;
+ }
+
+ return super.leaveNested(segment);
+ }
+
+ /*
+ * (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/SelectStatementVisitor.java b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/render/SelectStatementVisitor.java
new file mode 100644
index 00000000..424bd656
--- /dev/null
+++ b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/render/SelectStatementVisitor.java
@@ -0,0 +1,160 @@
+/*
+ * 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 java.util.OptionalLong;
+
+import org.springframework.data.relational.core.sql.From;
+import org.springframework.data.relational.core.sql.Join;
+import org.springframework.data.relational.core.sql.OrderByField;
+import org.springframework.data.relational.core.sql.Select;
+import org.springframework.data.relational.core.sql.SelectList;
+import org.springframework.data.relational.core.sql.Visitable;
+import org.springframework.data.relational.core.sql.Where;
+
+/**
+ * {@link PartRenderer} for {@link Select} statements.
+ *
+ * @author Mark Paluch
+ * @author Jens Schauder
+ * @since 1.1
+ */
+class SelectStatementVisitor extends DelegatingVisitor implements PartRenderer {
+
+ private final RenderContext context;
+
+ private StringBuilder builder = new StringBuilder();
+ private StringBuilder selectList = new StringBuilder();
+ private StringBuilder from = new StringBuilder();
+ private StringBuilder join = new StringBuilder();
+ private StringBuilder where = new StringBuilder();
+
+ private SelectListVisitor selectListVisitor;
+ private OrderByClauseVisitor orderByClauseVisitor;
+ private FromClauseVisitor fromClauseVisitor;
+ private WhereClauseVisitor whereClauseVisitor;
+
+ SelectStatementVisitor(RenderContext context) {
+
+ this.context = context;
+ this.selectListVisitor = new SelectListVisitor(context, selectList::append);
+ this.orderByClauseVisitor = new OrderByClauseVisitor(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 SelectList) {
+ return Delegation.delegateTo(selectListVisitor);
+ }
+
+ if (segment instanceof OrderByField) {
+ return Delegation.delegateTo(orderByClauseVisitor);
+ }
+
+ if (segment instanceof From) {
+ return Delegation.delegateTo(fromClauseVisitor);
+ }
+
+ if (segment instanceof Join) {
+ return Delegation.delegateTo(new JoinVisitor(context, it -> {
+
+ if (join.length() != 0) {
+ join.append(' ');
+ }
+
+ join.append(it);
+ }));
+ }
+
+ 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 Select) {
+
+ builder.append("SELECT ");
+ if (((Select) segment).isDistinct()) {
+ builder.append("DISTINCT ");
+ }
+
+ builder.append(selectList);
+
+ if (from.length() != 0) {
+ builder.append(" FROM ").append(from);
+ }
+
+ if (join.length() != 0) {
+ builder.append(' ').append(join);
+ }
+
+ if (where.length() != 0) {
+ builder.append(" WHERE ").append(where);
+ }
+
+ CharSequence orderBy = orderByClauseVisitor.getRenderedPart();
+ if (orderBy.length() != 0)
+ builder.append(" ORDER BY ").append(orderBy);
+
+ OptionalLong limit = ((Select) segment).getLimit();
+ if (limit.isPresent()) {
+ builder.append(" LIMIT ").append(limit.getAsLong());
+ }
+
+ OptionalLong offset = ((Select) segment).getOffset();
+ if (offset.isPresent()) {
+ builder.append(" OFFSET ").append(offset.getAsLong());
+ }
+
+ 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/SimpleRenderContext.java b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/render/SimpleRenderContext.java
new file mode 100644
index 00000000..a9e065fb
--- /dev/null
+++ b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/render/SimpleRenderContext.java
@@ -0,0 +1,31 @@
+/*
+ * 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 lombok.Value;
+
+/**
+ * Default {@link RenderContext} implementation.
+ *
+ * @author Mark Paluch
+ * @since 1.1
+ */
+@Value
+class SimpleRenderContext implements RenderContext {
+
+ private final RenderNamingStrategy namingStrategy;
+
+}
diff --git a/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/render/SqlRenderer.java b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/render/SqlRenderer.java
new file mode 100644
index 00000000..b114173c
--- /dev/null
+++ b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/render/SqlRenderer.java
@@ -0,0 +1,85 @@
+/*
+ * 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.Select;
+import org.springframework.util.Assert;
+
+/**
+ * Naive SQL renderer that does not consider dialect specifics. This class is to evaluate requirements of a SQL
+ * renderer.
+ *
+ * @author Mark Paluch
+ * @author Jens Schauder
+ * @since 1.1
+ */
+public class SqlRenderer {
+
+ private final Select select;
+ private final RenderContext context;
+
+ private SqlRenderer(Select select, RenderContext context) {
+ this.context = context;
+
+ Assert.notNull(select, "Select must not be null!");
+
+ this.select = select;
+ }
+
+ /**
+ * 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()));
+ }
+
+ /**
+ * 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);
+ }
+
+ /**
+ * Renders a {@link Select} statement into its SQL representation.
+ *
+ * @param select must not be {@literal null}.
+ * @return the rendered statement.
+ */
+ public static String render(Select select) {
+ return create(select).render();
+ }
+
+ /**
+ * Render the {@link Select} AST into a SQL statement.
+ *
+ * @return the rendered statement.
+ */
+ public String render() {
+
+ SelectStatementVisitor visitor = new SelectStatementVisitor(context);
+ select.visit(visitor);
+
+ return visitor.getRenderedPart().toString();
+ }
+}
diff --git a/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/render/TypedSingleConditionRenderSupport.java b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/render/TypedSingleConditionRenderSupport.java
new file mode 100644
index 00000000..9e8aedf2
--- /dev/null
+++ b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/render/TypedSingleConditionRenderSupport.java
@@ -0,0 +1,86 @@
+/*
+ * 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.Condition;
+import org.springframework.data.relational.core.sql.Expression;
+import org.springframework.data.relational.core.sql.Visitable;
+import org.springframework.lang.Nullable;
+import org.springframework.util.Assert;
+
+/**
+ * Support class for {@link TypedSubtreeVisitor typed visitors} that want to render a single {@link Condition} and
+ * delegate nested {@link Expression} and {@link Condition} rendering.
+ *
+ * @author Mark Paluch
+ * @since 1.1
+ */
+abstract class TypedSingleConditionRenderSupport extends TypedSubtreeVisitor {
+
+ private final RenderContext context;
+ private @Nullable PartRenderer current;
+
+ TypedSingleConditionRenderSupport(RenderContext context) {
+ this.context = context;
+ }
+
+ /*
+ * (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) {
+ ExpressionVisitor visitor = new ExpressionVisitor(context);
+ current = visitor;
+ return Delegation.delegateTo(visitor);
+ }
+
+ if (segment instanceof Condition) {
+ ConditionVisitor visitor = new ConditionVisitor(context);
+ current = visitor;
+ return Delegation.delegateTo(visitor);
+ }
+
+ throw new IllegalStateException("Cannot provide visitor for " + segment);
+ }
+
+ /**
+ * Returns whether rendering was delegated to a {@link ExpressionVisitor} or {@link ConditionVisitor}.
+ *
+ * @return {@literal true} when rendering was delegated to a {@link ExpressionVisitor} or {@link ConditionVisitor}.
+ */
+ protected boolean hasDelegatedRendering() {
+ return current != null;
+ }
+
+ /**
+ * Consumes the delegated rendering part. Call {@link #hasDelegatedRendering()} to check whether rendering was
+ * actually delegated. Consumption releases the delegated rendered.
+ *
+ * @return the delegated rendered part.
+ * @throws IllegalStateException if rendering was not delegate.
+ */
+ protected CharSequence consumeRenderedPart() {
+
+ Assert.state(hasDelegatedRendering(), "Rendering not delegated. Cannot consume delegated rendering part.");
+
+ PartRenderer current = this.current;
+ this.current = null;
+ return current.getRenderedPart();
+ }
+}
diff --git a/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/render/TypedSubtreeVisitor.java b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/render/TypedSubtreeVisitor.java
new file mode 100644
index 00000000..951b7bc8
--- /dev/null
+++ b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/render/TypedSubtreeVisitor.java
@@ -0,0 +1,145 @@
+/*
+ * 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 java.util.function.Predicate;
+
+import org.springframework.core.ResolvableType;
+import org.springframework.data.relational.core.sql.Visitable;
+import org.springframework.data.relational.core.sql.Visitor;
+import org.springframework.lang.Nullable;
+
+/**
+ * Type-filtering {@link DelegatingVisitor visitor} applying a {@link Class type filter} derived from the generic type
+ * parameter. Typically used as base class for {@link Visitor visitors} that wish to apply hierarchical processing based
+ * on a well-defined entry {@link Visitor segment}.
+ *
+ * Filtering is a three-way process:
+ *
+ * - Ignores elements that do not match the filter {@link Predicate}.
+ * - {@link #enterMatched(Visitable) enter}/{@link #leaveMatched(Visitable) leave} matched callbacks for the
+ * {@link Visitable segment} that matches the {@link Predicate}.
+ * - {@link #enterNested(Visitable) enter}/{@link #leaveNested(Visitable) leave} nested callbacks for direct/nested
+ * children of the matched {@link Visitable} until {@link #leaveMatched(Visitable) leaving the matched}
+ * {@link Visitable}.
+ *
+ *
+ * @author Mark Paluch
+ * @since 1.1
+ * @see FilteredSubtreeVisitor
+ */
+abstract class TypedSubtreeVisitor extends DelegatingVisitor {
+
+ private final ResolvableType type;
+ private @Nullable Visitable currentSegment;
+
+ /**
+ * Creates a new {@link TypedSubtreeVisitor}.
+ */
+ TypedSubtreeVisitor() {
+ this.type = ResolvableType.forClass(getClass()).as(TypedSubtreeVisitor.class).getGeneric(0);
+ }
+
+ /**
+ * {@link Visitor#enter(Visitable) Enter} callback for a {@link Visitable} that this {@link Visitor} is responsible
+ * for. The default implementation retains delegation by default.
+ *
+ * @param segment the segment, must not be {@literal null}.
+ * @return delegation options. Can be either {@link Delegation#retain()} or
+ * {@link Delegation#delegateTo(DelegatingVisitor)}.
+ * @see Delegation#retain()
+ */
+ Delegation enterMatched(T segment) {
+ return Delegation.retain();
+ }
+
+ /**
+ * {@link Visitor#enter(Visitable) Enter} callback for a nested {@link Visitable}. The default implementation retains
+ * delegation by default.
+ *
+ * @param segment the segment, must not be {@literal null}.
+ * @return delegation options. Can be either {@link Delegation#retain()} or
+ * {@link Delegation#delegateTo(DelegatingVisitor)}.
+ * @see Delegation#retain()
+ */
+ Delegation enterNested(Visitable segment) {
+ return Delegation.retain();
+ }
+
+ /**
+ * {@link Visitor#leave(Visitable) Leave} callback for the matched {@link Visitable}. The default implementation steps
+ * back from delegation by default.
+ *
+ * @param segment the segment, must not be {@literal null}.
+ * @return delegation options. Can be either {@link Delegation#retain()} or {@link Delegation#leave()}.
+ * @see Delegation#leave()
+ */
+ Delegation leaveMatched(T segment) {
+ return Delegation.leave();
+ }
+
+ /**
+ * {@link Visitor#leave(Visitable) Leave} callback for a nested {@link Visitable}. The default implementation retains
+ * delegation by default.
+ *
+ * @param segment the segment, must not be {@literal null}.
+ * @return delegation options. Can be either {@link Delegation#retain()} or {@link Delegation#leave()}.
+ * @see Delegation#retain()
+ */
+ Delegation leaveNested(Visitable segment) {
+ return Delegation.retain();
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see org.springframework.data.relational.core.sql.render.DelegatingVisitor#doEnter(org.springframework.data.relational.core.sql.Visitable)
+ */
+ @SuppressWarnings("unchecked")
+ @Override
+ public final Delegation doEnter(Visitable segment) {
+
+ if (currentSegment == null) {
+
+ if (this.type.isInstance(segment)) {
+
+ currentSegment = segment;
+ return enterMatched((T) segment);
+ }
+ } else {
+ return enterNested(segment);
+ }
+
+ return Delegation.retain();
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see org.springframework.data.relational.core.sql.render.DelegatingVisitor#doLeave(org.springframework.data.relational.core.sql.Visitable)
+ */
+ @SuppressWarnings("unchecked")
+ @Override
+ public final Delegation doLeave(Visitable segment) {
+
+ if (currentSegment == null) {
+ return Delegation.leave();
+ } else if (segment == currentSegment) {
+ currentSegment = null;
+ return leaveMatched((T) segment);
+ } else {
+ return leaveNested(segment);
+ }
+ }
+}
diff --git a/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/render/WhereClauseVisitor.java b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/render/WhereClauseVisitor.java
new file mode 100644
index 00000000..fa8ea8ac
--- /dev/null
+++ b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/render/WhereClauseVisitor.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 org.springframework.data.relational.core.sql.Condition;
+import org.springframework.data.relational.core.sql.Visitable;
+import org.springframework.data.relational.core.sql.Where;
+
+/**
+ * Renderer for {@link Where} segments. Uses a {@link RenderTarget} to call back for render results.
+ *
+ * @author Mark Paluch
+ * @author Jens Schauder
+ * @since 1.1
+ */
+class WhereClauseVisitor extends TypedSubtreeVisitor {
+
+ private final RenderTarget parent;
+ private final ConditionVisitor conditionVisitor;
+
+ WhereClauseVisitor(RenderContext context, RenderTarget parent) {
+ this.conditionVisitor = new ConditionVisitor(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 Condition) {
+ return Delegation.delegateTo(conditionVisitor);
+ }
+
+ return super.enterNested(segment);
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see org.springframework.data.relational.core.sql.render.TypedSubtreeVisitor#leaveMatched(org.springframework.data.relational.core.sql.Visitable)
+ */
+ @Override
+ Delegation leaveMatched(Where segment) {
+
+ parent.onRendered(conditionVisitor.getRenderedPart());
+ return super.leaveMatched(segment);
+ }
+}
diff --git a/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/render/package-info.java b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/render/package-info.java
new file mode 100644
index 00000000..34c541d4
--- /dev/null
+++ b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/render/package-info.java
@@ -0,0 +1,9 @@
+/**
+ * SQL rendering utilities to render SQL from the Statement Builder API.
+ */
+@NonNullApi
+@NonNullFields
+package org.springframework.data.relational.core.sql.render;
+
+import org.springframework.lang.NonNullApi;
+import org.springframework.lang.NonNullFields;
diff --git a/spring-data-relational/src/test/java/org/springframework/data/relational/core/sql/NaiveSqlRenderer.java b/spring-data-relational/src/test/java/org/springframework/data/relational/core/sql/NaiveSqlRenderer.java
deleted file mode 100644
index 200bb779..00000000
--- a/spring-data-relational/src/test/java/org/springframework/data/relational/core/sql/NaiveSqlRenderer.java
+++ /dev/null
@@ -1,655 +0,0 @@
-/*
- * 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.OptionalLong;
-import java.util.Stack;
-
-import org.springframework.util.Assert;
-
-/**
- * Naive SQL renderer that does not consider dialect specifics. This class is to evaluate requirements of a SQL
- * renderer.
- *
- * @author Mark Paluch
- * @author Jens Schauder
- */
-public class NaiveSqlRenderer {
-
- private final Select select;
-
- private NaiveSqlRenderer(Select select) {
-
- Assert.notNull(select, "Select must not be null!");
-
- this.select = select;
- }
-
- /**
- * Creates a new {@link NaiveSqlRenderer}.
- *
- * @param select must not be {@literal null}.
- * @return the renderer.
- */
- public static NaiveSqlRenderer create(Select select) {
- return new NaiveSqlRenderer(select);
- }
-
- /**
- * Renders a {@link Select} statement into its SQL representation.
- *
- * @param select must not be {@literal null}.
- * @return the rendered statement.
- */
- public static String render(Select select) {
- return create(select).render();
- }
-
- /**
- * Render the {@link Select} AST into a SQL statement.
- *
- * @return the rendered statement.
- */
- public String render() {
-
- StackBasedVisitor visitor = new StackBasedVisitor();
- select.visit(visitor);
-
- return visitor.selectStatementVisitor.getValue();
- }
-
- interface ValuedVisitor extends Visitor {
- String getValue();
- }
-
- static class StackBasedVisitor implements Visitor {
-
- private Stack visitors = new Stack<>();
-
- private SelectStatementVisitor selectStatementVisitor = new SelectStatementVisitor();
-
- {
- visitors.push(segment -> {});
- visitors.push(selectStatementVisitor);
- }
-
- @Override
- public void enter(Visitable segment) {
-
- Visitor delegate = visitors.peek();
- delegate.enter(segment);
- }
-
- @Override
- public void leave(Visitable segment) {
-
- Visitor delegate = visitors.peek();
- delegate.leave(segment);
- }
-
- /**
- * Handles a sequence of {@link Visitable} until encountering the first that does not matches the expectations. When
- * a not matching element is encountered it pops itself from the stack and delegates the call to the now top most
- * element of the stack.
- */
- abstract class ReadWhileMatchesVisitor implements Visitor {
-
- private Visitable currentSegment = null;
- private Visitor nextVisitor;
-
- abstract boolean matches(Visitable segment);
-
- void enterMatched(Visitable segment) {}
-
- void enterSub(Visitable segment) {}
-
- void leaveMatched(Visitable segment) {}
-
- void leaveSub(Visitable segment) {}
-
- @Override
- public void enter(Visitable segment) {
-
- if (currentSegment == null) {
-
- if (matches(segment)) {
-
- currentSegment = segment;
- enterMatched(segment);
- } else {
-
- Visitor popped = visitors.pop();
-
- Assert.isTrue(popped == this, "Popped the wrong visitor from the stack!");
-
- nextVisitor = visitors.peek();
- nextVisitor.enter(segment);
- }
-
- } else {
- enterSub(segment);
- }
- }
-
- @Override
- public void leave(Visitable segment) {
-
- if (currentSegment == null) {
- // we are receiving the leave event of the element above
- visitors.pop();
- nextVisitor = visitors.peek();
- nextVisitor.leave(segment);
- } else if (segment == currentSegment) {
-
- currentSegment = null;
- leaveMatched(segment);
- } else {
- leaveSub(segment);
- }
- }
- }
-
- /**
- * Visits exactly one element that must match the expectations as defined in {@link #matches(Visitable)}. Ones
- * handled it pops itself from the stack.
- */
- abstract class ReadOneVisitor implements Visitor {
-
- private Visitable currentSegment;
-
- abstract boolean matches(Visitable segment);
-
- void enterMatched(Visitable segment) {}
-
- void enterSub(Visitable segment) {}
-
- void leaveMatched(Visitable segment) {}
-
- void leaveSub(Visitable segment) {}
-
- @Override
- public void enter(Visitable segment) {
-
- if (currentSegment == null) {
-
- if (matches(segment)) {
-
- currentSegment = segment;
- enterMatched(segment);
- } else {
- Assert.isTrue(visitors.pop() == this, "Popped wrong visitor instance.");
- visitors.peek().enter(segment);
- }
- } else {
- enterSub(segment);
- }
- }
-
- @Override
- public void leave(Visitable segment) {
-
- if (currentSegment == null) {
- Assert.isTrue(visitors.pop() == this, "Popped wrong visitor instance.");
- visitors.peek().leave(segment);
- } else if (segment == currentSegment) {
- leaveMatched(segment);
- Assert.isTrue(visitors.pop() == this, "Popped wrong visitor instance.");
- } else {
- leaveSub(segment);
- }
-
- }
- }
-
- class SelectStatementVisitor extends ReadOneVisitor implements ValuedVisitor {
-
- private StringBuilder builder = new StringBuilder();
-
- private SelectListVisitor selectListVisitor = new SelectListVisitor();
- private FromClauseVisitor fromClauseVisitor = new FromClauseVisitor();
- private JoinVisitor joinVisitor = new JoinVisitor();
- private WhereClauseVisitor whereClauseVisitor = new WhereClauseVisitor();
- private OrderByClauseVisitor orderByClauseVisitor = new OrderByClauseVisitor();
-
- @Override
- boolean matches(Visitable segment) {
- return segment instanceof Select;
- }
-
- @Override
- void enterMatched(Visitable segment) {
-
- visitors.push(orderByClauseVisitor);
- visitors.push(whereClauseVisitor);
- visitors.push(joinVisitor);
- visitors.push(fromClauseVisitor);
- visitors.push(selectListVisitor);
- }
-
- @Override
- void leaveMatched(Visitable segment) {
-
- builder.append("SELECT ");
- if (((Select) segment).isDistinct()) {
- builder.append("DISTINCT ");
- }
-
- builder.append(selectListVisitor.getValue()) //
- .append(fromClauseVisitor.getValue()) //
- .append(joinVisitor.getValue()) //
- .append(whereClauseVisitor.getValue());
-
- builder.append(orderByClauseVisitor.getValue());
-
- OptionalLong limit = ((Select) segment).getLimit();
- if (limit.isPresent()) {
- builder.append(" LIMIT ").append(limit.getAsLong());
- }
-
- OptionalLong offset = ((Select) segment).getOffset();
- if (offset.isPresent()) {
- builder.append(" OFFSET ").append(offset.getAsLong());
- }
- }
-
- @Override
- public String getValue() {
- return builder.toString();
- }
- }
-
- class SelectListVisitor extends ReadWhileMatchesVisitor implements ValuedVisitor {
-
- private StringBuilder builder = new StringBuilder();
- private boolean first = true;
- private boolean insideFunction = false; // this is hackery and should be fix with a proper visitor for
- // subelements.
-
- @Override
- boolean matches(Visitable segment) {
- return segment instanceof Expression;
- }
-
- @Override
- void enterMatched(Visitable segment) {
-
- if (!first) {
- builder.append(", ");
- }
- if (segment instanceof SimpleFunction) {
- builder.append(((SimpleFunction) segment).getFunctionName()).append("(");
- insideFunction = true;
- } else {
- insideFunction = false;
- }
- }
-
- @Override
- void leaveMatched(Visitable segment) {
-
- first = false;
-
- if (segment instanceof SimpleFunction) {
- builder.append(")");
- } else if (segment instanceof Column) {
- builder.append(((Column) segment).getName());
- if (segment instanceof Column.AliasedColumn) {
- builder.append(" AS ").append(((Column.AliasedColumn) segment).getAlias());
- }
- }
- }
-
- @Override
- void leaveSub(Visitable segment) {
-
- if (segment instanceof Table) {
- builder.append(((Table) segment).getReferenceName()).append('.');
- }
- if (insideFunction) {
-
- if (segment instanceof SimpleFunction) {
- builder.append(")");
- } else if (segment instanceof Column) {
- builder.append(((Column) segment).getName());
- if (segment instanceof Column.AliasedColumn) {
- builder.append(" AS ").append(((Column.AliasedColumn) segment).getAlias());
- }
- first = false;
- }
- }
- }
-
- @Override
- public String getValue() {
- return builder.toString();
- }
- }
-
- private class FromClauseVisitor extends ReadOneVisitor implements ValuedVisitor {
-
- private FromTableVisitor fromTableVisitor = new FromTableVisitor();
-
- @Override
- boolean matches(Visitable segment) {
- return segment instanceof From;
- }
-
- @Override
- void enterMatched(Visitable segment) {
- visitors.push(fromTableVisitor);
- }
-
- @Override
- public String getValue() {
- return " FROM " + fromTableVisitor.getValue();
- }
- }
-
- private class FromTableVisitor extends ReadWhileMatchesVisitor implements ValuedVisitor {
-
- private final StringBuilder builder = new StringBuilder();
- private boolean first = true;
-
- @Override
- boolean matches(Visitable segment) {
- return segment instanceof Table;
- }
-
- @Override
- void enterMatched(Visitable segment) {
-
- if (!first) {
- builder.append(", ");
- }
- first = false;
-
- builder.append(((Table) segment).getName());
- if (segment instanceof Table.AliasedTable) {
- builder.append(" AS ").append(((Table.AliasedTable) segment).getAlias());
- }
- }
-
- @Override
- public String getValue() {
- return builder.toString();
- }
- }
-
- private class JoinVisitor extends ReadWhileMatchesVisitor implements ValuedVisitor {
-
- private StringBuilder internal = new StringBuilder();
- private JoinTableAndConditionVisitor subvisitor;
-
- @Override
- boolean matches(Visitable segment) {
- return segment instanceof Join;
- }
-
- @Override
- void enterMatched(Visitable segment) {
-
- subvisitor = new JoinTableAndConditionVisitor();
- visitors.push(subvisitor);
- }
-
- @Override
- void leaveMatched(Visitable segment) {
- append(" JOIN ");
- append(subvisitor.getValue());
- }
-
- @Override
- public String getValue() {
- return internal.toString();
- }
-
- void append(String s) {
- internal.append(s);
- }
-
- }
-
- private class JoinTableAndConditionVisitor extends ReadWhileMatchesVisitor implements ValuedVisitor {
-
- private final StringBuilder builder = new StringBuilder();
- boolean inCondition = false;
-
- @Override
- boolean matches(Visitable segment) {
- return segment instanceof Table || segment instanceof Condition;
- }
-
- @Override
- void enterMatched(Visitable segment) {
-
- if (segment instanceof Table && !inCondition) {
- builder.append(((Table) segment).getName());
- if (segment instanceof Table.AliasedTable) {
- builder.append(" AS ").append(((Table.AliasedTable) segment).getAlias());
- }
- } else if (segment instanceof Condition && !inCondition) {
- builder.append(" ON ");
- builder.append(segment);
- inCondition = true;
- }
- }
-
- @Override
- public String getValue() {
- return builder.toString();
- }
- }
-
- private class WhereClauseVisitor extends ReadOneVisitor implements ValuedVisitor {
-
- private ValuedVisitor conditionVisitor = new ConditionVisitor();
- private StringBuilder internal = new StringBuilder();
-
- @Override
- boolean matches(Visitable segment) {
- return segment instanceof Where;
- }
-
- @Override
- void enterMatched(Visitable segment) {
-
- internal.append(" WHERE ");
- visitors.push(conditionVisitor);
- }
-
- @Override
- void leaveMatched(Visitable segment) {
-
- internal.append(conditionVisitor.getValue());
- // builder.append(internal);
- }
-
- @Override
- public String getValue() {
- return internal.toString();
- }
- }
-
- private class ConditionVisitor extends ReadOneVisitor implements ValuedVisitor {
-
- private StringBuilder builder = new StringBuilder();
-
- ValuedVisitor left;
- ValuedVisitor right;
-
- @Override
- boolean matches(Visitable segment) {
- return segment instanceof Condition;
- }
-
- @Override
- void enterMatched(Visitable segment) {
-
- if (segment instanceof MultipleCondition) {
-
- left = new ConditionVisitor();
- right = new ConditionVisitor();
- visitors.push(right);
- visitors.push(left);
-
- } else if (segment instanceof IsNull) {
-
- left = new ExpressionVisitor();
- visitors.push(left);
-
- } else if (segment instanceof Equals || segment instanceof In) {
-
- left = new ExpressionVisitor();
- right = new ExpressionVisitor();
- visitors.push(right);
- visitors.push(left);
- }
- }
-
- @Override
- void leaveMatched(Visitable segment) {
-
- if (segment instanceof AndCondition) {
-
- builder.append(left.getValue()) //
- .append(" AND ") //
- .append(right.getValue());
-
- } else if (segment instanceof OrCondition) {
-
- builder.append("(") //
- .append(left.getValue()) //
- .append(" OR ") //
- .append(right.getValue()) //
- .append(")");
-
- } else if (segment instanceof IsNull) {
-
- builder.append(left.getValue());
- if (((IsNull) segment).isNegated()) {
- builder.append(" IS NOT NULL");
- } else {
- builder.append(" IS NULL");
- }
-
- } else if (segment instanceof Equals) {
-
- builder.append(left.getValue()).append(" = ").append(right.getValue());
-
- } else if (segment instanceof In) {
-
- builder.append(left.getValue()).append(" IN ").append("(").append(right.getValue()).append(")");
- }
- }
-
- @Override
- public String getValue() {
- return builder.toString();
- }
- }
-
- private class ExpressionVisitor extends ReadOneVisitor implements ValuedVisitor {
-
- private String value = "";
- private SelectStatementVisitor valuedVisitor;
-
- @Override
- boolean matches(Visitable segment) {
- return segment instanceof Expression;
- }
-
- @Override
- void enterMatched(Visitable segment) {
-
- if (segment instanceof SubselectExpression) {
-
- valuedVisitor = new SelectStatementVisitor();
- visitors.push(valuedVisitor);
- } else if (segment instanceof Column) {
- value = ((Column) segment).getTable().getName() + "." + ((Column) segment).getName();
- } else if (segment instanceof BindMarker) {
-
- if (segment instanceof BindMarker.NamedBindMarker) {
- value = ":" + ((BindMarker.NamedBindMarker) segment).getName();
- } else {
- value = segment.toString();
- }
- }
- }
-
- @Override
- void leaveMatched(Visitable segment) {
-
- if (valuedVisitor != null) {
- value = valuedVisitor.getValue();
- }
- }
-
- @Override
- public String getValue() {
- return value;
- }
- }
-
- private class OrderByClauseVisitor extends ReadWhileMatchesVisitor implements ValuedVisitor {
-
- StringBuilder builder = new StringBuilder();
- boolean first = true;
-
- @Override
- boolean matches(Visitable segment) {
- return segment instanceof OrderByField;
- }
-
- @Override
- void enterMatched(Visitable segment) {
-
- if (!first) {
- builder.append(", ");
- } else {
- builder.append(" ORDER BY ");
- }
- first = false;
- }
-
- @Override
- void leaveMatched(Visitable segment) {
-
- OrderByField field = (OrderByField) segment;
-
- if (field.getDirection() != null) {
- builder.append(" ") //
- .append(field.getDirection());
- }
- }
-
- @Override
- void leaveSub(Visitable segment) {
-
- if (segment instanceof Column) {
- builder.append(((Column) segment).getReferenceName());
- }
- }
-
- @Override
- public String getValue() {
- return builder.toString();
- }
- }
-
- }
-
-}
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 1a34dc88..4a446310 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
@@ -34,7 +34,7 @@ public class SelectBuilderUnitTests {
@Test // DATAJDBC-309
public void simpleSelect() {
- SelectBuilder builder = SQL.select();
+ SelectBuilder builder = StatementBuilder.select();
Table table = SQL.table("mytable");
Column foo = table.column("foo");
@@ -46,13 +46,12 @@ public class SelectBuilderUnitTests {
select.visit(visitor);
assertThat(visitor.enter).containsSequence(foo, table, bar, table, new From(table), table);
- assertThat(visitor.leave).containsSequence(table, foo, table, bar, table, new From(table));
}
@Test // DATAJDBC-309
public void selectTop() {
- SelectBuilder builder = SQL.select();
+ SelectBuilder builder = StatementBuilder.select();
Table table = SQL.table("mytable");
Column foo = table.column("foo");
@@ -69,7 +68,7 @@ public class SelectBuilderUnitTests {
@Test // DATAJDBC-309
public void moreAdvancedSelect() {
- SelectBuilder builder = SQL.select();
+ SelectBuilder builder = StatementBuilder.select();
Table table1 = SQL.table("mytable1");
Table table2 = SQL.table("mytable2");
@@ -88,7 +87,7 @@ public class SelectBuilderUnitTests {
@Test // DATAJDBC-309
public void orderBy() {
- SelectBuilder builder = SQL.select();
+ SelectBuilder builder = StatementBuilder.select();
Table table = SQL.table("mytable");
@@ -106,7 +105,7 @@ public class SelectBuilderUnitTests {
@Test // DATAJDBC-309
public void joins() {
- SelectBuilder builder = SQL.select();
+ SelectBuilder builder = StatementBuilder.select();
Table employee = SQL.table("employee");
Table department = SQL.table("department");
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 49029b51..d30525bb 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
@@ -32,7 +32,7 @@ public class SelectValidatorUnitTests {
Column column = SQL.table("table").column("foo");
assertThatThrownBy(() -> {
- SQL.newSelect(column).from(SQL.table("bar")).build();
+ StatementBuilder.select(column).from(SQL.table("bar")).build();
}).isInstanceOf(IllegalStateException.class)
.hasMessageContaining("Required table [table] by a SELECT column not imported by FROM [bar] or JOIN []");
}
@@ -43,7 +43,7 @@ public class SelectValidatorUnitTests {
Column column = SQL.table("table").column("foo");
assertThatThrownBy(() -> {
- SQL.newSelect(Functions.count(column)).from(SQL.table("bar")).build();
+ StatementBuilder.select(Functions.count(column)).from(SQL.table("bar")).build();
}).isInstanceOf(IllegalStateException.class)
.hasMessageContaining("Required table [table] by a SELECT column not imported by FROM [bar] or JOIN []");
}
@@ -54,7 +54,7 @@ public class SelectValidatorUnitTests {
Column column = SQL.table("table").column("foo");
assertThatThrownBy(() -> {
- SQL.newSelect(column).distinct().from(SQL.table("bar")).build();
+ StatementBuilder.select(column).distinct().from(SQL.table("bar")).build();
}).isInstanceOf(IllegalStateException.class)
.hasMessageContaining("Required table [table] by a SELECT column not imported by FROM [bar] or JOIN []");
}
@@ -66,7 +66,7 @@ public class SelectValidatorUnitTests {
Table bar = SQL.table("bar");
assertThatThrownBy(() -> {
- SQL.newSelect(bar.column("foo")) //
+ StatementBuilder.select(bar.column("foo")) //
.from(bar) //
.orderBy(foo) //
.build();
@@ -81,7 +81,7 @@ public class SelectValidatorUnitTests {
Table bar = SQL.table("bar");
assertThatThrownBy(() -> {
- SQL.newSelect(bar.column("foo")) //
+ StatementBuilder.select(bar.column("foo")) //
.from(bar) //
.where(new SimpleCondition(column, "=", "foo")) //
.build();
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
new file mode 100644
index 00000000..edf3dc80
--- /dev/null
+++ b/spring-data-relational/src/test/java/org/springframework/data/relational/core/sql/render/ConditionRendererUnitTests.java
@@ -0,0 +1,129 @@
+/*
+ * 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.StatementBuilder;
+import org.springframework.data.relational.core.sql.Table;
+
+/**
+ * Unit tests for rendered {@link org.springframework.data.relational.core.sql.Conditions}.
+ *
+ * @author Mark Paluch
+ */
+public class ConditionRendererUnitTests {
+
+ Table table = Table.create("my_table");
+ Column left = table.column("left");
+ Column right = table.column("right");
+
+ @Test // DATAJDBC-309
+ public void shouldRenderEquals() {
+
+ String sql = SqlRenderer
+ .render(StatementBuilder.select(left).from(table).where(left.isEqualTo(right)).build());
+
+ assertThat(sql).endsWith("WHERE my_table.left = my_table.right");
+ }
+
+ @Test // DATAJDBC-309
+ public void shouldRenderNotEquals() {
+
+ String sql = SqlRenderer
+ .render(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());
+
+ assertThat(sql).endsWith("WHERE my_table.left != my_table.right");
+ }
+
+ @Test // DATAJDBC-309
+ public void shouldRenderIsLess() {
+
+ String sql = SqlRenderer.render(StatementBuilder.select(left).from(table).where(left.isLess(right)).build());
+
+ assertThat(sql).endsWith("WHERE my_table.left < my_table.right");
+ }
+
+ @Test // DATAJDBC-309
+ public void shouldRenderIsLessOrEqualTo() {
+
+ String sql = SqlRenderer
+ .render(StatementBuilder.select(left).from(table).where(left.isLessOrEqualTo(right)).build());
+
+ assertThat(sql).endsWith("WHERE my_table.left <= my_table.right");
+ }
+
+ @Test // DATAJDBC-309
+ public void shouldRenderIsGreater() {
+
+ String sql = SqlRenderer
+ .render(StatementBuilder.select(left).from(table).where(left.isGreater(right)).build());
+
+ assertThat(sql).endsWith("WHERE my_table.left > my_table.right");
+ }
+
+ @Test // DATAJDBC-309
+ public void shouldRenderIsGreaterOrEqualTo() {
+
+ String sql = SqlRenderer
+ .render(StatementBuilder.select(left).from(table).where(left.isGreaterOrEqualTo(right)).build());
+
+ assertThat(sql).endsWith("WHERE my_table.left >= my_table.right");
+ }
+
+ @Test // DATAJDBC-309
+ public void shouldRenderIn() {
+
+ String sql = SqlRenderer.render(StatementBuilder.select(left).from(table).where(left.in(right)).build());
+
+ assertThat(sql).endsWith("WHERE my_table.left IN (my_table.right)");
+ }
+
+ @Test // DATAJDBC-309
+ public void shouldRenderLike() {
+
+ String sql = SqlRenderer.render(StatementBuilder.select(left).from(table).where(left.like(right)).build());
+
+ assertThat(sql).endsWith("WHERE my_table.left LIKE my_table.right");
+ }
+
+ @Test // DATAJDBC-309
+ public void shouldRenderIsNull() {
+
+ String sql = SqlRenderer.render(StatementBuilder.select(left).from(table).where(left.isNull()).build());
+
+ assertThat(sql).endsWith("WHERE my_table.left IS NULL");
+ }
+
+ @Test // DATAJDBC-309
+ public void shouldRenderIsNotNull() {
+
+ String sql = SqlRenderer.render(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());
+
+ 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/OrderByClauseVisitorUnitTests.java b/spring-data-relational/src/test/java/org/springframework/data/relational/core/sql/render/OrderByClauseVisitorUnitTests.java
new file mode 100644
index 00000000..2c7a2272
--- /dev/null
+++ b/spring-data-relational/src/test/java/org/springframework/data/relational/core/sql/render/OrderByClauseVisitorUnitTests.java
@@ -0,0 +1,61 @@
+/*
+ * 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.OrderByField;
+import org.springframework.data.relational.core.sql.SQL;
+import org.springframework.data.relational.core.sql.Select;
+import org.springframework.data.relational.core.sql.Table;
+
+/**
+ * Unit tests for {@link OrderByClauseVisitor}.
+ *
+ * @author Mark Paluch
+ */
+public class OrderByClauseVisitorUnitTests {
+
+ @Test // DATAJDBC-309
+ public void shouldRenderOrderByName() {
+
+ Table employee = SQL.table("employee").as("emp");
+ Column column = employee.column("name").as("emp_name");
+
+ Select select = Select.builder().select(column).from(employee).orderBy(OrderByField.from(column).asc()).build();
+
+ OrderByClauseVisitor visitor = new OrderByClauseVisitor(new SimpleRenderContext(NamingStrategies.asIs()));
+ select.visit(visitor);
+
+ assertThat(visitor.getRenderedPart().toString()).isEqualTo("emp_name ASC");
+ }
+
+ @Test // DATAJDBC-309
+ public void shouldApplyNamingStrategy() {
+
+ Table employee = SQL.table("employee").as("emp");
+ Column column = employee.column("name").as("emp_name");
+
+ Select select = Select.builder().select(column).from(employee).orderBy(OrderByField.from(column).asc()).build();
+
+ OrderByClauseVisitor visitor = new OrderByClauseVisitor(new SimpleRenderContext(NamingStrategies.toUpper()));
+ select.visit(visitor);
+
+ assertThat(visitor.getRenderedPart().toString()).isEqualTo("EMP_NAME ASC");
+ }
+}
diff --git a/spring-data-relational/src/test/java/org/springframework/data/relational/core/sql/NaiveSqlRendererUnitTests.java b/spring-data-relational/src/test/java/org/springframework/data/relational/core/sql/render/SqlRendererUnitTests.java
similarity index 61%
rename from spring-data-relational/src/test/java/org/springframework/data/relational/core/sql/NaiveSqlRendererUnitTests.java
rename to spring-data-relational/src/test/java/org/springframework/data/relational/core/sql/render/SqlRendererUnitTests.java
index 7cd83be4..42920236 100644
--- a/spring-data-relational/src/test/java/org/springframework/data/relational/core/sql/NaiveSqlRendererUnitTests.java
+++ b/spring-data-relational/src/test/java/org/springframework/data/relational/core/sql/render/SqlRendererUnitTests.java
@@ -13,19 +13,28 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package org.springframework.data.relational.core.sql;
+package org.springframework.data.relational.core.sql.render;
import static org.assertj.core.api.Assertions.*;
-import org.junit.Ignore;
import org.junit.Test;
+import org.springframework.data.relational.core.sql.Column;
+import org.springframework.data.relational.core.sql.Conditions;
+import org.springframework.data.relational.core.sql.Functions;
+import org.springframework.data.relational.core.sql.OrderByField;
+import org.springframework.data.relational.core.sql.SQL;
+import org.springframework.data.relational.core.sql.Select;
+import org.springframework.data.relational.core.sql.Table;
+import org.springframework.util.StringUtils;
+
/**
- * Unit tests for {@link NaiveSqlRenderer}.
+ * Unit tests for {@link SqlRenderer}.
*
* @author Mark Paluch
+ * @author Jens Schauder
*/
-public class NaiveSqlRendererUnitTests {
+public class SqlRendererUnitTests {
@Test // DATAJDBC-309
public void shouldRenderSingleColumn() {
@@ -35,7 +44,7 @@ public class NaiveSqlRendererUnitTests {
Select select = Select.builder().select(foo).from(bar).build();
- assertThat(NaiveSqlRenderer.render(select)).isEqualTo("SELECT bar.foo FROM bar");
+ assertThat(SqlRenderer.render(select)).isEqualTo("SELECT bar.foo FROM bar");
}
@Test // DATAJDBC-309
@@ -45,7 +54,7 @@ public class NaiveSqlRendererUnitTests {
Select select = Select.builder().select(table.column("foo").as("my_foo")).from(table).build();
- assertThat(NaiveSqlRenderer.render(select)).isEqualTo("SELECT my_bar.foo AS my_foo FROM bar AS my_bar");
+ assertThat(SqlRenderer.render(select)).isEqualTo("SELECT my_bar.foo AS my_foo FROM bar AS my_bar");
}
@Test // DATAJDBC-309
@@ -57,7 +66,7 @@ public class NaiveSqlRendererUnitTests {
Select select = Select.builder().select(table1.column("col1")).select(table2.column("col2")).from(table1)
.from(table2).build();
- assertThat(NaiveSqlRenderer.render(select)).isEqualTo("SELECT table1.col1, table2.col2 FROM table1, table2");
+ assertThat(SqlRenderer.render(select)).isEqualTo("SELECT table1.col1, table2.col2 FROM table1, table2");
}
@Test // DATAJDBC-309
@@ -69,7 +78,7 @@ public class NaiveSqlRendererUnitTests {
Select select = Select.builder().distinct().select(foo, bar).from(table).build();
- assertThat(NaiveSqlRenderer.render(select)).isEqualTo("SELECT DISTINCT bar.foo, bar.bar FROM bar");
+ assertThat(SqlRenderer.render(select)).isEqualTo("SELECT DISTINCT bar.foo, bar.bar FROM bar");
}
@Test // DATAJDBC-309
@@ -81,7 +90,7 @@ public class NaiveSqlRendererUnitTests {
Select select = Select.builder().select(Functions.count(foo), bar).from(table).build();
- assertThat(NaiveSqlRenderer.render(select)).isEqualTo("SELECT COUNT(bar.foo), bar.bar FROM bar");
+ assertThat(SqlRenderer.render(select)).isEqualTo("SELECT COUNT(bar.foo), bar.bar FROM bar");
}
@Test // DATAJDBC-309
@@ -94,7 +103,7 @@ public class NaiveSqlRendererUnitTests {
.join(department).on(employee.column("department_id")).equals(department.column("id")) //
.build();
- assertThat(NaiveSqlRenderer.render(select)).isEqualTo("SELECT employee.id, department.name FROM employee "
+ assertThat(SqlRenderer.render(select)).isEqualTo("SELECT employee.id, department.name FROM employee "
+ "JOIN department ON employee.department_id = department.id");
}
@@ -109,7 +118,7 @@ public class NaiveSqlRendererUnitTests {
.and(employee.column("tenant")).equals(department.column("tenant")) //
.build();
- assertThat(NaiveSqlRenderer.render(select)).isEqualTo("SELECT employee.id, department.name FROM employee "
+ assertThat(SqlRenderer.render(select)).isEqualTo("SELECT employee.id, department.name FROM employee "
+ "JOIN department ON employee.department_id = department.id " + "AND employee.tenant = department.tenant");
}
@@ -126,7 +135,7 @@ public class NaiveSqlRendererUnitTests {
.join(tenant).on(tenant.column("tenant_id")).equals(department.column("tenant")) //
.build();
- assertThat(NaiveSqlRenderer.render(select)).isEqualTo("SELECT employee.id, department.name FROM employee "
+ assertThat(SqlRenderer.render(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");
}
@@ -139,7 +148,7 @@ public class NaiveSqlRendererUnitTests {
Select select = Select.builder().select(column).from(employee).orderBy(OrderByField.from(column).asc()).build();
- assertThat(NaiveSqlRenderer.render(select))
+ assertThat(SqlRenderer.render(select))
.isEqualTo("SELECT emp.name AS emp_name FROM employee AS emp ORDER BY emp_name ASC");
}
@@ -151,7 +160,7 @@ public class NaiveSqlRendererUnitTests {
Select select = Select.builder().select(bar).from("foo").limitOffset(10, 20).build();
- assertThat(NaiveSqlRenderer.render(select)).isEqualTo("SELECT foo.bar FROM foo LIMIT 10 OFFSET 20");
+ assertThat(SqlRenderer.render(select)).isEqualTo("SELECT foo.bar FROM foo LIMIT 10 OFFSET 20");
}
@Test // DATAJDBC-309
@@ -162,7 +171,7 @@ public class NaiveSqlRendererUnitTests {
Select select = Select.builder().select(bar).from(table).where(Conditions.isNull(bar)).build();
- assertThat(NaiveSqlRenderer.render(select)).isEqualTo("SELECT foo.bar FROM foo WHERE foo.bar IS NULL");
+ assertThat(SqlRenderer.render(select)).isEqualTo("SELECT foo.bar FROM foo WHERE foo.bar IS NULL");
}
@Test // DATAJDBC-309
@@ -173,7 +182,7 @@ public class NaiveSqlRendererUnitTests {
Select select = Select.builder().select(bar).from(table).where(Conditions.isNull(bar).not()).build();
- assertThat(NaiveSqlRenderer.render(select)).isEqualTo("SELECT foo.bar FROM foo WHERE foo.bar IS NOT NULL");
+ assertThat(SqlRenderer.render(select)).isEqualTo("SELECT foo.bar FROM foo WHERE foo.bar IS NOT NULL");
}
@Test // DATAJDBC-309
@@ -182,9 +191,10 @@ public class NaiveSqlRendererUnitTests {
Table table = SQL.table("foo");
Column bar = table.column("bar");
- Select select = Select.builder().select(bar).from(table).where(Conditions.isEqual(bar, new BindMarker.NamedBindMarker("name"))).build();
+ Select select = Select.builder().select(bar).from(table).where(Conditions.isEqual(bar, SQL.bindMarker(":name")))
+ .build();
- assertThat(NaiveSqlRenderer.render(select)).isEqualTo("SELECT foo.bar FROM foo WHERE foo.bar = :name");
+ assertThat(SqlRenderer.render(select)).isEqualTo("SELECT foo.bar FROM foo WHERE foo.bar = :name");
}
@Test // DATAJDBC-309
@@ -194,13 +204,11 @@ public class NaiveSqlRendererUnitTests {
Column bar = table.column("bar");
Column baz = table.column("baz");
- Select select = Select.builder().select(bar).from(table).where(
- Conditions.isEqual(bar, new BindMarker.NamedBindMarker("name"))
- .or(Conditions.isEqual(bar, new BindMarker.NamedBindMarker("name2")))
- .and(Conditions.isNull(baz))
- ).build();
+ 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(NaiveSqlRenderer.render(select)).isEqualTo("SELECT foo.bar FROM foo WHERE (foo.bar = :name OR foo.bar = :name2) AND foo.baz IS NULL");
+ assertThat(SqlRenderer.render(select))
+ .isEqualTo("SELECT foo.bar FROM foo WHERE foo.bar = :name OR foo.bar = :name2 AND foo.baz IS NULL");
}
@Test // DATAJDBC-309
@@ -209,11 +217,21 @@ public class NaiveSqlRendererUnitTests {
Table table = SQL.table("foo");
Column bar = table.column("bar");
- Select select = Select.builder().select(bar).from(table).where(
- Conditions.in(bar, new BindMarker.NamedBindMarker("name"))
- ).build();
+ Select select = Select.builder().select(bar).from(table).where(Conditions.in(bar, SQL.bindMarker(":name"))).build();
- assertThat(NaiveSqlRenderer.render(select)).isEqualTo("SELECT foo.bar FROM foo WHERE foo.bar IN (:name)");
+ assertThat(SqlRenderer.render(select)).isEqualTo("SELECT foo.bar FROM foo WHERE foo.bar IN (:name)");
+ }
+
+ @Test // DATAJDBC-309
+ public void shouldInWithNamedParameters() {
+
+ Table table = SQL.table("foo");
+ Column bar = table.column("bar");
+
+ 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)");
}
@Test // DATAJDBC-309
@@ -227,8 +245,30 @@ public class NaiveSqlRendererUnitTests {
Select subselect = Select.builder().select(bah).from(floo).build();
- Select select = Select.builder().select(bar).from(foo).where(Conditions.in(bar, new SubselectExpression(subselect))).build();
+ Select select = Select.builder().select(bar).from(foo).where(Conditions.in(bar, subselect)).build();
- assertThat(NaiveSqlRenderer.render(select)).isEqualTo("SELECT foo.bar FROM foo WHERE foo.bar IN (SELECT floo.bah FROM floo)");
+ assertThat(SqlRenderer.render(select))
+ .isEqualTo("SELECT foo.bar FROM foo WHERE foo.bar IN (SELECT floo.bah FROM floo)");
}
+
+ @Test // DATAJDBC-309
+ public void shouldConsiderNamingStrategy() {
+
+ Table foo = SQL.table("Foo");
+ Column bar = foo.column("BaR");
+ Column baz = foo.column("BaZ");
+
+ Select select = Select.builder().select(bar).from(foo).where(bar.isEqualTo(baz)).build();
+
+ String upper = SqlRenderer.create(select, new SimpleRenderContext(NamingStrategies.toUpper())).render();
+ assertThat(upper).isEqualTo("SELECT FOO.BAR FROM FOO WHERE FOO.BAR = FOO.BAZ");
+
+ String lower = SqlRenderer.create(select, new SimpleRenderContext(NamingStrategies.toLower())).render();
+ 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();
+ assertThat(mapped).isEqualTo("SELECT foo.baR FROM foo WHERE foo.baR = foo.baZ");
+ }
+
}