DATAJDBC-309 - Polishing.
Javadoc, static factory methods, typos. Refactor SQL rendering from a shared stack-based implementation to independent delegating visitors. Introduce DelegatingVisitor and TypedSubtreeVisitor base classes. Introduce SelectList container. Extract nested renderes to top-level types. Move SQL renderer to renderer package. Extend In to multi-expression argument. Introduce helper methods in Table to create multiple columns. Introduce factory method on StatementBuilder to create a new builder given a collection of expressions. Add support for comparison conditions and LIKE and equal/not equal/less with equals to/greater with equals to conditions. Add condition creation methods to Column so Column objects can now create conditions for a fluent DSL as in (.where(left.isGreater(right)). StatementBuilder.select(left).from(table).where(left.isGreater(right)).build(). Introduce RenderContext and RenderNamingStrategy. Add since tags. Improve Javadoc. Original pull request: #119.
This commit is contained in:
@@ -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) {
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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}.
|
||||
* <p/>
|
||||
* * Renders to: {@code <table>.*} as in {@code SELECT <table>.* FROM …}.
|
||||
* * Renders to: {@code
|
||||
*
|
||||
<table>
|
||||
* .*} as in {@code SELECT
|
||||
*
|
||||
<table>
|
||||
* .* 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() {
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
|
||||
@@ -24,6 +24,7 @@ import org.springframework.util.Assert;
|
||||
* Renders to: {@code <name>} or {@code <table(alias)>.<name>}.
|
||||
*
|
||||
* @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()
|
||||
|
||||
@@ -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.
|
||||
* <p/>
|
||||
* Results in a rendered condition: {@code <left> <comparator> <right>} (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();
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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() {}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -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<Expression> 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<OrderByField> orderBy;
|
||||
|
||||
DefaultSelect(boolean distinct, List<Expression> selectList, List<Table> from, long limit, long offset,
|
||||
List<Join> joins, @Nullable Condition where, List<OrderByField> orderBy) {
|
||||
List<Join> joins, @Nullable Condition where, List<OrderByField> 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));
|
||||
|
||||
|
||||
@@ -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<Join> joins = new ArrayList<>();
|
||||
private Condition where;
|
||||
private @Nullable Condition where;
|
||||
private List<OrderByField> 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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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 {}
|
||||
|
||||
@@ -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 {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -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<Table> tables) {
|
||||
|
||||
super(tables.toArray(new Table[]{}));
|
||||
super(tables.toArray(new Table[] {}));
|
||||
|
||||
this.tables = tables;
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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<Expression> expressions;
|
||||
|
||||
public In(Expression left, Expression right) {
|
||||
private In(Expression left, Collection<Expression> expressions) {
|
||||
|
||||
super(left, right);
|
||||
super(toArray(left, expressions));
|
||||
|
||||
this.left = left;
|
||||
this.right = right;
|
||||
this.expressions = expressions;
|
||||
}
|
||||
|
||||
private static Segment[] toArray(Expression expression, Collection<Expression> 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, ", ") + ")";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,10 +19,12 @@ package org.springframework.data.relational.core.sql;
|
||||
* {@link Segment} for a {@code JOIN} declaration.
|
||||
* <p/>
|
||||
* Renders to: {@code JOIN
|
||||
* <table>
|
||||
*
|
||||
<table>
|
||||
* ON <condition>}.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
* @since 1.1
|
||||
*/
|
||||
public class Join extends AbstractSegment {
|
||||
|
||||
|
||||
@@ -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.
|
||||
* <p/>
|
||||
* Results in a rendered condition: {@code <left> = <right>}.
|
||||
* Results in a rendered condition: {@code <left> LIKE <right>}.
|
||||
*
|
||||
* @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();
|
||||
}
|
||||
}
|
||||
@@ -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 {
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
* <p/>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.
|
||||
* <p/>
|
||||
* 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() {}
|
||||
}
|
||||
|
||||
@@ -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}.
|
||||
* <p/>
|
||||
* 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}.
|
||||
* <p/>
|
||||
* 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}.
|
||||
*/
|
||||
|
||||
@@ -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:
|
||||
* <ol>
|
||||
* <li>Self</li>
|
||||
* <li>{@link Column SELECT columns} </li>
|
||||
* <li>{@link Column SELECT columns}</li>
|
||||
* <li>{@link Table FROM tables} clause</li>
|
||||
* <li>{@link Join JOINs}</li>
|
||||
* <li>{@link Condition WHERE} condition</li>
|
||||
@@ -30,12 +29,13 @@ import java.util.OptionalLong;
|
||||
* </ol>
|
||||
*
|
||||
* @author Mark Paluch
|
||||
* @since 1.1
|
||||
* @see StatementBuilder
|
||||
* @see SelectBuilder
|
||||
* @see SQL
|
||||
*/
|
||||
public interface Select extends Segment, Visitable {
|
||||
|
||||
|
||||
/**
|
||||
* Creates a new {@link SelectBuilder}.
|
||||
*
|
||||
|
||||
@@ -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.
|
||||
*/
|
||||
|
||||
@@ -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<Expression> selectList;
|
||||
|
||||
SelectList(List<Expression> 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, ", ");
|
||||
}
|
||||
}
|
||||
@@ -19,7 +19,13 @@ import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Validator for {@link Select} statements.
|
||||
* <p/>
|
||||
* 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();
|
||||
|
||||
@@ -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 {
|
||||
|
||||
|
||||
@@ -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<Expression> expressions;
|
||||
|
||||
SimpleFunction(String functionName, List<Expression> expressions) {
|
||||
private SimpleFunction(String functionName, List<Expression> 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<Expression> 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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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() {
|
||||
|
||||
}
|
||||
}
|
||||
@@ -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() + ")";
|
||||
|
||||
@@ -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 <name>} or {@code <name> AS <name>}.
|
||||
*
|
||||
* @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}.
|
||||
* <p/>
|
||||
* 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}.
|
||||
* <p/>
|
||||
* 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}.
|
||||
* <p/>
|
||||
* 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<Column> columns(Collection<String> names) {
|
||||
|
||||
Assert.notNull(names, "Names must not be null");
|
||||
|
||||
List<Column> 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
|
||||
* <table>
|
||||
*
|
||||
<table>
|
||||
* .*}.
|
||||
*
|
||||
* @return the select all marker for this {@link Table}.
|
||||
|
||||
@@ -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}.
|
||||
*/
|
||||
|
||||
@@ -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) {}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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.
|
||||
* <p/> 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.
|
||||
* <p/>
|
||||
* 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;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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<Condition> 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;
|
||||
}
|
||||
}
|
||||
@@ -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.
|
||||
* <p/>
|
||||
* <h3>Delegation</h3> 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.
|
||||
* <p/>
|
||||
* 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.
|
||||
* <p/>
|
||||
* 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<DelegatingVisitor> delegation = new Stack<>();
|
||||
|
||||
/**
|
||||
* Invoked for a {@link Visitable segment} when entering the segment.
|
||||
* <p/>
|
||||
* 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.
|
||||
* <p/>
|
||||
* 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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<Expression> 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;
|
||||
}
|
||||
}
|
||||
@@ -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<Visitable> 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();
|
||||
}
|
||||
}
|
||||
@@ -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}.
|
||||
* <p/>
|
||||
* Filtering is a three-way process:
|
||||
* <ol>
|
||||
* <li>Ignores elements that do not match the filter {@link Predicate}.</li>
|
||||
* <li>{@link #enterMatched(Visitable) enter}/{@link #leaveMatched(Visitable) leave} matched callbacks for the
|
||||
* {@link Visitable segment} that matches the {@link Predicate}.</li>
|
||||
* <li>{@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}.</li>
|
||||
* </ol>
|
||||
*
|
||||
* @author Mark Paluch
|
||||
* @see TypedSubtreeVisitor
|
||||
* @since 1.1
|
||||
*/
|
||||
abstract class FilteredSubtreeVisitor extends DelegatingVisitor {
|
||||
|
||||
private final Predicate<Visitable> 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<Visitable> 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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<From> {
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -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<Table> {
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -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<In> {
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -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<IsNull> {
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -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<Join> {
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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<String, String> 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<String, String> 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));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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<OrderByField> 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;
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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<String, String> mappingFunction) {
|
||||
|
||||
Assert.notNull(mappingFunction, "Mapping function must not be null!");
|
||||
|
||||
return new DelegatingRenderNamingStrategy(this, mappingFunction);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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<SelectList> 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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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<T extends Visitable & Condition> extends TypedSubtreeVisitor<T> {
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
@@ -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}.
|
||||
* <p/>
|
||||
* Filtering is a three-way process:
|
||||
* <ol>
|
||||
* <li>Ignores elements that do not match the filter {@link Predicate}.</li>
|
||||
* <li>{@link #enterMatched(Visitable) enter}/{@link #leaveMatched(Visitable) leave} matched callbacks for the
|
||||
* {@link Visitable segment} that matches the {@link Predicate}.</li>
|
||||
* <li>{@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}.</li>
|
||||
* </ol>
|
||||
*
|
||||
* @author Mark Paluch
|
||||
* @since 1.1
|
||||
* @see FilteredSubtreeVisitor
|
||||
*/
|
||||
abstract class TypedSubtreeVisitor<T extends Visitable> 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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<Where> {
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
@@ -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<Visitor> 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();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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");
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
@@ -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");
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user