diff --git a/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/AbstractSegment.java b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/AbstractSegment.java index 0c60c747..490da7b1 100644 --- a/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/AbstractSegment.java +++ b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/AbstractSegment.java @@ -15,6 +15,8 @@ */ package org.springframework.data.relational.core.sql; +import org.springframework.util.Assert; + /** * Abstract implementation to support {@link Segment} implementations. * @@ -22,6 +24,24 @@ package org.springframework.data.relational.core.sql; */ abstract class AbstractSegment implements Segment { + private final Segment[] children; + + protected AbstractSegment(Segment ... children) { + this.children = children; + } + + @Override + public void visit(Visitor visitor) { + + Assert.notNull(visitor, "Visitor must not be null!"); + + visitor.enter(this); + for (Segment child : children) { + child.visit(visitor); + } + visitor.leave(this); + } + /* * (non-Javadoc) * @see java.lang.Object#hashCode() diff --git a/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/SelectTop.java b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/AliasedExpression.java similarity index 61% rename from spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/SelectTop.java rename to spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/AliasedExpression.java index 173b9bbe..6d2b48a8 100644 --- a/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/SelectTop.java +++ b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/AliasedExpression.java @@ -16,35 +16,30 @@ package org.springframework.data.relational.core.sql; /** - * {@code TOP} clause for {@code SELECT TOP …}. + * An expression with an alias. * - * @author Mark Paluch + * @author Jens Schauder */ -public class SelectTop extends AbstractSegment implements Segment { +public class AliasedExpression extends AbstractSegment implements Aliased, Expression { - private final int count; + private final Expression expression; + private final String alias; - private SelectTop(int count) { - this.count = count; + public AliasedExpression(Expression expression, String alias) { + + super(expression); + + this.expression = expression; + this.alias = alias; } - public static SelectTop create(int count) { - return new SelectTop(count); + @Override + public String getAlias() { + return alias; } - /** - * @return the count. - */ - public int getCount() { - return count; - } - - /* - * (non-Javadoc) - * @see java.lang.Object#toString() - */ @Override public String toString() { - return "TOP " + count; + return expression.toString() + " AS " + alias; } } diff --git a/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/AndCondition.java b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/AndCondition.java index a80bdbc1..a0201d8c 100644 --- a/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/AndCondition.java +++ b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/AndCondition.java @@ -15,59 +15,16 @@ */ package org.springframework.data.relational.core.sql; -import org.springframework.util.Assert; - /** * {@link Condition} representing an {@code AND} relation between two {@link Condition}s. * * @author Mark Paluch * @see Condition#and(Condition) */ -public class AndCondition implements Condition { +public class AndCondition extends MultipleCondition { - private final Condition left; - private final Condition right; - - AndCondition(Condition left, Condition right) { - this.left = left; - this.right = right; + AndCondition(Condition... conditions) { + super(" AND ", conditions); } - /* - * (non-Javadoc) - * @see org.springframework.data.relational.core.sql.Visitable#visit(org.springframework.data.relational.core.sql.Visitor) - */ - @Override - public void visit(Visitor visitor) { - - Assert.notNull(visitor, "Visitor must not be null!"); - - visitor.enter(this); - left.visit(visitor); - right.visit(visitor); - visitor.leave(this); - } - - /** - * @return the left {@link Condition}. - */ - public Condition getLeft() { - return left; - } - - /** - * @return the right {@link Condition}. - */ - public Condition getRight() { - return right; - } - - /* - * (non-Javadoc) - * @see java.lang.Object#toString() - */ - @Override - public String toString() { - return left.toString() + " AND " + right.toString(); - } } diff --git a/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/AsteriskFromTable.java b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/AsteriskFromTable.java index dc91d52a..7dc5cd87 100644 --- a/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/AsteriskFromTable.java +++ b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/AsteriskFromTable.java @@ -30,6 +30,7 @@ public class AsteriskFromTable extends AbstractSegment implements Expression { private final Table table; AsteriskFromTable(Table table) { + super(table); this.table = table; } @@ -37,16 +38,6 @@ public class AsteriskFromTable extends AbstractSegment implements Expression { return new AsteriskFromTable(table); } - @Override - public void visit(Visitor visitor) { - - Assert.notNull(visitor, "Visitor must not be null!"); - - visitor.enter(this); - table.visit(visitor); - visitor.leave(this); - } - /** * @return the associated {@link Table}. */ diff --git a/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/BindMarker.java b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/BindMarker.java index 0c8c070a..5f14d0cb 100644 --- a/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/BindMarker.java +++ b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/BindMarker.java @@ -22,7 +22,7 @@ import org.springframework.lang.Nullable; * * @author Mark Paluch */ -public class BindMarker implements Segment { +public class BindMarker extends AbstractSegment implements Expression { /* * (non-Javadoc) diff --git a/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/Column.java b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/Column.java index e2e4cfa4..dccf97ee 100644 --- a/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/Column.java +++ b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/Column.java @@ -28,44 +28,17 @@ import org.springframework.util.Assert; public class Column extends AbstractSegment implements Expression, Named { private final String name; - private final @Nullable Table table; + private final Table table; - Column(String name, @Nullable Table table) { + Column(String name, Table table) { + super(table); Assert.notNull(name, "Name must not be null"); this.name = name; this.table = table; } - /** - * Creates a new {@link Column}. - * - * @param name column name, must not {@literal null} or empty. - * @return the new {@link Column}. - */ - public static Column create(String name) { - - Assert.hasText(name, "Name must not be null or empty"); - - return new Column(name, null); - } - - /** - * Creates a new aliased {@link Column}. - * - * @param name column name, must not {@literal null} or empty. - * @param alias alias name, must not {@literal null} or empty. - * @return the new {@link Column}. - */ - public static Column aliased(String name, String alias) { - - Assert.hasText(name, "Name must not be null or empty"); - Assert.hasText(alias, "Alias must not be null or empty"); - - return new AliasedColumn(name, null, alias); - } - /** * Creates a new {@link Column} associated with a {@link Table}. * @@ -124,23 +97,6 @@ public class Column extends AbstractSegment implements Expression, Named { return new Column(name, table); } - /* - * (non-Javadoc) - * @see org.springframework.data.relational.core.sql.Visitable#visit(org.springframework.data.relational.core.sql.Visitor) - */ - @Override - public void visit(Visitor visitor) { - - Assert.notNull(visitor, "Visitor must not be null!"); - - visitor.enter(this); - - if (table != null) { - table.visit(visitor); - } - visitor.leave(this); - } - /* * (non-Javadoc) * @see org.springframework.data.relational.core.sql.Named#getName() @@ -192,7 +148,7 @@ public class Column extends AbstractSegment implements Expression, Named { private final String alias; - private AliasedColumn(String name, @Nullable Table table, String alias) { + private AliasedColumn(String name, Table table, String alias) { super(name, table); this.alias = alias; } diff --git a/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/Condition.java b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/Condition.java index 803fcac4..88341993 100644 --- a/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/Condition.java +++ b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/Condition.java @@ -43,12 +43,7 @@ public interface Condition extends Segment { return new OrCondition(this, other); } - /** - * Encapsulate this {@link Condition} in a group of parentheses. - * - * @return the grouped {@link Condition}. - */ - default Condition group() { - return new ConditionGroup(this); + default Condition not() { + return new Not(this); } } diff --git a/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/ConditionGroup.java b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/ConditionGroup.java deleted file mode 100644 index f05db1c9..00000000 --- a/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/ConditionGroup.java +++ /dev/null @@ -1,63 +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 org.springframework.util.Assert; - -/** - * Grouped {@link Condition} wrapping one or more {@link Condition}s into a parentheses group. - * - * @author Mark Paluch - * @see Condition#group() - */ -public class ConditionGroup implements Condition { - - private final Condition nested; - - ConditionGroup(Condition nested) { - this.nested = nested; - } - - /* - * (non-Javadoc) - * @see org.springframework.data.relational.core.sql.Visitable#visit(org.springframework.data.relational.core.sql.Visitor) - */ - @Override - public void visit(Visitor visitor) { - - Assert.notNull(visitor, "Visitor must not be null!"); - - visitor.enter(this); - nested.visit(visitor); - visitor.leave(this); - } - - /** - * @return the nested (grouped) {@link Condition}. - */ - public Condition getNested() { - return nested; - } - - /* - * (non-Javadoc) - * @see java.lang.Object#toString() - */ - @Override - public String toString() { - return "(" + nested.toString() + ")"; - } -} diff --git a/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/Conditions.java b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/Conditions.java index a7b6cefe..3e16c13d 100644 --- a/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/Conditions.java +++ b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/Conditions.java @@ -46,6 +46,18 @@ public abstract class Conditions { private Conditions() { } + public static Condition isNull(Expression expression) { + return new IsNull(expression); + } + + public static Condition isEqual(Column bar, Expression param) { + return new Equals(bar, param); + } + + public static Condition in(Column bar, Expression subselectExpression) { + return new In(bar, subselectExpression); + } + static class ConstantCondition extends AbstractSegment implements Condition { private final String condition; diff --git a/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/DefaultSelect.java b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/DefaultSelect.java index 21b3ce7d..50f6507b 100644 --- a/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/DefaultSelect.java +++ b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/DefaultSelect.java @@ -29,7 +29,7 @@ import org.springframework.util.Assert; */ class DefaultSelect implements Select { - private final @Nullable SelectTop top; + private final boolean distinct; private final List selectList; private final From from; private final long limit; @@ -38,10 +38,10 @@ class DefaultSelect implements Select { private final @Nullable Where where; private final List orderBy; - DefaultSelect(@Nullable SelectTop top, List selectList, List from, long limit, long offset, + DefaultSelect(boolean distinct, List selectList, List
from, long limit, long offset, List joins, @Nullable Condition where, List orderBy) { - this.top = top; + this.distinct = distinct; this.selectList = new ArrayList<>(selectList); this.from = new From(from); this.limit = limit; @@ -69,6 +69,11 @@ class DefaultSelect implements Select { return offset == -1 ? OptionalLong.empty() : OptionalLong.of(offset); } + @Override + public boolean isDistinct() { + return distinct; + } + /* * (non-Javadoc) * @see org.springframework.data.relational.core.sql.Visitable#visit(org.springframework.data.relational.core.sql.Visitor) @@ -80,8 +85,6 @@ class DefaultSelect implements Select { visitor.enter(this); - visitIfNotNull(top, visitor); - selectList.forEach(it -> it.visit(visitor)); from.visit(visitor); joins.forEach(it -> it.visit(visitor)); @@ -94,6 +97,7 @@ class DefaultSelect implements Select { } private void visitIfNotNull(@Nullable Visitable visitable, Visitor visitor) { + if (visitable != null) { visitable.visit(visitor); } diff --git a/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/DefaultSelectBuilder.java b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/DefaultSelectBuilder.java index e1b080a9..15dfb3f6 100644 --- a/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/DefaultSelectBuilder.java +++ b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/DefaultSelectBuilder.java @@ -32,7 +32,7 @@ import org.springframework.data.relational.core.sql.SelectBuilder.SelectWhereAnd */ class DefaultSelectBuilder implements SelectBuilder, SelectAndFrom, SelectFromAndJoin, SelectWhereAndOr { - private SelectTop top; + private boolean distinct = false; private List selectList = new ArrayList<>(); private List
from = new ArrayList<>(); private long limit = -1; @@ -48,19 +48,10 @@ class DefaultSelectBuilder implements SelectBuilder, SelectAndFrom, SelectFromAn @Override public SelectBuilder top(int count) { - top = SelectTop.create(count); + limit = count; return this; } - /* - * (non-Javadoc) - * @see org.springframework.data.relational.core.sql.SelectBuilder#select(java.lang.String) - */ - @Override - public DefaultSelectBuilder select(String sql) { - return select(Column.create(sql)); - } - /* * (non-Javadoc) * @see org.springframework.data.relational.core.sql.SelectBuilder#select(org.springframework.data.relational.core.sql.Expression) @@ -91,6 +82,12 @@ class DefaultSelectBuilder implements SelectBuilder, SelectAndFrom, SelectFromAn return this; } + @Override + public DefaultSelectBuilder distinct() { + distinct = true; + return this; + } + /* * (non-Javadoc) * @see org.springframework.data.relational.core.sql.SelectBuilder.SelectFrom#from(java.lang.String) @@ -161,28 +158,6 @@ class DefaultSelectBuilder implements SelectBuilder, SelectAndFrom, SelectFromAn return this; } - /* - * (non-Javadoc) - * @see org.springframework.data.relational.core.sql.SelectBuilder.SelectFromAndOrderBy#orderBy(java.lang.String) - */ - @Override - public DefaultSelectBuilder orderBy(String field) { - return orderBy(OrderByField.create(field)); - } - - /* - * (non-Javadoc) - * @see org.springframework.data.relational.core.sql.SelectBuilder.SelectFromAndOrderBy#orderBy(int[]) - */ - @Override - public DefaultSelectBuilder orderBy(int... indexes) { - - for (int index : indexes) { - this.orderBy.add(OrderByField.index(index)); - } - return this; - } - /* * (non-Javadoc) * @see org.springframework.data.relational.core.sql.SelectBuilder.SelectFromAndOrderBy#orderBy(org.springframework.data.relational.core.sql.OrderByField[]) @@ -284,7 +259,7 @@ class DefaultSelectBuilder implements SelectBuilder, SelectAndFrom, SelectFromAn */ @Override public Select build() { - DefaultSelect select = new DefaultSelect(top, selectList, from, limit, offset, joins, where, orderBy); + DefaultSelect select = new DefaultSelect(distinct, selectList, from, limit, offset, joins, where, orderBy); SelectValidator.validate(select); return select; } @@ -305,15 +280,6 @@ class DefaultSelectBuilder implements SelectBuilder, SelectAndFrom, SelectFromAn this.selectBuilder = selectBuilder; } - /* - * (non-Javadoc) - * @see org.springframework.data.relational.core.sql.SelectBuilder.SelectOn#on(java.lang.String) - */ - @Override - public SelectOnConditionComparison on(String column) { - return on(Column.create(column)); - } - /* * (non-Javadoc) * @see org.springframework.data.relational.core.sql.SelectBuilder.SelectOn#on(org.springframework.data.relational.core.sql.Expression) @@ -325,15 +291,6 @@ class DefaultSelectBuilder implements SelectBuilder, SelectAndFrom, SelectFromAn return this; } - /* - * (non-Javadoc) - * @see org.springframework.data.relational.core.sql.SelectBuilder.SelectOnConditionComparison#equals(java.lang.String) - */ - @Override - public JoinBuilder equals(String column) { - return equals(Column.create(column)); - } - /* * (non-Javadoc) * @see org.springframework.data.relational.core.sql.SelectBuilder.SelectOnConditionComparison#equals(org.springframework.data.relational.core.sql.Expression) @@ -344,15 +301,6 @@ class DefaultSelectBuilder implements SelectBuilder, SelectAndFrom, SelectFromAn return this; } - /* - * (non-Javadoc) - * @see org.springframework.data.relational.core.sql.SelectBuilder.SelectOnCondition#and(java.lang.String) - */ - @Override - public SelectOnConditionComparison and(String column) { - return and(Column.create(column)); - } - /* * (non-Javadoc) * @see org.springframework.data.relational.core.sql.SelectBuilder.SelectOnCondition#and(org.springframework.data.relational.core.sql.Expression) @@ -381,27 +329,6 @@ class DefaultSelectBuilder implements SelectBuilder, SelectAndFrom, SelectFromAn return new Join(JoinType.JOIN, table, condition); } - /* - * (non-Javadoc) - * @see org.springframework.data.relational.core.sql.SelectBuilder.SelectOrdered#orderBy(java.lang.String) - */ - @Override - public SelectOrdered orderBy(String field) { - - selectBuilder.join(finishJoin()); - return selectBuilder.orderBy(field); - } - - /* - * (non-Javadoc) - * @see org.springframework.data.relational.core.sql.SelectBuilder.SelectOrdered#orderBy(int[]) - */ - @Override - public SelectOrdered orderBy(int... indexes) { - selectBuilder.join(finishJoin()); - return selectBuilder.orderBy(indexes); - } - /* * (non-Javadoc) * @see org.springframework.data.relational.core.sql.SelectBuilder.SelectOrdered#orderBy(org.springframework.data.relational.core.sql.OrderByField[]) diff --git a/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/Equals.java b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/Equals.java index 9452d6f1..5d077625 100644 --- a/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/Equals.java +++ b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/Equals.java @@ -30,6 +30,9 @@ public class Equals extends AbstractSegment implements Condition { private final Expression right; Equals(Expression left, Expression right) { + + super(left, right); + this.left = left; this.right = right; } @@ -49,17 +52,6 @@ public class Equals extends AbstractSegment implements Condition { return new Equals(left, right); } - @Override - public void visit(Visitor visitor) { - - Assert.notNull(visitor, "Visitor must not be null!"); - - visitor.enter(this); - left.visit(visitor); - right.visit(visitor); - visitor.leave(this); - } - /** * @return the left {@link Expression}. */ diff --git a/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/Expressions.java b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/Expressions.java index 68917c36..580d6d4f 100644 --- a/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/Expressions.java +++ b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/Expressions.java @@ -25,7 +25,7 @@ package org.springframework.data.relational.core.sql; */ public abstract class Expressions { - private static Expression ASTERISK = new ConstantExpression("*"); + private static Expression ASTERISK = new SimpleExpression("*"); /** * @return a new asterisk {@code *} expression. @@ -41,7 +41,7 @@ public abstract class Expressions { * @return a SQL {@link Expression}. */ public static Expression just(String sql) { - return new ConstantExpression(sql); + return new SimpleExpression(sql); } /** @@ -55,11 +55,11 @@ public abstract class Expressions { private Expressions() { } - static class ConstantExpression extends AbstractSegment implements Expression { + static class SimpleExpression extends AbstractSegment implements Expression { private final String expression; - ConstantExpression(String expression) { + SimpleExpression(String expression) { this.expression = expression; } diff --git a/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/From.java b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/From.java index 733d3377..777aeda0 100644 --- a/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/From.java +++ b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/From.java @@ -16,6 +16,7 @@ package org.springframework.data.relational.core.sql; import java.util.Arrays; +import java.util.Collections; import java.util.List; import org.springframework.util.Assert; @@ -26,7 +27,7 @@ import org.springframework.util.StringUtils; * * @author Mark Paluch */ -public class From extends AbstractSegment implements Segment { +public class From extends AbstractSegment { private final List
tables; @@ -35,25 +36,12 @@ public class From extends AbstractSegment implements Segment { } From(List
tables) { + + super(tables.toArray(new Table[]{})); + this.tables = tables; } - /* - * (non-Javadoc) - * @see org.springframework.data.relational.core.sql.Visitable#visit(org.springframework.data.relational.core.sql.Visitor) - */ - @Override - public void visit(Visitor visitor) { - - Assert.notNull(visitor, "Visitor must not be null!"); - - visitor.enter(this); - - tables.forEach(it -> it.visit(visitor)); - - visitor.leave(this); - } - /* * (non-Javadoc) * @see java.lang.Object#toString() diff --git a/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/Functions.java b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/Functions.java index f6834405..6edf7ae2 100644 --- a/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/Functions.java +++ b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/Functions.java @@ -18,7 +18,6 @@ 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; @@ -32,60 +31,6 @@ import org.springframework.util.Assert; */ public class Functions { - /** - * Creates a new {@link Distinct} function. - * - * @param columnNames column names to apply distinction, must not be {@literal null}. - * @return the new {@link Distinct} for {@code columns}. - */ - public static Distinct distinct(String... columnNames) { - - Assert.notNull(columnNames, "Columns must not be null!"); - - List columns = new ArrayList<>(); - for (String columnName : columnNames) { - columns.add(Column.create(columnName)); - } - - return distinct(columns); - } - - /** - * Creates a new {@link Distinct} function. - * - * @param columns columns to apply distinction, must not be {@literal null}. - * @return the new {@link Distinct} for {@code columns}. - */ - public static Distinct distinct(Column... columns) { - - Assert.notNull(columns, "Columns must not be null!"); - - return new Distinct(Arrays.asList(columns)); - } - - /** - * Creates a new {@link Distinct} function. - * - * @param columns columns to apply distinction, must not be {@literal null}. - * @return the new {@link Distinct} for {@code columns}. - */ - public static Distinct distinct(Collection columns) { - - Assert.notNull(columns, "Columns must not be null!"); - - return new Distinct(new ArrayList<>(columns)); - } - - /** - * Creates a new {@code COUNT} function for a single {@code column}. - * - * @param column column to apply count, must not be {@literal null} or empty. - * @return the new {@link SimpleFunction count function} for {@code column}. - */ - public static SimpleFunction count(String column) { - return count(Column.create(column)); - } - /** * Creates a new {@code COUNT} function. * @@ -114,6 +59,5 @@ public class Functions { } // Utility constructor. - private Functions() { - } + private Functions() {} } diff --git a/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/In.java b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/In.java new file mode 100644 index 00000000..a66c3d81 --- /dev/null +++ b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/In.java @@ -0,0 +1,38 @@ +/* + * 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; + +/** + * @author Jens Schauder + */ +public class In extends AbstractSegment implements Condition { + + private final Expression left; + private final Expression right; + + public In(Expression left, Expression right) { + + super(left, right); + + this.left = left; + this.right = right; + } + + @Override + public String toString() { + return left + " IN " + right; + } +} diff --git a/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/IsNull.java b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/IsNull.java new file mode 100644 index 00000000..88e7d29c --- /dev/null +++ b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/IsNull.java @@ -0,0 +1,52 @@ +/* + * Copyright 2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.relational.core.sql; + +/** + * @author Jens Schauder + */ +public class IsNull extends AbstractSegment implements Condition { + + private final Expression expression; + + private final boolean negated; + + public IsNull(Expression expression, boolean negated) { + + super(expression); + + this.expression = expression; + this.negated = negated; + } + + public IsNull(Expression expression) { + this(expression, false); + } + + @Override + public Condition not() { + return new IsNull(expression, !negated); + } + + @Override + public String toString() { + return expression + (negated ? " IS NOT NULL" : " IS NULL"); + } + + public boolean isNegated() { + return negated; + } +} diff --git a/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/Join.java b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/Join.java index 71b4f97c..58c6b98a 100644 --- a/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/Join.java +++ b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/Join.java @@ -24,33 +24,21 @@ package org.springframework.data.relational.core.sql; * * @author Mark Paluch */ -public class Join implements Segment { +public class Join extends AbstractSegment { private final JoinType type; private final Table joinTable; private final Condition on; Join(JoinType type, Table joinTable, Condition on) { + + super(joinTable, on); + this.joinTable = joinTable; this.type = type; this.on = on; } - /* - * (non-Javadoc) - * @see org.springframework.data.relational.core.sql.Visitable#visit(org.springframework.data.relational.core.sql.Visitor) - */ - @Override - public void visit(Visitor visitor) { - - visitor.enter(this); - - joinTable.visit(visitor); - on.visit(visitor); - - visitor.leave(this); - } - /** * @return join type. */ diff --git a/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/Distinct.java b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/MultipleCondition.java similarity index 52% rename from spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/Distinct.java rename to spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/MultipleCondition.java index f5a987c8..107e8dbb 100644 --- a/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/Distinct.java +++ b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/MultipleCondition.java @@ -15,38 +15,26 @@ */ package org.springframework.data.relational.core.sql; +import java.util.Arrays; import java.util.List; +import java.util.StringJoiner; import org.springframework.util.Assert; -import org.springframework.util.StringUtils; /** - * {@code DISTINCT} {@link Expression}. - *

- * * Renders to: {@code DISTINCT column1, column2, columnN}. - * - * @author Mark Paluch + * @author Jens Schauder */ -public class Distinct extends AbstractSegment implements Expression { +public abstract class MultipleCondition extends AbstractSegment implements Condition { - private List columns; + private final List conditions; + private final String delimiter; - Distinct(List columns) { - this.columns = columns; - } + MultipleCondition(String delimiter, Condition... conditions) { - /* - * (non-Javadoc) - * @see org.springframework.data.relational.core.sql.Visitable#visit(org.springframework.data.relational.core.sql.Visitor) - */ - @Override - public void visit(Visitor visitor) { + super(conditions); - Assert.notNull(visitor, "Visitor must not be null!"); - - visitor.enter(this); - columns.forEach(it -> it.visit(visitor)); - visitor.leave(this); + this.delimiter = delimiter; + this.conditions = Arrays.asList(conditions); } /* @@ -55,6 +43,9 @@ public class Distinct extends AbstractSegment implements Expression { */ @Override public String toString() { - return "DISTINCT " + StringUtils.collectionToDelimitedString(columns, ", "); + + StringJoiner joiner = new StringJoiner(delimiter); + conditions.forEach(c -> joiner.add(c.toString())); + return joiner.toString(); } } diff --git a/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/Not.java b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/Not.java new file mode 100644 index 00000000..993d5972 --- /dev/null +++ b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/Not.java @@ -0,0 +1,41 @@ +/* + * Copyright 2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.relational.core.sql; + +/** + * @author Jens Schauder + */ +public class Not extends AbstractSegment implements Condition { + + private final Condition condition; + + public Not(Condition condition) { + + super(condition); + + this.condition = condition; + } + + @Override + public Condition not() { + return condition; + } + + @Override + public String toString() { + return "NOT " + condition.toString(); + } +} diff --git a/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/OrCondition.java b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/OrCondition.java index c369c142..7886d978 100644 --- a/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/OrCondition.java +++ b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/OrCondition.java @@ -15,59 +15,15 @@ */ package org.springframework.data.relational.core.sql; -import org.springframework.util.Assert; - /** * {@link Condition} representing an {@code OR} relation between two {@link Condition}s. * * @author Mark Paluch * @see Condition#or(Condition) */ -public class OrCondition implements Condition { +public class OrCondition extends MultipleCondition { - private final Condition left; - private final Condition right; - - OrCondition(Condition left, Condition right) { - this.left = left; - this.right = right; - } - - /* - * (non-Javadoc) - * @see org.springframework.data.relational.core.sql.Visitable#visit(org.springframework.data.relational.core.sql.Visitor) - */ - @Override - public void visit(Visitor visitor) { - - Assert.notNull(visitor, "Visitor must not be null!"); - - visitor.enter(this); - left.visit(visitor); - right.visit(visitor); - visitor.leave(this); - } - - /** - * @return the left {@link Condition}. - */ - public Condition getLeft() { - return left; - } - - /** - * @return the right {@link Condition}. - */ - public Condition getRight() { - return right; - } - - /* - * (non-Javadoc) - * @see java.lang.Object#toString() - */ - @Override - public String toString() { - return left.toString() + " OR " + right.toString(); + OrCondition(Condition... conditions) { + super(" OR ", conditions); } } diff --git a/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/OrderByField.java b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/OrderByField.java index e0472db5..187f9c16 100644 --- a/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/OrderByField.java +++ b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/OrderByField.java @@ -24,7 +24,7 @@ import org.springframework.util.Assert; /** * @author Mark Paluch */ -public class OrderByField extends AbstractSegment implements Segment { +public class OrderByField extends AbstractSegment { private final Expression expression; private final @Nullable Sort.Direction direction; @@ -32,6 +32,7 @@ public class OrderByField extends AbstractSegment implements Segment { OrderByField(Expression expression, Direction direction, NullHandling nullHandling) { + super(expression); Assert.notNull(expression, "Order by expression must not be null"); Assert.notNull(nullHandling, "NullHandling by expression must not be null"); @@ -44,14 +45,6 @@ public class OrderByField extends AbstractSegment implements Segment { return new OrderByField(column, null, NullHandling.NATIVE); } - public static OrderByField create(String name) { - return new OrderByField(Column.create(name), null, NullHandling.NATIVE); - } - - public static OrderByField index(int index) { - return new OrderByField(new IndexedOrderByField(index), null, NullHandling.NATIVE); - } - public OrderByField asc() { return new OrderByField(expression, Direction.ASC, NullHandling.NATIVE); } @@ -64,18 +57,6 @@ public class OrderByField extends AbstractSegment implements Segment { return new OrderByField(expression, direction, nullHandling); } - /* - * (non-Javadoc) - * @see org.springframework.data.relational.core.sql.Visitable#visit(org.springframework.data.relational.core.sql.Visitor) - */ - @Override - public void visit(Visitor visitor) { - - visitor.enter(this); - expression.visit(visitor); - visitor.leave(this); - } - public Expression getExpression() { return expression; } @@ -97,11 +78,4 @@ public class OrderByField extends AbstractSegment implements Segment { public String toString() { return direction != null ? expression.toString() + " " + direction : expression.toString(); } - - static class IndexedOrderByField extends Column implements Expression { - - IndexedOrderByField(int index) { - super("" + index, null); - } - } } diff --git a/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/SQL.java b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/SQL.java index dabd35df..369727fe 100644 --- a/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/SQL.java +++ b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/SQL.java @@ -63,26 +63,6 @@ public abstract class SQL { return Select.builder(); } - /** - * Creates a new {@link SelectTop SELECT TOP count} segment. - * - * @param count the TOP count. - * @return the new {@link SelectTop} segment. - */ - public static SelectTop top(int count) { - return SelectTop.create(count); - } - - /** - * Creates a new {@link Column}. - * - * @param name column name, must not be {@literal null} or empty. - * @return the column with {@code name}. - */ - public static Column column(String name) { - return Column.create(name); - } - /** * Creates a new {@link Column} associated with a source {@link Table}. * diff --git a/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/Select.java b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/Select.java index ced213fd..d225364a 100644 --- a/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/Select.java +++ b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/Select.java @@ -22,7 +22,6 @@ import java.util.OptionalLong; * Visiting order: *

    *
  1. Self
  2. - *
  3. {@link SelectTop top clause}
  4. *
  5. {@link Column SELECT columns}
  6. *
  7. {@link Table FROM tables} clause
  8. *
  9. {@link Join JOINs}
  10. @@ -34,7 +33,7 @@ import java.util.OptionalLong; * @see SelectBuilder * @see SQL */ -public interface Select extends Visitable { +public interface Select extends Segment, Visitable { /** @@ -59,4 +58,11 @@ public interface Select extends Visitable { * @return */ OptionalLong getOffset(); + + /** + * Flag if this select is to return distinct rows. + * + * @return + */ + boolean isDistinct(); } diff --git a/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/SelectBuilder.java b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/SelectBuilder.java index 1abe0809..4825584a 100644 --- a/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/SelectBuilder.java +++ b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/SelectBuilder.java @@ -29,25 +29,14 @@ public interface SelectBuilder { * * @param count the top count. * @return {@code this} {@link SelectBuilder}. - * @see SelectTop */ SelectBuilder top(int count); - /** - * Include an arbitrary {@code sql} select list item. The {@code sql} is encapsulated into a simple {@link Expression}. - * - * @param sql the select list item. - * @return {@code this} builder. - * @see SQL#column(String) - */ - SelectAndFrom select(String sql); - /** * Include a {@link Expression} in the select list. * * @param expression the expression to include. * @return {@code this} builder. - * @see SQL#column(String) * @see Table#column(String) */ SelectAndFrom select(Expression expression); @@ -57,7 +46,6 @@ public interface SelectBuilder { * * @param expressions the expressions to include. * @return {@code this} builder. - * @see SQL#column(String) * @see Table#columns(String...) */ SelectAndFrom select(Expression... expressions); @@ -67,31 +55,27 @@ public interface SelectBuilder { * * @param expressions the expressions to include. * @return {@code this} builder. - * @see SQL#column(String) * @see Table#columns(String...) */ SelectAndFrom select(Collection expressions); + /** + * Makes the select statement distinct + * + * @return {@code this} builder. + */ + SelectAndFrom distinct(); + /** * Builder exposing {@code select} and {@code from} methods. */ interface SelectAndFrom extends SelectFrom { - /** - * Include an arbitrary {@code sql} select list item. The {@code sql} is encapsulated into a simple {@link Expression}. Multiple calls to this or other {@code select} methods keep adding items to the select list and do not replace previously contained items. - * - * @param sql the select list item. - * @return {@code this} builder. - * @see SQL#column(String) - */ - SelectFrom select(String sql); - /** * 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. - * @see SQL#column(String) * @see Table#column(String) */ SelectFrom select(Expression expression); @@ -101,7 +85,6 @@ public interface SelectBuilder { * * @param expressions the expressions to include. * @return {@code this} builder. - * @see SQL#column(String) * @see Table#columns(String...) */ SelectFrom select(Expression... expressions); @@ -111,11 +94,17 @@ public interface SelectBuilder { * * @param expressions the expressions to include. * @return {@code this} builder. - * @see SQL#column(String) * @see Table#columns(String...) */ SelectFrom select(Collection expressions); + /** + * Makes the select statement distinct + * + * @return {@code this} builder. + */ + 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. @@ -229,15 +218,9 @@ public interface SelectBuilder { @Override SelectFromAndOrderBy from(Collection tables); - @Override - SelectFromAndOrderBy orderBy(String field); - @Override SelectFromAndOrderBy orderBy(Column... columns); - @Override - SelectFromAndOrderBy orderBy(int... indexes); - @Override SelectFromAndOrderBy orderBy(OrderByField... orderByFields); @@ -379,39 +362,19 @@ public interface SelectBuilder { */ interface SelectOrdered extends BuildSelect { - /** - * Add an order by {@code field} using default sort semantics. - * - * @param field field name, must not be {@literal null} or empty. - * @return {@code this} builder. - * @see OrderByField#create(String) - */ - SelectOrdered orderBy(String field); - /** * Add one or more {@link Column columns} to order by. * * @param columns the columns to order by. * @return {@code this} builder. - * @see OrderByField#create(String) */ SelectOrdered orderBy(Column... columns); - /** - * Add an order by field using {@code indexes} using default sort semantics. - * - * @param indexes field indexes as declared in the select list. - * @return {@code this} builder. - * @see OrderByField#index(int) - */ - SelectOrdered orderBy(int... indexes); - /** * Add one or more {@link OrderByField order by fields}. * * @param orderByFields the fields to order by. * @return {@code this} builder. - * @see OrderByField#create(String) */ SelectOrdered orderBy(OrderByField... orderByFields); @@ -420,7 +383,6 @@ public interface SelectBuilder { * * @param orderByFields the fields to order by. * @return {@code this} builder. - * @see OrderByField#create(String) */ SelectOrdered orderBy(Collection orderByFields); } @@ -496,22 +458,12 @@ public interface SelectBuilder { */ interface SelectOn { - /** - * Declare the source column in the {@code JOIN}. - * - * @param column name of the source column, must not be {@literal null} or empty. - * @return {@code this} builder. - * @see SQL#column(String) - * @see Table#column(String) - */ - SelectOnConditionComparison on(String column); /** * Declare the source column in the {@code JOIN}. * * @param column the source column, must not be {@literal null} or empty. * @return {@code this} builder. - * @see SQL#column(String) * @see Table#column(String) */ SelectOnConditionComparison on(Expression column); @@ -522,22 +474,11 @@ public interface SelectBuilder { */ interface SelectOnConditionComparison { - /** - * Declare an equals {@link Condition} between the source column and the target {@code column}. - * - * @param column name of the target column, must not be {@literal null} or empty. - * @return {@code this} builder. - * @see SQL#column(String) - * @see Table#column(String) - */ - SelectFromAndJoinCondition equals(String column); - /** * Declare an equals {@link Condition} between the source column and the target {@link Column}. * * @param column the target column, must not be {@literal null}. * @return {@code this} builder. - * @see SQL#column(String) * @see Table#column(String) */ SelectFromAndJoinCondition equals(Expression column); @@ -548,22 +489,11 @@ public interface SelectBuilder { */ interface SelectOnCondition extends SelectJoin, BuildSelect { - /** - * Declare an additional source column in the {@code JOIN}. - * - * @param column the column name, must not be {@literal null} or empty. - * @return {@code this} builder. - * @see SQL#column(String) - * @see Table#column(String) - */ - SelectOnConditionComparison and(String column); - /** * Declare an additional source column in the {@code JOIN}. * * @param column the column, must not be {@literal null}. * @return {@code this} builder. - * @see SQL#column(String) * @see Table#column(String) */ SelectOnConditionComparison and(Expression column); diff --git a/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/SelectValidator.java b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/SelectValidator.java index d22adcf3..f760b019 100644 --- a/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/SelectValidator.java +++ b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/SelectValidator.java @@ -82,7 +82,7 @@ class SelectValidator implements Visitor { } if (segment instanceof Column - && (parent instanceof Select || parent instanceof SimpleFunction || parent instanceof Distinct)) { + && (parent instanceof Select || parent instanceof SimpleFunction)) { selectFieldCount++; Table table = ((Column) segment).getTable(); @@ -120,8 +120,7 @@ class SelectValidator implements Visitor { } if (segment instanceof Join || segment instanceof OrderByField || segment instanceof From - || segment instanceof Select || segment instanceof Where || segment instanceof SimpleFunction - || segment instanceof Distinct) { + || segment instanceof Select || segment instanceof Where || segment instanceof SimpleFunction) { parent = segment; } } @@ -131,6 +130,5 @@ class SelectValidator implements Visitor { * @see org.springframework.data.relational.core.sql.Visitor#leave(org.springframework.data.relational.core.sql.Visitable) */ @Override - public void leave(Visitable segment) { - } + public void leave(Visitable segment) {} } diff --git a/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/SimpleCondition.java b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/SimpleCondition.java index db0908cd..6a0d7853 100644 --- a/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/SimpleCondition.java +++ b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/SimpleCondition.java @@ -31,6 +31,9 @@ public class SimpleCondition extends AbstractSegment implements Condition { private final String predicate; SimpleCondition(Expression expression, String comparator, String predicate) { + + super(expression); + this.expression = expression; this.comparator = comparator; this.predicate = predicate; @@ -56,20 +59,6 @@ public class SimpleCondition extends AbstractSegment implements Condition { return predicate; } - /* - * (non-Javadoc) - * @see org.springframework.data.relational.core.sql.Visitable#visit(org.springframework.data.relational.core.sql.Visitor) - */ - @Override - public void visit(Visitor visitor) { - - Assert.notNull(visitor, "Visitor must not be null!"); - - visitor.enter(this); - expression.visit(visitor); - visitor.leave(this); - } - /* * (non-Javadoc) * @see java.lang.Object#toString() diff --git a/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/SimpleFunction.java b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/SimpleFunction.java index c43058fd..b7ac738c 100644 --- a/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/SimpleFunction.java +++ b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/SimpleFunction.java @@ -31,6 +31,9 @@ public class SimpleFunction extends AbstractSegment implements Expression { private List expressions; SimpleFunction(String functionName, List expressions) { + + super(expressions.toArray(new Expression[]{})); + this.functionName = functionName; this.expressions = expressions; } @@ -48,20 +51,6 @@ public class SimpleFunction extends AbstractSegment implements Expression { return new AliasedFunction(functionName, expressions, alias); } - /* - * (non-Javadoc) - * @see org.springframework.data.relational.core.sql.Visitable#visit(org.springframework.data.relational.core.sql.Visitor) - */ - @Override - public void visit(Visitor visitor) { - - Assert.notNull(visitor, "Visitor must not be null!"); - - visitor.enter(this); - expressions.forEach(it -> it.visit(visitor)); - visitor.leave(this); - } - /** * @return the function name. */ diff --git a/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/SubselectExpression.java b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/SubselectExpression.java new file mode 100644 index 00000000..7671ba89 --- /dev/null +++ b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/SubselectExpression.java @@ -0,0 +1,36 @@ +/* + * 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; + +/** + * @author Jens Schauder + */ +public class SubselectExpression extends AbstractSegment implements Expression { + + private final Select subselect; + + public SubselectExpression(Select subselect) { + + super(subselect); + + this.subselect = subselect; + } + + @Override + public String toString() { + return "(" + subselect.toString() + ")"; + } +} diff --git a/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/Table.java b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/Table.java index 0a1b100b..aaa7fc6c 100644 --- a/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/Table.java +++ b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/Table.java @@ -28,11 +28,12 @@ import org.springframework.util.Assert; * * @author Mark Paluch */ -public class Table extends AbstractSegment implements Segment, Named { +public class Table extends AbstractSegment { private final String name; Table(String name) { + super(); this.name = name; } diff --git a/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/Where.java b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/Where.java index f4ca7c37..42cc266f 100644 --- a/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/Where.java +++ b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/Where.java @@ -22,26 +22,17 @@ import org.springframework.util.Assert; * * @author Mark Paluch */ -public class Where extends AbstractSegment implements Segment { +public class Where extends AbstractSegment { private final Condition condition; Where(Condition condition) { + + super(condition); + this.condition = condition; } - @Override - public void visit(Visitor visitor) { - - Assert.notNull(visitor, "Visitor must not be null!"); - - visitor.enter(this); - - condition.visit(visitor); - - visitor.leave(this); - } - @Override public String toString() { return "WHERE " + condition.toString(); diff --git a/spring-data-relational/src/test/java/org/springframework/data/relational/core/sql/NaiveSqlRenderer.java b/spring-data-relational/src/test/java/org/springframework/data/relational/core/sql/NaiveSqlRenderer.java index c97d1e9c..200bb779 100644 --- a/spring-data-relational/src/test/java/org/springframework/data/relational/core/sql/NaiveSqlRenderer.java +++ b/spring-data-relational/src/test/java/org/springframework/data/relational/core/sql/NaiveSqlRenderer.java @@ -17,14 +17,15 @@ package org.springframework.data.relational.core.sql; import java.util.OptionalLong; import java.util.Stack; -import java.util.function.Consumer; import org.springframework.util.Assert; /** - * Naive SQL renderer that does not consider dialect specifics. This class is to evaluate requirements of a SQL renderer. + * 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 { @@ -64,286 +65,591 @@ public class NaiveSqlRenderer { */ public String render() { - RenderVisitor visitor = new RenderVisitor(); + StackBasedVisitor visitor = new StackBasedVisitor(); select.visit(visitor); - return visitor.builder.toString(); + return visitor.selectStatementVisitor.getValue(); } - /** - * {@link Visitor} to render the SQL. - */ - static class RenderVisitor implements Visitor { + interface ValuedVisitor extends Visitor { + String getValue(); + } - StringBuilder builder = new StringBuilder(); + static class StackBasedVisitor implements Visitor { - private boolean hasDistinct = false; - private boolean hasOrderBy = false; - private boolean nextRequiresComma = false; - private boolean nextExpressionRequiresContinue = false; - private boolean nextConditionRequiresContinue = false; - private boolean inSelectList = false; - private Stack segments = new Stack<>(); + private Stack visitors = new Stack<>(); + + private SelectStatementVisitor selectStatementVisitor = new SelectStatementVisitor(); + + { + visitors.push(segment -> {}); + visitors.push(selectStatementVisitor); + } - /* - * (non-Javadoc) - * @see org.springframework.data.relational.core.sql.Visitor#enter(org.springframework.data.relational.core.sql.Visitable) - */ @Override public void enter(Visitable segment) { - if (segment instanceof Select) { - builder.append("SELECT "); - inSelectList = true; - } - - if (segment instanceof From) { - - nextRequiresComma = false; - - addSpaceIfNecessary(); - - builder.append("FROM "); - } - - if (segment instanceof From || segment instanceof Join) { - inSelectList = false; - } - - if (segment instanceof Distinct && !hasDistinct) { - builder.append("DISTINCT "); - hasDistinct = true; - } - - if (segment instanceof OrderByField && !hasOrderBy) { - - addSpaceIfNecessary(); - - builder.append("ORDER BY "); - nextRequiresComma = false; - hasOrderBy = true; - } - - if (segment instanceof SimpleFunction) { - - nextRequiresComma = false; - builder.append(((SimpleFunction) segment).getFunctionName()).append("("); - } - - if (segment instanceof Table && segments.peek() instanceof From) { - - addCommaIfNecessary(); - - Table table = (Table) segment; - - builder.append(table.getName()); - - ifAliased(segment, aliased -> builder.append(" AS ").append(aliased.getAlias())); - - nextRequiresComma = true; - } - - if (segment instanceof Join) { - - addSpaceIfNecessary(); - - Join join = (Join) segment; - - builder.append(join.getType()); - } - - if (segment instanceof Table && segments.peek() instanceof Join) { - - addSpaceIfNecessary(); - - Table table = (Table) segment; - - builder.append(table.getName()); - ifAliased(segment, aliased -> builder.append(" AS ").append(aliased.getAlias())); - } - - if (segment instanceof Condition) { - nextRequiresComma = false; - } - - if (segment instanceof Condition && segments.peek() instanceof Join) { - - addSpaceIfNecessary(); - - builder.append("ON "); - } - - if ((segment instanceof Expression || segment instanceof Condition) && segments.peek() instanceof Condition) { - - if (segment instanceof Expression) { - - if (!nextExpressionRequiresContinue) { - nextExpressionRequiresContinue = true; - } else { - - renderCombinator((Condition) segments.peek()); - nextExpressionRequiresContinue = false; - } - } - } - - if (segment instanceof ConditionGroup) { - addSpaceIfNecessary(); - builder.append("("); - } else if (segment instanceof Condition && segments.peek() instanceof Condition) { - - if (!nextConditionRequiresContinue) { - nextConditionRequiresContinue = true; - } else { - - renderCombinator((Condition) segments.peek()); - nextConditionRequiresContinue = false; - } - } - - segments.add(segment); + Visitor delegate = visitors.peek(); + delegate.enter(segment); } - /* - * (non-Javadoc) - * @see org.springframework.data.relational.core.sql.Visitor#leave(org.springframework.data.relational.core.sql.Visitable) - */ @Override public void leave(Visitable segment) { - segments.pop(); - Visitable parent = segments.isEmpty() ? null : segments.peek(); + Visitor delegate = visitors.peek(); + delegate.leave(segment); + } - if (segment instanceof Condition) { - nextExpressionRequiresContinue = false; - } + /** + * 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 { - if (segment instanceof Select) { + private Visitable currentSegment = null; + private Visitor nextVisitor; - // Postgres syntax - Select select = (Select) segment; + abstract boolean matches(Visitable segment); - OptionalLong limit = select.getLimit(); - OptionalLong offset = select.getOffset(); + void enterMatched(Visitable segment) {} - limit.ifPresent(count -> { - addSpaceIfNecessary(); - builder.append("LIMIT ").append(count); - }); + void enterSub(Visitable segment) {} - offset.ifPresent(count -> { - addSpaceIfNecessary(); - builder.append("OFFSET ").append(count); - }); - } + void leaveMatched(Visitable segment) {} - if (segment instanceof Table && parent instanceof Column) { + void leaveSub(Visitable segment) {} - if (inSelectList || !(parent instanceof Aliased)) { + @Override + public void enter(Visitable segment) { - if (nextRequiresComma) { - builder.append(", "); - nextRequiresComma = false; + 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 Column - && (parent instanceof Select || parent instanceof Distinct || parent instanceof SimpleFunction)) { - - addCommaIfNecessary(); - - Column column = (Column) segment; - - builder.append(column.getName()); - - if (!(parent instanceof SimpleFunction)) { - ifAliased(segment, aliased -> builder.append(" AS ").append(aliased.getAlias())); + 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; + } } - - nextRequiresComma = true; } - if (segment instanceof Column && (parent instanceof Condition || parent instanceof OrderByField)) { + @Override + public String getValue() { + return builder.toString(); + } + } - addCommaIfNecessary(); + private class FromClauseVisitor extends ReadOneVisitor implements ValuedVisitor { - Column column = (Column) segment; + private FromTableVisitor fromTableVisitor = new FromTableVisitor(); - builder.append(column.getReferenceName()); + @Override + boolean matches(Visitable segment) { + return segment instanceof From; } - if (segment instanceof OrderByField) { + @Override + void enterMatched(Visitable segment) { + visitors.push(fromTableVisitor); + } - OrderByField orderBy = (OrderByField) segment; + @Override + public String getValue() { + return " FROM " + fromTableVisitor.getValue(); + } + } - if (orderBy.getDirection() != null) { - builder.append(' ').append(orderBy.getDirection()); + 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()); } - nextRequiresComma = true; } - if (segment instanceof SimpleFunction) { - - builder.append(")"); - ifAliased(segment, aliased -> builder.append(" AS ").append(aliased.getAlias())); - - nextRequiresComma = true; - } - - if (segment instanceof ConditionGroup) { - builder.append(")"); - } - - if (segment instanceof SimpleCondition) { - - nextRequiresComma = false; - - SimpleCondition condition = (SimpleCondition) segment; - - builder.append(' ').append(condition.getPredicate()).append(' ').append(condition.getPredicate()); + @Override + public String getValue() { + return builder.toString(); } } - private void addCommaIfNecessary() { - if (nextRequiresComma) { - builder.append(", "); + 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 void addSpaceIfNecessary() { + private class WhereClauseVisitor extends ReadOneVisitor implements ValuedVisitor { - if (requiresSpace()) { - builder.append(' '); + 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 void renderCombinator(Condition condition) { + private class ConditionVisitor extends ReadOneVisitor implements ValuedVisitor { - if (condition instanceof Equals) { - builder.append(" = "); + private StringBuilder builder = new StringBuilder(); + + ValuedVisitor left; + ValuedVisitor right; + + @Override + boolean matches(Visitable segment) { + return segment instanceof Condition; } - if (condition instanceof AndCondition) { - builder.append(" AND "); + @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); + } } - if (condition instanceof OrCondition) { - builder.append(" OR "); + @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 boolean requiresSpace() { - return builder.length() != 0 && builder.charAt(builder.length() - 1) != ' '; - } + private class ExpressionVisitor extends ReadOneVisitor implements ValuedVisitor { - private void ifAliased(Object segment, Consumer aliasedConsumer) { + private String value = ""; + private SelectStatementVisitor valuedVisitor; - if (segment instanceof Aliased) { - aliasedConsumer.accept((Aliased) segment); + @Override + boolean matches(Visitable segment) { + return segment instanceof Expression; + } + + @Override + void enterMatched(Visitable segment) { + + if (segment instanceof SubselectExpression) { + + valuedVisitor = new SelectStatementVisitor(); + visitors.push(valuedVisitor); + } else if (segment instanceof Column) { + value = ((Column) segment).getTable().getName() + "." + ((Column) segment).getName(); + } else if (segment instanceof BindMarker) { + + if (segment instanceof BindMarker.NamedBindMarker) { + value = ":" + ((BindMarker.NamedBindMarker) segment).getName(); + } else { + value = segment.toString(); + } + } + } + + @Override + void leaveMatched(Visitable segment) { + + if (valuedVisitor != null) { + value = valuedVisitor.getValue(); + } + } + + @Override + public String getValue() { + return value; } } + + private class OrderByClauseVisitor extends ReadWhileMatchesVisitor implements ValuedVisitor { + + StringBuilder builder = new StringBuilder(); + boolean first = true; + + @Override + boolean matches(Visitable segment) { + return segment instanceof OrderByField; + } + + @Override + void enterMatched(Visitable segment) { + + if (!first) { + builder.append(", "); + } else { + builder.append(" ORDER BY "); + } + first = false; + } + + @Override + void leaveMatched(Visitable segment) { + + OrderByField field = (OrderByField) segment; + + if (field.getDirection() != null) { + builder.append(" ") // + .append(field.getDirection()); + } + } + + @Override + void leaveSub(Visitable segment) { + + if (segment instanceof Column) { + builder.append(((Column) segment).getReferenceName()); + } + } + + @Override + public String getValue() { + return builder.toString(); + } + } + } + } diff --git a/spring-data-relational/src/test/java/org/springframework/data/relational/core/sql/NaiveSqlRendererUnitTests.java b/spring-data-relational/src/test/java/org/springframework/data/relational/core/sql/NaiveSqlRendererUnitTests.java index e31e6efa..7cd83be4 100644 --- a/spring-data-relational/src/test/java/org/springframework/data/relational/core/sql/NaiveSqlRendererUnitTests.java +++ b/spring-data-relational/src/test/java/org/springframework/data/relational/core/sql/NaiveSqlRendererUnitTests.java @@ -17,6 +17,7 @@ package org.springframework.data.relational.core.sql; import static org.assertj.core.api.Assertions.*; +import org.junit.Ignore; import org.junit.Test; /** @@ -29,9 +30,12 @@ public class NaiveSqlRendererUnitTests { @Test // DATAJDBC-309 public void shouldRenderSingleColumn() { - Select select = Select.builder().select("foo").from("bar").build(); + Table bar = SQL.table("bar"); + Column foo = bar.column("foo"); - assertThat(NaiveSqlRenderer.render(select)).isEqualTo("SELECT foo FROM bar"); + Select select = Select.builder().select(foo).from(bar).build(); + + assertThat(NaiveSqlRenderer.render(select)).isEqualTo("SELECT bar.foo FROM bar"); } @Test // DATAJDBC-309 @@ -50,7 +54,8 @@ public class NaiveSqlRendererUnitTests { Table table1 = Table.create("table1"); Table table2 = Table.create("table2"); - Select select = Select.builder().select(table1.column("col1")).select(table2.column("col2")).from(table1).from(table2).build(); + 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"); } @@ -58,17 +63,25 @@ public class NaiveSqlRendererUnitTests { @Test // DATAJDBC-309 public void shouldRenderDistinct() { - Select select = Select.builder().select(Functions.distinct("foo", "bar")).from("bar").build(); + Table table = SQL.table("bar"); + Column foo = table.column("foo"); + Column bar = table.column("bar"); - assertThat(NaiveSqlRenderer.render(select)).isEqualTo("SELECT DISTINCT foo, bar FROM bar"); + Select select = Select.builder().distinct().select(foo, bar).from(table).build(); + + assertThat(NaiveSqlRenderer.render(select)).isEqualTo("SELECT DISTINCT bar.foo, bar.bar FROM bar"); } @Test // DATAJDBC-309 public void shouldRenderCountFunction() { - Select select = Select.builder().select(Functions.count("foo"), Column.create("bar")).from("bar").build(); + Table table = SQL.table("bar"); + Column foo = table.column("foo"); + Column bar = table.column("bar"); - assertThat(NaiveSqlRenderer.render(select)).isEqualTo("SELECT COUNT(foo), bar FROM bar"); + 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"); } @Test // DATAJDBC-309 @@ -81,8 +94,8 @@ 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 " + - "JOIN department ON employee.department_id = department.id"); + assertThat(NaiveSqlRenderer.render(select)).isEqualTo("SELECT employee.id, department.name FROM employee " + + "JOIN department ON employee.department_id = department.id"); } @Test // DATAJDBC-309 @@ -96,9 +109,8 @@ public class NaiveSqlRendererUnitTests { .and(employee.column("tenant")).equals(department.column("tenant")) // .build(); - assertThat(NaiveSqlRenderer.render(select)).isEqualTo("SELECT employee.id, department.name FROM employee " + - "JOIN department ON employee.department_id = department.id " + - "AND employee.tenant = department.tenant"); + assertThat(NaiveSqlRenderer.render(select)).isEqualTo("SELECT employee.id, department.name FROM employee " + + "JOIN department ON employee.department_id = department.id " + "AND employee.tenant = department.tenant"); } @Test // DATAJDBC-309 @@ -114,18 +126,9 @@ 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 " + - "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"); - } - - @Test // DATAJDBC-309 - public void shouldRenderOrderByIndex() { - - Select select = Select.builder().select(Functions.count("foo"), Column.create("bar")).from("bar").orderBy(1, 2).build(); - - assertThat(NaiveSqlRenderer.render(select)).isEqualTo("SELECT COUNT(foo), bar FROM bar ORDER BY 1, 2"); + assertThat(NaiveSqlRenderer.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"); } @Test // DATAJDBC-309 @@ -136,14 +139,96 @@ public class NaiveSqlRendererUnitTests { Select select = Select.builder().select(column).from(employee).orderBy(OrderByField.from(column).asc()).build(); - assertThat(NaiveSqlRenderer.render(select)).isEqualTo("SELECT emp.name AS emp_name FROM employee AS emp ORDER BY emp_name ASC"); + assertThat(NaiveSqlRenderer.render(select)) + .isEqualTo("SELECT emp.name AS emp_name FROM employee AS emp ORDER BY emp_name ASC"); } @Test // DATAJDBC-309 public void shouldRenderOrderLimitOffset() { - Select select = Select.builder().select(Column.create("bar")).from("foo").limitOffset(10, 20).build(); + Table table = SQL.table("foo"); + Column bar = table.column("bar"); - assertThat(NaiveSqlRenderer.render(select)).isEqualTo("SELECT bar FROM foo LIMIT 10 OFFSET 20"); + 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"); + } + + @Test // DATAJDBC-309 + public void shouldRenderIsNull() { + + Table table = SQL.table("foo"); + Column bar = table.column("bar"); + + 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"); + } + + @Test // DATAJDBC-309 + public void shouldRenderNotNull() { + + Table table = SQL.table("foo"); + Column bar = table.column("bar"); + + 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"); + } + + @Test // DATAJDBC-309 + public void shouldRenderEqualityCondition() { + + 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(); + + assertThat(NaiveSqlRenderer.render(select)).isEqualTo("SELECT foo.bar FROM foo WHERE foo.bar = :name"); + } + + @Test // DATAJDBC-309 + public void shouldRendersAndOrConditionWithProperParentheses() { + + Table table = SQL.table("foo"); + 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(); + + assertThat(NaiveSqlRenderer.render(select)).isEqualTo("SELECT foo.bar FROM foo WHERE (foo.bar = :name OR foo.bar = :name2) AND foo.baz IS NULL"); + } + + @Test // DATAJDBC-309 + public void shouldInWithNamedParameter() { + + 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(); + + assertThat(NaiveSqlRenderer.render(select)).isEqualTo("SELECT foo.bar FROM foo WHERE foo.bar IN (:name)"); + } + + @Test // DATAJDBC-309 + public void shouldRenderInSubselect() { + + Table foo = SQL.table("foo"); + Column bar = foo.column("bar"); + + Table floo = SQL.table("floo"); + Column bah = floo.column("bah"); + + Select subselect = Select.builder().select(bah).from(floo).build(); + + Select select = Select.builder().select(bar).from(foo).where(Conditions.in(bar, new SubselectExpression(subselect))).build(); + + assertThat(NaiveSqlRenderer.render(select)).isEqualTo("SELECT foo.bar FROM foo WHERE foo.bar IN (SELECT floo.bah FROM floo)"); } } diff --git a/spring-data-relational/src/test/java/org/springframework/data/relational/core/sql/SelectBuilderUnitTests.java b/spring-data-relational/src/test/java/org/springframework/data/relational/core/sql/SelectBuilderUnitTests.java index d01e141e..1a34dc88 100644 --- a/spring-data-relational/src/test/java/org/springframework/data/relational/core/sql/SelectBuilderUnitTests.java +++ b/spring-data-relational/src/test/java/org/springframework/data/relational/core/sql/SelectBuilderUnitTests.java @@ -19,6 +19,7 @@ import static org.assertj.core.api.Assertions.*; import java.util.ArrayList; import java.util.List; +import java.util.OptionalLong; import org.junit.Test; import org.springframework.data.relational.core.sql.Join.JoinType; @@ -35,17 +36,17 @@ public class SelectBuilderUnitTests { SelectBuilder builder = SQL.select(); - Column foo = SQL.column("foo"); - Column bar = SQL.column("bar"); Table table = SQL.table("mytable"); + Column foo = table.column("foo"); + Column bar = table.column("bar"); Select select = builder.select(foo, bar).from(table).build(); CapturingSelectVisitor visitor = new CapturingSelectVisitor(); select.visit(visitor); - assertThat(visitor.enter).containsSequence(foo, bar, new From(table), table); - assertThat(visitor.leave).containsSequence(foo, bar, table, new From(table)); + 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 @@ -53,15 +54,16 @@ public class SelectBuilderUnitTests { SelectBuilder builder = SQL.select(); - Column foo = SQL.column("foo"); Table table = SQL.table("mytable"); + Column foo = table.column("foo"); Select select = builder.top(10).select(foo).from(table).build(); CapturingSelectVisitor visitor = new CapturingSelectVisitor(); select.visit(visitor); - assertThat(visitor.enter).containsSequence(SelectTop.create(10), foo, new From(table), table); + assertThat(visitor.enter).containsSequence(foo, table, new From(table), table); + assertThat(select.getLimit()).isEqualTo(OptionalLong.of(10)); } @Test // DATAJDBC-309 @@ -112,12 +114,9 @@ public class SelectBuilderUnitTests { Column name = employee.column("name").as("emp_name"); Column department_name = employee.column("name").as("department_name"); - Select select = builder.select(name, department_name).from(employee) - .join(department) - .on(SQL.column("department_id", employee)) - .equals(SQL.column("id", department)) - .and(SQL.column("tenant", employee)) - .equals(SQL.column("tenant", department)) + Select select = builder.select(name, department_name).from(employee).join(department) + .on(SQL.column("department_id", employee)).equals(SQL.column("id", department)) + .and(SQL.column("tenant", employee)).equals(SQL.column("tenant", department)) .orderBy(OrderByField.from(name).asc()).build(); CapturingSelectVisitor visitor = new CapturingSelectVisitor(); @@ -128,11 +127,11 @@ public class SelectBuilderUnitTests { Join join = visitor.enter.stream().filter(Join.class::isInstance).map(Join.class::cast).findFirst().get(); assertThat(join.getJoinTable()).isEqualTo(department); - assertThat(join.getOn().toString()).isEqualTo(new SimpleSegment("employee.department_id = department.id AND employee.tenant = department.tenant").toString()); + assertThat(join.getOn().toString()).isEqualTo( + new SimpleSegment("employee.department_id = department.id AND employee.tenant = department.tenant").toString()); assertThat(join.getType()).isEqualTo(JoinType.JOIN); } - static class CapturingSelectVisitor implements Visitor { final List enter = new ArrayList<>(); diff --git a/spring-data-relational/src/test/java/org/springframework/data/relational/core/sql/SelectValidatorUnitTests.java b/spring-data-relational/src/test/java/org/springframework/data/relational/core/sql/SelectValidatorUnitTests.java index 182fa998..49029b51 100644 --- a/spring-data-relational/src/test/java/org/springframework/data/relational/core/sql/SelectValidatorUnitTests.java +++ b/spring-data-relational/src/test/java/org/springframework/data/relational/core/sql/SelectValidatorUnitTests.java @@ -33,7 +33,8 @@ public class SelectValidatorUnitTests { assertThatThrownBy(() -> { SQL.newSelect(column).from(SQL.table("bar")).build(); - }).isInstanceOf(IllegalStateException.class).hasMessageContaining("Required table [table] by a SELECT column not imported by FROM [bar] or JOIN []"); + }).isInstanceOf(IllegalStateException.class) + .hasMessageContaining("Required table [table] by a SELECT column not imported by FROM [bar] or JOIN []"); } @Test // DATAJDBC-309 @@ -43,7 +44,8 @@ public class SelectValidatorUnitTests { assertThatThrownBy(() -> { SQL.newSelect(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 []"); + }).isInstanceOf(IllegalStateException.class) + .hasMessageContaining("Required table [table] by a SELECT column not imported by FROM [bar] or JOIN []"); } @Test // DATAJDBC-309 @@ -52,31 +54,38 @@ public class SelectValidatorUnitTests { Column column = SQL.table("table").column("foo"); assertThatThrownBy(() -> { - SQL.newSelect(Functions.distinct(column)).from(SQL.table("bar")).build(); - }).isInstanceOf(IllegalStateException.class).hasMessageContaining("Required table [table] by a SELECT column not imported by FROM [bar] or JOIN []"); + SQL.newSelect(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 []"); } @Test // DATAJDBC-309 public void shouldReportMissingTableViaOrderBy() { - Column column = SQL.table("table").column("foo"); + Column foo = SQL.table("table").column("foo"); + Table bar = SQL.table("bar"); assertThatThrownBy(() -> { - SQL.newSelect(SQL.column("foo")) // - .from(SQL.table("bar")).orderBy(column) // + SQL.newSelect(bar.column("foo")) // + .from(bar) // + .orderBy(foo) // .build(); - }).isInstanceOf(IllegalStateException.class).hasMessageContaining("Required table [table] by a ORDER BY column not imported by FROM [bar] or JOIN []"); + }).isInstanceOf(IllegalStateException.class) + .hasMessageContaining("Required table [table] by a ORDER BY column not imported by FROM [bar] or JOIN []"); } @Test // DATAJDBC-309 public void shouldReportMissingTableViaWhere() { Column column = SQL.table("table").column("foo"); + Table bar = SQL.table("bar"); assertThatThrownBy(() -> { - SQL.newSelect(SQL.column("foo")).from(SQL.table("bar")) // + SQL.newSelect(bar.column("foo")) // + .from(bar) // .where(new SimpleCondition(column, "=", "foo")) // .build(); - }).isInstanceOf(IllegalStateException.class).hasMessageContaining("Required table [table] by a WHERE predicate not imported by FROM [bar] or JOIN []"); + }).isInstanceOf(IllegalStateException.class) + .hasMessageContaining("Required table [table] by a WHERE predicate not imported by FROM [bar] or JOIN []"); } }