From 7281e225e784a623271a522297697d776de76180 Mon Sep 17 00:00:00 2001 From: Jeff Butler Date: Sun, 13 Feb 2022 13:35:40 -0500 Subject: [PATCH 01/12] Failing test for gh-437 --- .../kotlin/mybatis3/joins/ExistsTest.kt | 35 +++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/src/test/kotlin/examples/kotlin/mybatis3/joins/ExistsTest.kt b/src/test/kotlin/examples/kotlin/mybatis3/joins/ExistsTest.kt index d020c3d50..304fca1d2 100644 --- a/src/test/kotlin/examples/kotlin/mybatis3/joins/ExistsTest.kt +++ b/src/test/kotlin/examples/kotlin/mybatis3/joins/ExistsTest.kt @@ -164,6 +164,41 @@ class ExistsTest { } } + @Test + fun testPropagateTableAliasToExists() { + newSession().use { session -> + val mapper = session.getMapper(CommonSelectMapper::class.java) + + val selectStatement = select(itemMaster.allColumns()) { + from(itemMaster, "im") + where { + not { + exists { + select(orderLine.allColumns()) { + from(orderLine, "ol") + where { orderLine.itemId isEqualTo itemMaster.itemId } + } + } + } + } + orderBy(itemMaster.itemId) + } + + val expectedStatement: String = "select im.* from ItemMaster im" + + " where not exists (select ol.* from OrderLine ol where ol.item_id = im.item_id)" + + " order by item_id" + assertThat(selectStatement.selectStatement).isEqualTo(expectedStatement) + + val rows = mapper.selectManyMappedRows(selectStatement) + assertThat(rows).hasSize(1) + + with(rows[0]) { + assertThat(this).containsEntry("ITEM_ID", 55) + assertThat(this).containsEntry("DESCRIPTION", "Catcher Glove") + } + } + } + @Test fun testAndExists() { newSession().use { session -> From bf5c72b4fa7a86e38d7e2833d82d32f1fc71c6a0 Mon Sep 17 00:00:00 2001 From: Jeff Butler Date: Tue, 1 Mar 2022 13:51:51 -0500 Subject: [PATCH 02/12] Move creation of the table alias calculator to the rendering phase --- .../sql/select/QueryExpressionModel.java | 23 ++++----------- .../sql/select/render/JoinRenderer.java | 20 ++++++------- .../render/QueryExpressionRenderer.java | 29 +++++++++++++++---- 3 files changed, 38 insertions(+), 34 deletions(-) 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 4e0b14ba7..8c6a1aad9 100644 --- a/src/main/java/org/mybatis/dynamic/sql/select/QueryExpressionModel.java +++ b/src/main/java/org/mybatis/dynamic/sql/select/QueryExpressionModel.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2020 the original author or authors. + * Copyright 2016-2022 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. @@ -27,8 +27,6 @@ import org.mybatis.dynamic.sql.BasicColumn; import org.mybatis.dynamic.sql.SqlTable; import org.mybatis.dynamic.sql.TableExpression; -import org.mybatis.dynamic.sql.render.GuaranteedTableAliasCalculator; -import org.mybatis.dynamic.sql.render.TableAliasCalculator; import org.mybatis.dynamic.sql.select.join.JoinModel; import org.mybatis.dynamic.sql.where.WhereModel; @@ -38,7 +36,7 @@ public class QueryExpressionModel { private final List selectList; private final TableExpression table; private final JoinModel joinModel; - private final TableAliasCalculator tableAliasCalculator; + private final Map tableAliases; private final WhereModel whereModel; private final GroupByModel groupByModel; @@ -48,22 +46,11 @@ private QueryExpressionModel(Builder builder) { selectList = Objects.requireNonNull(builder.selectList); table = Objects.requireNonNull(builder.table); joinModel = builder.joinModel; - tableAliasCalculator = joinModel().map(jm -> determineJoinTableAliasCalculator(jm, builder.tableAliases)) - .orElseGet(() -> TableAliasCalculator.of(builder.tableAliases)); + tableAliases = 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); } @@ -80,8 +67,8 @@ public TableExpression table() { return table; } - public TableAliasCalculator tableAliasCalculator() { - return tableAliasCalculator; + public Map tableAliases() { + return tableAliases; } public Optional whereModel() { diff --git a/src/main/java/org/mybatis/dynamic/sql/select/render/JoinRenderer.java b/src/main/java/org/mybatis/dynamic/sql/select/render/JoinRenderer.java index 665530872..77e461167 100644 --- a/src/main/java/org/mybatis/dynamic/sql/select/render/JoinRenderer.java +++ b/src/main/java/org/mybatis/dynamic/sql/select/render/JoinRenderer.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2020 the original author or authors. + * Copyright 2016-2022 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. @@ -22,7 +22,7 @@ import java.util.stream.Collectors; import org.mybatis.dynamic.sql.BasicColumn; -import org.mybatis.dynamic.sql.select.QueryExpressionModel; +import org.mybatis.dynamic.sql.render.TableAliasCalculator; import org.mybatis.dynamic.sql.select.join.JoinCriterion; import org.mybatis.dynamic.sql.select.join.JoinModel; import org.mybatis.dynamic.sql.select.join.JoinSpecification; @@ -31,13 +31,13 @@ public class JoinRenderer { private final JoinModel joinModel; - private final QueryExpressionModel queryExpression; private final TableExpressionRenderer tableExpressionRenderer; + private final TableAliasCalculator tableAliasCalculator; private JoinRenderer(Builder builder) { joinModel = Objects.requireNonNull(builder.joinModel); - queryExpression = Objects.requireNonNull(builder.queryExpression); tableExpressionRenderer = Objects.requireNonNull(builder.tableExpressionRenderer); + tableAliasCalculator = Objects.requireNonNull(builder.tableAliasCalculator); } public FragmentAndParameters render() { @@ -75,7 +75,7 @@ private String renderCriterion(JoinCriterion joinCriterion) { } private String applyTableAlias(BasicColumn column) { - return column.renderWithTableAlias(queryExpression.tableAliasCalculator()); + return column.renderWithTableAlias(tableAliasCalculator); } public static Builder withJoinModel(JoinModel joinModel) { @@ -84,21 +84,21 @@ public static Builder withJoinModel(JoinModel joinModel) { public static class Builder { private JoinModel joinModel; - private QueryExpressionModel queryExpression; private TableExpressionRenderer tableExpressionRenderer; + private TableAliasCalculator tableAliasCalculator; public Builder withJoinModel(JoinModel joinModel) { this.joinModel = joinModel; return this; } - public Builder withQueryExpression(QueryExpressionModel queryExpression) { - this.queryExpression = queryExpression; + public Builder withTableExpressionRenderer(TableExpressionRenderer tableExpressionRenderer) { + this.tableExpressionRenderer = tableExpressionRenderer; return this; } - public Builder withTableExpressionRenderer(TableExpressionRenderer tableExpressionRenderer) { - this.tableExpressionRenderer = tableExpressionRenderer; + public Builder withTableAliasCalculator(TableAliasCalculator tableAliasCalculator) { + this.tableAliasCalculator = tableAliasCalculator; return this; } diff --git a/src/main/java/org/mybatis/dynamic/sql/select/render/QueryExpressionRenderer.java b/src/main/java/org/mybatis/dynamic/sql/select/render/QueryExpressionRenderer.java index 21d1c3db8..eb34513f5 100644 --- a/src/main/java/org/mybatis/dynamic/sql/select/render/QueryExpressionRenderer.java +++ b/src/main/java/org/mybatis/dynamic/sql/select/render/QueryExpressionRenderer.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2020 the original author or authors. + * Copyright 2016-2022 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. @@ -25,7 +25,9 @@ import org.mybatis.dynamic.sql.BasicColumn; import org.mybatis.dynamic.sql.TableExpression; +import org.mybatis.dynamic.sql.render.GuaranteedTableAliasCalculator; import org.mybatis.dynamic.sql.render.RenderingStrategy; +import org.mybatis.dynamic.sql.render.TableAliasCalculator; import org.mybatis.dynamic.sql.select.GroupByModel; import org.mybatis.dynamic.sql.select.QueryExpressionModel; import org.mybatis.dynamic.sql.select.join.JoinModel; @@ -40,18 +42,33 @@ public class QueryExpressionRenderer { private final RenderingStrategy renderingStrategy; private final AtomicInteger sequence; private final TableExpressionRenderer tableExpressionRenderer; + private final TableAliasCalculator tableAliasCalculator; private QueryExpressionRenderer(Builder builder) { queryExpression = Objects.requireNonNull(builder.queryExpression); renderingStrategy = Objects.requireNonNull(builder.renderingStrategy); sequence = Objects.requireNonNull(builder.sequence); + tableAliasCalculator = determineJoinTableAliasCalculator(queryExpression); tableExpressionRenderer = new TableExpressionRenderer.Builder() - .withTableAliasCalculator(queryExpression.tableAliasCalculator()) + .withTableAliasCalculator(tableAliasCalculator) .withRenderingStrategy(renderingStrategy) .withSequence(sequence) .build(); } + private TableAliasCalculator determineJoinTableAliasCalculator(QueryExpressionModel queryExpression) { + return queryExpression.joinModel().map(JoinModel::containsSubQueries).map(containsSubQueries -> { + if (containsSubQueries) { + // if there are subQueries, then force explicit qualifiers + return TableAliasCalculator.of(queryExpression.tableAliases()); + } else { + // there are joins, but no sub-queries. In this case, we can use the + // table names as qualifiers without requiring explicit qualifiers + return GuaranteedTableAliasCalculator.of(queryExpression.tableAliases()); + } + }).orElseGet(() -> TableAliasCalculator.of(queryExpression.tableAliases())); + } + public FragmentAndParameters render() { FragmentAndParameters answer = calculateQueryExpressionStart(); answer = addJoinClause(answer); @@ -81,7 +98,7 @@ private String calculateColumnList() { } private String applyTableAndColumnAlias(BasicColumn selectListItem) { - return selectListItem.renderWithTableAndColumnAlias(queryExpression.tableAliasCalculator()); + return selectListItem.renderWithTableAndColumnAlias(tableAliasCalculator); } private FragmentAndParameters renderTableExpression(TableExpression table) { @@ -97,8 +114,8 @@ private FragmentAndParameters addJoinClause(FragmentAndParameters partial) { private FragmentAndParameters renderJoin(JoinModel joinModel) { return JoinRenderer.withJoinModel(joinModel) - .withQueryExpression(queryExpression) .withTableExpressionRenderer(tableExpressionRenderer) + .withTableAliasCalculator(tableAliasCalculator) .build() .render(); } @@ -113,7 +130,7 @@ private FragmentAndParameters addWhereClause(FragmentAndParameters partial) { private Optional renderWhereClause(WhereModel whereModel) { return WhereRenderer.withWhereModel(whereModel) .withRenderingStrategy(renderingStrategy) - .withTableAliasCalculator(queryExpression.tableAliasCalculator()) + .withTableAliasCalculator(tableAliasCalculator) .withSequence(sequence) .build() .render(); @@ -132,7 +149,7 @@ private String renderGroupBy(GroupByModel groupByModel) { } private String applyTableAlias(BasicColumn column) { - return column.renderWithTableAlias(queryExpression.tableAliasCalculator()); + return column.renderWithTableAlias(tableAliasCalculator); } public static Builder withQueryExpression(QueryExpressionModel model) { From 128ffb732426e04f6aa8c341de087ff53e10c30a Mon Sep 17 00:00:00 2001 From: Jeff Butler Date: Tue, 1 Mar 2022 14:30:03 -0500 Subject: [PATCH 03/12] Table Alias Calculator should be an interface --- .../render/ExplicitTableAliasCalculator.java | 60 +++++++++++++++++++ .../GuaranteedTableAliasCalculator.java | 4 +- .../sql/render/TableAliasCalculator.java | 53 +++++----------- .../render/QueryExpressionRenderer.java | 5 +- .../examples/animal/data/AnimalDataTest.java | 7 ++- .../sql/mybatis3/CriterionRendererTest.java | 7 ++- .../where/render/CriterionRendererTest.java | 5 +- 7 files changed, 91 insertions(+), 50 deletions(-) create mode 100644 src/main/java/org/mybatis/dynamic/sql/render/ExplicitTableAliasCalculator.java diff --git a/src/main/java/org/mybatis/dynamic/sql/render/ExplicitTableAliasCalculator.java b/src/main/java/org/mybatis/dynamic/sql/render/ExplicitTableAliasCalculator.java new file mode 100644 index 000000000..eb0ce4b33 --- /dev/null +++ b/src/main/java/org/mybatis/dynamic/sql/render/ExplicitTableAliasCalculator.java @@ -0,0 +1,60 @@ +/* + * Copyright 2016-2022 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 org.mybatis.dynamic.sql.render; + +import org.mybatis.dynamic.sql.SqlTable; + +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; + +public class ExplicitTableAliasCalculator implements TableAliasCalculator { + private final Map aliases; + + protected ExplicitTableAliasCalculator(Map aliases) { + this.aliases = Objects.requireNonNull(aliases); + } + + @Override + public Optional aliasForColumn(SqlTable table) { + return explicitAliasOrTableAlias(table); + } + + @Override + public Optional aliasForTable(SqlTable table) { + return explicitAliasOrTableAlias(table); + } + + private Optional explicitAliasOrTableAlias(SqlTable table) { + String alias = aliases.get(table); + if (alias == null) { + return table.tableAlias(); + } else { + return Optional.of(alias); + } + } + + public static TableAliasCalculator of(SqlTable table, String alias) { + Map tableAliases = new HashMap<>(); + tableAliases.put(table, alias); + return of(tableAliases); + } + + public static TableAliasCalculator of(Map aliases) { + return new ExplicitTableAliasCalculator(aliases); + } +} diff --git a/src/main/java/org/mybatis/dynamic/sql/render/GuaranteedTableAliasCalculator.java b/src/main/java/org/mybatis/dynamic/sql/render/GuaranteedTableAliasCalculator.java index b55a8de60..23c457eec 100644 --- a/src/main/java/org/mybatis/dynamic/sql/render/GuaranteedTableAliasCalculator.java +++ b/src/main/java/org/mybatis/dynamic/sql/render/GuaranteedTableAliasCalculator.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2021 the original author or authors. + * Copyright 2016-2022 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. @@ -27,7 +27,7 @@ * @author Jeff Butler * */ -public class GuaranteedTableAliasCalculator extends TableAliasCalculator { +public class GuaranteedTableAliasCalculator extends ExplicitTableAliasCalculator { private GuaranteedTableAliasCalculator(Map aliases) { super(aliases); diff --git a/src/main/java/org/mybatis/dynamic/sql/render/TableAliasCalculator.java b/src/main/java/org/mybatis/dynamic/sql/render/TableAliasCalculator.java index 109b85980..722d13889 100644 --- a/src/main/java/org/mybatis/dynamic/sql/render/TableAliasCalculator.java +++ b/src/main/java/org/mybatis/dynamic/sql/render/TableAliasCalculator.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2021 the original author or authors. + * Copyright 2016-2022 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. @@ -15,50 +15,27 @@ */ package org.mybatis.dynamic.sql.render; -import java.util.Collections; -import java.util.HashMap; -import java.util.Map; -import java.util.Objects; import java.util.Optional; import org.mybatis.dynamic.sql.SqlTable; -public class TableAliasCalculator { +public interface TableAliasCalculator { - private final Map aliases; + Optional aliasForColumn(SqlTable table); - protected TableAliasCalculator(Map aliases) { - this.aliases = Objects.requireNonNull(aliases); - } - - public Optional aliasForColumn(SqlTable table) { - return explicitAliasOrTableAlias(table); - } - - public Optional aliasForTable(SqlTable table) { - return explicitAliasOrTableAlias(table); - } + Optional aliasForTable(SqlTable table); - private Optional explicitAliasOrTableAlias(SqlTable table) { - String alias = aliases.get(table); - if (alias == null) { - return table.tableAlias(); - } else { - return Optional.of(alias); - } - } - - public static TableAliasCalculator of(SqlTable table, String alias) { - Map tableAliases = new HashMap<>(); - tableAliases.put(table, alias); - return of(tableAliases); - } - - public static TableAliasCalculator of(Map aliases) { - return new TableAliasCalculator(aliases); - } + static TableAliasCalculator empty() { + return new TableAliasCalculator() { + @Override + public Optional aliasForColumn(SqlTable table) { + return Optional.empty(); + } - public static TableAliasCalculator empty() { - return of(Collections.emptyMap()); + @Override + public Optional aliasForTable(SqlTable table) { + return Optional.empty(); + } + }; } } diff --git a/src/main/java/org/mybatis/dynamic/sql/select/render/QueryExpressionRenderer.java b/src/main/java/org/mybatis/dynamic/sql/select/render/QueryExpressionRenderer.java index eb34513f5..1adad758f 100644 --- a/src/main/java/org/mybatis/dynamic/sql/select/render/QueryExpressionRenderer.java +++ b/src/main/java/org/mybatis/dynamic/sql/select/render/QueryExpressionRenderer.java @@ -25,6 +25,7 @@ import org.mybatis.dynamic.sql.BasicColumn; import org.mybatis.dynamic.sql.TableExpression; +import org.mybatis.dynamic.sql.render.ExplicitTableAliasCalculator; import org.mybatis.dynamic.sql.render.GuaranteedTableAliasCalculator; import org.mybatis.dynamic.sql.render.RenderingStrategy; import org.mybatis.dynamic.sql.render.TableAliasCalculator; @@ -60,13 +61,13 @@ private TableAliasCalculator determineJoinTableAliasCalculator(QueryExpressionMo return queryExpression.joinModel().map(JoinModel::containsSubQueries).map(containsSubQueries -> { if (containsSubQueries) { // if there are subQueries, then force explicit qualifiers - return TableAliasCalculator.of(queryExpression.tableAliases()); + return ExplicitTableAliasCalculator.of(queryExpression.tableAliases()); } else { // there are joins, but no sub-queries. In this case, we can use the // table names as qualifiers without requiring explicit qualifiers return GuaranteedTableAliasCalculator.of(queryExpression.tableAliases()); } - }).orElseGet(() -> TableAliasCalculator.of(queryExpression.tableAliases())); + }).orElseGet(() -> ExplicitTableAliasCalculator.of(queryExpression.tableAliases())); } public FragmentAndParameters render() { diff --git a/src/test/java/examples/animal/data/AnimalDataTest.java b/src/test/java/examples/animal/data/AnimalDataTest.java index 436589304..69b4601c7 100644 --- a/src/test/java/examples/animal/data/AnimalDataTest.java +++ b/src/test/java/examples/animal/data/AnimalDataTest.java @@ -53,8 +53,8 @@ import org.mybatis.dynamic.sql.insert.render.GeneralInsertStatementProvider; import org.mybatis.dynamic.sql.insert.render.InsertSelectStatementProvider; import org.mybatis.dynamic.sql.insert.render.InsertStatementProvider; +import org.mybatis.dynamic.sql.render.ExplicitTableAliasCalculator; import org.mybatis.dynamic.sql.render.RenderingStrategies; -import org.mybatis.dynamic.sql.render.TableAliasCalculator; import org.mybatis.dynamic.sql.select.SelectModel; import org.mybatis.dynamic.sql.select.render.SelectStatementProvider; import org.mybatis.dynamic.sql.update.render.UpdateStatementProvider; @@ -314,7 +314,7 @@ void testComplexConditionWithStandaloneWhereAndTableAlias() { WhereClauseProvider whereClause = where(id, isEqualTo(1), or(bodyWeight, isGreaterThan(1.0))) .build() - .render(RenderingStrategies.MYBATIS3, TableAliasCalculator.of(animalData, "a")); + .render(RenderingStrategies.MYBATIS3, ExplicitTableAliasCalculator.of(animalData, "a")); assertThat(whereClause.getWhereClause()).isEqualTo("where (a.id = #{parameters.p1,jdbcType=INTEGER} or a.body_weight > #{parameters.p2,jdbcType=DOUBLE})"); @@ -347,7 +347,8 @@ void testSelectRowsNotBetweenWithStandaloneWhereClauseAliasLimitAndOffset() { WhereClauseProvider whereClause = where(id, isLessThan(60)) .build() - .render(RenderingStrategies.MYBATIS3, TableAliasCalculator.of(animalData, "b"), "whereClauseProvider"); + .render(RenderingStrategies.MYBATIS3, ExplicitTableAliasCalculator.of(animalData, "b"), + "whereClauseProvider"); List animals = mapper.selectWithWhereClauseAliasLimitAndOffset(whereClause, 3, 24); assertAll( diff --git a/src/test/java/org/mybatis/dynamic/sql/mybatis3/CriterionRendererTest.java b/src/test/java/org/mybatis/dynamic/sql/mybatis3/CriterionRendererTest.java index c52a7191e..43319c90c 100644 --- a/src/test/java/org/mybatis/dynamic/sql/mybatis3/CriterionRendererTest.java +++ b/src/test/java/org/mybatis/dynamic/sql/mybatis3/CriterionRendererTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2021 the original author or authors. + * Copyright 2016-2022 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. @@ -28,6 +28,7 @@ import org.mybatis.dynamic.sql.SqlBuilder; import org.mybatis.dynamic.sql.SqlColumn; import org.mybatis.dynamic.sql.SqlTable; +import org.mybatis.dynamic.sql.render.ExplicitTableAliasCalculator; import org.mybatis.dynamic.sql.render.RenderingStrategies; import org.mybatis.dynamic.sql.render.TableAliasCalculator; import org.mybatis.dynamic.sql.util.FragmentAndParameters; @@ -74,7 +75,7 @@ void testAliasWithoutIgnore() { CriterionRenderer renderer = new CriterionRenderer.Builder() .withSequence(sequence) .withRenderingStrategy(RenderingStrategies.MYBATIS3) - .withTableAliasCalculator(TableAliasCalculator.of(tableAliases)) + .withTableAliasCalculator(ExplicitTableAliasCalculator.of(tableAliases)) .build(); assertThat(criterion.accept(renderer)).hasValueSatisfying(rc -> { @@ -127,7 +128,7 @@ void testTypeHandlerAndAlias() { CriterionRenderer renderer = new CriterionRenderer.Builder() .withSequence(sequence) .withRenderingStrategy(RenderingStrategies.MYBATIS3) - .withTableAliasCalculator(TableAliasCalculator.of(tableAliases)) + .withTableAliasCalculator(ExplicitTableAliasCalculator.of(tableAliases)) .build(); assertThat(criterion.accept(renderer)).hasValueSatisfying(rc -> { diff --git a/src/test/java/org/mybatis/dynamic/sql/where/render/CriterionRendererTest.java b/src/test/java/org/mybatis/dynamic/sql/where/render/CriterionRendererTest.java index 6ac6a6aff..cc9da2e29 100644 --- a/src/test/java/org/mybatis/dynamic/sql/where/render/CriterionRendererTest.java +++ b/src/test/java/org/mybatis/dynamic/sql/where/render/CriterionRendererTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2021 the original author or authors. + * Copyright 2016-2022 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. @@ -27,6 +27,7 @@ import org.mybatis.dynamic.sql.ColumnAndConditionCriterion; import org.mybatis.dynamic.sql.SqlColumn; import org.mybatis.dynamic.sql.SqlTable; +import org.mybatis.dynamic.sql.render.ExplicitTableAliasCalculator; import org.mybatis.dynamic.sql.render.RenderingStrategies; import org.mybatis.dynamic.sql.render.TableAliasCalculator; import org.mybatis.dynamic.sql.util.FragmentAndParameters; @@ -74,7 +75,7 @@ void testAliasWithoutIgnore() { CriterionRenderer renderer = new CriterionRenderer.Builder() .withSequence(sequence) .withRenderingStrategy(RenderingStrategies.MYBATIS3) - .withTableAliasCalculator(TableAliasCalculator.of(tableAliases)) + .withTableAliasCalculator(ExplicitTableAliasCalculator.of(tableAliases)) .build(); assertThat(criterion.accept(renderer)).hasValueSatisfying(rc -> { From 4b2b79f179e968377d47c9b28b8459ef313c4a07 Mon Sep 17 00:00:00 2001 From: Jeff Butler Date: Tue, 1 Mar 2022 16:03:11 -0500 Subject: [PATCH 04/12] Give the exists renderer access to table aliases from the outer query --- .../TableAliasCalculatorWithParent.java | 49 ++++++++++++++++ .../render/AbstractQueryRendererBuilder.java | 44 ++++++++++++++ .../render/QueryExpressionRenderer.java | 57 ++++++++++++------- .../sql/select/render/SelectRenderer.java | 32 +++++------ .../sql/where/render/CriterionRenderer.java | 1 + 5 files changed, 143 insertions(+), 40 deletions(-) create mode 100644 src/main/java/org/mybatis/dynamic/sql/render/TableAliasCalculatorWithParent.java create mode 100644 src/main/java/org/mybatis/dynamic/sql/select/render/AbstractQueryRendererBuilder.java diff --git a/src/main/java/org/mybatis/dynamic/sql/render/TableAliasCalculatorWithParent.java b/src/main/java/org/mybatis/dynamic/sql/render/TableAliasCalculatorWithParent.java new file mode 100644 index 000000000..64424b954 --- /dev/null +++ b/src/main/java/org/mybatis/dynamic/sql/render/TableAliasCalculatorWithParent.java @@ -0,0 +1,49 @@ +/* + * Copyright 2016-2022 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 org.mybatis.dynamic.sql.render; + +import org.mybatis.dynamic.sql.SqlTable; + +import java.util.Objects; +import java.util.Optional; + +public class TableAliasCalculatorWithParent implements TableAliasCalculator { + private final TableAliasCalculator parent; + private final TableAliasCalculator child; + + public TableAliasCalculatorWithParent(TableAliasCalculator parent, TableAliasCalculator child) { + this.parent = Objects.requireNonNull(parent); + this.child = Objects.requireNonNull(child); + } + + @Override + public Optional aliasForColumn(SqlTable table) { + Optional answer = child.aliasForColumn(table); + if (answer.isPresent()) { + return answer; + } + return parent.aliasForColumn(table); + } + + @Override + public Optional aliasForTable(SqlTable table) { + Optional answer = child.aliasForTable(table); + if (answer.isPresent()) { + return answer; + } + return parent.aliasForTable(table); + } +} diff --git a/src/main/java/org/mybatis/dynamic/sql/select/render/AbstractQueryRendererBuilder.java b/src/main/java/org/mybatis/dynamic/sql/select/render/AbstractQueryRendererBuilder.java new file mode 100644 index 000000000..e1f35229f --- /dev/null +++ b/src/main/java/org/mybatis/dynamic/sql/select/render/AbstractQueryRendererBuilder.java @@ -0,0 +1,44 @@ +/* + * Copyright 2016-2022 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 org.mybatis.dynamic.sql.select.render; + +import org.mybatis.dynamic.sql.render.RenderingStrategy; +import org.mybatis.dynamic.sql.render.TableAliasCalculator; + +import java.util.concurrent.atomic.AtomicInteger; + +public abstract class AbstractQueryRendererBuilder> { + RenderingStrategy renderingStrategy; + AtomicInteger sequence; + TableAliasCalculator parentTableAliasCalculator; + + public T withRenderingStrategy(RenderingStrategy renderingStrategy) { + this.renderingStrategy = renderingStrategy; + return getThis(); + } + + public T withSequence(AtomicInteger sequence) { + this.sequence = sequence; + return getThis(); + } + + public T withParentTableAliasCalculator(TableAliasCalculator parentTableAliasCalculator) { + this.parentTableAliasCalculator = parentTableAliasCalculator; + return getThis(); + } + + abstract T getThis(); +} diff --git a/src/main/java/org/mybatis/dynamic/sql/select/render/QueryExpressionRenderer.java b/src/main/java/org/mybatis/dynamic/sql/select/render/QueryExpressionRenderer.java index 1adad758f..da23cb87f 100644 --- a/src/main/java/org/mybatis/dynamic/sql/select/render/QueryExpressionRenderer.java +++ b/src/main/java/org/mybatis/dynamic/sql/select/render/QueryExpressionRenderer.java @@ -29,6 +29,7 @@ import org.mybatis.dynamic.sql.render.GuaranteedTableAliasCalculator; import org.mybatis.dynamic.sql.render.RenderingStrategy; import org.mybatis.dynamic.sql.render.TableAliasCalculator; +import org.mybatis.dynamic.sql.render.TableAliasCalculatorWithParent; import org.mybatis.dynamic.sql.select.GroupByModel; import org.mybatis.dynamic.sql.select.QueryExpressionModel; import org.mybatis.dynamic.sql.select.join.JoinModel; @@ -49,7 +50,7 @@ private QueryExpressionRenderer(Builder builder) { queryExpression = Objects.requireNonNull(builder.queryExpression); renderingStrategy = Objects.requireNonNull(builder.renderingStrategy); sequence = Objects.requireNonNull(builder.sequence); - tableAliasCalculator = determineJoinTableAliasCalculator(queryExpression); + tableAliasCalculator = calculateTableAliasCalculator(queryExpression, builder.parentTableAliasCalculator); tableExpressionRenderer = new TableExpressionRenderer.Builder() .withTableAliasCalculator(tableAliasCalculator) .withRenderingStrategy(renderingStrategy) @@ -57,17 +58,37 @@ private QueryExpressionRenderer(Builder builder) { .build(); } - private TableAliasCalculator determineJoinTableAliasCalculator(QueryExpressionModel queryExpression) { - return queryExpression.joinModel().map(JoinModel::containsSubQueries).map(containsSubQueries -> { - if (containsSubQueries) { - // if there are subQueries, then force explicit qualifiers - return ExplicitTableAliasCalculator.of(queryExpression.tableAliases()); - } else { - // there are joins, but no sub-queries. In this case, we can use the - // table names as qualifiers without requiring explicit qualifiers - return GuaranteedTableAliasCalculator.of(queryExpression.tableAliases()); - } - }).orElseGet(() -> ExplicitTableAliasCalculator.of(queryExpression.tableAliases())); + private TableAliasCalculator calculateTableAliasCalculator(QueryExpressionModel queryExpression, + TableAliasCalculator parentTableAliasCalculator) { + TableAliasCalculator baseTableAliasCalculator = queryExpression.joinModel() + .map(JoinModel::containsSubQueries) + .map(this::calculateTableAliasCalculatorWithJoins) + .orElseGet(this::explicitTableAliasCalculator); + + if (parentTableAliasCalculator == null) { + return baseTableAliasCalculator; + } else { + return new TableAliasCalculatorWithParent(parentTableAliasCalculator, baseTableAliasCalculator); + } + } + + private TableAliasCalculator calculateTableAliasCalculatorWithJoins(boolean hasSubQueries) { + if (hasSubQueries) { + // if there are subqueries, we cannot use the table name automatically + // so all aliases must be specified + return explicitTableAliasCalculator(); + } else { + // without subqueries, we can automatically use table names as aliases + return guaranteedTableAliasCalculator(); + } + } + + private TableAliasCalculator explicitTableAliasCalculator() { + return ExplicitTableAliasCalculator.of(queryExpression.tableAliases()); + } + + private TableAliasCalculator guaranteedTableAliasCalculator() { + return GuaranteedTableAliasCalculator.of(queryExpression.tableAliases()); } public FragmentAndParameters render() { @@ -157,23 +178,15 @@ public static Builder withQueryExpression(QueryExpressionModel model) { return new Builder().withQueryExpression(model); } - public static class Builder { + public static class Builder extends AbstractQueryRendererBuilder { private QueryExpressionModel queryExpression; - private RenderingStrategy renderingStrategy; - private AtomicInteger sequence; public Builder withQueryExpression(QueryExpressionModel queryExpression) { this.queryExpression = queryExpression; return this; } - public Builder withRenderingStrategy(RenderingStrategy renderingStrategy) { - this.renderingStrategy = renderingStrategy; - return this; - } - - public Builder withSequence(AtomicInteger sequence) { - this.sequence = sequence; + Builder getThis() { return this; } diff --git a/src/main/java/org/mybatis/dynamic/sql/select/render/SelectRenderer.java b/src/main/java/org/mybatis/dynamic/sql/select/render/SelectRenderer.java index fd092f6f2..7c86f5461 100644 --- a/src/main/java/org/mybatis/dynamic/sql/select/render/SelectRenderer.java +++ b/src/main/java/org/mybatis/dynamic/sql/select/render/SelectRenderer.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2020 the original author or authors. + * Copyright 2016-2022 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. @@ -22,6 +22,7 @@ import org.mybatis.dynamic.sql.SortSpecification; import org.mybatis.dynamic.sql.render.RenderingStrategy; +import org.mybatis.dynamic.sql.render.TableAliasCalculator; import org.mybatis.dynamic.sql.select.OrderByModel; import org.mybatis.dynamic.sql.select.PagingModel; import org.mybatis.dynamic.sql.select.QueryExpressionModel; @@ -34,11 +35,17 @@ public class SelectRenderer { private final SelectModel selectModel; private final RenderingStrategy renderingStrategy; private final AtomicInteger sequence; + private final TableAliasCalculator parentTableAliasCalculator; // may be null private SelectRenderer(Builder builder) { selectModel = Objects.requireNonNull(builder.selectModel); renderingStrategy = Objects.requireNonNull(builder.renderingStrategy); - sequence = builder.sequence().orElseGet(() -> new AtomicInteger(1)); + if(builder.sequence == null) { + sequence = new AtomicInteger(1); + } else { + sequence = builder.sequence; + } + parentTableAliasCalculator = builder.parentTableAliasCalculator; } public SelectStatementProvider render() { @@ -59,6 +66,7 @@ private FragmentAndParameters renderQueryExpression(QueryExpressionModel queryEx return QueryExpressionRenderer.withQueryExpression(queryExpressionModel) .withRenderingStrategy(renderingStrategy) .withSequence(sequence) + .withParentTableAliasCalculator(parentTableAliasCalculator) .build() .render(); } @@ -99,32 +107,20 @@ public static Builder withSelectModel(SelectModel selectModel) { return new Builder().withSelectModel(selectModel); } - public static class Builder { + public static class Builder extends AbstractQueryRendererBuilder { private SelectModel selectModel; - private RenderingStrategy renderingStrategy; - private AtomicInteger sequence; public Builder withSelectModel(SelectModel selectModel) { this.selectModel = selectModel; return this; } - public Builder withRenderingStrategy(RenderingStrategy renderingStrategy) { - this.renderingStrategy = renderingStrategy; - return this; + public SelectRenderer build() { + return new SelectRenderer(this); } - public Builder withSequence(AtomicInteger sequence) { - this.sequence = sequence; + Builder getThis() { return this; } - - private Optional sequence() { - return Optional.ofNullable(sequence); - } - - public SelectRenderer build() { - return new SelectRenderer(this); - } } } diff --git a/src/main/java/org/mybatis/dynamic/sql/where/render/CriterionRenderer.java b/src/main/java/org/mybatis/dynamic/sql/where/render/CriterionRenderer.java index 3fc9bcbfa..0a106011e 100644 --- a/src/main/java/org/mybatis/dynamic/sql/where/render/CriterionRenderer.java +++ b/src/main/java/org/mybatis/dynamic/sql/where/render/CriterionRenderer.java @@ -127,6 +127,7 @@ private FragmentAndParameters renderExists(ExistsCriterion criterion) { .withSelectModel(existsPredicate.selectModelBuilder().build()) .withRenderingStrategy(renderingStrategy) .withSequence(sequence) + .withParentTableAliasCalculator(tableAliasCalculator) .build() .render(); From 6bf1c05bfa302f50530640d642fbb1600e595d11 Mon Sep 17 00:00:00 2001 From: Jeff Butler Date: Tue, 1 Mar 2022 16:17:16 -0500 Subject: [PATCH 05/12] Checkstyle updates --- .../dynamic/sql/insert/render/InsertStatementProvider.java | 4 ++-- .../dynamic/sql/render/ExplicitTableAliasCalculator.java | 4 ++-- .../dynamic/sql/render/TableAliasCalculatorWithParent.java | 4 ++-- .../sql/select/render/AbstractQueryRendererBuilder.java | 4 ++-- .../org/mybatis/dynamic/sql/select/render/SelectRenderer.java | 2 +- 5 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/main/java/org/mybatis/dynamic/sql/insert/render/InsertStatementProvider.java b/src/main/java/org/mybatis/dynamic/sql/insert/render/InsertStatementProvider.java index ab7620cf9..65d31882d 100644 --- a/src/main/java/org/mybatis/dynamic/sql/insert/render/InsertStatementProvider.java +++ b/src/main/java/org/mybatis/dynamic/sql/insert/render/InsertStatementProvider.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2021 the original author or authors. + * Copyright 2016-2022 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. @@ -19,8 +19,8 @@ public interface InsertStatementProvider { /** * Return the row associated with this insert statement. * - * @deprecated in favor of {@link InsertStatementProvider#getRow()} * @return the row associated with this insert statement. + * @deprecated in favor of {@link InsertStatementProvider#getRow()} */ @Deprecated T getRecord(); diff --git a/src/main/java/org/mybatis/dynamic/sql/render/ExplicitTableAliasCalculator.java b/src/main/java/org/mybatis/dynamic/sql/render/ExplicitTableAliasCalculator.java index eb0ce4b33..d475fa58c 100644 --- a/src/main/java/org/mybatis/dynamic/sql/render/ExplicitTableAliasCalculator.java +++ b/src/main/java/org/mybatis/dynamic/sql/render/ExplicitTableAliasCalculator.java @@ -15,13 +15,13 @@ */ package org.mybatis.dynamic.sql.render; -import org.mybatis.dynamic.sql.SqlTable; - import java.util.HashMap; import java.util.Map; import java.util.Objects; import java.util.Optional; +import org.mybatis.dynamic.sql.SqlTable; + public class ExplicitTableAliasCalculator implements TableAliasCalculator { private final Map aliases; diff --git a/src/main/java/org/mybatis/dynamic/sql/render/TableAliasCalculatorWithParent.java b/src/main/java/org/mybatis/dynamic/sql/render/TableAliasCalculatorWithParent.java index 64424b954..30c358bc1 100644 --- a/src/main/java/org/mybatis/dynamic/sql/render/TableAliasCalculatorWithParent.java +++ b/src/main/java/org/mybatis/dynamic/sql/render/TableAliasCalculatorWithParent.java @@ -15,11 +15,11 @@ */ package org.mybatis.dynamic.sql.render; -import org.mybatis.dynamic.sql.SqlTable; - import java.util.Objects; import java.util.Optional; +import org.mybatis.dynamic.sql.SqlTable; + public class TableAliasCalculatorWithParent implements TableAliasCalculator { private final TableAliasCalculator parent; private final TableAliasCalculator child; diff --git a/src/main/java/org/mybatis/dynamic/sql/select/render/AbstractQueryRendererBuilder.java b/src/main/java/org/mybatis/dynamic/sql/select/render/AbstractQueryRendererBuilder.java index e1f35229f..41ba1d090 100644 --- a/src/main/java/org/mybatis/dynamic/sql/select/render/AbstractQueryRendererBuilder.java +++ b/src/main/java/org/mybatis/dynamic/sql/select/render/AbstractQueryRendererBuilder.java @@ -15,11 +15,11 @@ */ package org.mybatis.dynamic.sql.select.render; +import java.util.concurrent.atomic.AtomicInteger; + import org.mybatis.dynamic.sql.render.RenderingStrategy; import org.mybatis.dynamic.sql.render.TableAliasCalculator; -import java.util.concurrent.atomic.AtomicInteger; - public abstract class AbstractQueryRendererBuilder> { RenderingStrategy renderingStrategy; AtomicInteger sequence; diff --git a/src/main/java/org/mybatis/dynamic/sql/select/render/SelectRenderer.java b/src/main/java/org/mybatis/dynamic/sql/select/render/SelectRenderer.java index 7c86f5461..4d579501f 100644 --- a/src/main/java/org/mybatis/dynamic/sql/select/render/SelectRenderer.java +++ b/src/main/java/org/mybatis/dynamic/sql/select/render/SelectRenderer.java @@ -40,7 +40,7 @@ public class SelectRenderer { private SelectRenderer(Builder builder) { selectModel = Objects.requireNonNull(builder.selectModel); renderingStrategy = Objects.requireNonNull(builder.renderingStrategy); - if(builder.sequence == null) { + if (builder.sequence == null) { sequence = new AtomicInteger(1); } else { sequence = builder.sequence; From 9c900bd1fa3368cbbde5a05a110aae5f57a26d2f Mon Sep 17 00:00:00 2001 From: Jeff Butler Date: Tue, 1 Mar 2022 16:56:50 -0500 Subject: [PATCH 06/12] Note to future self about a difficult method --- .../render/QueryExpressionRenderer.java | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/src/main/java/org/mybatis/dynamic/sql/select/render/QueryExpressionRenderer.java b/src/main/java/org/mybatis/dynamic/sql/select/render/QueryExpressionRenderer.java index da23cb87f..a9e102945 100644 --- a/src/main/java/org/mybatis/dynamic/sql/select/render/QueryExpressionRenderer.java +++ b/src/main/java/org/mybatis/dynamic/sql/select/render/QueryExpressionRenderer.java @@ -58,6 +58,29 @@ private QueryExpressionRenderer(Builder builder) { .build(); } + /** + * This function calculates a table alias calculator to use in the current context. In general, + * there are two possibilities: this could be a renderer for a regular select statement, or it + * could be a renderer for a select statement in an "exists" condition. + * + *

In the case of "exists" conditions, we will have a parent table alias calculator. We want to give visibility + * to the aliases in the outer select statement to this renderer so columns in aliased tables can be used in exists + * conditions without having to re-specify the alias. + * + *

Another complication is that we calculate aliases differently if there are joins and sub queries. The + * cases are as follows: + * + *

    + *
  1. If there are no joins, then we will only use aliases that are explicitly set by the user
  2. + *
  3. If there are joins and sub queries, we will also only use explicit aliases
  4. + *
  5. If there are joins, but no sub queries, then we will automatically use the table name + * as an alias if no explicit alias has been specified
  6. + *
+ * + * @param queryExpression the model to render + * @param parentTableAliasCalculator table alias calculator from the parent query + * @return a table alias calculator appropriate for this context + */ private TableAliasCalculator calculateTableAliasCalculator(QueryExpressionModel queryExpression, TableAliasCalculator parentTableAliasCalculator) { TableAliasCalculator baseTableAliasCalculator = queryExpression.joinModel() From a76dbd7f1aecf34157252a056aefcd99197b7edf Mon Sep 17 00:00:00 2001 From: Jeff Butler Date: Tue, 1 Mar 2022 17:25:34 -0500 Subject: [PATCH 07/12] Documentation --- CHANGELOG.md | 5 +- src/site/markdown/docs/subQueries.md | 69 ++++++++++--------- .../kotlin/mybatis3/joins/ExistsTest.kt | 43 ++++++++++++ 3 files changed, 85 insertions(+), 32 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0745ae19d..632a50b9b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,7 +4,7 @@ This log will detail notable changes to MyBatis Dynamic SQL. Full details are av ## Release 1.4.0 - Unreleased -The release includes new function in the Where Clause DSL to support arbitrary grouping of conditions, and also use +The release includes new functionality in the Where Clause DSL to support arbitrary grouping of conditions, and also use of a "not" condition. It should now be possible to write any type of where clause. Additionally, there were significant updates to the Kotlin DSL - both to support the new functionality in the @@ -44,6 +44,9 @@ GitHub milestone: [https://github.com/mybatis/mybatis-dynamic-sql/issues?q=miles insertBatch, and insertMultiple, the "into" function is moved inside the completer lambda. The old methods are now deprecated and will be removed in version 1.5.0 of the library. This also allowed us to make some insert DSL methods into infix functions. ([#452](https://github.com/mybatis/mybatis-dynamic-sql/pull/452)) +8. Updated the "exists" support to expose table aliases specified in the outer query to the query in the exists + clause. This makes it easier to use exists without having to re-specify the aliases for columns from the outer query. + ([#437](https://github.com/mybatis/mybatis-dynamic-sql/issues/437)) ## Release 1.3.1 - December 18, 2021 diff --git a/src/site/markdown/docs/subQueries.md b/src/site/markdown/docs/subQueries.md index bac43ad55..57bfd9ec1 100644 --- a/src/site/markdown/docs/subQueries.md +++ b/src/site/markdown/docs/subQueries.md @@ -135,15 +135,16 @@ SelectStatementProvider selectStatement = select(itemMaster.allColumns()) .where(exists( select(orderLine.allColumns()) .from(orderLine, "ol") - .where(orderLine.itemId, isEqualTo(itemMaster.itemId.qualifiedWith("im"))) + .where(orderLine.itemId, isEqualTo(itemMaster.itemId)) )) .orderBy(itemMaster.itemId) .build() .render(RenderingStrategies.MYBATIS3); ``` -Note that we have to apply the qualifier for the outer query ("im") to the inner query. The qualifier -for the inner query ("ol") is automatically applied. +Note that the qualifier for the outer query ("im") is automatically applied to the inner query, as well as the +qualifier for the inner query ("ol"). Carrying alias from an outer query to an inner query is only supported with +exists or not exists sub queries. An example of a column based subquery is as follows: @@ -168,13 +169,15 @@ An example of an exists subquery is as follows: ```kotlin val selectStatement = select(ItemMaster.allColumns()) { from(ItemMaster, "im") - where(exists { - select(OrderLine.allColumns()) { - from(OrderLine, "ol") - where(OrderLine.itemId, isEqualTo(ItemMaster.itemId.qualifiedWith("im"))) - } - }) - orderBy(ItemMaster.itemId) + where { + exists { + select(OrderLine.allColumns()) { + from(OrderLine, "ol") + where { OrderLine.itemId isEqualTo ItemMaster.itemId } + } + } + orderBy(ItemMaster.itemId) + } } ``` @@ -182,11 +185,13 @@ An example of a column based subquery is as follows: ```kotlin val selectStatement = select(id, firstName, lastName, birthDate, employed, occupation, addressId) { from(Person) - where(id, isEqualTo { - select(max(id)) { - from(Person) - } - }) + where { + id isEqualTo { + select(max(id)) { + from(Person) + } + } + } } ``` @@ -241,12 +246,12 @@ with the select DSL. You can write subqueries like this: ```kotlin val updateStatement = update(Person) { - set(addressId).equalToQueryResult { + set(addressId) equalToQueryResult { select(add(max(addressId), constant("1"))) { from(Person) } } - where(id, isEqualTo(3)) + where { id isEqualTo 3 } } ``` @@ -289,12 +294,12 @@ val selectStatement = from { select(id, firstName) { from(Person) - where(id, isLessThan(22)) + where { id isLessThan 22 } orderBy(firstName.descending()) } } - where(rowNum, isLessThan(5)) - and(firstName, isLike("%a%")) + where { rowNum isLessThan 5 } + and { firstName isLike "%a%" } } ``` @@ -307,13 +312,13 @@ val selectStatement = from { select(id, firstName) { from(Person, "a") - where(id, isLessThan(22)) + where { id isLessThan 22 } orderBy(firstName.descending()) } + "b" } - where(rowNum, isLessThan(5)) - and(firstName, isLike("%a%")) + where { rowNum isLessThan 5 } + and { firstName isLike "%a%" } } ``` @@ -356,15 +361,17 @@ 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)) + on(OrderMaster.orderId) equalTo OrderLine.orderId } - leftJoin({ - select(ItemMaster.allColumns()) { - from(ItemMaster) - } - + "im" - }) { - on(OrderLine.itemId, equalTo(ItemMaster.itemId.qualifiedWith("im"))) + leftJoin( + { + select(ItemMaster.allColumns()) { + from(ItemMaster) + } + + "im" + } + ) { + on(OrderLine.itemId) equalTo (ItemMaster.itemId qualifiedWith "im") } orderBy(OrderLine.orderId, ItemMaster.itemId) } diff --git a/src/test/kotlin/examples/kotlin/mybatis3/joins/ExistsTest.kt b/src/test/kotlin/examples/kotlin/mybatis3/joins/ExistsTest.kt index 304fca1d2..36bc34d65 100644 --- a/src/test/kotlin/examples/kotlin/mybatis3/joins/ExistsTest.kt +++ b/src/test/kotlin/examples/kotlin/mybatis3/joins/ExistsTest.kt @@ -94,6 +94,49 @@ class ExistsTest { } } + @Test + fun testExistsPropagatedAliases() { + newSession().use { session -> + val mapper = session.getMapper(CommonSelectMapper::class.java) + + val selectStatement = select(itemMaster.allColumns()) { + from(itemMaster, "im") + where { + exists { + select(orderLine.allColumns()) { + from(orderLine, "ol") + where { orderLine.itemId isEqualTo itemMaster.itemId } + } + } + } + orderBy(itemMaster.itemId) + } + + val expectedStatement: String = "select im.* from ItemMaster im" + + " where exists (select ol.* from OrderLine ol where ol.item_id = im.item_id)" + + " order by item_id" + assertThat(selectStatement.selectStatement).isEqualTo(expectedStatement) + + val rows = mapper.selectManyMappedRows(selectStatement) + assertThat(rows).hasSize(3) + + with(rows[0]) { + assertThat(this).containsEntry("ITEM_ID", 22) + assertThat(this).containsEntry("DESCRIPTION", "Helmet") + } + + with(rows[1]) { + assertThat(this).containsEntry("ITEM_ID", 33) + assertThat(this).containsEntry("DESCRIPTION", "First Base Glove") + } + + with(rows[2]) { + assertThat(this).containsEntry("ITEM_ID", 44) + assertThat(this).containsEntry("DESCRIPTION", "Outfield Glove") + } + } + } + @Test fun testNotExists() { newSession().use { session -> From 3b25fc2d6991a7c52f6b4d24a3d670b87306228f Mon Sep 17 00:00:00 2001 From: Jeff Butler Date: Tue, 1 Mar 2022 20:42:31 -0500 Subject: [PATCH 08/12] Use a builder per coding standards --- .../TableAliasCalculatorWithParent.java | 25 ++++++++++++++++--- .../render/QueryExpressionRenderer.java | 12 ++++++--- 2 files changed, 30 insertions(+), 7 deletions(-) diff --git a/src/main/java/org/mybatis/dynamic/sql/render/TableAliasCalculatorWithParent.java b/src/main/java/org/mybatis/dynamic/sql/render/TableAliasCalculatorWithParent.java index 30c358bc1..300340485 100644 --- a/src/main/java/org/mybatis/dynamic/sql/render/TableAliasCalculatorWithParent.java +++ b/src/main/java/org/mybatis/dynamic/sql/render/TableAliasCalculatorWithParent.java @@ -24,9 +24,9 @@ public class TableAliasCalculatorWithParent implements TableAliasCalculator { private final TableAliasCalculator parent; private final TableAliasCalculator child; - public TableAliasCalculatorWithParent(TableAliasCalculator parent, TableAliasCalculator child) { - this.parent = Objects.requireNonNull(parent); - this.child = Objects.requireNonNull(child); + private TableAliasCalculatorWithParent(Builder builder) { + parent = Objects.requireNonNull(builder.parent); + child = Objects.requireNonNull(builder.child); } @Override @@ -46,4 +46,23 @@ public Optional aliasForTable(SqlTable table) { } return parent.aliasForTable(table); } + + public static class Builder { + private TableAliasCalculator parent; + private TableAliasCalculator child; + + public Builder withParent(TableAliasCalculator parent) { + this.parent = parent; + return this; + } + + public Builder withChild(TableAliasCalculator child) { + this.child = child; + return this; + } + + public TableAliasCalculatorWithParent build() { + return new TableAliasCalculatorWithParent(this); + } + } } diff --git a/src/main/java/org/mybatis/dynamic/sql/select/render/QueryExpressionRenderer.java b/src/main/java/org/mybatis/dynamic/sql/select/render/QueryExpressionRenderer.java index a9e102945..8cea99d2b 100644 --- a/src/main/java/org/mybatis/dynamic/sql/select/render/QueryExpressionRenderer.java +++ b/src/main/java/org/mybatis/dynamic/sql/select/render/QueryExpressionRenderer.java @@ -59,9 +59,10 @@ private QueryExpressionRenderer(Builder builder) { } /** - * This function calculates a table alias calculator to use in the current context. In general, - * there are two possibilities: this could be a renderer for a regular select statement, or it - * could be a renderer for a select statement in an "exists" condition. + * This function calculates a table alias calculator to use in the current context. There are several + * possibilities: this could be a renderer for a regular select statement, or it could be a renderer for a table + * expression in a join, or a column to sub query where condition, or it could be a renderer for a select + * statement in an "exists" condition. * *

In the case of "exists" conditions, we will have a parent table alias calculator. We want to give visibility * to the aliases in the outer select statement to this renderer so columns in aliased tables can be used in exists @@ -91,7 +92,10 @@ private TableAliasCalculator calculateTableAliasCalculator(QueryExpressionModel if (parentTableAliasCalculator == null) { return baseTableAliasCalculator; } else { - return new TableAliasCalculatorWithParent(parentTableAliasCalculator, baseTableAliasCalculator); + return new TableAliasCalculatorWithParent.Builder() + .withParent(parentTableAliasCalculator) + .withChild(baseTableAliasCalculator) + .build(); } } From de1e4d860a1bf6338f270256db723aeeb4eef3fa Mon Sep 17 00:00:00 2001 From: Jeff Butler Date: Wed, 2 Mar 2022 09:31:45 -0500 Subject: [PATCH 09/12] Fix a data error --- .../resources/examples/kotlin/mybatis3/joins/CreateJoinDB.sql | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/resources/examples/kotlin/mybatis3/joins/CreateJoinDB.sql b/src/test/resources/examples/kotlin/mybatis3/joins/CreateJoinDB.sql index 7b1f11398..c6448bb41 100644 --- a/src/test/resources/examples/kotlin/mybatis3/joins/CreateJoinDB.sql +++ b/src/test/resources/examples/kotlin/mybatis3/joins/CreateJoinDB.sql @@ -68,7 +68,7 @@ insert into ItemMaster(item_id, description) values(44, 'Outfield Glove'); insert into ItemMaster(item_id, description) values(55, 'Catcher Glove'); insert into OrderLine(order_id, item_id, line_number, quantity) values(1, 22, 1, 1); -insert into OrderLine(order_id, item_id, line_number, quantity) values(1, 33, 1, 1); +insert into OrderLine(order_id, item_id, line_number, quantity) values(1, 33, 2, 1); insert into OrderLine(order_id, item_id, line_number, quantity) values(2, 22, 1, 1); insert into OrderLine(order_id, item_id, line_number, quantity) values(2, 44, 2, 1); insert into OrderLine(order_id, item_id, line_number, quantity) values(2, 66, 3, 6); From 17a17ecfb75a61662ff179e1235d60d2c91f52b5 Mon Sep 17 00:00:00 2001 From: Jeff Butler Date: Wed, 2 Mar 2022 10:13:36 -0500 Subject: [PATCH 10/12] Expose aliases to all sub queries in where clauses --- .../render/QueryExpressionRenderer.java | 10 ++--- .../where/render/WhereConditionVisitor.java | 3 +- .../sql/subselect/FooDynamicSqlSupport.java | 37 +++++++++++++++ .../dynamic/sql/subselect/SubSelectTest.java | 32 ++++++++----- .../kotlin/mybatis3/joins/JoinMapperTest.kt | 45 +++++++++++++++++++ 5 files changed, 109 insertions(+), 18 deletions(-) create mode 100644 src/test/java/org/mybatis/dynamic/sql/subselect/FooDynamicSqlSupport.java diff --git a/src/main/java/org/mybatis/dynamic/sql/select/render/QueryExpressionRenderer.java b/src/main/java/org/mybatis/dynamic/sql/select/render/QueryExpressionRenderer.java index 8cea99d2b..7869d9b93 100644 --- a/src/main/java/org/mybatis/dynamic/sql/select/render/QueryExpressionRenderer.java +++ b/src/main/java/org/mybatis/dynamic/sql/select/render/QueryExpressionRenderer.java @@ -60,13 +60,13 @@ private QueryExpressionRenderer(Builder builder) { /** * This function calculates a table alias calculator to use in the current context. There are several - * possibilities: this could be a renderer for a regular select statement, or it could be a renderer for a table + * possibilities: this could be a renderer for a top level select statement, or it could be a renderer for a table * expression in a join, or a column to sub query where condition, or it could be a renderer for a select - * statement in an "exists" condition. + * statement in an "exists" condition in a where clause. * - *

In the case of "exists" conditions, we will have a parent table alias calculator. We want to give visibility - * to the aliases in the outer select statement to this renderer so columns in aliased tables can be used in exists - * conditions without having to re-specify the alias. + *

In the case of conditions in a where clause, we will have a parent table alias calculator. This will give + * visibility to the aliases in the outer select statement to this renderer so columns in aliased tables can be + * used in where clause sub query conditions without having to re-specify the alias. * *

Another complication is that we calculate aliases differently if there are joins and sub queries. The * cases are as follows: diff --git a/src/main/java/org/mybatis/dynamic/sql/where/render/WhereConditionVisitor.java b/src/main/java/org/mybatis/dynamic/sql/where/render/WhereConditionVisitor.java index b42112029..482de36da 100644 --- a/src/main/java/org/mybatis/dynamic/sql/where/render/WhereConditionVisitor.java +++ b/src/main/java/org/mybatis/dynamic/sql/where/render/WhereConditionVisitor.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2021 the original author or authors. + * Copyright 2016-2022 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. @@ -96,6 +96,7 @@ public FragmentAndParameters visit(AbstractSubselectCondition condition) { SelectStatementProvider selectStatement = SelectRenderer.withSelectModel(condition.selectModel()) .withRenderingStrategy(renderingStrategy) .withSequence(sequence) + .withParentTableAliasCalculator(tableAliasCalculator) .build() .render(); diff --git a/src/test/java/org/mybatis/dynamic/sql/subselect/FooDynamicSqlSupport.java b/src/test/java/org/mybatis/dynamic/sql/subselect/FooDynamicSqlSupport.java new file mode 100644 index 000000000..81348bb60 --- /dev/null +++ b/src/test/java/org/mybatis/dynamic/sql/subselect/FooDynamicSqlSupport.java @@ -0,0 +1,37 @@ +/* + * Copyright 2016-2022 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 org.mybatis.dynamic.sql.subselect; + +import org.mybatis.dynamic.sql.SqlColumn; +import org.mybatis.dynamic.sql.SqlTable; + +import java.sql.JDBCType; +import java.util.Date; + +public class FooDynamicSqlSupport { + public static final Foo foo = new Foo(); + public static final SqlColumn column1 = foo.column1; + static final SqlColumn column2 = foo.column2; + + public static class Foo extends SqlTable { + public final SqlColumn column1 = column("column1", JDBCType.DATE); + public final SqlColumn column2 = column("column2", JDBCType.INTEGER); + + public Foo() { + super("foo"); + } + } +} diff --git a/src/test/java/org/mybatis/dynamic/sql/subselect/SubSelectTest.java b/src/test/java/org/mybatis/dynamic/sql/subselect/SubSelectTest.java index e2294ab2c..caffc495c 100644 --- a/src/test/java/org/mybatis/dynamic/sql/subselect/SubSelectTest.java +++ b/src/test/java/org/mybatis/dynamic/sql/subselect/SubSelectTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2020 the original author or authors. + * Copyright 2016-2022 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. @@ -18,29 +18,30 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertAll; import static org.mybatis.dynamic.sql.SqlBuilder.*; +import static org.mybatis.dynamic.sql.subselect.FooDynamicSqlSupport.*; -import java.sql.JDBCType; import java.util.Date; import org.junit.jupiter.api.Test; -import org.mybatis.dynamic.sql.SqlColumn; -import org.mybatis.dynamic.sql.SqlTable; import org.mybatis.dynamic.sql.render.RenderingStrategies; import org.mybatis.dynamic.sql.select.render.SelectStatementProvider; class SubSelectTest { - static final SqlTable table = SqlTable.of("foo"); - static final SqlColumn column1 = table.column("column1", JDBCType.DATE); - static final SqlColumn column2 = table.column("column2", JDBCType.INTEGER); - @Test void testInSubSelect() { Date d = new Date(); + Foo foo2 = new Foo(); + SelectStatementProvider selectStatement = select(column1.as("A_COLUMN1"), column2) - .from(table, "a") - .where(column2, isIn(select(column2).from(table).where(column2, isEqualTo(3)))) + .from(foo, "a") + .where(column2, isIn( + select(foo2.column2) + .from(foo2) + .where(foo2.column2, isEqualTo(3)) + ) + ) .and(column1, isLessThan(d)) .build() .render(RenderingStrategies.MYBATIS3); @@ -61,9 +62,16 @@ void testInSubSelect() { void testNotInSubSelect() { Date d = new Date(); + Foo foo2 = new Foo(); + SelectStatementProvider selectStatement = select(column1.as("A_COLUMN1"), column2) - .from(table, "a") - .where(column2, isNotIn(select(column2).from(table).where(column2, isEqualTo(3)))) + .from(foo, "a") + .where(column2, isNotIn( + select(foo2.column2) + .from(foo2) + .where(foo2.column2, isEqualTo(3)) + ) + ) .and(column1, isLessThan(d)) .build() .render(RenderingStrategies.MYBATIS3); diff --git a/src/test/kotlin/examples/kotlin/mybatis3/joins/JoinMapperTest.kt b/src/test/kotlin/examples/kotlin/mybatis3/joins/JoinMapperTest.kt index 7831ec31c..c22d0f4a8 100644 --- a/src/test/kotlin/examples/kotlin/mybatis3/joins/JoinMapperTest.kt +++ b/src/test/kotlin/examples/kotlin/mybatis3/joins/JoinMapperTest.kt @@ -33,6 +33,7 @@ import org.assertj.core.api.Assertions.entry import org.junit.jupiter.api.Test import org.mybatis.dynamic.sql.util.kotlin.KInvalidSQLException import org.mybatis.dynamic.sql.util.kotlin.elements.equalTo +import org.mybatis.dynamic.sql.util.kotlin.elements.max import org.mybatis.dynamic.sql.util.kotlin.elements.qualifiedWith import org.mybatis.dynamic.sql.util.kotlin.mybatis3.select import java.io.InputStreamReader @@ -774,6 +775,50 @@ class JoinMapperTest { }.withMessage("You must specify an \"on\" condition in a join") } + @Test + fun testThatAliasesPropagateToSubQueryConditions() { + newSession().use { session -> + val mapper = session.getMapper(JoinMapper::class.java) + + val orderLine2 = OrderLineDynamicSQLSupport.OrderLine() + + val selectStatement = select(orderLine.orderId, orderLine.lineNumber) { + from(orderLine, "ol") + where { + orderLine.lineNumber isEqualTo { + select(max(orderLine2.lineNumber)) { + from(orderLine2, "ol2") + where { orderLine2.orderId isEqualTo orderLine.orderId } + } + } + } + orderBy(orderLine.orderId) + } + + val expectedStatement = "select ol.order_id, ol.line_number " + + "from OrderLine ol " + + "where ol.line_number = " + + "(select max(ol2.line_number) from OrderLine ol2 where ol2.order_id = ol.order_id) " + + "order by order_id" + + assertThat(selectStatement.selectStatement).isEqualTo(expectedStatement) + + val rows = mapper.selectManyMappedRows(selectStatement) + + assertThat(rows).hasSize(2) + + assertThat(rows[0]).containsOnly( + entry("ORDER_ID", 1), + entry("LINE_NUMBER", 2) + ) + + assertThat(rows[1]).containsOnly( + entry("ORDER_ID", 2), + entry("LINE_NUMBER", 3) + ) + } + } + companion object { const val JDBC_URL = "jdbc:hsqldb:mem:aname" const val JDBC_DRIVER = "org.hsqldb.jdbcDriver" From db4ccea3357921de2324e7d0aca6a0a0e2116b24 Mon Sep 17 00:00:00 2001 From: Jeff Butler Date: Wed, 2 Mar 2022 10:25:40 -0500 Subject: [PATCH 11/12] Documentation --- CHANGELOG.md | 5 +++-- .../examples/kotlin/mybatis3/joins/CreateJoinDB.sql | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 632a50b9b..a0c4c76ef 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -44,8 +44,9 @@ GitHub milestone: [https://github.com/mybatis/mybatis-dynamic-sql/issues?q=miles insertBatch, and insertMultiple, the "into" function is moved inside the completer lambda. The old methods are now deprecated and will be removed in version 1.5.0 of the library. This also allowed us to make some insert DSL methods into infix functions. ([#452](https://github.com/mybatis/mybatis-dynamic-sql/pull/452)) -8. Updated the "exists" support to expose table aliases specified in the outer query to the query in the exists - clause. This makes it easier to use exists without having to re-specify the aliases for columns from the outer query. +8. Updated the where clause to expose table aliases specified in an outer query to sub queries in the where clause + (either an "exists" clause, or a sub query to column comparison condition) This makes it easier to use these types + of sub queries without having to re-specify the aliases for columns from the outer query. ([#437](https://github.com/mybatis/mybatis-dynamic-sql/issues/437)) ## Release 1.3.1 - December 18, 2021 diff --git a/src/test/resources/examples/kotlin/mybatis3/joins/CreateJoinDB.sql b/src/test/resources/examples/kotlin/mybatis3/joins/CreateJoinDB.sql index c6448bb41..3eac468ed 100644 --- a/src/test/resources/examples/kotlin/mybatis3/joins/CreateJoinDB.sql +++ b/src/test/resources/examples/kotlin/mybatis3/joins/CreateJoinDB.sql @@ -1,5 +1,5 @@ -- --- Copyright 2016-2019 the original author or authors. +-- Copyright 2016-2022 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. From 25af53f91b6a1b1abee4d85a683f15f46578e066 Mon Sep 17 00:00:00 2001 From: Jeff Butler Date: Wed, 2 Mar 2022 10:35:00 -0500 Subject: [PATCH 12/12] Add PR detail to CHANGELOG.md --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a0c4c76ef..f5fb5d3de 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -47,7 +47,7 @@ GitHub milestone: [https://github.com/mybatis/mybatis-dynamic-sql/issues?q=miles 8. Updated the where clause to expose table aliases specified in an outer query to sub queries in the where clause (either an "exists" clause, or a sub query to column comparison condition) This makes it easier to use these types of sub queries without having to re-specify the aliases for columns from the outer query. - ([#437](https://github.com/mybatis/mybatis-dynamic-sql/issues/437)) + ([#459](https://github.com/mybatis/mybatis-dynamic-sql/pull/459)) ## Release 1.3.1 - December 18, 2021