Skip to content

Commit 1239bce

Browse files
authored
Merge pull request #293 from jeffgbutler/subquery-in-join
Support Subqueries in Join Clauses
2 parents 6b51759 + a86e399 commit 1239bce

24 files changed

+1587
-270
lines changed

CHANGELOG.md

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,19 @@ This log will detail notable changes to MyBatis Dynamic SQL. Full details are av
66

77
GitHub milestone: [https://github.com/mybatis/mybatis-dynamic-sql/issues?q=milestone%3A1.3.0+](https://github.com/mybatis/mybatis-dynamic-sql/issues?q=milestone%3A1.3.0+)
88

9+
### Release Themes
10+
11+
The major themes of this release include the following:
12+
13+
1. Add support for subqueries in select statements - both in a from clause and a join clause
14+
1. Continue to refine the Kotlin DSL. Most changes to the Kotlin DSL are internal and should be source code
15+
compatible with existing code. There is one breaking change detailed below.
16+
1. Remove deprecated code from prior releases
17+
918
### Breaking Change for Kotlin
1019

1120
In this release the Kotlin support for `select` and `count` statements has been refactored. This will not impact code
12-
created by MyBatis generator. It will have an impact on Spring users as well as MyBatis users that coded joins or
21+
created by MyBatis generator. It will have an impact on Spring/Kotlin users as well as MyBatis users that coded joins or
1322
other queries directly in Kotlin. The difference is that the `from` clause has been moved inside the lambda for select
1423
and count statements.
1524

@@ -37,6 +46,8 @@ Kotlin DSL.
3746
- Added the capability to generate a camel cased alias for a column ([#272](https://github.com/mybatis/mybatis-dynamic-sql/issues/272))
3847
- Added sub-query support for "from" clauses in a select statement ([#282](https://github.com/mybatis/mybatis-dynamic-sql/pull/282))
3948
- 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))
49+
- Added sub-query support for "join" clauses in a select statement ([#293](https://github.com/mybatis/mybatis-dynamic-sql/pull/293))
50+
4051

4152
## Release 1.2.1 - September 29, 2020
4253

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,4 +18,8 @@
1818
public interface TableExpression {
1919

2020
<R> R accept(TableExpressionVisitor<R> visitor);
21+
22+
default boolean isSubQuery() {
23+
return false;
24+
}
2125
}

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

Lines changed: 43 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,13 @@ public T join(SqlTable joinTable, String tableAlias, JoinCriterion onJoinCriteri
7171
return join(joinTable, onJoinCriterion, andJoinCriteria);
7272
}
7373

74+
public T join(Buildable<SelectModel> subQuery, String tableAlias, JoinCriterion onJoinCriterion,
75+
List<JoinCriterion> andJoinCriteria) {
76+
addJoinSpecificationBuilder(buildSubQuery(subQuery, tableAlias), onJoinCriterion, JoinType.INNER,
77+
andJoinCriteria);
78+
return getThis();
79+
}
80+
7481
public T leftJoin(SqlTable joinTable, JoinCriterion onJoinCriterion,
7582
JoinCriterion...andJoinCriteria) {
7683
addJoinSpecificationBuilder(joinTable, onJoinCriterion, JoinType.LEFT, Arrays.asList(andJoinCriteria));
@@ -95,6 +102,13 @@ public T leftJoin(SqlTable joinTable, String tableAlias, JoinCriterion onJoinCri
95102
return leftJoin(joinTable, onJoinCriterion, andJoinCriteria);
96103
}
97104

105+
public T leftJoin(Buildable<SelectModel> subQuery, String tableAlias, JoinCriterion onJoinCriterion,
106+
List<JoinCriterion> andJoinCriteria) {
107+
addJoinSpecificationBuilder(buildSubQuery(subQuery, tableAlias), onJoinCriterion, JoinType.LEFT,
108+
andJoinCriteria);
109+
return getThis();
110+
}
111+
98112
public T rightJoin(SqlTable joinTable, JoinCriterion onJoinCriterion,
99113
JoinCriterion...andJoinCriteria) {
100114
addJoinSpecificationBuilder(joinTable, onJoinCriterion, JoinType.RIGHT, Arrays.asList(andJoinCriteria));
@@ -119,6 +133,13 @@ public T rightJoin(SqlTable joinTable, String tableAlias, JoinCriterion onJoinCr
119133
return rightJoin(joinTable, onJoinCriterion, andJoinCriteria);
120134
}
121135

136+
public T rightJoin(Buildable<SelectModel> subQuery, String tableAlias, JoinCriterion onJoinCriterion,
137+
List<JoinCriterion> andJoinCriteria) {
138+
addJoinSpecificationBuilder(buildSubQuery(subQuery, tableAlias), onJoinCriterion, JoinType.RIGHT,
139+
andJoinCriteria);
140+
return getThis();
141+
}
142+
122143
public T fullJoin(SqlTable joinTable, JoinCriterion onJoinCriterion,
123144
JoinCriterion...andJoinCriteria) {
124145
addJoinSpecificationBuilder(joinTable, onJoinCriterion, JoinType.FULL, Arrays.asList(andJoinCriteria));
@@ -143,8 +164,15 @@ public T fullJoin(SqlTable joinTable, String tableAlias, JoinCriterion onJoinCri
143164
return fullJoin(joinTable, onJoinCriterion, andJoinCriteria);
144165
}
145166

146-
private void addJoinSpecificationBuilder(SqlTable joinTable, JoinCriterion onJoinCriterion, JoinType joinType,
147-
List<JoinCriterion> andJoinCriteria) {
167+
public T fullJoin(Buildable<SelectModel> subQuery, String tableAlias, JoinCriterion onJoinCriterion,
168+
List<JoinCriterion> andJoinCriteria) {
169+
addJoinSpecificationBuilder(buildSubQuery(subQuery, tableAlias), onJoinCriterion, JoinType.FULL,
170+
andJoinCriteria);
171+
return getThis();
172+
}
173+
174+
private void addJoinSpecificationBuilder(TableExpression joinTable, JoinCriterion onJoinCriterion,
175+
JoinType joinType, List<JoinCriterion> andJoinCriteria) {
148176
joinSpecificationBuilders.add(new JoinSpecification.Builder()
149177
.withJoinTable(joinTable)
150178
.withJoinType(joinType)
@@ -166,5 +194,18 @@ protected Optional<JoinModel> buildJoinModel() {
166194
.collect(Collectors.toList())));
167195
}
168196

197+
protected static SubQuery buildSubQuery(Buildable<SelectModel> selectModel) {
198+
return new SubQuery.Builder()
199+
.withSelectModel(selectModel.build())
200+
.build();
201+
}
202+
203+
protected static SubQuery buildSubQuery(Buildable<SelectModel> selectModel, String alias) {
204+
return new SubQuery.Builder()
205+
.withSelectModel(selectModel.build())
206+
.withAlias(alias)
207+
.build();
208+
}
209+
169210
protected abstract T getThis();
170211
}

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

Lines changed: 36 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,10 @@ public JoinSpecificationStarter join(SqlTable joinTable, String tableAlias) {
9090
return join(joinTable);
9191
}
9292

93+
public JoinSpecificationStarter join(Buildable<SelectModel> joinTable, String tableAlias) {
94+
return new JoinSpecificationStarter(buildSubQuery(joinTable, tableAlias), JoinType.INNER);
95+
}
96+
9397
public JoinSpecificationStarter leftJoin(SqlTable joinTable) {
9498
return new JoinSpecificationStarter(joinTable, JoinType.LEFT);
9599
}
@@ -99,6 +103,10 @@ public JoinSpecificationStarter leftJoin(SqlTable joinTable, String tableAlias)
99103
return leftJoin(joinTable);
100104
}
101105

106+
public JoinSpecificationStarter leftJoin(Buildable<SelectModel> joinTable, String tableAlias) {
107+
return new JoinSpecificationStarter(buildSubQuery(joinTable, tableAlias), JoinType.LEFT);
108+
}
109+
102110
public JoinSpecificationStarter rightJoin(SqlTable joinTable) {
103111
return new JoinSpecificationStarter(joinTable, JoinType.RIGHT);
104112
}
@@ -108,6 +116,10 @@ public JoinSpecificationStarter rightJoin(SqlTable joinTable, String tableAlias)
108116
return rightJoin(joinTable);
109117
}
110118

119+
public JoinSpecificationStarter rightJoin(Buildable<SelectModel> joinTable, String tableAlias) {
120+
return new JoinSpecificationStarter(buildSubQuery(joinTable, tableAlias), JoinType.RIGHT);
121+
}
122+
111123
public JoinSpecificationStarter fullJoin(SqlTable joinTable) {
112124
return new JoinSpecificationStarter(joinTable, JoinType.FULL);
113125
}
@@ -117,6 +129,10 @@ public JoinSpecificationStarter fullJoin(SqlTable joinTable, String tableAlias)
117129
return fullJoin(joinTable);
118130
}
119131

132+
public JoinSpecificationStarter fullJoin(Buildable<SelectModel> joinTable, String tableAlias) {
133+
return new JoinSpecificationStarter(buildSubQuery(joinTable, tableAlias), JoinType.FULL);
134+
}
135+
120136
public GroupByFinisher groupBy(BasicColumn...columns) {
121137
return groupBy(Arrays.asList(columns));
122138
}
@@ -201,19 +217,6 @@ public QueryExpressionDSL<R> from(SqlTable table, String tableAlias) {
201217
return selectDSL.newQueryExpression(this, table, tableAlias);
202218
}
203219

204-
private SubQuery buildSubQuery(Buildable<SelectModel> selectModel) {
205-
return new SubQuery.Builder()
206-
.withSelectModel(selectModel.build())
207-
.build();
208-
}
209-
210-
private SubQuery buildSubQuery(Buildable<SelectModel> selectModel, String alias) {
211-
return new SubQuery.Builder()
212-
.withSelectModel(selectModel.build())
213-
.withAlias(alias)
214-
.build();
215-
}
216-
217220
public static class Builder<R> {
218221
private String connector;
219222
private final List<BasicColumn> selectList = new ArrayList<>();
@@ -297,10 +300,10 @@ protected WhereModel buildWhereModel() {
297300
}
298301

299302
public class JoinSpecificationStarter {
300-
private final SqlTable joinTable;
303+
private final TableExpression joinTable;
301304
private final JoinType joinType;
302305

303-
public JoinSpecificationStarter(SqlTable joinTable, JoinType joinType) {
306+
public JoinSpecificationStarter(TableExpression joinTable, JoinType joinType) {
304307
this.joinTable = joinTable;
305308
this.joinType = joinType;
306309
}
@@ -318,7 +321,7 @@ public JoinSpecificationFinisher on(BasicColumn joinColumn, JoinCondition onJoin
318321
public class JoinSpecificationFinisher implements Buildable<R> {
319322
private final JoinSpecification.Builder joinSpecificationBuilder;
320323

321-
public JoinSpecificationFinisher(SqlTable table, BasicColumn joinColumn,
324+
public JoinSpecificationFinisher(TableExpression table, BasicColumn joinColumn,
322325
JoinCondition joinCondition, JoinType joinType) {
323326
JoinCriterion joinCriterion = new JoinCriterion.Builder()
324327
.withConnector("on") //$NON-NLS-1$
@@ -333,7 +336,7 @@ public JoinSpecificationFinisher(SqlTable table, BasicColumn joinColumn,
333336
addJoinSpecificationBuilder(joinSpecificationBuilder);
334337
}
335338

336-
public JoinSpecificationFinisher(SqlTable table, BasicColumn joinColumn,
339+
public JoinSpecificationFinisher(TableExpression table, BasicColumn joinColumn,
337340
JoinCondition joinCondition, JoinType joinType, JoinCriterion...andJoinCriteria) {
338341
JoinCriterion onJoinCriterion = new JoinCriterion.Builder()
339342
.withConnector("on") //$NON-NLS-1$
@@ -386,6 +389,10 @@ public JoinSpecificationStarter join(SqlTable joinTable, String tableAlias) {
386389
return QueryExpressionDSL.this.join(joinTable, tableAlias);
387390
}
388391

392+
public JoinSpecificationStarter join(Buildable<SelectModel> joinTable, String tableAlias) {
393+
return QueryExpressionDSL.this.join(joinTable, tableAlias);
394+
}
395+
389396
public JoinSpecificationStarter leftJoin(SqlTable joinTable) {
390397
return QueryExpressionDSL.this.leftJoin(joinTable);
391398
}
@@ -394,6 +401,10 @@ public JoinSpecificationStarter leftJoin(SqlTable joinTable, String tableAlias)
394401
return QueryExpressionDSL.this.leftJoin(joinTable, tableAlias);
395402
}
396403

404+
public JoinSpecificationStarter leftJoin(Buildable<SelectModel> joinTable, String tableAlias) {
405+
return QueryExpressionDSL.this.leftJoin(joinTable, tableAlias);
406+
}
407+
397408
public JoinSpecificationStarter rightJoin(SqlTable joinTable) {
398409
return QueryExpressionDSL.this.rightJoin(joinTable);
399410
}
@@ -402,6 +413,10 @@ public JoinSpecificationStarter rightJoin(SqlTable joinTable, String tableAlias)
402413
return QueryExpressionDSL.this.rightJoin(joinTable, tableAlias);
403414
}
404415

416+
public JoinSpecificationStarter rightJoin(Buildable<SelectModel> joinTable, String tableAlias) {
417+
return QueryExpressionDSL.this.rightJoin(joinTable, tableAlias);
418+
}
419+
405420
public JoinSpecificationStarter fullJoin(SqlTable joinTable) {
406421
return QueryExpressionDSL.this.fullJoin(joinTable);
407422
}
@@ -410,6 +425,10 @@ public JoinSpecificationStarter fullJoin(SqlTable joinTable, String tableAlias)
410425
return QueryExpressionDSL.this.fullJoin(joinTable, tableAlias);
411426
}
412427

428+
public JoinSpecificationStarter fullJoin(Buildable<SelectModel> joinTable, String tableAlias) {
429+
return QueryExpressionDSL.this.fullJoin(joinTable, tableAlias);
430+
}
431+
413432
public GroupByFinisher groupBy(BasicColumn...columns) {
414433
return QueryExpressionDSL.this.groupBy(columns);
415434
}

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

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,12 +48,22 @@ private QueryExpressionModel(Builder builder) {
4848
selectList = Objects.requireNonNull(builder.selectList);
4949
table = Objects.requireNonNull(builder.table);
5050
joinModel = builder.joinModel;
51-
tableAliasCalculator = joinModel().map(jm -> GuaranteedTableAliasCalculator.of(builder.tableAliases))
51+
tableAliasCalculator = joinModel().map(jm -> determineJoinTableAliasCalculator(jm, builder.tableAliases))
5252
.orElseGet(() -> TableAliasCalculator.of(builder.tableAliases));
5353
whereModel = builder.whereModel;
5454
groupByModel = builder.groupByModel;
5555
}
5656

57+
private TableAliasCalculator determineJoinTableAliasCalculator(JoinModel joinModel, Map<SqlTable,
58+
String> tableAliases) {
59+
if (joinModel.containsSubQueries()) {
60+
// if there are subQueries, then force explicit qualifiers
61+
return TableAliasCalculator.of(tableAliases);
62+
} else {
63+
return GuaranteedTableAliasCalculator.of(tableAliases);
64+
}
65+
}
66+
5767
public Optional<String> connector() {
5868
return Optional.ofNullable(connector);
5969
}

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,11 @@ public Optional<String> alias() {
3838
return Optional.ofNullable(alias);
3939
}
4040

41+
@Override
42+
public boolean isSubQuery() {
43+
return true;
44+
}
45+
4146
@Override
4247
public <R> R accept(TableExpressionVisitor<R> visitor) {
4348
return visitor.visit(this);

src/main/java/org/mybatis/dynamic/sql/select/join/JoinModel.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@
2020
import java.util.function.Function;
2121
import java.util.stream.Stream;
2222

23+
import org.mybatis.dynamic.sql.TableExpression;
24+
2325
public class JoinModel {
2426
private final List<JoinSpecification> joinSpecifications = new ArrayList<>();
2527

@@ -34,4 +36,10 @@ public <R> Stream<R> mapJoinSpecifications(Function<JoinSpecification, R> mapper
3436
public static JoinModel of(List<JoinSpecification> joinSpecifications) {
3537
return new JoinModel(joinSpecifications);
3638
}
39+
40+
public boolean containsSubQueries() {
41+
return joinSpecifications.stream()
42+
.map(JoinSpecification::table)
43+
.anyMatch(TableExpression::isSubQuery);
44+
}
3745
}

src/main/kotlin/org/mybatis/dynamic/sql/util/kotlin/CriteriaCollector.kt

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ import org.mybatis.dynamic.sql.BindableColumn
1919
import org.mybatis.dynamic.sql.SqlCriterion
2020
import org.mybatis.dynamic.sql.VisitableCondition
2121

22-
typealias CriteriaReceiver = CriteriaCollector.() -> CriteriaCollector
22+
typealias CriteriaReceiver = CriteriaCollector.() -> Unit
2323

2424
@MyBatisDslMarker
2525
class CriteriaCollector {
@@ -38,13 +38,13 @@ class CriteriaCollector {
3838
fun <T> and(
3939
column: BindableColumn<T>,
4040
condition: VisitableCondition<T>,
41-
collect: CriteriaReceiver
41+
criteriaReceiver: CriteriaReceiver
4242
) =
4343
apply {
4444
criteria.add(
4545
SqlCriterion.withColumn(column)
4646
.withCondition(condition)
47-
.withSubCriteria(collect(CriteriaCollector()).criteria)
47+
.withSubCriteria(CriteriaCollector().apply(criteriaReceiver).criteria)
4848
.withConnector("and")
4949
.build()
5050
)
@@ -63,13 +63,13 @@ class CriteriaCollector {
6363
fun <T> or(
6464
column: BindableColumn<T>,
6565
condition: VisitableCondition<T>,
66-
collect: CriteriaReceiver
66+
criteriaReceiver: CriteriaReceiver
6767
) =
6868
apply {
6969
criteria.add(
7070
SqlCriterion.withColumn(column)
7171
.withCondition(condition)
72-
.withSubCriteria(collect(CriteriaCollector()).criteria)
72+
.withSubCriteria(CriteriaCollector().apply(criteriaReceiver).criteria)
7373
.withConnector("or")
7474
.build()
7575
)

src/main/kotlin/org/mybatis/dynamic/sql/util/kotlin/JoinCollector.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ import org.mybatis.dynamic.sql.BasicColumn
1919
import org.mybatis.dynamic.sql.select.join.JoinCondition
2020
import org.mybatis.dynamic.sql.select.join.JoinCriterion
2121

22-
typealias JoinReceiver = JoinCollector.() -> JoinCollector
22+
typealias JoinReceiver = JoinCollector.() -> Unit
2323

2424
@MyBatisDslMarker
2525
class JoinCollector {

0 commit comments

Comments
 (0)