diff --git a/CHANGELOG.md b/CHANGELOG.md index 8e622653f..0d2df732a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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. @@ -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 diff --git a/src/main/java/org/mybatis/dynamic/sql/TableExpression.java b/src/main/java/org/mybatis/dynamic/sql/TableExpression.java index e0722894a..87c58023d 100644 --- a/src/main/java/org/mybatis/dynamic/sql/TableExpression.java +++ b/src/main/java/org/mybatis/dynamic/sql/TableExpression.java @@ -18,4 +18,8 @@ public interface TableExpression { R accept(TableExpressionVisitor visitor); + + default boolean isSubQuery() { + return false; + } } diff --git a/src/main/java/org/mybatis/dynamic/sql/select/AbstractQueryExpressionDSL.java b/src/main/java/org/mybatis/dynamic/sql/select/AbstractQueryExpressionDSL.java index 33287616e..5b63e6fa2 100644 --- a/src/main/java/org/mybatis/dynamic/sql/select/AbstractQueryExpressionDSL.java +++ b/src/main/java/org/mybatis/dynamic/sql/select/AbstractQueryExpressionDSL.java @@ -71,6 +71,13 @@ public T join(SqlTable joinTable, String tableAlias, JoinCriterion onJoinCriteri return join(joinTable, onJoinCriterion, andJoinCriteria); } + public T join(Buildable subQuery, String tableAlias, JoinCriterion onJoinCriterion, + List 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)); @@ -95,6 +102,13 @@ public T leftJoin(SqlTable joinTable, String tableAlias, JoinCriterion onJoinCri return leftJoin(joinTable, onJoinCriterion, andJoinCriteria); } + public T leftJoin(Buildable subQuery, String tableAlias, JoinCriterion onJoinCriterion, + List 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)); @@ -119,6 +133,13 @@ public T rightJoin(SqlTable joinTable, String tableAlias, JoinCriterion onJoinCr return rightJoin(joinTable, onJoinCriterion, andJoinCriteria); } + public T rightJoin(Buildable subQuery, String tableAlias, JoinCriterion onJoinCriterion, + List 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)); @@ -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 andJoinCriteria) { + public T fullJoin(Buildable subQuery, String tableAlias, JoinCriterion onJoinCriterion, + List andJoinCriteria) { + addJoinSpecificationBuilder(buildSubQuery(subQuery, tableAlias), onJoinCriterion, JoinType.FULL, + andJoinCriteria); + return getThis(); + } + + private void addJoinSpecificationBuilder(TableExpression joinTable, JoinCriterion onJoinCriterion, + JoinType joinType, List andJoinCriteria) { joinSpecificationBuilders.add(new JoinSpecification.Builder() .withJoinTable(joinTable) .withJoinType(joinType) @@ -166,5 +194,18 @@ protected Optional buildJoinModel() { .collect(Collectors.toList()))); } + protected static SubQuery buildSubQuery(Buildable selectModel) { + return new SubQuery.Builder() + .withSelectModel(selectModel.build()) + .build(); + } + + protected static SubQuery buildSubQuery(Buildable selectModel, String alias) { + return new SubQuery.Builder() + .withSelectModel(selectModel.build()) + .withAlias(alias) + .build(); + } + protected abstract T getThis(); } diff --git a/src/main/java/org/mybatis/dynamic/sql/select/QueryExpressionDSL.java b/src/main/java/org/mybatis/dynamic/sql/select/QueryExpressionDSL.java index 16204f999..c47b5f51d 100644 --- a/src/main/java/org/mybatis/dynamic/sql/select/QueryExpressionDSL.java +++ b/src/main/java/org/mybatis/dynamic/sql/select/QueryExpressionDSL.java @@ -90,6 +90,10 @@ public JoinSpecificationStarter join(SqlTable joinTable, String tableAlias) { return join(joinTable); } + public JoinSpecificationStarter join(Buildable joinTable, String tableAlias) { + return new JoinSpecificationStarter(buildSubQuery(joinTable, tableAlias), JoinType.INNER); + } + public JoinSpecificationStarter leftJoin(SqlTable joinTable) { return new JoinSpecificationStarter(joinTable, JoinType.LEFT); } @@ -99,6 +103,10 @@ public JoinSpecificationStarter leftJoin(SqlTable joinTable, String tableAlias) return leftJoin(joinTable); } + public JoinSpecificationStarter leftJoin(Buildable joinTable, String tableAlias) { + return new JoinSpecificationStarter(buildSubQuery(joinTable, tableAlias), JoinType.LEFT); + } + public JoinSpecificationStarter rightJoin(SqlTable joinTable) { return new JoinSpecificationStarter(joinTable, JoinType.RIGHT); } @@ -108,6 +116,10 @@ public JoinSpecificationStarter rightJoin(SqlTable joinTable, String tableAlias) return rightJoin(joinTable); } + public JoinSpecificationStarter rightJoin(Buildable joinTable, String tableAlias) { + return new JoinSpecificationStarter(buildSubQuery(joinTable, tableAlias), JoinType.RIGHT); + } + public JoinSpecificationStarter fullJoin(SqlTable joinTable) { return new JoinSpecificationStarter(joinTable, JoinType.FULL); } @@ -117,6 +129,10 @@ public JoinSpecificationStarter fullJoin(SqlTable joinTable, String tableAlias) return fullJoin(joinTable); } + public JoinSpecificationStarter fullJoin(Buildable joinTable, String tableAlias) { + return new JoinSpecificationStarter(buildSubQuery(joinTable, tableAlias), JoinType.FULL); + } + public GroupByFinisher groupBy(BasicColumn...columns) { return groupBy(Arrays.asList(columns)); } @@ -201,19 +217,6 @@ public QueryExpressionDSL from(SqlTable table, String tableAlias) { return selectDSL.newQueryExpression(this, table, tableAlias); } - private SubQuery buildSubQuery(Buildable selectModel) { - return new SubQuery.Builder() - .withSelectModel(selectModel.build()) - .build(); - } - - private SubQuery buildSubQuery(Buildable selectModel, String alias) { - return new SubQuery.Builder() - .withSelectModel(selectModel.build()) - .withAlias(alias) - .build(); - } - public static class Builder { private String connector; private final List selectList = new ArrayList<>(); @@ -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; } @@ -318,7 +321,7 @@ public JoinSpecificationFinisher on(BasicColumn joinColumn, JoinCondition onJoin public class JoinSpecificationFinisher implements Buildable { 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$ @@ -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$ @@ -386,6 +389,10 @@ public JoinSpecificationStarter join(SqlTable joinTable, String tableAlias) { return QueryExpressionDSL.this.join(joinTable, tableAlias); } + public JoinSpecificationStarter join(Buildable joinTable, String tableAlias) { + return QueryExpressionDSL.this.join(joinTable, tableAlias); + } + public JoinSpecificationStarter leftJoin(SqlTable joinTable) { return QueryExpressionDSL.this.leftJoin(joinTable); } @@ -394,6 +401,10 @@ public JoinSpecificationStarter leftJoin(SqlTable joinTable, String tableAlias) return QueryExpressionDSL.this.leftJoin(joinTable, tableAlias); } + public JoinSpecificationStarter leftJoin(Buildable joinTable, String tableAlias) { + return QueryExpressionDSL.this.leftJoin(joinTable, tableAlias); + } + public JoinSpecificationStarter rightJoin(SqlTable joinTable) { return QueryExpressionDSL.this.rightJoin(joinTable); } @@ -402,6 +413,10 @@ public JoinSpecificationStarter rightJoin(SqlTable joinTable, String tableAlias) return QueryExpressionDSL.this.rightJoin(joinTable, tableAlias); } + public JoinSpecificationStarter rightJoin(Buildable joinTable, String tableAlias) { + return QueryExpressionDSL.this.rightJoin(joinTable, tableAlias); + } + public JoinSpecificationStarter fullJoin(SqlTable joinTable) { return QueryExpressionDSL.this.fullJoin(joinTable); } @@ -410,6 +425,10 @@ public JoinSpecificationStarter fullJoin(SqlTable joinTable, String tableAlias) return QueryExpressionDSL.this.fullJoin(joinTable, tableAlias); } + public JoinSpecificationStarter fullJoin(Buildable joinTable, String tableAlias) { + return QueryExpressionDSL.this.fullJoin(joinTable, tableAlias); + } + public GroupByFinisher groupBy(BasicColumn...columns) { return QueryExpressionDSL.this.groupBy(columns); } diff --git a/src/main/java/org/mybatis/dynamic/sql/select/QueryExpressionModel.java b/src/main/java/org/mybatis/dynamic/sql/select/QueryExpressionModel.java index 6142792ec..4e0b14ba7 100644 --- a/src/main/java/org/mybatis/dynamic/sql/select/QueryExpressionModel.java +++ b/src/main/java/org/mybatis/dynamic/sql/select/QueryExpressionModel.java @@ -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 tableAliases) { + if (joinModel.containsSubQueries()) { + // if there are subQueries, then force explicit qualifiers + return TableAliasCalculator.of(tableAliases); + } else { + return GuaranteedTableAliasCalculator.of(tableAliases); + } + } + public Optional connector() { return Optional.ofNullable(connector); } diff --git a/src/main/java/org/mybatis/dynamic/sql/select/SubQuery.java b/src/main/java/org/mybatis/dynamic/sql/select/SubQuery.java index d22185c72..a2373d9b7 100644 --- a/src/main/java/org/mybatis/dynamic/sql/select/SubQuery.java +++ b/src/main/java/org/mybatis/dynamic/sql/select/SubQuery.java @@ -38,6 +38,11 @@ public Optional alias() { return Optional.ofNullable(alias); } + @Override + public boolean isSubQuery() { + return true; + } + @Override public R accept(TableExpressionVisitor visitor) { return visitor.visit(this); diff --git a/src/main/java/org/mybatis/dynamic/sql/select/join/JoinModel.java b/src/main/java/org/mybatis/dynamic/sql/select/join/JoinModel.java index 8baa1038a..dc5174e22 100644 --- a/src/main/java/org/mybatis/dynamic/sql/select/join/JoinModel.java +++ b/src/main/java/org/mybatis/dynamic/sql/select/join/JoinModel.java @@ -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 joinSpecifications = new ArrayList<>(); @@ -34,4 +36,10 @@ public Stream mapJoinSpecifications(Function mapper public static JoinModel of(List joinSpecifications) { return new JoinModel(joinSpecifications); } + + public boolean containsSubQueries() { + return joinSpecifications.stream() + .map(JoinSpecification::table) + .anyMatch(TableExpression::isSubQuery); + } } diff --git a/src/main/kotlin/org/mybatis/dynamic/sql/util/kotlin/CriteriaCollector.kt b/src/main/kotlin/org/mybatis/dynamic/sql/util/kotlin/CriteriaCollector.kt index e75b51a95..294d49266 100644 --- a/src/main/kotlin/org/mybatis/dynamic/sql/util/kotlin/CriteriaCollector.kt +++ b/src/main/kotlin/org/mybatis/dynamic/sql/util/kotlin/CriteriaCollector.kt @@ -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 { @@ -38,13 +38,13 @@ class CriteriaCollector { fun and( column: BindableColumn, condition: VisitableCondition, - collect: CriteriaReceiver + criteriaReceiver: CriteriaReceiver ) = apply { criteria.add( SqlCriterion.withColumn(column) .withCondition(condition) - .withSubCriteria(collect(CriteriaCollector()).criteria) + .withSubCriteria(CriteriaCollector().apply(criteriaReceiver).criteria) .withConnector("and") .build() ) @@ -63,13 +63,13 @@ class CriteriaCollector { fun or( column: BindableColumn, condition: VisitableCondition, - collect: CriteriaReceiver + criteriaReceiver: CriteriaReceiver ) = apply { criteria.add( SqlCriterion.withColumn(column) .withCondition(condition) - .withSubCriteria(collect(CriteriaCollector()).criteria) + .withSubCriteria(CriteriaCollector().apply(criteriaReceiver).criteria) .withConnector("or") .build() ) diff --git a/src/main/kotlin/org/mybatis/dynamic/sql/util/kotlin/JoinCollector.kt b/src/main/kotlin/org/mybatis/dynamic/sql/util/kotlin/JoinCollector.kt index 7d3bcb875..a12e87702 100644 --- a/src/main/kotlin/org/mybatis/dynamic/sql/util/kotlin/JoinCollector.kt +++ b/src/main/kotlin/org/mybatis/dynamic/sql/util/kotlin/JoinCollector.kt @@ -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 { diff --git a/src/main/kotlin/org/mybatis/dynamic/sql/util/kotlin/KotlinBaseBuilders.kt b/src/main/kotlin/org/mybatis/dynamic/sql/util/kotlin/KotlinBaseBuilders.kt index 4e808b608..4aa9859c3 100644 --- a/src/main/kotlin/org/mybatis/dynamic/sql/util/kotlin/KotlinBaseBuilders.kt +++ b/src/main/kotlin/org/mybatis/dynamic/sql/util/kotlin/KotlinBaseBuilders.kt @@ -38,7 +38,7 @@ abstract class KotlinBaseBuilder, B : KotlinBaseBuilder< fun where(column: BindableColumn, condition: VisitableCondition, subCriteria: CriteriaReceiver): B = applySelf { - getWhere().where(column, condition, subCriteria(CriteriaCollector()).criteria) + getWhere().where(column, condition, CriteriaCollector().apply(subCriteria).criteria) } fun applyWhere(whereApplier: WhereApplier): B = @@ -53,7 +53,7 @@ abstract class KotlinBaseBuilder, B : KotlinBaseBuilder< fun and(column: BindableColumn, condition: VisitableCondition, subCriteria: CriteriaReceiver): B = applySelf { - getWhere().and(column, condition, subCriteria(CriteriaCollector()).criteria) + getWhere().and(column, condition, CriteriaCollector().apply(subCriteria).criteria) } fun or(column: BindableColumn, condition: VisitableCondition): B = @@ -63,7 +63,7 @@ abstract class KotlinBaseBuilder, B : KotlinBaseBuilder< fun or(column: BindableColumn, condition: VisitableCondition, subCriteria: CriteriaReceiver): B = applySelf { - getWhere().or(column, condition, subCriteria(CriteriaCollector()).criteria) + getWhere().or(column, condition, CriteriaCollector().apply(subCriteria).criteria) } fun allRows() = self() @@ -76,6 +76,7 @@ abstract class KotlinBaseBuilder, B : KotlinBaseBuilder< protected abstract fun getWhere(): W } +@Suppress("TooManyFunctions") abstract class KotlinBaseJoiningBuilder, W : AbstractWhereDSL, B : KotlinBaseJoiningBuilder> : KotlinBaseBuilder() { @@ -89,6 +90,15 @@ abstract class KotlinBaseJoiningBuilder Unit, + joinCriteria: JoinReceiver + ) = + applyJoin(joinCriteria) { + val builder = KotlinQualifiedSubQueryBuilder().apply(subQuery) + getDsl().join(builder, builder.correlationName, it.onJoinCriterion, it.andJoinCriteria) + } + fun fullJoin(table: SqlTable, joinCriteria: JoinReceiver) = applyJoin(joinCriteria) { getDsl().fullJoin(table, it.onJoinCriterion, it.andJoinCriteria) @@ -99,6 +109,15 @@ abstract class KotlinBaseJoiningBuilder Unit, + joinCriteria: JoinReceiver + ) = + applyJoin(joinCriteria) { + val builder = KotlinQualifiedSubQueryBuilder().apply(subQuery) + getDsl().fullJoin(builder, builder.correlationName, it.onJoinCriterion, it.andJoinCriteria) + } + fun leftJoin(table: SqlTable, joinCriteria: JoinReceiver) = applyJoin(joinCriteria) { getDsl().leftJoin(table, it.onJoinCriterion, it.andJoinCriteria) @@ -109,6 +128,15 @@ abstract class KotlinBaseJoiningBuilder Unit, + joinCriteria: JoinReceiver + ) = + applyJoin(joinCriteria) { + val builder = KotlinQualifiedSubQueryBuilder().apply(subQuery) + getDsl().leftJoin(builder, builder.correlationName, it.onJoinCriterion, it.andJoinCriteria) + } + fun rightJoin(table: SqlTable, joinCriteria: JoinReceiver) = applyJoin(joinCriteria) { getDsl().rightJoin(table, it.onJoinCriterion, it.andJoinCriteria) @@ -119,9 +147,18 @@ abstract class KotlinBaseJoiningBuilder Unit, + joinCriteria: JoinReceiver + ) = + applyJoin(joinCriteria) { + val builder = KotlinQualifiedSubQueryBuilder().apply(subQuery) + getDsl().rightJoin(builder, builder.correlationName, it.onJoinCriterion, it.andJoinCriteria) + } + private fun applyJoin(joinCriteria: JoinReceiver, block: (JoinCollector) -> Unit) = applySelf { - joinCriteria(JoinCollector()).also(block) + JoinCollector().apply(joinCriteria).apply(block) } protected abstract fun getDsl(): AbstractQueryExpressionDSL diff --git a/src/main/kotlin/org/mybatis/dynamic/sql/util/kotlin/KotlinConditions.kt b/src/main/kotlin/org/mybatis/dynamic/sql/util/kotlin/KotlinConditions.kt index 012dab612..a44b8efb8 100644 --- a/src/main/kotlin/org/mybatis/dynamic/sql/util/kotlin/KotlinConditions.kt +++ b/src/main/kotlin/org/mybatis/dynamic/sql/util/kotlin/KotlinConditions.kt @@ -24,26 +24,26 @@ import org.mybatis.dynamic.sql.where.condition.IsLessThanWithSubselect import org.mybatis.dynamic.sql.where.condition.IsNotEqualToWithSubselect import org.mybatis.dynamic.sql.where.condition.IsNotInWithSubselect -fun isEqualTo(subQuery: KotlinSubQueryBuilder.() -> KotlinSubQueryBuilder) = - IsEqualToWithSubselect.of(subQuery(KotlinSubQueryBuilder()).selectBuilder) +fun isEqualTo(subQuery: KotlinSubQueryBuilder.() -> Unit) = + IsEqualToWithSubselect.of(KotlinSubQueryBuilder().apply(subQuery)) -fun isNotEqualTo(subQuery: KotlinSubQueryBuilder.() -> KotlinSubQueryBuilder) = - IsNotEqualToWithSubselect.of(subQuery(KotlinSubQueryBuilder()).selectBuilder) +fun isNotEqualTo(subQuery: KotlinSubQueryBuilder.() -> Unit) = + IsNotEqualToWithSubselect.of(KotlinSubQueryBuilder().apply(subQuery)) -fun isIn(subQuery: KotlinSubQueryBuilder.() -> KotlinSubQueryBuilder) = - IsInWithSubselect.of(subQuery(KotlinSubQueryBuilder()).selectBuilder) +fun isIn(subQuery: KotlinSubQueryBuilder.() -> Unit) = + IsInWithSubselect.of(KotlinSubQueryBuilder().apply(subQuery)) -fun isNotIn(subQuery: KotlinSubQueryBuilder.() -> KotlinSubQueryBuilder) = - IsNotInWithSubselect.of(subQuery(KotlinSubQueryBuilder()).selectBuilder) +fun isNotIn(subQuery: KotlinSubQueryBuilder.() -> Unit) = + IsNotInWithSubselect.of(KotlinSubQueryBuilder().apply(subQuery)) -fun isGreaterThan(subQuery: KotlinSubQueryBuilder.() -> KotlinSubQueryBuilder) = - IsGreaterThanWithSubselect.of(subQuery(KotlinSubQueryBuilder()).selectBuilder) +fun isGreaterThan(subQuery: KotlinSubQueryBuilder.() -> Unit) = + IsGreaterThanWithSubselect.of(KotlinSubQueryBuilder().apply(subQuery)) -fun isGreaterThanOrEqualTo(subQuery: KotlinSubQueryBuilder.() -> KotlinSubQueryBuilder) = - IsGreaterThanOrEqualToWithSubselect.of(subQuery(KotlinSubQueryBuilder()).selectBuilder) +fun isGreaterThanOrEqualTo(subQuery: KotlinSubQueryBuilder.() -> Unit) = + IsGreaterThanOrEqualToWithSubselect.of(KotlinSubQueryBuilder().apply(subQuery)) -fun isLessThan(subQuery: KotlinSubQueryBuilder.() -> KotlinSubQueryBuilder) = - IsLessThanWithSubselect.of(subQuery(KotlinSubQueryBuilder()).selectBuilder) +fun isLessThan(subQuery: KotlinSubQueryBuilder.() -> Unit) = + IsLessThanWithSubselect.of(KotlinSubQueryBuilder().apply(subQuery)) -fun isLessThanOrEqualTo(subQuery: KotlinSubQueryBuilder.() -> KotlinSubQueryBuilder) = - IsLessThanOrEqualToWithSubselect.of(subQuery(KotlinSubQueryBuilder()).selectBuilder) +fun isLessThanOrEqualTo(subQuery: KotlinSubQueryBuilder.() -> Unit) = + IsLessThanOrEqualToWithSubselect.of(KotlinSubQueryBuilder().apply(subQuery)) diff --git a/src/main/kotlin/org/mybatis/dynamic/sql/util/kotlin/KotlinCountBuilder.kt b/src/main/kotlin/org/mybatis/dynamic/sql/util/kotlin/KotlinCountBuilder.kt index ca4de6310..7fe80ca6f 100644 --- a/src/main/kotlin/org/mybatis/dynamic/sql/util/kotlin/KotlinCountBuilder.kt +++ b/src/main/kotlin/org/mybatis/dynamic/sql/util/kotlin/KotlinCountBuilder.kt @@ -20,7 +20,7 @@ import org.mybatis.dynamic.sql.select.CountDSL import org.mybatis.dynamic.sql.select.SelectModel import org.mybatis.dynamic.sql.util.Buildable -typealias CountCompleter = KotlinCountBuilder.() -> KotlinCountBuilder +typealias CountCompleter = KotlinCountBuilder.() -> Unit class KotlinCountBuilder(private val fromGatherer: CountDSL.FromGatherer) : KotlinBaseJoiningBuilder, diff --git a/src/main/kotlin/org/mybatis/dynamic/sql/util/kotlin/KotlinDeleteBuilder.kt b/src/main/kotlin/org/mybatis/dynamic/sql/util/kotlin/KotlinDeleteBuilder.kt index fdb9e3d52..ae78c7d24 100644 --- a/src/main/kotlin/org/mybatis/dynamic/sql/util/kotlin/KotlinDeleteBuilder.kt +++ b/src/main/kotlin/org/mybatis/dynamic/sql/util/kotlin/KotlinDeleteBuilder.kt @@ -19,7 +19,7 @@ import org.mybatis.dynamic.sql.delete.DeleteDSL import org.mybatis.dynamic.sql.delete.DeleteModel import org.mybatis.dynamic.sql.util.Buildable -typealias DeleteCompleter = KotlinDeleteBuilder.() -> KotlinDeleteBuilder +typealias DeleteCompleter = KotlinDeleteBuilder.() -> Unit class KotlinDeleteBuilder(private val dsl: DeleteDSL) : KotlinBaseBuilder.DeleteWhereBuilder, KotlinDeleteBuilder>(), Buildable { diff --git a/src/main/kotlin/org/mybatis/dynamic/sql/util/kotlin/KotlinInsertHelpers.kt b/src/main/kotlin/org/mybatis/dynamic/sql/util/kotlin/KotlinInsertHelpers.kt index 4b53cb945..232b7d25d 100644 --- a/src/main/kotlin/org/mybatis/dynamic/sql/util/kotlin/KotlinInsertHelpers.kt +++ b/src/main/kotlin/org/mybatis/dynamic/sql/util/kotlin/KotlinInsertHelpers.kt @@ -16,22 +16,16 @@ package org.mybatis.dynamic.sql.util.kotlin import org.mybatis.dynamic.sql.insert.BatchInsertDSL -import org.mybatis.dynamic.sql.insert.BatchInsertModel import org.mybatis.dynamic.sql.insert.GeneralInsertDSL -import org.mybatis.dynamic.sql.insert.GeneralInsertModel import org.mybatis.dynamic.sql.insert.InsertDSL -import org.mybatis.dynamic.sql.insert.InsertModel import org.mybatis.dynamic.sql.insert.MultiRowInsertDSL -import org.mybatis.dynamic.sql.insert.MultiRowInsertModel -import org.mybatis.dynamic.sql.util.Buildable -typealias GeneralInsertCompleter = @MyBatisDslMarker GeneralInsertDSL.() -> Buildable +typealias GeneralInsertCompleter = @MyBatisDslMarker GeneralInsertDSL.() -> Unit -typealias InsertCompleter = @MyBatisDslMarker InsertDSL.() -> Buildable> +typealias InsertCompleter = @MyBatisDslMarker InsertDSL.() -> Unit -typealias MultiRowInsertCompleter = @MyBatisDslMarker MultiRowInsertDSL.() -> Buildable> +typealias MultiRowInsertCompleter = @MyBatisDslMarker MultiRowInsertDSL.() -> Unit -typealias BatchInsertCompleter = @MyBatisDslMarker BatchInsertDSL.() -> Buildable> +typealias BatchInsertCompleter = @MyBatisDslMarker BatchInsertDSL.() -> Unit -typealias InsertSelectCompleter = - @MyBatisDslMarker KotlinInsertSelectSubQueryBuilder.() -> KotlinInsertSelectSubQueryBuilder +typealias InsertSelectCompleter = @MyBatisDslMarker KotlinInsertSelectSubQueryBuilder.() -> Unit diff --git a/src/main/kotlin/org/mybatis/dynamic/sql/util/kotlin/KotlinModelBuilderFunctions.kt b/src/main/kotlin/org/mybatis/dynamic/sql/util/kotlin/KotlinModelBuilderFunctions.kt index 528ad6210..fb5de806d 100644 --- a/src/main/kotlin/org/mybatis/dynamic/sql/util/kotlin/KotlinModelBuilderFunctions.kt +++ b/src/main/kotlin/org/mybatis/dynamic/sql/util/kotlin/KotlinModelBuilderFunctions.kt @@ -29,53 +29,53 @@ import org.mybatis.dynamic.sql.insert.MultiRowInsertDSL * with the similar functions that build providers for the different rendering * strategies. */ +@Suppress("TooManyFunctions") object KotlinModelBuilderFunctions { fun count(column: BasicColumn, completer: CountCompleter) = - completer(KotlinCountBuilder(SqlBuilder.countColumn(column))).build() + KotlinCountBuilder(SqlBuilder.countColumn(column)).apply(completer).build() fun countDistinct(column: BasicColumn, completer: CountCompleter) = - completer(KotlinCountBuilder(SqlBuilder.countDistinctColumn(column))).build() + KotlinCountBuilder(SqlBuilder.countDistinctColumn(column)).apply(completer).build() fun countFrom(table: SqlTable, completer: CountCompleter) = - with(KotlinCountBuilder(SqlBuilder.countColumn(SqlBuilder.constant("*")))) { - completer(from(table)).build() - } + KotlinCountBuilder(SqlBuilder.countColumn(SqlBuilder.constant("*"))) + .from(table).apply(completer).build() fun deleteFrom(table: SqlTable, completer: DeleteCompleter) = - completer(KotlinDeleteBuilder(SqlBuilder.deleteFrom(table))).build() + KotlinDeleteBuilder(SqlBuilder.deleteFrom(table)).apply(completer).build() fun insertInto(table: SqlTable, completer: GeneralInsertCompleter) = - completer(GeneralInsertDSL.insertInto(table)).build() + GeneralInsertDSL.insertInto(table).apply(completer).build() fun insertSelect(table: SqlTable, completer: InsertSelectCompleter) = - with(completer(KotlinInsertSelectSubQueryBuilder())) { + with(KotlinInsertSelectSubQueryBuilder().apply(completer)) { SqlBuilder.insertInto(table) .withColumnList(columnList) - .withSelectStatement(selectBuilder) + .withSelectStatement(this) .build() } fun BatchInsertDSL.IntoGatherer.into(table: SqlTable, completer: BatchInsertCompleter) = - completer(into(table)).build() + into(table).apply(completer).build() fun InsertDSL.IntoGatherer.into(table: SqlTable, completer: InsertCompleter) = - completer(into(table)).build() + into(table).apply(completer).build() fun MultiRowInsertDSL.IntoGatherer.into(table: SqlTable, completer: MultiRowInsertCompleter) = - completer(into(table)).build() + into(table).apply(completer).build() fun select(vararg columns: BasicColumn, completer: SelectCompleter) = select(columns.asList(), completer) fun select(columns: List, completer: SelectCompleter) = - completer(KotlinSelectBuilder(SqlBuilder.select(columns))).build() + KotlinSelectBuilder(SqlBuilder.select(columns)).apply(completer).build() fun selectDistinct(vararg columns: BasicColumn, completer: SelectCompleter) = selectDistinct(columns.asList(), completer) fun selectDistinct(columns: List, completer: SelectCompleter) = - completer(KotlinSelectBuilder(SqlBuilder.selectDistinct(columns))).build() + KotlinSelectBuilder(SqlBuilder.selectDistinct(columns)).apply(completer).build() fun update(table: SqlTable, completer: UpdateCompleter) = - completer(KotlinUpdateBuilder(SqlBuilder.update(table))).build() + KotlinUpdateBuilder(SqlBuilder.update(table)).apply(completer).build() } diff --git a/src/main/kotlin/org/mybatis/dynamic/sql/util/kotlin/KotlinSelectBuilder.kt b/src/main/kotlin/org/mybatis/dynamic/sql/util/kotlin/KotlinSelectBuilder.kt index 3b5eecdde..af1ee5088 100644 --- a/src/main/kotlin/org/mybatis/dynamic/sql/util/kotlin/KotlinSelectBuilder.kt +++ b/src/main/kotlin/org/mybatis/dynamic/sql/util/kotlin/KotlinSelectBuilder.kt @@ -22,7 +22,7 @@ import org.mybatis.dynamic.sql.select.SelectModel import org.mybatis.dynamic.sql.util.Buildable import org.mybatis.dynamic.sql.select.QueryExpressionDSL -typealias SelectCompleter = KotlinSelectBuilder.() -> KotlinSelectBuilder +typealias SelectCompleter = KotlinSelectBuilder.() -> Unit @Suppress("TooManyFunctions") class KotlinSelectBuilder(private val fromGatherer: QueryExpressionDSL.FromGatherer) : @@ -42,10 +42,10 @@ class KotlinSelectBuilder(private val fromGatherer: QueryExpressionDSL.FromGathe dsl = fromGatherer.from(table, alias) } - fun from(subQuery: KotlinQualifiedSubQueryBuilder.() -> KotlinQualifiedSubQueryBuilder) = + fun from(subQuery: KotlinQualifiedSubQueryBuilder.() -> Unit) = apply { - val builder = subQuery(KotlinQualifiedSubQueryBuilder()) - dsl = fromGatherer.from(builder.selectBuilder, builder.correlationName) + val builder = KotlinQualifiedSubQueryBuilder().apply(subQuery) + dsl = fromGatherer.from(builder, builder.correlationName) } fun groupBy(vararg columns: BasicColumn) = diff --git a/src/main/kotlin/org/mybatis/dynamic/sql/util/kotlin/KotlinSubQueryBuilders.kt b/src/main/kotlin/org/mybatis/dynamic/sql/util/kotlin/KotlinSubQueryBuilders.kt index 2c0d76de1..a5654a8fa 100644 --- a/src/main/kotlin/org/mybatis/dynamic/sql/util/kotlin/KotlinSubQueryBuilders.kt +++ b/src/main/kotlin/org/mybatis/dynamic/sql/util/kotlin/KotlinSubQueryBuilders.kt @@ -18,17 +18,19 @@ package org.mybatis.dynamic.sql.util.kotlin import org.mybatis.dynamic.sql.BasicColumn import org.mybatis.dynamic.sql.SqlBuilder import org.mybatis.dynamic.sql.SqlColumn +import org.mybatis.dynamic.sql.select.SelectModel +import org.mybatis.dynamic.sql.util.Buildable @MyBatisDslMarker -sealed class KotlinBaseSubQueryBuilder > { - lateinit var selectBuilder: KotlinSelectBuilder +sealed class KotlinBaseSubQueryBuilder> : Buildable { + private lateinit var selectBuilder: KotlinSelectBuilder fun select(vararg selectList: BasicColumn, completer: SelectCompleter) = select(selectList.toList(), completer) fun select(selectList: List, completer: SelectCompleter) = applySelf { - selectBuilder = completer(KotlinSelectBuilder(SqlBuilder.select(selectList))) + selectBuilder = KotlinSelectBuilder(SqlBuilder.select(selectList)).apply(completer) } fun selectDistinct(vararg selectList: BasicColumn, completer: SelectCompleter) = @@ -36,9 +38,11 @@ sealed class KotlinBaseSubQueryBuilder > { fun selectDistinct(selectList: List, completer: SelectCompleter) = applySelf { - selectBuilder = completer(KotlinSelectBuilder(SqlBuilder.selectDistinct(selectList))) + selectBuilder = KotlinSelectBuilder(SqlBuilder.selectDistinct(selectList)).apply(completer) } + override fun build() = selectBuilder.build() + private fun applySelf(block: T.() -> Unit): T = self().apply { block() } diff --git a/src/main/kotlin/org/mybatis/dynamic/sql/util/kotlin/KotlinUpdateBuilder.kt b/src/main/kotlin/org/mybatis/dynamic/sql/util/kotlin/KotlinUpdateBuilder.kt index 55895ec23..881a28cfd 100644 --- a/src/main/kotlin/org/mybatis/dynamic/sql/util/kotlin/KotlinUpdateBuilder.kt +++ b/src/main/kotlin/org/mybatis/dynamic/sql/util/kotlin/KotlinUpdateBuilder.kt @@ -21,7 +21,7 @@ import org.mybatis.dynamic.sql.update.UpdateDSL import org.mybatis.dynamic.sql.update.UpdateModel import org.mybatis.dynamic.sql.util.Buildable -typealias UpdateCompleter = KotlinUpdateBuilder.() -> KotlinUpdateBuilder +typealias UpdateCompleter = KotlinUpdateBuilder.() -> Unit class KotlinUpdateBuilder(private val dsl: UpdateDSL) : KotlinBaseBuilder.UpdateWhereBuilder, KotlinUpdateBuilder>(), Buildable { @@ -66,9 +66,9 @@ class KotlinUpdateBuilder(private val dsl: UpdateDSL) : set(column).equalTo(rightColumn) } - fun equalToQueryResult(subQuery: KotlinSubQueryBuilder.() -> KotlinSubQueryBuilder) = + fun equalToQueryResult(subQuery: KotlinSubQueryBuilder.() -> Unit) = applyToDsl { - set(column).equalTo(subQuery(KotlinSubQueryBuilder()).selectBuilder) + set(column).equalTo(KotlinSubQueryBuilder().apply(subQuery)) } fun equalToWhenPresent(value: () -> T?) = @@ -82,8 +82,8 @@ class KotlinUpdateBuilder(private val dsl: UpdateDSL) : } private fun applyToDsl(block: UpdateDSL.() -> Unit) = - this@KotlinUpdateBuilder.also { - it.dsl.apply(block) + this@KotlinUpdateBuilder.apply { + dsl.apply(block) } } } diff --git a/src/main/kotlin/org/mybatis/dynamic/sql/util/kotlin/mybatis3/ProviderBuilderFunctions.kt b/src/main/kotlin/org/mybatis/dynamic/sql/util/kotlin/mybatis3/ProviderBuilderFunctions.kt index 7ef49997a..d2e517830 100644 --- a/src/main/kotlin/org/mybatis/dynamic/sql/util/kotlin/mybatis3/ProviderBuilderFunctions.kt +++ b/src/main/kotlin/org/mybatis/dynamic/sql/util/kotlin/mybatis3/ProviderBuilderFunctions.kt @@ -67,7 +67,7 @@ fun MultiRowInsertDSL.IntoGatherer.into(table: SqlTable, completer: Multi into(table, completer).render(RenderingStrategies.MYBATIS3) fun select(start: KotlinSelectBuilder, completer: SelectCompleter) = - completer(start).build().render(RenderingStrategies.MYBATIS3) + start.apply(completer).build().render(RenderingStrategies.MYBATIS3) fun select(vararg columns: BasicColumn, completer: SelectCompleter) = select(columns = columns, completer).render(RenderingStrategies.MYBATIS3) diff --git a/src/site/markdown/docs/subQueries.md b/src/site/markdown/docs/subQueries.md index 16ab0019c..cd8398d35 100644 --- a/src/site/markdown/docs/subQueries.md +++ b/src/site/markdown/docs/subQueries.md @@ -5,6 +5,113 @@ The library currently supports subqueries in the following areas: 1. In certain insert statements 1. In update statements 1. In the "from" clause of a select statement +1. In join clauses of a select statement + +Before we show examples of subqueries, it is important to understand how the library generates and applies +table qualifiers in select statements. We'll cover that first. + +## Table Qualifiers in Select Statements + +The library attempts to automatically calculate table qualifiers. If a table qualifier is specified, +the library will automatically render the table qualifier on all columns associated with the +table. For example with the following query: + +```java +SelectStatementProvider selectStatement = + select(id, animalName) + .from(animalData, "ad") + .build() + .render(RenderingStrategies.MYBATIS3); +``` + +The library will render SQL as: + +```sql +select ad.id, ad.animal_name +from AnimalData ad +``` + +Notice that the table qualifier `ad` is automatically applied to columns in the select list. + +In the case of join queries the table qualifier specified, or if not specified the table name +itself, will be used as the table qualifier. However, this function is disabled for joins on subqueries. + +With subqueries, it is important to understand the limits of automatic table qualifiers. The rules are +as follows: + +1. The scope of automatic table qualifiers is limited to a single select statement. For subqueries, the outer + query has a different scope than the subquery. +1. A qualifier can be applied to a subquery, but that qualifier is not automatically applied to + any column + +As an example, consider the following query: + +```java +DerivedColumn rowNum = DerivedColumn.of("rownum()"); + +SelectStatementProvider selectStatement = + select(animalName, rowNum) + .from( + select(id, animalName) + .from(animalData, "a") + .where(id, isLessThan(22)) + .orderBy(animalName.descending()), + "b" + ) + .where(rowNum, isLessThan(5)) + .and(animalName, isLike("%a%")) + .build() + .render(RenderingStrategies.MYBATIS3); +``` + +The rendered SQL will be as follows: + +```sql +select animal_name, rownum() +from (select a.id, a.animal_name + from AnimalDate a + where id < #{parameters.p1} + order by animal_name desc) b +where rownum() < #{parameters.p2} + and animal_name like #{parameters.p3} +``` + +Notice that the qualifier `a` is automatically applied to columns in the subquery and that the +qualifier `b` is not applied anywhere. + +If your query requires the subquery qualifier to be applied to columns in the outer select list, +you can manually apply the qualifier to columns as follows: + +```java +DerivedColumn rowNum = DerivedColumn.of("rownum()"); + +SelectStatementProvider selectStatement = + select(animalName.qualifiedWith("b"), rowNum) + .from( + select(id, animalName) + .from(animalData, "a") + .where(id, isLessThan(22)) + .orderBy(animalName.descending()), + "b" + ) + .where(rowNum, isLessThan(5)) + .and(animalName.qualifiedWith("b"), isLike("%a%")) + .build() + .render(RenderingStrategies.MYBATIS3); +``` + +In this case, we have manually applied the qualifier `b` to columns in the outer query. The +rendered SQL looks like this: + +```sql +select b.animal_name, rownum() +from (select a.id, a.animal_name + from AnimalDate a + where id < #{parameters.p1} + order by animal_name desc) b +where rownum() < #{parameters.p2} + and b.animal_name like #{parameters.p3} +``` ## Subqueries in Where Conditions The library support subqueries in the following where conditions: @@ -136,109 +243,6 @@ SelectStatementProvider selectStatement = Notice the use of a `DerivedColumn` to easily specify a function like `rownum()` that can be used both in the select list and in a where condition. -### Table Qualifiers with Subqueries - -The library attempts to automatically calculate table qualifiers. If a table qualifier is specified, -the library will automatically render the table qualifier on all columns associated with the -table. For example with the following query: - -```java -SelectStatementProvider selectStatement = - select(id, animalName) - .from(animalData, "ad") - .build() - .render(RenderingStrategies.MYBATIS3); -``` - -The library will render SQL as: - -```sql -select ad.id, ad.animal_name -from AnimalData ad -``` - -Notice that the table qualifier `ad` is automatically applied to columns in the select list. - -In the case of join queries the table qualifier specified, or if not specified the table name -itself, will be used as the table qualifier. - -With subqueries, it is important to understand the limits of automatic table qualifiers. The rules are -as follows: - -1. The scope of automatic table qualifiers is limited to a single select statement. For subqueries, the outer - query has a different scope than the subquery. -1. A qualifier can be applied to a subquery as a whole, but that qualifier is not automatically applied to - any column - -As an example, consider the following query: - -```java -DerivedColumn rowNum = DerivedColumn.of("rownum()"); - -SelectStatementProvider selectStatement = - select(animalName, rowNum) - .from( - select(id, animalName) - .from(animalData, "a") - .where(id, isLessThan(22)) - .orderBy(animalName.descending()), - "b" - ) - .where(rowNum, isLessThan(5)) - .and(animalName, isLike("%a%")) - .build() - .render(RenderingStrategies.MYBATIS3); -``` - -The rendered SQL will be as follows: - -```sql -select animal_name, rownum() -from (select a.id, a.animal_name - from AnimalDate a - where id < #{parameters.p1} - order by animal_name desc) b -where rownum() < #{parameters.p2} - and animal_name like #{parameters.p3} -``` - -Notice that the qualifier `a` is automatically applied to columns in the subquery and that the -qualifier `b` is not applied anywhere. - -If your query requires the subquery qualifier to be applied to columns in the outer select list, -you can manually apply the qualifier to columns as follows: - -```java -DerivedColumn rowNum = DerivedColumn.of("rownum()"); - -SelectStatementProvider selectStatement = - select(animalName.qualifiedWith("b"), rowNum) - .from( - select(id, animalName) - .from(animalData, "a") - .where(id, isLessThan(22)) - .orderBy(animalName.descending()), - "b" - ) - .where(rowNum, isLessThan(5)) - .and(animalName.qualifiedWith("b"), isLike("%a%")) - .build() - .render(RenderingStrategies.MYBATIS3); -``` - -In this case, we have manually applied the qualifier `b` to columns in the outer query. The -rendered SQL looks like this: - -```sql -select b.animal_name, rownum() -from (select a.id, a.animal_name - from AnimalDate a - where id < #{parameters.p1} - order by animal_name desc) b -where rownum() < #{parameters.p2} - and b.animal_name like #{parameters.p3} -``` - ### Kotlin Support The library includes a Kotlin builder for subqueries that integrates with the select DSL. You @@ -280,3 +284,66 @@ val selectStatement = In this case the `a` qualifier is used in the context of the inner select statement and the `b` qualifier is applied to the subquery as a whole. + +## Subqueries in Join Clauses +The library supports subqueries in "join" clauses similarly to subqueries in "from" clauses. For example: + +```java +SelectStatementProvider selectStatement = select(orderMaster.orderId, orderMaster.orderDate, + orderDetail.lineNumber, orderDetail.description, orderDetail.quantity) + .from(orderMaster, "om") + .join( + select(orderDetail.orderId, orderDetail.lineNumber, orderDetail.description, orderDetail.quantity) + .from(orderDetail), + "od") + .on(orderMaster.orderId, equalTo(orderDetail.orderId.qualifiedWith("od"))) + .build() + .render(RenderingStrategies.MYBATIS3); +``` + +This is rendered as: + +```sql +select om.order_id, om.order_date, line_number, description, quantity +from OrderMaster om +join (select order_id, line_number, description, quantity from OrderDetail) od +on om.order_id = od.order_id +``` + +Notice that the subquery is aliased with "od", but that alias is not automatically applied so it must +be specified when required. If in doubt, specify the alias with the `qualifiedBy` method. + +### Kotlin Support +The Kotlin select build supports subqueries in joins as follows: + +```kotlin +val selectStatement = select(OrderLine.orderId, OrderLine.quantity, + ItemMaster.itemId.qualifiedWith("im"), ItemMaster.description) { + from(OrderMaster, "om") + join(OrderLine, "ol") { + on(OrderMaster.orderId, equalTo(OrderLine.orderId)) + } + leftJoin({ + select(ItemMaster.allColumns()) { + from(ItemMaster) + } + + "im" + }) { + on(OrderLine.itemId, equalTo(ItemMaster.itemId.qualifiedWith("im"))) + } + orderBy(OrderLine.orderId, ItemMaster.itemId) +} +``` + +This is rendered as: + +```sql +select ol.order_id, ol.quantity, im.item_id, description +from OrderMaster om join OrderLine ol on om.order_id = ol.order_id +left join (select * from ItemMaster) im on ol.item_id = im.item_id +order by order_id, item_id +``` + +Notice again that subquery qualifiers must be specified when needed. Also note that the Kotlin join methods accept +two lambda functions - one for the subquery and one for the join specification. Only the join specification can +be outside the parenthesis of the join methods. diff --git a/src/test/java/examples/joins/JoinMapperTest.java b/src/test/java/examples/joins/JoinMapperTest.java index ada9f33ba..ce63c9873 100644 --- a/src/test/java/examples/joins/JoinMapperTest.java +++ b/src/test/java/examples/joins/JoinMapperTest.java @@ -81,9 +81,9 @@ void testSingleTableJoin1() { .build() .render(RenderingStrategies.MYBATIS3); - String expectedStatment = "select om.order_id, om.order_date, od.line_number, od.description, od.quantity" + String expectedStatement = "select om.order_id, om.order_date, od.line_number, od.description, od.quantity" + " from OrderMaster om join OrderDetail od on om.order_id = od.order_id"; - assertThat(selectStatement.getSelectStatement()).isEqualTo(expectedStatment); + assertThat(selectStatement.getSelectStatement()).isEqualTo(expectedStatement); List rows = mapper.selectMany(selectStatement); @@ -115,9 +115,9 @@ void testSingleTableJoin2() { .build() .render(RenderingStrategies.MYBATIS3); - String expectedStatment = "select om.order_id, om.order_date, od.line_number, od.description, od.quantity" + String expectedStatement = "select om.order_id, om.order_date, od.line_number, od.description, od.quantity" + " from OrderMaster om join OrderDetail od on om.order_id = od.order_id"; - assertThat(selectStatement.getSelectStatement()).isEqualTo(expectedStatment); + assertThat(selectStatement.getSelectStatement()).isEqualTo(expectedStatement); List rows = mapper.selectMany(selectStatement); @@ -147,9 +147,9 @@ void testCompoundJoin1() { .build() .render(RenderingStrategies.MYBATIS3); - String expectedStatment = "select om.order_id, om.order_date, od.line_number, od.description, od.quantity" + String expectedStatement = "select om.order_id, om.order_date, od.line_number, od.description, od.quantity" + " from OrderMaster om join OrderDetail od on om.order_id = od.order_id and om.order_id = od.order_id"; - assertThat(selectStatement.getSelectStatement()).isEqualTo(expectedStatment); + assertThat(selectStatement.getSelectStatement()).isEqualTo(expectedStatement); } @Test @@ -162,9 +162,9 @@ void testCompoundJoin2() { .build() .render(RenderingStrategies.MYBATIS3); - String expectedStatment = "select om.order_id, om.order_date, od.line_number, od.description, od.quantity" + String expectedStatement = "select om.order_id, om.order_date, od.line_number, od.description, od.quantity" + " from OrderMaster om join OrderDetail od on om.order_id = od.order_id and om.order_id = od.order_id"; - assertThat(selectStatement.getSelectStatement()).isEqualTo(expectedStatment); + assertThat(selectStatement.getSelectStatement()).isEqualTo(expectedStatement); } @Test @@ -176,9 +176,9 @@ void testCompoundJoin3() { .build() .render(RenderingStrategies.MYBATIS3); - String expectedStatment = "select om.order_id, om.order_date, od.line_number, od.description, od.quantity" + String expectedStatement = "select om.order_id, om.order_date, od.line_number, od.description, od.quantity" + " from OrderMaster om join OrderDetail od on om.order_id = od.order_id and om.order_id = od.order_id"; - assertThat(selectStatement.getSelectStatement()).isEqualTo(expectedStatment); + assertThat(selectStatement.getSelectStatement()).isEqualTo(expectedStatement); } @Test @@ -190,9 +190,9 @@ void testCompoundJoin4() { .build() .render(RenderingStrategies.MYBATIS3); - String expectedStatment = "select om.order_id, om.order_date, od.line_number, od.description, od.quantity" + String expectedStatement = "select om.order_id, om.order_date, od.line_number, od.description, od.quantity" + " from OrderMaster om left join OrderDetail od on om.order_id = od.order_id and om.order_id = od.order_id"; - assertThat(selectStatement.getSelectStatement()).isEqualTo(expectedStatment); + assertThat(selectStatement.getSelectStatement()).isEqualTo(expectedStatement); } @Test @@ -204,9 +204,9 @@ void testCompoundJoin5() { .build() .render(RenderingStrategies.MYBATIS3); - String expectedStatment = "select om.order_id, om.order_date, od.line_number, od.description, od.quantity" + String expectedStatement = "select om.order_id, om.order_date, od.line_number, od.description, od.quantity" + " from OrderMaster om right join OrderDetail od on om.order_id = od.order_id and om.order_id = od.order_id"; - assertThat(selectStatement.getSelectStatement()).isEqualTo(expectedStatment); + assertThat(selectStatement.getSelectStatement()).isEqualTo(expectedStatement); } @Test @@ -218,9 +218,9 @@ void testCompoundJoin6() { .build() .render(RenderingStrategies.MYBATIS3); - String expectedStatment = "select om.order_id, om.order_date, od.line_number, od.description, od.quantity" + String expectedStatement = "select om.order_id, om.order_date, od.line_number, od.description, od.quantity" + " from OrderMaster om full join OrderDetail od on om.order_id = od.order_id and om.order_id = od.order_id"; - assertThat(selectStatement.getSelectStatement()).isEqualTo(expectedStatment); + assertThat(selectStatement.getSelectStatement()).isEqualTo(expectedStatement); } @Test @@ -236,10 +236,10 @@ void testMultipleTableJoinWithWhereClause() { .build() .render(RenderingStrategies.MYBATIS3); - String expectedStatment = "select om.order_id, om.order_date, ol.line_number, im.description, ol.quantity" + String expectedStatement = "select om.order_id, om.order_date, ol.line_number, im.description, ol.quantity" + " from OrderMaster om join OrderLine ol on om.order_id = ol.order_id join ItemMaster im on ol.item_id = im.item_id" + " where om.order_id = #{parameters.p1,jdbcType=INTEGER}"; - assertThat(selectStatement.getSelectStatement()).isEqualTo(expectedStatment); + assertThat(selectStatement.getSelectStatement()).isEqualTo(expectedStatement); List rows = mapper.selectMany(selectStatement); @@ -267,10 +267,10 @@ void testMultipleTableJoinWithApplyWhere() { .build() .render(RenderingStrategies.MYBATIS3); - String expectedStatment = "select om.order_id, om.order_date, ol.line_number, im.description, ol.quantity" + String expectedStatement = "select om.order_id, om.order_date, ol.line_number, im.description, ol.quantity" + " from OrderMaster om join OrderLine ol on om.order_id = ol.order_id join ItemMaster im on ol.item_id = im.item_id" + " where om.order_id = #{parameters.p1,jdbcType=INTEGER}"; - assertThat(selectStatement.getSelectStatement()).isEqualTo(expectedStatment); + assertThat(selectStatement.getSelectStatement()).isEqualTo(expectedStatement); List rows = mapper.selectMany(selectStatement); @@ -298,10 +298,10 @@ void testMultipleTableJoinWithComplexWhereClause() { .build() .render(RenderingStrategies.MYBATIS3); - String expectedStatment = "select om.order_id, om.order_date, ol.line_number, im.description, ol.quantity" + String expectedStatement = "select om.order_id, om.order_date, ol.line_number, im.description, ol.quantity" + " from OrderMaster om join OrderLine ol on om.order_id = ol.order_id join ItemMaster im on ol.item_id = im.item_id" + " where (om.order_id = #{parameters.p1,jdbcType=INTEGER} and ol.line_number = #{parameters.p2,jdbcType=INTEGER})"; - assertThat(selectStatement.getSelectStatement()).isEqualTo(expectedStatment); + assertThat(selectStatement.getSelectStatement()).isEqualTo(expectedStatement); List rows = mapper.selectMany(selectStatement); @@ -327,10 +327,10 @@ void testMultipleTableJoinWithOrderBy() { .build() .render(RenderingStrategies.MYBATIS3); - String expectedStatment = "select om.order_id, om.order_date, ol.line_number, im.description, ol.quantity" + String expectedStatement = "select om.order_id, om.order_date, ol.line_number, im.description, ol.quantity" + " from OrderMaster om join OrderLine ol on om.order_id = ol.order_id join ItemMaster im on ol.item_id = im.item_id" + " order by order_id"; - assertThat(selectStatement.getSelectStatement()).isEqualTo(expectedStatment); + assertThat(selectStatement.getSelectStatement()).isEqualTo(expectedStatement); List rows = mapper.selectMany(selectStatement); @@ -352,7 +352,7 @@ void testMultipleTableJoinWithOrderBy() { } @Test - void testMultibleTableJoinNoAliasWithOrderBy() { + void testMultipleTableJoinNoAliasWithOrderBy() { try (SqlSession session = sqlSessionFactory.openSession()) { JoinMapper mapper = session.getMapper(JoinMapper.class); @@ -365,11 +365,11 @@ void testMultibleTableJoinNoAliasWithOrderBy() { .build() .render(RenderingStrategies.MYBATIS3); - String expectedStatment = "select OrderMaster.order_id, OrderMaster.order_date, OrderLine.line_number, ItemMaster.description, OrderLine.quantity" + String expectedStatement = "select OrderMaster.order_id, OrderMaster.order_date, OrderLine.line_number, ItemMaster.description, OrderLine.quantity" + " from OrderMaster join OrderLine on OrderMaster.order_id = OrderLine.order_id join ItemMaster on OrderLine.item_id = ItemMaster.item_id" + " where OrderMaster.order_id = #{parameters.p1,jdbcType=INTEGER}" + " order by order_id"; - assertThat(selectStatement.getSelectStatement()).isEqualTo(expectedStatment); + assertThat(selectStatement.getSelectStatement()).isEqualTo(expectedStatement); List rows = mapper.selectMany(selectStatement); @@ -396,10 +396,10 @@ void testRightJoin() { .build() .render(RenderingStrategies.MYBATIS3); - String expectedStatment = "select ol.order_id, ol.quantity, im.item_id, im.description" + String expectedStatement = "select ol.order_id, ol.quantity, im.item_id, im.description" + " from OrderLine ol right join ItemMaster im on ol.item_id = im.item_id" + " order by item_id"; - assertThat(selectStatement.getSelectStatement()).isEqualTo(expectedStatment); + assertThat(selectStatement.getSelectStatement()).isEqualTo(expectedStatement); List> rows = mapper.selectManyMappedRows(selectStatement); @@ -431,11 +431,11 @@ void testRightJoin2() { .build() .render(RenderingStrategies.MYBATIS3); - String expectedStatment = "select ol.order_id, ol.quantity, im.item_id, im.description" + String expectedStatement = "select ol.order_id, ol.quantity, im.item_id, im.description" + " from OrderMaster om join OrderLine ol on om.order_id = ol.order_id" + " right join ItemMaster im on ol.item_id = im.item_id" + " order by order_id, item_id"; - assertThat(selectStatement.getSelectStatement()).isEqualTo(expectedStatment); + assertThat(selectStatement.getSelectStatement()).isEqualTo(expectedStatement); List> rows = mapper.selectManyMappedRows(selectStatement); @@ -467,11 +467,11 @@ void testRightJoin3() { .build() .render(RenderingStrategies.MYBATIS3); - String expectedStatment = "select ol.order_id, ol.quantity, im.item_id, im.description" + String expectedStatement = "select ol.order_id, ol.quantity, im.item_id, im.description" + " from OrderMaster om join OrderLine ol on om.order_id = ol.order_id" + " right join ItemMaster im on ol.item_id = im.item_id" + " order by order_id, item_id"; - assertThat(selectStatement.getSelectStatement()).isEqualTo(expectedStatment); + assertThat(selectStatement.getSelectStatement()).isEqualTo(expectedStatement); List> rows = mapper.selectManyMappedRows(selectStatement); @@ -503,11 +503,11 @@ void testRightJoinNoAliases() { .build() .render(RenderingStrategies.MYBATIS3); - String expectedStatment = "select OrderLine.order_id, OrderLine.quantity, ItemMaster.item_id, ItemMaster.description" + String expectedStatement = "select OrderLine.order_id, OrderLine.quantity, ItemMaster.item_id, ItemMaster.description" + " from OrderMaster join OrderLine on OrderMaster.order_id = OrderLine.order_id" + " right join ItemMaster on OrderLine.item_id = ItemMaster.item_id" + " order by order_id, item_id"; - assertThat(selectStatement.getSelectStatement()).isEqualTo(expectedStatment); + assertThat(selectStatement.getSelectStatement()).isEqualTo(expectedStatement); List> rows = mapper.selectManyMappedRows(selectStatement); @@ -538,10 +538,10 @@ void testLeftJoin() { .build() .render(RenderingStrategies.MYBATIS3); - String expectedStatment = "select ol.order_id, ol.quantity, im.item_id, im.description" + String expectedStatement = "select ol.order_id, ol.quantity, im.item_id, im.description" + " from ItemMaster im left join OrderLine ol on ol.item_id = im.item_id" + " order by item_id"; - assertThat(selectStatement.getSelectStatement()).isEqualTo(expectedStatment); + assertThat(selectStatement.getSelectStatement()).isEqualTo(expectedStatement); List> rows = mapper.selectManyMappedRows(selectStatement); @@ -573,11 +573,11 @@ void testLeftJoin2() { .build() .render(RenderingStrategies.MYBATIS3); - String expectedStatment = "select ol.order_id, ol.quantity, im.item_id, im.description" + String expectedStatement = "select ol.order_id, ol.quantity, im.item_id, im.description" + " from OrderMaster om join OrderLine ol on om.order_id = ol.order_id" + " left join ItemMaster im on ol.item_id = im.item_id" + " order by order_id, item_id"; - assertThat(selectStatement.getSelectStatement()).isEqualTo(expectedStatment); + assertThat(selectStatement.getSelectStatement()).isEqualTo(expectedStatement); List> rows = mapper.selectManyMappedRows(selectStatement); @@ -609,11 +609,11 @@ void testLeftJoin3() { .build() .render(RenderingStrategies.MYBATIS3); - String expectedStatment = "select ol.order_id, ol.quantity, im.item_id, im.description" + String expectedStatement = "select ol.order_id, ol.quantity, im.item_id, im.description" + " from OrderMaster om join OrderLine ol on om.order_id = ol.order_id" + " left join ItemMaster im on ol.item_id = im.item_id" + " order by order_id, item_id"; - assertThat(selectStatement.getSelectStatement()).isEqualTo(expectedStatment); + assertThat(selectStatement.getSelectStatement()).isEqualTo(expectedStatement); List> rows = mapper.selectManyMappedRows(selectStatement); @@ -645,11 +645,11 @@ void testLeftJoinNoAliases() { .build() .render(RenderingStrategies.MYBATIS3); - String expectedStatment = "select OrderLine.order_id, OrderLine.quantity, ItemMaster.item_id, ItemMaster.description" + String expectedStatement = "select OrderLine.order_id, OrderLine.quantity, ItemMaster.item_id, ItemMaster.description" + " from OrderMaster join OrderLine on OrderMaster.order_id = OrderLine.order_id" + " left join ItemMaster on OrderLine.item_id = ItemMaster.item_id" + " order by order_id, item_id"; - assertThat(selectStatement.getSelectStatement()).isEqualTo(expectedStatment); + assertThat(selectStatement.getSelectStatement()).isEqualTo(expectedStatement); List> rows = mapper.selectManyMappedRows(selectStatement); @@ -680,10 +680,10 @@ void testFullJoin() { .build() .render(RenderingStrategies.MYBATIS3); - String expectedStatment = "select ol.order_id, ol.quantity, ol.item_id as ol_itemid, im.item_id as im_itemid, im.description" + String expectedStatement = "select ol.order_id, ol.quantity, ol.item_id as ol_itemid, im.item_id as im_itemid, im.description" + " from ItemMaster im full join OrderLine ol on im.item_id = ol.item_id" + " order by im_itemid"; - assertThat(selectStatement.getSelectStatement()).isEqualTo(expectedStatment); + assertThat(selectStatement.getSelectStatement()).isEqualTo(expectedStatement); List> rows = mapper.selectManyMappedRows(selectStatement); @@ -722,11 +722,11 @@ void testFullJoin2() { .build() .render(RenderingStrategies.MYBATIS3); - String expectedStatment = "select ol.order_id, ol.quantity, im.item_id, im.description" + String expectedStatement = "select ol.order_id, ol.quantity, im.item_id, im.description" + " from OrderMaster om join OrderLine ol on om.order_id = ol.order_id" + " full join ItemMaster im on ol.item_id = im.item_id" + " order by order_id, item_id"; - assertThat(selectStatement.getSelectStatement()).isEqualTo(expectedStatment); + assertThat(selectStatement.getSelectStatement()).isEqualTo(expectedStatement); List> rows = mapper.selectManyMappedRows(selectStatement); @@ -764,11 +764,11 @@ void testFullJoin3() { .build() .render(RenderingStrategies.MYBATIS3); - String expectedStatment = "select ol.order_id, ol.quantity, im.item_id, im.description" + String expectedStatement = "select ol.order_id, ol.quantity, im.item_id, im.description" + " from OrderMaster om join OrderLine ol on om.order_id = ol.order_id" + " full join ItemMaster im on ol.item_id = im.item_id" + " order by order_id, item_id"; - assertThat(selectStatement.getSelectStatement()).isEqualTo(expectedStatment); + assertThat(selectStatement.getSelectStatement()).isEqualTo(expectedStatement); List> rows = mapper.selectManyMappedRows(selectStatement); @@ -884,11 +884,11 @@ void testFullJoinNoAliases() { .build() .render(RenderingStrategies.MYBATIS3); - String expectedStatment = "select OrderLine.order_id, OrderLine.quantity, ItemMaster.item_id, ItemMaster.description" + String expectedStatement = "select OrderLine.order_id, OrderLine.quantity, ItemMaster.item_id, ItemMaster.description" + " from OrderMaster join OrderLine on OrderMaster.order_id = OrderLine.order_id" + " full join ItemMaster on OrderLine.item_id = ItemMaster.item_id" + " order by order_id, item_id"; - assertThat(selectStatement.getSelectStatement()).isEqualTo(expectedStatment); + assertThat(selectStatement.getSelectStatement()).isEqualTo(expectedStatement); List> rows = mapper.selectManyMappedRows(selectStatement); @@ -929,10 +929,10 @@ void testSelf() { .build() .render(RenderingStrategies.MYBATIS3); - String expectedStatment = "select u1.user_id, u1.user_name, u1.parent_id" + String expectedStatement = "select u1.user_id, u1.user_name, u1.parent_id" + " from User u1 join User u2 on u1.user_id = u2.parent_id" + " where u2.user_id = #{parameters.p1,jdbcType=INTEGER}"; - assertThat(selectStatement.getSelectStatement()).isEqualTo(expectedStatment); + assertThat(selectStatement.getSelectStatement()).isEqualTo(expectedStatement); List rows = mapper.selectUsers(selectStatement); @@ -957,10 +957,10 @@ void testLimitAndOffsetAfterJoin() { .build() .render(RenderingStrategies.MYBATIS3); - String expectedStatment = "select ol.order_id, ol.quantity, im.item_id, im.description" + String expectedStatement = "select ol.order_id, ol.quantity, im.item_id, im.description" + " from ItemMaster im left join OrderLine ol on ol.item_id = im.item_id" + " limit #{parameters.p1} offset #{parameters.p2}"; - assertThat(selectStatement.getSelectStatement()).isEqualTo(expectedStatment); + assertThat(selectStatement.getSelectStatement()).isEqualTo(expectedStatement); List> rows = mapper.selectManyMappedRows(selectStatement); @@ -991,10 +991,10 @@ void testLimitOnlyAfterJoin() { .build() .render(RenderingStrategies.MYBATIS3); - String expectedStatment = "select ol.order_id, ol.quantity, im.item_id, im.description" + String expectedStatement = "select ol.order_id, ol.quantity, im.item_id, im.description" + " from ItemMaster im left join OrderLine ol on ol.item_id = im.item_id" + " limit #{parameters.p1}"; - assertThat(selectStatement.getSelectStatement()).isEqualTo(expectedStatment); + assertThat(selectStatement.getSelectStatement()).isEqualTo(expectedStatement); List> rows = mapper.selectManyMappedRows(selectStatement); @@ -1025,10 +1025,10 @@ void testOffsetOnlyAfterJoin() { .build() .render(RenderingStrategies.MYBATIS3); - String expectedStatment = "select ol.order_id, ol.quantity, im.item_id, im.description" + String expectedStatement = "select ol.order_id, ol.quantity, im.item_id, im.description" + " from ItemMaster im left join OrderLine ol on ol.item_id = im.item_id" + " offset #{parameters.p1} rows"; - assertThat(selectStatement.getSelectStatement()).isEqualTo(expectedStatment); + assertThat(selectStatement.getSelectStatement()).isEqualTo(expectedStatement); List> rows = mapper.selectManyMappedRows(selectStatement); @@ -1060,10 +1060,10 @@ void testOffsetAndFetchFirstAfterJoin() { .build() .render(RenderingStrategies.MYBATIS3); - String expectedStatment = "select ol.order_id, ol.quantity, im.item_id, im.description" + String expectedStatement = "select ol.order_id, ol.quantity, im.item_id, im.description" + " from ItemMaster im left join OrderLine ol on ol.item_id = im.item_id" + " offset #{parameters.p1} rows fetch first #{parameters.p2} rows only"; - assertThat(selectStatement.getSelectStatement()).isEqualTo(expectedStatment); + assertThat(selectStatement.getSelectStatement()).isEqualTo(expectedStatement); List> rows = mapper.selectManyMappedRows(selectStatement); @@ -1094,10 +1094,10 @@ void testFetchFirstOnlyAfterJoin() { .build() .render(RenderingStrategies.MYBATIS3); - String expectedStatment = "select ol.order_id, ol.quantity, im.item_id, im.description" + String expectedStatement = "select ol.order_id, ol.quantity, im.item_id, im.description" + " from ItemMaster im left join OrderLine ol on ol.item_id = im.item_id" + " fetch first #{parameters.p1} rows only"; - assertThat(selectStatement.getSelectStatement()).isEqualTo(expectedStatment); + assertThat(selectStatement.getSelectStatement()).isEqualTo(expectedStatement); List> rows = mapper.selectManyMappedRows(selectStatement); diff --git a/src/test/java/examples/joins/JoinSubQueryTest.java b/src/test/java/examples/joins/JoinSubQueryTest.java new file mode 100644 index 000000000..aa8af557c --- /dev/null +++ b/src/test/java/examples/joins/JoinSubQueryTest.java @@ -0,0 +1,938 @@ +/* + * Copyright 2016-2020 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 + * + * http://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 examples.joins; + +import org.apache.ibatis.datasource.unpooled.UnpooledDataSource; +import org.apache.ibatis.jdbc.ScriptRunner; +import org.apache.ibatis.mapping.Environment; +import org.apache.ibatis.session.Configuration; +import org.apache.ibatis.session.SqlSession; +import org.apache.ibatis.session.SqlSessionFactory; +import org.apache.ibatis.session.SqlSessionFactoryBuilder; +import org.apache.ibatis.transaction.jdbc.JdbcTransactionFactory; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mybatis.dynamic.sql.render.RenderingStrategies; +import org.mybatis.dynamic.sql.select.render.SelectStatementProvider; +import org.mybatis.dynamic.sql.util.mybatis3.CommonSelectMapper; + +import java.io.InputStream; +import java.io.InputStreamReader; +import java.sql.Connection; +import java.sql.DriverManager; +import java.util.List; +import java.util.Map; + +import static examples.joins.ItemMasterDynamicSQLSupport.itemMaster; +import static examples.joins.OrderDetailDynamicSQLSupport.orderDetail; +import static examples.joins.OrderLineDynamicSQLSupport.orderLine; +import static examples.joins.OrderMasterDynamicSQLSupport.orderMaster; +import static examples.joins.UserDynamicSQLSupport.user; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mybatis.dynamic.sql.SqlBuilder.and; +import static org.mybatis.dynamic.sql.SqlBuilder.equalTo; +import static org.mybatis.dynamic.sql.SqlBuilder.isEqualTo; +import static org.mybatis.dynamic.sql.SqlBuilder.on; +import static org.mybatis.dynamic.sql.SqlBuilder.select; +import static org.mybatis.dynamic.sql.SqlBuilder.sortColumn; + +class JoinSubQueryTest { + + private static final String JDBC_URL = "jdbc:hsqldb:mem:aname"; + private static final String JDBC_DRIVER = "org.hsqldb.jdbcDriver"; + + private SqlSessionFactory sqlSessionFactory; + + @BeforeEach + void setup() throws Exception { + Class.forName(JDBC_DRIVER); + InputStream is = getClass().getResourceAsStream("/examples/joins/CreateJoinDB.sql"); + try (Connection connection = DriverManager.getConnection(JDBC_URL, "sa", "")) { + ScriptRunner sr = new ScriptRunner(connection); + sr.setLogWriter(null); + sr.runScript(new InputStreamReader(is)); + } + + UnpooledDataSource ds = new UnpooledDataSource(JDBC_DRIVER, JDBC_URL, "sa", ""); + Environment environment = new Environment("test", new JdbcTransactionFactory(), ds); + Configuration config = new Configuration(environment); + config.addMapper(JoinMapper.class); + config.addMapper(CommonSelectMapper.class); + sqlSessionFactory = new SqlSessionFactoryBuilder().build(config); + } + + @Test + void testSingleTableJoin1() { + try (SqlSession session = sqlSessionFactory.openSession()) { + JoinMapper mapper = session.getMapper(JoinMapper.class); + + SelectStatementProvider selectStatement = select(orderMaster.orderId, orderMaster.orderDate, + orderDetail.lineNumber, orderDetail.description, orderDetail.quantity) + .from(orderMaster, "om") + .join(select(orderDetail.orderId, orderDetail.lineNumber, orderDetail.description, orderDetail.quantity) + .from(orderDetail), + "od").on(orderMaster.orderId, equalTo(orderDetail.orderId.qualifiedWith("od"))) + .build() + .render(RenderingStrategies.MYBATIS3); + + String expectedStatement = "select om.order_id, om.order_date, line_number, description, quantity" + + " from OrderMaster om join " + + "(select order_id, line_number, description, quantity from OrderDetail) od on om.order_id = od.order_id"; + assertThat(selectStatement.getSelectStatement()).isEqualTo(expectedStatement); + + List rows = mapper.selectMany(selectStatement); + + assertThat(rows).hasSize(2); + OrderMaster orderMaster = rows.get(0); + assertThat(orderMaster.getId()).isEqualTo(1); + assertThat(orderMaster.getDetails()).hasSize(2); + OrderDetail orderDetail = orderMaster.getDetails().get(0); + assertThat(orderDetail.getLineNumber()).isEqualTo(1); + orderDetail = orderMaster.getDetails().get(1); + assertThat(orderDetail.getLineNumber()).isEqualTo(2); + + orderMaster = rows.get(1); + assertThat(orderMaster.getId()).isEqualTo(2); + assertThat(orderMaster.getDetails()).hasSize(1); + orderDetail = orderMaster.getDetails().get(0); + assertThat(orderDetail.getLineNumber()).isEqualTo(1); + } + } + + @Test + void testMultipleTableJoinWithWhereClause() { + try (SqlSession session = sqlSessionFactory.openSession()) { + JoinMapper mapper = session.getMapper(JoinMapper.class); + + SelectStatementProvider selectStatement = select(orderMaster.orderId, orderMaster.orderDate, + orderLine.lineNumber, itemMaster.description, orderLine.quantity) + .from(orderMaster, "om") + .join(select(orderLine.orderId, orderLine.itemId, orderLine.quantity, orderLine.lineNumber) + .from(orderLine), + "ol") + .on(orderMaster.orderId, equalTo(orderLine.orderId.qualifiedWith("ol"))) + .join(select(itemMaster.itemId, itemMaster.description) + .from(itemMaster), + "im") + .on(orderLine.itemId.qualifiedWith("ol"), equalTo(itemMaster.itemId.qualifiedWith("im"))) + .where(orderMaster.orderId, isEqualTo(2)) + .build() + .render(RenderingStrategies.MYBATIS3); + + String expectedStatement = "select om.order_id, om.order_date, line_number, description, quantity" + + " from OrderMaster om join " + + "(select order_id, item_id, quantity, line_number from OrderLine) ol on om.order_id = ol.order_id " + + "join (select item_id, description from ItemMaster) im on ol.item_id = im.item_id" + + " where om.order_id = #{parameters.p1,jdbcType=INTEGER}"; + assertThat(selectStatement.getSelectStatement()).isEqualTo(expectedStatement); + + List rows = mapper.selectMany(selectStatement); + + assertThat(rows).hasSize(1); + OrderMaster orderMaster = rows.get(0); + assertThat(orderMaster.getId()).isEqualTo(2); + assertThat(orderMaster.getDetails()).hasSize(2); + OrderDetail orderDetail = orderMaster.getDetails().get(0); + assertThat(orderDetail.getLineNumber()).isEqualTo(1); + orderDetail = orderMaster.getDetails().get(1); + assertThat(orderDetail.getLineNumber()).isEqualTo(2); + } + } + + @Test + void testMultipleTableJoinWithSelectStar() { + try (SqlSession session = sqlSessionFactory.openSession()) { + JoinMapper mapper = session.getMapper(JoinMapper.class); + + SelectStatementProvider selectStatement = select(orderMaster.orderId, orderMaster.orderDate, orderLine.lineNumber, itemMaster.description, orderLine.quantity) + .from(orderMaster, "om") + .join(select(orderLine.allColumns()).from(orderLine), "ol") + .on(orderMaster.orderId, equalTo(orderLine.orderId.qualifiedWith("ol"))) + .join(select(itemMaster.allColumns()).from(itemMaster), "im") + .on(orderLine.itemId.qualifiedWith("ol"), equalTo(itemMaster.itemId.qualifiedWith("im"))) + .where(orderMaster.orderId, isEqualTo(2)) + .orderBy(orderMaster.orderId) + .build() + .render(RenderingStrategies.MYBATIS3); + + String expectedStatement = "select om.order_id, om.order_date, line_number, description, quantity" + + " from OrderMaster om join (select * from OrderLine) ol on om.order_id = ol.order_id" + + " join (select * from ItemMaster) im on ol.item_id = im.item_id" + + " where om.order_id = #{parameters.p1,jdbcType=INTEGER}" + + " order by order_id"; + assertThat(selectStatement.getSelectStatement()).isEqualTo(expectedStatement); + + List rows = mapper.selectMany(selectStatement); + + assertThat(rows).hasSize(1); + OrderMaster orderMaster = rows.get(0); + assertThat(orderMaster.getId()).isEqualTo(2); + assertThat(orderMaster.getDetails()).hasSize(2); + + OrderDetail orderDetail = orderMaster.getDetails().get(0); + assertThat(orderDetail.getLineNumber()).isEqualTo(1); + + orderDetail = orderMaster.getDetails().get(1); + assertThat(orderDetail.getLineNumber()).isEqualTo(2); + } + } + + @Test + void testRightJoin() { + try (SqlSession session = sqlSessionFactory.openSession()) { + CommonSelectMapper mapper = session.getMapper(CommonSelectMapper.class); + + SelectStatementProvider selectStatement = select(orderLine.orderId, orderLine.quantity, + itemMaster.itemId.qualifiedWith("im"), itemMaster.description) + .from(orderLine, "ol") + .rightJoin(select(itemMaster.allColumns()).from(itemMaster), "im") + .on(orderLine.itemId, equalTo(itemMaster.itemId.qualifiedWith("im"))) + .orderBy(itemMaster.itemId) + .build() + .render(RenderingStrategies.MYBATIS3); + + String expectedStatement = "select ol.order_id, ol.quantity, im.item_id, description" + + " from OrderLine ol right join (select * from ItemMaster) im on ol.item_id = im.item_id" + + " order by item_id"; + assertThat(selectStatement.getSelectStatement()).isEqualTo(expectedStatement); + + List> rows = mapper.selectManyMappedRows(selectStatement); + + assertThat(rows).hasSize(5); + Map row = rows.get(2); + assertThat(row).containsEntry("ORDER_ID", 1); + assertThat(row).containsEntry("QUANTITY", 1); + assertThat(row).containsEntry("DESCRIPTION", "First Base Glove"); + assertThat(row).containsEntry("ITEM_ID", 33); + + row = rows.get(4); + assertThat(row).doesNotContainKey("ORDER_ID"); + assertThat(row).doesNotContainKey("QUANTITY"); + assertThat(row).containsEntry("DESCRIPTION", "Catcher Glove"); + assertThat(row).containsEntry("ITEM_ID", 55); + } + } + + @Test + void testRightJoin2() { + try (SqlSession session = sqlSessionFactory.openSession()) { + CommonSelectMapper mapper = session.getMapper(CommonSelectMapper.class); + + SelectStatementProvider selectStatement = select(orderLine.orderId, orderLine.quantity, + itemMaster.itemId.qualifiedWith(("im")), itemMaster.description) + .from(orderMaster, "om") + .join(orderLine, "ol").on(orderMaster.orderId, equalTo(orderLine.orderId)) + .rightJoin(select(itemMaster.allColumns()).from(itemMaster), "im") + .on(orderLine.itemId, equalTo(itemMaster.itemId.qualifiedWith("im"))) + .orderBy(orderLine.orderId, itemMaster.itemId) + .build() + .render(RenderingStrategies.MYBATIS3); + + String expectedStatement = "select ol.order_id, ol.quantity, im.item_id, description" + + " from OrderMaster om join OrderLine ol on om.order_id = ol.order_id" + + " right join (select * from ItemMaster) im on ol.item_id = im.item_id" + + " order by order_id, item_id"; + assertThat(selectStatement.getSelectStatement()).isEqualTo(expectedStatement); + + List> rows = mapper.selectManyMappedRows(selectStatement); + + assertThat(rows).hasSize(5); + Map row = rows.get(0); + assertThat(row).doesNotContainKey("ORDER_ID"); + assertThat(row).doesNotContainKey("QUANTITY"); + assertThat(row).containsEntry("DESCRIPTION", "Catcher Glove"); + assertThat(row).containsEntry("ITEM_ID", 55); + + row = rows.get(4); + assertThat(row).containsEntry("ORDER_ID", 2); + assertThat(row).containsEntry("QUANTITY", 1); + assertThat(row).containsEntry("DESCRIPTION", "Outfield Glove"); + assertThat(row).containsEntry("ITEM_ID", 44); + } + } + +// @Test +// void testRightJoin3() { +// try (SqlSession session = sqlSessionFactory.openSession()) { +// CommonSelectMapper mapper = session.getMapper(CommonSelectMapper.class); +// +// SelectStatementProvider selectStatement = select(orderLine.orderId, orderLine.quantity, itemMaster.itemId, itemMaster.description) +// .from(orderMaster, "om") +// .join(orderLine, "ol", on(orderMaster.orderId, equalTo(orderLine.orderId))) +// .rightJoin(itemMaster, "im", on(orderLine.itemId, equalTo(itemMaster.itemId))) +// .orderBy(orderLine.orderId, itemMaster.itemId) +// .build() +// .render(RenderingStrategies.MYBATIS3); +// +// String expectedStatement = "select ol.order_id, ol.quantity, im.item_id, im.description" +// + " from OrderMaster om join OrderLine ol on om.order_id = ol.order_id" +// + " right join ItemMaster im on ol.item_id = im.item_id" +// + " order by order_id, item_id"; +// assertThat(selectStatement.getSelectStatement()).isEqualTo(expectedStatement); +// +// List> rows = mapper.selectManyMappedRows(selectStatement); +// +// assertThat(rows).hasSize(5); +// Map row = rows.get(0); +// assertThat(row).doesNotContainKey("ORDER_ID"); +// assertThat(row).doesNotContainKey("QUANTITY"); +// assertThat(row).containsEntry("DESCRIPTION", "Catcher Glove"); +// assertThat(row).containsEntry("ITEM_ID", 55); +// +// row = rows.get(4); +// assertThat(row).containsEntry("ORDER_ID", 2); +// assertThat(row).containsEntry("QUANTITY", 1); +// assertThat(row).containsEntry("DESCRIPTION", "Outfield Glove"); +// assertThat(row).containsEntry("ITEM_ID", 44); +// } +// } +// +// @Test +// void testRightJoinNoAliases() { +// try (SqlSession session = sqlSessionFactory.openSession()) { +// CommonSelectMapper mapper = session.getMapper(CommonSelectMapper.class); +// +// SelectStatementProvider selectStatement = select(orderLine.orderId, orderLine.quantity, itemMaster.itemId, itemMaster.description) +// .from(orderMaster) +// .join(orderLine).on(orderMaster.orderId, equalTo(orderLine.orderId)) +// .rightJoin(itemMaster).on(orderLine.itemId, equalTo(itemMaster.itemId)) +// .orderBy(orderLine.orderId, itemMaster.itemId) +// .build() +// .render(RenderingStrategies.MYBATIS3); +// +// String expectedStatement = "select OrderLine.order_id, OrderLine.quantity, ItemMaster.item_id, ItemMaster.description" +// + " from OrderMaster join OrderLine on OrderMaster.order_id = OrderLine.order_id" +// + " right join ItemMaster on OrderLine.item_id = ItemMaster.item_id" +// + " order by order_id, item_id"; +// assertThat(selectStatement.getSelectStatement()).isEqualTo(expectedStatement); +// +// List> rows = mapper.selectManyMappedRows(selectStatement); +// +// assertThat(rows).hasSize(5); +// Map row = rows.get(0); +// assertThat(row).doesNotContainKey("ORDER_ID"); +// assertThat(row).doesNotContainKey("QUANTITY"); +// assertThat(row).containsEntry("DESCRIPTION", "Catcher Glove"); +// assertThat(row).containsEntry("ITEM_ID", 55); +// +// row = rows.get(4); +// assertThat(row).containsEntry("ORDER_ID", 2); +// assertThat(row).containsEntry("QUANTITY", 1); +// assertThat(row).containsEntry("DESCRIPTION", "Outfield Glove"); +// assertThat(row).containsEntry("ITEM_ID", 44); +// } +// } +// + @Test + void testLeftJoin() { + try (SqlSession session = sqlSessionFactory.openSession()) { + CommonSelectMapper mapper = session.getMapper(CommonSelectMapper.class); + + SelectStatementProvider selectStatement = select(orderLine.orderId, orderLine.quantity, + itemMaster.itemId.qualifiedWith("im"), itemMaster.description) + .from(itemMaster, "im") + .leftJoin(select(orderLine.allColumns()).from(orderLine), "ol") + .on(orderLine.itemId.qualifiedWith("ol"), equalTo(itemMaster.itemId)) + .orderBy(itemMaster.itemId) + .build() + .render(RenderingStrategies.MYBATIS3); + + String expectedStatement = "select order_id, quantity, im.item_id, im.description" + + " from ItemMaster im" + + " left join (select * from OrderLine) ol on ol.item_id = im.item_id" + + " order by item_id"; + assertThat(selectStatement.getSelectStatement()).isEqualTo(expectedStatement); + + List> rows = mapper.selectManyMappedRows(selectStatement); + + assertThat(rows).hasSize(5); + Map row = rows.get(2); + assertThat(row).containsEntry("ORDER_ID", 1); + assertThat(row).containsEntry("QUANTITY", 1); + assertThat(row).containsEntry("DESCRIPTION", "First Base Glove"); + assertThat(row).containsEntry("ITEM_ID", 33); + + row = rows.get(4); + assertThat(row).doesNotContainKey("ORDER_ID"); + assertThat(row).doesNotContainKey("QUANTITY"); + assertThat(row).containsEntry("DESCRIPTION", "Catcher Glove"); + assertThat(row).containsEntry("ITEM_ID", 55); + } + } + + @Test + void testLeftJoin2() { + try (SqlSession session = sqlSessionFactory.openSession()) { + CommonSelectMapper mapper = session.getMapper(CommonSelectMapper.class); + + SelectStatementProvider selectStatement = select(orderLine.orderId, orderLine.quantity, + itemMaster.itemId.qualifiedWith("im"), itemMaster.description) + .from(orderMaster, "om") + .join(orderLine, "ol").on(orderMaster.orderId, equalTo(orderLine.orderId)) + .leftJoin(select(itemMaster.allColumns()).from(itemMaster), "im") + .on(orderLine.itemId, equalTo(itemMaster.itemId.qualifiedWith("im"))) + .orderBy(orderLine.orderId, itemMaster.itemId) + .build() + .render(RenderingStrategies.MYBATIS3); + + String expectedStatement = "select ol.order_id, ol.quantity, im.item_id, description" + + " from OrderMaster om join OrderLine ol on om.order_id = ol.order_id" + + " left join (select * from ItemMaster) im on ol.item_id = im.item_id" + + " order by order_id, item_id"; + assertThat(selectStatement.getSelectStatement()).isEqualTo(expectedStatement); + + List> rows = mapper.selectManyMappedRows(selectStatement); + + assertThat(rows).hasSize(5); + Map row = rows.get(2); + assertThat(row).containsEntry("ORDER_ID", 2); + assertThat(row).containsEntry("QUANTITY", 6); + assertThat(row).doesNotContainKey("DESCRIPTION"); + assertThat(row).doesNotContainKey("ITEM_ID"); + + row = rows.get(4); + assertThat(row).containsEntry("ORDER_ID", 2); + assertThat(row).containsEntry("QUANTITY", 1); + assertThat(row).containsEntry("DESCRIPTION", "Outfield Glove"); + assertThat(row).containsEntry("ITEM_ID", 44); + } + } + +// @Test +// void testLeftJoin3() { +// try (SqlSession session = sqlSessionFactory.openSession()) { +// CommonSelectMapper mapper = session.getMapper(CommonSelectMapper.class); +// +// SelectStatementProvider selectStatement = select(orderLine.orderId, orderLine.quantity, itemMaster.itemId, itemMaster.description) +// .from(orderMaster, "om") +// .join(orderLine, "ol", on(orderMaster.orderId, equalTo(orderLine.orderId))) +// .leftJoin(itemMaster, "im", on(orderLine.itemId, equalTo(itemMaster.itemId))) +// .orderBy(orderLine.orderId, itemMaster.itemId) +// .build() +// .render(RenderingStrategies.MYBATIS3); +// +// String expectedStatement = "select ol.order_id, ol.quantity, im.item_id, im.description" +// + " from OrderMaster om join OrderLine ol on om.order_id = ol.order_id" +// + " left join ItemMaster im on ol.item_id = im.item_id" +// + " order by order_id, item_id"; +// assertThat(selectStatement.getSelectStatement()).isEqualTo(expectedStatement); +// +// List> rows = mapper.selectManyMappedRows(selectStatement); +// +// assertThat(rows).hasSize(5); +// Map row = rows.get(2); +// assertThat(row).containsEntry("ORDER_ID", 2); +// assertThat(row).containsEntry("QUANTITY", 6); +// assertThat(row).doesNotContainKey("DESCRIPTION"); +// assertThat(row).doesNotContainKey("ITEM_ID"); +// +// row = rows.get(4); +// assertThat(row).containsEntry("ORDER_ID", 2); +// assertThat(row).containsEntry("QUANTITY", 1); +// assertThat(row).containsEntry("DESCRIPTION", "Outfield Glove"); +// assertThat(row).containsEntry("ITEM_ID", 44); +// } +// } +// +// @Test +// void testLeftJoinNoAliases() { +// try (SqlSession session = sqlSessionFactory.openSession()) { +// CommonSelectMapper mapper = session.getMapper(CommonSelectMapper.class); +// +// SelectStatementProvider selectStatement = select(orderLine.orderId, orderLine.quantity, itemMaster.itemId, itemMaster.description) +// .from(orderMaster) +// .join(orderLine).on(orderMaster.orderId, equalTo(orderLine.orderId)) +// .leftJoin(itemMaster).on(orderLine.itemId, equalTo(itemMaster.itemId)) +// .orderBy(orderLine.orderId, itemMaster.itemId) +// .build() +// .render(RenderingStrategies.MYBATIS3); +// +// String expectedStatement = "select OrderLine.order_id, OrderLine.quantity, ItemMaster.item_id, ItemMaster.description" +// + " from OrderMaster join OrderLine on OrderMaster.order_id = OrderLine.order_id" +// + " left join ItemMaster on OrderLine.item_id = ItemMaster.item_id" +// + " order by order_id, item_id"; +// assertThat(selectStatement.getSelectStatement()).isEqualTo(expectedStatement); +// +// List> rows = mapper.selectManyMappedRows(selectStatement); +// +// assertThat(rows).hasSize(5); +// Map row = rows.get(2); +// assertThat(row).containsEntry("ORDER_ID", 2); +// assertThat(row).containsEntry("QUANTITY", 6); +// assertThat(row).doesNotContainKey("DESCRIPTION"); +// assertThat(row).doesNotContainKey("ITEM_ID"); +// +// row = rows.get(4); +// assertThat(row).containsEntry("ORDER_ID", 2); +// assertThat(row).containsEntry("QUANTITY", 1); +// assertThat(row).containsEntry("DESCRIPTION", "Outfield Glove"); +// assertThat(row).containsEntry("ITEM_ID", 44); +// } +// } + + @Test + void testFullJoin() { + try (SqlSession session = sqlSessionFactory.openSession()) { + CommonSelectMapper mapper = session.getMapper(CommonSelectMapper.class); + + SelectStatementProvider selectStatement = select(orderLine.orderId, orderLine.quantity, + orderLine.itemId.as("ol_itemid").qualifiedWith("ol"), itemMaster.itemId.as("im_itemid"), itemMaster.description) + .from(itemMaster, "im") + .fullJoin(select(orderLine.allColumns()).from(orderLine), "ol") + .on(itemMaster.itemId, equalTo(orderLine.itemId.qualifiedWith("ol"))) + .orderBy(sortColumn("im_itemid")) + .build() + .render(RenderingStrategies.MYBATIS3); + + String expectedStatement = "select order_id, quantity, ol.item_id as ol_itemid, im.item_id as im_itemid, im.description" + + " from ItemMaster im" + + " full join (select * from OrderLine) ol on im.item_id = ol.item_id" + + " order by im_itemid"; + assertThat(selectStatement.getSelectStatement()).isEqualTo(expectedStatement); + + List> rows = mapper.selectManyMappedRows(selectStatement); + + assertThat(rows).hasSize(6); + Map row = rows.get(0); + assertThat(row).containsEntry("ORDER_ID", 2); + assertThat(row).containsEntry("QUANTITY", 6); + assertThat(row).containsEntry("OL_ITEMID", 66); + assertThat(row).doesNotContainKey("DESCRIPTION"); + assertThat(row).doesNotContainKey("IM_ITEMID"); + + row = rows.get(3); + assertThat(row).containsEntry("ORDER_ID", 1); + assertThat(row).containsEntry("QUANTITY", 1); + assertThat(row).containsEntry("DESCRIPTION", "First Base Glove"); + assertThat(row).containsEntry("IM_ITEMID", 33); + + row = rows.get(5); + assertThat(row).doesNotContainKey("ORDER_ID"); + assertThat(row).doesNotContainKey("QUANTITY"); + assertThat(row).containsEntry("DESCRIPTION", "Catcher Glove"); + assertThat(row).containsEntry("IM_ITEMID", 55); + } + } + + @Test + void testFullJoin2() { + try (SqlSession session = sqlSessionFactory.openSession()) { + CommonSelectMapper mapper = session.getMapper(CommonSelectMapper.class); + + SelectStatementProvider selectStatement = select(orderLine.orderId, orderLine.quantity, + itemMaster.itemId.qualifiedWith("im"), itemMaster.description) + .from(orderMaster, "om") + .join(orderLine, "ol").on(orderMaster.orderId, equalTo(orderLine.orderId)) + .fullJoin(select(itemMaster.allColumns()).from(itemMaster), "im") + .on(orderLine.itemId, equalTo(itemMaster.itemId.qualifiedWith("im"))) + .orderBy(orderLine.orderId, itemMaster.itemId) + .build() + .render(RenderingStrategies.MYBATIS3); + + String expectedStatement = "select ol.order_id, ol.quantity, im.item_id, description" + + " from OrderMaster om join OrderLine ol on om.order_id = ol.order_id" + + " full join (select * from ItemMaster) im on ol.item_id = im.item_id" + + " order by order_id, item_id"; + assertThat(selectStatement.getSelectStatement()).isEqualTo(expectedStatement); + + List> rows = mapper.selectManyMappedRows(selectStatement); + + assertThat(rows).hasSize(6); + Map row = rows.get(0); + assertThat(row).doesNotContainKey("ORDER_ID"); + assertThat(row).doesNotContainKey("QUANTITY"); + assertThat(row).containsEntry("DESCRIPTION", "Catcher Glove"); + assertThat(row).containsEntry("ITEM_ID", 55); + + row = rows.get(3); + assertThat(row).containsEntry("ORDER_ID", 2); + assertThat(row).containsEntry("QUANTITY", 6); + assertThat(row).doesNotContainKey("DESCRIPTION"); + assertThat(row).doesNotContainKey("ITEM_ID"); + + row = rows.get(5); + assertThat(row).containsEntry("ORDER_ID", 2); + assertThat(row).containsEntry("QUANTITY", 1); + assertThat(row).containsEntry("DESCRIPTION", "Outfield Glove"); + assertThat(row).containsEntry("ITEM_ID", 44); + } + } + +// @Test +// void testFullJoin3() { +// try (SqlSession session = sqlSessionFactory.openSession()) { +// CommonSelectMapper mapper = session.getMapper(CommonSelectMapper.class); +// +// SelectStatementProvider selectStatement = select(orderLine.orderId, orderLine.quantity, itemMaster.itemId, itemMaster.description) +// .from(orderMaster, "om") +// .join(orderLine, "ol", on(orderMaster.orderId, equalTo(orderLine.orderId))) +// .fullJoin(itemMaster, "im", on(orderLine.itemId, equalTo(itemMaster.itemId))) +// .orderBy(orderLine.orderId, itemMaster.itemId) +// .build() +// .render(RenderingStrategies.MYBATIS3); +// +// String expectedStatement = "select ol.order_id, ol.quantity, im.item_id, im.description" +// + " from OrderMaster om join OrderLine ol on om.order_id = ol.order_id" +// + " full join ItemMaster im on ol.item_id = im.item_id" +// + " order by order_id, item_id"; +// assertThat(selectStatement.getSelectStatement()).isEqualTo(expectedStatement); +// +// List> rows = mapper.selectManyMappedRows(selectStatement); +// +// assertThat(rows).hasSize(6); +// Map row = rows.get(0); +// assertThat(row).doesNotContainKey("ORDER_ID"); +// assertThat(row).doesNotContainKey("QUANTITY"); +// assertThat(row).containsEntry("DESCRIPTION", "Catcher Glove"); +// assertThat(row).containsEntry("ITEM_ID", 55); +// +// row = rows.get(3); +// assertThat(row).containsEntry("ORDER_ID", 2); +// assertThat(row).containsEntry("QUANTITY", 6); +// assertThat(row).doesNotContainKey("DESCRIPTION"); +// assertThat(row).doesNotContainKey("ITEM_ID"); +// +// row = rows.get(5); +// assertThat(row).containsEntry("ORDER_ID", 2); +// assertThat(row).containsEntry("QUANTITY", 1); +// assertThat(row).containsEntry("DESCRIPTION", "Outfield Glove"); +// assertThat(row).containsEntry("ITEM_ID", 44); +// } +// } +// +// @Test +// void testFullJoin4() { +// try (SqlSession session = sqlSessionFactory.openSession()) { +// CommonSelectMapper mapper = session.getMapper(CommonSelectMapper.class); +// +// SelectStatementProvider selectStatement = select(orderLine.orderId, orderLine.quantity, itemMaster.description) +// .from(orderMaster, "om") +// .join(orderLine, "ol", on(orderMaster.orderId, equalTo(orderLine.orderId))) +// .fullJoin(itemMaster, "im", on(orderLine.itemId, equalTo(itemMaster.itemId))) +// .orderBy(orderLine.orderId, sortColumn("im", itemMaster.itemId)) +// .build() +// .render(RenderingStrategies.MYBATIS3); +// +// String expectedStatement = "select ol.order_id, ol.quantity, im.description" +// + " from OrderMaster om join OrderLine ol on om.order_id = ol.order_id" +// + " full join ItemMaster im on ol.item_id = im.item_id" +// + " order by order_id, im.item_id"; +// assertThat(selectStatement.getSelectStatement()).isEqualTo(expectedStatement); +// +// List> rows = mapper.selectManyMappedRows(selectStatement); +// +// assertThat(rows).hasSize(6); +// Map row = rows.get(0); +// assertThat(row).doesNotContainKey("ORDER_ID"); +// assertThat(row).doesNotContainKey("QUANTITY"); +// assertThat(row).containsEntry("DESCRIPTION", "Catcher Glove"); +// +// row = rows.get(3); +// assertThat(row).containsEntry("ORDER_ID", 2); +// assertThat(row).containsEntry("QUANTITY", 6); +// assertThat(row).doesNotContainKey("DESCRIPTION"); +// +// row = rows.get(5); +// assertThat(row).containsEntry("ORDER_ID", 2); +// assertThat(row).containsEntry("QUANTITY", 1); +// assertThat(row).containsEntry("DESCRIPTION", "Outfield Glove"); +// } +// } +// +// @Test +// void testFullJoin5() { +// try (SqlSession session = sqlSessionFactory.openSession()) { +// CommonSelectMapper mapper = session.getMapper(CommonSelectMapper.class); +// +// SelectStatementProvider selectStatement = select(orderLine.orderId, orderLine.quantity, itemMaster.description) +// .from(orderMaster, "om") +// .join(orderLine, "ol", on(orderMaster.orderId, equalTo(orderLine.orderId))) +// .fullJoin(itemMaster, "im", on(orderLine.itemId, equalTo(itemMaster.itemId))) +// .orderBy(orderLine.orderId, sortColumn("im", itemMaster.itemId).descending()) +// .build() +// .render(RenderingStrategies.MYBATIS3); +// +// String expectedStatement = "select ol.order_id, ol.quantity, im.description" +// + " from OrderMaster om join OrderLine ol on om.order_id = ol.order_id" +// + " full join ItemMaster im on ol.item_id = im.item_id" +// + " order by order_id, im.item_id DESC"; +// assertThat(selectStatement.getSelectStatement()).isEqualTo(expectedStatement); +// +// List> rows = mapper.selectManyMappedRows(selectStatement); +// +// assertThat(rows).hasSize(6); +// Map row = rows.get(0); +// assertThat(row).doesNotContainKey("ORDER_ID"); +// assertThat(row).doesNotContainKey("QUANTITY"); +// assertThat(row).containsEntry("DESCRIPTION", "Catcher Glove"); +// +// row = rows.get(3); +// assertThat(row).containsEntry("ORDER_ID", 2); +// assertThat(row).containsEntry("QUANTITY", 6); +// assertThat(row).doesNotContainKey("DESCRIPTION"); +// +// row = rows.get(5); +// assertThat(row).containsEntry("ORDER_ID", 2); +// assertThat(row).containsEntry("QUANTITY", 1); +// assertThat(row).containsEntry("DESCRIPTION", "Helmet"); +// } +// } +// +// @Test +// void testFullJoinNoAliases() { +// try (SqlSession session = sqlSessionFactory.openSession()) { +// CommonSelectMapper mapper = session.getMapper(CommonSelectMapper.class); +// +// SelectStatementProvider selectStatement = select(orderLine.orderId, orderLine.quantity, itemMaster.itemId, itemMaster.description) +// .from(orderMaster) +// .join(orderLine).on(orderMaster.orderId, equalTo(orderLine.orderId)) +// .fullJoin(itemMaster).on(orderLine.itemId, equalTo(itemMaster.itemId)) +// .orderBy(orderLine.orderId, itemMaster.itemId) +// .build() +// .render(RenderingStrategies.MYBATIS3); +// +// String expectedStatement = "select OrderLine.order_id, OrderLine.quantity, ItemMaster.item_id, ItemMaster.description" +// + " from OrderMaster join OrderLine on OrderMaster.order_id = OrderLine.order_id" +// + " full join ItemMaster on OrderLine.item_id = ItemMaster.item_id" +// + " order by order_id, item_id"; +// assertThat(selectStatement.getSelectStatement()).isEqualTo(expectedStatement); +// +// List> rows = mapper.selectManyMappedRows(selectStatement); +// +// assertThat(rows).hasSize(6); +// Map row = rows.get(0); +// assertThat(row).doesNotContainKey("ORDER_ID"); +// assertThat(row).doesNotContainKey("QUANTITY"); +// assertThat(row).containsEntry("DESCRIPTION", "Catcher Glove"); +// assertThat(row).containsEntry("ITEM_ID", 55); +// +// row = rows.get(3); +// assertThat(row).containsEntry("ORDER_ID", 2); +// assertThat(row).containsEntry("QUANTITY", 6); +// assertThat(row).doesNotContainKey("DESCRIPTION"); +// assertThat(row).doesNotContainKey("ITEM_ID"); +// +// row = rows.get(5); +// assertThat(row).containsEntry("ORDER_ID", 2); +// assertThat(row).containsEntry("QUANTITY", 1); +// assertThat(row).containsEntry("DESCRIPTION", "Outfield Glove"); +// assertThat(row).containsEntry("ITEM_ID", 44); +// } +// } +// +// @Test +// void testSelf() { +// try (SqlSession session = sqlSessionFactory.openSession()) { +// JoinMapper mapper = session.getMapper(JoinMapper.class); +// +// // create second table instance for self-join +// UserDynamicSQLSupport.User user2 = new UserDynamicSQLSupport.User(); +// +// // get Bamm Bamm's parent - should be Barney +// SelectStatementProvider selectStatement = select(user.userId, user.userName, user.parentId) +// .from(user, "u1") +// .join(user2, "u2").on(user.userId, equalTo(user2.parentId)) +// .where(user2.userId, isEqualTo(4)) +// .build() +// .render(RenderingStrategies.MYBATIS3); +// +// String expectedStatement = "select u1.user_id, u1.user_name, u1.parent_id" +// + " from User u1 join User u2 on u1.user_id = u2.parent_id" +// + " where u2.user_id = #{parameters.p1,jdbcType=INTEGER}"; +// assertThat(selectStatement.getSelectStatement()).isEqualTo(expectedStatement); +// +// List rows = mapper.selectUsers(selectStatement); +// +// assertThat(rows).hasSize(1); +// User row = rows.get(0); +// assertThat(row.getUserId()).isEqualTo(2); +// assertThat(row.getUserName()).isEqualTo("Barney"); +// assertThat(row.getParentId()).isNull(); +// } +// } +// +// @Test +// void testLimitAndOffsetAfterJoin() { +// try (SqlSession session = sqlSessionFactory.openSession()) { +// CommonSelectMapper mapper = session.getMapper(CommonSelectMapper.class); +// +// SelectStatementProvider selectStatement = select(orderLine.orderId, orderLine.quantity, itemMaster.itemId, itemMaster.description) +// .from(itemMaster, "im") +// .leftJoin(orderLine, "ol").on(orderLine.itemId, equalTo(itemMaster.itemId)) +// .limit(2) +// .offset(1) +// .build() +// .render(RenderingStrategies.MYBATIS3); +// +// String expectedStatement = "select ol.order_id, ol.quantity, im.item_id, im.description" +// + " from ItemMaster im left join OrderLine ol on ol.item_id = im.item_id" +// + " limit #{parameters.p1} offset #{parameters.p2}"; +// assertThat(selectStatement.getSelectStatement()).isEqualTo(expectedStatement); +// +// List> rows = mapper.selectManyMappedRows(selectStatement); +// +// assertThat(rows).hasSize(2); +// Map row = rows.get(0); +// assertThat(row).containsEntry("ORDER_ID", 2); +// assertThat(row).containsEntry("QUANTITY", 1); +// assertThat(row).containsEntry("DESCRIPTION", "Helmet"); +// assertThat(row).containsEntry("ITEM_ID", 22); +// +// row = rows.get(1); +// assertThat(row).containsEntry("ORDER_ID", 1); +// assertThat(row).containsEntry("QUANTITY", 1); +// assertThat(row).containsEntry("DESCRIPTION", "First Base Glove"); +// assertThat(row).containsEntry("ITEM_ID", 33); +// } +// } +// +// @Test +// void testLimitOnlyAfterJoin() { +// try (SqlSession session = sqlSessionFactory.openSession()) { +// CommonSelectMapper mapper = session.getMapper(CommonSelectMapper.class); +// +// SelectStatementProvider selectStatement = select(orderLine.orderId, orderLine.quantity, itemMaster.itemId, itemMaster.description) +// .from(itemMaster, "im") +// .leftJoin(orderLine, "ol").on(orderLine.itemId, equalTo(itemMaster.itemId)) +// .limit(2) +// .build() +// .render(RenderingStrategies.MYBATIS3); +// +// String expectedStatement = "select ol.order_id, ol.quantity, im.item_id, im.description" +// + " from ItemMaster im left join OrderLine ol on ol.item_id = im.item_id" +// + " limit #{parameters.p1}"; +// assertThat(selectStatement.getSelectStatement()).isEqualTo(expectedStatement); +// +// List> rows = mapper.selectManyMappedRows(selectStatement); +// +// assertThat(rows).hasSize(2); +// Map row = rows.get(0); +// assertThat(row).containsEntry("ORDER_ID", 1); +// assertThat(row).containsEntry("QUANTITY", 1); +// assertThat(row).containsEntry("DESCRIPTION", "Helmet"); +// assertThat(row).containsEntry("ITEM_ID", 22); +// +// row = rows.get(1); +// assertThat(row).containsEntry("ORDER_ID", 2); +// assertThat(row).containsEntry("QUANTITY", 1); +// assertThat(row).containsEntry("DESCRIPTION", "Helmet"); +// assertThat(row).containsEntry("ITEM_ID", 22); +// } +// } +// +// @Test +// void testOffsetOnlyAfterJoin() { +// try (SqlSession session = sqlSessionFactory.openSession()) { +// CommonSelectMapper mapper = session.getMapper(CommonSelectMapper.class); +// +// SelectStatementProvider selectStatement = select(orderLine.orderId, orderLine.quantity, itemMaster.itemId, itemMaster.description) +// .from(itemMaster, "im") +// .leftJoin(orderLine, "ol").on(orderLine.itemId, equalTo(itemMaster.itemId)) +// .offset(2) +// .build() +// .render(RenderingStrategies.MYBATIS3); +// +// String expectedStatement = "select ol.order_id, ol.quantity, im.item_id, im.description" +// + " from ItemMaster im left join OrderLine ol on ol.item_id = im.item_id" +// + " offset #{parameters.p1} rows"; +// assertThat(selectStatement.getSelectStatement()).isEqualTo(expectedStatement); +// +// List> rows = mapper.selectManyMappedRows(selectStatement); +// +// assertThat(rows).hasSize(3); +// Map row = rows.get(0); +// assertThat(row).containsEntry("ORDER_ID", 1); +// assertThat(row).containsEntry("QUANTITY", 1); +// assertThat(row).containsEntry("DESCRIPTION", "First Base Glove"); +// assertThat(row).containsEntry("ITEM_ID", 33); +// +// row = rows.get(1); +// assertThat(row).containsEntry("ORDER_ID", 2); +// assertThat(row).containsEntry("QUANTITY", 1); +// assertThat(row).containsEntry("DESCRIPTION", "Outfield Glove"); +// assertThat(row).containsEntry("ITEM_ID", 44); +// } +// } +// +// @Test +// void testOffsetAndFetchFirstAfterJoin() { +// try (SqlSession session = sqlSessionFactory.openSession()) { +// CommonSelectMapper mapper = session.getMapper(CommonSelectMapper.class); +// +// SelectStatementProvider selectStatement = select(orderLine.orderId, orderLine.quantity, itemMaster.itemId, itemMaster.description) +// .from(itemMaster, "im") +// .leftJoin(orderLine, "ol").on(orderLine.itemId, equalTo(itemMaster.itemId)) +// .offset(1) +// .fetchFirst(2).rowsOnly() +// .build() +// .render(RenderingStrategies.MYBATIS3); +// +// String expectedStatement = "select ol.order_id, ol.quantity, im.item_id, im.description" +// + " from ItemMaster im left join OrderLine ol on ol.item_id = im.item_id" +// + " offset #{parameters.p1} rows fetch first #{parameters.p2} rows only"; +// assertThat(selectStatement.getSelectStatement()).isEqualTo(expectedStatement); +// +// List> rows = mapper.selectManyMappedRows(selectStatement); +// +// assertThat(rows).hasSize(2); +// Map row = rows.get(0); +// assertThat(row).containsEntry("ORDER_ID", 2); +// assertThat(row).containsEntry("QUANTITY", 1); +// assertThat(row).containsEntry("DESCRIPTION", "Helmet"); +// assertThat(row).containsEntry("ITEM_ID", 22); +// +// row = rows.get(1); +// assertThat(row).containsEntry("ORDER_ID", 1); +// assertThat(row).containsEntry("QUANTITY", 1); +// assertThat(row).containsEntry("DESCRIPTION", "First Base Glove"); +// assertThat(row).containsEntry("ITEM_ID", 33); +// } +// } +// +// @Test +// void testFetchFirstOnlyAfterJoin() { +// try (SqlSession session = sqlSessionFactory.openSession()) { +// CommonSelectMapper mapper = session.getMapper(CommonSelectMapper.class); +// +// SelectStatementProvider selectStatement = select(orderLine.orderId, orderLine.quantity, itemMaster.itemId, itemMaster.description) +// .from(itemMaster, "im") +// .leftJoin(orderLine, "ol").on(orderLine.itemId, equalTo(itemMaster.itemId)) +// .fetchFirst(2).rowsOnly() +// .build() +// .render(RenderingStrategies.MYBATIS3); +// +// String expectedStatement = "select ol.order_id, ol.quantity, im.item_id, im.description" +// + " from ItemMaster im left join OrderLine ol on ol.item_id = im.item_id" +// + " fetch first #{parameters.p1} rows only"; +// assertThat(selectStatement.getSelectStatement()).isEqualTo(expectedStatement); +// +// List> rows = mapper.selectManyMappedRows(selectStatement); +// +// assertThat(rows).hasSize(2); +// Map row = rows.get(0); +// assertThat(row).containsEntry("ORDER_ID", 1); +// assertThat(row).containsEntry("QUANTITY", 1); +// assertThat(row).containsEntry("DESCRIPTION", "Helmet"); +// assertThat(row).containsEntry("ITEM_ID", 22); +// +// row = rows.get(1); +// assertThat(row).containsEntry("ORDER_ID", 2); +// assertThat(row).containsEntry("QUANTITY", 1); +// assertThat(row).containsEntry("DESCRIPTION", "Helmet"); +// assertThat(row).containsEntry("ITEM_ID", 22); +// } +// } +} diff --git a/src/test/kotlin/examples/kotlin/mybatis3/canonical/PersonWithAddressMapperExtensions.kt b/src/test/kotlin/examples/kotlin/mybatis3/canonical/PersonWithAddressMapperExtensions.kt index afc007f1d..fac87a795 100644 --- a/src/test/kotlin/examples/kotlin/mybatis3/canonical/PersonWithAddressMapperExtensions.kt +++ b/src/test/kotlin/examples/kotlin/mybatis3/canonical/PersonWithAddressMapperExtensions.kt @@ -31,33 +31,36 @@ import org.mybatis.dynamic.sql.util.kotlin.mybatis3.selectList fun PersonWithAddressMapper.selectOne(completer: SelectCompleter): PersonWithAddress? { val start = KotlinSelectBuilder(select(id.`as`("A_ID"), firstName, lastName, birthDate, - employed, occupation, Address.id, Address.streetAddress, Address.city, Address.state)) - .from(Person) - .fullJoin(Address) { - on(Person.addressId, equalTo(Address.id)) - } + employed, occupation, Address.id, Address.streetAddress, Address.city, Address.state)).apply { + from(Person) + fullJoin(Address) { + on(Person.addressId, equalTo(Address.id)) + } + } return selectOne(this::selectOne, start, completer) } fun PersonWithAddressMapper.select(completer: SelectCompleter): List { val start = KotlinSelectBuilder(select(id.`as`("A_ID"), firstName, lastName, birthDate, - employed, occupation, Address.id, Address.streetAddress, Address.city, Address.state)) - .from(Person, "p") - .fullJoin(Address) { - on(Person.addressId, equalTo(Address.id)) - } + employed, occupation, Address.id, Address.streetAddress, Address.city, Address.state)).apply { + from(Person, "p") + fullJoin(Address) { + on(Person.addressId, equalTo(Address.id)) + } + } return selectList(this::selectMany, start, completer) } fun PersonWithAddressMapper.selectDistinct(completer: SelectCompleter): List { val start = KotlinSelectBuilder(selectDistinct(id.`as`("A_ID"), firstName, lastName, - birthDate, employed, occupation, Address.id, Address.streetAddress, Address.city, Address.state)) - .from(Person, "p") - .fullJoin(Address) { - on(Person.addressId, equalTo(Address.id)) - } + birthDate, employed, occupation, Address.id, Address.streetAddress, Address.city, Address.state)).apply { + from(Person, "p") + fullJoin(Address) { + on(Person.addressId, equalTo(Address.id)) + } + } return selectList(this::selectMany, start, completer) } diff --git a/src/test/kotlin/examples/kotlin/mybatis3/joins/JoinMapperTest.kt b/src/test/kotlin/examples/kotlin/mybatis3/joins/JoinMapperTest.kt index e43125c98..dd4f80a70 100644 --- a/src/test/kotlin/examples/kotlin/mybatis3/joins/JoinMapperTest.kt +++ b/src/test/kotlin/examples/kotlin/mybatis3/joins/JoinMapperTest.kt @@ -222,6 +222,87 @@ class JoinMapperTest { } } + @Test + fun testFullJoinWithSubQuery() { + newSession().use { session -> + val mapper = session.getMapper(JoinMapper::class.java) + + val selectStatement = select(OrderLine.orderId.qualifiedWith("ol"), + OrderLine.quantity, ItemMaster.itemId.qualifiedWith("im"), + ItemMaster.description) { + from { + select(OrderMaster.allColumns()) { + from(OrderMaster) + } + +"om" + } + join(subQuery = { + select(OrderLine.allColumns()) { + from(OrderLine) + } + + "ol" + }, joinCriteria = { + on( + OrderMaster.orderId.qualifiedWith("om"), + equalTo(OrderLine.orderId.qualifiedWith("ol")) + ) + }) + fullJoin({ + select(ItemMaster.allColumns()) { + from(ItemMaster) + } + + "im" + }) { + on(OrderLine.itemId.qualifiedWith("ol"), + equalTo(ItemMaster.itemId.qualifiedWith("im"))) + } + orderBy(OrderLine.orderId, ItemMaster.itemId) + } + + val expectedStatement = "select ol.order_id, quantity, im.item_id, description" + + " from (select * from OrderMaster) om" + + " join (select * from OrderLine) ol on om.order_id = ol.order_id" + + " full join (select * from ItemMaster) im on ol.item_id = im.item_id" + + " order by order_id, item_id" + + assertThat(selectStatement.selectStatement).isEqualTo(expectedStatement) + + data class OrderDetail(val itemId: Int?, val orderId: Int?, val quantity: Int?, val description: String?) + + val rows = mapper.selectMany(selectStatement) { + OrderDetail( + it["ITEM_ID"] as Int?, + it["ORDER_ID"] as Int?, + it["QUANTITY"] as Int?, + it["DESCRIPTION"] as String? + ) + } + + assertThat(rows).hasSize(6) + + with(rows[0]) { + assertThat(itemId).isEqualTo(55) + assertThat(orderId).isNull() + assertThat(quantity).isNull() + assertThat(description).isEqualTo("Catcher Glove") + } + + with(rows[3]) { + assertThat(itemId).isNull() + assertThat(orderId).isEqualTo(2) + assertThat(quantity).isEqualTo(6) + assertThat(description).isNull() + } + + with(rows[5]) { + assertThat(itemId).isEqualTo(44) + assertThat(orderId).isEqualTo(2) + assertThat(quantity).isEqualTo(1) + assertThat(description).isEqualTo("Outfield Glove") + } + } + } + @Test fun testFullJoinWithoutAliases() { newSession().use { session -> @@ -311,6 +392,54 @@ class JoinMapperTest { } } + @Test + fun testLeftJoinWithSubQuery() { + newSession().use { session -> + val mapper = session.getMapper(JoinMapper::class.java) + + val selectStatement = select(OrderLine.orderId, OrderLine.quantity, + ItemMaster.itemId.qualifiedWith("im"), + ItemMaster.description) { + from(OrderMaster, "om") + join(OrderLine, "ol") { + on(OrderMaster.orderId, equalTo(OrderLine.orderId)) + } + leftJoin({ + select(ItemMaster.allColumns()) { + from(ItemMaster) + } + + "im" + }) { + on(OrderLine.itemId, equalTo(ItemMaster.itemId.qualifiedWith("im"))) + } + orderBy(OrderLine.orderId, ItemMaster.itemId) + } + + val expectedStatement = "select ol.order_id, ol.quantity, im.item_id, description" + + " from OrderMaster om join OrderLine ol on om.order_id = ol.order_id" + + " left join (select * from ItemMaster) im on ol.item_id = im.item_id" + + " order by order_id, item_id" + + assertThat(selectStatement.selectStatement).isEqualTo(expectedStatement) + + val rows = mapper.selectManyMappedRows(selectStatement) + + assertThat(rows).hasSize(5) + + assertThat(rows[2]).containsExactly( + entry("ORDER_ID", 2), + entry("QUANTITY", 6) + ) + + assertThat(rows[4]).containsExactly( + entry("ORDER_ID", 2), + entry("QUANTITY", 1), + entry("DESCRIPTION", "Outfield Glove"), + entry("ITEM_ID", 44) + ) + } + } + @Test fun testLeftJoinWithoutAliases() { newSession().use { session -> @@ -395,6 +524,53 @@ class JoinMapperTest { } } + @Test + fun testRightJoinWithSubQuery() { + newSession().use { session -> + val mapper = session.getMapper(JoinMapper::class.java) + + val selectStatement = select(OrderLine.orderId, OrderLine.quantity, + ItemMaster.itemId.qualifiedWith("im"), ItemMaster.description) { + from(OrderMaster, "om") + join(OrderLine, "ol") { + on(OrderMaster.orderId, equalTo(OrderLine.orderId)) + } + rightJoin ({ + select(ItemMaster.allColumns()) { + from(ItemMaster) + } + +"im" + }) { + on(OrderLine.itemId, equalTo(ItemMaster.itemId.qualifiedWith("im"))) + } + orderBy(OrderLine.orderId, ItemMaster.itemId) + } + + val expectedStatement = "select ol.order_id, ol.quantity, im.item_id, description" + + " from OrderMaster om join OrderLine ol on om.order_id = ol.order_id" + + " right join (select * from ItemMaster) im on ol.item_id = im.item_id" + + " order by order_id, item_id" + + assertThat(selectStatement.selectStatement).isEqualTo(expectedStatement) + + val rows = mapper.selectManyMappedRows(selectStatement) + + assertThat(rows).hasSize(5) + + assertThat(rows[0]).containsExactly( + entry("DESCRIPTION", "Catcher Glove"), + entry("ITEM_ID", 55) + ) + + assertThat(rows[4]).containsExactly( + entry("ORDER_ID", 2), + entry("QUANTITY", 1), + entry("DESCRIPTION", "Outfield Glove"), + entry("ITEM_ID", 44) + ) + } + } + @Test fun testRightJoinWithoutAliases() { newSession().use { session ->