diff --git a/pom.xml b/pom.xml index 23b4c1e34..6024a94d7 100644 --- a/pom.xml +++ b/pom.xml @@ -15,14 +15,9 @@ limitations under the License. --> - - + 4.0.0 org.mybatis @@ -45,6 +40,11 @@ 4.1.2.RELEASE 1.1.0 org.mybatis.dynamic.sql + 1.3.50 + 1.8 + pom.xml,src/main/java,src/main/kotlin + src/test/java,src/test/kotlin + 0.8.4 @@ -60,6 +60,68 @@ + + org.jetbrains.kotlin + kotlin-maven-plugin + ${kotlin.version} + + + compile + + compile + + + + ${project.basedir}/src/main/kotlin + ${project.basedir}/src/main/java + + + + + test-compile + + test-compile + + + + ${project.basedir}/src/test/kotlin + ${project.basedir}/src/test/java + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + + + + default-compile + none + + + + default-testCompile + none + + + java-compile + compile + + compile + + + + java-test-compile + test-compile + + testCompile + + + + maven-resources-plugin @@ -82,6 +144,24 @@ + + com.github.ozsie + detekt-maven-plugin + 1.0.0 + + + verify + check + + + + + org.eluder.coveralls + coveralls-maven-plugin + + src/main/java,src/main/kotlin + + @@ -98,6 +178,12 @@ + + org.jetbrains.kotlin + kotlin-stdlib-jdk8 + ${kotlin.version} + provided + org.junit.jupiter junit-jupiter-api diff --git a/src/main/java/org/mybatis/dynamic/sql/SqlBuilder.java b/src/main/java/org/mybatis/dynamic/sql/SqlBuilder.java index 49b52f5c4..43acb6738 100644 --- a/src/main/java/org/mybatis/dynamic/sql/SqlBuilder.java +++ b/src/main/java/org/mybatis/dynamic/sql/SqlBuilder.java @@ -25,6 +25,7 @@ import org.mybatis.dynamic.sql.insert.InsertDSL; import org.mybatis.dynamic.sql.insert.InsertSelectDSL; import org.mybatis.dynamic.sql.insert.MultiRowInsertDSL; +import org.mybatis.dynamic.sql.select.CountDSL; import org.mybatis.dynamic.sql.select.QueryExpressionDSL.FromGatherer; import org.mybatis.dynamic.sql.select.SelectDSL; import org.mybatis.dynamic.sql.select.SelectModel; @@ -43,10 +44,9 @@ import org.mybatis.dynamic.sql.select.function.Substring; import org.mybatis.dynamic.sql.select.function.Subtract; import org.mybatis.dynamic.sql.select.function.Upper; -import org.mybatis.dynamic.sql.select.join.AndJoinCriterion; import org.mybatis.dynamic.sql.select.join.EqualTo; import org.mybatis.dynamic.sql.select.join.JoinCondition; -import org.mybatis.dynamic.sql.select.join.OnJoinCriterion; +import org.mybatis.dynamic.sql.select.join.JoinCriterion; import org.mybatis.dynamic.sql.update.UpdateDSL; import org.mybatis.dynamic.sql.update.UpdateModel; import org.mybatis.dynamic.sql.util.Buildable; @@ -103,6 +103,10 @@ public interface SqlBuilder { // statements + static CountDSL countFrom(SqlTable table) { + return CountDSL.countFrom(table); + } + static DeleteDSL deleteFrom(SqlTable table) { return DeleteDSL.deleteFrom(table); } @@ -192,15 +196,17 @@ static SqlCriterion and(BindableColumn column, VisitableCondition c } // join support - static AndJoinCriterion and(BasicColumn joinColumn, JoinCondition joinCondition) { - return new AndJoinCriterion.Builder() + static JoinCriterion and(BasicColumn joinColumn, JoinCondition joinCondition) { + return new JoinCriterion.Builder() + .withConnector("and") //$NON-NLS-1$ .withJoinColumn(joinColumn) .withJoinCondition(joinCondition) .build(); } - static OnJoinCriterion on(BasicColumn joinColumn, JoinCondition joinCondition) { - return new OnJoinCriterion.Builder() + static JoinCriterion on(BasicColumn joinColumn, JoinCondition joinCondition) { + return new JoinCriterion.Builder() + .withConnector("on") //$NON-NLS-1$ .withJoinColumn(joinColumn) .withJoinCondition(joinCondition) .build(); @@ -547,7 +553,16 @@ static IsNotLikeWhenPresent isNotLikeWhenPresent(T value) { static IsNotLikeWhenPresent isNotLikeWhenPresent(Supplier valueSupplier) { return IsNotLikeWhenPresent.of(valueSupplier); } - + + // shortcuts for booleans + static IsEqualTo isTrue() { + return isEqualTo(Boolean.TRUE); + } + + static IsEqualTo isFalse() { + return isEqualTo(Boolean.FALSE); + } + // conditions for strings only static IsLikeCaseInsensitive isLikeCaseInsensitive(String value) { return isLikeCaseInsensitive(() -> value); diff --git a/src/main/java/org/mybatis/dynamic/sql/SqlTable.java b/src/main/java/org/mybatis/dynamic/sql/SqlTable.java index 3f5e206bc..718cc89af 100644 --- a/src/main/java/org/mybatis/dynamic/sql/SqlTable.java +++ b/src/main/java/org/mybatis/dynamic/sql/SqlTable.java @@ -19,6 +19,7 @@ import java.util.Objects; import java.util.Optional; import java.util.function.Supplier; +import org.jetbrains.annotations.NotNull; public class SqlTable { @@ -84,15 +85,18 @@ public String tableNameAtRuntime() { public SqlColumn allColumns() { return SqlColumn.of("*", this); //$NON-NLS-1$ } - + + @NotNull public SqlColumn column(String name) { return SqlColumn.of(name, this); } + @NotNull public SqlColumn column(String name, JDBCType jdbcType) { return SqlColumn.of(name, this, jdbcType); } + @NotNull public SqlColumn column(String name, JDBCType jdbcType, String typeHandler) { return SqlColumn.of(name, this, jdbcType).withTypeHandler(typeHandler); } diff --git a/src/main/java/org/mybatis/dynamic/sql/delete/DeleteDSL.java b/src/main/java/org/mybatis/dynamic/sql/delete/DeleteDSL.java index 154395c9e..2af82cb47 100644 --- a/src/main/java/org/mybatis/dynamic/sql/delete/DeleteDSL.java +++ b/src/main/java/org/mybatis/dynamic/sql/delete/DeleteDSL.java @@ -32,7 +32,7 @@ public class DeleteDSL implements Buildable { private Function adapterFunction; private SqlTable table; - protected DeleteWhereBuilder whereBuilder; + private DeleteWhereBuilder whereBuilder = new DeleteWhereBuilder(); private DeleteDSL(SqlTable table, Function adapterFunction) { this.table = Objects.requireNonNull(table); @@ -40,21 +40,15 @@ private DeleteDSL(SqlTable table, Function adapterFunction) { } public DeleteWhereBuilder where() { - whereBuilder = new DeleteWhereBuilder(); - return whereBuilder; - } - - public DeleteWhereBuilder where(BindableColumn column, VisitableCondition condition) { - whereBuilder = new DeleteWhereBuilder(column, condition); return whereBuilder; } public DeleteWhereBuilder where(BindableColumn column, VisitableCondition condition, SqlCriterion...subCriteria) { - whereBuilder = new DeleteWhereBuilder(column, condition, subCriteria); + whereBuilder.and(column, condition, subCriteria); return whereBuilder; } - + /** * WARNING! Calling this method could result in an delete statement that deletes * all rows in a table. @@ -64,7 +58,7 @@ public DeleteWhereBuilder where(BindableColumn column, VisitableCondition @Override public R build() { DeleteModel deleteModel = DeleteModel.withTable(table) - .withWhereModel(whereBuilder == null ? null : whereBuilder.buildWhereModel()) + .withWhereModel(whereBuilder.buildWhereModel()) .build(); return adapterFunction.apply(deleteModel); } @@ -99,15 +93,6 @@ private DeleteWhereBuilder() { super(); } - private DeleteWhereBuilder(BindableColumn column, VisitableCondition condition) { - super(column, condition); - } - - private DeleteWhereBuilder(BindableColumn column, VisitableCondition condition, - SqlCriterion...subCriteria) { - super(column, condition, subCriteria); - } - @Override public R build() { return DeleteDSL.this.build(); diff --git a/src/main/java/org/mybatis/dynamic/sql/delete/DeleteDSLCompleter.java b/src/main/java/org/mybatis/dynamic/sql/delete/DeleteDSLCompleter.java index 6725a7fa6..90e270c5d 100644 --- a/src/main/java/org/mybatis/dynamic/sql/delete/DeleteDSLCompleter.java +++ b/src/main/java/org/mybatis/dynamic/sql/delete/DeleteDSLCompleter.java @@ -23,9 +23,9 @@ import org.mybatis.dynamic.sql.util.mybatis3.MyBatis3Utils; /** - * Represents a function that can be used to create a general delete method in the style - * of MyBatis Generator. When using this function, you can create a method that does not require a user to - * call the build() and render() methods - making client code look a bit cleaner. + * Represents a function that can be used to create a simplified delete method. When using this function + * you can create a method that does not require a user to call the build() and render() methods - making + * client code look a bit cleaner. * *

This function is intended to be used in conjunction with a utility method like * {@link MyBatis3Utils#deleteFrom(ToIntFunction, SqlTable, DeleteDSLCompleter)} diff --git a/src/main/java/org/mybatis/dynamic/sql/delete/DeleteModel.java b/src/main/java/org/mybatis/dynamic/sql/delete/DeleteModel.java index 9889d1855..67a56c1d6 100644 --- a/src/main/java/org/mybatis/dynamic/sql/delete/DeleteModel.java +++ b/src/main/java/org/mybatis/dynamic/sql/delete/DeleteModel.java @@ -18,6 +18,7 @@ import java.util.Objects; import java.util.Optional; +import org.jetbrains.annotations.NotNull; import org.mybatis.dynamic.sql.SqlTable; import org.mybatis.dynamic.sql.delete.render.DeleteRenderer; import org.mybatis.dynamic.sql.delete.render.DeleteStatementProvider; @@ -40,7 +41,8 @@ public SqlTable table() { public Optional whereModel() { return Optional.ofNullable(whereModel); } - + + @NotNull public DeleteStatementProvider render(RenderingStrategy renderingStrategy) { return DeleteRenderer.withDeleteModel(this) .withRenderingStrategy(renderingStrategy) diff --git a/src/main/java/org/mybatis/dynamic/sql/select/AbstractQueryExpressionDSL.java b/src/main/java/org/mybatis/dynamic/sql/select/AbstractQueryExpressionDSL.java new file mode 100644 index 000000000..b115d755c --- /dev/null +++ b/src/main/java/org/mybatis/dynamic/sql/select/AbstractQueryExpressionDSL.java @@ -0,0 +1,169 @@ +/** + * Copyright 2016-2019 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; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; +import java.util.stream.Collectors; + +import org.mybatis.dynamic.sql.SqlTable; +import org.mybatis.dynamic.sql.select.join.JoinCriterion; +import org.mybatis.dynamic.sql.select.join.JoinModel; +import org.mybatis.dynamic.sql.select.join.JoinSpecification; +import org.mybatis.dynamic.sql.select.join.JoinType; +import org.mybatis.dynamic.sql.util.Buildable; + +public abstract class AbstractQueryExpressionDSL, R> + implements Buildable { + + private List joinSpecificationBuilders = new ArrayList<>(); + protected Map tableAliases = new HashMap<>(); + private SqlTable table; + + protected AbstractQueryExpressionDSL(SqlTable table) { + this.table = Objects.requireNonNull(table); + } + + public SqlTable table() { + return table; + } + + 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) { + tableAliases.put(joinTable, tableAlias); + return join(joinTable, onJoinCriterion, 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) { + tableAliases.put(joinTable, tableAlias); + return join(joinTable, onJoinCriterion, 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) { + tableAliases.put(joinTable, tableAlias); + return leftJoin(joinTable, onJoinCriterion, 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) { + tableAliases.put(joinTable, tableAlias); + return leftJoin(joinTable, onJoinCriterion, 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) { + tableAliases.put(joinTable, tableAlias); + return rightJoin(joinTable, onJoinCriterion, 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) { + tableAliases.put(joinTable, tableAlias); + return rightJoin(joinTable, onJoinCriterion, 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) { + tableAliases.put(joinTable, tableAlias); + return fullJoin(joinTable, onJoinCriterion, 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) { + tableAliases.put(joinTable, tableAlias); + return fullJoin(joinTable, onJoinCriterion, andJoinCriteria); + } + + private void addJoinSpecificationBuilder(SqlTable joinTable, JoinCriterion onJoinCriterion, JoinType joinType, + List andJoinCriteria) { + joinSpecificationBuilders.add(new JoinSpecification.Builder() + .withJoinTable(joinTable) + .withJoinType(joinType) + .withJoinCriterion(onJoinCriterion) + .withJoinCriteria(andJoinCriteria)); + } + + protected void addJoinSpecificationBuilder(JoinSpecification.Builder builder) { + joinSpecificationBuilders.add(builder); + } + + protected Optional buildJoinModel() { + if (joinSpecificationBuilders.isEmpty()) { + return Optional.empty(); + } + + return Optional.of(JoinModel.of(joinSpecificationBuilders.stream() + .map(JoinSpecification.Builder::build) + .collect(Collectors.toList()))); + } + + protected abstract T getThis(); +} diff --git a/src/main/java/org/mybatis/dynamic/sql/select/CountDSL.java b/src/main/java/org/mybatis/dynamic/sql/select/CountDSL.java new file mode 100644 index 000000000..14819bc84 --- /dev/null +++ b/src/main/java/org/mybatis/dynamic/sql/select/CountDSL.java @@ -0,0 +1,104 @@ +/** + * Copyright 2016-2019 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; + +import java.util.Objects; +import java.util.function.Function; + +import org.mybatis.dynamic.sql.BindableColumn; +import org.mybatis.dynamic.sql.SqlBuilder; +import org.mybatis.dynamic.sql.SqlCriterion; +import org.mybatis.dynamic.sql.SqlTable; +import org.mybatis.dynamic.sql.VisitableCondition; +import org.mybatis.dynamic.sql.util.Buildable; +import org.mybatis.dynamic.sql.where.AbstractWhereDSL; + +/** + * DSL for building count queries. Count queries are specializations of select queries. They have joins and where + * clauses, but not the other parts of a select (group by, order by, etc.) Count queries always return + * a long. If these restrictions are not acceptable, then use the Select DSL for an unrestricted select statement. + * + * @param the type of model built by this Builder. Typically SelectModel. + * + * @author Jeff Butler + */ +public class CountDSL extends AbstractQueryExpressionDSL, R> implements Buildable { + + private Function adapterFunction; + private CountWhereBuilder whereBuilder = new CountWhereBuilder(); + + private CountDSL(SqlTable table, Function adapterFunction) { + super(table); + this.adapterFunction = Objects.requireNonNull(adapterFunction); + } + + public CountWhereBuilder where() { + return whereBuilder; + } + + public CountWhereBuilder where(BindableColumn column, VisitableCondition condition, + SqlCriterion...subCriteria) { + whereBuilder.and(column, condition, subCriteria); + return whereBuilder; + } + + @Override + public R build() { + return adapterFunction.apply(buildModel()); + } + + private SelectModel buildModel() { + QueryExpressionModel.Builder b = new QueryExpressionModel.Builder() + .withSelectColumn(SqlBuilder.count()) + .withTable(table()) + .withTableAliases(tableAliases) + .withWhereModel(whereBuilder.buildWhereModel()); + + buildJoinModel().ifPresent(b::withJoinModel); + + return new SelectModel.Builder() + .withQueryExpression(b.build()) + .build(); + } + + public static CountDSL countFrom(SqlTable table) { + return countFrom(Function.identity(), table); + } + + public static CountDSL countFrom(Function adapterFunction, SqlTable table) { + return new CountDSL<>(table, adapterFunction); + } + + @Override + protected CountDSL getThis() { + return this; + } + + public class CountWhereBuilder extends AbstractWhereDSL + implements Buildable { + private CountWhereBuilder() {} + + @Override + public R build() { + return CountDSL.this.build(); + } + + @Override + protected CountWhereBuilder getThis() { + return this; + } + } +} diff --git a/src/main/java/org/mybatis/dynamic/sql/select/CountDSLCompleter.java b/src/main/java/org/mybatis/dynamic/sql/select/CountDSLCompleter.java new file mode 100644 index 000000000..63dc135cc --- /dev/null +++ b/src/main/java/org/mybatis/dynamic/sql/select/CountDSLCompleter.java @@ -0,0 +1,76 @@ +/** + * Copyright 2016-2019 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; + +import java.util.function.Function; +import java.util.function.ToLongFunction; + +import org.mybatis.dynamic.sql.util.Buildable; +import org.mybatis.dynamic.sql.util.mybatis3.MyBatis3Utils; + +/** + * Represents a function that can be used to create a general count method. When using this function, + * you can create a method that does not require a user to call the build() and render() methods - making + * client code look a bit cleaner. + * + *

This function is intended to by used in conjunction with a utility method like + * {@link MyBatis3Utils#count(ToLongFunction, CountDSL, CountDSLCompleter)} + * + *

For example, you can create mapper interface methods like this: + * + *

+ * @SelectProvider(type=SqlProviderAdapter.class, method="select")
+ * long count(SelectStatementProvider selectStatement);
+ *
+ * default long count(CountDSLCompleter completer) {
+ *     return MyBatis3Utils.count(this::count, person, completer);
+ * } 
+ * 
+ * + *

And then call the simplified default method like this: + * + *

+ * long rows = mapper.count(c ->
+ *         c.where(occupation, isNull()));
+ * 
+ * + *

You can implement a "count all" with the following code: + * + *

+ * long rows = mapper.count(c -> c);
+ * 
+ * + *

Or + * + *

+ * long rows = mapper.count(CountDSLCompleter.allRows());
+ * 
+ * + * @author Jeff Butler + */ +@FunctionalInterface +public interface CountDSLCompleter extends + Function, Buildable> { + + /** + * Returns a completer that can be used to count every row in a table. + * + * @return the completer that will count every row in a table + */ + static CountDSLCompleter allRows() { + return c -> c; + } +} 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 e404144d6..6f47d0aa1 100644 --- a/src/main/java/org/mybatis/dynamic/sql/select/QueryExpressionDSL.java +++ b/src/main/java/org/mybatis/dynamic/sql/select/QueryExpressionDSL.java @@ -15,13 +15,9 @@ */ package org.mybatis.dynamic.sql.select; -import java.util.ArrayList; import java.util.Arrays; -import java.util.HashMap; import java.util.List; -import java.util.Map; import java.util.Objects; -import java.util.stream.Collectors; import org.mybatis.dynamic.sql.BasicColumn; import org.mybatis.dynamic.sql.BindableColumn; @@ -29,57 +25,46 @@ import org.mybatis.dynamic.sql.SqlCriterion; import org.mybatis.dynamic.sql.SqlTable; import org.mybatis.dynamic.sql.VisitableCondition; -import org.mybatis.dynamic.sql.select.join.AndJoinCriterion; import org.mybatis.dynamic.sql.select.join.JoinCondition; import org.mybatis.dynamic.sql.select.join.JoinCriterion; -import org.mybatis.dynamic.sql.select.join.JoinModel; import org.mybatis.dynamic.sql.select.join.JoinSpecification; import org.mybatis.dynamic.sql.select.join.JoinType; -import org.mybatis.dynamic.sql.select.join.OnJoinCriterion; import org.mybatis.dynamic.sql.util.Buildable; import org.mybatis.dynamic.sql.where.AbstractWhereDSL; -public class QueryExpressionDSL implements Buildable { +public class QueryExpressionDSL extends AbstractQueryExpressionDSL, R> + implements Buildable { private String connector; private SelectDSL selectDSL; private boolean isDistinct; private List selectList; - private SqlTable table; - private Map tableAliases = new HashMap<>(); - protected QueryExpressionWhereBuilder whereBuilder; + private QueryExpressionWhereBuilder whereBuilder = new QueryExpressionWhereBuilder(); private GroupByModel groupByModel; - private List joinSpecificationBuilders = new ArrayList<>(); QueryExpressionDSL(FromGatherer fromGatherer) { + super(fromGatherer.table); connector = fromGatherer.connector; selectList = Arrays.asList(fromGatherer.selectList); isDistinct = fromGatherer.isDistinct; selectDSL = Objects.requireNonNull(fromGatherer.selectDSL); - table = Objects.requireNonNull(fromGatherer.table); } QueryExpressionDSL(FromGatherer fromGatherer, String tableAlias) { this(fromGatherer); - tableAliases.put(table, tableAlias); + tableAliases.put(fromGatherer.table, tableAlias); } public QueryExpressionWhereBuilder where() { - whereBuilder = new QueryExpressionWhereBuilder(); - return whereBuilder; - } - - public QueryExpressionWhereBuilder where(BindableColumn column, VisitableCondition condition) { - whereBuilder = new QueryExpressionWhereBuilder(column, condition); return whereBuilder; } public QueryExpressionWhereBuilder where(BindableColumn column, VisitableCondition condition, SqlCriterion...subCriteria) { - whereBuilder = new QueryExpressionWhereBuilder(column, condition, subCriteria); + whereBuilder.and(column, condition, subCriteria); return whereBuilder; } - + @Override public R build() { return selectDSL.build(); @@ -94,18 +79,6 @@ public JoinSpecificationStarter join(SqlTable joinTable, String tableAlias) { return join(joinTable); } - public QueryExpressionDSL join(SqlTable joinTable, OnJoinCriterion joinCriterion, - AndJoinCriterion...joinCriteria) { - addJoinSpecificationBuilder(joinTable, joinCriterion, JoinType.INNER, joinCriteria); - return this; - } - - public QueryExpressionDSL join(SqlTable joinTable, String tableAlias, OnJoinCriterion joinCriterion, - AndJoinCriterion...joinCriteria) { - tableAliases.put(joinTable, tableAlias); - return join(joinTable, joinCriterion, joinCriteria); - } - public JoinSpecificationStarter leftJoin(SqlTable joinTable) { return new JoinSpecificationStarter(joinTable, JoinType.LEFT); } @@ -115,18 +88,6 @@ public JoinSpecificationStarter leftJoin(SqlTable joinTable, String tableAlias) return leftJoin(joinTable); } - public QueryExpressionDSL leftJoin(SqlTable joinTable, OnJoinCriterion joinCriterion, - AndJoinCriterion...joinCriteria) { - addJoinSpecificationBuilder(joinTable, joinCriterion, JoinType.LEFT, joinCriteria); - return this; - } - - public QueryExpressionDSL leftJoin(SqlTable joinTable, String tableAlias, OnJoinCriterion joinCriterion, - AndJoinCriterion...joinCriteria) { - tableAliases.put(joinTable, tableAlias); - return leftJoin(joinTable, joinCriterion, joinCriteria); - } - public JoinSpecificationStarter rightJoin(SqlTable joinTable) { return new JoinSpecificationStarter(joinTable, JoinType.RIGHT); } @@ -136,18 +97,6 @@ public JoinSpecificationStarter rightJoin(SqlTable joinTable, String tableAlias) return rightJoin(joinTable); } - public QueryExpressionDSL rightJoin(SqlTable joinTable, OnJoinCriterion joinCriterion, - AndJoinCriterion...joinCriteria) { - addJoinSpecificationBuilder(joinTable, joinCriterion, JoinType.RIGHT, joinCriteria); - return this; - } - - public QueryExpressionDSL rightJoin(SqlTable joinTable, String tableAlias, OnJoinCriterion joinCriterion, - AndJoinCriterion...joinCriteria) { - tableAliases.put(joinTable, tableAlias); - return rightJoin(joinTable, joinCriterion, joinCriteria); - } - public JoinSpecificationStarter fullJoin(SqlTable joinTable) { return new JoinSpecificationStarter(joinTable, JoinType.FULL); } @@ -157,27 +106,6 @@ public JoinSpecificationStarter fullJoin(SqlTable joinTable, String tableAlias) return fullJoin(joinTable); } - public QueryExpressionDSL fullJoin(SqlTable joinTable, OnJoinCriterion joinCriterion, - AndJoinCriterion...joinCriteria) { - addJoinSpecificationBuilder(joinTable, joinCriterion, JoinType.FULL, joinCriteria); - return this; - } - - public QueryExpressionDSL fullJoin(SqlTable joinTable, String tableAlias, OnJoinCriterion joinCriterion, - AndJoinCriterion...joinCriteria) { - tableAliases.put(joinTable, tableAlias); - return fullJoin(joinTable, joinCriterion, joinCriteria); - } - - private void addJoinSpecificationBuilder(SqlTable joinTable, OnJoinCriterion joinCriterion, JoinType joinType, - AndJoinCriterion...joinCriteria) { - joinSpecificationBuilders.add(new JoinSpecification.Builder() - .withJoinTable(joinTable) - .withJoinType(joinType) - .withJoinCriterion(joinCriterion) - .withJoinCriteria(Arrays.asList(joinCriteria))); - } - public GroupByFinisher groupBy(BasicColumn...columns) { groupByModel = GroupByModel.of(columns); return new GroupByFinisher(); @@ -199,21 +127,15 @@ public UnionBuilder unionAll() { protected QueryExpressionModel buildModel() { return QueryExpressionModel.withSelectList(selectList) .withConnector(connector) - .withTable(table) + .withTable(table()) .isDistinct(isDistinct) .withTableAliases(tableAliases) - .withWhereModel(whereBuilder == null ? null : whereBuilder.buildWhereModel()) - .withJoinModel(joinSpecificationBuilders.isEmpty() ? null : buildJoinModel()) + .withWhereModel(whereBuilder.buildWhereModel()) + .withJoinModel(buildJoinModel().orElse(null)) .withGroupByModel(groupByModel) .build(); } - private JoinModel buildJoinModel() { - return JoinModel.of(joinSpecificationBuilders.stream() - .map(JoinSpecification.Builder::build) - .collect(Collectors.toList())); - } - public SelectDSL.LimitFinisher limit(long limit) { return selectDSL.limit(limit); } @@ -226,6 +148,11 @@ public SelectDSL.FetchFirstFinisher fetchFirst(long fetchFirstRows) { return selectDSL.fetchFirst(fetchFirstRows); } + @Override + protected QueryExpressionDSL getThis() { + return this; + } + public static class FromGatherer { private String connector; private BasicColumn[] selectList; @@ -287,15 +214,6 @@ public class QueryExpressionWhereBuilder extends AbstractWhereDSL QueryExpressionWhereBuilder() { } - private QueryExpressionWhereBuilder(BindableColumn column, VisitableCondition condition) { - super(column, condition); - } - - private QueryExpressionWhereBuilder(BindableColumn column, VisitableCondition condition, - SqlCriterion...subCriteria) { - super(column, condition, subCriteria); - } - public UnionBuilder union() { return QueryExpressionDSL.this.union(); } @@ -348,9 +266,9 @@ public JoinSpecificationFinisher on(BasicColumn joinColumn, JoinCondition joinCo return new JoinSpecificationFinisher(joinTable, joinColumn, joinCondition, joinType); } - public JoinSpecificationFinisher on(BasicColumn joinColumn, JoinCondition joinCondition, - AndJoinCriterion...joinCriteria) { - return new JoinSpecificationFinisher(joinTable, joinColumn, joinCondition, joinType, joinCriteria); + public JoinSpecificationFinisher on(BasicColumn joinColumn, JoinCondition onJoinCondition, + JoinCriterion...andJoinCriteria) { + return new JoinSpecificationFinisher(joinTable, joinColumn, onJoinCondition, joinType, andJoinCriteria); } } @@ -359,7 +277,8 @@ public class JoinSpecificationFinisher implements Buildable { public JoinSpecificationFinisher(SqlTable table, BasicColumn joinColumn, JoinCondition joinCondition, JoinType joinType) { - JoinCriterion joinCriterion = new OnJoinCriterion.Builder() + JoinCriterion joinCriterion = new JoinCriterion.Builder() + .withConnector("on") //$NON-NLS-1$ .withJoinColumn(joinColumn) .withJoinCondition(joinCondition) .build(); @@ -368,22 +287,23 @@ public JoinSpecificationFinisher(SqlTable table, BasicColumn joinColumn, .withJoinType(joinType) .withJoinCriterion(joinCriterion); - joinSpecificationBuilders.add(joinSpecificationBuilder); + addJoinSpecificationBuilder(joinSpecificationBuilder); } public JoinSpecificationFinisher(SqlTable table, BasicColumn joinColumn, - JoinCondition joinCondition, JoinType joinType, AndJoinCriterion...joinCriteria) { - JoinCriterion joinCriterion = new OnJoinCriterion.Builder() + JoinCondition joinCondition, JoinType joinType, JoinCriterion...andJoinCriteria) { + JoinCriterion onJoinCriterion = new JoinCriterion.Builder() + .withConnector("on") //$NON-NLS-1$ .withJoinColumn(joinColumn) .withJoinCondition(joinCondition) .build(); joinSpecificationBuilder = JoinSpecification.withJoinTable(table) .withJoinType(joinType) - .withJoinCriterion(joinCriterion) - .withJoinCriteria(Arrays.asList(joinCriteria)); + .withJoinCriterion(onJoinCriterion) + .withJoinCriteria(Arrays.asList(andJoinCriteria)); - joinSpecificationBuilders.add(joinSpecificationBuilder); + addJoinSpecificationBuilder(joinSpecificationBuilder); } @Override @@ -395,17 +315,14 @@ public QueryExpressionWhereBuilder where() { return QueryExpressionDSL.this.where(); } - public QueryExpressionWhereBuilder where(BindableColumn column, VisitableCondition condition) { - return QueryExpressionDSL.this.where(column, condition); - } - public QueryExpressionWhereBuilder where(BindableColumn column, VisitableCondition condition, SqlCriterion...subCriteria) { return QueryExpressionDSL.this.where(column, condition, subCriteria); } public JoinSpecificationFinisher and(BasicColumn joinColumn, JoinCondition joinCondition) { - JoinCriterion joinCriterion = new AndJoinCriterion.Builder() + JoinCriterion joinCriterion = new JoinCriterion.Builder() + .withConnector("and") //$NON-NLS-1$ .withJoinColumn(joinColumn) .withJoinCondition(joinCondition) .build(); 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 5200dfdd1..3561b5b51 100644 --- a/src/main/java/org/mybatis/dynamic/sql/select/QueryExpressionModel.java +++ b/src/main/java/org/mybatis/dynamic/sql/select/QueryExpressionModel.java @@ -121,6 +121,11 @@ public Builder isDistinct(boolean isDistinct) { return this; } + public Builder withSelectColumn(BasicColumn selectColumn) { + this.selectList.add(selectColumn); + return this; + } + public Builder withSelectList(List selectList) { this.selectList.addAll(selectList); return this; diff --git a/src/main/java/org/mybatis/dynamic/sql/select/SelectDSL.java b/src/main/java/org/mybatis/dynamic/sql/select/SelectDSL.java index 45b34073e..9a549ead3 100644 --- a/src/main/java/org/mybatis/dynamic/sql/select/SelectDSL.java +++ b/src/main/java/org/mybatis/dynamic/sql/select/SelectDSL.java @@ -29,11 +29,11 @@ import org.mybatis.dynamic.sql.util.mybatis3.MyBatis3Utils; /** - * Implements a standard SQL dialect for building model classes. + * Implements a SQL DSL for building select statements. * * @author Jeff Butler * - * @param the type of model produced by this builder + * @param the type of model produced by this builder, typically SelectModel */ public class SelectDSL implements Buildable { @@ -79,7 +79,7 @@ public static QueryExpressionDSL.FromGatherer selectDistinct(Function the return type from a MyBatis mapper - typically a List or a single record - * @param mapperMethod MyBatis3 Mapper Method to perfomr the select + * @param mapperMethod MyBatis3 mapper method that performs the select * @param selectList the column list to select * @return the partially created query */ @@ -95,7 +95,7 @@ public static QueryExpressionDSL.FromGatherer> * @deprecated in favor of various select methods in {@link MyBatis3Utils}. * This method will be removed without direct replacement in a future version * @param the return type from a MyBatis mapper - typically a List or a single record - * @param mapperMethod MyBatis3 Mapper Method to perfomr the select + * @param mapperMethod MyBatis3 mapper method that performs the select * @param selectList the column list to select * @return the partially created query */ diff --git a/src/main/java/org/mybatis/dynamic/sql/select/SelectDSLCompleter.java b/src/main/java/org/mybatis/dynamic/sql/select/SelectDSLCompleter.java index 427ebb351..db20d0f73 100644 --- a/src/main/java/org/mybatis/dynamic/sql/select/SelectDSLCompleter.java +++ b/src/main/java/org/mybatis/dynamic/sql/select/SelectDSLCompleter.java @@ -22,43 +22,52 @@ import org.mybatis.dynamic.sql.util.mybatis3.MyBatis3Utils; /** - * Represents a function that can be used to create a general count method in the style - * of MyBatis Generator. When using this function, you can create a method that does not require a user to - * call the build() and render() methods - making client code look a bit cleaner. + * Represents a function that can be used to create a general select method. When using this function, + * you can create a method that does not require a user to call the build() and render() methods - making + * client code look a bit cleaner. * - *

This function is intended to by used in conjunction with utility methods like the select and count - * methods in {@link MyBatis3Utils} + *

This function is intended to by used in conjunction with utility methods like the select methods in + * {@link MyBatis3Utils}. * *

For example, you can create mapper interface methods like this: * *

  * @SelectProvider(type=SqlProviderAdapter.class, method="select")
- * long count(SelectStatementProvider selectStatement);
+ * List<PersonRecord> selectMany(SelectStatementProvider selectStatement);
  *   
- * default long count(SelectDSLCompleter completer) {
-        return MyBatis3Utils.count(this::count, person, completer);
+ * BasicColumn[] selectList =
+ *     BasicColumn.columnList(id, firstName, lastName, birthDate, employed, occupation, addressId);
+ * 
+ * default List<PersonRecord> select(SelectDSLCompleter completer) {
+ *      return MyBatis3Utils.select(this::selectMany, selectList, person, completer);
  * }
  * 
* *

And then call the simplified default method like this: * *

- * long rows = mapper.count(c ->
+ * List<PersonRecord> rows = mapper.select(c ->
  *         c.where(occupation, isNull()));
  * 
* - *

You can implement a "count all" with the following code: + *

You can implement a "select all" with the following code: * *

- * long rows = mapper.count(c -> c);
+ * List<PersonRecord> rows = mapper.select(c -> c);
  * 
* *

Or * *

- * long rows = mapper.count(SelectDSLCompleter.allRows());
+ * List<PersonRecord> rows = mapper.select(SelectDSLCompleter.allRows());
+ * 
+ * + *

There is also a utility method to support selecting all rows in a specified order: + * + *

+ * List<PersonRecord> rows = mapper.select(SelectDSLCompleter.allRowsOrderedBy(lastName, firstName));
  * 
- * + * * @author Jeff Butler */ @FunctionalInterface diff --git a/src/main/java/org/mybatis/dynamic/sql/select/SelectModel.java b/src/main/java/org/mybatis/dynamic/sql/select/SelectModel.java index ffb116212..5e4c4c009 100644 --- a/src/main/java/org/mybatis/dynamic/sql/select/SelectModel.java +++ b/src/main/java/org/mybatis/dynamic/sql/select/SelectModel.java @@ -22,6 +22,7 @@ import java.util.function.Function; import java.util.stream.Stream; +import org.jetbrains.annotations.NotNull; import org.mybatis.dynamic.sql.render.RenderingStrategy; import org.mybatis.dynamic.sql.select.render.SelectRenderer; import org.mybatis.dynamic.sql.select.render.SelectStatementProvider; @@ -48,7 +49,8 @@ public Optional orderByModel() { public Optional pagingModel() { return Optional.ofNullable(pagingModel); } - + + @NotNull public SelectStatementProvider render(RenderingStrategy renderingStrategy) { return SelectRenderer.withSelectModel(this) .withRenderingStrategy(renderingStrategy) @@ -65,6 +67,11 @@ public static class Builder { private OrderByModel orderByModel; private PagingModel pagingModel; + public Builder withQueryExpression(QueryExpressionModel queryExpression) { + this.queryExpressions.add(queryExpression); + return this; + } + public Builder withQueryExpressions(List queryExpressions) { this.queryExpressions.addAll(queryExpressions); return this; 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 ca19176d8..33983d691 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 @@ -19,17 +19,21 @@ import org.mybatis.dynamic.sql.BasicColumn; -public abstract class JoinCriterion { +public class JoinCriterion { + private String connector; private BasicColumn leftColumn; private JoinCondition joinCondition; - protected JoinCriterion(AbstractBuilder builder) { + private JoinCriterion(Builder builder) { + connector = Objects.requireNonNull(builder.connector); leftColumn = Objects.requireNonNull(builder.joinColumn); joinCondition = Objects.requireNonNull(builder.joinCondition); } - public abstract String connector(); + public String connector() { + return connector; + } public BasicColumn leftColumn() { return leftColumn; @@ -43,20 +47,28 @@ public String operator() { return joinCondition.operator(); } - public abstract static class AbstractBuilder> { + public static class Builder { + private String connector; private BasicColumn joinColumn; private JoinCondition joinCondition; - public T withJoinColumn(BasicColumn joinColumn) { + public Builder withConnector(String connector) { + this.connector = connector; + return this; + } + + public Builder withJoinColumn(BasicColumn joinColumn) { this.joinColumn = joinColumn; - return getThis(); + return this; } - public T withJoinCondition(JoinCondition joinCondition) { + public Builder withJoinCondition(JoinCondition joinCondition) { this.joinCondition = joinCondition; - return getThis(); + return this; } - protected abstract T getThis(); + public JoinCriterion build() { + return new JoinCriterion(this); + } } } diff --git a/src/main/java/org/mybatis/dynamic/sql/update/UpdateDSL.java b/src/main/java/org/mybatis/dynamic/sql/update/UpdateDSL.java index 3746d143e..134177d77 100644 --- a/src/main/java/org/mybatis/dynamic/sql/update/UpdateDSL.java +++ b/src/main/java/org/mybatis/dynamic/sql/update/UpdateDSL.java @@ -46,7 +46,7 @@ public class UpdateDSL implements Buildable { private Function adapterFunction; private List columnMappings = new ArrayList<>(); private SqlTable table; - protected UpdateWhereBuilder whereBuilder; + private UpdateWhereBuilder whereBuilder = new UpdateWhereBuilder(); private UpdateDSL(SqlTable table, Function adapterFunction) { this.table = Objects.requireNonNull(table); @@ -58,21 +58,15 @@ public SetClauseFinisher set(SqlColumn column) { } public UpdateWhereBuilder where() { - whereBuilder = new UpdateWhereBuilder(); - return whereBuilder; - } - - public UpdateWhereBuilder where(BindableColumn column, VisitableCondition condition) { - whereBuilder = new UpdateWhereBuilder(column, condition); return whereBuilder; } public UpdateWhereBuilder where(BindableColumn column, VisitableCondition condition, SqlCriterion...subCriteria) { - whereBuilder = new UpdateWhereBuilder(column, condition, subCriteria); + whereBuilder.and(column, condition, subCriteria); return whereBuilder; } - + /** * WARNING! Calling this method could result in an update statement that updates * all rows in a table. @@ -83,7 +77,7 @@ public UpdateWhereBuilder where(BindableColumn column, VisitableCondition public R build() { UpdateModel updateModel = UpdateModel.withTable(table) .withColumnMappings(columnMappings) - .withWhereModel(whereBuilder == null ? null : whereBuilder.buildWhereModel()) + .withWhereModel(whereBuilder.buildWhereModel()) .build(); return adapterFunction.apply(updateModel); } @@ -172,15 +166,6 @@ public UpdateWhereBuilder() { super(); } - public UpdateWhereBuilder(BindableColumn column, VisitableCondition condition) { - super(column, condition); - } - - public UpdateWhereBuilder(BindableColumn column, VisitableCondition condition, - SqlCriterion...subCriteria) { - super(column, condition, subCriteria); - } - @Override public R build() { return UpdateDSL.this.build(); diff --git a/src/main/java/org/mybatis/dynamic/sql/update/UpdateDSLCompleter.java b/src/main/java/org/mybatis/dynamic/sql/update/UpdateDSLCompleter.java index 8c5996d0c..6ed8857ef 100644 --- a/src/main/java/org/mybatis/dynamic/sql/update/UpdateDSLCompleter.java +++ b/src/main/java/org/mybatis/dynamic/sql/update/UpdateDSLCompleter.java @@ -23,9 +23,9 @@ import org.mybatis.dynamic.sql.util.mybatis3.MyBatis3Utils; /** - * Represents a function that can be used to create a general update method in the style - * of MyBatis Generator. When using this function, you can create a method that does not require a user to - * call the build() and render() methods - making client code look a bit cleaner. + * Represents a function that can be used to create a general update method. When using this function, + * you can create a method that does not require a user to call the build() and render() methods - making + * client code look a bit cleaner. * *

This function is intended to be used in conjunction in the utility method like * {@link MyBatis3Utils#update(ToIntFunction, SqlTable, UpdateDSLCompleter)} diff --git a/src/main/java/org/mybatis/dynamic/sql/update/UpdateModel.java b/src/main/java/org/mybatis/dynamic/sql/update/UpdateModel.java index 025a2aef7..d006da8ca 100644 --- a/src/main/java/org/mybatis/dynamic/sql/update/UpdateModel.java +++ b/src/main/java/org/mybatis/dynamic/sql/update/UpdateModel.java @@ -22,6 +22,7 @@ import java.util.function.Function; import java.util.stream.Stream; +import org.jetbrains.annotations.NotNull; import org.mybatis.dynamic.sql.SqlTable; import org.mybatis.dynamic.sql.render.RenderingStrategy; import org.mybatis.dynamic.sql.update.render.UpdateRenderer; @@ -51,7 +52,8 @@ public Optional whereModel() { public Stream mapColumnMappings(Function mapper) { return columnMappings.stream().map(mapper); } - + + @NotNull public UpdateStatementProvider render(RenderingStrategy renderingStrategy) { return UpdateRenderer.withUpdateModel(this) .withRenderingStrategy(renderingStrategy) diff --git a/src/main/java/org/mybatis/dynamic/sql/util/mybatis3/MyBatis3Utils.java b/src/main/java/org/mybatis/dynamic/sql/util/mybatis3/MyBatis3Utils.java index 0c5eb9e54..1fae9cd6e 100644 --- a/src/main/java/org/mybatis/dynamic/sql/util/mybatis3/MyBatis3Utils.java +++ b/src/main/java/org/mybatis/dynamic/sql/util/mybatis3/MyBatis3Utils.java @@ -17,15 +17,17 @@ import java.util.Collection; import java.util.List; +import java.util.Optional; import java.util.function.Function; import java.util.function.ToIntFunction; import java.util.function.ToLongFunction; import java.util.function.UnaryOperator; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; import org.mybatis.dynamic.sql.BasicColumn; import org.mybatis.dynamic.sql.SqlBuilder; import org.mybatis.dynamic.sql.SqlTable; -import org.mybatis.dynamic.sql.delete.DeleteDSL; import org.mybatis.dynamic.sql.delete.DeleteDSLCompleter; import org.mybatis.dynamic.sql.delete.render.DeleteStatementProvider; import org.mybatis.dynamic.sql.insert.InsertDSL; @@ -33,12 +35,12 @@ import org.mybatis.dynamic.sql.insert.render.InsertStatementProvider; import org.mybatis.dynamic.sql.insert.render.MultiRowInsertStatementProvider; import org.mybatis.dynamic.sql.render.RenderingStrategies; +import org.mybatis.dynamic.sql.select.CountDSL; +import org.mybatis.dynamic.sql.select.CountDSLCompleter; import org.mybatis.dynamic.sql.select.QueryExpressionDSL; -import org.mybatis.dynamic.sql.select.SelectDSL; import org.mybatis.dynamic.sql.select.SelectDSLCompleter; import org.mybatis.dynamic.sql.select.SelectModel; import org.mybatis.dynamic.sql.select.render.SelectStatementProvider; -import org.mybatis.dynamic.sql.update.UpdateDSL; import org.mybatis.dynamic.sql.update.UpdateDSLCompleter; import org.mybatis.dynamic.sql.update.render.UpdateStatementProvider; @@ -52,19 +54,19 @@ public class MyBatis3Utils { private MyBatis3Utils() {} public static long count(ToLongFunction mapper, - SqlTable table, SelectDSLCompleter completer) { - return count(mapper, SelectDSL.select(SqlBuilder.count()).from(table), completer); + SqlTable table, CountDSLCompleter completer) { + return count(mapper, SqlBuilder.countFrom(table), completer); } public static long count(ToLongFunction mapper, - QueryExpressionDSL start, SelectDSLCompleter completer) { + CountDSL start, CountDSLCompleter completer) { return mapper.applyAsLong(completer.apply(start).build().render(RenderingStrategies.MYBATIS3)); } public static int deleteFrom(ToIntFunction mapper, SqlTable table, DeleteDSLCompleter completer) { return mapper.applyAsInt( - completer.apply(DeleteDSL.deleteFrom(table)) + completer.apply(SqlBuilder.deleteFrom(table)) .build() .render(RenderingStrategies.MYBATIS3)); } @@ -72,18 +74,18 @@ public static int deleteFrom(ToIntFunction mapper, public static int insert(ToIntFunction> mapper, R record, SqlTable table, UnaryOperator> completer) { return mapper.applyAsInt(completer.apply( - InsertDSL.insert(record).into(table)).build().render(RenderingStrategies.MYBATIS3)); + SqlBuilder.insert(record).into(table)).build().render(RenderingStrategies.MYBATIS3)); } public static int insertMultiple(ToIntFunction> mapper, Collection records, SqlTable table, UnaryOperator> completer) { return mapper.applyAsInt(completer.apply( - MultiRowInsertDSL.insert(records).into(table)).build().render(RenderingStrategies.MYBATIS3)); + SqlBuilder.insertMultiple(records).into(table)).build().render(RenderingStrategies.MYBATIS3)); } - + public static List selectDistinct(Function> mapper, BasicColumn[] selectList, SqlTable table, SelectDSLCompleter completer) { - return selectDistinct(mapper, SelectDSL.selectDistinct(selectList).from(table), completer); + return selectDistinct(mapper, SqlBuilder.selectDistinct(selectList).from(table), completer); } public static List selectDistinct(Function> mapper, @@ -93,7 +95,7 @@ public static List selectDistinct(Function List selectList(Function> mapper, BasicColumn[] selectList, SqlTable table, SelectDSLCompleter completer) { - return selectList(mapper, SelectDSL.select(selectList).from(table), completer); + return selectList(mapper, SqlBuilder.select(selectList).from(table), completer); } public static List selectList(Function> mapper, @@ -101,21 +103,36 @@ public static List selectList(Function> return mapper.apply(completer.apply(start).build().render(RenderingStrategies.MYBATIS3)); } + @Nullable public static R selectOne(Function mapper, BasicColumn[] selectList, SqlTable table, SelectDSLCompleter completer) { - return selectOne(mapper, SelectDSL.select(selectList).from(table), completer); + return selectOne(mapper, SqlBuilder.select(selectList).from(table), completer); } + @Nullable public static R selectOne(Function mapper, QueryExpressionDSL start, SelectDSLCompleter completer) { return mapper.apply(completer.apply(start).build().render(RenderingStrategies.MYBATIS3)); } + @NotNull + public static Optional selectOptional(Function> mapper, + BasicColumn[] selectList, SqlTable table, SelectDSLCompleter completer) { + return selectOptional(mapper, SqlBuilder.select(selectList).from(table), completer); + } + + @NotNull + public static Optional selectOptional(Function> mapper, + QueryExpressionDSL start, + SelectDSLCompleter completer) { + return mapper.apply(completer.apply(start).build().render(RenderingStrategies.MYBATIS3)); + } + public static int update(ToIntFunction mapper, SqlTable table, UpdateDSLCompleter completer) { return mapper.applyAsInt( - completer.apply(UpdateDSL.update(table)) + completer.apply(SqlBuilder.update(table)) .build() .render(RenderingStrategies.MYBATIS3)); } diff --git a/src/main/java/org/mybatis/dynamic/sql/where/AbstractWhereDSL.java b/src/main/java/org/mybatis/dynamic/sql/where/AbstractWhereDSL.java index 725a97510..0f5888164 100644 --- a/src/main/java/org/mybatis/dynamic/sql/where/AbstractWhereDSL.java +++ b/src/main/java/org/mybatis/dynamic/sql/where/AbstractWhereDSL.java @@ -52,6 +52,11 @@ public T and(BindableColumn column, VisitableCondition condition) { } public T and(BindableColumn column, VisitableCondition condition, SqlCriterion...subCriteria) { + addCriterion("and", column, condition, Arrays.asList(subCriteria)); //$NON-NLS-1$ + return getThis(); + } + + public T and(BindableColumn column, VisitableCondition condition, List> subCriteria) { addCriterion("and", column, condition, subCriteria); //$NON-NLS-1$ return getThis(); } @@ -62,6 +67,11 @@ public T or(BindableColumn column, VisitableCondition condition) { } public T or(BindableColumn column, VisitableCondition condition, SqlCriterion...subCriteria) { + addCriterion("or", column, condition, Arrays.asList(subCriteria)); //$NON-NLS-1$ + return getThis(); + } + + public T or(BindableColumn column, VisitableCondition condition, List> subCriteria) { addCriterion("or", column, condition, subCriteria); //$NON-NLS-1$ return getThis(); } @@ -75,11 +85,11 @@ private void addCriterion(String connector, BindableColumn column, Visita } private void addCriterion(String connector, BindableColumn column, VisitableCondition condition, - SqlCriterion...subCriteria) { + List> subCriteria) { SqlCriterion criterion = SqlCriterion.withColumn(column) .withConnector(connector) .withCondition(condition) - .withSubCriteria(Arrays.asList(subCriteria)) + .withSubCriteria(subCriteria) .build(); criteria.add(criterion); } diff --git a/src/main/kotlin/org/mybatis/dynamic/sql/util/kotlin/AbstractQueryExpressionDSLExtensions.kt b/src/main/kotlin/org/mybatis/dynamic/sql/util/kotlin/AbstractQueryExpressionDSLExtensions.kt new file mode 100644 index 000000000..3b1a987a7 --- /dev/null +++ b/src/main/kotlin/org/mybatis/dynamic/sql/util/kotlin/AbstractQueryExpressionDSLExtensions.kt @@ -0,0 +1,77 @@ +/** + * Copyright 2016-2019 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.util.kotlin + +import org.mybatis.dynamic.sql.SqlTable +import org.mybatis.dynamic.sql.select.AbstractQueryExpressionDSL +import org.mybatis.dynamic.sql.select.SelectModel + +fun > AbstractQueryExpressionDSL + .join(table: SqlTable, collect: JoinReceiver): T { + val collector = JoinCollector() + collect(collector) + return join(table, collector.onJoinCriterion, collector.andJoinCriteria) +} + +fun > AbstractQueryExpressionDSL + .join(table: SqlTable, alias: String, collect: JoinReceiver): T { + val collector = JoinCollector() + collect(collector) + return join(table, alias, collector.onJoinCriterion, collector.andJoinCriteria) +} + +fun > AbstractQueryExpressionDSL + .fullJoin(table: SqlTable, collect: JoinReceiver): T { + val collector = JoinCollector() + collect(collector) + return fullJoin(table, collector.onJoinCriterion, collector.andJoinCriteria) +} + +fun > AbstractQueryExpressionDSL + .fullJoin(table: SqlTable, alias: String, collect: JoinReceiver): T { + val collector = JoinCollector() + collect(collector) + return fullJoin(table, alias, collector.onJoinCriterion, collector.andJoinCriteria) +} + +fun > AbstractQueryExpressionDSL + .leftJoin(table: SqlTable, collect: JoinReceiver): T { + val collector = JoinCollector() + collect(collector) + return leftJoin(table, collector.onJoinCriterion, collector.andJoinCriteria) +} + +fun > AbstractQueryExpressionDSL + .leftJoin(table: SqlTable, alias: String, collect: JoinReceiver): T { + val collector = JoinCollector() + collect(collector) + return leftJoin(table, alias, collector.onJoinCriterion, collector.andJoinCriteria) +} + +fun > AbstractQueryExpressionDSL + .rightJoin(table: SqlTable, collect: JoinReceiver): T { + val collector = JoinCollector() + collect(collector) + return rightJoin(table, collector.onJoinCriterion, collector.andJoinCriteria) +} + +fun > AbstractQueryExpressionDSL + .rightJoin(table: SqlTable, alias: String, collect: JoinReceiver): T { + val collector = JoinCollector() + collect(collector) + return rightJoin(table, alias, collector.onJoinCriterion, collector.andJoinCriteria) +} + diff --git a/src/main/kotlin/org/mybatis/dynamic/sql/util/kotlin/AbstractWhereDSLExtensions.kt b/src/main/kotlin/org/mybatis/dynamic/sql/util/kotlin/AbstractWhereDSLExtensions.kt new file mode 100644 index 000000000..0211e13d6 --- /dev/null +++ b/src/main/kotlin/org/mybatis/dynamic/sql/util/kotlin/AbstractWhereDSLExtensions.kt @@ -0,0 +1,34 @@ +/** + * Copyright 2016-2019 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.util.kotlin + +import org.mybatis.dynamic.sql.BindableColumn +import org.mybatis.dynamic.sql.VisitableCondition +import org.mybatis.dynamic.sql.where.AbstractWhereDSL + +fun > AbstractWhereDSL.and(column: BindableColumn, condition: VisitableCondition, + collect: CriteriaReceiver): M { + val collector = CriteriaCollector() + collect(collector) + return and(column, condition, collector.criteria) +} + +fun > AbstractWhereDSL.or(column: BindableColumn, condition: VisitableCondition, + collect: CriteriaReceiver): M { + val collector = CriteriaCollector() + collect(collector) + return or(column, condition, collector.criteria) +} diff --git a/src/main/kotlin/org/mybatis/dynamic/sql/util/kotlin/CountDSLExtensions.kt b/src/main/kotlin/org/mybatis/dynamic/sql/util/kotlin/CountDSLExtensions.kt new file mode 100644 index 000000000..f22d4954d --- /dev/null +++ b/src/main/kotlin/org/mybatis/dynamic/sql/util/kotlin/CountDSLExtensions.kt @@ -0,0 +1,56 @@ +/** + * Copyright 2016-2019 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.util.kotlin + +import org.mybatis.dynamic.sql.BindableColumn +import org.mybatis.dynamic.sql.VisitableCondition +import org.mybatis.dynamic.sql.delete.DeleteDSL +import org.mybatis.dynamic.sql.delete.DeleteModel +import org.mybatis.dynamic.sql.select.CountDSL +import org.mybatis.dynamic.sql.select.SelectModel +import org.mybatis.dynamic.sql.util.Buildable + +typealias CountCompleter = CountDSL.() -> Buildable + +fun CountDSL.where(column: BindableColumn, condition: VisitableCondition, + collect: CriteriaReceiver) = + apply { + where().and(column, condition, collect) + } + +fun CountDSL.and(column: BindableColumn, condition: VisitableCondition) = + apply { + where().and(column, condition) + } + +fun CountDSL.and(column: BindableColumn, condition: VisitableCondition, + collect: CriteriaReceiver) = + apply { + where().and(column, condition, collect) + } + +fun CountDSL.or(column: BindableColumn, condition: VisitableCondition) = + apply { + where().or(column, condition) + } + +fun CountDSL.or(column: BindableColumn, condition: VisitableCondition, + collect: CriteriaReceiver) = + apply { + where().or(column, condition, collect) + } + +fun CountDSL.allRows() = this as Buildable diff --git a/src/main/kotlin/org/mybatis/dynamic/sql/util/kotlin/CriteriaCollector.kt b/src/main/kotlin/org/mybatis/dynamic/sql/util/kotlin/CriteriaCollector.kt new file mode 100644 index 000000000..85b468382 --- /dev/null +++ b/src/main/kotlin/org/mybatis/dynamic/sql/util/kotlin/CriteriaCollector.kt @@ -0,0 +1,68 @@ +/** + * Copyright 2016-2019 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.util.kotlin + +import org.mybatis.dynamic.sql.BindableColumn +import org.mybatis.dynamic.sql.SqlCriterion +import org.mybatis.dynamic.sql.VisitableCondition + +typealias CriteriaReceiver = CriteriaCollector.() -> CriteriaCollector + +class CriteriaCollector { + val criteria = mutableListOf>() + + fun and(column: BindableColumn, condition: VisitableCondition) = + apply { + criteria.add(SqlCriterion.withColumn(column) + .withCondition(condition) + .withConnector("and") + .build()) + } + + fun and(column: BindableColumn, condition: VisitableCondition, + collect: CriteriaCollector.() -> CriteriaCollector) = + apply { + val collector = CriteriaCollector() + collect(collector) + val criterion: SqlCriterion = SqlCriterion.withColumn(column) + .withCondition(condition) + .withSubCriteria(collector.criteria) + .withConnector("and") + .build() + criteria.add(criterion) + } + + fun or(column: BindableColumn, condition: VisitableCondition) = + apply { + criteria.add(SqlCriterion.withColumn(column) + .withCondition(condition) + .withConnector("or") + .build()) + } + + fun or(column: BindableColumn, condition: VisitableCondition, + collect: CriteriaCollector.() -> CriteriaCollector) = + apply { + val collector = CriteriaCollector() + collect(collector) + val criterion: SqlCriterion = SqlCriterion.withColumn(column) + .withCondition(condition) + .withSubCriteria(collector.criteria) + .withConnector("or") + .build() + criteria.add(criterion) + } +} diff --git a/src/main/kotlin/org/mybatis/dynamic/sql/util/kotlin/DeleteDSLExtensions.kt b/src/main/kotlin/org/mybatis/dynamic/sql/util/kotlin/DeleteDSLExtensions.kt new file mode 100644 index 000000000..e894888b2 --- /dev/null +++ b/src/main/kotlin/org/mybatis/dynamic/sql/util/kotlin/DeleteDSLExtensions.kt @@ -0,0 +1,54 @@ +/** + * Copyright 2016-2019 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.util.kotlin + +import org.mybatis.dynamic.sql.BindableColumn +import org.mybatis.dynamic.sql.VisitableCondition +import org.mybatis.dynamic.sql.delete.DeleteDSL +import org.mybatis.dynamic.sql.delete.DeleteModel +import org.mybatis.dynamic.sql.util.Buildable + +typealias DeleteCompleter = DeleteDSL.() -> Buildable + +fun DeleteDSL.where(column: BindableColumn, condition: VisitableCondition, + collect: CriteriaReceiver) = + apply { + where().and(column, condition, collect) + } + +fun DeleteDSL.and(column: BindableColumn, condition: VisitableCondition) = + apply { + where().and(column, condition) + } + +fun DeleteDSL.and(column: BindableColumn, condition: VisitableCondition, + collect: CriteriaReceiver) = + apply { + where().and(column, condition, collect) + } + +fun DeleteDSL.or(column: BindableColumn, condition: VisitableCondition) = + apply { + where().or(column, condition) + } + +fun DeleteDSL.or(column: BindableColumn, condition: VisitableCondition, + collect: CriteriaReceiver) = + apply { + where().or(column, condition, collect) + } + +fun DeleteDSL.allRows() = this as Buildable diff --git a/src/main/kotlin/org/mybatis/dynamic/sql/util/kotlin/FromGathererExtensions.kt b/src/main/kotlin/org/mybatis/dynamic/sql/util/kotlin/FromGathererExtensions.kt new file mode 100644 index 000000000..3791087ac --- /dev/null +++ b/src/main/kotlin/org/mybatis/dynamic/sql/util/kotlin/FromGathererExtensions.kt @@ -0,0 +1,33 @@ +/** + * Copyright 2016-2019 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.util.kotlin + +import org.mybatis.dynamic.sql.SqlTable +import org.mybatis.dynamic.sql.select.QueryExpressionDSL +import org.mybatis.dynamic.sql.select.SelectModel + +// These functions are intended for use in a Join mapper where a join is setup before the remainder +// of the query is completed + +typealias QueryExpressionEnhancer = QueryExpressionDSL.() -> QueryExpressionDSL + +fun QueryExpressionDSL.FromGatherer.fromJoining(table: SqlTable, + enhance: QueryExpressionEnhancer) = + enhance(from(table)) + +fun QueryExpressionDSL.FromGatherer.fromJoining(table: SqlTable, alias: String, + enhance: QueryExpressionEnhancer) = + enhance(from(table, alias)) 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 new file mode 100644 index 000000000..df2b64546 --- /dev/null +++ b/src/main/kotlin/org/mybatis/dynamic/sql/util/kotlin/JoinCollector.kt @@ -0,0 +1,46 @@ +/** + * Copyright 2016-2019 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.util.kotlin + +import org.mybatis.dynamic.sql.BasicColumn +import org.mybatis.dynamic.sql.select.join.JoinCondition +import org.mybatis.dynamic.sql.select.join.JoinCriterion + +typealias JoinReceiver = JoinCollector.() -> JoinCollector + +class JoinCollector { + lateinit var onJoinCriterion: JoinCriterion + val andJoinCriteria = mutableListOf() + + fun on(column: BasicColumn, condition: JoinCondition) = + apply { + onJoinCriterion = JoinCriterion.Builder() + .withConnector("on") + .withJoinColumn(column) + .withJoinCondition(condition) + .build() + } + + fun and(column: BasicColumn, condition: JoinCondition) = + apply { + andJoinCriteria.add(JoinCriterion.Builder() + .withConnector("and") + .withJoinColumn(column) + .withJoinCondition(condition) + .build()) + + } +} diff --git a/src/main/kotlin/org/mybatis/dynamic/sql/util/kotlin/QueryExpressionDSLExtensions.kt b/src/main/kotlin/org/mybatis/dynamic/sql/util/kotlin/QueryExpressionDSLExtensions.kt new file mode 100644 index 000000000..0d0fa0ce0 --- /dev/null +++ b/src/main/kotlin/org/mybatis/dynamic/sql/util/kotlin/QueryExpressionDSLExtensions.kt @@ -0,0 +1,54 @@ +/** + * Copyright 2016-2019 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.util.kotlin + +import org.mybatis.dynamic.sql.BindableColumn +import org.mybatis.dynamic.sql.VisitableCondition +import org.mybatis.dynamic.sql.select.QueryExpressionDSL +import org.mybatis.dynamic.sql.select.SelectModel +import org.mybatis.dynamic.sql.util.Buildable + +typealias SelectCompleter = QueryExpressionDSL.() -> Buildable + +fun QueryExpressionDSL.where(column: BindableColumn, condition: VisitableCondition, + collect: CriteriaReceiver) = + apply { + where().and(column, condition, collect) + } + +fun QueryExpressionDSL.and(column: BindableColumn, condition: VisitableCondition) = + apply { + where().and(column, condition) + } + +fun QueryExpressionDSL.and(column: BindableColumn, condition: VisitableCondition, + collect: CriteriaReceiver) = + apply { + where().and(column, condition, collect) + } + +fun QueryExpressionDSL.or(column: BindableColumn, condition: VisitableCondition) = + apply { + where().or(column, condition) + } + +fun QueryExpressionDSL.or(column: BindableColumn, condition: VisitableCondition, + collect: CriteriaReceiver) = + apply { + where().or(column, condition, collect) + } + +fun QueryExpressionDSL.allRows() = this as Buildable diff --git a/src/main/kotlin/org/mybatis/dynamic/sql/util/kotlin/UpdateDSLExtensions.kt b/src/main/kotlin/org/mybatis/dynamic/sql/util/kotlin/UpdateDSLExtensions.kt new file mode 100644 index 000000000..c05023405 --- /dev/null +++ b/src/main/kotlin/org/mybatis/dynamic/sql/util/kotlin/UpdateDSLExtensions.kt @@ -0,0 +1,52 @@ +/** + * Copyright 2016-2019 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.util.kotlin + +import org.mybatis.dynamic.sql.BindableColumn +import org.mybatis.dynamic.sql.VisitableCondition +import org.mybatis.dynamic.sql.update.UpdateDSL +import org.mybatis.dynamic.sql.update.UpdateModel +import org.mybatis.dynamic.sql.util.Buildable + +typealias UpdateCompleter = UpdateDSL.() -> Buildable + +fun UpdateDSL.where(column: BindableColumn, condition: VisitableCondition, + collect: CriteriaReceiver) = + apply { + where().and(column, condition, collect) + } + +fun UpdateDSL.and(column: BindableColumn, condition: VisitableCondition) = + apply { + where().and(column, condition) + } + +fun UpdateDSL.and(column: BindableColumn, condition: VisitableCondition, + collect: CriteriaReceiver) = + apply { + where().and(column, condition, collect) + } + +fun UpdateDSL.or(column: BindableColumn, condition: VisitableCondition) = + apply { + where().or(column, condition) + } + +fun UpdateDSL.or(column: BindableColumn, condition: VisitableCondition, + collect: CriteriaReceiver) = + apply { + where().or(column, condition, collect) + } diff --git a/src/main/kotlin/org/mybatis/dynamic/sql/util/kotlin/mybatis3/UtilityFunctions.kt b/src/main/kotlin/org/mybatis/dynamic/sql/util/kotlin/mybatis3/UtilityFunctions.kt new file mode 100644 index 000000000..ede902853 --- /dev/null +++ b/src/main/kotlin/org/mybatis/dynamic/sql/util/kotlin/mybatis3/UtilityFunctions.kt @@ -0,0 +1,52 @@ +/** + * Copyright 2016-2019 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.util.kotlin.mybatis3 + +import org.mybatis.dynamic.sql.SqlBuilder +import org.mybatis.dynamic.sql.SqlTable +import org.mybatis.dynamic.sql.insert.InsertDSL +import org.mybatis.dynamic.sql.insert.MultiRowInsertDSL +import org.mybatis.dynamic.sql.insert.render.InsertStatementProvider +import org.mybatis.dynamic.sql.insert.render.MultiRowInsertStatementProvider +import org.mybatis.dynamic.sql.render.RenderingStrategies +import org.mybatis.dynamic.sql.select.QueryExpressionDSL +import org.mybatis.dynamic.sql.select.SelectModel +import org.mybatis.dynamic.sql.util.kotlin.DeleteCompleter +import org.mybatis.dynamic.sql.util.kotlin.SelectCompleter +import org.mybatis.dynamic.sql.util.kotlin.UpdateCompleter +import org.mybatis.dynamic.sql.util.mybatis3.MyBatis3Utils + +fun deleteFrom(table: SqlTable, complete: DeleteCompleter) = + complete(SqlBuilder.deleteFrom(table)).build().render(RenderingStrategies.MYBATIS3) + +fun insert(mapper: (InsertStatementProvider) -> Int, record: T, table: SqlTable, + completer: InsertDSL.() -> InsertDSL) = + MyBatis3Utils.insert(mapper, record, table, completer) + +fun insertMultiple(mapper: (MultiRowInsertStatementProvider) -> Int, records: Collection, table: SqlTable, + completer: MultiRowInsertDSL.() -> MultiRowInsertDSL) = + MyBatis3Utils.insertMultiple(mapper, records, table, completer) + +fun QueryExpressionDSL.FromGatherer.from(table: SqlTable, + complete: SelectCompleter) = + complete(from(table)).build().render(RenderingStrategies.MYBATIS3) + +fun QueryExpressionDSL.FromGatherer.from(table: SqlTable, alias: String, + complete: SelectCompleter) = + complete(from(table, alias)).build().render(RenderingStrategies.MYBATIS3) + +fun update(table: SqlTable, complete: UpdateCompleter) = + complete(SqlBuilder.update(table)).build().render(RenderingStrategies.MYBATIS3) diff --git a/src/site/markdown/docs/kotlin.md b/src/site/markdown/docs/kotlin.md new file mode 100644 index 000000000..398d6b1b6 --- /dev/null +++ b/src/site/markdown/docs/kotlin.md @@ -0,0 +1,362 @@ +# Kotlin Support for MyBatis3 +MyBatis Dynamic SQL includes Kotlin extension methods that enable an SQL DSL for Kotlin. This is the recommended method of using the library in Kotlin. + +The standard usage patterns for MyBatis Dynamic SQL and MyBatis3 in Java must be modified somewhat for Kotlin. Kotlin interfaces can contain both abstract and non-abstract methods (somewhat similar to Java's default methods in an interface). But using these methods in Kotlin based mapper interfaces will cause a failure with MyBatis because of the underlying Kotlin implementation. + +This page will show our recommended pattern for using the MyBatis Dynamic SQL with Kotlin. The code shown on this page is from the `src/test/kotlin/examples/kotlin/canonical` directory in this repository. That directory contains a complete example of using this library with Kotlin. + +All Kotlin support is available in two packages: + +* `org.mybatis.dynamic.sql.util.kotlin` - contains extension methods and utilities to enable an idiomatic Kotlin DSL for MyBatis Dynamic SQL. These objects can be used for clients using any execution target (i.e. MyBatis3 or Spring JDBC Templates) +* `org.mybatis.dynamic.sql.util.kotlin.mybatis3` - contains utlities specifically to simplify MyBatis3 based clients + +Using the support in these packages, it is possible to create reusable Kotlin classes, interfaces, and extension methods that mimic the code created by MyBatis Generator for Java - but code that is more idiomatic for Kotlin. + +## Kotlin Dynamic SQL Support Objects +Because Kotlin does not support static class members, we recommend a simpler pattern for creating the class containing the support objects. For example: + +```kotlin +object PersonDynamicSqlSupport { + object Person : SqlTable("Person") { + val id = column("id", JDBCType.INTEGER) + val firstName = column("first_name", JDBCType.VARCHAR) + val lastName = column("last_name", JDBCType.VARCHAR) + val birthDate = column("birth_date", JDBCType.DATE) + val employed = column("employed", JDBCType.VARCHAR, "examples.kotlin.YesNoTypeHandler") + val occupation = column("occupation", JDBCType.VARCHAR) + val addressId = column("address_id", JDBCType.INTEGER) + } +} +``` + +This object is a singleton containing the `SqlTable` and `SqlColumn` objects that map to the database table. + +## Kotlin Mappers for MyBatis3 +If you create a Kotlin mapper interface that includes both abstract and non-abstract methods, MyBatis will be confused and throw errors. By default Kotlin does not create Java default methods in an interface. For this reason, Kotlin mapper interfaces should only contain the actual MyBatis mapper abstract interface methods. What would normally be coded as default or static methods in a mapper interface should be coded as extension methods in Kotlin. For example, a simple MyBatis mapper could be coded like this: + +```kotlin +@Mapper +interface PersonMapper { + @SelectProvider(type = SqlProviderAdapter::class, method = "select") + @Results(id = "PersonRecordResult", value = [ + Result(column = "a_id", property = "id"), + Result(column = "first_name", property = "firstName"), + Result(column = "last_name", property = "lastName"), + Result(column = "birth_date", property = "birthDate"), + Result(column = "employed", property = "employed", typeHandler = YesNoTypeHandler::class), + Result(column = "occupation", property = "occupation"), + Result(column = "address_id", property = "addressId") + ]) + fun selectMany(selectStatement: SelectStatementProvider): List +} +``` + +And then extensions could be added to make a shortcut method as follows: + +```kotlin +private val selectList = arrayOf(id.`as`("A_ID"), firstName, lastName, birthDate, employed, occupation, addressId) + +fun PersonMapper.select(completer: SelectCompleter): List = + MyBatis3Utils.selectList(this::selectMany, selectList, Person, completer) +``` + +The extension method shows the use of the `SelectCompleter` type alias. This is a DSL extension supplied with the library. We will detail its use below. For now see that the extension method can be used in client code as follows: + +```kotlin +val rows = mapper.select { + where(id, isLessThan(100)) + or (employed, isTrue()) { + and (occupation, isEqualTo("Developer")) + } + orderBy(id) +} +``` + +This shows that the Kotlin support enables a more idiomatic Kotlin DSL. + +## Count Method Support + +A count query is a specialized select - it returns a single column - typically a long - and supports joins and a where clause. + +Count method support enables the creation of methods that execute a count query allowing a user to specify a where clause at runtime, but abstracting away all other details. + +To use this support, we envision creating two methods - one standard mapper method, and one extension method. The first method is the standard MyBatis Dynamic SQL method that will execute a select: + +```kotlin +@Mapper +interface PersonMapper { + @SelectProvider(type = SqlProviderAdapter::class, method = "select") + fun count(selectStatement: SelectStatementProvider): Long +} +``` + +This is a standard method for MyBatis Dynamic SQL that executes a query and returns a `Long`. The second method should be an extension maethod. It will reuse the abstract method and supply everything needed to build the select statement except the where clause: + +```kotlin +fun PersonMapper.count(completer: CountCompleter) = + MyBatis3Utils.count(this::count, Person, completer) +``` + +This method shows the use of `CountCompleter` which is a Kotlin typealias for a function with a receiver that will allow a user to supply a where clause. Clients can use the method as follows: + +```kotlin +val rows = mapper.count { + where(occupation, isNull()) { + and(employed, isFalse()) + } +} +``` + +There is also an extention method that can be used to count all rows in a table: + +```kotlin +val rows = mapper.count { allRows() } +``` + +## Delete Method Support + +Delete method support enables the creation of methods that execute a delete statement allowing a user to specify a where clause at runtime, but abstracting away all other details. + +To use this support, we envision creating two methods - one standard mapper method, and one extension method. The first method is the standard MyBatis Dynamic SQL method that will execute a delete: + +```kotlin +@Mapper +interface PersonMapper { + @DeleteProvider(type = SqlProviderAdapter::class, method = "delete") + fun delete(deleteStatement: DeleteStatementProvider): Int +} +``` + +This is a standard method for MyBatis Dynamic SQL that executes a delete and returns an `Int` - the number of rows deleted. The second method should be an extension method. It will reuse the abstract method and supply everything needed to build the delete statement except the where clause: + +```kotlin +fun PersonMapper.delete(completer: DeleteCompleter) = + MyBatis3Utils.deleteFrom(this::delete, Person, completer) +``` + +This method shows the use of `DeleteCompleter` which is a Kotlin typealias for a function with a receiver that will allow a user to supply a where clause. Clients can use the method as follows: + +```kotlin +val rows = mapper.delete { + where(occupation, isNull()) +} +``` + +There is an extension method that can be used to delete all rows in a table: + +```kotlin +val rows = mapper.delete { allRows() } +``` + +## Insert Method Support + +Insert method support enables the removal of some of the boilerplate code from insert methods in a mapper interfaces. + +To use this support, we envision creating several methods - two standard mapper methods, and other extension methods. The standard mapper methods are standard MyBatis Dynamic SQL methods that will execute a delete: + +```kotlin +@Mapper +interface PersonMapper { + @InsertProvider(type = SqlProviderAdapter::class, method = "insert") + fun insert(insertStatement: InsertStatementProvider): Int + + @InsertProvider(type = SqlProviderAdapter::class, method = "insertMultiple") + fun insertMultiple(insertStatement: MultiRowInsertStatementProvider): Int +} +``` + +These methods can be used to implement simplified insert methods with Kotlin extension methods: + +```kotlin +fun PersonMapper.insert(record: PersonRecord) = + insert(this::insert, record, Person) { + map(id).toProperty("id") + map(firstName).toProperty("firstName") + map(lastName).toProperty("lastName") + map(birthDate).toProperty("birthDate") + map(employed).toProperty("employed") + map(occupation).toProperty("occupation") + map(addressId).toProperty("addressId") + } + +fun PersonMapper.insertMultiple(vararg records: PersonRecord) = + insertMultiple(records.toList()) + +fun PersonMapper.insertMultiple(records: Collection) = + insertMultiple(this::insertMultiple, records, Person) { + map(id).toProperty("id") + map(firstName).toProperty("firstName") + map(lastName).toProperty("lastName") + map(birthDate).toProperty("birthDate") + map(employed).toProperty("employed") + map(occupation).toProperty("occupation") + map(addressId).toProperty("addressId") + } + +fun PersonMapper.insertSelective(record: PersonRecord) = + insert(this::insert, record, Person) { + map(id).toPropertyWhenPresent("id", record::id) + map(firstName).toPropertyWhenPresent("firstName", record::firstName) + map(lastName).toPropertyWhenPresent("lastName", record::lastName) + map(birthDate).toPropertyWhenPresent("birthDate", record::birthDate) + map(employed).toPropertyWhenPresent("employed", record::employed) + map(occupation).toPropertyWhenPresent("occupation", record::occupation) + map(addressId).toPropertyWhenPresent("addressId", record::addressId) + } +``` + +Note these methods use Kotlin utility methods named `insert` and `insertMultiple`. Both methods accept a function with a receiver that will allow column mappings. + +Clients use these methods as follows: + +```kotlin +// single insert... +val record = PersonRecord(100, "Joe", LastName("Jones"), Date(), true, "Developer", 1) +val rows = mapper.insert(record) + +// multiple insert... +val record1 = PersonRecord(100, "Joe", LastName("Jones"), Date(), true, "Developer", 1) +val record2 = PersonRecord(101, "Sarah", LastName("Smith"), Date(), true, "Architect", 2) +val rows = mapper.insertMultiple(record1, record2) +``` + +## Select Method Support + +Select method support enables the creation of methods that execute a query allowing a user to specify a where clause and/or an order by clause and/or pagination clauses at runtime, but abstracting away all other details. + +To use this support, we envision creating several methods - two standard mapper methods, and other extension methods. The standard mapper methods are standard MyBatis Dynamic SQL methods that will execute a select: + +```kotlin +@Mapper +interface PersonMapper { + @SelectProvider(type = SqlProviderAdapter::class, method = "select") + @Results(id = "PersonResult", value = [ + Result(column = "A_ID", property = "id", jdbcType = JdbcType.INTEGER, id = true), + Result(column = "first_name", property = "firstName", jdbcType = JdbcType.VARCHAR), + Result(column = "last_name", property = "lastName", jdbcType = JdbcType.VARCHAR, + typeHandler = LastNameTypeHandler::class), + Result(column = "birth_date", property = "birthDate", jdbcType = JdbcType.DATE), + Result(column = "employed", property = "employed", jdbcType = JdbcType.VARCHAR, + typeHandler = YesNoTypeHandler::class), + Result(column = "occupation", property = "occupation", jdbcType = JdbcType.VARCHAR), + Result(column = "address_id", property = "addressId", jdbcType = JdbcType.INTEGER)]) + fun selectMany(selectStatement: SelectStatementProvider): List + + @SelectProvider(type = SqlProviderAdapter::class, method = "select") + @ResultMap("PersonResult") + fun selectOne(selectStatement: SelectStatementProvider): PersonRecord? +} +``` + +These methods can be used to create simplified select methods with Kotlin extension methods: + +```kotlin +private val selectList = arrayOf(id.`as`("A_ID"), firstName, lastName, birthDate, employed, occupation, addressId) + +fun PersonMapper.selectOne(completer: SelectCompleter) = + MyBatis3Utils.selectOne(this::selectOne, selectList, Person, completer) + +fun PersonMapper.select(completer: SelectCompleter): List = + MyBatis3Utils.selectList(this::selectMany, selectList, Person, completer) + +fun PersonMapper.selectDistinct(completer: SelectCompleter): List = + MyBatis3Utils.selectDistinct(this::selectMany, selectList, Person, completer) +``` + +These methods show the use of `SelectCompleter` which is a which is a Kotlin typealias for a function with a receiver that will allow a user to supply a where clause. The `selectMany` method can be used to implement generalized select methods where a user can specify a where clause and/or an order by clause. Typically we recommend two of these methods - for select, and select distinct. The `selectOne` method is used to create a generalized select method where a user can specify a where clause. + +The general `selectOne` method can also be used to implement a `selectByPrimaryKey` method: + +```kotlin +fun PersonMapper.selectByPrimaryKey(id_: Int) = + selectOne { + where(id, isEqualTo(id_)) + } +``` + +Clients can use the methods as follows: + +```kotlin +val rows = mapper.select { + where(firstName, isIn("Fred", "Barney")) + orderBy(id) + limit(3) +} +``` + +There is a utility methods that will select all rows in a table: + +```kotlin +val rows = mapper.select { allRows() } +``` + +The following query will select all rows in a specified order: + +```kotlin +val rows = mapper.select { + allRows() + orderBy(lastName, firstName) +} +``` + +## Update Method Support + +Update method support enables the creation of methods that execute an update allowing a user to specify SET clauses and/or a WHERE clause, but abstracting away all other details. + +To use this support, we envision creating several methods - one standard mapper method, and other extension methods. The standard mapper method is a standard MyBatis Dynamic SQL methods that will execute an update: + +```kotlin +@Mapper +interface PersonMapper { + @UpdateProvider(type = SqlProviderAdapter::class, method = "update") + fun update(updateStatement: UpdateStatementProvider): Int +} +``` + +This is a standard method for MyBatis Dynamic SQL that executes an update and returns an `int` - the number of rows updated. The extension methods will reuse this method and supply everything needed to build the update statement except the values and the where clause: + +```kotlin +fun PersonMapper.update(completer: UpdateCompleter) = + MyBatis3Utils.update(this::update, Person, completer) +``` + +This extension method shows the use of `UpdateCompleter` which is a Kotlin typealias for a function with a receiver that will allow a user to supply values and a where clause. Clients can use the method as follows: + +```kotlin +val rows = mapper.update { + set(occupation).equalTo("Programmer") + where(id, isEqualTo(100)) +} +``` + +All rows in a table can be updated by simply omitting the where clause: + +```kotlin +val rows = mapper.update { + set(occupation).equalTo("Programmer") +} +``` + +It is also possible to write a utility method that will set values. For example: + +```kotlin +fun UpdateDSL.setSelective(record: PersonRecord) = + apply { + set(id).equalToWhenPresent(record::id) + set(firstName).equalToWhenPresent(record::firstName) + set(lastName).equalToWhenPresent(record::lastName) + set(birthDate).equalToWhenPresent(record::birthDate) + set(employed).equalToWhenPresent(record::employed) + set(occupation).equalToWhenPresent(record::occupation) + set(addressId).equalToWhenPresent(record::addressId) + } +``` + +This method will selectively set values if corresponding fields in a record are non null. This method can be used as follows: + +```kotlin +val rows = mapper.update { + setSelective(updateRecord) + where(id, isEqualTo(100)) +} +``` diff --git a/src/site/markdown/docs/mybatis3.md b/src/site/markdown/docs/mybatis3.md index 2363ec41c..51938ad26 100644 --- a/src/site/markdown/docs/mybatis3.md +++ b/src/site/markdown/docs/mybatis3.md @@ -3,7 +3,7 @@ Most of the examples shown on this site are for usage with MyBatis3 - even thoug The goal of this support is to reduce the amount of boilerplate code needed for a typical CRUD mapper. For example, this support allows you to create a reusable SELECT method where the user only needs to specify a WHERE clause. -With version 1.1.3, specialized interfaces and utilities were added that can further simplify client code. This support enables the creation of methods that have similar functionality to some of the methods generated in previous versions of MyBatis generator like countByExample, deleteByExample, and selectByExample. We no longer use the "by example" terms for these methods as this library has eliminated the Example class that was generated by prior versions of MyBatis generator. +With version 1.1.3, specialized interfaces and utilities were added that can further simplify client code. This support enables the creation of methods that have similar functionality to some of the methods generated in previous versions of MyBatis generator like countByExample, deleteByExample, and selectByExample. We no longer use the "by example" terms for these methods as this library has eliminated the Example class that was generated by prior versions of MyBatis Generator. ## Count Method Support @@ -19,12 +19,12 @@ long count(SelectStatementProvider selectStatement); This is a standard method for MyBatis Dynamic SQL that executes a query and returns a `long`. The second method will reuse this method and supply everything needed to build the select statement except the where clause: ```java -default long count(SelectDSLCompleter completer) { +default long count(CountDSLCompleter completer) { return MyBatis3Utils.count(this::count, person, completer); } ``` -This method shows the use of `SelectDSLCompleter` which is a specialization of a `java.util.Function` that will allow a user to supply a where clause. Clients can use the method as follows: +This method shows the use of `CountDSLCompleter` which is a specialization of a `java.util.Function` that will allow a user to supply a where clause. Clients can use the method as follows: ```java long rows = mapper.count(c -> @@ -34,7 +34,7 @@ long rows = mapper.count(c -> There is a utility method that can be used to count all rows in a table: ```java -long rows = mapper.count(SelectDSLCompleter.allRows()); +long rows = mapper.count(CountDSLCompleter.allRows()); ``` ## Delete Method Support @@ -147,7 +147,7 @@ These two methods are standard methods for MyBatis Dynamic SQL. They execute a s We also envision creating a static field for a reusable list of columns for a select statement: ```java -static BasicColumn[] selectList = +BasicColumn[] selectList = BasicColumn.columnList(id.as("A_ID"), firstName, lastName, birthDate, employed, occupation, addressId); ``` diff --git a/src/site/site.xml b/src/site/site.xml index 5f0fc0686..665d86d7e 100644 --- a/src/site/site.xml +++ b/src/site/site.xml @@ -48,6 +48,7 @@ + diff --git a/src/test/java/examples/simple/PersonMapper.java b/src/test/java/examples/simple/PersonMapper.java index ad66c8081..77b55cb5d 100644 --- a/src/test/java/examples/simple/PersonMapper.java +++ b/src/test/java/examples/simple/PersonMapper.java @@ -37,6 +37,7 @@ import org.mybatis.dynamic.sql.delete.render.DeleteStatementProvider; import org.mybatis.dynamic.sql.insert.render.InsertStatementProvider; import org.mybatis.dynamic.sql.insert.render.MultiRowInsertStatementProvider; +import org.mybatis.dynamic.sql.select.CountDSLCompleter; import org.mybatis.dynamic.sql.select.SelectDSLCompleter; import org.mybatis.dynamic.sql.select.render.SelectStatementProvider; import org.mybatis.dynamic.sql.update.UpdateDSLCompleter; @@ -83,13 +84,13 @@ public interface PersonMapper { @ResultMap("PersonResult") Optional selectOne(SelectStatementProvider selectStatement); - static BasicColumn[] selectList = + BasicColumn[] selectList = BasicColumn.columnList(id.as("A_ID"), firstName, lastName, birthDate, employed, occupation, addressId); @UpdateProvider(type=SqlProviderAdapter.class, method="update") int update(UpdateStatementProvider updateStatement); - default long count(SelectDSLCompleter completer) { + default long count(CountDSLCompleter completer) { return MyBatis3Utils.count(this::count, person, completer); } @@ -144,7 +145,7 @@ default int insertSelective(PersonRecord record) { } default Optional selectOne(SelectDSLCompleter completer) { - return MyBatis3Utils.selectOne(this::selectOne, selectList, person, completer); + return MyBatis3Utils.selectOptional(this::selectOne, selectList, person, completer); } default List select(SelectDSLCompleter completer) { diff --git a/src/test/java/examples/simple/PersonMapperTest.java b/src/test/java/examples/simple/PersonMapperTest.java index 3ffecbd54..0ab1a3181 100644 --- a/src/test/java/examples/simple/PersonMapperTest.java +++ b/src/test/java/examples/simple/PersonMapperTest.java @@ -40,6 +40,7 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.mybatis.dynamic.sql.delete.DeleteDSLCompleter; +import org.mybatis.dynamic.sql.select.CountDSLCompleter; import org.mybatis.dynamic.sql.select.SelectDSLCompleter; public class PersonMapperTest { @@ -448,7 +449,7 @@ public void testCount() { public void testCountAll() { try (SqlSession session = sqlSessionFactory.openSession()) { PersonMapper mapper = session.getMapper(PersonMapper.class); - long rows = mapper.count(SelectDSLCompleter.allRows()); + long rows = mapper.count(CountDSLCompleter.allRows()); assertThat(rows).isEqualTo(6L); } @@ -554,4 +555,24 @@ public void testJoinPrimaryKeyInvalidRecord() { assertThat(record).isEmpty(); } } + + @Test + public void testJoinCount() { + try (SqlSession session = sqlSessionFactory.openSession()) { + PersonWithAddressMapper mapper = session.getMapper(PersonWithAddressMapper.class); + long count = mapper.count(c -> c.where(person.id, isEqualTo(55))); + + assertThat(count).isEqualTo(0); + } + } + + @Test + public void testJoinCountWithSubcriteria() { + try (SqlSession session = sqlSessionFactory.openSession()) { + PersonWithAddressMapper mapper = session.getMapper(PersonWithAddressMapper.class); + long count = mapper.count(c -> c.where(person.id, isEqualTo(55), or(person.id, isEqualTo(1)))); + + assertThat(count).isEqualTo(1); + } + } } diff --git a/src/test/java/examples/simple/PersonWithAddressMapper.java b/src/test/java/examples/simple/PersonWithAddressMapper.java index 5f9b59fd3..13cadc6c8 100644 --- a/src/test/java/examples/simple/PersonWithAddressMapper.java +++ b/src/test/java/examples/simple/PersonWithAddressMapper.java @@ -36,6 +36,8 @@ import org.apache.ibatis.type.JdbcType; import org.mybatis.dynamic.sql.BasicColumn; import org.mybatis.dynamic.sql.SqlBuilder; +import org.mybatis.dynamic.sql.select.CountDSL; +import org.mybatis.dynamic.sql.select.CountDSLCompleter; import org.mybatis.dynamic.sql.select.QueryExpressionDSL; import org.mybatis.dynamic.sql.select.SelectDSLCompleter; import org.mybatis.dynamic.sql.select.SelectModel; @@ -70,7 +72,10 @@ public interface PersonWithAddressMapper { @ResultMap("PersonWithAddressResult") Optional selectOne(SelectStatementProvider selectStatement); - static BasicColumn[] selectList = + @SelectProvider(type=SqlProviderAdapter.class, method="select") + long count(SelectStatementProvider selectStatement); + + BasicColumn[] selectList = BasicColumn.columnList(id.as("A_ID"), firstName, lastName, birthDate, employed, occupation, address.id, address.streetAddress, address.city, address.state); @@ -91,4 +96,10 @@ default Optional selectByPrimaryKey(Integer id_) { c.where(id, isEqualTo(id_)) ); } + + default long count(CountDSLCompleter completer) { + CountDSL start = countFrom(person) + .join(address, on(person.addressId, equalTo(address.id))); + return MyBatis3Utils.count(this::count, start, completer); + } } diff --git a/src/main/java/org/mybatis/dynamic/sql/select/join/OnJoinCriterion.java b/src/test/kotlin/examples/kotlin/canonical/AddressDynamicSqlSupport.kt similarity index 55% rename from src/main/java/org/mybatis/dynamic/sql/select/join/OnJoinCriterion.java rename to src/test/kotlin/examples/kotlin/canonical/AddressDynamicSqlSupport.kt index c8238b948..87912460c 100644 --- a/src/main/java/org/mybatis/dynamic/sql/select/join/OnJoinCriterion.java +++ b/src/test/kotlin/examples/kotlin/canonical/AddressDynamicSqlSupport.kt @@ -13,27 +13,17 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.mybatis.dynamic.sql.select.join; +package examples.kotlin.canonical -public class OnJoinCriterion extends JoinCriterion { - - private OnJoinCriterion(Builder builder) { - super(builder); - } +import org.mybatis.dynamic.sql.SqlTable - @Override - public String connector() { - return "on"; //$NON-NLS-1$ - } +import java.sql.JDBCType - public static class Builder extends AbstractBuilder { - @Override - protected Builder getThis() { - return this; - } - - public OnJoinCriterion build() { - return new OnJoinCriterion(this); - } +object AddressDynamicSqlSupport { + object Address : SqlTable("Address") { + val id = column("address_id", JDBCType.INTEGER) + val streetAddress = column("street_address", JDBCType.VARCHAR) + val city = column("city", JDBCType.VARCHAR) + val state = column("state", JDBCType.VARCHAR) } } diff --git a/src/test/kotlin/examples/kotlin/canonical/AddressRecord.kt b/src/test/kotlin/examples/kotlin/canonical/AddressRecord.kt new file mode 100644 index 000000000..9f77222aa --- /dev/null +++ b/src/test/kotlin/examples/kotlin/canonical/AddressRecord.kt @@ -0,0 +1,19 @@ +/** + * Copyright 2016-2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package examples.kotlin.canonical + +data class AddressRecord(var id: Int? = null, var streetAddress: String? = null, var city: String? = null, + var state: String? = null) diff --git a/src/test/kotlin/examples/kotlin/canonical/LastName.kt b/src/test/kotlin/examples/kotlin/canonical/LastName.kt new file mode 100644 index 000000000..068305b88 --- /dev/null +++ b/src/test/kotlin/examples/kotlin/canonical/LastName.kt @@ -0,0 +1,18 @@ +/** + * Copyright 2016-2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package examples.kotlin.canonical + +data class LastName(var name: String) diff --git a/src/test/kotlin/examples/kotlin/canonical/LastNameTypeHandler.kt b/src/test/kotlin/examples/kotlin/canonical/LastNameTypeHandler.kt new file mode 100644 index 000000000..29ff22107 --- /dev/null +++ b/src/test/kotlin/examples/kotlin/canonical/LastNameTypeHandler.kt @@ -0,0 +1,41 @@ +/** + * Copyright 2016-2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package examples.kotlin.canonical + +import org.apache.ibatis.type.JdbcType +import org.apache.ibatis.type.TypeHandler + +import java.sql.CallableStatement +import java.sql.PreparedStatement +import java.sql.ResultSet + +class LastNameTypeHandler : TypeHandler { + + override fun setParameter(ps: PreparedStatement, i: Int, parameter: LastName, jdbcType: JdbcType) = + ps.setString(i, parameter.name) + + override fun getResult(rs: ResultSet, columnName: String) = + toLastName(rs.getString(columnName)) + + override fun getResult(rs: ResultSet, columnIndex: Int) = + toLastName(rs.getString(columnIndex)) + + override fun getResult(cs: CallableStatement, columnIndex: Int) = + toLastName(cs.getString(columnIndex)) + + private fun toLastName(s: String?) = + if (s == null) null else LastName(s) +} diff --git a/src/test/kotlin/examples/kotlin/canonical/PersonDynamicSqlSupport.kt b/src/test/kotlin/examples/kotlin/canonical/PersonDynamicSqlSupport.kt new file mode 100644 index 000000000..13ef50ca7 --- /dev/null +++ b/src/test/kotlin/examples/kotlin/canonical/PersonDynamicSqlSupport.kt @@ -0,0 +1,32 @@ +/** + * Copyright 2016-2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package examples.kotlin.canonical + +import org.mybatis.dynamic.sql.SqlTable +import java.sql.JDBCType +import java.util.* + +object PersonDynamicSqlSupport { + object Person : SqlTable("Person") { + val id = column("id", JDBCType.INTEGER) + val firstName = column("first_name", JDBCType.VARCHAR) + val lastName = column("last_name", JDBCType.VARCHAR, "examples.kotlin.canonical.LastNameTypeHandler") + val birthDate = column("birth_date", JDBCType.DATE) + val employed = column("employed", JDBCType.VARCHAR, "examples.kotlin.canonical.YesNoTypeHandler") + val occupation = column("occupation", JDBCType.VARCHAR) + val addressId = column("address_id", JDBCType.INTEGER) + } +} diff --git a/src/test/kotlin/examples/kotlin/canonical/PersonMapper.kt b/src/test/kotlin/examples/kotlin/canonical/PersonMapper.kt new file mode 100644 index 000000000..0be3d505d --- /dev/null +++ b/src/test/kotlin/examples/kotlin/canonical/PersonMapper.kt @@ -0,0 +1,67 @@ +/** + * Copyright 2016-2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package examples.kotlin.canonical + +import org.apache.ibatis.annotations.* +import org.apache.ibatis.type.JdbcType +import org.mybatis.dynamic.sql.delete.render.DeleteStatementProvider +import org.mybatis.dynamic.sql.insert.render.InsertStatementProvider +import org.mybatis.dynamic.sql.insert.render.MultiRowInsertStatementProvider +import org.mybatis.dynamic.sql.select.render.SelectStatementProvider +import org.mybatis.dynamic.sql.update.render.UpdateStatementProvider +import org.mybatis.dynamic.sql.util.SqlProviderAdapter + +/** + * + * Note: this is the canonical mapper with the new style methods + * and represents the desired output for MyBatis Generator + * + */ +@Mapper +interface PersonMapper { + + @SelectProvider(type = SqlProviderAdapter::class, method = "select") + fun count(selectStatement: SelectStatementProvider): Long + + @DeleteProvider(type = SqlProviderAdapter::class, method = "delete") + fun delete(deleteStatement: DeleteStatementProvider): Int + + @InsertProvider(type = SqlProviderAdapter::class, method = "insert") + fun insert(insertStatement: InsertStatementProvider): Int + + @InsertProvider(type = SqlProviderAdapter::class, method = "insertMultiple") + fun insertMultiple(insertStatement: MultiRowInsertStatementProvider): Int + + @SelectProvider(type = SqlProviderAdapter::class, method = "select") + @Results(id = "PersonResult", value = [ + Result(column = "A_ID", property = "id", jdbcType = JdbcType.INTEGER, id = true), + Result(column = "first_name", property = "firstName", jdbcType = JdbcType.VARCHAR), + Result(column = "last_name", property = "lastName", jdbcType = JdbcType.VARCHAR, + typeHandler = LastNameTypeHandler::class), + Result(column = "birth_date", property = "birthDate", jdbcType = JdbcType.DATE), + Result(column = "employed", property = "employed", jdbcType = JdbcType.VARCHAR, + typeHandler = YesNoTypeHandler::class), + Result(column = "occupation", property = "occupation", jdbcType = JdbcType.VARCHAR), + Result(column = "address_id", property = "addressId", jdbcType = JdbcType.INTEGER)]) + fun selectMany(selectStatement: SelectStatementProvider): List + + @SelectProvider(type = SqlProviderAdapter::class, method = "select") + @ResultMap("PersonResult") + fun selectOne(selectStatement: SelectStatementProvider): PersonRecord? + + @UpdateProvider(type = SqlProviderAdapter::class, method = "update") + fun update(updateStatement: UpdateStatementProvider): Int +} diff --git a/src/test/kotlin/examples/kotlin/canonical/PersonMapperExtensions.kt b/src/test/kotlin/examples/kotlin/canonical/PersonMapperExtensions.kt new file mode 100644 index 000000000..0f16346e2 --- /dev/null +++ b/src/test/kotlin/examples/kotlin/canonical/PersonMapperExtensions.kt @@ -0,0 +1,142 @@ +/** + * Copyright 2016-2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package examples.kotlin.canonical + +import examples.kotlin.canonical.PersonDynamicSqlSupport.Person +import examples.kotlin.canonical.PersonDynamicSqlSupport.Person.addressId +import examples.kotlin.canonical.PersonDynamicSqlSupport.Person.birthDate +import examples.kotlin.canonical.PersonDynamicSqlSupport.Person.employed +import examples.kotlin.canonical.PersonDynamicSqlSupport.Person.firstName +import examples.kotlin.canonical.PersonDynamicSqlSupport.Person.id +import examples.kotlin.canonical.PersonDynamicSqlSupport.Person.lastName +import examples.kotlin.canonical.PersonDynamicSqlSupport.Person.occupation +import org.mybatis.dynamic.sql.SqlBuilder.isEqualTo +import org.mybatis.dynamic.sql.update.UpdateDSL +import org.mybatis.dynamic.sql.update.UpdateModel +import org.mybatis.dynamic.sql.util.kotlin.* +import org.mybatis.dynamic.sql.util.kotlin.mybatis3.insert +import org.mybatis.dynamic.sql.util.kotlin.mybatis3.insertMultiple +import org.mybatis.dynamic.sql.util.mybatis3.MyBatis3Utils + +fun PersonMapper.count(completer: CountCompleter) = + MyBatis3Utils.count(this::count, Person, completer) + +fun PersonMapper.delete(completer: DeleteCompleter) = + MyBatis3Utils.deleteFrom(this::delete, Person, completer) + +fun PersonMapper.deleteByPrimaryKey(id_: Int) = + delete { + where(id, isEqualTo(id_)) + } + +fun PersonMapper.insert(record: PersonRecord) = + insert(this::insert, record, Person) { + map(id).toProperty("id") + map(firstName).toProperty("firstName") + map(lastName).toProperty("lastName") + map(birthDate).toProperty("birthDate") + map(employed).toProperty("employed") + map(occupation).toProperty("occupation") + map(addressId).toProperty("addressId") + } + +fun PersonMapper.insertMultiple(vararg records: PersonRecord) = + insertMultiple(records.toList()) + +fun PersonMapper.insertMultiple(records: Collection) = + insertMultiple(this::insertMultiple, records, Person) { + map(id).toProperty("id") + map(firstName).toProperty("firstName") + map(lastName).toProperty("lastName") + map(birthDate).toProperty("birthDate") + map(employed).toProperty("employed") + map(occupation).toProperty("occupation") + map(addressId).toProperty("addressId") + } + +fun PersonMapper.insertSelective(record: PersonRecord) = + insert(this::insert, record, Person) { + map(id).toPropertyWhenPresent("id", record::id) + map(firstName).toPropertyWhenPresent("firstName", record::firstName) + map(lastName).toPropertyWhenPresent("lastName", record::lastName) + map(birthDate).toPropertyWhenPresent("birthDate", record::birthDate) + map(employed).toPropertyWhenPresent("employed", record::employed) + map(occupation).toPropertyWhenPresent("occupation", record::occupation) + map(addressId).toPropertyWhenPresent("addressId", record::addressId) + } + +private val selectList = arrayOf(id.`as`("A_ID"), firstName, lastName, birthDate, employed, occupation, addressId) + +fun PersonMapper.selectOne(completer: SelectCompleter) = + MyBatis3Utils.selectOne(this::selectOne, selectList, Person, completer) + +fun PersonMapper.select(completer: SelectCompleter): List = + MyBatis3Utils.selectList(this::selectMany, selectList, Person, completer) + +fun PersonMapper.selectDistinct(completer: SelectCompleter): List = + MyBatis3Utils.selectDistinct(this::selectMany, selectList, Person, completer) + +fun PersonMapper.selectByPrimaryKey(id_: Int) = + selectOne { + where(id, isEqualTo(id_)) + } + +fun PersonMapper.update(completer: UpdateCompleter) = + MyBatis3Utils.update(this::update, Person, completer) + +fun UpdateDSL.setAll(record: PersonRecord) = + apply { + set(id).equalTo(record::id) + set(firstName).equalTo(record::firstName) + set(lastName).equalTo(record::lastName) + set(birthDate).equalTo(record::birthDate) + set(employed).equalTo(record::employed) + set(occupation).equalTo(record::occupation) + set(addressId).equalTo(record::addressId) + } + +fun UpdateDSL.setSelective(record: PersonRecord) = + apply { + set(id).equalToWhenPresent(record::id) + set(firstName).equalToWhenPresent(record::firstName) + set(lastName).equalToWhenPresent(record::lastName) + set(birthDate).equalToWhenPresent(record::birthDate) + set(employed).equalToWhenPresent(record::employed) + set(occupation).equalToWhenPresent(record::occupation) + set(addressId).equalToWhenPresent(record::addressId) + } + +fun PersonMapper.updateByPrimaryKey(record: PersonRecord) = + update { + set(firstName).equalTo(record::firstName) + set(lastName).equalTo(record::lastName) + set(birthDate).equalTo(record::birthDate) + set(employed).equalTo(record::employed) + set(occupation).equalTo(record::occupation) + set(addressId).equalTo(record::addressId) + where(id, isEqualTo(record::id)) + } + +fun PersonMapper.updateByPrimaryKeySelective(record: PersonRecord) = + update { + set(firstName).equalToWhenPresent(record::firstName) + set(lastName).equalToWhenPresent(record::lastName) + set(birthDate).equalToWhenPresent(record::birthDate) + set(employed).equalToWhenPresent(record::employed) + set(occupation).equalToWhenPresent(record::occupation) + set(addressId).equalToWhenPresent(record::addressId) + where(id, isEqualTo(record::id)) + } diff --git a/src/test/kotlin/examples/kotlin/canonical/PersonMapperTest.kt b/src/test/kotlin/examples/kotlin/canonical/PersonMapperTest.kt new file mode 100644 index 000000000..480f9c756 --- /dev/null +++ b/src/test/kotlin/examples/kotlin/canonical/PersonMapperTest.kt @@ -0,0 +1,564 @@ +/** + * Copyright 2016-2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package examples.kotlin.canonical + +import examples.kotlin.canonical.PersonDynamicSqlSupport.Person.employed +import examples.kotlin.canonical.PersonDynamicSqlSupport.Person.firstName +import examples.kotlin.canonical.PersonDynamicSqlSupport.Person.id +import examples.kotlin.canonical.PersonDynamicSqlSupport.Person.lastName +import examples.kotlin.canonical.PersonDynamicSqlSupport.Person.occupation +import org.apache.ibatis.datasource.unpooled.UnpooledDataSource +import org.apache.ibatis.jdbc.ScriptRunner +import org.apache.ibatis.mapping.Environment +import org.apache.ibatis.session.Configuration +import org.apache.ibatis.session.SqlSession +import org.apache.ibatis.session.SqlSessionFactoryBuilder +import org.apache.ibatis.transaction.jdbc.JdbcTransactionFactory +import org.assertj.core.api.Assertions.assertThat +import org.junit.jupiter.api.Test +import org.mybatis.dynamic.sql.SqlBuilder.* +import org.mybatis.dynamic.sql.util.kotlin.* +import java.io.InputStreamReader +import java.sql.DriverManager +import java.util.* + +class PersonMapperTest { + private fun newSession(): SqlSession { + Class.forName(JDBC_DRIVER) + val script = javaClass.getResourceAsStream("/examples/kotlin/CreateSimpleDB.sql") + DriverManager.getConnection(JDBC_URL, "sa", "").use { connection -> + val sr = ScriptRunner(connection) + sr.setLogWriter(null) + sr.runScript(InputStreamReader(script)) + } + + val ds = UnpooledDataSource(JDBC_DRIVER, JDBC_URL, "sa", "") + val environment = Environment("test", JdbcTransactionFactory(), ds) + val config = Configuration(environment) + config.typeHandlerRegistry.register(YesNoTypeHandler::class.java) + config.addMapper(PersonMapper::class.java) + config.addMapper(PersonWithAddressMapper::class.java) + return SqlSessionFactoryBuilder().build(config).openSession() + } + + @Test + fun testSelect() { + newSession().use { session -> + val mapper = session.getMapper(PersonMapper::class.java) + + val rows = mapper.select { + where(id, isEqualTo(1)) + or(occupation, isNull()) + } + + assertThat(rows.size).isEqualTo(3) + } + } + + @Test + fun testSelectAll() { + newSession().use { session -> + val mapper = session.getMapper(PersonMapper::class.java) + + val rows = mapper.select { allRows() } + + assertThat(rows.size).isEqualTo(6) + assertThat(rows[0].id).isEqualTo(1) + assertThat(rows[5].id).isEqualTo(6) + } + } + + @Test + fun testSelectAllOrdered() { + newSession().use { session -> + val mapper = session.getMapper(PersonMapper::class.java) + + val rows = mapper.select { + allRows() + orderBy(lastName.descending(), firstName.descending()) + } + + assertThat(rows.size).isEqualTo(6) + assertThat(rows[0].id).isEqualTo(5) + assertThat(rows[5].id).isEqualTo(1) + } + } + + @Test + fun testSelectDistinct() { + newSession().use { session -> + val mapper = session.getMapper(PersonMapper::class.java) + + val rows = mapper.selectDistinct { + where(id, isGreaterThan(1)) + or(occupation, isNull()) + } + + assertThat(rows.size).isEqualTo(5) + } + } + + @Test + fun testSelectWithTypeHandler() { + newSession().use { session -> + val mapper = session.getMapper(PersonMapper::class.java) + + val rows = mapper.select { + where(employed, isEqualTo(false)) + orderBy(id) + } + + assertThat(rows.size).isEqualTo(2) + assertThat(rows[0].id).isEqualTo(3) + assertThat(rows[1].id).isEqualTo(6) + } + } + + @Test + fun testSelectByPrimaryKeyWithMissingRecord() { + newSession().use { session -> + val mapper = session.getMapper(PersonMapper::class.java) + + val record = mapper.selectByPrimaryKey(300) + assertThat(record).isNull() + } + } + + @Test + fun testFirstNameIn() { + newSession().use { session -> + val mapper = session.getMapper(PersonMapper::class.java) + + val rows = mapper.select { + where(firstName, isIn("Fred", "Barney")) + } + + assertThat(rows.size).isEqualTo(2) + assertThat(rows[0].lastName?.name).isEqualTo("Flintstone") + assertThat(rows[1].lastName?.name).isEqualTo("Rubble") + } + } + + @Test + fun testDelete() { + newSession().use { session -> + val mapper = session.getMapper(PersonMapper::class.java) + + val rows = mapper.delete { + where(occupation, isNull()) + } + assertThat(rows).isEqualTo(2) + } + } + + @Test + fun testDeleteAll() { + newSession().use { session -> + val mapper = session.getMapper(PersonMapper::class.java) + + val rows = mapper.delete { allRows() } + + assertThat(rows).isEqualTo(6) + } + } + + @Test + fun testDeleteByPrimaryKey() { + newSession().use { session -> + val mapper = session.getMapper(PersonMapper::class.java) + + val rows = mapper.deleteByPrimaryKey(2) + + assertThat(rows).isEqualTo(1) + } + } + + @Test + fun testInsert() { + newSession().use { session -> + val mapper = session.getMapper(PersonMapper::class.java) + + val record = PersonRecord(100, "Joe", LastName("Jones"), Date(), true, "Developer", 1) + + val rows = mapper.insert(record) + assertThat(rows).isEqualTo(1) + } + } + + @Test + fun testInsertMultiple() { + newSession().use { session -> + val mapper = session.getMapper(PersonMapper::class.java) + + val record1 = PersonRecord(100, "Joe", LastName("Jones"), Date(), true, "Developer", 1) + val record2 = PersonRecord(101, "Sarah", LastName("Smith"), Date(), true, "Architect", 2) + + val rows = mapper.insertMultiple(record1, record2) + assertThat(rows).isEqualTo(2) + } + } + + @Test + fun testInsertSelective() { + newSession().use { session -> + val mapper = session.getMapper(PersonMapper::class.java) + + val record = PersonRecord(100, "Joe", LastName("Jones"), Date(), false, null, 1) + + val rows = mapper.insertSelective(record) + assertThat(rows).isEqualTo(1) + } + } + + @Test + fun testUpdateByPrimaryKey() { + newSession().use { session -> + val mapper = session.getMapper(PersonMapper::class.java) + + val record = PersonRecord(100, "Joe", LastName("Jones"), Date(), true, "Developer", 1) + + var rows = mapper.insert(record) + assertThat(rows).isEqualTo(1) + + record.occupation = "Programmer" + rows = mapper.updateByPrimaryKey(record) + assertThat(rows).isEqualTo(1) + + val newRecord = mapper.selectByPrimaryKey(100) + assertThat(newRecord?.occupation).isEqualTo("Programmer") + } + } + + @Test + fun testUpdateByPrimaryKeySelective() { + newSession().use { session -> + val mapper = session.getMapper(PersonMapper::class.java) + + val record = PersonRecord(100, "Joe", LastName("Jones"), Date(), true, "Developer", 1) + + var rows = mapper.insert(record) + assertThat(rows).isEqualTo(1) + + val updateRecord = PersonRecord(id = 100, occupation = "Programmer") + rows = mapper.updateByPrimaryKeySelective(updateRecord) + assertThat(rows).isEqualTo(1) + + val newRecord = mapper.selectByPrimaryKey(100) + assertThat(newRecord?.occupation).isEqualTo("Programmer") + assertThat(newRecord?.firstName).isEqualTo("Joe") + } + } + + @Test + fun testUpdate() { + newSession().use { session -> + val mapper = session.getMapper(PersonMapper::class.java) + + val record = PersonRecord(100, "Joe", LastName("Jones"), Date(), true, "Developer", 1) + + var rows = mapper.insert(record) + assertThat(rows).isEqualTo(1) + + record.occupation = "Programmer" + + rows = mapper.update { + setAll(record) + where(id, isEqualTo(100)) + and(firstName, isEqualTo("Joe")) + } + + assertThat(rows).isEqualTo(1) + + val newRecord = mapper.selectByPrimaryKey(100) + assertThat(newRecord?.occupation).isEqualTo("Programmer") + } + } + + @Test + fun testUpdateOneField() { + newSession().use { session -> + val mapper = session.getMapper(PersonMapper::class.java) + + val record = PersonRecord(100, "Joe", LastName("Jones"), Date(), true, "Developer", 1) + + var rows = mapper.insert(record) + assertThat(rows).isEqualTo(1) + + rows = mapper.update { + set(occupation).equalTo("Programmer") + where(id, isEqualTo(100)) + } + + assertThat(rows).isEqualTo(1) + + val newRecord = mapper.selectByPrimaryKey(100) + assertThat(newRecord?.occupation).isEqualTo("Programmer") + } + } + + @Test + fun testUpdateAll() { + newSession().use { session -> + val mapper = session.getMapper(PersonMapper::class.java) + + val record = PersonRecord(100, "Joe", LastName("Jones"), Date(), true, "Developer", 1) + + var rows = mapper.insert(record) + assertThat(rows).isEqualTo(1) + + val updateRecord = PersonRecord(occupation = "Programmer") + + rows = mapper.update { + setSelective(updateRecord) + } + + assertThat(rows).isEqualTo(7) + + val newRecord = mapper.selectByPrimaryKey(100) + assertThat(newRecord?.occupation).isEqualTo("Programmer") + } + } + + @Test + fun testUpdateSelective() { + newSession().use { session -> + val mapper = session.getMapper(PersonMapper::class.java) + + val record = PersonRecord(100, "Joe", LastName("Jones"), Date(), true, "Developer", 1) + + var rows = mapper.insert(record) + assertThat(rows).isEqualTo(1) + + val updateRecord = PersonRecord(occupation = "Programmer") + + rows = mapper.update { + setSelective(updateRecord) + where(id, isEqualTo(100)) + } + + assertThat(rows).isEqualTo(1) + + val newRecord = mapper.selectByPrimaryKey(100) + assertThat(newRecord?.occupation).isEqualTo("Programmer") + } + } + + @Test + fun testCount1() { + newSession().use { session -> + val mapper = session.getMapper(PersonMapper::class.java) + + val rows = mapper.count { + where(occupation, isNull()) { + and(employed, isFalse()) + } + } + + assertThat(rows).isEqualTo(2L) + } + } + + @Test + fun testCount2() { + newSession().use { session -> + val mapper = session.getMapper(PersonMapper::class.java) + + val rows = mapper.count { + where(employed, isTrue()) + and(occupation, isEqualTo("Brontosaurus Operator")) + } + + assertThat(rows).isEqualTo(2L) + } + } + + @Test + fun testCount3() { + newSession().use { session -> + val mapper = session.getMapper(PersonMapper::class.java) + + val rows = mapper.count { + where(id, isEqualTo(1)) + or(id, isEqualTo(2)) + } + + assertThat(rows).isEqualTo(2L) + } + } + + @Test + fun testCount4() { + newSession().use { session -> + val mapper = session.getMapper(PersonMapper::class.java) + + val rows = mapper.count { + where(id, isEqualTo(1)) + or(id, isEqualTo(2)) { + or(id, isEqualTo(3)) + } + } + + assertThat(rows).isEqualTo(3L) + } + } + + @Test + fun testCount5() { + newSession().use { session -> + val mapper = session.getMapper(PersonMapper::class.java) + + val rows = mapper.count { + where(id, isLessThan(5)) + and(id, isLessThan(3)) { + and(id, isEqualTo(1)) + } + } + + assertThat(rows).isEqualTo(1L) + } + } + + @Test + fun testCountAll() { + newSession().use { session -> + val mapper = session.getMapper(PersonMapper::class.java) + + val rows = mapper.count { allRows() } + + assertThat(rows).isEqualTo(6L) + } + } + + @Test + fun testTypeHandledLike() { + newSession().use { session -> + val mapper = session.getMapper(PersonMapper::class.java) + + val rows = mapper.select { + where(lastName, isLike(LastName("Fl%"))) + orderBy(id) + } + + assertThat(rows.size).isEqualTo(3) + assertThat(rows[0].firstName).isEqualTo("Fred") + } + } + + @Test + fun testTypeHandledNotLike() { + newSession().use { session -> + val mapper = session.getMapper(PersonMapper::class.java) + + val rows = mapper.select { + where(lastName, isNotLike(LastName("Fl%"))) + orderBy(id) + } + + assertThat(rows.size).isEqualTo(3) + assertThat(rows[0].firstName).isEqualTo("Barney") + } + } + + @Test + fun testJoinAllRows() { + newSession().use { session -> + val mapper = session.getMapper(PersonWithAddressMapper::class.java) + + val records = mapper.select { + allRows() + orderBy(id) + } + + assertThat(records.size).isEqualTo(6L) + with(records[0]) { + assertThat(id).isEqualTo(1) + assertThat(employed).isTrue() + assertThat(firstName).isEqualTo("Fred") + assertThat(lastName).isEqualTo(LastName("Flintstone")) + assertThat(occupation).isEqualTo("Brontosaurus Operator") + assertThat(birthDate).isNotNull() + assertThat(address?.id).isEqualTo(1) + assertThat(address?.streetAddress).isEqualTo("123 Main Street") + assertThat(address?.city).isEqualTo("Bedrock") + assertThat(address?.state).isEqualTo("IN") + } + } + } + + @Test + fun testJoinOneRow() { + newSession().use { session -> + val mapper = session.getMapper(PersonWithAddressMapper::class.java) + + val records = mapper.select { + where(id, isEqualTo(1)) + } + + assertThat(records.size).isEqualTo(1L) + with(records[0]) { + assertThat(id).isEqualTo(1) + assertThat(employed).isTrue() + assertThat(firstName).isEqualTo("Fred") + assertThat(lastName).isEqualTo(LastName("Flintstone")) + assertThat(occupation).isEqualTo("Brontosaurus Operator") + assertThat(birthDate).isNotNull() + assertThat(address?.id).isEqualTo(1) + assertThat(address?.streetAddress).isEqualTo("123 Main Street") + assertThat(address?.city).isEqualTo("Bedrock") + assertThat(address?.state).isEqualTo("IN") + } + } + } + + @Test + fun testJoinPrimaryKey() { + newSession().use { session -> + val mapper = session.getMapper(PersonWithAddressMapper::class.java) + + val record = mapper.selectByPrimaryKey(1) + + assertThat(record).isNotNull() + with(record!!) { + assertThat(id).isEqualTo(1) + assertThat(employed).isTrue() + assertThat(firstName).isEqualTo("Fred") + assertThat(lastName).isEqualTo(LastName("Flintstone")) + assertThat(occupation).isEqualTo("Brontosaurus Operator") + assertThat(birthDate).isNotNull() + assertThat(address?.id).isEqualTo(1) + assertThat(address?.streetAddress).isEqualTo("123 Main Street") + assertThat(address?.city).isEqualTo("Bedrock") + assertThat(address?.state).isEqualTo("IN") + } + } + } + + @Test + fun testJoinPrimaryKeyInvalidRecord() { + newSession().use { session -> + val mapper = session.getMapper(PersonWithAddressMapper::class.java) + + val record = mapper.selectByPrimaryKey(55) + + assertThat(record).isNull() + } + } + + companion object { + const val JDBC_URL = "jdbc:hsqldb:mem:aname" + const val JDBC_DRIVER = "org.hsqldb.jdbcDriver" + } +} diff --git a/src/test/kotlin/examples/kotlin/canonical/PersonRecord.kt b/src/test/kotlin/examples/kotlin/canonical/PersonRecord.kt new file mode 100644 index 000000000..d0c8733a2 --- /dev/null +++ b/src/test/kotlin/examples/kotlin/canonical/PersonRecord.kt @@ -0,0 +1,23 @@ +/** + * Copyright 2016-2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package examples.kotlin.canonical + +import java.util.* + +data class PersonRecord(var id: Int? = null, var firstName: String? = null, var lastName: LastName? = null, + var birthDate: Date? = null, var employed: Boolean? = null, var occupation: String? = null, + var addressId: Int? = null) + diff --git a/src/test/kotlin/examples/kotlin/canonical/PersonWithAddress.kt b/src/test/kotlin/examples/kotlin/canonical/PersonWithAddress.kt new file mode 100644 index 000000000..9511863c9 --- /dev/null +++ b/src/test/kotlin/examples/kotlin/canonical/PersonWithAddress.kt @@ -0,0 +1,22 @@ +/** + * Copyright 2016-2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package examples.kotlin.canonical + +import java.util.* + +data class PersonWithAddress(var id: Int? = null, var firstName: String? = null, var lastName: LastName? = null, + var birthDate: Date? = null, var employed: Boolean? = null, var occupation: String? = null, + var address: AddressRecord? = null) diff --git a/src/test/kotlin/examples/kotlin/canonical/PersonWithAddressMapper.kt b/src/test/kotlin/examples/kotlin/canonical/PersonWithAddressMapper.kt new file mode 100644 index 000000000..575deddf4 --- /dev/null +++ b/src/test/kotlin/examples/kotlin/canonical/PersonWithAddressMapper.kt @@ -0,0 +1,51 @@ +/** + * Copyright 2016-2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package examples.kotlin.canonical + +import org.apache.ibatis.annotations.* +import org.apache.ibatis.type.JdbcType +import org.mybatis.dynamic.sql.select.render.SelectStatementProvider +import org.mybatis.dynamic.sql.util.SqlProviderAdapter + +/** + * + * This is a mapper that shows coding a join + * + */ +@Mapper +interface PersonWithAddressMapper { + + @SelectProvider(type = SqlProviderAdapter::class, method = "select") + @Results(id = "PersonWithAddressResult", value = [ + Result(column = "A_ID", property = "id", jdbcType = JdbcType.INTEGER, id = true), + Result(column = "first_name", property = "firstName", jdbcType = JdbcType.VARCHAR), + Result(column = "last_name", property = "lastName", jdbcType = JdbcType.VARCHAR, + typeHandler = LastNameTypeHandler::class), + Result(column = "birth_date", property = "birthDate", jdbcType = JdbcType.DATE), + Result(column = "employed", property = "employed", jdbcType = JdbcType.VARCHAR, + typeHandler = YesNoTypeHandler::class), + Result(column = "occupation", property = "occupation", jdbcType = JdbcType.VARCHAR), + Result(column = "address_id", property = "address.id", jdbcType = JdbcType.INTEGER), + Result(column = "street_address", property = "address.streetAddress", jdbcType = JdbcType.VARCHAR), + Result(column = "city", property = "address.city", jdbcType = JdbcType.VARCHAR), + Result(column = "state", property = "address.state", jdbcType = JdbcType.CHAR) + ]) + fun selectMany(selectStatement: SelectStatementProvider): List + + @SelectProvider(type = SqlProviderAdapter::class, method = "select") + @ResultMap("PersonWithAddressResult") + fun selectOne(selectStatement: SelectStatementProvider): PersonWithAddress? +} diff --git a/src/test/kotlin/examples/kotlin/canonical/PersonWithAddressMapperExtensions.kt b/src/test/kotlin/examples/kotlin/canonical/PersonWithAddressMapperExtensions.kt new file mode 100644 index 000000000..67b4a743d --- /dev/null +++ b/src/test/kotlin/examples/kotlin/canonical/PersonWithAddressMapperExtensions.kt @@ -0,0 +1,57 @@ +/** + * Copyright 2016-2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package examples.kotlin.canonical + +import examples.kotlin.canonical.AddressDynamicSqlSupport.Address +import examples.kotlin.canonical.PersonDynamicSqlSupport.Person +import examples.kotlin.canonical.PersonDynamicSqlSupport.Person.birthDate +import examples.kotlin.canonical.PersonDynamicSqlSupport.Person.employed +import examples.kotlin.canonical.PersonDynamicSqlSupport.Person.firstName +import examples.kotlin.canonical.PersonDynamicSqlSupport.Person.id +import examples.kotlin.canonical.PersonDynamicSqlSupport.Person.lastName +import examples.kotlin.canonical.PersonDynamicSqlSupport.Person.occupation +import org.mybatis.dynamic.sql.SqlBuilder.* +import org.mybatis.dynamic.sql.util.kotlin.SelectCompleter +import org.mybatis.dynamic.sql.util.kotlin.fromJoining +import org.mybatis.dynamic.sql.util.kotlin.fullJoin +import org.mybatis.dynamic.sql.util.mybatis3.MyBatis3Utils + +private val selectList = arrayOf(id.`as`("A_ID"), firstName, lastName, birthDate, employed, occupation, Address.id, + Address.streetAddress, Address.city, Address.state) + +fun PersonWithAddressMapper.selectOne(completer: SelectCompleter): PersonWithAddress? { + val start = select(*selectList).fromJoining(Person) { + fullJoin(Address) { + on(Person.addressId, equalTo(Address.id)) + } + } + + return MyBatis3Utils.selectOne(this::selectOne, start, completer) +} + +fun PersonWithAddressMapper.select(completer: SelectCompleter): List { + val start = select(*selectList).fromJoining(Person, "p") { + fullJoin(Address) { + on(Person.addressId, equalTo(Address.id)) + } + } + return MyBatis3Utils.selectList(this::selectMany, start, completer) +} + +fun PersonWithAddressMapper.selectByPrimaryKey(id_: Int) = + selectOne { + where(id, isEqualTo(id_)) + } diff --git a/src/test/kotlin/examples/kotlin/canonical/YesNoTypeHandler.kt b/src/test/kotlin/examples/kotlin/canonical/YesNoTypeHandler.kt new file mode 100644 index 000000000..762c02740 --- /dev/null +++ b/src/test/kotlin/examples/kotlin/canonical/YesNoTypeHandler.kt @@ -0,0 +1,37 @@ +/** + * Copyright 2016-2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package examples.kotlin.canonical + +import org.apache.ibatis.type.JdbcType +import org.apache.ibatis.type.TypeHandler +import java.sql.CallableStatement +import java.sql.PreparedStatement +import java.sql.ResultSet + +class YesNoTypeHandler : TypeHandler { + + override fun setParameter(ps: PreparedStatement, i: Int, parameter: Boolean, jdbcType: JdbcType) = + ps.setString(i, if (parameter) "Yes" else "No") + + override fun getResult(rs: ResultSet, columnName: String) = + "Yes" == rs.getString(columnName) + + override fun getResult(rs: ResultSet, columnIndex: Int) = + "Yes" == rs.getString(columnIndex) + + override fun getResult(cs: CallableStatement, columnIndex: Int) = + "Yes" == cs.getString(columnIndex) +} diff --git a/src/test/kotlin/examples/kotlin/general/GeneralKotlinTest.kt b/src/test/kotlin/examples/kotlin/general/GeneralKotlinTest.kt new file mode 100644 index 000000000..000af4032 --- /dev/null +++ b/src/test/kotlin/examples/kotlin/general/GeneralKotlinTest.kt @@ -0,0 +1,552 @@ +/** + * Copyright 2016-2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package examples.kotlin.general + +import examples.kotlin.canonical.* +import examples.kotlin.canonical.AddressDynamicSqlSupport.Address +import examples.kotlin.canonical.PersonDynamicSqlSupport.Person +import examples.kotlin.canonical.PersonDynamicSqlSupport.Person.addressId +import examples.kotlin.canonical.PersonDynamicSqlSupport.Person.birthDate +import examples.kotlin.canonical.PersonDynamicSqlSupport.Person.employed +import examples.kotlin.canonical.PersonDynamicSqlSupport.Person.firstName +import examples.kotlin.canonical.PersonDynamicSqlSupport.Person.id +import examples.kotlin.canonical.PersonDynamicSqlSupport.Person.lastName +import examples.kotlin.canonical.PersonDynamicSqlSupport.Person.occupation +import org.apache.ibatis.datasource.unpooled.UnpooledDataSource +import org.apache.ibatis.jdbc.ScriptRunner +import org.apache.ibatis.mapping.Environment +import org.apache.ibatis.session.Configuration +import org.apache.ibatis.session.SqlSession +import org.apache.ibatis.session.SqlSessionFactoryBuilder +import org.apache.ibatis.transaction.jdbc.JdbcTransactionFactory +import org.assertj.core.api.Assertions.assertThat +import org.junit.jupiter.api.Test +import org.mybatis.dynamic.sql.SqlBuilder.* +import org.mybatis.dynamic.sql.util.kotlin.* +import org.mybatis.dynamic.sql.util.kotlin.mybatis3.deleteFrom +import org.mybatis.dynamic.sql.util.kotlin.mybatis3.from +import org.mybatis.dynamic.sql.util.kotlin.mybatis3.update +import java.io.InputStreamReader +import java.sql.DriverManager + +class GeneralKotlinTest { + private fun newSession(): SqlSession { + Class.forName(PersonMapperTest.JDBC_DRIVER) + val script = javaClass.getResourceAsStream("/examples/kotlin/CreateSimpleDB.sql") + DriverManager.getConnection(PersonMapperTest.JDBC_URL, "sa", "").use { connection -> + val sr = ScriptRunner(connection) + sr.setLogWriter(null) + sr.runScript(InputStreamReader(script)) + } + + val ds = UnpooledDataSource(PersonMapperTest.JDBC_DRIVER, PersonMapperTest.JDBC_URL, "sa", "") + val environment = Environment("test", JdbcTransactionFactory(), ds) + val config = Configuration(environment) + config.typeHandlerRegistry.register(YesNoTypeHandler::class.java) + config.addMapper(PersonMapper::class.java) + config.addMapper(PersonWithAddressMapper::class.java) + return SqlSessionFactoryBuilder().build(config).openSession() + } + + @Test + fun testRawDelete1() { + newSession().use { session -> + val mapper = session.getMapper(PersonMapper::class.java) + + val deleteStatement = deleteFrom(Person) { + where(id, isLessThan(4)) + } + + assertThat(deleteStatement.deleteStatement).isEqualTo("delete from Person" + + " where id < #{parameters.p1,jdbcType=INTEGER}") + + val rows = mapper.delete(deleteStatement) + + assertThat(rows).isEqualTo(3) + } + } + + @Test + fun testRawDelete2() { + newSession().use { session -> + val mapper = session.getMapper(PersonMapper::class.java) + + val deleteStatement = deleteFrom(Person) { + where(id, isLessThan(4)) + and(occupation, isNotNull()) + } + + assertThat(deleteStatement.deleteStatement).isEqualTo("delete from Person" + + " where id < #{parameters.p1,jdbcType=INTEGER} and occupation is not null") + + val rows = mapper.delete(deleteStatement) + + assertThat(rows).isEqualTo(2) + } + } + + @Test + fun testRawDelete3() { + newSession().use { session -> + val mapper = session.getMapper(PersonMapper::class.java) + + val deleteStatement = deleteFrom(Person) { + where(id, isLessThan(4)) + or(occupation, isNotNull()) + } + + assertThat(deleteStatement.deleteStatement).isEqualTo("delete from Person" + + " where id < #{parameters.p1,jdbcType=INTEGER} or occupation is not null") + + val rows = mapper.delete(deleteStatement) + + assertThat(rows).isEqualTo(5) + } + } + + @Test + fun testRawDelete4() { + newSession().use { session -> + val mapper = session.getMapper(PersonMapper::class.java) + + val deleteStatement = deleteFrom(Person) { + where(id, isLessThan(4)) { + or(occupation, isNotNull()) + } + and(employed, isEqualTo(true)) + } + + val expected = "delete from Person" + + " where (id < #{parameters.p1,jdbcType=INTEGER} or occupation is not null)" + + " and employed =" + + " #{parameters.p2,jdbcType=VARCHAR,typeHandler=examples.kotlin.canonical.YesNoTypeHandler}" + + assertThat(deleteStatement.deleteStatement).isEqualTo(expected) + + val rows = mapper.delete(deleteStatement) + + assertThat(rows).isEqualTo(4) + } + } + + @Test + fun testRawDelete5() { + newSession().use { session -> + val mapper = session.getMapper(PersonMapper::class.java) + + val deleteStatement = deleteFrom(Person) { + where(id, isLessThan(4)) + or(occupation, isNotNull()) { + and(employed, isEqualTo(true)) + } + } + + val expected = "delete from Person" + + " where id < #{parameters.p1,jdbcType=INTEGER} or (occupation is not null" + + " and employed =" + + " #{parameters.p2,jdbcType=VARCHAR,typeHandler=examples.kotlin.canonical.YesNoTypeHandler})" + + assertThat(deleteStatement.deleteStatement).isEqualTo(expected) + + val rows = mapper.delete(deleteStatement) + + assertThat(rows).isEqualTo(5) + } + } + + @Test + fun testRawDelete6() { + newSession().use { session -> + val mapper = session.getMapper(PersonMapper::class.java) + + val deleteStatement = deleteFrom(Person) { + where(id, isLessThan(4)) + and(occupation, isNotNull()) { + and(employed, isEqualTo(true)) + } + } + + val expected = "delete from Person where id < #{parameters.p1,jdbcType=INTEGER}" + + " and (occupation is not null and" + + " employed =" + + " #{parameters.p2,jdbcType=VARCHAR,typeHandler=examples.kotlin.canonical.YesNoTypeHandler})" + + assertThat(deleteStatement.deleteStatement).isEqualTo(expected) + + val rows = mapper.delete(deleteStatement) + + assertThat(rows).isEqualTo(2) + } + } + + @Test + fun testRawSelect() { + newSession().use { session -> + val mapper = session.getMapper(PersonMapper::class.java) + + val selectStatement = select(id.`as`("A_ID"), firstName, lastName, birthDate, employed, occupation, + addressId).from(Person) { + where(id, isLessThan(4)) { + and(occupation, isNotNull()) + } + and(occupation, isNotNull()) + orderBy(id) + limit(3) + } + + val rows = mapper.selectMany(selectStatement) + + assertThat(rows.size).isEqualTo(2) + with(rows[0]) { + assertThat(id).isEqualTo(1) + assertThat(firstName).isEqualTo("Fred") + assertThat(lastName?.name).isEqualTo("Flintstone") + assertThat(birthDate).isNotNull() + assertThat(employed).isTrue() + assertThat(occupation).isEqualTo("Brontosaurus Operator") + assertThat(addressId).isEqualTo(1) + } + } + } + + @Test + fun testRawSelectWithJoin() { + newSession().use { session -> + val mapper = session.getMapper(PersonWithAddressMapper::class.java) + + val selectStatement = select(id.`as`("A_ID"), firstName, lastName, birthDate, employed, occupation, + Address.id, Address.streetAddress, Address.city, Address.state) + .from(Person) { + join(Address) { + on(addressId, equalTo(Address.id)) + } + where(id, isLessThan(4)) + orderBy(id) + limit(3) + } + + val rows = mapper.selectMany(selectStatement) + + assertThat(rows.size).isEqualTo(3) + with(rows[0]) { + assertThat(id).isEqualTo(1) + assertThat(firstName).isEqualTo("Fred") + assertThat(lastName?.name).isEqualTo("Flintstone") + assertThat(birthDate).isNotNull() + assertThat(employed).isTrue() + assertThat(occupation).isEqualTo("Brontosaurus Operator") + assertThat(address?.id).isEqualTo(1) + assertThat(address?.streetAddress).isEqualTo("123 Main Street") + assertThat(address?.city).isEqualTo("Bedrock") + assertThat(address?.state).isEqualTo("IN") + } + } + } + + @Test + fun testRawSelectWithJoinAndComplexWhere1() { + newSession().use { session -> + val mapper = session.getMapper(PersonWithAddressMapper::class.java) + + val selectStatement = select(id.`as`("A_ID"), firstName, lastName, birthDate, employed, occupation, + Address.id, Address.streetAddress, Address.city, Address.state) + .from(Person) { + join(Address) { + on(addressId, equalTo(Address.id)) + } + where(id, isLessThan(5)) + and(id, isLessThan(4)) { + and(id, isLessThan(3)) { + and(id, isLessThan(2)) + } + } + } + + val rows = mapper.selectMany(selectStatement) + + assertThat(rows.size).isEqualTo(1) + with(rows[0]) { + assertThat(id).isEqualTo(1) + assertThat(firstName).isEqualTo("Fred") + assertThat(lastName?.name).isEqualTo("Flintstone") + assertThat(birthDate).isNotNull() + assertThat(employed).isTrue() + assertThat(occupation).isEqualTo("Brontosaurus Operator") + assertThat(address?.id).isEqualTo(1) + assertThat(address?.streetAddress).isEqualTo("123 Main Street") + assertThat(address?.city).isEqualTo("Bedrock") + assertThat(address?.state).isEqualTo("IN") + } + } + } + + @Test + fun testRawSelectWithJoinAndComplexWhere2() { + newSession().use { session -> + val mapper = session.getMapper(PersonWithAddressMapper::class.java) + + val selectStatement = select(id.`as`("A_ID"), firstName, lastName, birthDate, employed, occupation, + Address.id, Address.streetAddress, Address.city, Address.state).from(Person) { + join(Address) { + on(addressId, equalTo(Address.id)) + } + where(id, isEqualTo(5)) + or(id, isEqualTo(4)) { + or(id, isEqualTo(3)) { + or(id, isEqualTo(2)) + } + } + orderBy(id) + limit(3) + } + + val rows = mapper.selectMany(selectStatement) + + assertThat(rows.size).isEqualTo(3) + with(rows[2]) { + assertThat(id).isEqualTo(4) + assertThat(firstName).isEqualTo("Barney") + assertThat(lastName?.name).isEqualTo("Rubble") + assertThat(birthDate).isNotNull() + assertThat(employed).isTrue() + assertThat(occupation).isEqualTo("Brontosaurus Operator") + assertThat(address?.id).isEqualTo(2) + assertThat(address?.streetAddress).isEqualTo("456 Main Street") + assertThat(address?.city).isEqualTo("Bedrock") + assertThat(address?.state).isEqualTo("IN") + } + } + } + + @Test + fun testRawSelectWithComplexWhere1() { + newSession().use { session -> + val mapper = session.getMapper(PersonMapper::class.java) + + val selectStatement = select(id.`as`("A_ID"), firstName, lastName, birthDate, employed, occupation, + addressId).from(Person) { + where(id, isLessThan(5)) + and(id, isLessThan(4)) { + and(id, isLessThan(3)) { + and(id, isLessThan(2)) + } + } + orderBy(id) + limit(3) + } + + val expected = "select id as A_ID, first_name, last_name, birth_date, employed, occupation, address_id" + + " from Person" + + " where id < #{parameters.p1,jdbcType=INTEGER}" + + " and (id < #{parameters.p2,jdbcType=INTEGER}" + + " and (id < #{parameters.p3,jdbcType=INTEGER} and id < #{parameters.p4,jdbcType=INTEGER}))" + + " order by id limit #{parameters._limit}" + + assertThat(selectStatement.selectStatement).isEqualTo(expected) + + val rows = mapper.selectMany(selectStatement) + + assertThat(rows.size).isEqualTo(1) + with(rows[0]) { + assertThat(id).isEqualTo(1) + assertThat(firstName).isEqualTo("Fred") + assertThat(lastName?.name).isEqualTo("Flintstone") + assertThat(birthDate).isNotNull() + assertThat(employed).isTrue() + assertThat(occupation).isEqualTo("Brontosaurus Operator") + assertThat(addressId).isEqualTo(1) + } + } + } + + @Test + fun testRawSelectWithComplexWhere2() { + newSession().use { session -> + val mapper = session.getMapper(PersonMapper::class.java) + + val selectStatement = select(id.`as`("A_ID"), firstName, lastName, birthDate, employed, occupation, + addressId).from(Person) { + where(id, isEqualTo(5)) + or(id, isEqualTo(4)) { + or(id, isEqualTo(3)) { + or(id, isEqualTo(2)) + } + } + orderBy(id) + limit(3) + } + + val expected = "select id as A_ID, first_name, last_name, birth_date, employed, occupation, address_id" + + " from Person" + + " where id = #{parameters.p1,jdbcType=INTEGER}" + + " or (id = #{parameters.p2,jdbcType=INTEGER}" + + " or (id = #{parameters.p3,jdbcType=INTEGER} or id = #{parameters.p4,jdbcType=INTEGER}))" + + " order by id limit #{parameters._limit}" + + assertThat(selectStatement.selectStatement).isEqualTo(expected) + + val rows = mapper.selectMany(selectStatement) + + assertThat(rows.size).isEqualTo(3) + with(rows[2]) { + assertThat(id).isEqualTo(4) + assertThat(firstName).isEqualTo("Barney") + assertThat(lastName?.name).isEqualTo("Rubble") + assertThat(birthDate).isNotNull() + assertThat(employed).isTrue() + assertThat(occupation).isEqualTo("Brontosaurus Operator") + assertThat(addressId).isEqualTo(2) + } + } + } + + @Test + fun testSelect() { + newSession().use { session -> + val mapper = session.getMapper(PersonMapper::class.java) + + val rows = mapper.select { + allRows() + orderBy(id) + limit(3) + offset(2) + } + + assertThat(rows.size).isEqualTo(3) + with(rows[0]) { + assertThat(id).isEqualTo(3) + assertThat(firstName).isEqualTo("Pebbles") + assertThat(lastName?.name).isEqualTo("Flintstone") + assertThat(birthDate).isNotNull() + assertThat(employed).isFalse() + assertThat(occupation).isNull() + assertThat(addressId).isEqualTo(1) + } + } + } + + @Test + fun testRawUpdate1() { + newSession().use { session -> + val mapper = session.getMapper(PersonMapper::class.java) + + val updateStatement = update(Person) { + set(firstName).equalTo("Sam") + where(firstName, isEqualTo("Fred")) + } + + assertThat(updateStatement.updateStatement).isEqualTo("update Person" + + " set first_name = #{parameters.p1,jdbcType=VARCHAR}" + + " where first_name = #{parameters.p2,jdbcType=VARCHAR}") + + val rows = mapper.update(updateStatement) + + assertThat(rows).isEqualTo(1) + } + } + + @Test + fun testRawUpdate2() { + newSession().use { session -> + val mapper = session.getMapper(PersonMapper::class.java) + + val updateStatement = update(Person) { + set(firstName).equalTo("Sam") + where(firstName, isEqualTo("Fred")) { + or(id, isGreaterThan(3)) + } + } + + assertThat(updateStatement.updateStatement).isEqualTo("update Person" + + " set first_name = #{parameters.p1,jdbcType=VARCHAR}" + + " where (first_name = #{parameters.p2,jdbcType=VARCHAR} or id > #{parameters.p3,jdbcType=INTEGER})") + + val rows = mapper.update(updateStatement) + + assertThat(rows).isEqualTo(4) + } + } + + @Test + fun testRawUpdate3() { + newSession().use { session -> + val mapper = session.getMapper(PersonMapper::class.java) + + val updateStatement = update(Person) { + set(firstName).equalTo("Sam") + where(firstName, isEqualTo("Fred")) + or(id, isEqualTo(5)) { + or(id, isEqualTo(6)) + } + } + + assertThat(updateStatement.updateStatement).isEqualTo("update Person" + + " set first_name = #{parameters.p1,jdbcType=VARCHAR}" + + " where first_name = #{parameters.p2,jdbcType=VARCHAR}" + + " or (id = #{parameters.p3,jdbcType=INTEGER} or id = #{parameters.p4,jdbcType=INTEGER})") + + val rows = mapper.update(updateStatement) + + assertThat(rows).isEqualTo(3) + } + } + + @Test + fun testRawUpdate4() { + newSession().use { session -> + val mapper = session.getMapper(PersonMapper::class.java) + + val updateStatement = update(Person) { + set(firstName).equalTo("Sam") + where(firstName, isEqualTo("Fred")) + and(id, isEqualTo(1)) { + or(id, isEqualTo(6)) + } + } + + assertThat(updateStatement.updateStatement).isEqualTo("update Person" + + " set first_name = #{parameters.p1,jdbcType=VARCHAR}" + + " where first_name = #{parameters.p2,jdbcType=VARCHAR}" + + " and (id = #{parameters.p3,jdbcType=INTEGER} or id = #{parameters.p4,jdbcType=INTEGER})") + + val rows = mapper.update(updateStatement) + + assertThat(rows).isEqualTo(1) + } + } + + @Test + fun testRawUpdate5() { + newSession().use { session -> + val mapper = session.getMapper(PersonMapper::class.java) + + val updateStatement = update(Person) { + set(firstName).equalTo("Sam") + where(firstName, isEqualTo("Fred")) + or(id, isEqualTo(3)) + } + + assertThat(updateStatement.updateStatement).isEqualTo("update Person" + + " set first_name = #{parameters.p1,jdbcType=VARCHAR}" + + " where first_name = #{parameters.p2,jdbcType=VARCHAR}" + + " or id = #{parameters.p3,jdbcType=INTEGER}") + + val rows = mapper.update(updateStatement) + + assertThat(rows).isEqualTo(2) + } + } +} diff --git a/src/test/kotlin/examples/kotlin/joins/Domain.kt b/src/test/kotlin/examples/kotlin/joins/Domain.kt new file mode 100644 index 000000000..2144390d6 --- /dev/null +++ b/src/test/kotlin/examples/kotlin/joins/Domain.kt @@ -0,0 +1,25 @@ +/** + * Copyright 2016-2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package examples.kotlin.joins + +import java.util.* + +data class OrderDetail(var orderId: Int? = null, var lineNumber: Int? = null, var description: String? = null, + var quantity: Int? = null) + +data class User(var userId: Int? = null, var userName: String? = null, var parentId: Int? = null) + +data class OrderMaster(var id: Int? = null, var orderDate: Date? = null, var details: List? = null) diff --git a/src/test/kotlin/examples/kotlin/joins/ItemMasterDynamicSQLSupport.kt b/src/test/kotlin/examples/kotlin/joins/ItemMasterDynamicSQLSupport.kt new file mode 100644 index 000000000..28abba9f1 --- /dev/null +++ b/src/test/kotlin/examples/kotlin/joins/ItemMasterDynamicSQLSupport.kt @@ -0,0 +1,27 @@ +/** + * Copyright 2016-2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package examples.kotlin.joins + +import org.mybatis.dynamic.sql.SqlTable +import java.sql.JDBCType +import java.util.* + +object ItemMasterDynamicSQLSupport { + object ItemMaster : SqlTable("ItemMaster") { + val itemId = column("item_id", JDBCType.INTEGER) + val description = column("description", JDBCType.DATE) + } +} diff --git a/src/test/kotlin/examples/kotlin/joins/JoinMapper.kt b/src/test/kotlin/examples/kotlin/joins/JoinMapper.kt new file mode 100644 index 000000000..64e68c5fd --- /dev/null +++ b/src/test/kotlin/examples/kotlin/joins/JoinMapper.kt @@ -0,0 +1,30 @@ +/** + * Copyright 2016-2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package examples.kotlin.joins + +import org.apache.ibatis.annotations.ResultMap +import org.apache.ibatis.annotations.SelectProvider +import org.mybatis.dynamic.sql.select.render.SelectStatementProvider +import org.mybatis.dynamic.sql.util.SqlProviderAdapter + +interface JoinMapper { + @SelectProvider(type = SqlProviderAdapter::class, method = "select") + @ResultMap("SimpleJoinResult") + fun selectMany(selectStatement: SelectStatementProvider): List + + @SelectProvider(type = SqlProviderAdapter::class, method = "select") + fun generalSelect(selectStatement: SelectStatementProvider): List> +} diff --git a/src/test/kotlin/examples/kotlin/joins/JoinMapperTest.kt b/src/test/kotlin/examples/kotlin/joins/JoinMapperTest.kt new file mode 100644 index 000000000..1e47dbcc2 --- /dev/null +++ b/src/test/kotlin/examples/kotlin/joins/JoinMapperTest.kt @@ -0,0 +1,437 @@ +/** + * Copyright 2016-2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package examples.kotlin.joins + +import examples.kotlin.joins.ItemMasterDynamicSQLSupport.ItemMaster +import examples.kotlin.joins.OrderDetailDynamicSQLSupport.OrderDetail +import examples.kotlin.joins.OrderLineDynamicSQLSupport.OrderLine +import examples.kotlin.joins.OrderMasterDynamicSQLSupport.OrderMaster +import org.apache.ibatis.datasource.unpooled.UnpooledDataSource +import org.apache.ibatis.jdbc.ScriptRunner +import org.apache.ibatis.mapping.Environment +import org.apache.ibatis.session.Configuration +import org.apache.ibatis.session.SqlSession +import org.apache.ibatis.session.SqlSessionFactoryBuilder +import org.apache.ibatis.transaction.jdbc.JdbcTransactionFactory +import org.assertj.core.api.Assertions.assertThat +import org.junit.jupiter.api.Test +import org.mybatis.dynamic.sql.SqlBuilder.* +import org.mybatis.dynamic.sql.util.kotlin.fullJoin +import org.mybatis.dynamic.sql.util.kotlin.join +import org.mybatis.dynamic.sql.util.kotlin.leftJoin +import org.mybatis.dynamic.sql.util.kotlin.mybatis3.from +import org.mybatis.dynamic.sql.util.kotlin.rightJoin +import java.io.InputStreamReader +import java.sql.DriverManager + +class JoinMapperTest { + + private fun newSession(): SqlSession { + Class.forName(JDBC_DRIVER) + val script = javaClass.getResourceAsStream("/examples/kotlin/joins/CreateJoinDB.sql") + + DriverManager.getConnection(JDBC_URL, "sa", "").use { connection -> + val sr = ScriptRunner(connection) + sr.setLogWriter(null) + sr.runScript(InputStreamReader(script)) + } + + val ds = UnpooledDataSource(JDBC_DRIVER, JDBC_URL, "sa", "") + val environment = Environment("test", JdbcTransactionFactory(), ds) + val config = Configuration(environment) + config.addMapper(JoinMapper::class.java) + return SqlSessionFactoryBuilder().build(config).openSession() + } + + @Test + fun testSingleTableJoin() { + newSession().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)) + } + } + + 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" + assertThat(selectStatement.selectStatement).isEqualTo(expectedStatement) + + val rows = mapper.selectMany(selectStatement) + + assertThat(rows.size).isEqualTo(2) + + with(rows[0]) { + assertThat(id).isEqualTo(1) + assertThat(details?.size).isEqualTo(2) + assertThat(details?.get(0)?.lineNumber).isEqualTo(1) + assertThat(details?.get(1)?.lineNumber).isEqualTo(2) + } + + with(rows[1]) { + assertThat(id).isEqualTo(2) + assertThat(details?.size).isEqualTo(1) + assertThat(details?.get(0)?.lineNumber).isEqualTo(1) + } + } + } + + @Test + fun testCompoundJoin1() { + // this is a nonsensical join, but it does test the "and" capability + 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(OrderDetail.orderId)) + } + } + + 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 = od.order_id" + assertThat(selectStatement.selectStatement).isEqualTo(expectedStatement) + } + + @Test + fun testCompoundJoin2() { + // this is a nonsensical join, but it does test the "and" capability + 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(OrderDetail.orderId)) + } + where(OrderMaster.orderId, isEqualTo(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 = od.order_id" + + " where om.order_id = #{parameters.p1,jdbcType=INTEGER}" + assertThat(selectStatement.selectStatement).isEqualTo(expectedStatement) + } + + + @Test + fun testMultipleTableJoinWithWhereClause() { + newSession().use { session -> + val mapper = session.getMapper(JoinMapper::class.java) + + val selectStatement = select(OrderMaster.orderId, OrderMaster.orderDate, OrderLine.lineNumber, + ItemMaster.description, OrderLine.quantity).from(OrderMaster, "om") { + join(OrderLine, "ol") { + on(OrderMaster.orderId, equalTo(OrderLine.orderId)) + } + join(ItemMaster, "im") { + on(OrderLine.itemId, equalTo(ItemMaster.itemId)) + } + where(OrderMaster.orderId, isEqualTo(2)) + } + + val expectedStatement = "select om.order_id, om.order_date, ol.line_number, im.description, ol.quantity" + + " from OrderMaster om join OrderLine ol" + + " on om.order_id = ol.order_id join ItemMaster im on ol.item_id = im.item_id" + + " where om.order_id = #{parameters.p1,jdbcType=INTEGER}" + assertThat(selectStatement.selectStatement).isEqualTo(expectedStatement) + + val rows = mapper.selectMany(selectStatement) + + assertThat(rows.size).isEqualTo(1) + with(rows[0]) { + assertThat(id).isEqualTo(2) + assertThat(details?.size).isEqualTo(2) + assertThat(details?.get(0)?.lineNumber).isEqualTo(1) + assertThat(details?.get(1)?.lineNumber).isEqualTo(2) + } + } + } + + @Test + fun testFullJoinWithAliases() { + newSession().use { session -> + val mapper = session.getMapper(JoinMapper::class.java) + + val selectStatement = select(OrderLine.orderId, OrderLine.quantity, ItemMaster.itemId, + ItemMaster.description).from(OrderMaster, "om") { + join(OrderLine, "ol") { + on(OrderMaster.orderId, equalTo(OrderLine.orderId)) + } + fullJoin(ItemMaster, "im") { + on(OrderLine.itemId, equalTo(ItemMaster.itemId)) + } + orderBy(OrderLine.orderId, ItemMaster.itemId) + } + + val expectedStatement = "select ol.order_id, ol.quantity, im.item_id, im.description" + + " from OrderMaster om join OrderLine ol on om.order_id = ol.order_id" + + " full join ItemMaster im on ol.item_id = im.item_id" + + " order by order_id, item_id" + + assertThat(selectStatement.selectStatement).isEqualTo(expectedStatement) + + val rows = mapper.generalSelect(selectStatement) + + assertThat(rows.size).isEqualTo(6) + + with(rows[0]) { + assertThat(get("ORDER_ID")).isNull() + assertThat(get("QUANTITY")).isNull() + assertThat(get("DESCRIPTION")).isEqualTo("Catcher Glove") + assertThat(get("ITEM_ID")).isEqualTo(55) + } + + with(rows[3]) { + assertThat(get("ORDER_ID")).isEqualTo(2) + assertThat(get("QUANTITY")).isEqualTo(6) + assertThat(get("DESCRIPTION")).isNull() + assertThat(get("ITEM_ID")).isNull() + } + + with(rows[5]) { + assertThat(get("ORDER_ID")).isEqualTo(2) + assertThat(get("QUANTITY")).isEqualTo(1) + assertThat(get("DESCRIPTION")).isEqualTo("Outfield Glove") + assertThat(get("ITEM_ID")).isEqualTo(44) + } + } + } + + @Test + fun testFullJoinWithoutAliases() { + newSession().use { session -> + val mapper = session.getMapper(JoinMapper::class.java) + + val selectStatement = select(OrderLine.orderId, OrderLine.quantity, ItemMaster.itemId, + ItemMaster.description).from(OrderMaster, "om") { + join(OrderLine, "ol") { + on(OrderMaster.orderId, equalTo(OrderLine.orderId)) + } + fullJoin(ItemMaster) { + on(OrderLine.itemId, equalTo(ItemMaster.itemId)) + } + orderBy(OrderLine.orderId, ItemMaster.itemId) + } + + val expectedStatement = "select ol.order_id, ol.quantity, ItemMaster.item_id, ItemMaster.description" + + " from OrderMaster om join OrderLine ol on om.order_id = ol.order_id" + + " full join ItemMaster on ol.item_id = ItemMaster.item_id" + + " order by order_id, item_id" + + assertThat(selectStatement.selectStatement).isEqualTo(expectedStatement) + + val rows = mapper.generalSelect(selectStatement) + + assertThat(rows.size).isEqualTo(6) + + with(rows[0]) { + assertThat(get("ORDER_ID")).isNull() + assertThat(get("QUANTITY")).isNull() + assertThat(get("DESCRIPTION")).isEqualTo("Catcher Glove") + assertThat(get("ITEM_ID")).isEqualTo(55) + } + + with(rows[3]) { + assertThat(get("ORDER_ID")).isEqualTo(2) + assertThat(get("QUANTITY")).isEqualTo(6) + assertThat(get("DESCRIPTION")).isNull() + assertThat(get("ITEM_ID")).isNull() + } + + with(rows[5]) { + assertThat(get("ORDER_ID")).isEqualTo(2) + assertThat(get("QUANTITY")).isEqualTo(1) + assertThat(get("DESCRIPTION")).isEqualTo("Outfield Glove") + assertThat(get("ITEM_ID")).isEqualTo(44) + } + } + } + + @Test + fun testLeftJoinWithAliases() { + newSession().use { session -> + val mapper = session.getMapper(JoinMapper::class.java) + + val selectStatement = select(OrderLine.orderId, OrderLine.quantity, ItemMaster.itemId, + ItemMaster.description).from(OrderMaster, "om") { + join(OrderLine, "ol") { + on(OrderMaster.orderId, equalTo(OrderLine.orderId)) + } + leftJoin(ItemMaster, "im") { + on(OrderLine.itemId, equalTo(ItemMaster.itemId)) + } + orderBy(OrderLine.orderId, ItemMaster.itemId) + } + + val expectedStatement = "select ol.order_id, ol.quantity, im.item_id, im.description" + + " from OrderMaster om join OrderLine ol on om.order_id = ol.order_id" + + " left join ItemMaster im on ol.item_id = im.item_id" + + " order by order_id, item_id" + + assertThat(selectStatement.selectStatement).isEqualTo(expectedStatement) + + val rows = mapper.generalSelect(selectStatement) + + assertThat(rows.size).isEqualTo(5) + + with(rows[2]) { + assertThat(get("ORDER_ID")).isEqualTo(2) + assertThat(get("QUANTITY")).isEqualTo(6) + assertThat(get("DESCRIPTION")).isNull() + assertThat(get("ITEM_ID")).isNull() + } + + with(rows[4]) { + assertThat(get("ORDER_ID")).isEqualTo(2) + assertThat(get("QUANTITY")).isEqualTo(1) + assertThat(get("DESCRIPTION")).isEqualTo("Outfield Glove") + assertThat(get("ITEM_ID")).isEqualTo(44) + } + } + } + + @Test + fun testLeftJoinWithoutAliases() { + newSession().use { session -> + val mapper = session.getMapper(JoinMapper::class.java) + + val selectStatement = select(OrderLine.orderId, OrderLine.quantity, ItemMaster.itemId, + ItemMaster.description).from(OrderMaster, "om") { + join(OrderLine, "ol") { + on(OrderMaster.orderId, equalTo(OrderLine.orderId)) + } + leftJoin(ItemMaster) { + on(OrderLine.itemId, equalTo(ItemMaster.itemId)) + } + orderBy(OrderLine.orderId, ItemMaster.itemId) + } + + val expectedStatement = "select ol.order_id, ol.quantity, ItemMaster.item_id, ItemMaster.description" + + " from OrderMaster om join OrderLine ol on om.order_id = ol.order_id" + + " left join ItemMaster on ol.item_id = ItemMaster.item_id" + + " order by order_id, item_id" + + assertThat(selectStatement.selectStatement).isEqualTo(expectedStatement) + + val rows = mapper.generalSelect(selectStatement) + + assertThat(rows.size).isEqualTo(5) + + with(rows[2]) { + assertThat(get("ORDER_ID")).isEqualTo(2) + assertThat(get("QUANTITY")).isEqualTo(6) + assertThat(get("DESCRIPTION")).isNull() + assertThat(get("ITEM_ID")).isNull() + } + + with(rows[4]) { + assertThat(get("ORDER_ID")).isEqualTo(2) + assertThat(get("QUANTITY")).isEqualTo(1) + assertThat(get("DESCRIPTION")).isEqualTo("Outfield Glove") + assertThat(get("ITEM_ID")).isEqualTo(44) + } + } + } + + @Test + fun testRightJoinWithAliases() { + newSession().use { session -> + val mapper = session.getMapper(JoinMapper::class.java) + + val selectStatement = select(OrderLine.orderId, OrderLine.quantity, ItemMaster.itemId, + ItemMaster.description).from(OrderMaster, "om") { + join(OrderLine, "ol") { + on(OrderMaster.orderId, equalTo(OrderLine.orderId)) + } + rightJoin(ItemMaster, "im") { + on(OrderLine.itemId, equalTo(ItemMaster.itemId)) + } + orderBy(OrderLine.orderId, ItemMaster.itemId) + } + + val expectedStatement = "select ol.order_id, ol.quantity, im.item_id, im.description" + + " from OrderMaster om join OrderLine ol on om.order_id = ol.order_id" + + " right join ItemMaster im on ol.item_id = im.item_id" + + " order by order_id, item_id" + + assertThat(selectStatement.selectStatement).isEqualTo(expectedStatement) + + val rows = mapper.generalSelect(selectStatement) + + assertThat(rows.size).isEqualTo(5) + with(rows[0]) { + assertThat(get("ORDER_ID")).isNull() + assertThat(get("QUANTITY")).isNull() + assertThat(get("DESCRIPTION")).isEqualTo("Catcher Glove") + assertThat(get("ITEM_ID")).isEqualTo(55) + } + + with(rows[4]) { + assertThat(get("ORDER_ID")).isEqualTo(2) + assertThat(get("QUANTITY")).isEqualTo(1) + assertThat(get("DESCRIPTION")).isEqualTo("Outfield Glove") + assertThat(get("ITEM_ID")).isEqualTo(44) + } + } + } + + @Test + fun testRightJoinWithoutAliases() { + newSession().use { session -> + val mapper = session.getMapper(JoinMapper::class.java) + + val selectStatement = select(OrderLine.orderId, OrderLine.quantity, ItemMaster.itemId, + ItemMaster.description).from(OrderMaster, "om") { + join(OrderLine, "ol") { + on(OrderMaster.orderId, equalTo(OrderLine.orderId)) + } + rightJoin(ItemMaster) { + on(OrderLine.itemId, equalTo(ItemMaster.itemId)) + } + orderBy(OrderLine.orderId, ItemMaster.itemId) + } + + val expectedStatement = "select ol.order_id, ol.quantity, ItemMaster.item_id, ItemMaster.description" + + " from OrderMaster om join OrderLine ol on om.order_id = ol.order_id" + + " right join ItemMaster on ol.item_id = ItemMaster.item_id" + + " order by order_id, item_id" + + assertThat(selectStatement.selectStatement).isEqualTo(expectedStatement) + + val rows = mapper.generalSelect(selectStatement) + + assertThat(rows.size).isEqualTo(5) + with(rows[0]) { + assertThat(get("ORDER_ID")).isNull() + assertThat(get("QUANTITY")).isNull() + assertThat(get("DESCRIPTION")).isEqualTo("Catcher Glove") + assertThat(get("ITEM_ID")).isEqualTo(55) + } + + with(rows[4]) { + assertThat(get("ORDER_ID")).isEqualTo(2) + assertThat(get("QUANTITY")).isEqualTo(1) + assertThat(get("DESCRIPTION")).isEqualTo("Outfield Glove") + assertThat(get("ITEM_ID")).isEqualTo(44) + } + } + } + + companion object { + const val JDBC_URL = "jdbc:hsqldb:mem:aname" + const val JDBC_DRIVER = "org.hsqldb.jdbcDriver" + } +} diff --git a/src/main/java/org/mybatis/dynamic/sql/select/join/AndJoinCriterion.java b/src/test/kotlin/examples/kotlin/joins/OrderDetailDynamicSQLSupport.kt similarity index 55% rename from src/main/java/org/mybatis/dynamic/sql/select/join/AndJoinCriterion.java rename to src/test/kotlin/examples/kotlin/joins/OrderDetailDynamicSQLSupport.kt index a6733808c..00a2ae68c 100644 --- a/src/main/java/org/mybatis/dynamic/sql/select/join/AndJoinCriterion.java +++ b/src/test/kotlin/examples/kotlin/joins/OrderDetailDynamicSQLSupport.kt @@ -13,27 +13,17 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.mybatis.dynamic.sql.select.join; +package examples.kotlin.joins -public class AndJoinCriterion extends JoinCriterion { - - private AndJoinCriterion(Builder builder) { - super(builder); - } - - @Override - public String connector() { - return "and"; //$NON-NLS-1$ - } +import org.mybatis.dynamic.sql.SqlTable - public static class Builder extends AbstractBuilder { - @Override - protected Builder getThis() { - return this; - } +import java.sql.JDBCType - public AndJoinCriterion build() { - return new AndJoinCriterion(this); - } +object OrderDetailDynamicSQLSupport { + object OrderDetail : SqlTable("OrderDetail") { + val orderId = column("order_id", JDBCType.INTEGER) + val lineNumber = column("line_number", JDBCType.INTEGER) + val description = column("description", JDBCType.VARCHAR) + val quantity = column("quantity", JDBCType.INTEGER) } } diff --git a/src/test/kotlin/examples/kotlin/joins/OrderLineDynamicSQLSupport.kt b/src/test/kotlin/examples/kotlin/joins/OrderLineDynamicSQLSupport.kt new file mode 100644 index 000000000..861461605 --- /dev/null +++ b/src/test/kotlin/examples/kotlin/joins/OrderLineDynamicSQLSupport.kt @@ -0,0 +1,29 @@ +/** + * Copyright 2016-2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package examples.kotlin.joins + +import org.mybatis.dynamic.sql.SqlTable + +import java.sql.JDBCType + +object OrderLineDynamicSQLSupport { + object OrderLine : SqlTable("OrderLine") { + val orderId = column("order_id", JDBCType.INTEGER) + val itemId = column("item_id", JDBCType.INTEGER) + val lineNumber = column("line_number", JDBCType.INTEGER) + val quantity = column("quantity", JDBCType.INTEGER) + } +} diff --git a/src/test/kotlin/examples/kotlin/joins/OrderMasterDynamicSQLSupport.kt b/src/test/kotlin/examples/kotlin/joins/OrderMasterDynamicSQLSupport.kt new file mode 100644 index 000000000..288c773a2 --- /dev/null +++ b/src/test/kotlin/examples/kotlin/joins/OrderMasterDynamicSQLSupport.kt @@ -0,0 +1,27 @@ +/** + * Copyright 2016-2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package examples.kotlin.joins + +import org.mybatis.dynamic.sql.SqlTable +import java.sql.JDBCType +import java.util.* + +object OrderMasterDynamicSQLSupport { + object OrderMaster : SqlTable("OrderMaster") { + val orderId = column("order_id", JDBCType.INTEGER) + val orderDate = column("order_date", JDBCType.DATE) + } +} diff --git a/src/test/kotlin/examples/kotlin/joins/UserDynamicSQLSupport.kt b/src/test/kotlin/examples/kotlin/joins/UserDynamicSQLSupport.kt new file mode 100644 index 000000000..e3392d9a7 --- /dev/null +++ b/src/test/kotlin/examples/kotlin/joins/UserDynamicSQLSupport.kt @@ -0,0 +1,34 @@ +/** + * Copyright 2016-2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package examples.kotlin.joins + +import org.mybatis.dynamic.sql.SqlTable + +import java.sql.JDBCType + +object UserDynamicSQLSupport { + object User1 : SqlTable("User") { + val userId = column("user_id", JDBCType.INTEGER) + val userName = column("user_name", JDBCType.VARCHAR) + val parentId = column("parent_id", JDBCType.INTEGER) + } + + object User2 : SqlTable("User") { + val userId = column("user_id", JDBCType.INTEGER) + val userName = column("user_name", JDBCType.VARCHAR) + val parentId = column("parent_id", JDBCType.INTEGER) + } +} diff --git a/src/test/resources/examples/kotlin/CreateSimpleDB.sql b/src/test/resources/examples/kotlin/CreateSimpleDB.sql new file mode 100644 index 000000000..88834d2aa --- /dev/null +++ b/src/test/resources/examples/kotlin/CreateSimpleDB.sql @@ -0,0 +1,57 @@ +-- +-- Copyright 2016-2019 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. +-- + +drop table Address if exists; +drop table Person if exists; + +create table Address ( + address_id int not null, + street_address varchar(50) not null, + city varchar(20) not null, + state varchar(2) not null, + primary key(address_id) +); + +create table Person ( + id int not null, + first_name varchar(30) not null, + last_name varchar(30) not null, + birth_date date not null, + employed varchar(3) not null, + occupation varchar(30) null, + address_id int not null, + primary key(id) +); + +insert into Address (address_id, street_address, city, state) +values(1, '123 Main Street', 'Bedrock', 'IN'); + +insert into Address (address_id, street_address, city, state) +values(2, '456 Main Street', 'Bedrock', 'IN'); + +insert into Person values(1, 'Fred', 'Flintstone', '1935-02-01', 'Yes', 'Brontosaurus Operator', 1); + +insert into Person values(2, 'Wilma', 'Flintstone', '1940-02-01', 'Yes', 'Accountant', 1); + +insert into Person(id, first_name, last_name, birth_date, employed, address_id) +values(3, 'Pebbles', 'Flintstone', '1960-05-06', 'No', 1); + +insert into Person values(4, 'Barney', 'Rubble', '1937-02-01', 'Yes', 'Brontosaurus Operator', 2); + +insert into Person values(5, 'Betty', 'Rubble', '1943-02-01', 'Yes', 'Engineer', 2); + +insert into Person(id, first_name, last_name, birth_date, employed, address_id) +values(6, 'Bamm Bamm', 'Rubble', '1963-07-08', 'No', 2); diff --git a/src/test/resources/examples/kotlin/joins/CreateJoinDB.sql b/src/test/resources/examples/kotlin/joins/CreateJoinDB.sql new file mode 100644 index 000000000..7b1f11398 --- /dev/null +++ b/src/test/resources/examples/kotlin/joins/CreateJoinDB.sql @@ -0,0 +1,79 @@ +-- +-- Copyright 2016-2019 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. +-- + +drop table OrderLine if exists; +drop table ItemMaster if exists; +drop table OrderDetail if exists; +drop table OrderMaster if exists; +drop table User if exists; + +create table User ( + user_id int not null, + user_name varchar(30) not null, + parent_id int null, + primary key (user_id) +); + +create table OrderMaster ( + order_id int not null, + order_date date not null, + primary key(order_id) +); + +create table OrderDetail ( + order_id int not null, + line_number int not null, + description varchar(30) not null, + quantity int not null, + primary key(order_id, line_number) +); + +create table ItemMaster ( + item_id int not null, + description varchar(30) not null, + primary key(item_id) +); + +create table OrderLine ( + order_id int not null, + item_id int not null, + line_number int not null, + quantity int not null, + primary key(order_id, item_id) +); + +insert into OrderMaster(order_id, order_date) values(1, '2017-01-17'); +insert into OrderDetail(order_id, line_number, Description, quantity) values(1, 1, 'Tennis Ball', 3); +insert into OrderDetail(order_id, line_number, Description, quantity) values(1, 2, 'Tennis Racket', 1); + +insert into OrderMaster (order_id, order_date) values(2, '2017-01-18'); +insert into OrderDetail(order_id, line_number, Description, quantity) values(2, 1, 'Football', 2); + +insert into ItemMaster(item_id, description) values(22, 'Helmet'); +insert into ItemMaster(item_id, description) values(33, 'First Base Glove'); +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(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); + +insert into User(user_id, user_name) values(1, 'Fred'); +insert into User(user_id, user_name) values(2, 'Barney'); +insert into User(user_id, user_name, parent_id) values(3, 'Pebbles', 1); +insert into User(user_id, user_name, parent_id) values(4, 'Bamm Bamm', 2); diff --git a/src/test/resources/examples/kotlin/joins/JoinMapper.xml b/src/test/resources/examples/kotlin/joins/JoinMapper.xml new file mode 100644 index 000000000..8454b79e0 --- /dev/null +++ b/src/test/resources/examples/kotlin/joins/JoinMapper.xml @@ -0,0 +1,31 @@ + + + + + + + + + + + + + + +