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

terrymanu 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 e81c7c9aa5b Fix nullable SQL normalization in projection matching 
(#40226)
e81c7c9aa5b is described below

commit e81c7c9aa5b49f6c1939eb62ec5c9f6c70e2491d
Author: Liang Zhang <[email protected]>
AuthorDate: Sun Oct 18 23:22:30 2026 +0800

    Fix nullable SQL normalization in projection matching (#40226)
---
 .../engine/ProjectionsContextEngine.java           | 24 ++++---
 .../engine/ProjectionsContextEngineTest.java       | 84 ++++++++++++++++++++++
 .../sql/parser/statement/core/util/SQLUtils.java   | 15 ++--
 .../parser/statement/core/util/SQLUtilsTest.java   |  5 ++
 4 files changed, 111 insertions(+), 17 deletions(-)

diff --git 
a/infra/binder/core/src/main/java/org/apache/shardingsphere/infra/binder/context/segment/select/projection/engine/ProjectionsContextEngine.java
 
b/infra/binder/core/src/main/java/org/apache/shardingsphere/infra/binder/context/segment/select/projection/engine/ProjectionsContextEngine.java
index 7b1029036ae..8e84a039726 100644
--- 
a/infra/binder/core/src/main/java/org/apache/shardingsphere/infra/binder/context/segment/select/projection/engine/ProjectionsContextEngine.java
+++ 
b/infra/binder/core/src/main/java/org/apache/shardingsphere/infra/binder/context/segment/select/projection/engine/ProjectionsContextEngine.java
@@ -27,6 +27,7 @@ import 
org.apache.shardingsphere.infra.binder.context.segment.select.projection.
 import 
org.apache.shardingsphere.infra.binder.context.segment.select.projection.impl.ColumnProjection;
 import 
org.apache.shardingsphere.infra.binder.context.segment.select.projection.impl.DerivedProjection;
 import 
org.apache.shardingsphere.infra.binder.context.segment.select.projection.impl.ShorthandProjection;
+import org.apache.shardingsphere.infra.exception.ShardingSpherePreconditions;
 import 
org.apache.shardingsphere.sql.parser.statement.core.segment.dml.column.ColumnSegment;
 import 
org.apache.shardingsphere.sql.parser.statement.core.segment.dml.item.ProjectionSegment;
 import 
org.apache.shardingsphere.sql.parser.statement.core.segment.dml.item.ProjectionsSegment;
@@ -99,26 +100,27 @@ public final class ProjectionsContextEngine {
         if (orderByItem instanceof IndexOrderByItemSegment) {
             return true;
         }
+        String normalizedText = 
SQLUtils.getExactlyValue(((TextOrderByItemSegment) orderByItem).getText());
+        ShardingSpherePreconditions.checkNotNull(normalizedText, () -> new 
IllegalStateException("Order item text must not be null."));
         for (Projection each : projections) {
-            if (orderByItem instanceof ColumnOrderByItemSegment && 
isSameColumn(each, ((ColumnOrderByItemSegment) orderByItem).getColumn())) {
+            if (orderByItem instanceof ColumnOrderByItemSegment && 
isSameColumn(each, ((ColumnOrderByItemSegment) orderByItem).getColumn(), 
normalizedText)) {
                 return true;
             }
-            String text = ((TextOrderByItemSegment) orderByItem).getText();
-            if (isSameAlias(each, text) || isSameQualifiedName(each, text)) {
+            if (isSameAlias(each, normalizedText) || isSameQualifiedName(each, 
normalizedText)) {
                 return true;
             }
         }
         return false;
     }
     
-    private boolean isSameColumn(final Projection projection, final 
ColumnSegment columnSegment) {
+    private boolean isSameColumn(final Projection projection, final 
ColumnSegment columnSegment, final String normalizedText) {
         Collection<ColumnProjection> columns = 
getColumnProjections(projection);
         if (columns.isEmpty()) {
             return false;
         }
         boolean columnSegmentPresent = columnSegment.getOwner().isPresent();
         for (ColumnProjection each : columns) {
-            if (columnSegmentPresent ? isSameQualifiedName(each, 
columnSegment.getQualifiedName()) : isSameName(each, 
columnSegment.getQualifiedName())) {
+            if (columnSegmentPresent ? isSameQualifiedName(each, 
normalizedText) : isSameName(each, normalizedText)) {
                 return true;
             }
         }
@@ -136,15 +138,15 @@ public final class ProjectionsContextEngine {
         return result;
     }
     
-    private boolean isSameName(final ColumnProjection projection, final String 
text) {
-        return 
SQLUtils.getExactlyValue(text).equalsIgnoreCase(projection.getName().getValue());
+    private boolean isSameName(final ColumnProjection projection, final String 
normalizedText) {
+        return 
normalizedText.equalsIgnoreCase(projection.getName().getValue());
     }
     
-    private boolean isSameAlias(final Projection projection, final String 
text) {
-        return projection.getAlias().isPresent() && 
SQLUtils.getExactlyValue(text).equalsIgnoreCase(SQLUtils.getExactlyValue(projection.getAlias().get().getValue()));
+    private boolean isSameAlias(final Projection projection, final String 
normalizedText) {
+        return projection.getAlias().isPresent() && 
normalizedText.equalsIgnoreCase(SQLUtils.getExactlyValue(projection.getAlias().get().getValue()));
     }
     
-    private boolean isSameQualifiedName(final Projection projection, final 
String text) {
-        return 
SQLUtils.getExactlyValue(text).equalsIgnoreCase(SQLUtils.getExactlyValue(projection.getExpression()));
+    private boolean isSameQualifiedName(final Projection projection, final 
String normalizedText) {
+        return 
normalizedText.equalsIgnoreCase(SQLUtils.getExactlyValue(projection.getExpression()));
     }
 }
diff --git 
a/infra/binder/core/src/test/java/org/apache/shardingsphere/infra/binder/context/segment/select/projection/engine/ProjectionsContextEngineTest.java
 
b/infra/binder/core/src/test/java/org/apache/shardingsphere/infra/binder/context/segment/select/projection/engine/ProjectionsContextEngineTest.java
index 9f79ffd4789..dffafabc4df 100644
--- 
a/infra/binder/core/src/test/java/org/apache/shardingsphere/infra/binder/context/segment/select/projection/engine/ProjectionsContextEngineTest.java
+++ 
b/infra/binder/core/src/test/java/org/apache/shardingsphere/infra/binder/context/segment/select/projection/engine/ProjectionsContextEngineTest.java
@@ -27,11 +27,13 @@ import 
org.apache.shardingsphere.infra.spi.type.typed.TypedSPILoader;
 import 
org.apache.shardingsphere.sql.parser.statement.core.enums.OrderDirection;
 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.ExpressionProjectionSegment;
 import 
org.apache.shardingsphere.sql.parser.statement.core.segment.dml.item.ProjectionsSegment;
 import 
org.apache.shardingsphere.sql.parser.statement.core.segment.dml.item.ShorthandProjectionSegment;
 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.generic.AliasSegment;
 import 
org.apache.shardingsphere.sql.parser.statement.core.segment.generic.OwnerSegment;
 import 
org.apache.shardingsphere.sql.parser.statement.core.segment.generic.bound.TableSegmentBoundInfo;
 import 
org.apache.shardingsphere.sql.parser.statement.core.value.identifier.IdentifierValue;
@@ -43,6 +45,7 @@ import java.util.Collections;
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.hamcrest.Matchers.is;
 import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertThrows;
 
 class ProjectionsContextEngineTest {
     
@@ -174,4 +177,85 @@ class ProjectionsContextEngineTest {
         ProjectionsContext actual = 
engine.createProjectionsContext(projectionsSegment, new 
GroupByContext(Collections.emptyList()), orderByContext);
         assertThat(actual.getProjections().size(), is(2));
     }
+    
+    @Test
+    void assertCreateProjectionsContextWithNullOrderByText() {
+        ProjectionsSegment projectionsSegment = new ProjectionsSegment(0, 0);
+        projectionsSegment.getProjections().add(new 
ShorthandProjectionSegment(0, 0));
+        OrderByItem orderByItem = new OrderByItem(new 
ExpressionOrderByItemSegment(0, 0, null, OrderDirection.ASC, 
NullsOrderType.FIRST));
+        ProjectionsContextEngine engine = new 
ProjectionsContextEngine(databaseType);
+        IllegalStateException actual = 
assertThrows(IllegalStateException.class, () -> 
engine.createProjectionsContext(projectionsSegment, new 
GroupByContext(Collections.emptyList()),
+                new OrderByContext(Collections.singleton(orderByItem), 
false)));
+        assertThat(actual.getMessage(), is("Order item text must not be 
null."));
+    }
+    
+    @Test
+    void assertCreateProjectionsContextWithNullGroupByText() {
+        ProjectionsSegment projectionsSegment = new ProjectionsSegment(0, 0);
+        projectionsSegment.getProjections().add(new 
ShorthandProjectionSegment(0, 0));
+        OrderByItem groupByItem = new OrderByItem(new 
ExpressionOrderByItemSegment(0, 0, null, OrderDirection.ASC, 
NullsOrderType.FIRST));
+        ProjectionsContextEngine engine = new 
ProjectionsContextEngine(databaseType);
+        IllegalStateException actual = 
assertThrows(IllegalStateException.class, () -> 
engine.createProjectionsContext(projectionsSegment, new 
GroupByContext(Collections.singleton(groupByItem)),
+                new OrderByContext(Collections.emptyList(), false)));
+        assertThat(actual.getMessage(), is("Order item text must not be 
null."));
+    }
+    
+    @Test
+    void 
assertCreateProjectionsContextWithNullOrderByTextAndEmptyProjections() {
+        ProjectionsSegment projectionsSegment = new ProjectionsSegment(0, 0);
+        OrderByItem orderByItem = new OrderByItem(new 
ExpressionOrderByItemSegment(0, 0, null, OrderDirection.ASC, 
NullsOrderType.FIRST));
+        ProjectionsContextEngine engine = new 
ProjectionsContextEngine(databaseType);
+        IllegalStateException actual = 
assertThrows(IllegalStateException.class, () -> 
engine.createProjectionsContext(projectionsSegment, new 
GroupByContext(Collections.emptyList()),
+                new OrderByContext(Collections.singleton(orderByItem), 
false)));
+        assertThat(actual.getMessage(), is("Order item text must not be 
null."));
+    }
+    
+    @Test
+    void assertCreateProjectionsContextWithMatchingColumnName() {
+        ProjectionsSegment projectionsSegment = new ProjectionsSegment(0, 0);
+        ColumnSegment columnSegment = new ColumnSegment(0, 0, new 
IdentifierValue("foo_column"));
+        columnSegment.setOwner(new OwnerSegment(0, 0, new 
IdentifierValue("foo_table")));
+        projectionsSegment.getProjections().add(new 
ColumnProjectionSegment(columnSegment));
+        ColumnSegment orderColumn = new ColumnSegment(0, 0, new 
IdentifierValue("`FOO_COLUMN`"));
+        OrderByItem orderByItem = new OrderByItem(new 
ColumnOrderByItemSegment(orderColumn, OrderDirection.ASC, 
NullsOrderType.FIRST));
+        ProjectionsContextEngine engine = new 
ProjectionsContextEngine(databaseType);
+        ProjectionsContext actual = 
engine.createProjectionsContext(projectionsSegment, new 
GroupByContext(Collections.emptyList()), new 
OrderByContext(Collections.singleton(orderByItem), false));
+        assertThat(actual.getProjections().size(), is(1));
+    }
+    
+    @Test
+    void assertCreateProjectionsContextWithMatchingQualifiedColumnName() {
+        ProjectionsSegment projectionsSegment = new ProjectionsSegment(0, 0);
+        ColumnSegment columnSegment = new ColumnSegment(0, 0, new 
IdentifierValue("foo_column"));
+        columnSegment.setOwner(new OwnerSegment(0, 0, new 
IdentifierValue("foo_table")));
+        projectionsSegment.getProjections().add(new 
ColumnProjectionSegment(columnSegment));
+        ColumnSegment orderColumn = new ColumnSegment(0, 0, new 
IdentifierValue("\"FOO_COLUMN\""));
+        orderColumn.setOwner(new OwnerSegment(0, 0, new 
IdentifierValue("\"FOO_TABLE\"")));
+        OrderByItem orderByItem = new OrderByItem(new 
ColumnOrderByItemSegment(orderColumn, OrderDirection.ASC, 
NullsOrderType.FIRST));
+        ProjectionsContextEngine engine = new 
ProjectionsContextEngine(databaseType);
+        ProjectionsContext actual = 
engine.createProjectionsContext(projectionsSegment, new 
GroupByContext(Collections.emptyList()), new 
OrderByContext(Collections.singleton(orderByItem), false));
+        assertThat(actual.getProjections().size(), is(1));
+    }
+    
+    @Test
+    void assertCreateProjectionsContextWithMatchingAlias() {
+        ProjectionsSegment projectionsSegment = new ProjectionsSegment(0, 0);
+        ExpressionProjectionSegment projectionSegment = new 
ExpressionProjectionSegment(0, 0, "foo_column + 1");
+        projectionSegment.setAlias(new AliasSegment(0, 0, new 
IdentifierValue("`foo_alias`")));
+        projectionsSegment.getProjections().add(projectionSegment);
+        OrderByItem orderByItem = new OrderByItem(new 
ExpressionOrderByItemSegment(0, 0, "`FOO_ALIAS`", OrderDirection.ASC, 
NullsOrderType.FIRST));
+        ProjectionsContextEngine engine = new 
ProjectionsContextEngine(databaseType);
+        ProjectionsContext actual = 
engine.createProjectionsContext(projectionsSegment, new 
GroupByContext(Collections.emptyList()), new 
OrderByContext(Collections.singleton(orderByItem), false));
+        assertThat(actual.getProjections().size(), is(1));
+    }
+    
+    @Test
+    void assertCreateProjectionsContextWithMatchingExpression() {
+        ProjectionsSegment projectionsSegment = new ProjectionsSegment(0, 0);
+        projectionsSegment.getProjections().add(new 
ExpressionProjectionSegment(0, 0, "`foo_column` + 1"));
+        OrderByItem orderByItem = new OrderByItem(new 
ExpressionOrderByItemSegment(0, 0, "`FOO_COLUMN` + 1", OrderDirection.ASC, 
NullsOrderType.FIRST));
+        ProjectionsContextEngine engine = new 
ProjectionsContextEngine(databaseType);
+        ProjectionsContext actual = 
engine.createProjectionsContext(projectionsSegment, new 
GroupByContext(Collections.emptyList()), new 
OrderByContext(Collections.singleton(orderByItem), false));
+        assertThat(actual.getProjections().size(), is(1));
+    }
 }
diff --git 
a/parser/sql/statement/core/src/main/java/org/apache/shardingsphere/sql/parser/statement/core/util/SQLUtils.java
 
b/parser/sql/statement/core/src/main/java/org/apache/shardingsphere/sql/parser/statement/core/util/SQLUtils.java
index cb54c623138..c6805bf12c5 100644
--- 
a/parser/sql/statement/core/src/main/java/org/apache/shardingsphere/sql/parser/statement/core/util/SQLUtils.java
+++ 
b/parser/sql/statement/core/src/main/java/org/apache/shardingsphere/sql/parser/statement/core/util/SQLUtils.java
@@ -38,6 +38,7 @@ import 
org.apache.shardingsphere.sql.parser.statement.core.value.literal.impl.Ot
 import 
org.apache.shardingsphere.sql.parser.statement.core.value.literal.impl.StringLiteralValue;
 import 
org.apache.shardingsphere.sql.parser.statement.core.value.literal.impl.TemporalLiteralValue;
 
+import javax.annotation.Nullable;
 import java.math.BigDecimal;
 import java.math.BigInteger;
 import java.util.Collections;
@@ -104,10 +105,11 @@ public final class SQLUtils {
      *
      * <p>remove special char for SQL expression</p>
      *
-     * @param value SQL expression
-     * @return exactly SQL expression
+     * @param value SQL expression, or {@code null}
+     * @return exactly SQL expression, or {@code null} if {@code value} is 
{@code null}
      */
-    public static String getExactlyValue(final String value) {
+    @Nullable
+    public static String getExactlyValue(@Nullable final String value) {
         return null == value ? null : 
tryGetRealContentInBackticks(CharMatcher.anyOf(EXCLUDED_CHARACTERS).removeFrom(value));
     }
     
@@ -116,11 +118,12 @@ public final class SQLUtils {
      *
      * <p>remove special char for SQL expression</p>
      *
-     * @param value SQL expression
+     * @param value SQL expression, or {@code null}
      * @param reservedCharacters characters to be reserved
-     * @return exactly SQL expression
+     * @return exactly SQL expression, or {@code null} if {@code value} is 
{@code null}
      */
-    public static String getExactlyValue(final String value, final String 
reservedCharacters) {
+    @Nullable
+    public static String getExactlyValue(@Nullable final String value, final 
String reservedCharacters) {
         if (null == value) {
             return null;
         }
diff --git 
a/parser/sql/statement/core/src/test/java/org/apache/shardingsphere/sql/parser/statement/core/util/SQLUtilsTest.java
 
b/parser/sql/statement/core/src/test/java/org/apache/shardingsphere/sql/parser/statement/core/util/SQLUtilsTest.java
index 70cb777848b..96962c212b3 100644
--- 
a/parser/sql/statement/core/src/test/java/org/apache/shardingsphere/sql/parser/statement/core/util/SQLUtilsTest.java
+++ 
b/parser/sql/statement/core/src/test/java/org/apache/shardingsphere/sql/parser/statement/core/util/SQLUtilsTest.java
@@ -83,6 +83,11 @@ class SQLUtilsTest {
         assertNull(SQLUtils.getExactlyValue(null));
     }
     
+    @Test
+    void assertGetExactlyValueWithReservedCharactersUsingNull() {
+        assertNull(SQLUtils.getExactlyValue(null, "`"));
+    }
+    
     @Test
     void assertGetExactlyExpressionUsingAndReturningNull() {
         assertNull(SQLUtils.getExactlyExpression(null));

Reply via email to