diff --git a/CHANGELOG.md b/CHANGELOG.md index 5affe6ae2..7c4a95341 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,34 @@ This log will detail notable changes to MyBatis Dynamic SQL. Full details are available on the GitHub milestone pages. +## Release 1.5.1 - Unreleased + +This is a minor release with a few small enhancements. + +GitHub milestone: [https://github.com/mybatis/mybatis-dynamic-sql/milestone/13](https://github.com/mybatis/mybatis-dynamic-sql/milestone/13) + +### Parameter Values in Joins + +We've added the ability to specify typed values in equi-joins. This allows you to avoid the use of constants, and it is +type safe. For example: + +```java +SelectStatementProvider selectStatement = select(orderLine.orderId, orderLine.quantity, itemMaster.itemId, itemMaster.description) + .from(itemMaster, "im") + .join(orderLine, "ol").on(orderLine.itemId, equalTo(itemMaster.itemId)) + .and(orderLine.orderId, equalTo(1)) + .build() + .render(RenderingStrategies.MYBATIS3); +``` + +Note the phrase `and(orderLine.orderId, equalTo(1))` which will be rendered with a bound SQL parameter. Currently, this +capability is limited to equality only. If you have a use for other functions (not equal, less then, greater than, etc.) +please let us know. + +In order to add this capability, we've modified the join DSL to add type information to the join columns. This should +be source code compatible with most uses. There could be an issue if you are joining tables with columns of different +types - which is a rare usage. Please let us know if this causes an undo hardship. + ## Release 1.5.0 - April 21, 2023 GitHub milestone: [https://github.com/mybatis/mybatis-dynamic-sql/milestone/12?closed=1](https://github.com/mybatis/mybatis-dynamic-sql/milestone/12?closed=1) diff --git a/pom.xml b/pom.xml index 9113b386d..db2938b1b 100644 --- a/pom.xml +++ b/pom.xml @@ -57,7 +57,7 @@ checkstyle-override.xml - 1.2.0 + 1.5.0 org.mybatis.dynamic.sql diff --git a/src/main/java/org/mybatis/dynamic/sql/SqlBuilder.java b/src/main/java/org/mybatis/dynamic/sql/SqlBuilder.java index 4ed0de186..d77b0b026 100644 --- a/src/main/java/org/mybatis/dynamic/sql/SqlBuilder.java +++ b/src/main/java/org/mybatis/dynamic/sql/SqlBuilder.java @@ -54,6 +54,7 @@ import org.mybatis.dynamic.sql.select.function.Subtract; import org.mybatis.dynamic.sql.select.function.Upper; import org.mybatis.dynamic.sql.select.join.EqualTo; +import org.mybatis.dynamic.sql.select.join.EqualToValue; import org.mybatis.dynamic.sql.select.join.JoinCondition; import org.mybatis.dynamic.sql.select.join.JoinCriterion; import org.mybatis.dynamic.sql.update.UpdateDSL; @@ -425,24 +426,28 @@ static AndOrCriteriaGroup and(List subCriteria) { } // join support - static JoinCriterion and(BasicColumn joinColumn, JoinCondition joinCondition) { - return new JoinCriterion.Builder() + static JoinCriterion and(BindableColumn joinColumn, JoinCondition joinCondition) { + return new JoinCriterion.Builder() .withConnector("and") //$NON-NLS-1$ .withJoinColumn(joinColumn) .withJoinCondition(joinCondition) .build(); } - static JoinCriterion on(BasicColumn joinColumn, JoinCondition joinCondition) { - return new JoinCriterion.Builder() + static JoinCriterion on(BindableColumn joinColumn, JoinCondition joinCondition) { + return new JoinCriterion.Builder() .withConnector("on") //$NON-NLS-1$ .withJoinColumn(joinColumn) .withJoinCondition(joinCondition) .build(); } - static EqualTo equalTo(BasicColumn column) { - return new EqualTo(column); + static EqualTo equalTo(BindableColumn column) { + return new EqualTo<>(column); + } + + static EqualToValue equalTo(T value) { + return new EqualToValue<>(value); } // aggregate support diff --git a/src/main/java/org/mybatis/dynamic/sql/select/AbstractQueryExpressionDSL.java b/src/main/java/org/mybatis/dynamic/sql/select/AbstractQueryExpressionDSL.java index 6fdac9fd7..e6ec1d17b 100644 --- a/src/main/java/org/mybatis/dynamic/sql/select/AbstractQueryExpressionDSL.java +++ b/src/main/java/org/mybatis/dynamic/sql/select/AbstractQueryExpressionDSL.java @@ -52,132 +52,132 @@ public TableExpression table() { return table; } - public T join(SqlTable joinTable, JoinCriterion onJoinCriterion, - JoinCriterion... andJoinCriteria) { + public T join(SqlTable joinTable, JoinCriterion onJoinCriterion, + JoinCriterion... andJoinCriteria) { addJoinSpecificationBuilder(joinTable, onJoinCriterion, JoinType.INNER, Arrays.asList(andJoinCriteria)); return getThis(); } - public T join(SqlTable joinTable, String tableAlias, JoinCriterion onJoinCriterion, - JoinCriterion... andJoinCriteria) { + public T join(SqlTable joinTable, String tableAlias, JoinCriterion onJoinCriterion, + JoinCriterion... andJoinCriteria) { addTableAlias(joinTable, tableAlias); return join(joinTable, onJoinCriterion, andJoinCriteria); } - public T join(SqlTable joinTable, JoinCriterion onJoinCriterion, - List andJoinCriteria) { + public T join(SqlTable joinTable, JoinCriterion onJoinCriterion, + List> andJoinCriteria) { addJoinSpecificationBuilder(joinTable, onJoinCriterion, JoinType.INNER, andJoinCriteria); return getThis(); } - public T join(SqlTable joinTable, String tableAlias, JoinCriterion onJoinCriterion, - List andJoinCriteria) { + public T join(SqlTable joinTable, String tableAlias, JoinCriterion onJoinCriterion, + List> andJoinCriteria) { addTableAlias(joinTable, tableAlias); return join(joinTable, onJoinCriterion, andJoinCriteria); } - public T join(Buildable subQuery, String tableAlias, JoinCriterion onJoinCriterion, - List andJoinCriteria) { + public T join(Buildable subQuery, String tableAlias, JoinCriterion onJoinCriterion, + List> andJoinCriteria) { addJoinSpecificationBuilder(buildSubQuery(subQuery, tableAlias), onJoinCriterion, JoinType.INNER, andJoinCriteria); return getThis(); } - public T leftJoin(SqlTable joinTable, JoinCriterion onJoinCriterion, - JoinCriterion... andJoinCriteria) { + public T leftJoin(SqlTable joinTable, JoinCriterion onJoinCriterion, + JoinCriterion... andJoinCriteria) { addJoinSpecificationBuilder(joinTable, onJoinCriterion, JoinType.LEFT, Arrays.asList(andJoinCriteria)); return getThis(); } - public T leftJoin(SqlTable joinTable, String tableAlias, JoinCriterion onJoinCriterion, - JoinCriterion... andJoinCriteria) { + public T leftJoin(SqlTable joinTable, String tableAlias, JoinCriterion onJoinCriterion, + JoinCriterion... andJoinCriteria) { addTableAlias(joinTable, tableAlias); return leftJoin(joinTable, onJoinCriterion, andJoinCriteria); } - public T leftJoin(SqlTable joinTable, JoinCriterion onJoinCriterion, - List andJoinCriteria) { + public T leftJoin(SqlTable joinTable, JoinCriterion onJoinCriterion, + List> andJoinCriteria) { addJoinSpecificationBuilder(joinTable, onJoinCriterion, JoinType.LEFT, andJoinCriteria); return getThis(); } - public T leftJoin(SqlTable joinTable, String tableAlias, JoinCriterion onJoinCriterion, - List andJoinCriteria) { + public T leftJoin(SqlTable joinTable, String tableAlias, JoinCriterion onJoinCriterion, + List> andJoinCriteria) { addTableAlias(joinTable, tableAlias); return leftJoin(joinTable, onJoinCriterion, andJoinCriteria); } - public T leftJoin(Buildable subQuery, String tableAlias, JoinCriterion onJoinCriterion, - List andJoinCriteria) { + public T leftJoin(Buildable subQuery, String tableAlias, JoinCriterion onJoinCriterion, + List> andJoinCriteria) { addJoinSpecificationBuilder(buildSubQuery(subQuery, tableAlias), onJoinCriterion, JoinType.LEFT, andJoinCriteria); return getThis(); } - public T rightJoin(SqlTable joinTable, JoinCriterion onJoinCriterion, - JoinCriterion... andJoinCriteria) { + public T rightJoin(SqlTable joinTable, JoinCriterion onJoinCriterion, + JoinCriterion... andJoinCriteria) { addJoinSpecificationBuilder(joinTable, onJoinCriterion, JoinType.RIGHT, Arrays.asList(andJoinCriteria)); return getThis(); } - public T rightJoin(SqlTable joinTable, String tableAlias, JoinCriterion onJoinCriterion, - JoinCriterion... andJoinCriteria) { + public T rightJoin(SqlTable joinTable, String tableAlias, JoinCriterion onJoinCriterion, + JoinCriterion... andJoinCriteria) { addTableAlias(joinTable, tableAlias); return rightJoin(joinTable, onJoinCriterion, andJoinCriteria); } - public T rightJoin(SqlTable joinTable, JoinCriterion onJoinCriterion, - List andJoinCriteria) { + public T rightJoin(SqlTable joinTable, JoinCriterion onJoinCriterion, + List> andJoinCriteria) { addJoinSpecificationBuilder(joinTable, onJoinCriterion, JoinType.RIGHT, andJoinCriteria); return getThis(); } - public T rightJoin(SqlTable joinTable, String tableAlias, JoinCriterion onJoinCriterion, - List andJoinCriteria) { + public T rightJoin(SqlTable joinTable, String tableAlias, JoinCriterion onJoinCriterion, + List> andJoinCriteria) { addTableAlias(joinTable, tableAlias); return rightJoin(joinTable, onJoinCriterion, andJoinCriteria); } - public T rightJoin(Buildable subQuery, String tableAlias, JoinCriterion onJoinCriterion, - List andJoinCriteria) { + public T rightJoin(Buildable subQuery, String tableAlias, JoinCriterion onJoinCriterion, + List> andJoinCriteria) { addJoinSpecificationBuilder(buildSubQuery(subQuery, tableAlias), onJoinCriterion, JoinType.RIGHT, andJoinCriteria); return getThis(); } - public T fullJoin(SqlTable joinTable, JoinCriterion onJoinCriterion, - JoinCriterion... andJoinCriteria) { + public T fullJoin(SqlTable joinTable, JoinCriterion onJoinCriterion, + JoinCriterion... andJoinCriteria) { addJoinSpecificationBuilder(joinTable, onJoinCriterion, JoinType.FULL, Arrays.asList(andJoinCriteria)); return getThis(); } - public T fullJoin(SqlTable joinTable, String tableAlias, JoinCriterion onJoinCriterion, - JoinCriterion... andJoinCriteria) { + public T fullJoin(SqlTable joinTable, String tableAlias, JoinCriterion onJoinCriterion, + JoinCriterion... andJoinCriteria) { addTableAlias(joinTable, tableAlias); return fullJoin(joinTable, onJoinCriterion, andJoinCriteria); } - public T fullJoin(SqlTable joinTable, JoinCriterion onJoinCriterion, - List andJoinCriteria) { + public T fullJoin(SqlTable joinTable, JoinCriterion onJoinCriterion, + List> andJoinCriteria) { addJoinSpecificationBuilder(joinTable, onJoinCriterion, JoinType.FULL, andJoinCriteria); return getThis(); } - public T fullJoin(SqlTable joinTable, String tableAlias, JoinCriterion onJoinCriterion, - List andJoinCriteria) { + public T fullJoin(SqlTable joinTable, String tableAlias, JoinCriterion onJoinCriterion, + List> andJoinCriteria) { addTableAlias(joinTable, tableAlias); return fullJoin(joinTable, onJoinCriterion, andJoinCriteria); } - public T fullJoin(Buildable subQuery, String tableAlias, JoinCriterion onJoinCriterion, - List andJoinCriteria) { + public T fullJoin(Buildable subQuery, String tableAlias, JoinCriterion onJoinCriterion, + List> andJoinCriteria) { addJoinSpecificationBuilder(buildSubQuery(subQuery, tableAlias), onJoinCriterion, JoinType.FULL, andJoinCriteria); return getThis(); } - private void addJoinSpecificationBuilder(TableExpression joinTable, JoinCriterion onJoinCriterion, - JoinType joinType, List andJoinCriteria) { + private void addJoinSpecificationBuilder(TableExpression joinTable, JoinCriterion onJoinCriterion, + JoinType joinType, List> andJoinCriteria) { joinSpecificationBuilders.add(new JoinSpecification.Builder() .withJoinTable(joinTable) .withJoinType(joinType) diff --git a/src/main/java/org/mybatis/dynamic/sql/select/QueryExpressionDSL.java b/src/main/java/org/mybatis/dynamic/sql/select/QueryExpressionDSL.java index 422f915f2..22647f230 100644 --- a/src/main/java/org/mybatis/dynamic/sql/select/QueryExpressionDSL.java +++ b/src/main/java/org/mybatis/dynamic/sql/select/QueryExpressionDSL.java @@ -24,6 +24,7 @@ import org.jetbrains.annotations.NotNull; import org.mybatis.dynamic.sql.BasicColumn; +import org.mybatis.dynamic.sql.BindableColumn; import org.mybatis.dynamic.sql.CriteriaGroup; import org.mybatis.dynamic.sql.SortSpecification; import org.mybatis.dynamic.sql.SqlTable; @@ -349,12 +350,12 @@ public JoinSpecificationStarter(TableExpression joinTable, JoinType joinType) { this.joinType = joinType; } - public JoinSpecificationFinisher on(BasicColumn joinColumn, JoinCondition joinCondition) { + public JoinSpecificationFinisher on(BindableColumn joinColumn, JoinCondition joinCondition) { return new JoinSpecificationFinisher(joinTable, joinColumn, joinCondition, joinType); } - public JoinSpecificationFinisher on(BasicColumn joinColumn, JoinCondition onJoinCondition, - JoinCriterion... andJoinCriteria) { + public JoinSpecificationFinisher on(BindableColumn joinColumn, JoinCondition onJoinCondition, + JoinCriterion... andJoinCriteria) { return new JoinSpecificationFinisher(joinTable, joinColumn, onJoinCondition, joinType, andJoinCriteria); } } @@ -364,9 +365,9 @@ public class JoinSpecificationFinisher implements Buildable { private final JoinSpecification.Builder joinSpecificationBuilder; - public JoinSpecificationFinisher(TableExpression table, BasicColumn joinColumn, - JoinCondition joinCondition, JoinType joinType) { - JoinCriterion joinCriterion = new JoinCriterion.Builder() + public JoinSpecificationFinisher(TableExpression table, BindableColumn joinColumn, + JoinCondition joinCondition, JoinType joinType) { + JoinCriterion joinCriterion = new JoinCriterion.Builder() .withConnector("on") //$NON-NLS-1$ .withJoinColumn(joinColumn) .withJoinCondition(joinCondition) @@ -379,9 +380,9 @@ public JoinSpecificationFinisher(TableExpression table, BasicColumn joinColumn, addJoinSpecificationBuilder(joinSpecificationBuilder); } - public JoinSpecificationFinisher(TableExpression table, BasicColumn joinColumn, - JoinCondition joinCondition, JoinType joinType, JoinCriterion... andJoinCriteria) { - JoinCriterion onJoinCriterion = new JoinCriterion.Builder() + public JoinSpecificationFinisher(TableExpression table, BindableColumn joinColumn, + JoinCondition joinCondition, JoinType joinType, JoinCriterion... andJoinCriteria) { + JoinCriterion onJoinCriterion = new JoinCriterion.Builder() .withConnector("on") //$NON-NLS-1$ .withJoinColumn(joinColumn) .withJoinCondition(joinCondition) @@ -412,8 +413,8 @@ public QueryExpressionWhereBuilder where() { return QueryExpressionDSL.this.where(); } - public JoinSpecificationFinisher and(BasicColumn joinColumn, JoinCondition joinCondition) { - JoinCriterion joinCriterion = new JoinCriterion.Builder() + public JoinSpecificationFinisher and(BindableColumn joinColumn, JoinCondition joinCondition) { + JoinCriterion joinCriterion = new JoinCriterion.Builder() .withConnector("and") //$NON-NLS-1$ .withJoinColumn(joinColumn) .withJoinCondition(joinCondition) diff --git a/src/main/java/org/mybatis/dynamic/sql/select/join/ColumnBasedJoinCondition.java b/src/main/java/org/mybatis/dynamic/sql/select/join/ColumnBasedJoinCondition.java new file mode 100644 index 000000000..fda626dbb --- /dev/null +++ b/src/main/java/org/mybatis/dynamic/sql/select/join/ColumnBasedJoinCondition.java @@ -0,0 +1,37 @@ +/* + * Copyright 2016-2023 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 + * + * https://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.join; + +import java.util.Objects; + +import org.mybatis.dynamic.sql.BasicColumn; + +public abstract class ColumnBasedJoinCondition implements JoinCondition { + private final BasicColumn rightColumn; + + protected ColumnBasedJoinCondition(BasicColumn rightColumn) { + this.rightColumn = Objects.requireNonNull(rightColumn); + } + + public BasicColumn rightColumn() { + return rightColumn; + } + + @Override + public R accept(JoinConditionVisitor visitor) { + return visitor.visit(this); + } +} diff --git a/src/main/java/org/mybatis/dynamic/sql/select/join/EqualTo.java b/src/main/java/org/mybatis/dynamic/sql/select/join/EqualTo.java index d73cec215..4f20ec920 100644 --- a/src/main/java/org/mybatis/dynamic/sql/select/join/EqualTo.java +++ b/src/main/java/org/mybatis/dynamic/sql/select/join/EqualTo.java @@ -17,7 +17,7 @@ import org.mybatis.dynamic.sql.BasicColumn; -public class EqualTo extends JoinCondition { +public class EqualTo extends ColumnBasedJoinCondition { public EqualTo(BasicColumn rightColumn) { super(rightColumn); diff --git a/src/main/java/org/mybatis/dynamic/sql/select/join/EqualToValue.java b/src/main/java/org/mybatis/dynamic/sql/select/join/EqualToValue.java new file mode 100644 index 000000000..c7cb39c27 --- /dev/null +++ b/src/main/java/org/mybatis/dynamic/sql/select/join/EqualToValue.java @@ -0,0 +1,27 @@ +/* + * Copyright 2016-2023 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 + * + * https://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.join; + +public class EqualToValue extends TypedJoinCondition { + public EqualToValue(T value) { + super(value); + } + + @Override + public String operator() { + return "="; //$NON-NLS-1$ + } +} diff --git a/src/main/java/org/mybatis/dynamic/sql/select/join/JoinCondition.java b/src/main/java/org/mybatis/dynamic/sql/select/join/JoinCondition.java index 7db8327e2..6965db5b9 100644 --- a/src/main/java/org/mybatis/dynamic/sql/select/join/JoinCondition.java +++ b/src/main/java/org/mybatis/dynamic/sql/select/join/JoinCondition.java @@ -15,20 +15,8 @@ */ package org.mybatis.dynamic.sql.select.join; -import java.util.Objects; +public interface JoinCondition { + String operator(); -import org.mybatis.dynamic.sql.BasicColumn; - -public abstract class JoinCondition { - private final BasicColumn rightColumn; - - protected JoinCondition(BasicColumn rightColumn) { - this.rightColumn = Objects.requireNonNull(rightColumn); - } - - public BasicColumn rightColumn() { - return rightColumn; - } - - public abstract String operator(); + R accept(JoinConditionVisitor visitor); } diff --git a/src/main/java/org/mybatis/dynamic/sql/select/join/JoinConditionVisitor.java b/src/main/java/org/mybatis/dynamic/sql/select/join/JoinConditionVisitor.java new file mode 100644 index 000000000..7cee1a596 --- /dev/null +++ b/src/main/java/org/mybatis/dynamic/sql/select/join/JoinConditionVisitor.java @@ -0,0 +1,22 @@ +/* + * Copyright 2016-2023 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 + * + * https://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.join; + +public interface JoinConditionVisitor { + R visit(TypedJoinCondition condition); + + R visit(ColumnBasedJoinCondition condition); +} diff --git a/src/main/java/org/mybatis/dynamic/sql/select/join/JoinCriterion.java b/src/main/java/org/mybatis/dynamic/sql/select/join/JoinCriterion.java index 269c77c28..e40407b42 100644 --- a/src/main/java/org/mybatis/dynamic/sql/select/join/JoinCriterion.java +++ b/src/main/java/org/mybatis/dynamic/sql/select/join/JoinCriterion.java @@ -17,15 +17,15 @@ import java.util.Objects; -import org.mybatis.dynamic.sql.BasicColumn; +import org.mybatis.dynamic.sql.BindableColumn; -public class JoinCriterion { +public class JoinCriterion { private final String connector; - private final BasicColumn leftColumn; - private final JoinCondition joinCondition; + private final BindableColumn leftColumn; + private final JoinCondition joinCondition; - private JoinCriterion(Builder builder) { + private JoinCriterion(Builder builder) { connector = Objects.requireNonNull(builder.connector); leftColumn = Objects.requireNonNull(builder.joinColumn); joinCondition = Objects.requireNonNull(builder.joinCondition); @@ -35,40 +35,36 @@ public String connector() { return connector; } - public BasicColumn leftColumn() { + public BindableColumn leftColumn() { return leftColumn; } - public BasicColumn rightColumn() { - return joinCondition.rightColumn(); + public JoinCondition joinCondition() { + return joinCondition; } - public String operator() { - return joinCondition.operator(); - } - - public static class Builder { + public static class Builder { private String connector; - private BasicColumn joinColumn; - private JoinCondition joinCondition; + private BindableColumn joinColumn; + private JoinCondition joinCondition; - public Builder withConnector(String connector) { + public Builder withConnector(String connector) { this.connector = connector; return this; } - public Builder withJoinColumn(BasicColumn joinColumn) { + public Builder withJoinColumn(BindableColumn joinColumn) { this.joinColumn = joinColumn; return this; } - public Builder withJoinCondition(JoinCondition joinCondition) { + public Builder withJoinCondition(JoinCondition joinCondition) { this.joinCondition = joinCondition; return this; } - public JoinCriterion build() { - return new JoinCriterion(this); + public JoinCriterion build() { + return new JoinCriterion<>(this); } } } diff --git a/src/main/java/org/mybatis/dynamic/sql/select/join/JoinSpecification.java b/src/main/java/org/mybatis/dynamic/sql/select/join/JoinSpecification.java index c1024f441..7aba7473e 100644 --- a/src/main/java/org/mybatis/dynamic/sql/select/join/JoinSpecification.java +++ b/src/main/java/org/mybatis/dynamic/sql/select/join/JoinSpecification.java @@ -28,7 +28,7 @@ public class JoinSpecification { private final TableExpression table; - private final List joinCriteria; + private final List> joinCriteria; private final JoinType joinType; private JoinSpecification(Builder builder) { @@ -45,7 +45,7 @@ public TableExpression table() { return table; } - public Stream mapJoinCriteria(Function mapper) { + public Stream mapJoinCriteria(Function, R> mapper) { return joinCriteria.stream().map(mapper); } @@ -59,7 +59,7 @@ public static Builder withJoinTable(TableExpression table) { public static class Builder { private TableExpression table; - private final List joinCriteria = new ArrayList<>(); + private final List> joinCriteria = new ArrayList<>(); private JoinType joinType; public Builder withJoinTable(TableExpression table) { @@ -67,12 +67,12 @@ public Builder withJoinTable(TableExpression table) { return this; } - public Builder withJoinCriterion(JoinCriterion joinCriterion) { + public Builder withJoinCriterion(JoinCriterion joinCriterion) { this.joinCriteria.add(joinCriterion); return this; } - public Builder withJoinCriteria(List joinCriteria) { + public Builder withJoinCriteria(List> joinCriteria) { this.joinCriteria.addAll(joinCriteria); return this; } diff --git a/src/main/java/org/mybatis/dynamic/sql/select/join/TypedJoinCondition.java b/src/main/java/org/mybatis/dynamic/sql/select/join/TypedJoinCondition.java new file mode 100644 index 000000000..0feb6c78c --- /dev/null +++ b/src/main/java/org/mybatis/dynamic/sql/select/join/TypedJoinCondition.java @@ -0,0 +1,35 @@ +/* + * Copyright 2016-2023 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 + * + * https://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.join; + +import java.util.Objects; + +public abstract class TypedJoinCondition implements JoinCondition { + private final T value; + + protected TypedJoinCondition(T value) { + this.value = Objects.requireNonNull(value); + } + + public T value() { + return value; + } + + @Override + public R accept(JoinConditionVisitor visitor) { + return visitor.visit(this); + } +} diff --git a/src/main/java/org/mybatis/dynamic/sql/select/render/JoinConditionRenderer.java b/src/main/java/org/mybatis/dynamic/sql/select/render/JoinConditionRenderer.java new file mode 100644 index 000000000..75e8e382f --- /dev/null +++ b/src/main/java/org/mybatis/dynamic/sql/select/render/JoinConditionRenderer.java @@ -0,0 +1,98 @@ +/* + * Copyright 2016-2023 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 + * + * https://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 static org.mybatis.dynamic.sql.util.StringUtilities.spaceBefore; + +import java.util.Objects; +import java.util.concurrent.atomic.AtomicInteger; + +import org.mybatis.dynamic.sql.BasicColumn; +import org.mybatis.dynamic.sql.BindableColumn; +import org.mybatis.dynamic.sql.render.RenderingStrategy; +import org.mybatis.dynamic.sql.render.TableAliasCalculator; +import org.mybatis.dynamic.sql.select.join.ColumnBasedJoinCondition; +import org.mybatis.dynamic.sql.select.join.JoinConditionVisitor; +import org.mybatis.dynamic.sql.select.join.TypedJoinCondition; +import org.mybatis.dynamic.sql.util.FragmentAndParameters; + +public class JoinConditionRenderer implements JoinConditionVisitor { + private final RenderingStrategy renderingStrategy; + private final AtomicInteger sequence; + private final BindableColumn leftColumn; + private final TableAliasCalculator tableAliasCalculator; + + private JoinConditionRenderer(Builder builder) { + renderingStrategy = Objects.requireNonNull(builder.renderingStrategy); + sequence = Objects.requireNonNull(builder.sequence); + leftColumn = Objects.requireNonNull(builder.leftColumn); + tableAliasCalculator = Objects.requireNonNull(builder.tableAliasCalculator); + } + + @Override + public FragmentAndParameters visit(TypedJoinCondition condition) { + String mapKey = renderingStrategy.formatParameterMapKey(sequence); + + String placeHolder = leftColumn.renderingStrategy().orElse(renderingStrategy) + .getFormattedJdbcPlaceholder(leftColumn, RenderingStrategy.DEFAULT_PARAMETER_PREFIX, mapKey); + + return FragmentAndParameters.withFragment(condition.operator() + spaceBefore(placeHolder)) + .withParameter(mapKey, condition.value()) + .build(); + } + + @Override + public FragmentAndParameters visit(ColumnBasedJoinCondition condition) { + return FragmentAndParameters + .withFragment(condition.operator() + spaceBefore(applyTableAlias(condition.rightColumn()))) + .build(); + } + + private String applyTableAlias(BasicColumn column) { + return column.renderWithTableAlias(tableAliasCalculator); + } + + public static class Builder { + private RenderingStrategy renderingStrategy; + private AtomicInteger sequence; + private BindableColumn leftColumn; + private TableAliasCalculator tableAliasCalculator; + + public Builder withRenderingStrategy(RenderingStrategy renderingStrategy) { + this.renderingStrategy = renderingStrategy; + return this; + } + + public Builder withSequence(AtomicInteger sequence) { + this.sequence = sequence; + return this; + } + + public Builder withLeftColumn(BindableColumn leftColumn) { + this.leftColumn = leftColumn; + return this; + } + + public Builder withTableAliasCalculator(TableAliasCalculator tableAliasCalculator) { + this.tableAliasCalculator = tableAliasCalculator; + return this; + } + + public JoinConditionRenderer build() { + return new JoinConditionRenderer<>(this); + } + } +} 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 59efad3b7..8e9e7a04c 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 @@ -18,9 +18,11 @@ import static org.mybatis.dynamic.sql.util.StringUtilities.spaceBefore; import java.util.Objects; +import java.util.concurrent.atomic.AtomicInteger; import java.util.stream.Collectors; import org.mybatis.dynamic.sql.BasicColumn; +import org.mybatis.dynamic.sql.render.RenderingStrategy; import org.mybatis.dynamic.sql.render.TableAliasCalculator; import org.mybatis.dynamic.sql.select.join.JoinCriterion; import org.mybatis.dynamic.sql.select.join.JoinModel; @@ -32,11 +34,15 @@ public class JoinRenderer { private final JoinModel joinModel; private final TableExpressionRenderer tableExpressionRenderer; private final TableAliasCalculator tableAliasCalculator; + private final RenderingStrategy renderingStrategy; + private final AtomicInteger sequence; private JoinRenderer(Builder builder) { joinModel = Objects.requireNonNull(builder.joinModel); tableExpressionRenderer = Objects.requireNonNull(builder.tableExpressionRenderer); tableAliasCalculator = Objects.requireNonNull(builder.tableAliasCalculator); + renderingStrategy = Objects.requireNonNull(builder.renderingStrategy); + sequence = Objects.requireNonNull(builder.sequence); } public FragmentAndParameters render() { @@ -50,26 +56,44 @@ public FragmentAndParameters render() { private FragmentAndParameters renderJoinSpecification(JoinSpecification joinSpecification) { FragmentAndParameters renderedTable = joinSpecification.table().accept(tableExpressionRenderer); + FragmentAndParameters renderedJoin = renderConditions(joinSpecification); String fragment = joinSpecification.joinType().type() + spaceBefore(renderedTable.fragment()) - + spaceBefore(renderConditions(joinSpecification)); + + spaceBefore(renderedJoin.fragment()); return FragmentAndParameters.withFragment(fragment) .withParameters(renderedTable.parameters()) + .withParameters(renderedJoin.parameters()) .build(); } - private String renderConditions(JoinSpecification joinSpecification) { - return joinSpecification.mapJoinCriteria(this::renderCriterion) - .collect(Collectors.joining(" ")); //$NON-NLS-1$ + private FragmentAndParameters renderConditions(JoinSpecification joinSpecification) { + FragmentCollector fragmentCollector = joinSpecification.mapJoinCriteria(this::renderCriterion) + .collect(FragmentCollector.collect()); + + return FragmentAndParameters + .withFragment(fragmentCollector.fragments().collect(Collectors.joining(" "))) //$NON-NLS-1$ + .withParameters(fragmentCollector.parameters()) + .build(); } - private String renderCriterion(JoinCriterion joinCriterion) { - return joinCriterion.connector() - + spaceBefore(applyTableAlias(joinCriterion.leftColumn())) - + spaceBefore(joinCriterion.operator()) - + spaceBefore(applyTableAlias(joinCriterion.rightColumn())); + private FragmentAndParameters renderCriterion(JoinCriterion joinCriterion) { + String prefix = joinCriterion.connector() + + spaceBefore(applyTableAlias(joinCriterion.leftColumn())); + + JoinConditionRenderer joinConditionRenderer = new JoinConditionRenderer.Builder() + .withRenderingStrategy(renderingStrategy) + .withSequence(sequence) + .withTableAliasCalculator(tableAliasCalculator) + .withLeftColumn(joinCriterion.leftColumn()) + .build(); + + FragmentAndParameters suffix = joinCriterion.joinCondition().accept(joinConditionRenderer); + + return FragmentAndParameters.withFragment(prefix + spaceBefore(suffix.fragment())) + .withParameters(suffix.parameters()) + .build(); } private String applyTableAlias(BasicColumn column) { @@ -84,6 +108,8 @@ public static class Builder { private JoinModel joinModel; private TableExpressionRenderer tableExpressionRenderer; private TableAliasCalculator tableAliasCalculator; + private RenderingStrategy renderingStrategy; + private AtomicInteger sequence; public Builder withJoinModel(JoinModel joinModel) { this.joinModel = joinModel; @@ -100,6 +126,16 @@ public Builder withTableAliasCalculator(TableAliasCalculator tableAliasCalculato return this; } + public Builder withRenderingStrategy(RenderingStrategy renderingStrategy) { + this.renderingStrategy = renderingStrategy; + return this; + } + + public Builder withSequence(AtomicInteger sequence) { + this.sequence = sequence; + return this; + } + public JoinRenderer build() { return new JoinRenderer(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 49bb09694..e69d576df 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 @@ -171,6 +171,8 @@ private FragmentAndParameters renderJoin(JoinModel joinModel) { return JoinRenderer.withJoinModel(joinModel) .withTableExpressionRenderer(tableExpressionRenderer) .withTableAliasCalculator(tableAliasCalculator) + .withRenderingStrategy(renderingStrategy) + .withSequence(sequence) .build() .render(); } diff --git a/src/main/kotlin/org/mybatis/dynamic/sql/util/kotlin/JoinCollector.kt b/src/main/kotlin/org/mybatis/dynamic/sql/util/kotlin/JoinCollector.kt index 84829f9d6..24473360f 100644 --- a/src/main/kotlin/org/mybatis/dynamic/sql/util/kotlin/JoinCollector.kt +++ b/src/main/kotlin/org/mybatis/dynamic/sql/util/kotlin/JoinCollector.kt @@ -15,7 +15,7 @@ */ package org.mybatis.dynamic.sql.util.kotlin -import org.mybatis.dynamic.sql.BasicColumn +import org.mybatis.dynamic.sql.BindableColumn import org.mybatis.dynamic.sql.SqlBuilder import org.mybatis.dynamic.sql.select.join.JoinCondition import org.mybatis.dynamic.sql.select.join.JoinCriterion @@ -25,23 +25,23 @@ typealias JoinReceiver = JoinCollector.() -> Unit @MyBatisDslMarker class JoinCollector { - private var onJoinCriterion: JoinCriterion? = null - internal val andJoinCriteria = mutableListOf() + private var onJoinCriterion: JoinCriterion<*>? = null + internal val andJoinCriteria = mutableListOf>() - internal fun onJoinCriterion() : JoinCriterion = + internal fun onJoinCriterion() : JoinCriterion<*> = onJoinCriterion?: throw KInvalidSQLException(Messages.getString("ERROR.22")) //$NON-NLS-1$ - fun on(leftColumn: BasicColumn): RightColumnCollector = RightColumnCollector { - onJoinCriterion = JoinCriterion.Builder() + fun on(leftColumn: BindableColumn): RightColumnCollector = RightColumnCollector { + onJoinCriterion = JoinCriterion.Builder() .withConnector("on") //$NON-NLS-1$ .withJoinColumn(leftColumn) .withJoinCondition(it) .build() } - fun and(leftColumn: BasicColumn): RightColumnCollector = RightColumnCollector { + fun and(leftColumn: BindableColumn): RightColumnCollector = RightColumnCollector { andJoinCriteria.add( - JoinCriterion.Builder() + JoinCriterion.Builder() .withConnector("and") //$NON-NLS-1$ .withJoinColumn(leftColumn) .withJoinCondition(it) @@ -50,6 +50,8 @@ class JoinCollector { } } -class RightColumnCollector(private val joinConditionConsumer: (JoinCondition) -> Unit) { - infix fun equalTo(rightColumn: BasicColumn) = joinConditionConsumer.invoke(SqlBuilder.equalTo(rightColumn)) +class RightColumnCollector(private val joinConditionConsumer: (JoinCondition) -> Unit) { + infix fun equalTo(rightColumn: BindableColumn) = joinConditionConsumer.invoke(SqlBuilder.equalTo(rightColumn)) + + infix fun equalTo(value: T) = joinConditionConsumer.invoke(SqlBuilder.equalTo(value)) } diff --git a/src/test/java/examples/joins/JoinMapperTest.java b/src/test/java/examples/joins/JoinMapperTest.java index 1b90b06a5..9417e467f 100644 --- a/src/test/java/examples/joins/JoinMapperTest.java +++ b/src/test/java/examples/joins/JoinMapperTest.java @@ -1193,4 +1193,72 @@ void testFetchFirstOnlyAfterJoin() { assertThat(row).containsEntry("ITEM_ID", 22); } } + + @Test + void testJoinWithParameterValue() { + try (SqlSession session = sqlSessionFactory.openSession()) { + CommonSelectMapper mapper = session.getMapper(CommonSelectMapper.class); + + SelectStatementProvider selectStatement = select(orderLine.orderId, orderLine.quantity, itemMaster.itemId, itemMaster.description) + .from(itemMaster, "im") + .join(orderLine, "ol").on(orderLine.itemId, equalTo(itemMaster.itemId)) + .and(orderLine.orderId, equalTo(1)) + .build() + .render(RenderingStrategies.MYBATIS3); + + String expectedStatement = "select ol.order_id, ol.quantity, im.item_id, im.description" + + " from ItemMaster im join OrderLine ol on ol.item_id = im.item_id" + + " and ol.order_id = #{parameters.p1,jdbcType=INTEGER}"; + assertThat(selectStatement.getSelectStatement()).isEqualTo(expectedStatement); + + List> rows = mapper.selectManyMappedRows(selectStatement); + + assertThat(rows).hasSize(2); + Map row = rows.get(0); + assertThat(row).containsEntry("ORDER_ID", 1); + assertThat(row).containsEntry("QUANTITY", 1); + assertThat(row).containsEntry("DESCRIPTION", "Helmet"); + assertThat(row).containsEntry("ITEM_ID", 22); + + row = rows.get(1); + assertThat(row).containsEntry("ORDER_ID", 1); + assertThat(row).containsEntry("QUANTITY", 1); + assertThat(row).containsEntry("DESCRIPTION", "First Base Glove"); + assertThat(row).containsEntry("ITEM_ID", 33); + } + } + + @Test + void testJoinWithConstant() { + try (SqlSession session = sqlSessionFactory.openSession()) { + CommonSelectMapper mapper = session.getMapper(CommonSelectMapper.class); + + SelectStatementProvider selectStatement = select(orderLine.orderId, orderLine.quantity, itemMaster.itemId, itemMaster.description) + .from(itemMaster, "im") + .join(orderLine, "ol").on(orderLine.itemId, equalTo(itemMaster.itemId)) + .and(orderLine.orderId, equalTo(constant("1"))) + .build() + .render(RenderingStrategies.MYBATIS3); + + String expectedStatement = "select ol.order_id, ol.quantity, im.item_id, im.description" + + " from ItemMaster im join OrderLine ol on ol.item_id = im.item_id" + + " and ol.order_id = 1"; + assertThat(selectStatement.getSelectStatement()).isEqualTo(expectedStatement); + + List> rows = mapper.selectManyMappedRows(selectStatement); + + assertThat(rows).hasSize(2); + Map row = rows.get(0); + assertThat(row).containsEntry("ORDER_ID", 1); + assertThat(row).containsEntry("QUANTITY", 1); + assertThat(row).containsEntry("DESCRIPTION", "Helmet"); + assertThat(row).containsEntry("ITEM_ID", 22); + + row = rows.get(1); + assertThat(row).containsEntry("ORDER_ID", 1); + assertThat(row).containsEntry("QUANTITY", 1); + assertThat(row).containsEntry("DESCRIPTION", "First Base Glove"); + assertThat(row).containsEntry("ITEM_ID", 33); + } + } } diff --git a/src/test/kotlin/examples/kotlin/mybatis3/joins/JoinMapperTest.kt b/src/test/kotlin/examples/kotlin/mybatis3/joins/JoinMapperTest.kt index 737917ae7..11ce7be25 100644 --- a/src/test/kotlin/examples/kotlin/mybatis3/joins/JoinMapperTest.kt +++ b/src/test/kotlin/examples/kotlin/mybatis3/joins/JoinMapperTest.kt @@ -30,6 +30,7 @@ import org.junit.jupiter.api.Test import org.junit.jupiter.api.TestInstance import org.mybatis.dynamic.sql.util.Messages import org.mybatis.dynamic.sql.util.kotlin.KInvalidSQLException +import org.mybatis.dynamic.sql.util.kotlin.elements.constant import org.mybatis.dynamic.sql.util.kotlin.elements.invoke import org.mybatis.dynamic.sql.util.kotlin.elements.max import org.mybatis.dynamic.sql.util.kotlin.mybatis3.select @@ -86,6 +87,76 @@ class JoinMapperTest { } } + @Test + fun testSingleTableJoinWithValue() { + sqlSessionFactory.openSession().use { session -> + val mapper = session.getMapper(JoinMapper::class.java) + + val selectStatement = select( + orderMaster.orderId, orderMaster.orderDate, + orderDetail.lineNumber, orderDetail.description, orderDetail.quantity + ) { + from(orderMaster, "om") + join(orderDetail, "od") { + on(orderMaster.orderId) equalTo orderDetail.orderId + and(orderMaster.orderId) equalTo 1 + } + } + + val expectedStatement = "select om.order_id, om.order_date, od.line_number, od.description, od.quantity" + + " from OrderMaster om join OrderDetail od on om.order_id = od.order_id" + + " and om.order_id = #{parameters.p1,jdbcType=INTEGER}" + + assertThat(selectStatement.selectStatement).isEqualTo(expectedStatement) + + val rows = mapper.selectMany(selectStatement) + + assertThat(rows).hasSize(1) + + with(rows[0]) { + assertThat(id).isEqualTo(1) + assertThat(details).hasSize(2) + assertThat(details?.get(0)?.lineNumber).isEqualTo(1) + assertThat(details?.get(1)?.lineNumber).isEqualTo(2) + } + } + } + + @Test + fun testSingleTableJoinWithConstant() { + sqlSessionFactory.openSession().use { session -> + val mapper = session.getMapper(JoinMapper::class.java) + + val selectStatement = select( + orderMaster.orderId, orderMaster.orderDate, + orderDetail.lineNumber, orderDetail.description, orderDetail.quantity + ) { + from(orderMaster, "om") + join(orderDetail, "od") { + on(orderMaster.orderId) equalTo orderDetail.orderId + and(orderMaster.orderId) equalTo constant("1") + } + } + + val expectedStatement = "select om.order_id, om.order_date, od.line_number, od.description, od.quantity" + + " from OrderMaster om join OrderDetail od on om.order_id = od.order_id" + + " and om.order_id = 1" + + assertThat(selectStatement.selectStatement).isEqualTo(expectedStatement) + + val rows = mapper.selectMany(selectStatement) + + assertThat(rows).hasSize(1) + + with(rows[0]) { + assertThat(id).isEqualTo(1) + assertThat(details).hasSize(2) + assertThat(details?.get(0)?.lineNumber).isEqualTo(1) + assertThat(details?.get(1)?.lineNumber).isEqualTo(2) + } + } + } + @Test fun testCompoundJoin1() { // this is a nonsensical join, but it does test the "and" capability