Skip to content

Commit 909f8cf

Browse files
authored
Merge pull request #282 from jeffgbutler/table-expressions
Add Support for Sub-Queries in Select Statement "from" Clauses
2 parents aff3640 + e5b9d94 commit 909f8cf

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

46 files changed

+1768
-121
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,8 @@ Kotlin DSL.
3535

3636
- Added a new sort specification that is useful in selects with joins ([#269](https://github.com/mybatis/mybatis-dynamic-sql/pull/269))
3737
- Added the capability to generate a camel cased alias for a column ([#272](https://github.com/mybatis/mybatis-dynamic-sql/issues/272))
38+
- Added sub-query support for "from" clauses in a select statement ([#282](https://github.com/mybatis/mybatis-dynamic-sql/pull/282))
39+
- Added Kotlin DSL updates to support sub-queries in select statements, where clauses, and insert statements ([#282](https://github.com/mybatis/mybatis-dynamic-sql/pull/282))
3840

3941
## Release 1.2.1 - September 29, 2020
4042

Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
/*
2+
* Copyright 2016-2020 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.mybatis.dynamic.sql;
17+
18+
import java.sql.JDBCType;
19+
import java.util.Objects;
20+
import java.util.Optional;
21+
22+
import org.mybatis.dynamic.sql.render.TableAliasCalculator;
23+
24+
/**
25+
* A derived column is a column that is not directly related to a table. This is primarily
26+
* used for supporting sub-queries. The main difference in this class and {@link SqlColumn} is
27+
* that this class does not have a related {@link SqlTable} and therefore ignores any table
28+
* qualifier set in a query. If a table qualifier is required it can be set directly in the
29+
* builder for this class.
30+
*
31+
* @param <T> The Java type that corresponds to this column - not used except for compiler type checking
32+
* for conditions
33+
*/
34+
public class DerivedColumn<T> implements BindableColumn<T> {
35+
private final String name;
36+
private final String tableQualifier;
37+
private final String columnAlias;
38+
private final JDBCType jdbcType;
39+
private final String typeHandler;
40+
41+
protected DerivedColumn(Builder<T> builder) {
42+
this.name = Objects.requireNonNull(builder.name);
43+
this.tableQualifier = builder.tableQualifier;
44+
this.columnAlias = builder.columnAlias;
45+
this.jdbcType = builder.jdbcType;
46+
this.typeHandler = builder.typeHandler;
47+
}
48+
49+
@Override
50+
public Optional<String> alias() {
51+
return Optional.ofNullable(columnAlias);
52+
}
53+
54+
@Override
55+
public Optional<JDBCType> jdbcType() {
56+
return Optional.ofNullable(jdbcType);
57+
}
58+
59+
@Override
60+
public Optional<String> typeHandler() {
61+
return Optional.ofNullable(typeHandler);
62+
}
63+
64+
@Override
65+
public String renderWithTableAlias(TableAliasCalculator tableAliasCalculator) {
66+
return tableQualifier == null ? name : tableQualifier + "." + name; //$NON-NLS-1$
67+
}
68+
69+
@Override
70+
public DerivedColumn<T> as(String columnAlias) {
71+
return new Builder<T>()
72+
.withName(name)
73+
.withColumnAlias(columnAlias)
74+
.withJdbcType(jdbcType)
75+
.withTypeHandler(typeHandler)
76+
.withTableQualifier(tableQualifier)
77+
.build();
78+
}
79+
80+
public static <T> DerivedColumn<T> of(String name) {
81+
return new Builder<T>()
82+
.withName(name)
83+
.build();
84+
}
85+
86+
public static <T> DerivedColumn<T> of(String name, String tableQualifier) {
87+
return new Builder<T>()
88+
.withName(name)
89+
.withTableQualifier(tableQualifier)
90+
.build();
91+
}
92+
93+
public static class Builder<T> {
94+
private String name;
95+
private String tableQualifier;
96+
private String columnAlias;
97+
private JDBCType jdbcType;
98+
private String typeHandler;
99+
100+
public Builder<T> withName(String name) {
101+
this.name = name;
102+
return this;
103+
}
104+
105+
public Builder<T> withTableQualifier(String tableQualifier) {
106+
this.tableQualifier = tableQualifier;
107+
return this;
108+
}
109+
110+
public Builder<T> withColumnAlias(String columnAlias) {
111+
this.columnAlias = columnAlias;
112+
return this;
113+
}
114+
115+
public Builder<T> withJdbcType(JDBCType jdbcType) {
116+
this.jdbcType = jdbcType;
117+
return this;
118+
}
119+
120+
public Builder<T> withTypeHandler(String typeHandler) {
121+
this.typeHandler = typeHandler;
122+
return this;
123+
}
124+
125+
public DerivedColumn<T> build() {
126+
return new DerivedColumn<>(this);
127+
}
128+
}
129+
}

src/main/java/org/mybatis/dynamic/sql/SqlBuilder.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717

1818
import java.util.Arrays;
1919
import java.util.Collection;
20+
import java.util.List;
2021
import java.util.Objects;
2122
import java.util.function.Supplier;
2223

@@ -760,6 +761,11 @@ public InsertSelectDSL.SelectGatherer withColumnList(SqlColumn<?>...columns) {
760761
.withColumnList(columns);
761762
}
762763

764+
public InsertSelectDSL.SelectGatherer withColumnList(List<SqlColumn<?>> columns) {
765+
return InsertSelectDSL.insertInto(table)
766+
.withColumnList(columns);
767+
}
768+
763769
public <T> GeneralInsertDSL.SetClauseFinisher<T> set(SqlColumn<T> column) {
764770
return GeneralInsertDSL.insertInto(table)
765771
.set(column);

src/main/java/org/mybatis/dynamic/sql/SqlColumn.java

Lines changed: 28 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
import java.sql.JDBCType;
1919
import java.util.Objects;
2020
import java.util.Optional;
21+
import java.util.function.BiFunction;
2122

2223
import org.jetbrains.annotations.NotNull;
2324
import org.mybatis.dynamic.sql.render.RenderingStrategy;
@@ -34,6 +35,7 @@ public class SqlColumn<T> implements BindableColumn<T>, SortSpecification {
3435
protected final String typeHandler;
3536
protected final RenderingStrategy renderingStrategy;
3637
protected final ParameterTypeConverter<T, ?> parameterTypeConverter;
38+
protected final BiFunction<TableAliasCalculator, SqlTable, Optional<String>> tableQualifierFunction;
3739

3840
private SqlColumn(Builder<T> builder) {
3941
name = Objects.requireNonNull(builder.name);
@@ -44,6 +46,7 @@ private SqlColumn(Builder<T> builder) {
4446
typeHandler = builder.typeHandler;
4547
renderingStrategy = builder.renderingStrategy;
4648
parameterTypeConverter = builder.parameterTypeConverter;
49+
tableQualifierFunction = Objects.requireNonNull(builder.tableQualifierFunction);
4750
}
4851

4952
public String name() {
@@ -86,6 +89,19 @@ public SqlColumn<T> as(String alias) {
8689
return b.withAlias(alias).build();
8790
}
8891

92+
/**
93+
* Override the calculated table qualifier if there is one. This is useful for sub-queries
94+
* where the calculated table qualifier may not be correct in all cases.
95+
*
96+
* @param tableQualifier the table qualifier to apply to the rendered column name
97+
* @return a new column that will be rendered with the specified table qualifier
98+
*/
99+
public SqlColumn<T> qualifiedWith(String tableQualifier) {
100+
Builder<T> b = copy();
101+
b.withTableQualifierFunction((tac, t) -> Optional.of(tableQualifier));
102+
return b.build();
103+
}
104+
89105
/**
90106
* Set an alias with a camel cased string based on the column name. The can be useful for queries using
91107
* the {@link org.mybatis.dynamic.sql.util.mybatis3.CommonSelectMapper} where the columns are placed into
@@ -114,7 +130,7 @@ public String orderByName() {
114130

115131
@Override
116132
public String renderWithTableAlias(TableAliasCalculator tableAliasCalculator) {
117-
return tableAliasCalculator.aliasForColumn(table)
133+
return tableQualifierFunction.apply(tableAliasCalculator, table)
118134
.map(this::applyTableAlias)
119135
.orElseGet(this::name);
120136
}
@@ -160,8 +176,9 @@ private <S> Builder<S> copy() {
160176
.withDescending(this.isDescending)
161177
.withAlias(this.alias)
162178
.withTypeHandler(this.typeHandler)
163-
.withRenderingStrategy((this.renderingStrategy))
164-
.withParameterTypeConverter((ParameterTypeConverter<S, ?>) this.parameterTypeConverter);
179+
.withRenderingStrategy(this.renderingStrategy)
180+
.withParameterTypeConverter((ParameterTypeConverter<S, ?>) this.parameterTypeConverter)
181+
.withTableQualifierFunction(this.tableQualifierFunction);
165182
}
166183

167184
private String applyTableAlias(String tableAlias) {
@@ -190,6 +207,8 @@ public static class Builder<T> {
190207
protected String typeHandler;
191208
protected RenderingStrategy renderingStrategy;
192209
protected ParameterTypeConverter<T, ?> parameterTypeConverter;
210+
protected BiFunction<TableAliasCalculator, SqlTable, Optional<String>> tableQualifierFunction =
211+
TableAliasCalculator::aliasForColumn;
193212

194213
public Builder<T> withName(String name) {
195214
this.name = name;
@@ -231,6 +250,12 @@ public Builder<T> withParameterTypeConverter(ParameterTypeConverter<T, ?> parame
231250
return this;
232251
}
233252

253+
private Builder<T> withTableQualifierFunction(
254+
BiFunction<TableAliasCalculator, SqlTable, Optional<String>> tableQualifierFunction) {
255+
this.tableQualifierFunction = tableQualifierFunction;
256+
return this;
257+
}
258+
234259
public SqlColumn<T> build() {
235260
return new SqlColumn<>(this);
236261
}

src/main/java/org/mybatis/dynamic/sql/SqlTable.java

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@
2222

2323
import org.jetbrains.annotations.NotNull;
2424

25-
public class SqlTable {
25+
public class SqlTable implements TableExpression {
2626

2727
private final Supplier<String> nameSupplier;
2828

@@ -103,6 +103,11 @@ public <T> SqlColumn<T> column(String name, JDBCType jdbcType, String typeHandle
103103
return column.withTypeHandler(typeHandler);
104104
}
105105

106+
@Override
107+
public <R> R accept(TableExpressionVisitor<R> visitor) {
108+
return visitor.visit(this);
109+
}
110+
106111
public static SqlTable of(String name) {
107112
return new SqlTable(name);
108113
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
/*
2+
* Copyright 2016-2020 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.mybatis.dynamic.sql;
17+
18+
public interface TableExpression {
19+
20+
<R> R accept(TableExpressionVisitor<R> visitor);
21+
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
/*
2+
* Copyright 2016-2020 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.mybatis.dynamic.sql;
17+
18+
import org.mybatis.dynamic.sql.select.SubQuery;
19+
20+
public interface TableExpressionVisitor<R> {
21+
R visit(SqlTable table);
22+
23+
R visit(SubQuery subQuery);
24+
}

src/main/java/org/mybatis/dynamic/sql/insert/InsertSelectDSL.java

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,11 @@ private InsertColumnGatherer(SqlTable table) {
6161
}
6262

6363
public SelectGatherer withColumnList(SqlColumn<?>...columns) {
64-
return new SelectGatherer(table, Arrays.asList(columns));
64+
return withColumnList(Arrays.asList(columns));
65+
}
66+
67+
public SelectGatherer withColumnList(List<SqlColumn<?>> columns) {
68+
return new SelectGatherer(table, columns);
6569
}
6670

6771
public InsertSelectDSL withSelectStatement(Buildable<SelectModel> selectModelBuilder) {

src/main/java/org/mybatis/dynamic/sql/insert/InsertSelectModel.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
import java.util.Objects;
1919
import java.util.Optional;
2020

21+
import org.jetbrains.annotations.NotNull;
2122
import org.mybatis.dynamic.sql.SqlTable;
2223
import org.mybatis.dynamic.sql.insert.render.InsertSelectRenderer;
2324
import org.mybatis.dynamic.sql.insert.render.InsertSelectStatementProvider;
@@ -47,6 +48,7 @@ public Optional<InsertColumnListModel> columnList() {
4748
return Optional.ofNullable(columnList);
4849
}
4950

51+
@NotNull
5052
public InsertSelectStatementProvider render(RenderingStrategy renderingStrategy) {
5153
return InsertSelectRenderer.withInsertSelectModel(this)
5254
.withRenderingStrategy(renderingStrategy)

src/main/java/org/mybatis/dynamic/sql/select/AbstractQueryExpressionDSL.java

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
import java.util.stream.Collectors;
2626

2727
import org.mybatis.dynamic.sql.SqlTable;
28+
import org.mybatis.dynamic.sql.TableExpression;
2829
import org.mybatis.dynamic.sql.select.join.JoinCriterion;
2930
import org.mybatis.dynamic.sql.select.join.JoinModel;
3031
import org.mybatis.dynamic.sql.select.join.JoinSpecification;
@@ -36,13 +37,13 @@ public abstract class AbstractQueryExpressionDSL<T extends AbstractQueryExpressi
3637

3738
private final List<JoinSpecification.Builder> joinSpecificationBuilders = new ArrayList<>();
3839
protected final Map<SqlTable, String> tableAliases = new HashMap<>();
39-
private final SqlTable table;
40+
private final TableExpression table;
4041

41-
protected AbstractQueryExpressionDSL(SqlTable table) {
42+
protected AbstractQueryExpressionDSL(TableExpression table) {
4243
this.table = Objects.requireNonNull(table);
4344
}
4445

45-
public SqlTable table() {
46+
public TableExpression table() {
4647
return table;
4748
}
4849

0 commit comments

Comments
 (0)