diff --git a/CHANGELOG.md b/CHANGELOG.md index 0745ae19d..f5fb5d3de 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,10 @@ 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 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. + ([#459](https://github.com/mybatis/mybatis-dynamic-sql/pull/459)) ## Release 1.3.1 - December 18, 2021 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 new file mode 100644 index 000000000..d475fa58c --- /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 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; + + 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/render/TableAliasCalculatorWithParent.java b/src/main/java/org/mybatis/dynamic/sql/render/TableAliasCalculatorWithParent.java new file mode 100644 index 000000000..300340485 --- /dev/null +++ b/src/main/java/org/mybatis/dynamic/sql/render/TableAliasCalculatorWithParent.java @@ -0,0 +1,68 @@ +/* + * 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 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; + + private TableAliasCalculatorWithParent(Builder builder) { + parent = Objects.requireNonNull(builder.parent); + child = Objects.requireNonNull(builder.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); + } + + 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/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/AbstractQueryRendererBuilder.java b/src/main/java/org/mybatis/dynamic/sql/select/render/AbstractQueryRendererBuilder.java new file mode 100644 index 000000000..41ba1d090 --- /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 java.util.concurrent.atomic.AtomicInteger; + +import org.mybatis.dynamic.sql.render.RenderingStrategy; +import org.mybatis.dynamic.sql.render.TableAliasCalculator; + +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/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..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 @@ -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,11 @@ 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; +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; @@ -40,18 +44,80 @@ 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 = calculateTableAliasCalculator(queryExpression, builder.parentTableAliasCalculator); tableExpressionRenderer = new TableExpressionRenderer.Builder() - .withTableAliasCalculator(queryExpression.tableAliasCalculator()) + .withTableAliasCalculator(tableAliasCalculator) .withRenderingStrategy(renderingStrategy) .withSequence(sequence) .build(); } + /** + * This function calculates a table alias calculator to use in the current context. There are several + * 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 in a where clause. + * + *

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: + * + *

    + *
  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() + .map(JoinModel::containsSubQueries) + .map(this::calculateTableAliasCalculatorWithJoins) + .orElseGet(this::explicitTableAliasCalculator); + + if (parentTableAliasCalculator == null) { + return baseTableAliasCalculator; + } else { + return new TableAliasCalculatorWithParent.Builder() + .withParent(parentTableAliasCalculator) + .withChild(baseTableAliasCalculator) + .build(); + } + } + + 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() { FragmentAndParameters answer = calculateQueryExpressionStart(); answer = addJoinClause(answer); @@ -81,7 +147,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 +163,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 +179,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,30 +198,22 @@ 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) { 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..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 @@ -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(); 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/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/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/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/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 -> { diff --git a/src/test/kotlin/examples/kotlin/mybatis3/joins/ExistsTest.kt b/src/test/kotlin/examples/kotlin/mybatis3/joins/ExistsTest.kt index d020c3d50..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 -> @@ -164,6 +207,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 -> 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" diff --git a/src/test/resources/examples/kotlin/mybatis3/joins/CreateJoinDB.sql b/src/test/resources/examples/kotlin/mybatis3/joins/CreateJoinDB.sql index 7b1f11398..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. @@ -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);