You are viewing a plain text version of this content. The canonical link for it is here.
Posted to notifications@shardingsphere.apache.org by hu...@apache.org on 2026/03/10 06:07:59 UTC

(shardingsphere) branch master updated: Support select order by index bind metadata (#38386)

This is an automated email from the ASF dual-hosted git repository.

hucong pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/shardingsphere.git


The following commit(s) were added to refs/heads/master by this push:
     new 76a95be97cb Support select order by index bind metadata (#38386)
76a95be97cb is described below

commit 76a95be97cb095fb2c41a39fbdaae87e6b39d3d4
Author: Zhengqiang Duan <du...@apache.org>
AuthorDate: Tue Mar 10 14:07:53 2026 +0800

    Support select order by index bind metadata (#38386)
---
 RELEASE-NOTES.md                                   |  1 +
 .../order/item/IndexOrderByItemSegmentBinder.java  | 64 +++++++++++++++++++++
 .../dml/order/item/OrderByItemSegmentBinder.java   |  4 ++
 .../item/IndexOrderByItemSegmentBinderTest.java    | 65 ++++++++++++++++++++++
 .../dml/order/item/IndexOrderByItemSegment.java    | 16 ++++++
 .../binder/src/test/resources/cases/dml/select.xml | 62 ++++++++++++++++++++-
 .../binder/src/test/resources/sqls/dml/select.xml  |  1 +
 .../asserts/segment/orderby/OrderByItemAssert.java |  4 ++
 .../item/impl/ExpectedIndexOrderByItem.java        |  5 ++
 9 files changed, 221 insertions(+), 1 deletion(-)

diff --git a/RELEASE-NOTES.md b/RELEASE-NOTES.md
index c50fb017f3c..d783dcb0830 100644
--- a/RELEASE-NOTES.md
+++ b/RELEASE-NOTES.md
@@ -14,6 +14,7 @@
 1. SQL Parser: Support parsing MySQL SELECT sql - [#38233](https://github.com/apache/shardingsphere/pull/38233)
 1. SQL Parser: Support parsing MySQL CREATE sql - [#38237](https://github.com/apache/shardingsphere/pull/38237)
 1. SQL Parser: Support mysql subquery table projection alias visit to sql statement - [#38320](https://github.com/apache/shardingsphere/pull/38320)
+1. SQL Binder: Support select order by index bind metadata - [#38386](https://github.com/apache/shardingsphere/pull/38386)
 1. JDBC: Support setMaxRows and getMaxRows method in jdbc when not execute sql - [#38337](https://github.com/apache/shardingsphere/pull/38337)
 1. Sharding: Fix HASH_MOD routing mismatch for same negative numeric values across numeric Java types with compatibility switch `normalize-numeric-int-range` - [#38327](https://github.com/apache/shardingsphere/pull/38327)
 
diff --git a/infra/binder/core/src/main/java/org/apache/shardingsphere/infra/binder/engine/segment/dml/order/item/IndexOrderByItemSegmentBinder.java b/infra/binder/core/src/main/java/org/apache/shardingsphere/infra/binder/engine/segment/dml/order/item/IndexOrderByItemSegmentBinder.java
new file mode 100644
index 00000000000..fcba507f686
--- /dev/null
+++ b/infra/binder/core/src/main/java/org/apache/shardingsphere/infra/binder/engine/segment/dml/order/item/IndexOrderByItemSegmentBinder.java
@@ -0,0 +1,64 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.apache.shardingsphere.infra.binder.engine.segment.dml.order.item;
+
+import com.cedarsoftware.util.CaseInsensitiveMap.CaseInsensitiveString;
+import com.google.common.collect.Multimap;
+import lombok.AccessLevel;
+import lombok.NoArgsConstructor;
+import org.apache.shardingsphere.infra.binder.engine.segment.dml.from.context.TableSegmentBinderContext;
+import org.apache.shardingsphere.sql.parser.statement.core.segment.dml.column.ColumnSegment;
+import org.apache.shardingsphere.sql.parser.statement.core.segment.dml.item.ColumnProjectionSegment;
+import org.apache.shardingsphere.sql.parser.statement.core.segment.dml.item.ProjectionSegment;
+import org.apache.shardingsphere.sql.parser.statement.core.segment.dml.order.item.IndexOrderByItemSegment;
+
+import java.util.Optional;
+
+/**
+ * Index order by item segment binder.
+ */
+@NoArgsConstructor(access = AccessLevel.PRIVATE)
+public final class IndexOrderByItemSegmentBinder {
+    
+    /**
+     * Bind index order by item segment.
+     *
+     * @param segment index order by item segment
+     * @param tableBinderContexts table binder contexts
+     * @return bound index order by item segment
+     */
+    public static IndexOrderByItemSegment bind(final IndexOrderByItemSegment segment, final Multimap<CaseInsensitiveString, TableSegmentBinderContext> tableBinderContexts) {
+        IndexOrderByItemSegment result = new IndexOrderByItemSegment(segment.getStartIndex(),
+                segment.getStopIndex(), segment.getColumnIndex(), segment.getOrderDirection(), segment.getNullsOrderType().orElse(null));
+        findBoundColumn(segment.getColumnIndex(), tableBinderContexts).ifPresent(result::setBoundColumn);
+        return result;
+    }
+    
+    private static Optional<ColumnSegment> findBoundColumn(final int columnIndex, final Multimap<CaseInsensitiveString, TableSegmentBinderContext> tableBinderContexts) {
+        for (TableSegmentBinderContext each : tableBinderContexts.values()) {
+            int index = 1;
+            for (ProjectionSegment projectionSegment : each.getProjectionSegments()) {
+                if (index == columnIndex) {
+                    return projectionSegment instanceof ColumnProjectionSegment ? Optional.of(((ColumnProjectionSegment) projectionSegment).getColumn()) : Optional.empty();
+                }
+                index++;
+            }
+        }
+        return Optional.empty();
+    }
+}
diff --git a/infra/binder/core/src/main/java/org/apache/shardingsphere/infra/binder/engine/segment/dml/order/item/OrderByItemSegmentBinder.java b/infra/binder/core/src/main/java/org/apache/shardingsphere/infra/binder/engine/segment/dml/order/item/OrderByItemSegmentBinder.java
index c967cc415af..72d30e906c0 100644
--- a/infra/binder/core/src/main/java/org/apache/shardingsphere/infra/binder/engine/segment/dml/order/item/OrderByItemSegmentBinder.java
+++ b/infra/binder/core/src/main/java/org/apache/shardingsphere/infra/binder/engine/segment/dml/order/item/OrderByItemSegmentBinder.java
@@ -26,6 +26,7 @@ import org.apache.shardingsphere.infra.binder.engine.segment.dml.from.context.Ta
 import org.apache.shardingsphere.infra.binder.engine.statement.SQLStatementBinderContext;
 import org.apache.shardingsphere.sql.parser.statement.core.segment.dml.order.item.ColumnOrderByItemSegment;
 import org.apache.shardingsphere.sql.parser.statement.core.segment.dml.order.item.ExpressionOrderByItemSegment;
+import org.apache.shardingsphere.sql.parser.statement.core.segment.dml.order.item.IndexOrderByItemSegment;
 import org.apache.shardingsphere.sql.parser.statement.core.segment.dml.order.item.OrderByItemSegment;
 
 /**
@@ -53,6 +54,9 @@ public final class OrderByItemSegmentBinder {
         if (segment instanceof ExpressionOrderByItemSegment) {
             return ExpressionOrderByItemSegmentBinder.bind((ExpressionOrderByItemSegment) segment, binderContext, tableBinderContexts, outerTableBinderContexts, segmentType);
         }
+        if (segment instanceof IndexOrderByItemSegment) {
+            return IndexOrderByItemSegmentBinder.bind((IndexOrderByItemSegment) segment, tableBinderContexts);
+        }
         return segment;
     }
 }
diff --git a/infra/binder/core/src/test/java/org/apache/shardingsphere/infra/binder/engine/segment/dml/order/item/IndexOrderByItemSegmentBinderTest.java b/infra/binder/core/src/test/java/org/apache/shardingsphere/infra/binder/engine/segment/dml/order/item/IndexOrderByItemSegmentBinderTest.java
new file mode 100644
index 00000000000..f0e93d2755d
--- /dev/null
+++ b/infra/binder/core/src/test/java/org/apache/shardingsphere/infra/binder/engine/segment/dml/order/item/IndexOrderByItemSegmentBinderTest.java
@@ -0,0 +1,65 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.apache.shardingsphere.infra.binder.engine.segment.dml.order.item;
+
+import com.cedarsoftware.util.CaseInsensitiveMap.CaseInsensitiveString;
+import com.google.common.collect.LinkedHashMultimap;
+import com.google.common.collect.Multimap;
+import org.apache.shardingsphere.database.connector.core.metadata.database.enums.NullsOrderType;
+import org.apache.shardingsphere.infra.binder.engine.segment.dml.from.context.TableSegmentBinderContext;
+import org.apache.shardingsphere.infra.binder.engine.segment.dml.from.context.type.SimpleTableSegmentBinderContext;
+import org.apache.shardingsphere.sql.parser.statement.core.enums.OrderDirection;
+import org.apache.shardingsphere.sql.parser.statement.core.enums.TableSourceType;
+import org.apache.shardingsphere.sql.parser.statement.core.segment.dml.column.ColumnSegment;
+import org.apache.shardingsphere.sql.parser.statement.core.segment.dml.item.ColumnProjectionSegment;
+import org.apache.shardingsphere.sql.parser.statement.core.segment.dml.order.item.IndexOrderByItemSegment;
+import org.apache.shardingsphere.sql.parser.statement.core.segment.generic.bound.ColumnSegmentBoundInfo;
+import org.apache.shardingsphere.sql.parser.statement.core.segment.generic.bound.TableSegmentBoundInfo;
+import org.apache.shardingsphere.sql.parser.statement.core.value.identifier.IdentifierValue;
+import org.junit.jupiter.api.Test;
+
+import java.util.Collections;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.is;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+class IndexOrderByItemSegmentBinderTest {
+    
+    @Test
+    void assertBind() {
+        ColumnSegment columnSegment = new ColumnSegment(0, 0, new IdentifierValue("user_name"));
+        columnSegment.setColumnBoundInfo(new ColumnSegmentBoundInfo(new TableSegmentBoundInfo(new IdentifierValue("foo_db"), new IdentifierValue("")),
+                new IdentifierValue("t_user"), new IdentifierValue("user_name"), TableSourceType.PHYSICAL_TABLE));
+        Multimap<CaseInsensitiveString, TableSegmentBinderContext> tableBinderContexts = LinkedHashMultimap.create();
+        tableBinderContexts.put(CaseInsensitiveString.of(""), new SimpleTableSegmentBinderContext(Collections.singleton(new ColumnProjectionSegment(columnSegment)), TableSourceType.MIXED_TABLE));
+        IndexOrderByItemSegment actual = IndexOrderByItemSegmentBinder.bind(new IndexOrderByItemSegment(6, 6, 1, OrderDirection.ASC, NullsOrderType.FIRST), tableBinderContexts);
+        assertTrue(actual.getBoundColumn().isPresent());
+        assertThat(actual.getBoundColumn().get().getColumnBoundInfo().getOriginalTable().getValue(), is("t_user"));
+        assertThat(actual.getBoundColumn().get().getColumnBoundInfo().getOriginalColumn().getValue(), is("user_name"));
+    }
+    
+    @Test
+    void assertBindWithoutBoundColumn() {
+        Multimap<CaseInsensitiveString, TableSegmentBinderContext> tableBinderContexts = LinkedHashMultimap.create();
+        tableBinderContexts.put(CaseInsensitiveString.of(""), new SimpleTableSegmentBinderContext(Collections.emptyList(), TableSourceType.MIXED_TABLE));
+        IndexOrderByItemSegment actual = IndexOrderByItemSegmentBinder.bind(new IndexOrderByItemSegment(6, 6, 1, OrderDirection.ASC, NullsOrderType.FIRST), tableBinderContexts);
+        assertFalse(actual.getBoundColumn().isPresent());
+    }
+}
diff --git a/parser/sql/statement/core/src/main/java/org/apache/shardingsphere/sql/parser/statement/core/segment/dml/order/item/IndexOrderByItemSegment.java b/parser/sql/statement/core/src/main/java/org/apache/shardingsphere/sql/parser/statement/core/segment/dml/order/item/IndexOrderByItemSegment.java
index 24ef2c1f36f..c202fd259e9 100644
--- a/parser/sql/statement/core/src/main/java/org/apache/shardingsphere/sql/parser/statement/core/segment/dml/order/item/IndexOrderByItemSegment.java
+++ b/parser/sql/statement/core/src/main/java/org/apache/shardingsphere/sql/parser/statement/core/segment/dml/order/item/IndexOrderByItemSegment.java
@@ -18,8 +18,12 @@
 package org.apache.shardingsphere.sql.parser.statement.core.segment.dml.order.item;
 
 import lombok.Getter;
+import lombok.Setter;
 import org.apache.shardingsphere.database.connector.core.metadata.database.enums.NullsOrderType;
 import org.apache.shardingsphere.sql.parser.statement.core.enums.OrderDirection;
+import org.apache.shardingsphere.sql.parser.statement.core.segment.dml.column.ColumnSegment;
+
+import java.util.Optional;
 
 /**
  * Order by item segment for index.
@@ -29,8 +33,20 @@ public final class IndexOrderByItemSegment extends OrderByItemSegment {
     
     private final int columnIndex;
     
+    @Setter
+    private ColumnSegment boundColumn;
+    
     public IndexOrderByItemSegment(final int startIndex, final int stopIndex, final int columnIndex, final OrderDirection orderDirection, final NullsOrderType nullsOrderType) {
         super(startIndex, stopIndex, orderDirection, nullsOrderType);
         this.columnIndex = columnIndex;
     }
+    
+    /**
+     * Get bound column.
+     *
+     * @return bound column
+     */
+    public Optional<ColumnSegment> getBoundColumn() {
+        return Optional.ofNullable(boundColumn);
+    }
 }
diff --git a/test/it/binder/src/test/resources/cases/dml/select.xml b/test/it/binder/src/test/resources/cases/dml/select.xml
index 0f18d928dfa..c3f50b33ab0 100644
--- a/test/it/binder/src/test/resources/cases/dml/select.xml
+++ b/test/it/binder/src/test/resources/cases/dml/select.xml
@@ -669,7 +669,67 @@
             </simple-table>
         </from>
     </select>
-
+    
+    <select sql-case-id="select_with_order_by_index_bound">
+        <projections start-index="7" stop-index="31">
+            <column-projection name="user_id" start-index="7" stop-index="13">
+                <column-bound>
+                    <original-database name="foo_db_1" />
+                    <original-schema name="foo_db_1" />
+                    <original-table name="t_user" />
+                    <original-column name="user_id" start-delimiter="`" end-delimiter="`" />
+                    <table-source-type name="PHYSICAL_TABLE"/>
+                </column-bound>
+            </column-projection>
+            <column-projection name="user_name" start-index="16" stop-index="24">
+                <column-bound>
+                    <original-database name="foo_db_1" />
+                    <original-schema name="foo_db_1" />
+                    <original-table name="t_user" />
+                    <original-column name="user_name" start-delimiter="`" end-delimiter="`" />
+                    <table-source-type name="PHYSICAL_TABLE"/>
+                </column-bound>
+            </column-projection>
+            <column-projection name="email" start-index="27" stop-index="31">
+                <column-bound>
+                    <original-database name="foo_db_1" />
+                    <original-schema name="foo_db_1" />
+                    <original-table name="t_user" />
+                    <original-column name="email" start-delimiter="`" end-delimiter="`" />
+                    <table-source-type name="PHYSICAL_TABLE"/>
+                </column-bound>
+            </column-projection>
+        </projections>
+        <from start-index="33" stop-index="43">
+            <simple-table name="t_user" start-index="38" stop-index="43">
+                <table-bound>
+                    <original-database name="foo_db_1" />
+                    <original-schema name="foo_db_1" />
+                </table-bound>
+            </simple-table>
+        </from>
+        <order-by>
+            <index-item index="2" order-direction="ASC" start-index="54" stop-index="54">
+                <column-bound>
+                    <original-database name="foo_db_1" />
+                    <original-schema name="foo_db_1" />
+                    <original-table name="t_user" />
+                    <original-column name="user_name" start-delimiter="`" end-delimiter="`" />
+                    <table-source-type name="PHYSICAL_TABLE"/>
+                </column-bound>
+            </index-item>
+            <index-item index="1" order-direction="DESC" start-index="61" stop-index="61">
+                <column-bound>
+                    <original-database name="foo_db_1" />
+                    <original-schema name="foo_db_1" />
+                    <original-table name="t_user" />
+                    <original-column name="user_id" start-delimiter="`" end-delimiter="`" />
+                    <table-source-type name="PHYSICAL_TABLE"/>
+                </column-bound>
+            </index-item>
+        </order-by>
+    </select>
+    
     <select sql-case-id="select_with_multiple_ctes_left_join_and_order_by">
         <with start-index="0" stop-index="142">
             <common-table-expression name="user_orders" start-index="5" stop-index="88">
diff --git a/test/it/binder/src/test/resources/sqls/dml/select.xml b/test/it/binder/src/test/resources/sqls/dml/select.xml
index c0d19ed2534..1d5c6f549f8 100644
--- a/test/it/binder/src/test/resources/sqls/dml/select.xml
+++ b/test/it/binder/src/test/resources/sqls/dml/select.xml
@@ -23,6 +23,7 @@
     <sql-case id="select_with_clause_with_multiple_cte_definitions" value="WITH cte1(status, user_id) AS (SELECT status, user_id FROM t_order a), cte2(user_id, item_id) AS (SELECT user_id, item_id FROM t_order_item b) SELECT status, user_id, item_id FROM cte1 INNER JOIN cte2 ON cte1.user_id = cte2.user_id" db-types="MySQL" />
     <sql-case id="select_with_with_clause_with_column_definition" value="WITH t_order_tmp (col1, col2, col3, col4, col5, col6) AS (SELECT * FROM t_order o) SELECT col1 FROM t_order_tmp" db-types="MySQL"/>
     <sql-case id="select_with_multiple_ctes_left_join_and_order_by" value="WITH user_orders AS (SELECT user_id, COUNT(*) AS order_cnt FROM t_order GROUP BY user_id), user_info AS (SELECT user_id, user_name FROM t_user) SELECT ui.user_name, uo.order_cnt FROM user_info ui LEFT JOIN user_orders uo ON ui.user_id = uo.user_id ORDER BY ui.user_id, ui.user_name" db-types="MySQL"/>
+    <sql-case id="select_with_order_by_index_bound" value="SELECT user_id, user_name, email FROM t_user ORDER BY 2 ASC, 1 DESC" db-types="MySQL"/>
     <sql-case id="select_with_current_select_projection_reference" value="SELECT order_id AS orderId, (SELECT orderId) AS tempOrderId FROM t_order" db-types="MySQL"/>
     <sql-case id="select_with_subquery_table_alias_columns_and_order_by" value="SELECT * FROM (SELECT user_id, user_name FROM t_user WHERE user_id &lt;= 10 UNION SELECT merchant_id, merchant_name FROM t_merchant WHERE merchant_id &lt;= 5) AS combined(id, name) ORDER BY id" db-types="MySQL"/>
     <sql-case id="select_with_outer_join_operator" value="SELECT o.order_id FROM t_order o, t_order_item i WHERE o.order_id = i.order_id(+)" db-types="Oracle"/>
diff --git a/test/it/parser/src/main/java/org/apache/shardingsphere/test/it/sql/parser/internal/asserts/segment/orderby/OrderByItemAssert.java b/test/it/parser/src/main/java/org/apache/shardingsphere/test/it/sql/parser/internal/asserts/segment/orderby/OrderByItemAssert.java
index c9e2e0ad9e1..8b4279bc8e9 100644
--- a/test/it/parser/src/main/java/org/apache/shardingsphere/test/it/sql/parser/internal/asserts/segment/orderby/OrderByItemAssert.java
+++ b/test/it/parser/src/main/java/org/apache/shardingsphere/test/it/sql/parser/internal/asserts/segment/orderby/OrderByItemAssert.java
@@ -120,6 +120,10 @@ public final class OrderByItemAssert {
     private static void assertIndexOrderByItem(final SQLCaseAssertContext assertContext,
                                                final IndexOrderByItemSegment actual, final ExpectedIndexOrderByItem expected, final String type) {
         assertThat(assertContext.getText(String.format("%s item index assertion error: ", type)), actual.getColumnIndex(), is(expected.getIndex()));
+        if (null != expected.getColumnBound()) {
+            assertTrue(actual.getBoundColumn().isPresent(), assertContext.getText("Actual bound column should exist."));
+            ColumnBoundAssert.assertIs(assertContext, actual.getBoundColumn().get().getColumnBoundInfo(), expected.getColumnBound());
+        }
         SQLSegmentAssert.assertIs(assertContext, actual, expected);
     }
     
diff --git a/test/it/parser/src/main/java/org/apache/shardingsphere/test/it/sql/parser/internal/cases/parser/jaxb/segment/impl/orderby/item/impl/ExpectedIndexOrderByItem.java b/test/it/parser/src/main/java/org/apache/shardingsphere/test/it/sql/parser/internal/cases/parser/jaxb/segment/impl/orderby/item/impl/ExpectedIndexOrderByItem.java
index a5183a107d7..ee9e9a1f1c2 100644
--- a/test/it/parser/src/main/java/org/apache/shardingsphere/test/it/sql/parser/internal/cases/parser/jaxb/segment/impl/orderby/item/impl/ExpectedIndexOrderByItem.java
+++ b/test/it/parser/src/main/java/org/apache/shardingsphere/test/it/sql/parser/internal/cases/parser/jaxb/segment/impl/orderby/item/impl/ExpectedIndexOrderByItem.java
@@ -19,9 +19,11 @@ package org.apache.shardingsphere.test.it.sql.parser.internal.cases.parser.jaxb.
 
 import lombok.Getter;
 import lombok.Setter;
+import org.apache.shardingsphere.test.it.sql.parser.internal.cases.parser.jaxb.segment.impl.bound.ExpectedColumnBoundInfo;
 import org.apache.shardingsphere.test.it.sql.parser.internal.cases.parser.jaxb.segment.impl.orderby.item.ExpectedOrderByItem;
 
 import javax.xml.bind.annotation.XmlAttribute;
+import javax.xml.bind.annotation.XmlElement;
 
 /**
  * Expected index order by item.
@@ -32,4 +34,7 @@ public final class ExpectedIndexOrderByItem extends ExpectedOrderByItem {
     
     @XmlAttribute
     private int index;
+    
+    @XmlElement(name = "column-bound")
+    private ExpectedColumnBoundInfo columnBound;
 }