From 3174cb6392a2ae64d7f9db796eaff0c18e27abda Mon Sep 17 00:00:00 2001 From: Jeff Butler Date: Mon, 5 Oct 2020 13:34:22 -0400 Subject: [PATCH 1/5] Better method name in SortSpecification --- .../java/org/mybatis/dynamic/sql/SortSpecification.java | 7 ++++--- src/main/java/org/mybatis/dynamic/sql/SqlColumn.java | 2 +- .../dynamic/sql/select/SimpleSortSpecification.java | 5 ++--- .../mybatis/dynamic/sql/select/render/SelectRenderer.java | 2 +- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/main/java/org/mybatis/dynamic/sql/SortSpecification.java b/src/main/java/org/mybatis/dynamic/sql/SortSpecification.java index dbeca8236..7ed9479dd 100644 --- a/src/main/java/org/mybatis/dynamic/sql/SortSpecification.java +++ b/src/main/java/org/mybatis/dynamic/sql/SortSpecification.java @@ -31,11 +31,12 @@ public interface SortSpecification { SortSpecification descending(); /** - * Return the column alias or column name. + * Return the phrase that should be written into a rendered order by clause. This should + * NOT include the "DESC" word for descending sort specifications. * - * @return the column alias if one has been specified by the user, or else the column name + * @return the order by phrase */ - String aliasOrName(); + String orderByName(); /** * Return true if the sort order is descending. diff --git a/src/main/java/org/mybatis/dynamic/sql/SqlColumn.java b/src/main/java/org/mybatis/dynamic/sql/SqlColumn.java index 90e0d0080..4fab312c1 100644 --- a/src/main/java/org/mybatis/dynamic/sql/SqlColumn.java +++ b/src/main/java/org/mybatis/dynamic/sql/SqlColumn.java @@ -91,7 +91,7 @@ public boolean isDescending() { } @Override - public String aliasOrName() { + public String orderByName() { return alias().orElse(name); } diff --git a/src/main/java/org/mybatis/dynamic/sql/select/SimpleSortSpecification.java b/src/main/java/org/mybatis/dynamic/sql/select/SimpleSortSpecification.java index 85d86590c..823a79592 100644 --- a/src/main/java/org/mybatis/dynamic/sql/select/SimpleSortSpecification.java +++ b/src/main/java/org/mybatis/dynamic/sql/select/SimpleSortSpecification.java @@ -31,8 +31,7 @@ public class SimpleSortSpecification implements SortSpecification { private final boolean isDescending; private SimpleSortSpecification(String name) { - this.name = Objects.requireNonNull(name); - this.isDescending = false; + this(name, false); } private SimpleSortSpecification(String name, boolean isDescending) { @@ -46,7 +45,7 @@ public SortSpecification descending() { } @Override - public String aliasOrName() { + public String orderByName() { return name; } diff --git a/src/main/java/org/mybatis/dynamic/sql/select/render/SelectRenderer.java b/src/main/java/org/mybatis/dynamic/sql/select/render/SelectRenderer.java index 6cb1208fc..fd092f6f2 100644 --- a/src/main/java/org/mybatis/dynamic/sql/select/render/SelectRenderer.java +++ b/src/main/java/org/mybatis/dynamic/sql/select/render/SelectRenderer.java @@ -74,7 +74,7 @@ private void renderOrderBy(FragmentCollector fragmentCollector, OrderByModel ord } private String calculateOrderByPhrase(SortSpecification column) { - String phrase = column.aliasOrName(); + String phrase = column.orderByName(); if (column.isDescending()) { phrase = phrase + " DESC"; //$NON-NLS-1$ } From 34b2fc7aeb0ae0876ad0f15ad339638ca16be07e Mon Sep 17 00:00:00 2001 From: Jeff Butler Date: Mon, 5 Oct 2020 13:34:51 -0400 Subject: [PATCH 2/5] Add sort specification for use in joins --- .../org/mybatis/dynamic/sql/SqlBuilder.java | 28 +++++++ .../sql/select/ColumnSortSpecification.java | 52 +++++++++++++ .../java/examples/joins/JoinMapperTest.java | 78 +++++++++++++++++++ 3 files changed, 158 insertions(+) create mode 100644 src/main/java/org/mybatis/dynamic/sql/select/ColumnSortSpecification.java diff --git a/src/main/java/org/mybatis/dynamic/sql/SqlBuilder.java b/src/main/java/org/mybatis/dynamic/sql/SqlBuilder.java index 05051bb58..28e21f90f 100644 --- a/src/main/java/org/mybatis/dynamic/sql/SqlBuilder.java +++ b/src/main/java/org/mybatis/dynamic/sql/SqlBuilder.java @@ -27,6 +27,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.ColumnSortSpecification; import org.mybatis.dynamic.sql.select.CountDSL; import org.mybatis.dynamic.sql.select.QueryExpressionDSL.FromGatherer; import org.mybatis.dynamic.sql.select.SelectDSL; @@ -737,10 +738,37 @@ static IsNotInCaseInsensitiveWhenPresent isNotInCaseInsensitiveWhenPresent(Colle } // order by support + + /** + * Creates a sort specification based on a String. This is useful when a column has been + * aliased in the select list. For example: + * + *
+     *     select(foo.as("bar"))
+     *     .from(baz)
+     *     .orderBy(sortColumn("bar"))
+     * 
+ * + * @param name the string to use as a sort specification + * @return a sort specification + */ static SortSpecification sortColumn(String name) { return SimpleSortSpecification.of(name); } + /** + * Creates a sort specification based on a column and a table alias. This can be useful in a join + * where the desired sort order is based on a column not in the select list. This will likely + * fail in union queries depending on database support. + * + * @param tableAlias the table alias + * @param column the column + * @return a sort specification + */ + static SortSpecification sortColumn(String tableAlias, SqlColumn column) { + return new ColumnSortSpecification(tableAlias, column); + } + class InsertIntoNextStep { private final SqlTable table; diff --git a/src/main/java/org/mybatis/dynamic/sql/select/ColumnSortSpecification.java b/src/main/java/org/mybatis/dynamic/sql/select/ColumnSortSpecification.java new file mode 100644 index 000000000..c43100056 --- /dev/null +++ b/src/main/java/org/mybatis/dynamic/sql/select/ColumnSortSpecification.java @@ -0,0 +1,52 @@ +/* + * Copyright 2016-2020 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 org.mybatis.dynamic.sql.SortSpecification; +import org.mybatis.dynamic.sql.SqlColumn; + +import java.util.Objects; + +public class ColumnSortSpecification implements SortSpecification { + private final String tableAlias; + private final SqlColumn column; + private final boolean isDescending; + + public ColumnSortSpecification(String tableAlias, SqlColumn column) { + this(tableAlias, column, false); + } + + private ColumnSortSpecification(String tableAlias, SqlColumn column, boolean isDescending) { + this.tableAlias = Objects.requireNonNull(tableAlias); + this.column = Objects.requireNonNull(column); + this.isDescending = isDescending; + } + + @Override + public SortSpecification descending() { + return new ColumnSortSpecification(tableAlias, column, true); + } + + @Override + public String orderByName() { + return tableAlias + "." + column.name(); //$NON-NLS-1$ + } + + @Override + public boolean isDescending() { + return isDescending; + } +} diff --git a/src/test/java/examples/joins/JoinMapperTest.java b/src/test/java/examples/joins/JoinMapperTest.java index 74832adf7..ada9f33ba 100644 --- a/src/test/java/examples/joins/JoinMapperTest.java +++ b/src/test/java/examples/joins/JoinMapperTest.java @@ -793,6 +793,84 @@ void testFullJoin3() { } } + @Test + void testFullJoin4() { + try (SqlSession session = sqlSessionFactory.openSession()) { + CommonSelectMapper mapper = session.getMapper(CommonSelectMapper.class); + + SelectStatementProvider selectStatement = select(orderLine.orderId, orderLine.quantity, 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, sortColumn("im", itemMaster.itemId)) + .build() + .render(RenderingStrategies.MYBATIS3); + + String expectedStatement = "select ol.order_id, ol.quantity, 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, im.item_id"; + assertThat(selectStatement.getSelectStatement()).isEqualTo(expectedStatement); + + List> rows = mapper.selectManyMappedRows(selectStatement); + + assertThat(rows).hasSize(6); + Map row = rows.get(0); + assertThat(row).doesNotContainKey("ORDER_ID"); + assertThat(row).doesNotContainKey("QUANTITY"); + assertThat(row).containsEntry("DESCRIPTION", "Catcher Glove"); + + row = rows.get(3); + assertThat(row).containsEntry("ORDER_ID", 2); + assertThat(row).containsEntry("QUANTITY", 6); + assertThat(row).doesNotContainKey("DESCRIPTION"); + + row = rows.get(5); + assertThat(row).containsEntry("ORDER_ID", 2); + assertThat(row).containsEntry("QUANTITY", 1); + assertThat(row).containsEntry("DESCRIPTION", "Outfield Glove"); + } + } + + @Test + void testFullJoin5() { + try (SqlSession session = sqlSessionFactory.openSession()) { + CommonSelectMapper mapper = session.getMapper(CommonSelectMapper.class); + + SelectStatementProvider selectStatement = select(orderLine.orderId, orderLine.quantity, 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, sortColumn("im", itemMaster.itemId).descending()) + .build() + .render(RenderingStrategies.MYBATIS3); + + String expectedStatement = "select ol.order_id, ol.quantity, 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, im.item_id DESC"; + assertThat(selectStatement.getSelectStatement()).isEqualTo(expectedStatement); + + List> rows = mapper.selectManyMappedRows(selectStatement); + + assertThat(rows).hasSize(6); + Map row = rows.get(0); + assertThat(row).doesNotContainKey("ORDER_ID"); + assertThat(row).doesNotContainKey("QUANTITY"); + assertThat(row).containsEntry("DESCRIPTION", "Catcher Glove"); + + row = rows.get(3); + assertThat(row).containsEntry("ORDER_ID", 2); + assertThat(row).containsEntry("QUANTITY", 6); + assertThat(row).doesNotContainKey("DESCRIPTION"); + + row = rows.get(5); + assertThat(row).containsEntry("ORDER_ID", 2); + assertThat(row).containsEntry("QUANTITY", 1); + assertThat(row).containsEntry("DESCRIPTION", "Helmet"); + } + } + @Test void testFullJoinNoAliases() { try (SqlSession session = sqlSessionFactory.openSession()) { From 1dcb0489b06d2d0df11a4b0468afaddc518d23ea Mon Sep 17 00:00:00 2001 From: Jeff Butler Date: Mon, 5 Oct 2020 13:38:39 -0400 Subject: [PATCH 3/5] Checkstyle --- .../mybatis/dynamic/sql/select/ColumnSortSpecification.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/mybatis/dynamic/sql/select/ColumnSortSpecification.java b/src/main/java/org/mybatis/dynamic/sql/select/ColumnSortSpecification.java index c43100056..1c80baf3b 100644 --- a/src/main/java/org/mybatis/dynamic/sql/select/ColumnSortSpecification.java +++ b/src/main/java/org/mybatis/dynamic/sql/select/ColumnSortSpecification.java @@ -15,11 +15,11 @@ */ package org.mybatis.dynamic.sql.select; +import java.util.Objects; + import org.mybatis.dynamic.sql.SortSpecification; import org.mybatis.dynamic.sql.SqlColumn; -import java.util.Objects; - public class ColumnSortSpecification implements SortSpecification { private final String tableAlias; private final SqlColumn column; From 02bb4b94229f5e8256048b41908486154006a21a Mon Sep 17 00:00:00 2001 From: Jeff Butler Date: Mon, 5 Oct 2020 14:14:56 -0400 Subject: [PATCH 4/5] Documentation --- CHANGELOG.md | 7 +++++++ src/site/markdown/docs/select.md | 15 ++++++++++++--- 2 files changed, 19 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index fdcf2e925..309fe5c2e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,13 @@ This log will detail notable changes to MyBatis Dynamic SQL. Full details are available on the GitHub milestone pages. +## Release 1.3.0 - Unreleased + +### Added + +- Added a new sort specification that is useful in selects with joins + + ## Release 1.2.1 - September 29, 2020 GitHub milestone: [https://github.com/mybatis/mybatis-dynamic-sql/issues?q=milestone%3A1.2.1+](https://github.com/mybatis/mybatis-dynamic-sql/issues?q=milestone%3A1.2.1+) diff --git a/src/site/markdown/docs/select.md b/src/site/markdown/docs/select.md index ef476480c..c846a34e4 100644 --- a/src/site/markdown/docs/select.md +++ b/src/site/markdown/docs/select.md @@ -176,13 +176,22 @@ The XML element should look like this: ## Notes on Order By Order by phrases can be difficult to calculate when there are aliased columns, aliased tables, unions, and joins. -This library has taken a simple approach - the library will either write the column alias or the column -name into the order by phrase. For the order by phrase, the table alias (if there is one) will be ignored. +This library has taken a relatively simple approach: + +1. When specifying an SqlColumn in an ORDER BY phrase the library will either write the column alias or the column +name into the ORDER BY phrase. For the ORDER BY phrase, the table alias (if there is one) will be ignored. Use this pattern +when the ORDER BY column is a member of the select list. For example `orderBy(foo)`. If the column has an alias, then +it is easist to use the "arbitrary string" method with the column alias as shown below. +1. It is also possible to explicitly specify a table alias for a column in an ORDER BY phrase. Use this pattern when +there is a join, and the ORDER BY column is in two or more tables, and the ORDER BY column is not in the select +list. For example `orderBy(sortColumn("t1", foo))`. +1. If none of the above use cases meet your needs, then you can specify an arbitrary String to write into the rendered ORDER BY +phrase (see below for an example). In our testing, this caused an issue in only one case. When there is an outer join and the select list contains both the left and right join column. In that case, the workaround is to supply a column alias for both columns. -When using a column function (lower, upper, etc.), then is is customary to give the calculated column an alias so you will have a predictable result set. In cases like this there will not be a column to use for an alias. The library supports arbitrary values in an ORDER BY expression like this: +When using a column function (lower, upper, etc.), then it is customary to give the calculated column an alias so you will have a predictable result set. In cases like this there will not be a column to use for an alias. The library supports arbitrary values in an ORDER BY expression like this: ```java SelectStatementProvider selectStatement = select(substring(gender, 1, 1).as("ShortGender"), avg(age).as("AverageAge")) From ee204a86635b51e322a8dba5c38c1e587e11d7f0 Mon Sep 17 00:00:00 2001 From: Jeff Butler Date: Mon, 5 Oct 2020 14:18:15 -0400 Subject: [PATCH 5/5] Update changelog --- CHANGELOG.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 309fe5c2e..7252e37c3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,8 +6,7 @@ This log will detail notable changes to MyBatis Dynamic SQL. Full details are av ### Added -- Added a new sort specification that is useful in selects with joins - +- Added a new sort specification that is useful in selects with joins ([#269](https://github.com/mybatis/mybatis-dynamic-sql/pull/269)) ## Release 1.2.1 - September 29, 2020