Skip to content

Support Subqueries in Join Clauses #293

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 14 commits into from
Nov 20, 2020
13 changes: 12 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,19 @@ This log will detail notable changes to MyBatis Dynamic SQL. Full details are av

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+)

### Release Themes

The major themes of this release include the following:

1. Add support for subqueries in select statements - both in a from clause and a join clause
1. Continue to refine the Kotlin DSL. Most changes to the Kotlin DSL are internal and should be source code
compatible with existing code. There is one breaking change detailed below.
1. Remove deprecated code from prior releases

### Breaking Change for Kotlin

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

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


## Release 1.2.1 - September 29, 2020

Expand Down
4 changes: 4 additions & 0 deletions src/main/java/org/mybatis/dynamic/sql/TableExpression.java
Original file line number Diff line number Diff line change
Expand Up @@ -18,4 +18,8 @@
public interface TableExpression {

<R> R accept(TableExpressionVisitor<R> visitor);

default boolean isSubQuery() {
return false;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,13 @@ public T join(SqlTable joinTable, String tableAlias, JoinCriterion onJoinCriteri
return join(joinTable, onJoinCriterion, andJoinCriteria);
}

public T join(Buildable<SelectModel> subQuery, String tableAlias, JoinCriterion onJoinCriterion,
List<JoinCriterion> andJoinCriteria) {
addJoinSpecificationBuilder(buildSubQuery(subQuery, tableAlias), onJoinCriterion, JoinType.INNER,
andJoinCriteria);
return getThis();
}

public T leftJoin(SqlTable joinTable, JoinCriterion onJoinCriterion,
JoinCriterion...andJoinCriteria) {
addJoinSpecificationBuilder(joinTable, onJoinCriterion, JoinType.LEFT, Arrays.asList(andJoinCriteria));
Expand All @@ -95,6 +102,13 @@ public T leftJoin(SqlTable joinTable, String tableAlias, JoinCriterion onJoinCri
return leftJoin(joinTable, onJoinCriterion, andJoinCriteria);
}

public T leftJoin(Buildable<SelectModel> subQuery, String tableAlias, JoinCriterion onJoinCriterion,
List<JoinCriterion> andJoinCriteria) {
addJoinSpecificationBuilder(buildSubQuery(subQuery, tableAlias), onJoinCriterion, JoinType.LEFT,
andJoinCriteria);
return getThis();
}

public T rightJoin(SqlTable joinTable, JoinCriterion onJoinCriterion,
JoinCriterion...andJoinCriteria) {
addJoinSpecificationBuilder(joinTable, onJoinCriterion, JoinType.RIGHT, Arrays.asList(andJoinCriteria));
Expand All @@ -119,6 +133,13 @@ public T rightJoin(SqlTable joinTable, String tableAlias, JoinCriterion onJoinCr
return rightJoin(joinTable, onJoinCriterion, andJoinCriteria);
}

public T rightJoin(Buildable<SelectModel> subQuery, String tableAlias, JoinCriterion onJoinCriterion,
List<JoinCriterion> andJoinCriteria) {
addJoinSpecificationBuilder(buildSubQuery(subQuery, tableAlias), onJoinCriterion, JoinType.RIGHT,
andJoinCriteria);
return getThis();
}

public T fullJoin(SqlTable joinTable, JoinCriterion onJoinCriterion,
JoinCriterion...andJoinCriteria) {
addJoinSpecificationBuilder(joinTable, onJoinCriterion, JoinType.FULL, Arrays.asList(andJoinCriteria));
Expand All @@ -143,8 +164,15 @@ public T fullJoin(SqlTable joinTable, String tableAlias, JoinCriterion onJoinCri
return fullJoin(joinTable, onJoinCriterion, andJoinCriteria);
}

private void addJoinSpecificationBuilder(SqlTable joinTable, JoinCriterion onJoinCriterion, JoinType joinType,
List<JoinCriterion> andJoinCriteria) {
public T fullJoin(Buildable<SelectModel> subQuery, String tableAlias, JoinCriterion onJoinCriterion,
List<JoinCriterion> andJoinCriteria) {
addJoinSpecificationBuilder(buildSubQuery(subQuery, tableAlias), onJoinCriterion, JoinType.FULL,
andJoinCriteria);
return getThis();
}

private void addJoinSpecificationBuilder(TableExpression joinTable, JoinCriterion onJoinCriterion,
JoinType joinType, List<JoinCriterion> andJoinCriteria) {
joinSpecificationBuilders.add(new JoinSpecification.Builder()
.withJoinTable(joinTable)
.withJoinType(joinType)
Expand All @@ -166,5 +194,18 @@ protected Optional<JoinModel> buildJoinModel() {
.collect(Collectors.toList())));
}

protected static SubQuery buildSubQuery(Buildable<SelectModel> selectModel) {
return new SubQuery.Builder()
.withSelectModel(selectModel.build())
.build();
}

protected static SubQuery buildSubQuery(Buildable<SelectModel> selectModel, String alias) {
return new SubQuery.Builder()
.withSelectModel(selectModel.build())
.withAlias(alias)
.build();
}

protected abstract T getThis();
}
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,10 @@ public JoinSpecificationStarter join(SqlTable joinTable, String tableAlias) {
return join(joinTable);
}

public JoinSpecificationStarter join(Buildable<SelectModel> joinTable, String tableAlias) {
return new JoinSpecificationStarter(buildSubQuery(joinTable, tableAlias), JoinType.INNER);
}

public JoinSpecificationStarter leftJoin(SqlTable joinTable) {
return new JoinSpecificationStarter(joinTable, JoinType.LEFT);
}
Expand All @@ -99,6 +103,10 @@ public JoinSpecificationStarter leftJoin(SqlTable joinTable, String tableAlias)
return leftJoin(joinTable);
}

public JoinSpecificationStarter leftJoin(Buildable<SelectModel> joinTable, String tableAlias) {
return new JoinSpecificationStarter(buildSubQuery(joinTable, tableAlias), JoinType.LEFT);
}

public JoinSpecificationStarter rightJoin(SqlTable joinTable) {
return new JoinSpecificationStarter(joinTable, JoinType.RIGHT);
}
Expand All @@ -108,6 +116,10 @@ public JoinSpecificationStarter rightJoin(SqlTable joinTable, String tableAlias)
return rightJoin(joinTable);
}

public JoinSpecificationStarter rightJoin(Buildable<SelectModel> joinTable, String tableAlias) {
return new JoinSpecificationStarter(buildSubQuery(joinTable, tableAlias), JoinType.RIGHT);
}

public JoinSpecificationStarter fullJoin(SqlTable joinTable) {
return new JoinSpecificationStarter(joinTable, JoinType.FULL);
}
Expand All @@ -117,6 +129,10 @@ public JoinSpecificationStarter fullJoin(SqlTable joinTable, String tableAlias)
return fullJoin(joinTable);
}

public JoinSpecificationStarter fullJoin(Buildable<SelectModel> joinTable, String tableAlias) {
return new JoinSpecificationStarter(buildSubQuery(joinTable, tableAlias), JoinType.FULL);
}

public GroupByFinisher groupBy(BasicColumn...columns) {
return groupBy(Arrays.asList(columns));
}
Expand Down Expand Up @@ -201,19 +217,6 @@ public QueryExpressionDSL<R> from(SqlTable table, String tableAlias) {
return selectDSL.newQueryExpression(this, table, tableAlias);
}

private SubQuery buildSubQuery(Buildable<SelectModel> selectModel) {
return new SubQuery.Builder()
.withSelectModel(selectModel.build())
.build();
}

private SubQuery buildSubQuery(Buildable<SelectModel> selectModel, String alias) {
return new SubQuery.Builder()
.withSelectModel(selectModel.build())
.withAlias(alias)
.build();
}

public static class Builder<R> {
private String connector;
private final List<BasicColumn> selectList = new ArrayList<>();
Expand Down Expand Up @@ -297,10 +300,10 @@ protected WhereModel buildWhereModel() {
}

public class JoinSpecificationStarter {
private final SqlTable joinTable;
private final TableExpression joinTable;
private final JoinType joinType;

public JoinSpecificationStarter(SqlTable joinTable, JoinType joinType) {
public JoinSpecificationStarter(TableExpression joinTable, JoinType joinType) {
this.joinTable = joinTable;
this.joinType = joinType;
}
Expand All @@ -318,7 +321,7 @@ public JoinSpecificationFinisher on(BasicColumn joinColumn, JoinCondition onJoin
public class JoinSpecificationFinisher implements Buildable<R> {
private final JoinSpecification.Builder joinSpecificationBuilder;

public JoinSpecificationFinisher(SqlTable table, BasicColumn joinColumn,
public JoinSpecificationFinisher(TableExpression table, BasicColumn joinColumn,
JoinCondition joinCondition, JoinType joinType) {
JoinCriterion joinCriterion = new JoinCriterion.Builder()
.withConnector("on") //$NON-NLS-1$
Expand All @@ -333,7 +336,7 @@ public JoinSpecificationFinisher(SqlTable table, BasicColumn joinColumn,
addJoinSpecificationBuilder(joinSpecificationBuilder);
}

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

public JoinSpecificationStarter join(Buildable<SelectModel> joinTable, String tableAlias) {
return QueryExpressionDSL.this.join(joinTable, tableAlias);
}

public JoinSpecificationStarter leftJoin(SqlTable joinTable) {
return QueryExpressionDSL.this.leftJoin(joinTable);
}
Expand All @@ -394,6 +401,10 @@ public JoinSpecificationStarter leftJoin(SqlTable joinTable, String tableAlias)
return QueryExpressionDSL.this.leftJoin(joinTable, tableAlias);
}

public JoinSpecificationStarter leftJoin(Buildable<SelectModel> joinTable, String tableAlias) {
return QueryExpressionDSL.this.leftJoin(joinTable, tableAlias);
}

public JoinSpecificationStarter rightJoin(SqlTable joinTable) {
return QueryExpressionDSL.this.rightJoin(joinTable);
}
Expand All @@ -402,6 +413,10 @@ public JoinSpecificationStarter rightJoin(SqlTable joinTable, String tableAlias)
return QueryExpressionDSL.this.rightJoin(joinTable, tableAlias);
}

public JoinSpecificationStarter rightJoin(Buildable<SelectModel> joinTable, String tableAlias) {
return QueryExpressionDSL.this.rightJoin(joinTable, tableAlias);
}

public JoinSpecificationStarter fullJoin(SqlTable joinTable) {
return QueryExpressionDSL.this.fullJoin(joinTable);
}
Expand All @@ -410,6 +425,10 @@ public JoinSpecificationStarter fullJoin(SqlTable joinTable, String tableAlias)
return QueryExpressionDSL.this.fullJoin(joinTable, tableAlias);
}

public JoinSpecificationStarter fullJoin(Buildable<SelectModel> joinTable, String tableAlias) {
return QueryExpressionDSL.this.fullJoin(joinTable, tableAlias);
}

public GroupByFinisher groupBy(BasicColumn...columns) {
return QueryExpressionDSL.this.groupBy(columns);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,12 +48,22 @@ private QueryExpressionModel(Builder builder) {
selectList = Objects.requireNonNull(builder.selectList);
table = Objects.requireNonNull(builder.table);
joinModel = builder.joinModel;
tableAliasCalculator = joinModel().map(jm -> GuaranteedTableAliasCalculator.of(builder.tableAliases))
tableAliasCalculator = joinModel().map(jm -> determineJoinTableAliasCalculator(jm, builder.tableAliases))
.orElseGet(() -> TableAliasCalculator.of(builder.tableAliases));
whereModel = builder.whereModel;
groupByModel = builder.groupByModel;
}

private TableAliasCalculator determineJoinTableAliasCalculator(JoinModel joinModel, Map<SqlTable,
String> tableAliases) {
if (joinModel.containsSubQueries()) {
// if there are subQueries, then force explicit qualifiers
return TableAliasCalculator.of(tableAliases);
} else {
return GuaranteedTableAliasCalculator.of(tableAliases);
}
}

public Optional<String> connector() {
return Optional.ofNullable(connector);
}
Expand Down
5 changes: 5 additions & 0 deletions src/main/java/org/mybatis/dynamic/sql/select/SubQuery.java
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,11 @@ public Optional<String> alias() {
return Optional.ofNullable(alias);
}

@Override
public boolean isSubQuery() {
return true;
}

@Override
public <R> R accept(TableExpressionVisitor<R> visitor) {
return visitor.visit(this);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@
import java.util.function.Function;
import java.util.stream.Stream;

import org.mybatis.dynamic.sql.TableExpression;

public class JoinModel {
private final List<JoinSpecification> joinSpecifications = new ArrayList<>();

Expand All @@ -34,4 +36,10 @@ public <R> Stream<R> mapJoinSpecifications(Function<JoinSpecification, R> mapper
public static JoinModel of(List<JoinSpecification> joinSpecifications) {
return new JoinModel(joinSpecifications);
}

public boolean containsSubQueries() {
return joinSpecifications.stream()
.map(JoinSpecification::table)
.anyMatch(TableExpression::isSubQuery);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ import org.mybatis.dynamic.sql.BindableColumn
import org.mybatis.dynamic.sql.SqlCriterion
import org.mybatis.dynamic.sql.VisitableCondition

typealias CriteriaReceiver = CriteriaCollector.() -> CriteriaCollector
typealias CriteriaReceiver = CriteriaCollector.() -> Unit

@MyBatisDslMarker
class CriteriaCollector {
Expand All @@ -38,13 +38,13 @@ class CriteriaCollector {
fun <T> and(
column: BindableColumn<T>,
condition: VisitableCondition<T>,
collect: CriteriaReceiver
criteriaReceiver: CriteriaReceiver
) =
apply {
criteria.add(
SqlCriterion.withColumn(column)
.withCondition(condition)
.withSubCriteria(collect(CriteriaCollector()).criteria)
.withSubCriteria(CriteriaCollector().apply(criteriaReceiver).criteria)
.withConnector("and")
.build()
)
Expand All @@ -63,13 +63,13 @@ class CriteriaCollector {
fun <T> or(
column: BindableColumn<T>,
condition: VisitableCondition<T>,
collect: CriteriaReceiver
criteriaReceiver: CriteriaReceiver
) =
apply {
criteria.add(
SqlCriterion.withColumn(column)
.withCondition(condition)
.withSubCriteria(collect(CriteriaCollector()).criteria)
.withSubCriteria(CriteriaCollector().apply(criteriaReceiver).criteria)
.withConnector("or")
.build()
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ import org.mybatis.dynamic.sql.BasicColumn
import org.mybatis.dynamic.sql.select.join.JoinCondition
import org.mybatis.dynamic.sql.select.join.JoinCriterion

typealias JoinReceiver = JoinCollector.() -> JoinCollector
typealias JoinReceiver = JoinCollector.() -> Unit

@MyBatisDslMarker
class JoinCollector {
Expand Down
Loading