From 18c043ba3806fd88dc0bf1384205ba900244eb76 Mon Sep 17 00:00:00 2001 From: Jeff Butler Date: Wed, 22 Feb 2023 15:13:17 -0500 Subject: [PATCH 1/5] Initial work on multi-select --- .../org/mybatis/dynamic/sql/SqlBuilder.java | 5 + .../dynamic/sql/select/MultiSelectDSL.java | 143 +++++++++++ .../dynamic/sql/select/MultiSelectModel.java | 122 ++++++++++ .../select/render/MultiSelectRenderer.java | 127 ++++++++++ .../util/kotlin/KotlinMultiSelectBuilder.kt | 86 +++++++ .../kotlin/model/ModelBuilderFunctions.kt | 6 + .../mybatis3/ProviderBuilderFunctions.kt | 4 + .../kotlin/spring/ProviderBuilderFunctions.kt | 4 + .../dynamic/sql/util/messages.properties | 4 + .../examples/simple/PersonMapperTest.java | 227 +++++++++++++++++- .../mybatis3/canonical/PersonMapperTest.kt | 55 +++++ .../canonical/CanonicalSpringKotlinTest.kt | 164 +++++++++++++ 12 files changed, 936 insertions(+), 11 deletions(-) create mode 100644 src/main/java/org/mybatis/dynamic/sql/select/MultiSelectDSL.java create mode 100644 src/main/java/org/mybatis/dynamic/sql/select/MultiSelectModel.java create mode 100644 src/main/java/org/mybatis/dynamic/sql/select/render/MultiSelectRenderer.java create mode 100644 src/main/kotlin/org/mybatis/dynamic/sql/util/kotlin/KotlinMultiSelectBuilder.kt diff --git a/src/main/java/org/mybatis/dynamic/sql/SqlBuilder.java b/src/main/java/org/mybatis/dynamic/sql/SqlBuilder.java index d74ada5f2..6ab3b9289 100644 --- a/src/main/java/org/mybatis/dynamic/sql/SqlBuilder.java +++ b/src/main/java/org/mybatis/dynamic/sql/SqlBuilder.java @@ -30,6 +30,7 @@ import org.mybatis.dynamic.sql.insert.MultiRowInsertDSL; import org.mybatis.dynamic.sql.select.ColumnSortSpecification; import org.mybatis.dynamic.sql.select.CountDSL; +import org.mybatis.dynamic.sql.select.MultiSelectDSL; import org.mybatis.dynamic.sql.select.QueryExpressionDSL.FromGatherer; import org.mybatis.dynamic.sql.select.SelectDSL; import org.mybatis.dynamic.sql.select.SelectModel; @@ -227,6 +228,10 @@ static FromGatherer selectDistinct(Collection selectLi return SelectDSL.selectDistinct(selectList); } + static MultiSelectDSL multiSelect(Buildable selectModelBuilder) { + return new MultiSelectDSL(selectModelBuilder); + } + static UpdateDSL update(SqlTable table) { return UpdateDSL.update(table); } diff --git a/src/main/java/org/mybatis/dynamic/sql/select/MultiSelectDSL.java b/src/main/java/org/mybatis/dynamic/sql/select/MultiSelectDSL.java new file mode 100644 index 000000000..f8bd872e4 --- /dev/null +++ b/src/main/java/org/mybatis/dynamic/sql/select/MultiSelectDSL.java @@ -0,0 +1,143 @@ +/* + * Copyright 2016-2023 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.mybatis.dynamic.sql.select; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.List; + +import org.jetbrains.annotations.NotNull; +import org.mybatis.dynamic.sql.SortSpecification; +import org.mybatis.dynamic.sql.common.OrderByModel; +import org.mybatis.dynamic.sql.util.Buildable; +public class MultiSelectDSL implements Buildable { + private final List unionQueries = new ArrayList<>(); + private final SelectModel initialSelect; + private OrderByModel orderByModel; + private Long limit; + private Long offset; + private Long fetchFirstRows; + + public MultiSelectDSL(Buildable builder) { + initialSelect = builder.build(); + } + + public MultiSelectDSL union(Buildable builder) { + unionQueries.add(new MultiSelectModel.UnionQuery("union", builder.build())); //$NON-NLS-1$ + return this; + } + + public MultiSelectDSL unionAll(Buildable builder) { + unionQueries.add(new MultiSelectModel.UnionQuery("union all", builder.build())); //$NON-NLS-1$ + return this; + } + + public MultiSelectDSL orderBy(SortSpecification... columns) { + return orderBy(Arrays.asList(columns)); + } + + public MultiSelectDSL orderBy(Collection columns) { + orderByModel = OrderByModel.of(columns); + return this; + } + + public LimitFinisher limit(long limit) { + this.limit = limit; + return new LimitFinisher(); + } + + public OffsetFirstFinisher offset(long offset) { + this.offset = offset; + return new OffsetFirstFinisher(); + } + + public FetchFirstFinisher fetchFirst(long fetchFirstRows) { + this.fetchFirstRows = fetchFirstRows; + return new FetchFirstFinisher(); + } + + @NotNull + @Override + public MultiSelectModel build() { + return new MultiSelectModel.Builder() + .withInitialSelect(initialSelect) + .withUnionQueries(unionQueries) + .withOrderByModel(orderByModel) + .withPagingModel(buildPagingModel()) + .build(); + } + + private PagingModel buildPagingModel() { + if (limit == null && offset == null && fetchFirstRows == null) { + return null; + } + + return new PagingModel.Builder() + .withLimit(limit) + .withOffset(offset) + .withFetchFirstRows(fetchFirstRows) + .build(); + } + + public class LimitFinisher implements Buildable { + public OffsetFinisher offset(long offset) { + MultiSelectDSL.this.offset(offset); + return new OffsetFinisher(); + } + + @NotNull + @Override + public MultiSelectModel build() { + return MultiSelectDSL.this.build(); + } + } + + public class OffsetFinisher implements Buildable { + @NotNull + @Override + public MultiSelectModel build() { + return MultiSelectDSL.this.build(); + } + } + + public class OffsetFirstFinisher implements Buildable { + public FetchFirstFinisher fetchFirst(long fetchFirstRows) { + MultiSelectDSL.this.fetchFirst(fetchFirstRows); + return new FetchFirstFinisher(); + } + + @NotNull + @Override + public MultiSelectModel build() { + return MultiSelectDSL.this.build(); + } + } + + public class FetchFirstFinisher { + public RowsOnlyFinisher rowsOnly() { + return new RowsOnlyFinisher(); + } + } + + public class RowsOnlyFinisher implements Buildable { + @NotNull + @Override + public MultiSelectModel build() { + return MultiSelectDSL.this.build(); + } + } +} diff --git a/src/main/java/org/mybatis/dynamic/sql/select/MultiSelectModel.java b/src/main/java/org/mybatis/dynamic/sql/select/MultiSelectModel.java new file mode 100644 index 000000000..1b9c1fc0f --- /dev/null +++ b/src/main/java/org/mybatis/dynamic/sql/select/MultiSelectModel.java @@ -0,0 +1,122 @@ +/* + * Copyright 2016-2023 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.mybatis.dynamic.sql.select; + +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; +import java.util.Optional; +import java.util.function.Function; +import java.util.stream.Stream; + +import org.jetbrains.annotations.NotNull; +import org.mybatis.dynamic.sql.common.OrderByModel; +import org.mybatis.dynamic.sql.exception.InvalidSqlException; +import org.mybatis.dynamic.sql.render.RenderingStrategy; +import org.mybatis.dynamic.sql.select.render.MultiSelectRenderer; +import org.mybatis.dynamic.sql.select.render.SelectStatementProvider; +import org.mybatis.dynamic.sql.util.Messages; + +public class MultiSelectModel { + private final SelectModel initialSelect; + private final List unionQueries; + private final OrderByModel orderByModel; + private final PagingModel pagingModel; + + private MultiSelectModel(Builder builder) { + initialSelect = Objects.requireNonNull(builder.initialSelect); + unionQueries = builder.unionQueries; + orderByModel = builder.orderByModel; + pagingModel = builder.pagingModel; + if (unionQueries.isEmpty()) { + throw new InvalidSqlException(Messages.getString("ERROR.35")); //$NON-NLS-1$ + } + } + + public SelectModel initialSelect() { + return initialSelect; + } + + public Stream mapUnionQueries(Function mapper) { + return unionQueries.stream().map(mapper); + } + + public Optional orderByModel() { + return Optional.ofNullable(orderByModel); + } + + public Optional pagingModel() { + return Optional.ofNullable(pagingModel); + } + + @NotNull + public SelectStatementProvider render(RenderingStrategy renderingStrategy) { + return new MultiSelectRenderer.Builder() + .withMultiSelectModel(this) + .withRenderingStrategy(renderingStrategy) + .build() + .render(); + } + + public static class Builder { + private SelectModel initialSelect; + private final List unionQueries = new ArrayList<>(); + private OrderByModel orderByModel; + private PagingModel pagingModel; + + public Builder withInitialSelect(SelectModel initialSelect) { + this.initialSelect = initialSelect; + return this; + } + + public Builder withUnionQueries(List unionQueries) { + this.unionQueries.addAll((unionQueries)); + return this; + } + + public Builder withOrderByModel(OrderByModel orderByModel) { + this.orderByModel = orderByModel; + return this; + } + + public Builder withPagingModel(PagingModel pagingModel) { + this.pagingModel = pagingModel; + return this; + } + + public MultiSelectModel build() { + return new MultiSelectModel(this); + } + } + + public static class UnionQuery { + private final String connector; + private final SelectModel selectModel; + + public UnionQuery(String connector, SelectModel selectModel) { + this.connector = Objects.requireNonNull(connector); + this.selectModel = Objects.requireNonNull(selectModel); + } + + public String connector() { + return connector; + } + + public SelectModel selectModel() { + return selectModel; + } + } +} diff --git a/src/main/java/org/mybatis/dynamic/sql/select/render/MultiSelectRenderer.java b/src/main/java/org/mybatis/dynamic/sql/select/render/MultiSelectRenderer.java new file mode 100644 index 000000000..9550baed0 --- /dev/null +++ b/src/main/java/org/mybatis/dynamic/sql/select/render/MultiSelectRenderer.java @@ -0,0 +1,127 @@ +/* + * Copyright 2016-2023 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.mybatis.dynamic.sql.select.render; + +import java.util.Objects; +import java.util.Optional; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.stream.Collectors; + +import org.mybatis.dynamic.sql.common.OrderByModel; +import org.mybatis.dynamic.sql.common.OrderByRenderer; +import org.mybatis.dynamic.sql.render.RenderingStrategy; +import org.mybatis.dynamic.sql.select.MultiSelectModel; +import org.mybatis.dynamic.sql.select.PagingModel; +import org.mybatis.dynamic.sql.select.SelectModel; +import org.mybatis.dynamic.sql.util.FragmentAndParameters; +import org.mybatis.dynamic.sql.util.FragmentCollector; + +public class MultiSelectRenderer { + private final RenderingStrategy renderingStrategy; + private final AtomicInteger sequence = new AtomicInteger(1); + private final MultiSelectModel multiSelectModel; + + private MultiSelectRenderer(Builder builder) { + renderingStrategy = Objects.requireNonNull(builder.renderingStrategy); + multiSelectModel = Objects.requireNonNull(builder.multiSelectModel); + } + + public SelectStatementProvider render() { + FragmentAndParameters initialSelect = renderSelect(multiSelectModel.initialSelect()); + + FragmentCollector fragmentCollector = multiSelectModel + .mapUnionQueries(this::renderSelect) + .collect(FragmentCollector.collect(initialSelect)); + + renderOrderBy().ifPresent(fragmentCollector::add); + renderPagingModel().ifPresent(fragmentCollector::add); + + return toSelectStatementProvider(fragmentCollector); + } + + private SelectStatementProvider toSelectStatementProvider(FragmentCollector fragmentCollector) { + return DefaultSelectStatementProvider + .withSelectStatement(fragmentCollector.fragments().collect(Collectors.joining(" "))) //$NON-NLS-1$ + .withParameters(fragmentCollector.parameters()) + .build(); + } + + private FragmentAndParameters renderSelect(SelectModel selectModel) { + SelectStatementProvider selectStatement = SelectRenderer.withSelectModel(selectModel) + .withRenderingStrategy(renderingStrategy) + .withSequence(sequence) + .build() + .render(); + + return FragmentAndParameters + .withFragment("(" + selectStatement.getSelectStatement() + ")") //$NON-NLS-1$ //$NON-NLS-2$ + .withParameters(selectStatement.getParameters()) + .build(); + } + + private FragmentAndParameters renderSelect(MultiSelectModel.UnionQuery unionQuery) { + SelectStatementProvider selectStatement = SelectRenderer.withSelectModel(unionQuery.selectModel()) + .withRenderingStrategy(renderingStrategy) + .withSequence(sequence) + .build() + .render(); + + return FragmentAndParameters.withFragment( + unionQuery.connector() + " (" + selectStatement.getSelectStatement() + ")") //$NON-NLS-1$ //$NON-NLS-2$ + .withParameters(selectStatement.getParameters()) + .build(); + } + + private Optional renderOrderBy() { + return multiSelectModel.orderByModel().map(this::renderOrderBy); + } + + private FragmentAndParameters renderOrderBy(OrderByModel orderByModel) { + return new OrderByRenderer().render(orderByModel); + } + + private Optional renderPagingModel() { + return multiSelectModel.pagingModel().map(this::renderPagingModel); + } + + private FragmentAndParameters renderPagingModel(PagingModel pagingModel) { + return new PagingModelRenderer.Builder() + .withPagingModel(pagingModel) + .withRenderingStrategy(renderingStrategy) + .withSequence(sequence) + .build() + .render(); + } + + public static class Builder { + private RenderingStrategy renderingStrategy; + private MultiSelectModel multiSelectModel; + + public Builder withRenderingStrategy(RenderingStrategy renderingStrategy) { + this.renderingStrategy = renderingStrategy; + return this; + } + + public Builder withMultiSelectModel(MultiSelectModel multiSelectModel) { + this.multiSelectModel = multiSelectModel; + return this; + } + + public MultiSelectRenderer build() { + return new MultiSelectRenderer(this); + } + } +} diff --git a/src/main/kotlin/org/mybatis/dynamic/sql/util/kotlin/KotlinMultiSelectBuilder.kt b/src/main/kotlin/org/mybatis/dynamic/sql/util/kotlin/KotlinMultiSelectBuilder.kt new file mode 100644 index 000000000..f747c8cb6 --- /dev/null +++ b/src/main/kotlin/org/mybatis/dynamic/sql/util/kotlin/KotlinMultiSelectBuilder.kt @@ -0,0 +1,86 @@ +/* + * Copyright 2016-2023 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.mybatis.dynamic.sql.util.kotlin + +import org.mybatis.dynamic.sql.BasicColumn +import org.mybatis.dynamic.sql.SortSpecification +import org.mybatis.dynamic.sql.SqlBuilder +import org.mybatis.dynamic.sql.select.MultiSelectDSL +import org.mybatis.dynamic.sql.select.MultiSelectModel +import org.mybatis.dynamic.sql.util.Buildable +import org.mybatis.dynamic.sql.util.Messages + +typealias MultiSelectCompleter = KotlinMultiSelectBuilder.() -> Unit + +@MyBatisDslMarker +class KotlinMultiSelectBuilder: Buildable { + private var dsl: MultiSelectDSL? = null + private set(value) { + if (field != null) { + throw KInvalidSQLException(Messages.getString("ERROR.33")) //$NON-NLS-1$ + } + field = value + } + + fun select(vararg selectList: BasicColumn, completer: SelectCompleter) = + select(selectList.asList(), completer) + + fun select(selectList: List, completer: SelectCompleter) { + val b = KotlinSelectBuilder(SqlBuilder.select(selectList)).apply(completer) + dsl = SqlBuilder.multiSelect(b) + } + + fun selectDistinct(vararg selectList: BasicColumn, completer: SelectCompleter) = + selectDistinct(selectList.asList(), completer) + + fun selectDistinct(selectList: List, completer: SelectCompleter) { + val b = KotlinSelectBuilder(SqlBuilder.selectDistinct(selectList)).apply(completer) + dsl = SqlBuilder.multiSelect(b) + } + + fun union(completer: KotlinSubQueryBuilder.() -> Unit) { + val b = KotlinSubQueryBuilder().apply(completer) + getDsl().union(b) + } + + fun unionAll(completer: KotlinSubQueryBuilder.() -> Unit) { + val b = KotlinSubQueryBuilder().apply(completer) + getDsl().unionAll(b) + } + + fun orderBy(vararg columns: SortSpecification) { + getDsl().orderBy(columns.asList()) + } + + fun limit(limit: Long) { + getDsl().limit(limit) + } + + fun offset(offset: Long) { + getDsl().offset(offset) + } + + fun fetchFirst(fetchFirstRows: Long) { + getDsl().fetchFirst(fetchFirstRows).rowsOnly() + } + + override fun build(): MultiSelectModel = + getDsl().build() + + private fun getDsl(): MultiSelectDSL { + return dsl?: throw KInvalidSQLException(Messages.getString("ERROR.34")) //$NON-NLS-1$ + } +} diff --git a/src/main/kotlin/org/mybatis/dynamic/sql/util/kotlin/model/ModelBuilderFunctions.kt b/src/main/kotlin/org/mybatis/dynamic/sql/util/kotlin/model/ModelBuilderFunctions.kt index 231d36977..2294a0fbe 100644 --- a/src/main/kotlin/org/mybatis/dynamic/sql/util/kotlin/model/ModelBuilderFunctions.kt +++ b/src/main/kotlin/org/mybatis/dynamic/sql/util/kotlin/model/ModelBuilderFunctions.kt @@ -25,6 +25,7 @@ import org.mybatis.dynamic.sql.insert.GeneralInsertModel import org.mybatis.dynamic.sql.insert.InsertModel import org.mybatis.dynamic.sql.insert.InsertSelectModel import org.mybatis.dynamic.sql.insert.MultiRowInsertModel +import org.mybatis.dynamic.sql.select.MultiSelectModel import org.mybatis.dynamic.sql.select.SelectModel import org.mybatis.dynamic.sql.update.UpdateModel import org.mybatis.dynamic.sql.util.kotlin.CountCompleter @@ -41,8 +42,10 @@ import org.mybatis.dynamic.sql.util.kotlin.KotlinInsertCompleter import org.mybatis.dynamic.sql.util.kotlin.KotlinInsertSelectSubQueryBuilder import org.mybatis.dynamic.sql.util.kotlin.KotlinMultiRowInsertBuilder import org.mybatis.dynamic.sql.util.kotlin.KotlinMultiRowInsertCompleter +import org.mybatis.dynamic.sql.util.kotlin.KotlinMultiSelectBuilder import org.mybatis.dynamic.sql.util.kotlin.KotlinSelectBuilder import org.mybatis.dynamic.sql.util.kotlin.KotlinUpdateBuilder +import org.mybatis.dynamic.sql.util.kotlin.MultiSelectCompleter import org.mybatis.dynamic.sql.util.kotlin.SelectCompleter import org.mybatis.dynamic.sql.util.kotlin.UpdateCompleter @@ -89,6 +92,9 @@ fun selectDistinct(vararg columns: BasicColumn, completer: SelectCompleter): Sel fun selectDistinct(columns: List, completer: SelectCompleter): SelectModel = KotlinSelectBuilder(SqlBuilder.selectDistinct(columns)).apply(completer).build() +fun multiSelect(completer: MultiSelectCompleter): MultiSelectModel = + KotlinMultiSelectBuilder().apply(completer).build() + fun update(table: SqlTable, completer: UpdateCompleter): UpdateModel = KotlinUpdateBuilder(SqlBuilder.update(table)).apply(completer).build() diff --git a/src/main/kotlin/org/mybatis/dynamic/sql/util/kotlin/mybatis3/ProviderBuilderFunctions.kt b/src/main/kotlin/org/mybatis/dynamic/sql/util/kotlin/mybatis3/ProviderBuilderFunctions.kt index fbe644bb2..2766bcdde 100644 --- a/src/main/kotlin/org/mybatis/dynamic/sql/util/kotlin/mybatis3/ProviderBuilderFunctions.kt +++ b/src/main/kotlin/org/mybatis/dynamic/sql/util/kotlin/mybatis3/ProviderBuilderFunctions.kt @@ -34,6 +34,7 @@ import org.mybatis.dynamic.sql.util.kotlin.InsertSelectCompleter import org.mybatis.dynamic.sql.util.kotlin.KotlinBatchInsertCompleter import org.mybatis.dynamic.sql.util.kotlin.KotlinInsertCompleter import org.mybatis.dynamic.sql.util.kotlin.KotlinMultiRowInsertCompleter +import org.mybatis.dynamic.sql.util.kotlin.MultiSelectCompleter import org.mybatis.dynamic.sql.util.kotlin.SelectCompleter import org.mybatis.dynamic.sql.util.kotlin.UpdateCompleter import org.mybatis.dynamic.sql.util.kotlin.model.count @@ -94,6 +95,9 @@ fun selectDistinct(vararg columns: BasicColumn, completer: SelectCompleter): Sel fun selectDistinct(columns: List, completer: SelectCompleter): SelectStatementProvider = selectDistinct(columns, completer).render(RenderingStrategies.MYBATIS3) +fun multiSelect(completer: MultiSelectCompleter): SelectStatementProvider = + org.mybatis.dynamic.sql.util.kotlin.model.multiSelect(completer).render(RenderingStrategies.MYBATIS3) + fun update(table: SqlTable, completer: UpdateCompleter): UpdateStatementProvider = update(table, completer).render(RenderingStrategies.MYBATIS3) diff --git a/src/main/kotlin/org/mybatis/dynamic/sql/util/kotlin/spring/ProviderBuilderFunctions.kt b/src/main/kotlin/org/mybatis/dynamic/sql/util/kotlin/spring/ProviderBuilderFunctions.kt index f45b81e55..b72a18de5 100644 --- a/src/main/kotlin/org/mybatis/dynamic/sql/util/kotlin/spring/ProviderBuilderFunctions.kt +++ b/src/main/kotlin/org/mybatis/dynamic/sql/util/kotlin/spring/ProviderBuilderFunctions.kt @@ -34,6 +34,7 @@ import org.mybatis.dynamic.sql.util.kotlin.InsertSelectCompleter import org.mybatis.dynamic.sql.util.kotlin.KotlinBatchInsertCompleter import org.mybatis.dynamic.sql.util.kotlin.KotlinInsertCompleter import org.mybatis.dynamic.sql.util.kotlin.KotlinMultiRowInsertCompleter +import org.mybatis.dynamic.sql.util.kotlin.MultiSelectCompleter import org.mybatis.dynamic.sql.util.kotlin.SelectCompleter import org.mybatis.dynamic.sql.util.kotlin.UpdateCompleter import org.mybatis.dynamic.sql.util.kotlin.model.count @@ -94,6 +95,9 @@ fun selectDistinct(vararg columns: BasicColumn, completer: SelectCompleter): Sel fun selectDistinct(columns: List, completer: SelectCompleter): SelectStatementProvider = selectDistinct(columns, completer).render(RenderingStrategies.SPRING_NAMED_PARAMETER) +fun multiSelect(completer: MultiSelectCompleter): SelectStatementProvider = + org.mybatis.dynamic.sql.util.kotlin.model.multiSelect(completer).render(RenderingStrategies.SPRING_NAMED_PARAMETER) + fun update(table: SqlTable, completer: UpdateCompleter): UpdateStatementProvider = update(table, completer).render(RenderingStrategies.SPRING_NAMED_PARAMETER) diff --git a/src/main/resources/org/mybatis/dynamic/sql/util/messages.properties b/src/main/resources/org/mybatis/dynamic/sql/util/messages.properties index 72ebdcb3a..126bcaf20 100644 --- a/src/main/resources/org/mybatis/dynamic/sql/util/messages.properties +++ b/src/main/resources/org/mybatis/dynamic/sql/util/messages.properties @@ -48,4 +48,8 @@ ERROR.29=Insert Select Statements Must Contain an "into" phrase ERROR.30=The parameters for insertMultipleWithGeneratedKeys must contain exactly one parameter of type String ERROR.31=You cannot specify more than one "having" clause in a query expression ERROR.32=You cannot specify more than one "where" clause in a statement +ERROR.33=Calling "select" or "selectDistinct" more than once is not allowed. Additional queries should be added with a \ + union or unionAll expression +ERROR.34=You must specify "select" or "selectDistinct" before any other clauses in a multi-select statement +ERROR.35=Multi-select statements must have at least one "union" or "union all" expression INTERNAL.ERROR=Internal Error {0} diff --git a/src/test/java/examples/simple/PersonMapperTest.java b/src/test/java/examples/simple/PersonMapperTest.java index 01bf28e1b..d67b5145c 100644 --- a/src/test/java/examples/simple/PersonMapperTest.java +++ b/src/test/java/examples/simple/PersonMapperTest.java @@ -25,7 +25,6 @@ import static examples.simple.PersonDynamicSqlSupport.occupation; import static examples.simple.PersonDynamicSqlSupport.person; import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.jupiter.api.Assertions.assertAll; import static org.mybatis.dynamic.sql.SqlBuilder.*; import java.io.InputStream; @@ -69,6 +68,7 @@ class PersonMapperTest { void setup() throws Exception { Class.forName(JDBC_DRIVER); InputStream is = getClass().getResourceAsStream("/examples/simple/CreateSimpleDB.sql"); + assert is != null; try (Connection connection = DriverManager.getConnection(JDBC_URL, "sa", "")) { ScriptRunner sr = new ScriptRunner(connection); sr.setLogWriter(null); @@ -219,11 +219,9 @@ void testSelectWithTypeHandler() { c.where(employed, isEqualTo(false)) .orderBy(id)); - assertAll( - () -> assertThat(rows).hasSize(2), - () -> assertThat(rows.get(0).getId()).isEqualTo(3), - () -> assertThat(rows.get(1).getId()).isEqualTo(6) - ); + assertThat(rows).hasSize(2); + assertThat(rows.get(0).getId()).isEqualTo(3); + assertThat(rows.get(1).getId()).isEqualTo(6); } } @@ -245,11 +243,9 @@ void testFirstNameIn() { List rows = mapper.select(c -> c.where(firstName, isIn("Fred", "Barney"))); - assertAll( - () -> assertThat(rows).hasSize(2), - () -> assertThat(rows.get(0).getLastName().getName()).isEqualTo("Flintstone"), - () -> assertThat(rows.get(1).getLastName().getName()).isEqualTo("Rubble") - ); + assertThat(rows).hasSize(2); + assertThat(rows.get(0).getLastName().getName()).isEqualTo("Flintstone"); + assertThat(rows.get(1).getLastName().getName()).isEqualTo("Rubble"); } } @@ -768,4 +764,213 @@ void testWithEnumOrdinalTypeHandler() { assertThat(type).hasValueSatisfying(i -> assertThat(i).isZero()); } } + + @Test + void testMultiSelectWithUnion() { + try (SqlSession session = sqlSessionFactory.openSession()) { + PersonMapper mapper = session.getMapper(PersonMapper.class); + + SelectStatementProvider selectStatement = multiSelect( + select(id.as("A_ID"), firstName, lastName, birthDate, employed, occupation, addressId) + .from(person) + .where(id, isLessThanOrEqualTo(2)) + .orderBy(id) + .limit(1) + ).union( + select(id.as("A_ID"), firstName, lastName, birthDate, employed, occupation, addressId) + .from(person) + .where(id, isGreaterThanOrEqualTo(4)) + .orderBy(id.descending()) + .limit(1) + ) + .orderBy(sortColumn("A_ID")) + .limit(3) + .build() + .render(RenderingStrategies.MYBATIS3); + + String expected = + "(select id as A_ID, first_name, last_name, birth_date, employed, occupation, address_id " + + "from Person " + + "where id <= #{parameters.p1,jdbcType=INTEGER} " + + "order by id limit #{parameters.p2}) " + + "union " + + "(select id as A_ID, first_name, last_name, birth_date, employed, occupation, address_id " + + "from Person " + + "where id >= #{parameters.p3,jdbcType=INTEGER} " + + "order by id DESC limit #{parameters.p4}) " + + "order by A_ID " + + "limit #{parameters.p5}"; + + assertThat(selectStatement.getSelectStatement()).isEqualTo(expected); + + List records = mapper.selectMany(selectStatement); + + assertThat(records).hasSize(2); + assertThat(records.get(0).getId()).isEqualTo(1); + assertThat(records.get(1).getId()).isEqualTo(6); + } + } + + @Test + void testMultiSelectWithUnionAll() { + try (SqlSession session = sqlSessionFactory.openSession()) { + PersonMapper mapper = session.getMapper(PersonMapper.class); + + SelectStatementProvider selectStatement = multiSelect( + select(id.as("A_ID"), firstName, lastName, birthDate, employed, occupation, addressId) + .from(person) + .where(id, isLessThanOrEqualTo(2)) + .orderBy(id) + .limit(1) + ).unionAll( + select(id.as("A_ID"), firstName, lastName, birthDate, employed, occupation, addressId) + .from(person) + .where(id, isGreaterThanOrEqualTo(4)) + .orderBy(id.descending()) + .limit(1) + ).orderBy(sortColumn("A_ID")) + .fetchFirst(2).rowsOnly() + .build() + .render(RenderingStrategies.MYBATIS3); + + String expected = + "(select id as A_ID, first_name, last_name, birth_date, employed, occupation, address_id " + + "from Person " + + "where id <= #{parameters.p1,jdbcType=INTEGER} " + + "order by id limit #{parameters.p2}) " + + "union all " + + "(select id as A_ID, first_name, last_name, birth_date, employed, occupation, address_id " + + "from Person " + + "where id >= #{parameters.p3,jdbcType=INTEGER} " + + "order by id DESC limit #{parameters.p4}) " + + "order by A_ID " + + "fetch first #{parameters.p5} rows only"; + + assertThat(selectStatement.getSelectStatement()).isEqualTo(expected); + + List records = mapper.selectMany(selectStatement); + + assertThat(records).hasSize(2); + assertThat(records.get(0).getId()).isEqualTo(1); + assertThat(records.get(1).getId()).isEqualTo(6); + } + } + + @Test + void testMultiSelectPagingVariation1() { + SelectStatementProvider selectStatement = multiSelect( + select(id, firstName, lastName, birthDate, employed, occupation, addressId) + .from(person) + .where(id, isLessThanOrEqualTo(2)) + ).unionAll( + select(id, firstName, lastName, birthDate, employed, occupation, addressId) + .from(person) + .where(id, isGreaterThanOrEqualTo(4)) + ) + .orderBy(id) + .limit(3).offset(2) + .build() + .render(RenderingStrategies.MYBATIS3); + + String expected = + "(select id, first_name, last_name, birth_date, employed, occupation, address_id " + + "from Person " + + "where id <= #{parameters.p1,jdbcType=INTEGER}) " + + "union all " + + "(select id, first_name, last_name, birth_date, employed, occupation, address_id " + + "from Person " + + "where id >= #{parameters.p2,jdbcType=INTEGER}) " + + "order by id " + + "limit #{parameters.p3} offset #{parameters.p4}"; + + assertThat(selectStatement.getSelectStatement()).isEqualTo(expected); + } + + @Test + void testMultiSelectPagingVariation2() { + SelectStatementProvider selectStatement = multiSelect( + select(id, firstName, lastName, birthDate, employed, occupation, addressId) + .from(person) + .where(id, isLessThanOrEqualTo(2)) + ).unionAll( + select(id, firstName, lastName, birthDate, employed, occupation, addressId) + .from(person) + .where(id, isGreaterThanOrEqualTo(4)) + ) + .orderBy(id) + .offset(2) + .build() + .render(RenderingStrategies.MYBATIS3); + + String expected = + "(select id, first_name, last_name, birth_date, employed, occupation, address_id " + + "from Person " + + "where id <= #{parameters.p1,jdbcType=INTEGER}) " + + "union all " + + "(select id, first_name, last_name, birth_date, employed, occupation, address_id " + + "from Person " + + "where id >= #{parameters.p2,jdbcType=INTEGER}) " + + "order by id " + + "offset #{parameters.p3} rows"; + + assertThat(selectStatement.getSelectStatement()).isEqualTo(expected); + } + + @Test + void testMultiSelectPagingVariation3() { + SelectStatementProvider selectStatement = multiSelect( + select(id, firstName, lastName, birthDate, employed, occupation, addressId) + .from(person) + .where(id, isLessThanOrEqualTo(2)) + ).unionAll( + select(id, firstName, lastName, birthDate, employed, occupation, addressId) + .from(person) + .where(id, isGreaterThanOrEqualTo(4)) + ) + .orderBy(id) + .offset(2).fetchFirst(3).rowsOnly() + .build() + .render(RenderingStrategies.MYBATIS3); + + String expected = + "(select id, first_name, last_name, birth_date, employed, occupation, address_id " + + "from Person " + + "where id <= #{parameters.p1,jdbcType=INTEGER}) " + + "union all " + + "(select id, first_name, last_name, birth_date, employed, occupation, address_id " + + "from Person " + + "where id >= #{parameters.p2,jdbcType=INTEGER}) " + + "order by id " + + "offset #{parameters.p3} rows fetch first #{parameters.p4} rows only"; + + assertThat(selectStatement.getSelectStatement()).isEqualTo(expected); + } + + @Test + void testMultiSelectPagingVariation() { + SelectStatementProvider selectStatement = multiSelect( + select(id, firstName, lastName, birthDate, employed, occupation, addressId) + .from(person) + .where(id, isLessThanOrEqualTo(2)) + ).unionAll( + select(id, firstName, lastName, birthDate, employed, occupation, addressId) + .from(person) + .where(id, isGreaterThanOrEqualTo(4)) + ) + .orderBy(id) + .build() + .render(RenderingStrategies.MYBATIS3); + + String expected = + "(select id, first_name, last_name, birth_date, employed, occupation, address_id " + + "from Person " + + "where id <= #{parameters.p1,jdbcType=INTEGER}) " + + "union all " + + "(select id, first_name, last_name, birth_date, employed, occupation, address_id " + + "from Person " + + "where id >= #{parameters.p2,jdbcType=INTEGER}) " + + "order by id"; + + assertThat(selectStatement.getSelectStatement()).isEqualTo(expected); + } } diff --git a/src/test/kotlin/examples/kotlin/mybatis3/canonical/PersonMapperTest.kt b/src/test/kotlin/examples/kotlin/mybatis3/canonical/PersonMapperTest.kt index 44eb22ca2..728f7629d 100644 --- a/src/test/kotlin/examples/kotlin/mybatis3/canonical/PersonMapperTest.kt +++ b/src/test/kotlin/examples/kotlin/mybatis3/canonical/PersonMapperTest.kt @@ -35,8 +35,10 @@ import org.junit.jupiter.api.TestInstance.Lifecycle import org.mybatis.dynamic.sql.util.kotlin.elements.add import org.mybatis.dynamic.sql.util.kotlin.elements.constant import org.mybatis.dynamic.sql.util.kotlin.elements.isIn +import org.mybatis.dynamic.sql.util.kotlin.elements.sortColumn import org.mybatis.dynamic.sql.util.kotlin.mybatis3.insertInto import org.mybatis.dynamic.sql.util.kotlin.mybatis3.insertSelect +import org.mybatis.dynamic.sql.util.kotlin.mybatis3.multiSelect import org.mybatis.dynamic.sql.util.kotlin.mybatis3.select import java.util.* @@ -757,4 +759,57 @@ class PersonMapperTest { assertThat(type).hasValueSatisfying { assertThat(it).isEqualTo(0) } } } + + @Test + fun testRawMultiSelectWithUnion() { + sqlSessionFactory.openSession().use { session -> + val mapper = session.getMapper(PersonMapper::class.java) + val selectStatement = multiSelect { + select(id.`as`("A_ID"), firstName, lastName, birthDate, employed, occupation, addressId) { + from(person) + where { id isLessThanOrEqualTo 2 } + orderBy(id) + limit(1) + } + union { + select(id.`as`("A_ID"), firstName, lastName, birthDate, employed, occupation, addressId) { + from(person) + where { id isGreaterThanOrEqualTo 4 } + orderBy(id.descending()) + limit(1) + } + } + orderBy(sortColumn("A_ID")) + limit(2) + offset(1) + } + + 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} " + + "order by id limit #{parameters.p2}) " + + "union " + + "(select id as A_ID, first_name, last_name, birth_date, employed, occupation, address_id " + + "from Person " + + "where id >= #{parameters.p3,jdbcType=INTEGER} " + + "order by id DESC limit #{parameters.p4}) " + + "order by A_ID limit #{parameters.p5} offset #{parameters.p6}" + + assertThat(selectStatement.selectStatement).isEqualTo(expected) + + val records = mapper.selectMany(selectStatement) + + assertThat(records).hasSize(1) + with(records[0]) { + assertThat(id).isEqualTo(6) + assertThat(firstName).isEqualTo("Bamm Bamm") + assertThat(lastName!!.name).isEqualTo("Rubble") + assertThat(birthDate).isNotNull + assertThat(employed).isFalse + assertThat(occupation).isNull() + assertThat(addressId).isEqualTo(2) + } + } + } } diff --git a/src/test/kotlin/examples/kotlin/spring/canonical/CanonicalSpringKotlinTest.kt b/src/test/kotlin/examples/kotlin/spring/canonical/CanonicalSpringKotlinTest.kt index c18ce7b08..cd1227282 100644 --- a/src/test/kotlin/examples/kotlin/spring/canonical/CanonicalSpringKotlinTest.kt +++ b/src/test/kotlin/examples/kotlin/spring/canonical/CanonicalSpringKotlinTest.kt @@ -49,6 +49,7 @@ import org.mybatis.dynamic.sql.util.kotlin.spring.insertBatch import org.mybatis.dynamic.sql.util.kotlin.spring.insertInto import org.mybatis.dynamic.sql.util.kotlin.spring.insertMultiple import org.mybatis.dynamic.sql.util.kotlin.spring.insertSelect +import org.mybatis.dynamic.sql.util.kotlin.spring.multiSelect import org.mybatis.dynamic.sql.util.kotlin.spring.select import org.mybatis.dynamic.sql.util.kotlin.spring.selectDistinct import org.mybatis.dynamic.sql.util.kotlin.spring.selectList @@ -758,6 +759,169 @@ open class CanonicalSpringKotlinTest { } } + @Test + fun testRawMultiSelectWithUnion() { + val selectStatement = multiSelect { + select(id, firstName, lastName, birthDate, employed, occupation, addressId) { + from(person, "p1") + where { id isLessThanOrEqualTo 2 } + orderBy(id) + limit(1) + } + union { + select(id, firstName, lastName, birthDate, employed, occupation, addressId) { + from(person, "p2") + where { id isGreaterThanOrEqualTo 4 } + orderBy(id.descending()) + limit(1) + } + } + orderBy(id) + limit(2) + } + + val expected = + "(select p1.id, p1.first_name, p1.last_name, p1.birth_date, p1.employed, p1.occupation, p1.address_id " + + "from Person p1 " + + "where p1.id <= :p1 " + + "order by id limit :p2) " + + "union " + + "(select p2.id, p2.first_name, p2.last_name, p2.birth_date, p2.employed, p2.occupation, p2.address_id " + + "from Person p2 " + + "where p2.id >= :p3 " + + "order by id DESC limit :p4) " + + "order by id limit :p5" + + assertThat(selectStatement.selectStatement).isEqualTo(expected) + + val records = template.selectList(selectStatement, personRowMapper) + + assertThat(records).hasSize(2) + with(records[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) + } + + with(records[1]) { + assertThat(id).isEqualTo(6) + assertThat(firstName).isEqualTo("Bamm Bamm") + assertThat(lastName!!.name).isEqualTo("Rubble") + assertThat(birthDate).isNotNull + assertThat(employed).isFalse + assertThat(occupation).isNull() + assertThat(addressId).isEqualTo(2) + } + } + + @Test + fun testRawMultiSelectWithUnionAll() { + val selectStatement = multiSelect { + selectDistinct(id, firstName, lastName, birthDate, employed, occupation, addressId) { + from(person, "p1") + where { id isLessThanOrEqualTo 2 } + orderBy(id) + limit(1) + } + unionAll { + select(id, firstName, lastName, birthDate, employed, occupation, addressId) { + from(person, "p2") + where { id isGreaterThanOrEqualTo 4 } + orderBy(id.descending()) + limit(1) + } + } + orderBy(id) + fetchFirst(1) + offset(1) + } + + val expected = + "(select distinct p1.id, p1.first_name, p1.last_name, p1.birth_date, p1.employed, p1.occupation, p1.address_id " + + "from Person p1 " + + "where p1.id <= :p1 " + + "order by id limit :p2) " + + "union all " + + "(select p2.id, p2.first_name, p2.last_name, p2.birth_date, p2.employed, p2.occupation, p2.address_id " + + "from Person p2 " + + "where p2.id >= :p3 " + + "order by id DESC limit :p4) " + + "order by id offset :p5 rows fetch first :p6 rows only" + + assertThat(selectStatement.selectStatement).isEqualTo(expected) + + val records = template.selectList(selectStatement, personRowMapper) + + assertThat(records).hasSize(1) + with(records[0]) { + assertThat(id).isEqualTo(6) + assertThat(firstName).isEqualTo("Bamm Bamm") + assertThat(lastName!!.name).isEqualTo("Rubble") + assertThat(birthDate).isNotNull + assertThat(employed).isFalse + assertThat(occupation).isNull() + assertThat(addressId).isEqualTo(2) + } + } + + @Test + fun testRawMultiSelectWithoutUnion() { + assertThatExceptionOfType(InvalidSqlException::class.java).isThrownBy { + multiSelect { + selectDistinct(id, firstName, lastName, birthDate, employed, occupation, addressId) { + from(person, "p1") + where { id isLessThanOrEqualTo 2 } + orderBy(id) + limit(1) + } + orderBy(id) + fetchFirst(1) + offset(1) + } + }.withMessage(Messages.getString("ERROR.35")) //$NON-NLS-1$ + } + + @Test + fun testInvalidDoubleSelect() { + assertThatExceptionOfType(KInvalidSQLException::class.java).isThrownBy { + multiSelect { + selectDistinct(id, firstName, lastName, birthDate, employed, occupation, addressId) { + from(person, "p1") + where { id isLessThanOrEqualTo 2 } + orderBy(id) + limit(1) + } + select(id, firstName, lastName, birthDate, employed, occupation, addressId) { + from(person, "p2") + where { id isGreaterThanOrEqualTo 4 } + orderBy(id.descending()) + limit(1) + } + } + }.withMessage(Messages.getString("ERROR.33")) //$NON-NLS-1$ + } + + @Test + fun testInvalidMissingInitialSelect() { + assertThatExceptionOfType(KInvalidSQLException::class.java).isThrownBy { + multiSelect { + union { + select(id, firstName, lastName, birthDate, employed, occupation, addressId) { + from(person, "p2") + where { id isGreaterThanOrEqualTo 4 } + orderBy(id.descending()) + limit(1) + } + } + } + }.withMessage(Messages.getString("ERROR.34")) //$NON-NLS-1$ + + } + @Test fun testRawSelectWithUnionAndAlias() { val selectStatement = select( From fa5981741d3ebe405492a611a8198cc61b68a6fe Mon Sep 17 00:00:00 2001 From: Jeff Butler Date: Wed, 22 Feb 2023 15:16:09 -0500 Subject: [PATCH 2/5] Checkstyle --- src/main/java/org/mybatis/dynamic/sql/SqlBuilder.java | 4 ++-- .../java/org/mybatis/dynamic/sql/select/MultiSelectDSL.java | 1 + .../java/org/mybatis/dynamic/sql/select/MultiSelectModel.java | 2 +- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/main/java/org/mybatis/dynamic/sql/SqlBuilder.java b/src/main/java/org/mybatis/dynamic/sql/SqlBuilder.java index 6ab3b9289..87571de0e 100644 --- a/src/main/java/org/mybatis/dynamic/sql/SqlBuilder.java +++ b/src/main/java/org/mybatis/dynamic/sql/SqlBuilder.java @@ -501,8 +501,8 @@ static Subtract subtract(BindableColumn firstColumn, BasicColumn secon * @param firstColumn first column * @param secondColumn second column * @param subsequentColumns subsequent columns - * @return a Concatenate instance * @param type of column + * @return a Concatenate instance */ static Concatenate concatenate(BindableColumn firstColumn, BasicColumn secondColumn, BasicColumn... subsequentColumns) { @@ -515,8 +515,8 @@ static Concatenate concatenate(BindableColumn firstColumn, BasicColumn * * @param firstColumn first column * @param subsequentColumns subsequent columns - * @return a Concat instance * @param type of column + * @return a Concat instance */ static Concat concat(BindableColumn firstColumn, BasicColumn... subsequentColumns) { return Concat.concat(firstColumn, subsequentColumns); diff --git a/src/main/java/org/mybatis/dynamic/sql/select/MultiSelectDSL.java b/src/main/java/org/mybatis/dynamic/sql/select/MultiSelectDSL.java index f8bd872e4..130f63062 100644 --- a/src/main/java/org/mybatis/dynamic/sql/select/MultiSelectDSL.java +++ b/src/main/java/org/mybatis/dynamic/sql/select/MultiSelectDSL.java @@ -24,6 +24,7 @@ import org.mybatis.dynamic.sql.SortSpecification; import org.mybatis.dynamic.sql.common.OrderByModel; import org.mybatis.dynamic.sql.util.Buildable; + public class MultiSelectDSL implements Buildable { private final List unionQueries = new ArrayList<>(); private final SelectModel initialSelect; diff --git a/src/main/java/org/mybatis/dynamic/sql/select/MultiSelectModel.java b/src/main/java/org/mybatis/dynamic/sql/select/MultiSelectModel.java index 1b9c1fc0f..640b0a23d 100644 --- a/src/main/java/org/mybatis/dynamic/sql/select/MultiSelectModel.java +++ b/src/main/java/org/mybatis/dynamic/sql/select/MultiSelectModel.java @@ -83,7 +83,7 @@ public Builder withInitialSelect(SelectModel initialSelect) { } public Builder withUnionQueries(List unionQueries) { - this.unionQueries.addAll((unionQueries)); + this.unionQueries.addAll(unionQueries); return this; } From 06983bfa716006d15b38a527163f3d8ee2027756 Mon Sep 17 00:00:00 2001 From: Jeff Butler Date: Wed, 22 Feb 2023 16:39:09 -0500 Subject: [PATCH 3/5] Docs --- CHANGELOG.md | 7 ++ src/site/markdown/docs/kotlinMyBatis3.md | 8 ++ src/site/markdown/docs/kotlinOverview.md | 149 +++++++++++++++-------- src/site/markdown/docs/kotlinSpring.md | 12 +- src/site/markdown/docs/select.md | 38 +++++- 5 files changed, 153 insertions(+), 61 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a31d1793f..24cf536ee 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -35,6 +35,13 @@ functions in the Kotlin DSL still only apply to the where clause. The pull request for this change is ([#550](https://github.com/mybatis/mybatis-dynamic-sql/pull/550)) +### Multi-Select Queries + +A multi-select query is a special case of a union select statement. The difference is that it allows "order by" and +paging clauses to be applied to the nested queries. + +The pull request for this change is...TODO + ### Other Changes 1. Added support for specifying "limit" and "order by" on the DELETE and UPDATE statements. Not all databases support diff --git a/src/site/markdown/docs/kotlinMyBatis3.md b/src/site/markdown/docs/kotlinMyBatis3.md index 5e5fa5721..dd74587fc 100644 --- a/src/site/markdown/docs/kotlinMyBatis3.md +++ b/src/site/markdown/docs/kotlinMyBatis3.md @@ -940,6 +940,14 @@ val records = mapper.select { } ``` +## Multi-Select Statement Support + +Multi-select statements are a special case of select statement. All the above information about MyBatis mappers applies +equally to multi-select statements. + +The library does not provide a "one-step" shortcut for multi-select queries. You can execute a multi-select query +with the two-step method using either a "selectMany" or "selectOne" mapper method as shown above. + ## Update Method Support ### Two-Step Method diff --git a/src/site/markdown/docs/kotlinOverview.md b/src/site/markdown/docs/kotlinOverview.md index b21e5468e..27aee8ad1 100644 --- a/src/site/markdown/docs/kotlinOverview.md +++ b/src/site/markdown/docs/kotlinOverview.md @@ -6,9 +6,9 @@ DSL. You certainly can use the Java DSL with Kotlin. However, using the more spe benefits: 1. The Kotlin DSL generally masks the platform types that are inferred with the underlying Java DSL -1. The Kotlin DSL accurately expresses the nullability expectations of the underlying Java DSL -1. Using the Kotlin DSL will avoid some confusion with overloaded function names that are present in the Java DSL -1. The Kotlin DSL makes extensive use of Kotlin DSL construction features. It more closely mimics actual SQL than the +2. The Kotlin DSL accurately expresses the nullability expectations of the underlying Java DSL +3. Using the Kotlin DSL will avoid some confusion with overloaded function names that are present in the Java DSL +4. The Kotlin DSL makes extensive use of Kotlin DSL construction features. It more closely mimics actual SQL than the Java DSL and will likely feel more natural to Kotlin developers We take the customary approach to DSL building in Kotlin in that we attempt to create a somewhat natural feel for SQL, @@ -41,7 +41,7 @@ generated by the DSL. In general, the DSL can be used to generate the following users these objects can be considered intermediate objects and will not need to be accessed directly. However, if you want to implement a custom rendering strategy then you might need to work with "model" objects (this is an unusual use case) -1. "Provider" objects have been rendered into a form that can be used with SQL execution engines +2. "Provider" objects have been rendered into a form that can be used with SQL execution engines directly. Currently, the library supports rendering for MyBatis3 and Spring JDBC Template. Most users will interact with "provider" objects in some form or another @@ -87,7 +87,7 @@ import org.mybatis.dynamic.sql.util.kotlin.elements.column import java.util.Date object PersonDynamicSqlSupport { - val person: Person() + val person = Person() val id = person.id val firstName = person.firstName val lastName = person.lastName @@ -114,9 +114,9 @@ object PersonDynamicSqlSupport { Notes: 1. The outer object is a singleton containing the `SqlTable` and `SqlColumn` objects that map to the database table. -1. The inner `SqlTable` is declared as a `class` rather than an `object` - this allows you to create additional +2. The inner `SqlTable` is declared as a `class` rather than an `object` - this allows you to create additional instances for use in self-joins. -1. Note the use of the `column` extension function. This function accepts different +3. Note the use of the `column` extension function. This function accepts different parameters for the different attributes that can be assigned to a column (such as a MyBatis3 type handler, or a custom rendering strategy). We recommend using this extension function rather than the corresponding `column` and `withXXX` methods in the Java native DSL because the extension method will retain the non-nullable type information @@ -132,15 +132,17 @@ The library supports the following types of statements: 1. Count statements of various types - these are specialized select statements that return a single Long column, Count statements support where clauses, joins, and subqueries. -1. Delete statement with or without a where clause. -1. Insert statements of various types: +2. Delete statement with or without a where clause. +3. Insert statements of various types: 1. Single row insert - a statement where the insert values are obtained from a record class - 1. General insert - a statement where the insert values are set directly in the statement - 1. Multi-row Insert - a statement where the insert values are derived from a collection of records - 1. Batch insert - a set of insert statements appropriate for use as a JDBC batch - 1. Insert select - a statement where the insert values are obtained from a select statement -1. Select statement that supports joins, subqueries, where clauses, order by clauses, group by clauses, etc. -1. Update Statement with or without a where clause + 2. General insert - a statement where the insert values are set directly in the statement + 3. Multi-row Insert - a statement where the insert values are derived from a collection of records + 4. Batch insert - a set of insert statements appropriate for use as a JDBC batch + 5. Insert select - a statement where the insert values are obtained from a select statement +4. Select statement that supports joins, subqueries, where clauses, order by clauses, group by clauses, etc. +5. Multi-Select statements - multiple full select statements (including order by and paging clauses) merged together + with "union" or "union all" operators +6. Update Statement with or without a where clause ## Count Statements @@ -150,8 +152,8 @@ where clause. The library supports three types of count statements: 1. `count(*)` - counts the number of rows that match a where clause -1. `count(column)` - counts the number of non-null column values that match a where clause -1. `count(distinct column)` - counts the number of unique column values that match a where clause +2. `count(column)` - counts the number of non-null column values that match a where clause +3. `count(distinct column)` - counts the number of unique column values that match a where clause The DSL for count statements looks like this: @@ -174,11 +176,11 @@ val countDistinctColumnStatement = countDistinct(lastName) { These methods create models or providers depending on which package is used: -| Package | Resulting Object | -|---|---| -| org.mybatis.dynamic.sql.util.kotlin.model | org.mybatis.dynamic.sql.select.SelectModel | +| Package | Resulting Object | +|----------------------------------------------|---------------------------------------------------------------------------------------| +| org.mybatis.dynamic.sql.util.kotlin.model | org.mybatis.dynamic.sql.select.SelectModel | | org.mybatis.dynamic.sql.util.kotlin.mybatis3 | org.mybatis.dynamic.sql.select.render.SelectStatementProvider (rendered for MyBatis3) | -| org.mybatis.dynamic.sql.util.kotlin.spring | org.mybatis.dynamic.sql.select.render.SelectStatementProvider (rendered for Spring) | +| org.mybatis.dynamic.sql.util.kotlin.spring | org.mybatis.dynamic.sql.select.render.SelectStatementProvider (rendered for Spring) | ## Delete Statement @@ -202,11 +204,11 @@ val rows = template.deleteFrom(person) { This method creates models or providers depending on which package is used: -| Package | Resulting Object | -|---|---| -| org.mybatis.dynamic.sql.util.kotlin.model | org.mybatis.dynamic.sql.delete.DeleteModel | +| Package | Resulting Object | +|----------------------------------------------|---------------------------------------------------------------------------------------| +| org.mybatis.dynamic.sql.util.kotlin.model | org.mybatis.dynamic.sql.delete.DeleteModel | | org.mybatis.dynamic.sql.util.kotlin.mybatis3 | org.mybatis.dynamic.sql.delete.render.DeleteStatementProvider (rendered for MyBatis3) | -| org.mybatis.dynamic.sql.util.kotlin.spring | org.mybatis.dynamic.sql.delete.render.DeleteStatementProvider (rendered for Spring) | +| org.mybatis.dynamic.sql.util.kotlin.spring | org.mybatis.dynamic.sql.delete.render.DeleteStatementProvider (rendered for Spring) | ## Single Row Insert Statement @@ -247,11 +249,11 @@ as shown above. This method creates models or providers depending on which package is used: -| Package | Resulting Object | -|---|---| -| org.mybatis.dynamic.sql.util.kotlin.model | org.mybatis.dynamic.sql.insert.InsertModel | +| Package | Resulting Object | +|----------------------------------------------|---------------------------------------------------------------------------------------| +| org.mybatis.dynamic.sql.util.kotlin.model | org.mybatis.dynamic.sql.insert.InsertModel | | org.mybatis.dynamic.sql.util.kotlin.mybatis3 | org.mybatis.dynamic.sql.insert.render.InsertStatementProvider (rendered for MyBatis3) | -| org.mybatis.dynamic.sql.util.kotlin.spring | org.mybatis.dynamic.sql.insert.render.InsertStatementProvider (rendered for Spring) | +| org.mybatis.dynamic.sql.util.kotlin.spring | org.mybatis.dynamic.sql.insert.render.InsertStatementProvider (rendered for Spring) | ## General Insert Statement @@ -274,11 +276,11 @@ val generalInsertStatement = insertInto(person) { This method creates models or providers depending on which package is used: -| Package | Resulting Object | -|---|---| -| org.mybatis.dynamic.sql.util.kotlin.model | org.mybatis.dynamic.sql.insert.GeneralInsertModel | +| Package | Resulting Object | +|----------------------------------------------|----------------------------------------------------------------------------------------------| +| org.mybatis.dynamic.sql.util.kotlin.model | org.mybatis.dynamic.sql.insert.GeneralInsertModel | | org.mybatis.dynamic.sql.util.kotlin.mybatis3 | org.mybatis.dynamic.sql.insert.render.GeneralInsertStatementProvider (rendered for MyBatis3) | -| org.mybatis.dynamic.sql.util.kotlin.spring | org.mybatis.dynamic.sql.insert.render.GeneralInsertStatementProvider (rendered for Spring) | +| org.mybatis.dynamic.sql.util.kotlin.spring | org.mybatis.dynamic.sql.insert.render.GeneralInsertStatementProvider (rendered for Spring) | ## Multi-Row Insert Statement @@ -324,11 +326,11 @@ the documentation pages for those utilities. This method creates models or providers depending on which package is used: -| Package | Resulting Object | -|---|---| -| org.mybatis.dynamic.sql.util.kotlin.model | org.mybatis.dynamic.sql.insert.MultiRowInsertModel | +| Package | Resulting Object | +|----------------------------------------------|-----------------------------------------------------------------------------------------------| +| org.mybatis.dynamic.sql.util.kotlin.model | org.mybatis.dynamic.sql.insert.MultiRowInsertModel | | org.mybatis.dynamic.sql.util.kotlin.mybatis3 | org.mybatis.dynamic.sql.insert.render.MultiRowInsertStatementProvider (rendered for MyBatis3) | -| org.mybatis.dynamic.sql.util.kotlin.spring | org.mybatis.dynamic.sql.insert.render.MultiRowInsertStatementProvider (rendered for Spring) | +| org.mybatis.dynamic.sql.util.kotlin.spring | org.mybatis.dynamic.sql.insert.render.MultiRowInsertStatementProvider (rendered for Spring) | ## Batch Insert Statement @@ -367,11 +369,11 @@ the documentation pages for those utilities. This method creates models or providers depending on which package is used: -| Package | Resulting Object | -|---|---| -| org.mybatis.dynamic.sql.util.kotlin.model | org.mybatis.dynamic.sql.insert.BatchInsertModel | +| Package | Resulting Object | +|----------------------------------------------|---------------------------------------------------------------------------| +| org.mybatis.dynamic.sql.util.kotlin.model | org.mybatis.dynamic.sql.insert.BatchInsertModel | | org.mybatis.dynamic.sql.util.kotlin.mybatis3 | org.mybatis.dynamic.sql.insert.render.BatchInsert (rendered for MyBatis3) | -| org.mybatis.dynamic.sql.util.kotlin.spring | org.mybatis.dynamic.sql.insert.render.BatchInsert (rendered for Spring) | +| org.mybatis.dynamic.sql.util.kotlin.spring | org.mybatis.dynamic.sql.insert.render.BatchInsert (rendered for Spring) | ## Insert Select Statement @@ -397,11 +399,11 @@ number of rows returned from the query. This method creates models or providers depending on which package is used: -| Package | Resulting Object | -|---|---| -| org.mybatis.dynamic.sql.util.kotlin.model | org.mybatis.dynamic.sql.insert.InsertSelectModel | +| Package | Resulting Object | +|----------------------------------------------|---------------------------------------------------------------------------------------------| +| org.mybatis.dynamic.sql.util.kotlin.model | org.mybatis.dynamic.sql.insert.InsertSelectModel | | org.mybatis.dynamic.sql.util.kotlin.mybatis3 | org.mybatis.dynamic.sql.insert.render.InsertSelectStatementProvider (rendered for MyBatis3) | -| org.mybatis.dynamic.sql.util.kotlin.spring | org.mybatis.dynamic.sql.insert.render.InsertSelectStatementProvider (rendered for Spring) | +| org.mybatis.dynamic.sql.util.kotlin.spring | org.mybatis.dynamic.sql.insert.render.InsertSelectStatementProvider (rendered for Spring) | ## Select Statement @@ -458,11 +460,52 @@ val selectStatement = selectDistinct(id, firstName, lastName, birthDate, employe These methods create models or providers depending on which package is used: -| Package | Resulting Object | -|---|---| -| org.mybatis.dynamic.sql.util.kotlin.model | org.mybatis.dynamic.sql.select.SelectModel | +| Package | Resulting Object | +|----------------------------------------------|---------------------------------------------------------------------------------------| +| org.mybatis.dynamic.sql.util.kotlin.model | org.mybatis.dynamic.sql.select.SelectModel | | org.mybatis.dynamic.sql.util.kotlin.mybatis3 | org.mybatis.dynamic.sql.select.render.SelectStatementProvider (rendered for MyBatis3) | -| org.mybatis.dynamic.sql.util.kotlin.spring | org.mybatis.dynamic.sql.select.render.SelectStatementProvider (rendered for Spring) | +| org.mybatis.dynamic.sql.util.kotlin.spring | org.mybatis.dynamic.sql.select.render.SelectStatementProvider (rendered for Spring) | + +## Multi-Select Statement + +A multi-select statement is a special case of a union query. In a multi-select statement, each select statement is +wrapped with parentheses. This means that you can use "order by" and paging clauses on the select statements that are +merged with a "union" or "union all" operator. You can also apply "order by" and paging clauses to the query as a whole. + +The full DSL for multi-select statements looks like this: + +```kotlin +val selectStatement = multiSelect { + selectDistinct(id, firstName, lastName, birthDate, employed, occupation, addressId) { + from(person) + where { id isLessThanOrEqualTo 2 } + orderBy(id) + limit(1) + } + unionAll { + select(id, firstName, lastName, birthDate, employed, occupation, addressId) { + from(person) + where { id isGreaterThanOrEqualTo 4 } + orderBy(id.descending()) + limit(1) + } + } + orderBy(id) + fetchFirst(1) + offset(1) +} +``` + +Each nested select statement can be either "select" or "selectDistinct". They can be merged with either +"union" or "unionAll". There is no limit to the number of statements that can be merged. + +These methods create models or providers depending on which package is used: + +| Package | Resulting Object | +|----------------------------------------------|---------------------------------------------------------------------------------------| +| org.mybatis.dynamic.sql.util.kotlin.model | org.mybatis.dynamic.sql.select.MultiSelectModel | +| org.mybatis.dynamic.sql.util.kotlin.mybatis3 | org.mybatis.dynamic.sql.select.render.SelectStatementProvider (rendered for MyBatis3) | +| org.mybatis.dynamic.sql.util.kotlin.spring | org.mybatis.dynamic.sql.select.render.SelectStatementProvider (rendered for Spring) | ## Update Statement @@ -482,8 +525,8 @@ If you omit the `where` clause, the statement will update every row in a table. This method creates models or providers depending on which package is used: -| Package | Resulting Object | -|---|---| -| org.mybatis.dynamic.sql.util.kotlin.model | org.mybatis.dynamic.sql.update.UpdateModel | +| Package | Resulting Object | +|----------------------------------------------|---------------------------------------------------------------------------------------| +| org.mybatis.dynamic.sql.util.kotlin.model | org.mybatis.dynamic.sql.update.UpdateModel | | org.mybatis.dynamic.sql.util.kotlin.mybatis3 | org.mybatis.dynamic.sql.update.render.UpdateStatementProvider (rendered for MyBatis3) | -| org.mybatis.dynamic.sql.util.kotlin.spring | org.mybatis.dynamic.sql.update.render.UpdateStatementProvider (rendered for Spring) | +| org.mybatis.dynamic.sql.util.kotlin.spring | org.mybatis.dynamic.sql.update.render.UpdateStatementProvider (rendered for Spring) | diff --git a/src/site/markdown/docs/kotlinSpring.md b/src/site/markdown/docs/kotlinSpring.md index adcfc7b3e..100b8685f 100644 --- a/src/site/markdown/docs/kotlinSpring.md +++ b/src/site/markdown/docs/kotlinSpring.md @@ -24,13 +24,13 @@ For each operation, there are two different methods of executing SQL: 1. The first method is a two-step method. With this method you build SQL provider objects as shown on the Kotlin overview page and then execute the generated SQL by passing the provider to an extension method on `NamedParameterJdbcTemplate` -1. The second method is a one-step method that combines these operations into a single step +2. The second method is a one-step method that combines these operations into a single step We will illustrate both approaches below. ## Kotlin Dynamic SQL Support Objects -The pattern for the meta-model is the same as shown on the Kotlin overview page. We'll repeat it here to show some +The pattern for the metamodel is the same as shown on the Kotlin overview page. We'll repeat it here to show some specifics for Spring. ```kotlin @@ -580,6 +580,14 @@ val personRecord: List = template.selectDistinct(id, firstName, la } ``` +## Multi-Select Statement Support + +Multi-select statements are a special case of select statement. All the above information about row mappers applies +equally to multi-select statements. + +The library does not provide a "one-step" shortcut for multi-select queries. You can execute a multi-select query +with the two-step method using either the "selectList" or "selectOne" extension methods as shown above. + ## Update Method Support ### Two-Step Method diff --git a/src/site/markdown/docs/select.md b/src/site/markdown/docs/select.md index ac7dd1fdb..bb1e0f01d 100644 --- a/src/site/markdown/docs/select.md +++ b/src/site/markdown/docs/select.md @@ -6,21 +6,22 @@ select statements, but purposely does not cover every possibility. In general, the following are supported: 1. The typical parts of a select statement including SELECT, DISTINCT, FROM, JOIN, WHERE, GROUP BY, UNION, - UNION ALL, ORDER BY + UNION ALL, ORDER BY, HAVING 2. Tables can be aliased per select statement 3. Columns can be aliased per select statement 4. Some support for aggregates (avg, min, max, sum) 5. Equijoins of type INNER, LEFT OUTER, RIGHT OUTER, FULL OUTER -6. Subqueries in where clauses. For example, `where foo in (select foo from foos where id < 36)` +6. Subqueries in where clauses. For example, `where foo in (select foo from foos where id < 36)` +7. Select from another select. For example `select count(*) from (select foo from foos where id < 36)` +8. Multi-Selects. For example `(select * from foo order by id limit 3) union (select * from foo order by id desc limit 3)` At this time, the library does not support the following: 1. WITH expressions -2. HAVING expressions -3. Select from another select. For example `select count(*) from (select foo from foos where id < 36)` -4. INTERSECT, EXCEPT, etc. +2. INTERSECT, EXCEPT, etc. -The user guide page for WHERE Clauses shows examples of many different types of SELECT statements with different complexities of the WHERE clause including support for sub-queries. We will just show a single example here, including an ORDER BY clause: +The user guide page for WHERE Clauses shows examples of many types of SELECT statements with different complexities of +the WHERE clause including support for sub-queries. We will just show a single example here, including an ORDER BY clause: ```java SelectStatementProvider selectStatement = select(id, animalName, bodyWeight, brainWeight) @@ -85,6 +86,31 @@ The library supports the generation of UNION and UNION ALL queries. For example: Any number of SELECT statements can be added to a UNION query. Only one ORDER BY phrase is allowed. +With this type of union query, the "order by" and paging clauses are applied to the query as a whole. If +you need to apply "order by" or paging clauses to the nested queries, use a multi-select query as shown +below. + +## Multi-Select Queries + +Multi-select queries are a special case of union select statements. The difference is that "order by" and +paging clauses can be applied to the merged queries. For example: + +```java + SelectStatementProvider selectStatement = multiSelect( + select(id, animalName, bodyWeight, brainWeight) + .from(animalData) + .orderBy(id) + .limit(2) + ).union( + selectDistinct(id, animalName, bodyWeight, brainWeight) + .from(animalData) + .orderBy(id.descending()) + .limit(3) + ) + .build() + .render(RenderingStrategies.MYBATIS3); +``` + ## MyBatis Mapper for Select Statements The SelectStatementProvider object can be used as a parameter to a MyBatis mapper method directly. If you From 3ec17f7687ed280912d89d076eaf205d9ec692d2 Mon Sep 17 00:00:00 2001 From: Jeff Butler Date: Wed, 22 Feb 2023 17:28:48 -0500 Subject: [PATCH 4/5] Refactor nested class --- .../dynamic/sql/select/MultiSelectDSL.java | 6 ++-- .../dynamic/sql/select/MultiSelectModel.java | 18 ---------- .../dynamic/sql/select/UnionQuery.java | 36 +++++++++++++++++++ .../select/render/MultiSelectRenderer.java | 3 +- 4 files changed, 41 insertions(+), 22 deletions(-) create mode 100644 src/main/java/org/mybatis/dynamic/sql/select/UnionQuery.java diff --git a/src/main/java/org/mybatis/dynamic/sql/select/MultiSelectDSL.java b/src/main/java/org/mybatis/dynamic/sql/select/MultiSelectDSL.java index 130f63062..3ab4bf88b 100644 --- a/src/main/java/org/mybatis/dynamic/sql/select/MultiSelectDSL.java +++ b/src/main/java/org/mybatis/dynamic/sql/select/MultiSelectDSL.java @@ -26,7 +26,7 @@ import org.mybatis.dynamic.sql.util.Buildable; public class MultiSelectDSL implements Buildable { - private final List unionQueries = new ArrayList<>(); + private final List unionQueries = new ArrayList<>(); private final SelectModel initialSelect; private OrderByModel orderByModel; private Long limit; @@ -38,12 +38,12 @@ public MultiSelectDSL(Buildable builder) { } public MultiSelectDSL union(Buildable builder) { - unionQueries.add(new MultiSelectModel.UnionQuery("union", builder.build())); //$NON-NLS-1$ + unionQueries.add(new UnionQuery("union", builder.build())); //$NON-NLS-1$ return this; } public MultiSelectDSL unionAll(Buildable builder) { - unionQueries.add(new MultiSelectModel.UnionQuery("union all", builder.build())); //$NON-NLS-1$ + unionQueries.add(new UnionQuery("union all", builder.build())); //$NON-NLS-1$ return this; } diff --git a/src/main/java/org/mybatis/dynamic/sql/select/MultiSelectModel.java b/src/main/java/org/mybatis/dynamic/sql/select/MultiSelectModel.java index 640b0a23d..aa5a804cd 100644 --- a/src/main/java/org/mybatis/dynamic/sql/select/MultiSelectModel.java +++ b/src/main/java/org/mybatis/dynamic/sql/select/MultiSelectModel.java @@ -101,22 +101,4 @@ public MultiSelectModel build() { return new MultiSelectModel(this); } } - - public static class UnionQuery { - private final String connector; - private final SelectModel selectModel; - - public UnionQuery(String connector, SelectModel selectModel) { - this.connector = Objects.requireNonNull(connector); - this.selectModel = Objects.requireNonNull(selectModel); - } - - public String connector() { - return connector; - } - - public SelectModel selectModel() { - return selectModel; - } - } } diff --git a/src/main/java/org/mybatis/dynamic/sql/select/UnionQuery.java b/src/main/java/org/mybatis/dynamic/sql/select/UnionQuery.java new file mode 100644 index 000000000..a201903ed --- /dev/null +++ b/src/main/java/org/mybatis/dynamic/sql/select/UnionQuery.java @@ -0,0 +1,36 @@ +/* + * Copyright 2016-2023 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.mybatis.dynamic.sql.select; + +import java.util.Objects; + +public class UnionQuery { + private final String connector; + private final SelectModel selectModel; + + public UnionQuery(String connector, SelectModel selectModel) { + this.connector = Objects.requireNonNull(connector); + this.selectModel = Objects.requireNonNull(selectModel); + } + + public String connector() { + return connector; + } + + public SelectModel selectModel() { + return selectModel; + } +} diff --git a/src/main/java/org/mybatis/dynamic/sql/select/render/MultiSelectRenderer.java b/src/main/java/org/mybatis/dynamic/sql/select/render/MultiSelectRenderer.java index 9550baed0..c450d4765 100644 --- a/src/main/java/org/mybatis/dynamic/sql/select/render/MultiSelectRenderer.java +++ b/src/main/java/org/mybatis/dynamic/sql/select/render/MultiSelectRenderer.java @@ -26,6 +26,7 @@ import org.mybatis.dynamic.sql.select.MultiSelectModel; import org.mybatis.dynamic.sql.select.PagingModel; import org.mybatis.dynamic.sql.select.SelectModel; +import org.mybatis.dynamic.sql.select.UnionQuery; import org.mybatis.dynamic.sql.util.FragmentAndParameters; import org.mybatis.dynamic.sql.util.FragmentCollector; @@ -72,7 +73,7 @@ private FragmentAndParameters renderSelect(SelectModel selectModel) { .build(); } - private FragmentAndParameters renderSelect(MultiSelectModel.UnionQuery unionQuery) { + private FragmentAndParameters renderSelect(UnionQuery unionQuery) { SelectStatementProvider selectStatement = SelectRenderer.withSelectModel(unionQuery.selectModel()) .withRenderingStrategy(renderingStrategy) .withSequence(sequence) From 9b9c04a2ebaba571412ab297f032a25c1e0b8a08 Mon Sep 17 00:00:00 2001 From: Jeff Butler Date: Wed, 22 Feb 2023 21:47:24 -0500 Subject: [PATCH 5/5] Update change log --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 24cf536ee..13660bd5c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -40,7 +40,7 @@ The pull request for this change is ([#550](https://github.com/mybatis/mybatis-d A multi-select query is a special case of a union select statement. The difference is that it allows "order by" and paging clauses to be applied to the nested queries. -The pull request for this change is...TODO +The pull request for this change is ([#591](https://github.com/mybatis/mybatis-dynamic-sql/pull/591)) ### Other Changes