Support for in line queries for tables.
`InlineQuery` can be used wherever a `Table` was used up to now.
```
Table one = ...;
Select select = Select.builder()
.select(one.column("id"), employee.column("name"))
.from(one)
.build();
InlineQuery inline = InlineQuery.create(select, "inline");
Select select = Select.builder()
.select(inline.column("id"), inline.column("name"))
.from(inline)
.build();
```
Join and From renderer now use the same FromTableVisitor.
Also the SelectListVisitor reuses now the ExpressionVisitor.
Fixes #1003
Original pull request: #1018.
This commit is contained in:
committed by
Mark Paluch
parent
0d03b5928b
commit
f6eacf327c
@@ -121,7 +121,7 @@ class QueryMapper {
|
||||
|
||||
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));
|
||||
|
||||
|
||||
@@ -23,11 +23,11 @@ import java.util.Set;
|
||||
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 @@ public class PostgresDialect extends AbstractDialect {
|
||||
@Override
|
||||
public String getLock(LockOptions lockOptions) {
|
||||
|
||||
List<Table> tables = lockOptions.getFrom().getTables();
|
||||
List<TableLike> tables = lockOptions.getFrom().getTables();
|
||||
if (tables.isEmpty()) {
|
||||
return "";
|
||||
}
|
||||
|
||||
@@ -18,13 +18,7 @@ package org.springframework.data.relational.core.sql;
|
||||
/**
|
||||
* {@link Segment} to select all columns from a {@link Table}.
|
||||
* <p/>
|
||||
* * Renders to: {@code
|
||||
*
|
||||
<table>
|
||||
* .*} as in {@code SELECT
|
||||
*
|
||||
<table>
|
||||
* .* FROM …}.
|
||||
* Renders to: {@code <table>.*} as in {@code SELECT <table>.* FROM …}.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
* @since 1.1
|
||||
@@ -32,9 +26,9 @@ package org.springframework.data.relational.core.sql;
|
||||
*/
|
||||
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 class AsteriskFromTable extends AbstractSegment implements Expression {
|
||||
/**
|
||||
* @return the associated {@link Table}.
|
||||
*/
|
||||
public Table getTable() {
|
||||
public TableLike getTable() {
|
||||
return table;
|
||||
}
|
||||
|
||||
|
||||
@@ -30,9 +30,9 @@ import org.springframework.util.Assert;
|
||||
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 class Column extends AbstractSegment implements Expression, Named {
|
||||
* {@link Table}.
|
||||
*/
|
||||
@Nullable
|
||||
public Table getTable() {
|
||||
public TableLike getTable() {
|
||||
return table;
|
||||
}
|
||||
|
||||
@@ -370,12 +370,12 @@ public class Column extends AbstractSegment implements Expression, Named {
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -42,7 +42,7 @@ class DefaultSelect implements Select {
|
||||
private final List<OrderByField> orderBy;
|
||||
private final @Nullable LockMode lockMode;
|
||||
|
||||
DefaultSelect(boolean distinct, List<Expression> selectList, List<Table> from, long limit, long offset,
|
||||
DefaultSelect(boolean distinct, List<Expression> selectList, List<TableLike> from, long limit, long offset,
|
||||
List<Join> joins, @Nullable Condition where, List<OrderByField> orderBy, @Nullable LockMode lockMode) {
|
||||
|
||||
this.distinct = distinct;
|
||||
|
||||
@@ -38,7 +38,7 @@ class DefaultSelectBuilder implements SelectBuilder, SelectAndFrom, SelectFromAn
|
||||
|
||||
private boolean distinct = false;
|
||||
private List<Expression> selectList = new ArrayList<>();
|
||||
private List<Table> from = new ArrayList<>();
|
||||
private List<TableLike> from = new ArrayList<>();
|
||||
private long limit = -1;
|
||||
private long offset = -1;
|
||||
private List<Join> joins = new ArrayList<>();
|
||||
@@ -107,7 +107,7 @@ class DefaultSelectBuilder implements SelectBuilder, SelectAndFrom, SelectFromAn
|
||||
* @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 @@ class DefaultSelectBuilder implements SelectBuilder, SelectAndFrom, SelectFromAn
|
||||
* @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 @@ class DefaultSelectBuilder implements SelectBuilder, SelectAndFrom, SelectFromAn
|
||||
* @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 @@ class DefaultSelectBuilder implements SelectBuilder, SelectAndFrom, SelectFromAn
|
||||
* @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 @@ class DefaultSelectBuilder implements SelectBuilder, SelectAndFrom, SelectFromAn
|
||||
* @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 @@ class DefaultSelectBuilder implements SelectBuilder, SelectAndFrom, SelectFromAn
|
||||
*/
|
||||
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 @@ class DefaultSelectBuilder implements SelectBuilder, SelectAndFrom, SelectFromAn
|
||||
* @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 @@ class DefaultSelectBuilder implements SelectBuilder, SelectAndFrom, SelectFromAn
|
||||
* @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);
|
||||
}
|
||||
|
||||
@@ -29,20 +29,20 @@ import org.springframework.util.StringUtils;
|
||||
*/
|
||||
public class From extends AbstractSegment {
|
||||
|
||||
private final List<Table> tables;
|
||||
private final List<TableLike> tables;
|
||||
|
||||
From(Table... tables) {
|
||||
From(TableLike... tables) {
|
||||
this(Arrays.asList(tables));
|
||||
}
|
||||
|
||||
From(List<Table> tables) {
|
||||
From(List<TableLike> tables) {
|
||||
|
||||
super(tables.toArray(new Table[] {}));
|
||||
super(tables.toArray(new TableLike[] {}));
|
||||
|
||||
this.tables = Collections.unmodifiableList(tables);
|
||||
}
|
||||
|
||||
public List<Table> getTables() {
|
||||
public List<TableLike> getTables() {
|
||||
return this.tables;
|
||||
}
|
||||
|
||||
|
||||
@@ -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.
|
||||
* <p/>
|
||||
* Renders to: {@code (<SELECT>) AS <ALIAS>} in a from or join clause, and to {@code <ALIAS>} when used in an
|
||||
* expression.
|
||||
* <p/>
|
||||
* 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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -29,10 +29,10 @@ package org.springframework.data.relational.core.sql;
|
||||
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 class Join extends AbstractSegment {
|
||||
/**
|
||||
* @return the joined {@link Table}.
|
||||
*/
|
||||
public Table getJoinTable() {
|
||||
public TableLike getJoinTable() {
|
||||
return joinTable;
|
||||
}
|
||||
|
||||
|
||||
@@ -134,7 +134,7 @@ public interface SelectBuilder {
|
||||
* @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 @@ public interface SelectBuilder {
|
||||
* @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 @@ public interface SelectBuilder {
|
||||
* @see SQL#table(String)
|
||||
*/
|
||||
@Override
|
||||
SelectFromAndJoin from(Collection<? extends Table> tables);
|
||||
SelectFromAndJoin from(Collection<? extends TableLike> tables);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -186,7 +186,7 @@ public interface SelectBuilder {
|
||||
* @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 @@ public interface SelectBuilder {
|
||||
* @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 @@ public interface SelectBuilder {
|
||||
* @see From
|
||||
* @see SQL#table(String)
|
||||
*/
|
||||
SelectFromAndOrderBy from(Collection<? extends Table> tables);
|
||||
SelectFromAndOrderBy from(Collection<? extends TableLike> tables);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -229,13 +229,13 @@ public interface SelectBuilder {
|
||||
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 @@ public interface SelectBuilder {
|
||||
* @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 @@ public interface SelectBuilder {
|
||||
* @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 @@ public interface SelectBuilder {
|
||||
* @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 @@ public interface SelectBuilder {
|
||||
* @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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -34,10 +34,10 @@ class SelectValidator extends AbstractImportValidator {
|
||||
private final Stack<Select> selects = new Stack<>();
|
||||
|
||||
private int selectFieldCount;
|
||||
private Set<Table> requiredBySelect = new HashSet<>();
|
||||
private Set<Table> requiredByOrderBy = new HashSet<>();
|
||||
private Set<TableLike> requiredBySelect = new HashSet<>();
|
||||
private Set<TableLike> requiredByOrderBy = new HashSet<>();
|
||||
|
||||
private Set<Table> join = new HashSet<>();
|
||||
private Set<TableLike> join = new HashSet<>();
|
||||
|
||||
/**
|
||||
* Validates a {@link Select} statement.
|
||||
@@ -57,7 +57,7 @@ class SelectValidator extends AbstractImportValidator {
|
||||
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 @@ class SelectValidator extends AbstractImportValidator {
|
||||
}
|
||||
}
|
||||
|
||||
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 @@ class SelectValidator extends AbstractImportValidator {
|
||||
|
||||
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 @@ class SelectValidator extends AbstractImportValidator {
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -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}.
|
||||
* <p/>
|
||||
* Renders to: {@code <name>} or {@code <name> AS <name>}.
|
||||
@@ -31,7 +26,7 @@ import org.springframework.util.Assert;
|
||||
* @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 class Table extends AbstractSegment {
|
||||
return new AliasedTable(name, alias);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@link Column} associated with this {@link Table}.
|
||||
* <p/>
|
||||
* Note: This {@link Table} does not track column creation and there is no possibility to enumerate all
|
||||
* {@link Column}s that were created for this table.
|
||||
*
|
||||
* @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}.
|
||||
* <p/>
|
||||
* Note: This {@link Table} does not track column creation and there is no possibility to enumerate all
|
||||
* {@link Column}s that were created for this table.
|
||||
*
|
||||
* @param 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}.
|
||||
* <p/>
|
||||
* Note: This {@link Table} does not track column creation and there is no possibility to enumerate all
|
||||
* {@link Column}s that were created for this table.
|
||||
*
|
||||
* @param names column names, must not be {@literal null} or empty.
|
||||
* @return a new {@link List} of {@link Column}s associated with this {@link Table}.
|
||||
*/
|
||||
public List<Column> columns(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}.
|
||||
* <p/>
|
||||
* Note: This {@link Table} does not track column creation and there is no possibility to enumerate all
|
||||
* {@link Column}s that were created for this table.
|
||||
*
|
||||
* @param names column names, must not be {@literal null} or empty.
|
||||
* @return a new {@link List} of {@link Column}s associated with this {@link Table}.
|
||||
* @since 2.0
|
||||
*/
|
||||
public List<Column> columns(SqlIdentifier... names) {
|
||||
|
||||
Assert.notNull(names, "Names must not be null");
|
||||
|
||||
List<Column> 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}.
|
||||
* <p/>
|
||||
* Note: This {@link Table} does not track column creation and there is no possibility to enumerate all
|
||||
* {@link Column}s that were created for this table.
|
||||
*
|
||||
* @param names column names, must not be {@literal null} or empty.
|
||||
* @return a new {@link List} of {@link Column}s associated with this {@link Table}.
|
||||
*/
|
||||
public List<Column> columns(Collection<String> names) {
|
||||
|
||||
Assert.notNull(names, "Names must not be null");
|
||||
|
||||
List<Column> columns = new ArrayList<>();
|
||||
for (String name : names) {
|
||||
columns.add(column(name));
|
||||
}
|
||||
|
||||
return columns;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a {@link AsteriskFromTable} maker selecting all columns from this {@link Table} (e.g. {@code SELECT
|
||||
*
|
||||
<table>
|
||||
* .*}.
|
||||
*
|
||||
* @return the select all marker for this {@link Table}.
|
||||
*/
|
||||
public AsteriskFromTable asterisk() {
|
||||
return new AsteriskFromTable(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the table name.
|
||||
*/
|
||||
|
||||
@@ -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}.
|
||||
* <p/>
|
||||
* Note: This {@link Table} does not track column creation and there is no possibility to enumerate all
|
||||
* {@link Column}s that were created for this table.
|
||||
*
|
||||
* @param 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}.
|
||||
* <p/>
|
||||
* Note: This {@link Table} does not track column creation and there is no possibility to enumerate all
|
||||
* {@link Column}s that were created for this table.
|
||||
*
|
||||
* @param 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}.
|
||||
* <p/>
|
||||
* Note: This {@link Table} does not track column creation and there is no possibility to enumerate all
|
||||
* {@link Column}s that were created for this table.
|
||||
*
|
||||
* @param names column names, must not be {@literal null} or empty.
|
||||
* @return a new {@link List} of {@link Column}s associated with this {@link Table}.
|
||||
*/
|
||||
default List<Column> 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}.
|
||||
* <p/>
|
||||
* Note: This {@link Table} does not track column creation and there is no possibility to enumerate all
|
||||
* {@link Column}s that were created for this table.
|
||||
*
|
||||
* @param names column names, must not be {@literal null} or empty.
|
||||
* @return a new {@link List} of {@link Column}s associated with this {@link Table}.
|
||||
* @since 2.0
|
||||
*/
|
||||
default List<Column> columns(SqlIdentifier... names) {
|
||||
|
||||
Assert.notNull(names, "Names must not be null");
|
||||
|
||||
List<Column> 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}.
|
||||
* <p/>
|
||||
* Note: This {@link Table} does not track column creation and there is no possibility to enumerate all
|
||||
* {@link Column}s that were created for this table.
|
||||
*
|
||||
* @param names column names, must not be {@literal null} or empty.
|
||||
* @return a new {@link List} of {@link Column}s associated with this {@link Table}.
|
||||
*/
|
||||
default List<Column> columns(Collection<String> names) {
|
||||
|
||||
Assert.notNull(names, "Names must not be null");
|
||||
|
||||
List<Column> columns = new ArrayList<>();
|
||||
for (String name : names) {
|
||||
columns.add(column(name));
|
||||
}
|
||||
|
||||
return columns;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a {@link AsteriskFromTable} maker selecting all columns from this {@link Table} (e.g. {@code SELECT
|
||||
*
|
||||
<table>
|
||||
* .*}.
|
||||
*
|
||||
* @return the select all marker for this {@link Table}.
|
||||
*/
|
||||
default AsteriskFromTable asterisk() {
|
||||
return new AsteriskFromTable(this);
|
||||
}
|
||||
|
||||
SqlIdentifier getName();
|
||||
|
||||
SqlIdentifier getReferenceName();
|
||||
}
|
||||
@@ -17,12 +17,14 @@ package org.springframework.data.relational.core.sql.render;
|
||||
|
||||
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
|
||||
* <table>
|
||||
* .<column>} or {@literal <column>}.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
* @since 1.1
|
||||
@@ -65,8 +67,8 @@ class ColumnVisitor extends TypedSubtreeVisitor<Column> {
|
||||
@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);
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
*/
|
||||
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;
|
||||
@@ -24,6 +25,7 @@ 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.
|
||||
@@ -37,12 +39,33 @@ import org.springframework.lang.Nullable;
|
||||
class ExpressionVisitor extends TypedSubtreeVisitor<Expression> 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;
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -70,7 +93,8 @@ class ExpressionVisitor extends TypedSubtreeVisitor<Expression> implements PartR
|
||||
|
||||
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) {
|
||||
@@ -78,7 +102,10 @@ class ExpressionVisitor extends TypedSubtreeVisitor<Expression> implements PartR
|
||||
} else {
|
||||
value = segment.toString();
|
||||
}
|
||||
} else { // works for Literal and SimpleExpression and possibly more
|
||||
} else if (segment instanceof AsteriskFromTable) {
|
||||
value = NameRenderer.render(context, ((AsteriskFromTable) segment).getTable()) + ".*";
|
||||
} else {
|
||||
// works for literals and just and possibly more
|
||||
value = segment.toString();
|
||||
}
|
||||
|
||||
@@ -93,6 +120,7 @@ class ExpressionVisitor extends TypedSubtreeVisitor<Expression> implements PartR
|
||||
Delegation enterNested(Visitable segment) {
|
||||
|
||||
if (segment instanceof Condition) {
|
||||
|
||||
ConditionVisitor visitor = new ConditionVisitor(context);
|
||||
partRenderer = visitor;
|
||||
return Delegation.delegateTo(visitor);
|
||||
@@ -124,4 +152,20 @@ class ExpressionVisitor extends TypedSubtreeVisitor<Expression> implements PartR
|
||||
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 <alias>}.
|
||||
*/
|
||||
USE
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,20 +17,28 @@ package org.springframework.data.relational.core.sql.render;
|
||||
|
||||
import org.springframework.data.relational.core.sql.Aliased;
|
||||
import org.springframework.data.relational.core.sql.From;
|
||||
import org.springframework.data.relational.core.sql.Table;
|
||||
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<Table> {
|
||||
class FromTableVisitor extends TypedSubtreeVisitor<TableLike> {
|
||||
|
||||
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<Table> {
|
||||
* @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 @@ class FromTableVisitor extends TypedSubtreeVisitor<Table> {
|
||||
|
||||
parent.onRendered(builder);
|
||||
|
||||
return super.enterMatched(segment);
|
||||
return super.leaveMatched(segment);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 @@ import org.springframework.data.relational.core.sql.Visitable;
|
||||
*/
|
||||
class JoinVisitor extends TypedSubtreeVisitor<Join> {
|
||||
|
||||
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 @@ class JoinVisitor extends TypedSubtreeVisitor<Join> {
|
||||
@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 @@ class JoinVisitor extends TypedSubtreeVisitor<Join> {
|
||||
*/
|
||||
@Override
|
||||
Delegation leaveMatched(Join segment) {
|
||||
|
||||
parent.onRendered(joinClause);
|
||||
return super.leaveMatched(segment);
|
||||
}
|
||||
|
||||
@@ -21,34 +21,28 @@ import org.springframework.data.relational.core.sql.IdentifierProcessing;
|
||||
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 @@ class NameRenderer {
|
||||
|
||||
/**
|
||||
* 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 @@ class NameRenderer {
|
||||
|
||||
/**
|
||||
* 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 @@ class NameRenderer {
|
||||
|
||||
/**
|
||||
* 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 @@ class NameRenderer {
|
||||
/**
|
||||
* 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 @@ class NameRenderer {
|
||||
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());
|
||||
|
||||
@@ -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 abstract class NamingStrategies {
|
||||
}
|
||||
|
||||
enum AsIs implements RenderNamingStrategy {
|
||||
INSTANCE;
|
||||
INSTANCE
|
||||
}
|
||||
|
||||
static class DelegatingRenderNamingStrategy implements RenderNamingStrategy {
|
||||
@@ -135,12 +136,12 @@ public abstract class NamingStrategies {
|
||||
}
|
||||
|
||||
@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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,6 +20,7 @@ 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.data.relational.core.sql.render.NamingStrategies.DelegatingRenderNamingStrategy;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
@@ -27,6 +28,7 @@ import org.springframework.util.Assert;
|
||||
* Naming strategy for SQL rendering.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
* @author Jens Schauder
|
||||
* @see NamingStrategies
|
||||
* @since 1.1
|
||||
*/
|
||||
@@ -55,24 +57,24 @@ public interface RenderNamingStrategy {
|
||||
}
|
||||
|
||||
/**
|
||||
* 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();
|
||||
}
|
||||
|
||||
|
||||
@@ -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<SelectList> 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 @@ class SelectListVisitor extends TypedSubtreeVisitor<SelectList> implements PartR
|
||||
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 @@ class SelectListVisitor extends TypedSubtreeVisitor<SelectList> implements PartR
|
||||
@Override
|
||||
Delegation leaveNested(Visitable segment) {
|
||||
|
||||
if (segment instanceof Table) {
|
||||
builder.append(NameRenderer.reference(context, (Table) segment)).append('.');
|
||||
if (segment instanceof Expression) {
|
||||
|
||||
builder.append(expressionVisitor.getRenderedPart());
|
||||
requiresComma = true;
|
||||
}
|
||||
|
||||
if (segment instanceof SimpleFunction) {
|
||||
|
||||
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("*");
|
||||
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 @@ class SelectListVisitor extends TypedSubtreeVisitor<SelectList> implements PartR
|
||||
public CharSequence getRenderedPart() {
|
||||
return builder;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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<Fixture> 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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<Fixture> 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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<Fixture> 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
@@ -18,7 +18,6 @@ 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.dialect.PostgresDialect;
|
||||
import org.springframework.data.relational.core.dialect.RenderContextFactory;
|
||||
import org.springframework.data.relational.core.sql.*;
|
||||
@@ -43,6 +42,18 @@ public class SelectRendererUnitTests {
|
||||
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() {
|
||||
|
||||
@@ -164,6 +175,55 @@ public class SelectRendererUnitTests {
|
||||
+ "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() {
|
||||
|
||||
|
||||
@@ -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<String> events = new ArrayList<>();
|
||||
|
||||
@Test // GH-1003
|
||||
void enterAndLeavesSingleSegment() {
|
||||
|
||||
final TypedSubtreeVisitor<TestSegment> 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<TestSegment> 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<TestSegment> 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<TestSegment> 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<TestSegment> 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<TestSegment> {
|
||||
|
||||
final String prefix;
|
||||
Function<TestSegment, Delegation> enterMatchedDelegation;
|
||||
Function<Visitable, Delegation> 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<TestSegment, Delegation> 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<Visitable, Delegation> delegation) {
|
||||
enterNestedDelegation = delegation;
|
||||
}
|
||||
|
||||
@Override
|
||||
Delegation leaveNested(Visitable segment) {
|
||||
|
||||
events.add(prefix + "leave nested " + segment);
|
||||
return super.leaveNested(segment);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user