diff --git a/pom.xml b/pom.xml
index eeaa0b9e93..557756261e 100644
--- a/pom.xml
+++ b/pom.xml
@@ -5,7 +5,7 @@
org.springframework.data
spring-data-relational-parent
- 2.3.0-SNAPSHOT
+ 2.3.0-1003-join-subselect-SNAPSHOT
pom
Spring Data Relational Parent
diff --git a/spring-data-jdbc-distribution/pom.xml b/spring-data-jdbc-distribution/pom.xml
index 03d6a5c2a0..36970858d9 100644
--- a/spring-data-jdbc-distribution/pom.xml
+++ b/spring-data-jdbc-distribution/pom.xml
@@ -14,7 +14,7 @@
org.springframework.data
spring-data-relational-parent
- 2.3.0-SNAPSHOT
+ 2.3.0-1003-join-subselect-SNAPSHOT
../pom.xml
diff --git a/spring-data-jdbc/pom.xml b/spring-data-jdbc/pom.xml
index af9ad0904e..40b19527d3 100644
--- a/spring-data-jdbc/pom.xml
+++ b/spring-data-jdbc/pom.xml
@@ -6,7 +6,7 @@
4.0.0
spring-data-jdbc
- 2.3.0-SNAPSHOT
+ 2.3.0-1003-join-subselect-SNAPSHOT
Spring Data JDBC
Spring Data module for JDBC repositories.
@@ -15,7 +15,7 @@
org.springframework.data
spring-data-relational-parent
- 2.3.0-SNAPSHOT
+ 2.3.0-1003-join-subselect-SNAPSHOT
diff --git a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/query/QueryMapper.java b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/query/QueryMapper.java
index 6f75dbb081..a9cd3f35e8 100644
--- a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/query/QueryMapper.java
+++ b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/query/QueryMapper.java
@@ -121,7 +121,7 @@ Expression getMappedObject(Expression expression, @Nullable RelationalPersistent
Column column = (Column) expression;
Field field = createPropertyField(entity, column.getName());
- Table table = column.getTable();
+ TableLike table = column.getTable();
Assert.state(table != null, String.format("The column %s must have a table set.", column));
diff --git a/spring-data-relational/pom.xml b/spring-data-relational/pom.xml
index 4e42a006ec..af62c76fcb 100644
--- a/spring-data-relational/pom.xml
+++ b/spring-data-relational/pom.xml
@@ -6,7 +6,7 @@
4.0.0
spring-data-relational
- 2.3.0-SNAPSHOT
+ 2.3.0-1003-join-subselect-SNAPSHOT
Spring Data Relational
Spring Data Relational support
@@ -14,7 +14,7 @@
org.springframework.data
spring-data-relational-parent
- 2.3.0-SNAPSHOT
+ 2.3.0-1003-join-subselect-SNAPSHOT
diff --git a/spring-data-relational/src/main/java/org/springframework/data/relational/core/dialect/PostgresDialect.java b/spring-data-relational/src/main/java/org/springframework/data/relational/core/dialect/PostgresDialect.java
index a8d95c517c..9495c2cd30 100644
--- a/spring-data-relational/src/main/java/org/springframework/data/relational/core/dialect/PostgresDialect.java
+++ b/spring-data-relational/src/main/java/org/springframework/data/relational/core/dialect/PostgresDialect.java
@@ -23,11 +23,11 @@
import java.util.function.Consumer;
import org.springframework.data.relational.core.sql.IdentifierProcessing;
-import org.springframework.data.relational.core.sql.LockOptions;
-import org.springframework.data.relational.core.sql.SqlIdentifier;
-import org.springframework.data.relational.core.sql.Table;
import org.springframework.data.relational.core.sql.IdentifierProcessing.LetterCasing;
import org.springframework.data.relational.core.sql.IdentifierProcessing.Quoting;
+import org.springframework.data.relational.core.sql.LockOptions;
+import org.springframework.data.relational.core.sql.SqlIdentifier;
+import org.springframework.data.relational.core.sql.TableLike;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
@@ -139,7 +139,7 @@ static class PostgresLockClause implements LockClause {
@Override
public String getLock(LockOptions lockOptions) {
- List tables = lockOptions.getFrom().getTables();
+ List tables = lockOptions.getFrom().getTables();
if (tables.isEmpty()) {
return "";
}
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 62f331137b..b2af696f39 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
@@ -18,13 +18,7 @@
/**
* {@link Segment} to select all columns from a {@link Table}.
*
- * * Renders to: {@code
- *
-
- * .*} as in {@code SELECT
- *
-
- * .* FROM …}.
+ * Renders to: {@code .*} as in {@code SELECT .* FROM …}.
*
* @author Mark Paluch
* @since 1.1
@@ -32,9 +26,9 @@
*/
public class AsteriskFromTable extends AbstractSegment implements Expression {
- private final Table table;
+ private final TableLike table;
- AsteriskFromTable(Table table) {
+ AsteriskFromTable(TableLike table) {
super(table);
this.table = table;
}
@@ -46,7 +40,7 @@ public static AsteriskFromTable create(Table table) {
/**
* @return the associated {@link Table}.
*/
- public Table getTable() {
+ public TableLike getTable() {
return table;
}
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 f377f5af9e..29ce325d16 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
@@ -30,9 +30,9 @@
public class Column extends AbstractSegment implements Expression, Named {
private final SqlIdentifier name;
- private final Table table;
+ private final TableLike table;
- Column(String name, Table table) {
+ Column(String name, TableLike table) {
super(table);
Assert.notNull(name, "Name must not be null");
@@ -41,7 +41,7 @@ public class Column extends AbstractSegment implements Expression, Named {
this.table = table;
}
- Column(SqlIdentifier name, Table table) {
+ Column(SqlIdentifier name, TableLike table) {
super(table);
Assert.notNull(name, "Name must not be null");
@@ -57,7 +57,7 @@ public class Column extends AbstractSegment implements Expression, Named {
* @param table the table, must not be {@literal null}.
* @return the new {@link Column}.
*/
- public static Column create(String name, Table table) {
+ public static Column create(String name, TableLike table) {
Assert.hasText(name, "Name must not be null or empty");
Assert.notNull(table, "Table must not be null");
@@ -341,7 +341,7 @@ public SqlIdentifier getReferenceName() {
* {@link Table}.
*/
@Nullable
- public Table getTable() {
+ public TableLike getTable() {
return table;
}
@@ -370,12 +370,12 @@ static class AliasedColumn extends Column implements Aliased {
private final SqlIdentifier alias;
- private AliasedColumn(String name, Table table, String alias) {
+ private AliasedColumn(String name, TableLike table, String alias) {
super(name, table);
this.alias = SqlIdentifier.unquoted(alias);
}
- private AliasedColumn(SqlIdentifier name, Table table, SqlIdentifier alias) {
+ private AliasedColumn(SqlIdentifier name, TableLike table, SqlIdentifier alias) {
super(name, table);
this.alias = alias;
}
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 80a4e40c52..06f449d78e 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
@@ -42,7 +42,7 @@ class DefaultSelect implements Select {
private final List orderBy;
private final @Nullable LockMode lockMode;
- DefaultSelect(boolean distinct, 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, @Nullable LockMode lockMode) {
this.distinct = distinct;
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 61de3bc716..1e071744a1 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
@@ -38,7 +38,7 @@ class DefaultSelectBuilder implements SelectBuilder, SelectAndFrom, SelectFromAn
private boolean distinct = false;
private List selectList = new ArrayList<>();
- private List from = new ArrayList<>();
+ private List from = new ArrayList<>();
private long limit = -1;
private long offset = -1;
private List joins = new ArrayList<>();
@@ -107,7 +107,7 @@ public SelectFromAndJoin from(String table) {
* @see org.springframework.data.relational.core.sql.SelectBuilder.SelectAndFrom#from(org.springframework.data.relational.core.sql.Table)
*/
@Override
- public SelectFromAndJoin from(Table table) {
+ public SelectFromAndJoin from(TableLike table) {
from.add(table);
return this;
}
@@ -117,7 +117,7 @@ public SelectFromAndJoin from(Table table) {
* @see org.springframework.data.relational.core.sql.SelectBuilder.SelectAndFrom#from(org.springframework.data.relational.core.sql.Table[])
*/
@Override
- public SelectFromAndJoin from(Table... tables) {
+ public SelectFromAndJoin from(TableLike... tables) {
from.addAll(Arrays.asList(tables));
return this;
}
@@ -127,7 +127,7 @@ public SelectFromAndJoin from(Table... tables) {
* @see org.springframework.data.relational.core.sql.SelectBuilder.SelectAndFrom#from(java.util.Collection)
*/
@Override
- public SelectFromAndJoin from(Collection extends Table> tables) {
+ public SelectFromAndJoin from(Collection extends TableLike> tables) {
from.addAll(tables);
return this;
}
@@ -248,7 +248,7 @@ public SelectOn join(String table) {
* @see org.springframework.data.relational.core.sql.SelectBuilder.SelectJoin#join(org.springframework.data.relational.core.sql.Table)
*/
@Override
- public SelectOn join(Table table) {
+ public SelectOn join(TableLike table) {
return new JoinBuilder(table, this);
}
@@ -257,7 +257,7 @@ public SelectOn join(Table table) {
* @see org.springframework.data.relational.core.sql.SelectBuilder.SelectJoin#join(org.springframework.data.relational.core.sql.Table)
*/
@Override
- public SelectOn leftOuterJoin(Table table) {
+ public SelectOn leftOuterJoin(TableLike table) {
return new JoinBuilder(table, this, JoinType.LEFT_OUTER_JOIN);
}
@@ -295,21 +295,21 @@ public Select build() {
*/
static class JoinBuilder implements SelectOn, SelectOnConditionComparison, SelectFromAndJoinCondition {
- private final Table table;
+ private final TableLike table;
private final DefaultSelectBuilder selectBuilder;
private final JoinType joinType;
private @Nullable Expression from;
private @Nullable Expression to;
private @Nullable Condition condition;
- JoinBuilder(Table table, DefaultSelectBuilder selectBuilder, JoinType joinType) {
+ JoinBuilder(TableLike table, DefaultSelectBuilder selectBuilder, JoinType joinType) {
this.table = table;
this.selectBuilder = selectBuilder;
this.joinType = joinType;
}
- JoinBuilder(Table table, DefaultSelectBuilder selectBuilder) {
+ JoinBuilder(TableLike table, DefaultSelectBuilder selectBuilder) {
this(table, selectBuilder, JoinType.JOIN);
}
@@ -417,7 +417,7 @@ public SelectOn join(String table) {
* @see org.springframework.data.relational.core.sql.SelectBuilder.SelectJoin#join(org.springframework.data.relational.core.sql.Table)
*/
@Override
- public SelectOn join(Table table) {
+ public SelectOn join(TableLike table) {
selectBuilder.join(finishJoin());
return selectBuilder.join(table);
}
@@ -427,7 +427,7 @@ public SelectOn join(Table table) {
* @see org.springframework.data.relational.core.sql.SelectBuilder.SelectJoin#leftOuterJoin(org.springframework.data.relational.core.sql.Table)
*/
@Override
- public SelectOn leftOuterJoin(Table table) {
+ public SelectOn leftOuterJoin(TableLike table) {
selectBuilder.join(finishJoin());
return selectBuilder.leftOuterJoin(table);
}
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 6f87aab370..e41d6955d2 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
@@ -29,20 +29,20 @@
*/
public class From extends AbstractSegment {
- private final List tables;
+ private final List tables;
- From(Table... tables) {
+ From(TableLike... tables) {
this(Arrays.asList(tables));
}
- From(List tables) {
+ From(List tables) {
- super(tables.toArray(new Table[] {}));
+ super(tables.toArray(new TableLike[] {}));
this.tables = Collections.unmodifiableList(tables);
}
- public List getTables() {
+ public List getTables() {
return this.tables;
}
diff --git a/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/InlineQuery.java b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/InlineQuery.java
new file mode 100644
index 0000000000..88c083afd5
--- /dev/null
+++ b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/InlineQuery.java
@@ -0,0 +1,99 @@
+/*
+ * Copyright 2019-2021 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
+ *
+ * https://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;
+
+/**
+ * Represents a inline query within a SQL statement. Typically used in {@code FROM} or {@code JOIN} clauses.
+ *
+ * Renders to: {@code () AS } in a from or join clause, and to {@code } when used in an
+ * expression.
+ *
+ * Note that this does not implement {@link Aliased} because the Alias is not optional but required and therefore more
+ * like a name although the SQL term is "alias".
+ *
+ * @author Jens Schauder
+ * @since 2.3
+ */
+public class InlineQuery extends AbstractSegment implements TableLike {
+
+ private final Select select;
+ private final SqlIdentifier alias;
+
+ InlineQuery(Select select, SqlIdentifier alias) {
+
+ super(select);
+
+ this.select = select;
+ this.alias = alias;
+ }
+
+ /**
+ * Creates a new {@link InlineQuery} using an {@code alias}.
+ *
+ * @param select must not be {@literal null}.
+ * @param alias must not be {@literal null} or empty.
+ * @return the new {@link InlineQuery} using the {@code alias}.
+ */
+ public static InlineQuery create(Select select, SqlIdentifier alias) {
+
+ Assert.notNull(select, "Select must not be null!");
+ Assert.notNull(alias, "Alias must not be null or empty!");
+
+ return new InlineQuery(select, alias);
+ }
+
+ /**
+ * Creates a new {@link InlineQuery} using an {@code alias}.
+ *
+ * @param select must not be {@literal null} or empty.
+ * @param alias must not be {@literal null} or empty.
+ * @return the new {@link InlineQuery} using the {@code alias}.
+ */
+ public static InlineQuery create(Select select, String alias) {
+ return create(select, SqlIdentifier.unquoted(alias));
+ }
+
+ /**
+ * @return the table name.
+ */
+ /*
+ * (non-Javadoc)
+ * @see org.springframework.data.relational.core.sql.Named#getName()
+ */
+ public SqlIdentifier getName() {
+ return alias;
+ }
+
+ /**
+ * @return the table name as it is used in references. This can be the actual {@link #getName() name} or an
+ * {@link Aliased#getAlias() alias}.
+ */
+ public SqlIdentifier getReferenceName() {
+ return alias;
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see java.lang.Object#toString()
+ */
+ @Override
+ public String toString() {
+ return "(" + select + ") AS " + alias;
+ }
+
+}
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 8b8289b1c0..8adfea5144 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
@@ -29,10 +29,10 @@
public class Join extends AbstractSegment {
private final JoinType type;
- private final Table joinTable;
+ private final TableLike joinTable;
private final Condition on;
- Join(JoinType type, Table joinTable, Condition on) {
+ Join(JoinType type, TableLike joinTable, Condition on) {
super(joinTable, on);
@@ -51,7 +51,7 @@ public JoinType getType() {
/**
* @return the joined {@link Table}.
*/
- public Table getJoinTable() {
+ public TableLike getJoinTable() {
return joinTable;
}
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 382bc9d135..9570d7fa4d 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
@@ -134,7 +134,7 @@ interface SelectAndFrom extends SelectFrom {
* @see SQL#table(String)
*/
@Override
- SelectFromAndJoin from(Table table);
+ SelectFromAndJoin from(TableLike table);
/**
* Declare one or more {@link Table}s to {@code SELECT … FROM}. Multiple calls to this or other {@code from} methods
@@ -146,7 +146,7 @@ interface SelectAndFrom extends SelectFrom {
* @see SQL#table(String)
*/
@Override
- SelectFromAndJoin from(Table... tables);
+ SelectFromAndJoin from(TableLike... tables);
/**
* Declare one or more {@link Table}s to {@code SELECT … FROM}. Multiple calls to this or other {@code from} methods
@@ -158,7 +158,7 @@ interface SelectAndFrom extends SelectFrom {
* @see SQL#table(String)
*/
@Override
- SelectFromAndJoin from(Collection extends Table> tables);
+ SelectFromAndJoin from(Collection extends TableLike> tables);
}
/**
@@ -186,7 +186,7 @@ interface SelectFrom extends BuildSelect {
* @see From
* @see SQL#table(String)
*/
- SelectFromAndOrderBy from(Table table);
+ SelectFromAndOrderBy from(TableLike table);
/**
* Declare one or more {@link Table}s to {@code SELECT … FROM}. Multiple calls to this or other {@code from} methods
@@ -197,7 +197,7 @@ interface SelectFrom extends BuildSelect {
* @see From
* @see SQL#table(String)
*/
- SelectFromAndOrderBy from(Table... tables);
+ SelectFromAndOrderBy from(TableLike... tables);
/**
* Declare one or more {@link Table}s to {@code SELECT … FROM}. Multiple calls to this or other {@code from} methods
@@ -208,7 +208,7 @@ interface SelectFrom extends BuildSelect {
* @see From
* @see SQL#table(String)
*/
- SelectFromAndOrderBy from(Collection extends Table> tables);
+ SelectFromAndOrderBy from(Collection extends TableLike> tables);
}
/**
@@ -229,13 +229,13 @@ interface SelectFromAndOrderBy extends SelectFrom, SelectOrdered, SelectLimitOff
SelectFromAndOrderBy from(String table);
@Override
- SelectFromAndOrderBy from(Table table);
+ SelectFromAndOrderBy from(TableLike table);
@Override
- SelectFromAndOrderBy from(Table... tables);
+ SelectFromAndOrderBy from(TableLike... tables);
@Override
- SelectFromAndOrderBy from(Collection extends Table> tables);
+ SelectFromAndOrderBy from(Collection extends TableLike> tables);
@Override
SelectFromAndOrderBy orderBy(Column... columns);
@@ -263,7 +263,7 @@ interface SelectFromAndJoin
* @see SQL#table(String)
*/
@Override
- SelectFromAndJoin from(Table table);
+ SelectFromAndJoin from(TableLike table);
/**
* Declare one or more {@link Table}s to {@code SELECT … FROM}. Multiple calls to this or other {@code from} methods
@@ -275,7 +275,7 @@ interface SelectFromAndJoin
* @see SQL#table(String)
*/
@Override
- SelectFromAndJoin from(Table... tables);
+ SelectFromAndJoin from(TableLike... tables);
/**
* Declare one or more {@link Table}s to {@code SELECT … FROM}. Multiple calls to this or other {@code from} methods
@@ -287,7 +287,7 @@ interface SelectFromAndJoin
* @see SQL#table(String)
*/
@Override
- SelectFromAndJoin from(Collection extends Table> tables);
+ SelectFromAndJoin from(Collection extends TableLike> tables);
/**
* Apply {@code limit} and {@code offset} parameters to the select statement. To read the first 20 rows from start
@@ -474,17 +474,17 @@ interface SelectJoin extends SelectLock, BuildSelect {
* @see Join
* @see SQL#table(String)
*/
- SelectOn join(Table table);
+ SelectOn join(TableLike table);
/**
* Declare a {@code LEFT OUTER JOIN} {@link Table}.
*
- * @param table name of the table, must not be {@literal null}.
+ * @param table must not be {@literal null}.
* @return {@code this} builder.
* @see Join
* @see SQL#table(String)
*/
- SelectOn leftOuterJoin(Table table);
+ SelectOn leftOuterJoin(TableLike table);
}
/**
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 3c09e9091f..e95936bddd 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
@@ -34,10 +34,10 @@ class SelectValidator extends AbstractImportValidator {
private final Stack selects = new Stack<>();
private int selectFieldCount;
- private Set requiredBySelect = new HashSet<>();
- private Set requiredByOrderBy = new HashSet<>();
+ private Set requiredBySelect = new HashSet<>();
+ private Set requiredByOrderBy = new HashSet<>();
- private Set join = new HashSet<>();
+ private Set join = new HashSet<>();
/**
* Validates a {@link Select} statement.
@@ -57,7 +57,7 @@ private void doValidate(Select select) {
throw new IllegalStateException("SELECT does not declare a select list");
}
- for (Table table : requiredBySelect) {
+ for (TableLike table : requiredBySelect) {
if (!join.contains(table) && !from.contains(table)) {
throw new IllegalStateException(String
.format("Required table [%s] by a SELECT column not imported by FROM %s or JOIN %s", table, from, join));
@@ -71,7 +71,7 @@ private void doValidate(Select select) {
}
}
- for (Table table : requiredByOrderBy) {
+ for (TableLike table : requiredByOrderBy) {
if (!join.contains(table) && !from.contains(table)) {
throw new IllegalStateException(String
.format("Required table [%s] by a ORDER BY column not imported by FROM %s or JOIN %s", table, from, join));
@@ -100,13 +100,13 @@ public void enter(Visitable segment) {
if (segment instanceof AsteriskFromTable && parent instanceof Select) {
- Table table = ((AsteriskFromTable) segment).getTable();
+ TableLike table = ((AsteriskFromTable) segment).getTable();
requiredBySelect.add(table);
}
if (segment instanceof Column && (parent instanceof Select || parent instanceof SimpleFunction)) {
- Table table = ((Column) segment).getTable();
+ TableLike table = ((Column) segment).getTable();
if (table != null) {
requiredBySelect.add(table);
@@ -115,15 +115,15 @@ public void enter(Visitable segment) {
if (segment instanceof Column && parent instanceof OrderByField) {
- Table table = ((Column) segment).getTable();
+ TableLike table = ((Column) segment).getTable();
if (table != null) {
requiredByOrderBy.add(table);
}
}
- if (segment instanceof Table && parent instanceof Join) {
- join.add((Table) segment);
+ if (segment instanceof TableLike && parent instanceof Join) {
+ join.add((TableLike) segment);
}
super.enter(segment);
}
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 86707a6235..640066796a 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
@@ -15,15 +15,10 @@
*/
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;
/**
- * Represents a table reference within an SQL statement. Typically used to denote {@code FROM} or {@code JOIN} or to
+ * Represents a table reference within a SQL statement. Typically used to denote {@code FROM} or {@code JOIN} or to
* prefix a {@link Column}.
*
* Renders to: {@code } or {@code AS }.
@@ -31,7 +26,7 @@
* @author Mark Paluch
* @since 1.1
*/
-public class Table extends AbstractSegment {
+public class Table extends AbstractSegment implements TableLike {
private final SqlIdentifier name;
@@ -112,110 +107,6 @@ public Table as(SqlIdentifier alias) {
return new AliasedTable(name, alias);
}
- /**
- * Creates a new {@link Column} associated with this {@link Table}.
- *
- * Note: This {@link Table} does not track column creation and there is no possibility to enumerate all
- * {@link Column}s that were created for this table.
- *
- * @param name column name, must not be {@literal null} or empty.
- * @return a new {@link Column} associated with this {@link Table}.
- */
- public Column column(String name) {
-
- Assert.hasText(name, "Name must not be null or empty!");
-
- return new Column(name, this);
- }
-
- /**
- * Creates a new {@link Column} associated with this {@link Table}.
- *
- * Note: This {@link Table} does not track column creation and there is no possibility to enumerate all
- * {@link Column}s that were created for this table.
- *
- * @param name column name, must not be {@literal null} or empty.
- * @return a new {@link Column} associated with this {@link Table}.
- * @since 2.0
- */
- public Column column(SqlIdentifier name) {
-
- Assert.notNull(name, "Name must not be null");
-
- return new Column(name, this);
- }
-
- /**
- * Creates a {@link List} of {@link Column}s associated with this {@link Table}.
- *
- * Note: This {@link Table} does not track column creation and there is no possibility to enumerate all
- * {@link Column}s that were created for this table.
- *
- * @param names column names, must not be {@literal null} or empty.
- * @return a new {@link List} of {@link Column}s associated with this {@link Table}.
- */
- public List columns(String... names) {
-
- Assert.notNull(names, "Names must not be null");
-
- return columns(Arrays.asList(names));
- }
-
- /**
- * Creates a {@link List} of {@link Column}s associated with this {@link Table}.
- *
- * Note: This {@link Table} does not track column creation and there is no possibility to enumerate all
- * {@link Column}s that were created for this table.
- *
- * @param names column names, must not be {@literal null} or empty.
- * @return a new {@link List} of {@link Column}s associated with this {@link Table}.
- * @since 2.0
- */
- public List columns(SqlIdentifier... names) {
-
- Assert.notNull(names, "Names must not be null");
-
- List columns = new ArrayList<>();
- for (SqlIdentifier name : names) {
- columns.add(column(name));
- }
-
- return columns;
- }
-
- /**
- * Creates a {@link List} of {@link Column}s associated with this {@link Table}.
- *
- * Note: This {@link Table} does not track column creation and there is no possibility to enumerate all
- * {@link Column}s that were created for this table.
- *
- * @param names column names, must not be {@literal null} or empty.
- * @return a new {@link List} of {@link Column}s associated with this {@link Table}.
- */
- public List columns(Collection names) {
-
- Assert.notNull(names, "Names must not be null");
-
- List columns = new ArrayList<>();
- for (String name : names) {
- columns.add(column(name));
- }
-
- return columns;
- }
-
- /**
- * Creates a {@link AsteriskFromTable} maker selecting all columns from this {@link Table} (e.g. {@code SELECT
- *
-
- * .*}.
- *
- * @return the select all marker for this {@link Table}.
- */
- public AsteriskFromTable asterisk() {
- return new AsteriskFromTable(this);
- }
-
/**
* @return the table name.
*/
diff --git a/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/TableLike.java b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/TableLike.java
new file mode 100644
index 0000000000..ef5484e5ff
--- /dev/null
+++ b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/TableLike.java
@@ -0,0 +1,138 @@
+/*
+ * Copyright 2021 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
+ *
+ * https://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;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.List;
+
+/**
+ * A segment that can be used as table in a query.
+ *
+ * @author Jens Schauder
+ * @Since 2.3
+ */
+public interface TableLike extends Segment {
+ /**
+ * Creates a new {@link Column} associated with this {@link Table}.
+ *
+ * Note: This {@link Table} does not track column creation and there is no possibility to enumerate all
+ * {@link Column}s that were created for this table.
+ *
+ * @param name column name, must not be {@literal null} or empty.
+ * @return a new {@link Column} associated with this {@link Table}.
+ */
+ default Column column(String name) {
+
+ Assert.hasText(name, "Name must not be null or empty!");
+
+ return new Column(name, this);
+ }
+
+ /**
+ * Creates a new {@link Column} associated with this {@link Table}.
+ *
+ * Note: This {@link Table} does not track column creation and there is no possibility to enumerate all
+ * {@link Column}s that were created for this table.
+ *
+ * @param name column name, must not be {@literal null} or empty.
+ * @return a new {@link Column} associated with this {@link Table}.
+ * @since 2.0
+ */
+ default Column column(SqlIdentifier name) {
+
+ Assert.notNull(name, "Name must not be null");
+
+ return new Column(name, this);
+ }
+ /**
+ * Creates a {@link List} of {@link Column}s associated with this {@link Table}.
+ *
+ * Note: This {@link Table} does not track column creation and there is no possibility to enumerate all
+ * {@link Column}s that were created for this table.
+ *
+ * @param names column names, must not be {@literal null} or empty.
+ * @return a new {@link List} of {@link Column}s associated with this {@link Table}.
+ */
+ default List columns(String... names) {
+
+ Assert.notNull(names, "Names must not be null");
+
+ return columns(Arrays.asList(names));
+ }
+
+ /**
+ * Creates a {@link List} of {@link Column}s associated with this {@link Table}.
+ *
+ * Note: This {@link Table} does not track column creation and there is no possibility to enumerate all
+ * {@link Column}s that were created for this table.
+ *
+ * @param names column names, must not be {@literal null} or empty.
+ * @return a new {@link List} of {@link Column}s associated with this {@link Table}.
+ * @since 2.0
+ */
+ default List columns(SqlIdentifier... names) {
+
+ Assert.notNull(names, "Names must not be null");
+
+ List columns = new ArrayList<>();
+ for (SqlIdentifier name : names) {
+ columns.add(column(name));
+ }
+
+ return columns;
+ }
+
+ /**
+ * Creates a {@link List} of {@link Column}s associated with this {@link Table}.
+ *
+ * Note: This {@link Table} does not track column creation and there is no possibility to enumerate all
+ * {@link Column}s that were created for this table.
+ *
+ * @param names column names, must not be {@literal null} or empty.
+ * @return a new {@link List} of {@link Column}s associated with this {@link Table}.
+ */
+ default List columns(Collection names) {
+
+ Assert.notNull(names, "Names must not be null");
+
+ List columns = new ArrayList<>();
+ for (String name : names) {
+ columns.add(column(name));
+ }
+
+ return columns;
+ }
+
+ /**
+ * Creates a {@link AsteriskFromTable} maker selecting all columns from this {@link Table} (e.g. {@code SELECT
+ *
+
+ * .*}.
+ *
+ * @return the select all marker for this {@link Table}.
+ */
+ default AsteriskFromTable asterisk() {
+ return new AsteriskFromTable(this);
+ }
+
+ SqlIdentifier getName();
+
+ SqlIdentifier getReferenceName();
+}
diff --git a/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/render/ColumnVisitor.java b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/render/ColumnVisitor.java
index 78da0bf13a..9f49d5e2db 100644
--- a/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/render/ColumnVisitor.java
+++ b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/render/ColumnVisitor.java
@@ -17,12 +17,14 @@
import org.springframework.data.relational.core.sql.Column;
import org.springframework.data.relational.core.sql.SqlIdentifier;
-import org.springframework.data.relational.core.sql.Table;
+import org.springframework.data.relational.core.sql.TableLike;
import org.springframework.data.relational.core.sql.Visitable;
import org.springframework.lang.Nullable;
/**
- * Renderer for {@link Column}s.
+ * Renderer for {@link Column}s. Renders a column as {@literal
+ *
+ * .} or {@literal }.
*
* @author Mark Paluch
* @since 1.1
@@ -65,8 +67,8 @@ Delegation leaveMatched(Column segment) {
@Override
Delegation leaveNested(Visitable segment) {
- if (segment instanceof Table) {
- tableName = context.getNamingStrategy().getReferenceName((Table) segment);
+ if (segment instanceof TableLike) {
+ tableName = context.getNamingStrategy().getReferenceName((TableLike) segment);
}
return super.leaveNested(segment);
diff --git a/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/render/ExpressionVisitor.java b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/render/ExpressionVisitor.java
index a568c23d47..0f890f2915 100644
--- a/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/render/ExpressionVisitor.java
+++ b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/render/ExpressionVisitor.java
@@ -15,16 +15,17 @@
*/
package org.springframework.data.relational.core.sql.render;
+import org.springframework.data.relational.core.sql.AsteriskFromTable;
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.Literal;
import org.springframework.data.relational.core.sql.Named;
import org.springframework.data.relational.core.sql.SimpleFunction;
import org.springframework.data.relational.core.sql.SubselectExpression;
import org.springframework.data.relational.core.sql.Visitable;
import org.springframework.lang.Nullable;
+import org.springframework.util.Assert;
/**
* {@link PartRenderer} for {@link Expression}s.
@@ -38,12 +39,33 @@
class ExpressionVisitor extends TypedSubtreeVisitor implements PartRenderer {
private final RenderContext context;
+ private final AliasHandling aliasHandling;
private CharSequence value = "";
private @Nullable PartRenderer partRenderer;
+ /**
+ * Creates an {@code ExpressionVisitor} that does not use aliases for column names
+ * @param context must not be {@literal null}.
+ */
ExpressionVisitor(RenderContext context) {
+ this(context, AliasHandling.IGNORE);
+ }
+
+ /**
+ * Creates an {@code ExpressionVisitor}.
+ *
+ * @param context must not be {@literal null}.
+ * @param aliasHandling controls if columns should be rendered as their alias or using their table names.
+ * @since 2.3
+ */
+ ExpressionVisitor(RenderContext context, AliasHandling aliasHandling) {
+
+ Assert.notNull(context, "The render context must not be null");
+ Assert.notNull(aliasHandling, "The aliasHandling must not be null");
+
this.context = context;
+ this.aliasHandling = aliasHandling;
}
/*
@@ -71,7 +93,8 @@ Delegation enterMatched(Expression segment) {
Column column = (Column) segment;
- value = NameRenderer.fullyQualifiedReference(context, column);
+ value = aliasHandling == AliasHandling.USE ? NameRenderer.fullyQualifiedReference(context, column)
+ : NameRenderer.fullyQualifiedUnaliasedReference(context, column);
} else if (segment instanceof BindMarker) {
if (segment instanceof Named) {
@@ -79,7 +102,10 @@ Delegation enterMatched(Expression segment) {
} else {
value = segment.toString();
}
- } else if (segment instanceof Literal) {
+ } else if (segment instanceof AsteriskFromTable) {
+ value = NameRenderer.render(context, ((AsteriskFromTable) segment).getTable()) + ".*";
+ } else {
+ // works for literals and just and possibly more
value = segment.toString();
}
@@ -94,6 +120,7 @@ Delegation enterMatched(Expression segment) {
Delegation enterNested(Visitable segment) {
if (segment instanceof Condition) {
+
ConditionVisitor visitor = new ConditionVisitor(context);
partRenderer = visitor;
return Delegation.delegateTo(visitor);
@@ -125,4 +152,20 @@ Delegation leaveMatched(Expression segment) {
public CharSequence getRenderedPart() {
return value;
}
+
+ /**
+ * Describes how aliases of columns should be rendered.
+ * @since 2.3
+ */
+ enum AliasHandling {
+ /**
+ * The alias does not get used.
+ */
+ IGNORE,
+
+ /**
+ * The alias gets used. This means aliased columns get rendered as {@literal }.
+ */
+ USE
+ }
}
diff --git a/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/render/FromTableVisitor.java b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/render/FromTableVisitor.java
index 2fd80d8278..89bffd3560 100644
--- a/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/render/FromTableVisitor.java
+++ b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/render/FromTableVisitor.java
@@ -17,20 +17,28 @@
import org.springframework.data.relational.core.sql.Aliased;
import org.springframework.data.relational.core.sql.From;
-import org.springframework.data.relational.core.sql.Table;
+import org.springframework.data.relational.core.sql.InlineQuery;
+import org.springframework.data.relational.core.sql.TableLike;
+import org.springframework.lang.Nullable;
+import org.springframework.util.Assert;
/**
- * Renderer for {@link Table} used within a {@link From} clause. Uses a {@link RenderTarget} to call back for render
+ * Renderer for {@link TableLike} used within a {@link From} or
+ * {@link org.springframework.data.relational.core.sql.Join} clause. Uses a {@link RenderTarget} to call back for render
* results.
*
* @author Mark Paluch
* @author Jens Schauder
* @since 1.1
*/
-class FromTableVisitor extends TypedSubtreeVisitor {
+class FromTableVisitor extends TypedSubtreeVisitor {
private final RenderContext context;
private final RenderTarget parent;
+ @Nullable
+ private SelectStatementVisitor delegate;
+ @Nullable
+ private StringBuilder builder = null;
FromTableVisitor(RenderContext context, RenderTarget parent) {
super();
@@ -43,9 +51,32 @@ class FromTableVisitor extends TypedSubtreeVisitor {
* @see org.springframework.data.relational.core.sql.render.TypedSubtreeVisitor#enterMatched(org.springframework.data.relational.core.sql.Visitable)
*/
@Override
- Delegation enterMatched(Table segment) {
+ Delegation enterMatched(TableLike segment) {
- StringBuilder builder = new StringBuilder();
+ builder = new StringBuilder();
+
+ if (segment instanceof InlineQuery) {
+
+ builder.append("(");
+ delegate = new SelectStatementVisitor(context);
+ return Delegation.delegateTo(delegate);
+ }
+
+ return super.enterMatched(segment);
+ }
+
+ @Override
+ Delegation leaveMatched(TableLike segment) {
+
+ Assert.state(builder != null, "Builder must not be null in leaveMatched.");
+
+ if (delegate != null) {
+
+ builder.append(delegate.getRenderedPart());
+ builder.append(") ");
+
+ delegate = null;
+ }
builder.append(NameRenderer.render(context, segment));
if (segment instanceof Aliased) {
@@ -54,6 +85,6 @@ Delegation enterMatched(Table segment) {
parent.onRendered(builder);
- return super.enterMatched(segment);
+ return super.leaveMatched(segment);
}
}
diff --git a/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/render/JoinVisitor.java b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/render/JoinVisitor.java
index c5a560ee6f..91273258a3 100644
--- a/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/render/JoinVisitor.java
+++ b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/render/JoinVisitor.java
@@ -15,10 +15,9 @@
*/
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.TableLike;
import org.springframework.data.relational.core.sql.Visitable;
/**
@@ -30,18 +29,18 @@
*/
class JoinVisitor extends TypedSubtreeVisitor {
- private final RenderContext context;
private final RenderTarget parent;
private final StringBuilder joinClause = new StringBuilder();
+ private final FromTableVisitor fromTableVisitor;
private final ConditionVisitor conditionVisitor;
private boolean inCondition = false;
private boolean hasSeenCondition = false;
JoinVisitor(RenderContext context, RenderTarget parent) {
- this.context = context;
this.parent = parent;
this.conditionVisitor = new ConditionVisitor(context);
+ this.fromTableVisitor = new FromTableVisitor(context, joinClause::append);
}
/*
@@ -63,11 +62,8 @@ Delegation enterMatched(Join segment) {
@Override
Delegation enterNested(Visitable segment) {
- if (segment instanceof Table && !inCondition) {
- joinClause.append(NameRenderer.render(context, (Table) segment));
- if (segment instanceof Aliased) {
- joinClause.append(" ").append(NameRenderer.render(context, (Aliased) segment));
- }
+ if (segment instanceof TableLike && !inCondition) {
+ return Delegation.delegateTo(fromTableVisitor);
} else if (segment instanceof Condition) {
inCondition = true;
@@ -108,6 +104,7 @@ Delegation leaveNested(Visitable segment) {
*/
@Override
Delegation leaveMatched(Join segment) {
+
parent.onRendered(joinClause);
return super.leaveMatched(segment);
}
diff --git a/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/render/NameRenderer.java b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/render/NameRenderer.java
index 0eedad6f28..831346b6a8 100644
--- a/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/render/NameRenderer.java
+++ b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/render/NameRenderer.java
@@ -21,34 +21,28 @@
import org.springframework.data.relational.core.sql.Named;
import org.springframework.data.relational.core.sql.SqlIdentifier;
import org.springframework.data.relational.core.sql.Table;
+import org.springframework.data.relational.core.sql.TableLike;
/**
* Utility to render {@link Column} and {@link Table} names using {@link SqlIdentifier} and {@link RenderContext} to
* SQL.
*
* @author Mark Paluch
+ * @author Jens Schauder
*/
class NameRenderer {
/**
- * Render the {@link Table#getName() table name } with considering the {@link RenderNamingStrategy#getName(Table)
- * naming strategy}.
- *
- * @param context
- * @param table
- * @return
+ * Render the {@link TableLike#getName() table name } with considering the
+ * {@link RenderNamingStrategy#getName(TableLike) naming strategy}.
*/
- static CharSequence render(RenderContext context, Table table) {
+ static CharSequence render(RenderContext context, TableLike table) {
return render(context, context.getNamingStrategy().getName(table));
}
/**
* Render the {@link Column#getName() column name} with considering the {@link RenderNamingStrategy#getName(Column)
* naming strategy}.
- *
- * @param context
- * @param table
- * @return
*/
static CharSequence render(RenderContext context, Column column) {
return render(context, context.getNamingStrategy().getName(column));
@@ -56,10 +50,6 @@ static CharSequence render(RenderContext context, Column column) {
/**
* Render the {@link Named#getName() name}.
- *
- * @param context
- * @param table
- * @return
*/
static CharSequence render(RenderContext context, Named named) {
return render(context, named.getName());
@@ -67,10 +57,6 @@ static CharSequence render(RenderContext context, Named named) {
/**
* Render the {@link Aliased#getAlias() alias}.
- *
- * @param context
- * @param table
- * @return
*/
static CharSequence render(RenderContext context, Aliased aliased) {
return render(context, aliased.getAlias());
@@ -78,23 +64,15 @@ static CharSequence render(RenderContext context, Aliased aliased) {
/**
* Render the {@link Table#getReferenceName()} table reference name} with considering the
- * {@link RenderNamingStrategy#getReferenceName(Table) naming strategy}.
- *
- * @param context
- * @param table
- * @return
+ * {@link RenderNamingStrategy#getReferenceName(TableLike) naming strategy}.
*/
- static CharSequence reference(RenderContext context, Table table) {
+ static CharSequence reference(RenderContext context, TableLike table) {
return render(context, context.getNamingStrategy().getReferenceName(table));
}
/**
* Render the {@link Column#getReferenceName()} column reference name} with considering the
* {@link RenderNamingStrategy#getReferenceName(Column) naming strategy}.
- *
- * @param context
- * @param table
- * @return
*/
static CharSequence reference(RenderContext context, Column column) {
return render(context, context.getNamingStrategy().getReferenceName(column));
@@ -103,9 +81,6 @@ static CharSequence reference(RenderContext context, Column column) {
/**
* Render the fully-qualified table and column name with considering the naming strategies of each component.
*
- * @param context
- * @param column
- * @return
* @see RenderNamingStrategy#getReferenceName
*/
static CharSequence fullyQualifiedReference(RenderContext context, Column column) {
@@ -116,13 +91,24 @@ static CharSequence fullyQualifiedReference(RenderContext context, Column column
namingStrategy.getReferenceName(column)));
}
+ /**
+ * Render the fully-qualified table and column name with considering the naming strategies of each component without
+ * using the alias for the column. For the table the alias is still used.
+ *
+ * @see #fullyQualifiedReference(RenderContext, Column)
+ * @since 2.3
+ */
+ static CharSequence fullyQualifiedUnaliasedReference(RenderContext context, Column column) {
+
+ RenderNamingStrategy namingStrategy = context.getNamingStrategy();
+
+ return render(context,
+ SqlIdentifier.from(namingStrategy.getReferenceName(column.getTable()), namingStrategy.getName(column)));
+ }
+
/**
* Render the {@link SqlIdentifier#toSql(IdentifierProcessing) identifier to SQL} considering
* {@link IdentifierProcessing}.
- *
- * @param context
- * @param identifier
- * @return
*/
static CharSequence render(RenderContext context, SqlIdentifier identifier) {
return identifier.toSql(context.getIdentifierProcessing());
diff --git a/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/render/NamingStrategies.java b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/render/NamingStrategies.java
index 47c2f65563..e0f51ddb50 100644
--- a/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/render/NamingStrategies.java
+++ b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/render/NamingStrategies.java
@@ -15,14 +15,15 @@
*/
package org.springframework.data.relational.core.sql.render;
+import java.util.Locale;
+import java.util.function.Function;
+
import org.springframework.data.relational.core.sql.Column;
import org.springframework.data.relational.core.sql.SqlIdentifier;
import org.springframework.data.relational.core.sql.Table;
+import org.springframework.data.relational.core.sql.TableLike;
import org.springframework.util.Assert;
-import java.util.Locale;
-import java.util.function.Function;
-
/**
* Factory for {@link RenderNamingStrategy} objects.
*
@@ -110,7 +111,7 @@ public static RenderNamingStrategy toLower(Locale locale) {
}
enum AsIs implements RenderNamingStrategy {
- INSTANCE;
+ INSTANCE
}
static class DelegatingRenderNamingStrategy implements RenderNamingStrategy {
@@ -135,12 +136,12 @@ public SqlIdentifier getReferenceName(Column column) {
}
@Override
- public SqlIdentifier getName(Table table) {
+ public SqlIdentifier getName(TableLike table) {
return delegate.getName(table).transform(mappingFunction::apply);
}
@Override
- public SqlIdentifier getReferenceName(Table table) {
+ public SqlIdentifier getReferenceName(TableLike table) {
return delegate.getReferenceName(table).transform(mappingFunction::apply);
}
}
diff --git a/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/render/RenderNamingStrategy.java b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/render/RenderNamingStrategy.java
index aac84a38b0..01f2661066 100644
--- a/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/render/RenderNamingStrategy.java
+++ b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/render/RenderNamingStrategy.java
@@ -20,6 +20,7 @@
import org.springframework.data.relational.core.sql.Column;
import org.springframework.data.relational.core.sql.SqlIdentifier;
import org.springframework.data.relational.core.sql.Table;
+import org.springframework.data.relational.core.sql.TableLike;
import org.springframework.data.relational.core.sql.render.NamingStrategies.DelegatingRenderNamingStrategy;
import org.springframework.util.Assert;
@@ -27,6 +28,7 @@
* Naming strategy for SQL rendering.
*
* @author Mark Paluch
+ * @author Jens Schauder
* @see NamingStrategies
* @since 1.1
*/
@@ -55,24 +57,24 @@ default SqlIdentifier getReferenceName(Column column) {
}
/**
- * Return the {@link Table#getName() table name}.
+ * Return the {@link TableLike#getName() table name}.
*
* @param table the table.
- * @return the {@link Table#getName() table name}.
+ * @return the {@link TableLike#getName() table name}.
* @see Table#getName()
*/
- default SqlIdentifier getName(Table table) {
+ default SqlIdentifier getName(TableLike table) {
return table.getName();
}
/**
- * Return the {@link Table#getReferenceName() table reference name}.
+ * Return the {@link TableLike#getReferenceName() table reference name}.
*
* @param table the table.
- * @return the {@link Table#getReferenceName() table name}.
- * @see Table#getReferenceName()
+ * @return the {@link TableLike#getReferenceName() table name}.
+ * @see TableLike#getReferenceName()
*/
- default SqlIdentifier getReferenceName(Table table) {
+ default SqlIdentifier getReferenceName(TableLike table) {
return table.getReferenceName();
}
diff --git a/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/render/SelectListVisitor.java b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/render/SelectListVisitor.java
index 5e399911a9..8c4fd8c811 100644
--- a/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/render/SelectListVisitor.java
+++ b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/render/SelectListVisitor.java
@@ -15,14 +15,7 @@
*/
package org.springframework.data.relational.core.sql.render;
-import org.springframework.data.relational.core.sql.Aliased;
-import org.springframework.data.relational.core.sql.AsteriskFromTable;
-import org.springframework.data.relational.core.sql.Column;
-import org.springframework.data.relational.core.sql.Expression;
-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;
+import org.springframework.data.relational.core.sql.*;
/**
* {@link PartRenderer} for {@link SelectList}s.
@@ -38,11 +31,14 @@ class SelectListVisitor extends TypedSubtreeVisitor implements PartR
private final RenderTarget target;
private boolean requiresComma = false;
private boolean insideFunction = false; // this is hackery and should be fix with a proper visitor for
+ private ExpressionVisitor expressionVisitor;
// subelements.
SelectListVisitor(RenderContext context, RenderTarget target) {
+
this.context = context;
this.target = target;
+ this.expressionVisitor = new ExpressionVisitor(context, ExpressionVisitor.AliasHandling.IGNORE);
}
/*
@@ -56,9 +52,8 @@ Delegation enterNested(Visitable segment) {
builder.append(", ");
requiresComma = false;
}
- if (segment instanceof SimpleFunction) {
- builder.append(((SimpleFunction) segment).getFunctionName()).append("(");
- insideFunction = true;
+ if (segment instanceof Expression) {
+ return Delegation.delegateTo(expressionVisitor);
}
return super.enterNested(segment);
@@ -82,34 +77,14 @@ Delegation leaveMatched(SelectList segment) {
@Override
Delegation leaveNested(Visitable segment) {
- if (segment instanceof Table) {
- builder.append(NameRenderer.reference(context, (Table) segment)).append('.');
- }
-
- if (segment instanceof SimpleFunction) {
+ if (segment instanceof Expression) {
- builder.append(")");
- if (segment instanceof Aliased) {
- builder.append(" AS ").append(NameRenderer.render(context, (Aliased) segment));
- }
-
- insideFunction = false;
- requiresComma = true;
- } else if (segment instanceof AsteriskFromTable) {
- builder.append("*");
+ builder.append(expressionVisitor.getRenderedPart());
requiresComma = true;
- } else if (segment instanceof Column) {
+ }
- builder.append(NameRenderer.render(context, (Column) segment));
- if (segment instanceof Aliased && !insideFunction) {
- builder.append(" AS ").append(NameRenderer.render(context, (Aliased) segment));
- }
- requiresComma = true;
- } else if (segment instanceof AsteriskFromTable) {
- // the toString of AsteriskFromTable includes the table name, which would cause it to appear twice.
- builder.append("*");
- } else if (segment instanceof Expression) {
- builder.append(segment.toString());
+ if (segment instanceof Aliased && !insideFunction) {
+ builder.append(" AS ").append(NameRenderer.render(context, (Aliased) segment));
}
return super.leaveNested(segment);
@@ -123,4 +98,6 @@ Delegation leaveNested(Visitable segment) {
public CharSequence getRenderedPart() {
return builder;
}
+
+
}
diff --git a/spring-data-relational/src/test/java/org/springframework/data/relational/core/sql/AbstractTestSegment.java b/spring-data-relational/src/test/java/org/springframework/data/relational/core/sql/AbstractTestSegment.java
new file mode 100644
index 0000000000..98991efde1
--- /dev/null
+++ b/spring-data-relational/src/test/java/org/springframework/data/relational/core/sql/AbstractTestSegment.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright 2021 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
+ *
+ * https://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;
+
+/**
+ * Public {@link AbstractSegment} for usage in tests in other packages.
+ *
+ * @author Jens Schauder
+ */
+public class AbstractTestSegment extends AbstractSegment{
+ protected AbstractTestSegment(Segment... children) {
+ super(children);
+ }
+}
diff --git a/spring-data-relational/src/test/java/org/springframework/data/relational/core/sql/TestFrom.java b/spring-data-relational/src/test/java/org/springframework/data/relational/core/sql/TestFrom.java
new file mode 100644
index 0000000000..d30547ec49
--- /dev/null
+++ b/spring-data-relational/src/test/java/org/springframework/data/relational/core/sql/TestFrom.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright 2021 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
+ *
+ * https://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;
+
+/**
+ * A variant of {@link From} that can be used in tests in other packages.
+ *
+ * @author Jens Schauder
+ */
+public class TestFrom extends From {
+
+ public TestFrom(TableLike... tables) {
+ super(tables);
+ }
+}
diff --git a/spring-data-relational/src/test/java/org/springframework/data/relational/core/sql/TestJoin.java b/spring-data-relational/src/test/java/org/springframework/data/relational/core/sql/TestJoin.java
new file mode 100644
index 0000000000..ff4cd69194
--- /dev/null
+++ b/spring-data-relational/src/test/java/org/springframework/data/relational/core/sql/TestJoin.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright 2021 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
+ *
+ * https://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;
+
+/**
+ * Public {@link Join} with public constructor for tests in other packages.
+ *
+ * @author Jens Schauder
+ */
+public class TestJoin extends Join {
+ public TestJoin(JoinType type, TableLike joinTable, Condition on) {
+ super(type, joinTable, on);
+ }
+}
diff --git a/spring-data-relational/src/test/java/org/springframework/data/relational/core/sql/render/ExpressionVisitorUnitTests.java b/spring-data-relational/src/test/java/org/springframework/data/relational/core/sql/render/ExpressionVisitorUnitTests.java
new file mode 100644
index 0000000000..9808aed290
--- /dev/null
+++ b/spring-data-relational/src/test/java/org/springframework/data/relational/core/sql/render/ExpressionVisitorUnitTests.java
@@ -0,0 +1,139 @@
+/*
+ * Copyright 2021 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
+ *
+ * https://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 java.util.Arrays.*;
+import static org.assertj.core.api.Assertions.*;
+
+import java.util.List;
+
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.MethodSource;
+import org.springframework.data.relational.core.sql.Column;
+import org.springframework.data.relational.core.sql.Expression;
+import org.springframework.data.relational.core.sql.Expressions;
+import org.springframework.data.relational.core.sql.Functions;
+import org.springframework.data.relational.core.sql.SQL;
+import org.springframework.data.relational.core.sql.SimpleFunction;
+import org.springframework.data.relational.core.sql.Table;
+
+/**
+ * Tests for the {@link ExpressionVisitor}.
+ *
+ * @author Jens Schauder
+ */
+public class ExpressionVisitorUnitTests {
+
+ static SimpleRenderContext simpleRenderContext = new SimpleRenderContext(NamingStrategies.asIs());
+
+ @ParameterizedTest // GH-1003
+ @MethodSource
+ void expressionsWithOutAliasGetRendered(Fixture f) {
+
+ ExpressionVisitor visitor = new ExpressionVisitor(simpleRenderContext);
+
+ f.expression.visit(visitor);
+
+ assertThat(visitor.getRenderedPart().toString()).as(f.comment).isEqualTo(f.renderResult);
+ }
+
+ static List expressionsWithOutAliasGetRendered() {
+
+ // final Select select = Select.builder().select(Functions.count(Expressions.asterisk()),
+ // SQL.nullLiteral()).build();
+
+ return asList( //
+ fixture("String literal", SQL.literalOf("one"), "'one'"), //
+ fixture("Numeric literal", SQL.literalOf(23L), "23"), //
+ fixture("Boolean literal", SQL.literalOf(true), "TRUE"), //
+ fixture("Just", SQL.literalOf(Expressions.just("just an arbitrary String")), "just an arbitrary String"), //
+ fixture("Column", Column.create("col", Table.create("tab")), "tab.col"), //
+ fixture("*", Expressions.asterisk(), "*"), //
+ fixture("tab.*", Expressions.asterisk(Table.create("tab")), "tab.*"), //
+ fixture("Count 1", Functions.count(SQL.literalOf(1)), "COUNT(1)"), //
+ fixture("Count *", Functions.count(Expressions.asterisk()), "COUNT(*)"), //
+ fixture("Function", SimpleFunction.create("Function", asList(SQL.literalOf("one"), SQL.literalOf("two"))), //
+ "Function('one', 'two')"), //
+ fixture("Null", SQL.nullLiteral(), "NULL")); //
+ }
+
+ @Test // GH-1003
+ void renderAliasedExpressionWithAliasHandlingUse() {
+
+ ExpressionVisitor visitor = new ExpressionVisitor(simpleRenderContext, ExpressionVisitor.AliasHandling.USE);
+
+ Column expression = Column.aliased("col", Table.create("tab"), "col_alias");
+ expression.visit(visitor);
+
+ assertThat(visitor.getRenderedPart().toString()).isEqualTo("tab.col_alias");
+ }
+
+ @Test // GH-1003
+ void renderAliasedExpressionWithAliasHandlingDeclare() {
+
+ ExpressionVisitor visitor = new ExpressionVisitor(simpleRenderContext, ExpressionVisitor.AliasHandling.IGNORE);
+
+ Column expression = Column.aliased("col", Table.create("tab"), "col_alias");
+ expression.visit(visitor);
+
+ assertThat(visitor.getRenderedPart().toString()).isEqualTo("tab.col");
+ }
+
+ @Test // GH-1003
+ void considersNamingStrategy() {
+
+ ExpressionVisitor visitor = new ExpressionVisitor(new SimpleRenderContext(NamingStrategies.toUpper()));
+
+ Column expression = Column.create("col", Table.create("tab"));
+ expression.visit(visitor);
+
+ assertThat(visitor.getRenderedPart().toString()).isEqualTo("TAB.COL");
+ }
+
+ @Test // GH-1003
+ void considerNamingStrategyForTableAsterisk() {
+
+ ExpressionVisitor visitor = new ExpressionVisitor(new SimpleRenderContext(NamingStrategies.toUpper()));
+
+ Expression expression = Table.create("tab").asterisk();
+ expression.visit(visitor);
+
+ assertThat(visitor.getRenderedPart().toString()).isEqualTo("TAB.*");
+ }
+
+ static Fixture fixture(String comment, Expression expression, String renderResult) {
+
+ Fixture f = new Fixture();
+ f.comment = comment;
+ f.expression = expression;
+ f.renderResult = renderResult;
+
+ return f;
+ }
+
+ static class Fixture {
+
+ String comment;
+ Expression expression;
+ String renderResult;
+
+ @Override
+ public String toString() {
+ return comment;
+ }
+ }
+}
diff --git a/spring-data-relational/src/test/java/org/springframework/data/relational/core/sql/render/FromClauseVisitorUnitTests.java b/spring-data-relational/src/test/java/org/springframework/data/relational/core/sql/render/FromClauseVisitorUnitTests.java
new file mode 100644
index 0000000000..a0cb0d1d7e
--- /dev/null
+++ b/spring-data-relational/src/test/java/org/springframework/data/relational/core/sql/render/FromClauseVisitorUnitTests.java
@@ -0,0 +1,92 @@
+/*
+ * Copyright 2021 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
+ *
+ * https://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 java.util.Arrays.*;
+import static org.assertj.core.api.Assertions.*;
+
+import java.util.List;
+
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.MethodSource;
+import org.springframework.data.relational.core.sql.Column;
+import org.springframework.data.relational.core.sql.From;
+import org.springframework.data.relational.core.sql.InlineQuery;
+import org.springframework.data.relational.core.sql.Select;
+import org.springframework.data.relational.core.sql.Table;
+import org.springframework.data.relational.core.sql.TestFrom;
+
+/**
+ * Unit tests for the {@link FromClauseVisitor}.
+ *
+ * @author Jens Schauder
+ */
+public class FromClauseVisitorUnitTests {
+
+ StringBuilder renderResult = new StringBuilder();
+ FromClauseVisitor visitor = new FromClauseVisitor(new SimpleRenderContext(NamingStrategies.asIs()), renderResult::append);
+
+ @ParameterizedTest
+ @MethodSource
+ void testRendering(Fixture f) {
+
+ From from = f.from;
+
+ from.visit(visitor);
+
+ assertThat(renderResult.toString()).isEqualTo(f.renderResult);
+ }
+
+ static List testRendering() {
+
+ final Table tabOne = Table.create("tabOne");
+ final Table tabTwo = Table.create("tabTwo");
+ final Select selectOne = Select.builder().select(Column.create("oneId", tabOne)).from(tabOne).build();
+ final Select selectTwo = Select.builder().select(Column.create("twoId", tabTwo)).from(tabTwo).build();
+
+ return asList(
+ fixture("single table", new TestFrom(Table.create("one")), "one"),
+ fixture("single table with alias", new TestFrom(Table.aliased("one", "one_alias")), "one one_alias"),
+ fixture("multiple tables", new TestFrom(Table.create("one"),Table.create("two")), "one, two"),
+ fixture("multiple tables with alias", new TestFrom(Table.aliased("one", "one_alias"),Table.aliased("two", "two_alias")), "one one_alias, two two_alias"),
+ fixture("single inline query", new TestFrom(InlineQuery.create(selectOne, "ilAlias")), "(SELECT tabOne.oneId FROM tabOne) ilAlias"),
+ fixture("inline query with table", new TestFrom(InlineQuery.create(selectOne, "ilAlias"), tabTwo), "(SELECT tabOne.oneId FROM tabOne) ilAlias, tabTwo"),
+ fixture("table with inline query", new TestFrom(tabTwo,InlineQuery.create(selectOne, "ilAlias")), "tabTwo, (SELECT tabOne.oneId FROM tabOne) ilAlias"),
+ fixture("two inline queries", new TestFrom(InlineQuery.create(selectOne, "aliasOne"),InlineQuery.create(selectTwo, "aliasTwo")), "(SELECT tabOne.oneId FROM tabOne) aliasOne, (SELECT tabTwo.twoId FROM tabTwo) aliasTwo")
+ );
+ }
+
+ private static Fixture fixture(String comment, From from, String renderResult) {
+
+ Fixture fixture = new Fixture();
+ fixture.comment = comment;
+ fixture.from = from;
+ fixture.renderResult = renderResult;
+ return fixture;
+ }
+
+ static class Fixture {
+
+ String comment;
+ From from;
+ String renderResult;
+
+ @Override
+ public String toString() {
+ return comment;
+ }
+ }
+}
diff --git a/spring-data-relational/src/test/java/org/springframework/data/relational/core/sql/render/JoinVisitorTestsUnitTest.java b/spring-data-relational/src/test/java/org/springframework/data/relational/core/sql/render/JoinVisitorTestsUnitTest.java
new file mode 100644
index 0000000000..382608ea9f
--- /dev/null
+++ b/spring-data-relational/src/test/java/org/springframework/data/relational/core/sql/render/JoinVisitorTestsUnitTest.java
@@ -0,0 +1,87 @@
+/*
+ * Copyright 2021 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
+ *
+ * https://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 java.util.Arrays.*;
+import static org.assertj.core.api.Assertions.*;
+
+import java.util.List;
+
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.MethodSource;
+import org.springframework.data.relational.core.sql.Column;
+import org.springframework.data.relational.core.sql.InlineQuery;
+import org.springframework.data.relational.core.sql.Join;
+import org.springframework.data.relational.core.sql.Select;
+import org.springframework.data.relational.core.sql.Table;
+import org.springframework.data.relational.core.sql.TestJoin;
+import org.springframework.data.relational.core.sql.Visitor;
+
+public class JoinVisitorTestsUnitTest {
+
+ final StringBuilder builder = new StringBuilder();
+ Visitor visitor = new JoinVisitor(new SimpleRenderContext(NamingStrategies.asIs()), builder::append);
+
+ @ParameterizedTest
+ @MethodSource
+ void renderJoins(Fixture f) {
+
+ Join join = f.join;
+
+ join.visit(visitor);
+
+ assertThat(builder.toString()).isEqualTo(f.renderResult);
+ }
+
+ static List renderJoins() {
+
+ Column colOne = Column.create("colOne", Table.create("tabOne"));
+ Table tabTwo = Table.create("tabTwo");
+ Column colTwo = Column.create("colTwo", tabTwo);
+ final Column renamed = colOne.as("renamed");
+ final Select select = Select.builder().select(renamed).from(colOne.getTable()).build();
+ final InlineQuery inlineQuery = InlineQuery.create(select, "inline");
+
+ return asList(
+ fixture("simple join", new TestJoin(Join.JoinType.JOIN, tabTwo, colOne.isEqualTo(colTwo)),
+ "JOIN tabTwo ON tabOne.colOne = tabTwo.colTwo"),
+ fixture("inlineQuery",
+ new TestJoin(Join.JoinType.JOIN, inlineQuery, colTwo.isEqualTo(inlineQuery.column("renamed"))),
+ "JOIN (SELECT tabOne.colOne AS renamed FROM tabOne) inline ON tabTwo.colTwo = inline.renamed"));
+ }
+
+ private static Fixture fixture(String comment, Join join, String renderResult) {
+
+ final Fixture fixture = new Fixture();
+ fixture.comment = comment;
+ fixture.join = join;
+ fixture.renderResult = renderResult;
+
+ return fixture;
+ }
+
+ static class Fixture {
+
+ String comment;
+ Join join;
+ String renderResult;
+
+ @Override
+ public String toString() {
+ return comment;
+ }
+ }
+}
diff --git a/spring-data-relational/src/test/java/org/springframework/data/relational/core/sql/render/NameRendererUnitTests.java b/spring-data-relational/src/test/java/org/springframework/data/relational/core/sql/render/NameRendererUnitTests.java
new file mode 100644
index 0000000000..c87d795310
--- /dev/null
+++ b/spring-data-relational/src/test/java/org/springframework/data/relational/core/sql/render/NameRendererUnitTests.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright 2021 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
+ *
+ * https://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.jupiter.api.Test;
+import org.springframework.data.relational.core.sql.Column;
+import org.springframework.data.relational.core.sql.Table;
+
+/**
+ * Unit tests for the {@link NameRenderer}.
+ *
+ * @author Jens Schauder
+ */
+class NameRendererUnitTests {
+
+ RenderContext context = new SimpleRenderContext(NamingStrategies.asIs());
+
+ @Test // GH-1003
+ void rendersColumnWithoutTableName() {
+
+ Column column = Column.create("column", Table.create("table"));
+
+ CharSequence rendered = NameRenderer.render(context, column);
+
+ assertThat(rendered).isEqualTo("column");
+ }
+
+ @Test // GH-1003
+ void fullyQualifiedReference() {
+
+ Column column = Column.aliased("col", Table.aliased("table", "tab_alias"), "col_alias");
+
+ CharSequence rendered = NameRenderer.fullyQualifiedReference(context, column);
+
+ assertThat(rendered).isEqualTo("tab_alias.col_alias");
+ }
+
+ @Test // GH-1003
+ void fullyQualifiedUnaliasedReference() {
+
+ Column column = Column.aliased("col", Table.aliased("table", "tab_alias"), "col_alias");
+
+ CharSequence rendered = NameRenderer.fullyQualifiedUnaliasedReference(context, column);
+
+ assertThat(rendered).isEqualTo("tab_alias.col");
+ }
+}
diff --git a/spring-data-relational/src/test/java/org/springframework/data/relational/core/sql/render/SelectRendererUnitTests.java b/spring-data-relational/src/test/java/org/springframework/data/relational/core/sql/render/SelectRendererUnitTests.java
index abf4a88ecc..90a802fda3 100644
--- a/spring-data-relational/src/test/java/org/springframework/data/relational/core/sql/render/SelectRendererUnitTests.java
+++ b/spring-data-relational/src/test/java/org/springframework/data/relational/core/sql/render/SelectRendererUnitTests.java
@@ -18,18 +18,9 @@
import static org.assertj.core.api.Assertions.*;
import org.junit.jupiter.api.Test;
-
import org.springframework.data.relational.core.dialect.PostgresDialect;
import org.springframework.data.relational.core.dialect.RenderContextFactory;
-import org.springframework.data.relational.core.sql.Column;
-import org.springframework.data.relational.core.sql.Conditions;
-import org.springframework.data.relational.core.sql.Expressions;
-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.SqlIdentifier;
-import org.springframework.data.relational.core.sql.Table;
+import org.springframework.data.relational.core.sql.*;
import org.springframework.util.StringUtils;
/**
@@ -51,6 +42,18 @@ public void shouldRenderSingleColumn() {
assertThat(SqlRenderer.toString(select)).isEqualTo("SELECT bar.foo FROM bar");
}
+ @Test
+ public void honorsNamingStrategy() {
+
+ Table bar = SQL.table("bar");
+ Column foo = bar.column("foo");
+
+ Select select = Select.builder().select(foo).from(bar).build();
+
+ assertThat(SqlRenderer.create(new SimpleRenderContext(NamingStrategies.toUpper())).render(select))
+ .isEqualTo("SELECT BAR.FOO FROM BAR");
+ }
+
@Test // DATAJDBC-309
public void shouldRenderAliasedColumnAndFrom() {
@@ -172,6 +175,55 @@ public void shouldRenderMultipleJoinWithAnd() {
+ "JOIN tenant tenant_base ON tenant_base.tenant_id = department.tenant");
}
+ @Test // GH-1003
+ public void shouldRenderJoinWithInlineQuery() {
+
+ Table employee = SQL.table("employee");
+ Table department = SQL.table("department");
+
+ Select innerSelect = Select.builder()
+ .select(employee.column("id"), employee.column("department_Id"), employee.column("name")).from(employee)
+ .build();
+
+ final InlineQuery one = InlineQuery.create(innerSelect, "one");
+
+ Select select = Select.builder().select(one.column("id"), department.column("name")).from(department) //
+ .join(one).on(one.column("department_id")).equals(department.column("id")) //
+ .build();
+
+ final String sql = SqlRenderer.toString(select);
+
+ assertThat(sql).isEqualTo("SELECT one.id, department.name FROM department " //
+ + "JOIN (SELECT employee.id, employee.department_Id, employee.name FROM employee) one " //
+ + "ON one.department_id = department.id");
+ }
+
+ @Test // GH-1003
+ public void shouldRenderJoinWithTwoInlineQueries() {
+
+ Table employee = SQL.table("employee");
+ Table department = SQL.table("department");
+
+ Select innerSelectOne = Select.builder()
+ .select(employee.column("id"), employee.column("department_Id"), employee.column("name")).from(employee)
+ .build();
+ Select innerSelectTwo = Select.builder().select(department.column("id"), department.column("name")).from(department)
+ .build();
+
+ final InlineQuery one = InlineQuery.create(innerSelectOne, "one");
+ final InlineQuery two = InlineQuery.create(innerSelectTwo, "two");
+
+ Select select = Select.builder().select(one.column("id"), two.column("name")).from(one) //
+ .join(two).on(two.column("department_id")).equals(one.column("id")) //
+ .build();
+
+ final String sql = SqlRenderer.toString(select);
+ assertThat(sql).isEqualTo("SELECT one.id, two.name FROM (" //
+ + "SELECT employee.id, employee.department_Id, employee.name FROM employee) one " //
+ + "JOIN (SELECT department.id, department.name FROM department) two " //
+ + "ON two.department_id = one.id");
+ }
+
@Test // DATAJDBC-309
public void shouldRenderOrderByName() {
diff --git a/spring-data-relational/src/test/java/org/springframework/data/relational/core/sql/render/TypedSubtreeVisitorUnitTests.java b/spring-data-relational/src/test/java/org/springframework/data/relational/core/sql/render/TypedSubtreeVisitorUnitTests.java
new file mode 100644
index 0000000000..d20dc2a20a
--- /dev/null
+++ b/spring-data-relational/src/test/java/org/springframework/data/relational/core/sql/render/TypedSubtreeVisitorUnitTests.java
@@ -0,0 +1,230 @@
+/*
+ * Copyright 2021 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
+ *
+ * https://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 static org.springframework.data.relational.core.sql.render.DelegatingVisitor.Delegation.*;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.function.Function;
+
+import org.junit.jupiter.api.Test;
+import org.springframework.data.relational.core.sql.AbstractTestSegment;
+import org.springframework.data.relational.core.sql.Segment;
+import org.springframework.data.relational.core.sql.Visitable;
+
+/**
+ * Unit tests for {@link org.springframework.data.relational.core.sql.render.TypedSubtreeVisitor}.
+ *
+ * @author Jens Schauder
+ */
+class TypedSubtreeVisitorUnitTests {
+
+ List events = new ArrayList<>();
+
+ @Test // GH-1003
+ void enterAndLeavesSingleSegment() {
+
+ final TypedSubtreeVisitor visitor = new LoggingTypedSubtreeVisitor();
+ final TestSegment root = new TestSegment("root");
+
+ root.visit(visitor);
+
+ assertThat(events).containsExactly("enter matched root", "leave matched root");
+ }
+
+ @Test // GH-1003
+ void enterAndLeavesChainOfMatchingSegmentsAsNested() {
+
+ final TypedSubtreeVisitor visitor = new LoggingTypedSubtreeVisitor();
+ final TestSegment root = new TestSegment("root", new TestSegment("level 1", new TestSegment("level 2")));
+
+ root.visit(visitor);
+
+ assertThat(events).containsExactly("enter matched root", "enter nested level 1", "enter nested level 2",
+ "leave nested level 2", "leave nested level 1", "leave matched root");
+ }
+
+ @Test // GH-1003
+ void enterAndLeavesMatchingChildrenAsNested() {
+
+ final TypedSubtreeVisitor visitor = new LoggingTypedSubtreeVisitor();
+ final TestSegment root = new TestSegment("root", new TestSegment("child 1"), new TestSegment("child 2"));
+
+ root.visit(visitor);
+
+ assertThat(events).containsExactly("enter matched root", "enter nested child 1", "leave nested child 1",
+ "enter nested child 2", "leave nested child 2", "leave matched root");
+ }
+
+ @Test // GH-1003
+ void enterAndLeavesChainOfOtherSegmentsAsNested() {
+
+ final TypedSubtreeVisitor visitor = new LoggingTypedSubtreeVisitor();
+ final TestSegment root = new TestSegment("root", new OtherSegment("level 1", new OtherSegment("level 2")));
+
+ root.visit(visitor);
+
+ assertThat(events).containsExactly("enter matched root", "enter nested level 1", "enter nested level 2",
+ "leave nested level 2", "leave nested level 1", "leave matched root");
+ }
+
+ @Test // GH-1003
+ void enterAndLeavesOtherChildrenAsNested() {
+
+ final TypedSubtreeVisitor visitor = new LoggingTypedSubtreeVisitor();
+ final TestSegment root = new TestSegment("root", new OtherSegment("child 1"), new OtherSegment("child 2"));
+
+ root.visit(visitor);
+
+ assertThat(events).containsExactly("enter matched root", "enter nested child 1", "leave nested child 1",
+ "enter nested child 2", "leave nested child 2", "leave matched root");
+ }
+
+ @Test // GH-1003
+ void visitorIsReentrant() {
+
+ final LoggingTypedSubtreeVisitor visitor = new LoggingTypedSubtreeVisitor();
+ final TestSegment root1 = new TestSegment("root 1");
+ final TestSegment root2 = new TestSegment("root 2");
+
+ root1.visit(visitor);
+ root2.visit(visitor);
+
+ assertThat(events).containsExactly("enter matched root 1", "leave matched root 1", "enter matched root 2",
+ "leave matched root 2");
+ }
+
+ @Test // GH-1003
+ void delegateToOtherVisitorOnEnterMatchedRevisitsTheSegment() {
+
+ final LoggingTypedSubtreeVisitor first = new LoggingTypedSubtreeVisitor("first ");
+ final LoggingTypedSubtreeVisitor second = new LoggingTypedSubtreeVisitor("second ");
+ first.enterMatched(s -> delegateTo(second));
+ final TestSegment root = new TestSegment("root", new TestSegment("child 1"), new TestSegment("child 2"));
+
+ root.visit(first);
+
+ assertThat(events).containsExactly("first enter matched root", "second enter matched root",
+ "second enter nested child 1", "second leave nested child 1", "second enter nested child 2",
+ "second leave nested child 2", "second leave matched root", "first leave matched root");
+ }
+
+ @Test // GH-1003
+ void delegateToOtherVisitorOnEnterNestedRevisitsTheNestedSegment() {
+
+ final LoggingTypedSubtreeVisitor first = new LoggingTypedSubtreeVisitor("first ");
+ final LoggingTypedSubtreeVisitor second = new LoggingTypedSubtreeVisitor("second ");
+ first.enterNested(
+ s -> ((TestSegment) s).name.equals("child 2") ? delegateTo(second) : DelegatingVisitor.Delegation.retain());
+ final TestSegment root = new TestSegment("root", new TestSegment("child 1"), new TestSegment("child 2"),
+ new TestSegment("child 3"));
+
+ root.visit(first);
+
+ assertThat(events).containsExactly("first enter matched root", "first enter nested child 1",
+ "first leave nested child 1", "first enter nested child 2", "second enter matched child 2",
+ "second leave matched child 2", "first leave nested child 2", "first enter nested child 3",
+ "first leave nested child 3", "first leave matched root");
+ }
+
+ static class TestSegment extends AbstractTestSegment {
+
+ private final String name;
+
+ TestSegment(String name, Segment... children) {
+
+ super(children);
+ this.name = name;
+ }
+
+ @Override
+ public String toString() {
+ return name;
+ }
+ }
+
+ static class OtherSegment extends AbstractTestSegment {
+
+ private final String name;
+
+ public OtherSegment(String name, Segment... children) {
+
+ super(children);
+ this.name = name;
+ }
+
+ @Override
+ public String toString() {
+ return name;
+ }
+ }
+
+ class LoggingTypedSubtreeVisitor extends TypedSubtreeVisitor {
+
+ final String prefix;
+ Function enterMatchedDelegation;
+ Function enterNestedDelegation;
+
+ LoggingTypedSubtreeVisitor(String prefix) {
+ this.prefix = prefix;
+ }
+
+ LoggingTypedSubtreeVisitor() {
+ this("");
+ }
+
+ @Override
+ Delegation enterMatched(TestSegment segment) {
+
+ events.add(prefix + "enter matched " + segment);
+ final Delegation delegation = super.enterMatched(segment);
+
+ return enterMatchedDelegation == null ? delegation : enterMatchedDelegation.apply(segment);
+ }
+
+ void enterMatched(Function delegation) {
+ enterMatchedDelegation = delegation;
+ }
+
+ @Override
+ Delegation leaveMatched(TestSegment segment) {
+
+ events.add(prefix + "leave matched " + segment);
+ return super.leaveMatched(segment);
+ }
+
+ @Override
+ Delegation enterNested(Visitable segment) {
+
+ events.add(prefix + "enter nested " + segment);
+ return enterNestedDelegation == null ? super.enterNested(segment) : enterNestedDelegation.apply(segment);
+ }
+
+ void enterNested(Function delegation) {
+ enterNestedDelegation = delegation;
+ }
+
+ @Override
+ Delegation leaveNested(Visitable segment) {
+
+ events.add(prefix + "leave nested " + segment);
+ return super.leaveNested(segment);
+ }
+
+ }
+}