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 dd9d59940f9 Refine BINARY operator handling in sharding compare 
condition value generator (#35623) (#39243)
dd9d59940f9 is described below

commit dd9d59940f9c34a0f980abb0b5a0a25992ec50c0
Author: fudian <[email protected]>
AuthorDate: Tue Aug 18 15:25:05 2026 +0800

    Refine BINARY operator handling in sharding compare condition value 
generator (#35623) (#39243)
    
    * Refine BINARY operator handling in sharding compare condition value 
generator
    
    MySQL's BINARY operator wraps a column or value as a 
UnaryOperationExpression.
    The compare condition value generator did not unwrap BINARY before checking
    whether a side is a ColumnSegment, so sharding conditions on BINARY-prefixed
    columns (e.g. WHERE BINARY id = 100) were silently dropped.
    
    Add unwrapBinaryOperator to strip the BINARY unary wrapper so the underlying
    column/value is used for sharding condition generation.
    
    Refs #35623
    
    * Enable BINARY rewrite integration assertions
    
    Signed-off-by: 付典 <[email protected]>
    
    * Restrict BINARY unwrap to equality predicates in compare condition value 
generator
    
    MySQL evaluates BINARY range comparisons byte-wise, while range routing 
prunes
    partitions on converted endpoints, so unwrapping BINARY is 
semantics-preserving
    only for equality predicates. Range predicates with BINARY operands now keep
    the pre-existing broadcast routing.
    
    - Gate unwrapBinaryOperator behind the equality operator
    - Add generator tests asserting empty conditions for BINARY with >, >=, <, 
<=
      on both operand sides
    - Add where-clause engine test asserting no sharding condition for a string
      sharding key with BINARY and a range operator
    - Add route engine test asserting full-route broadcast without sharding
      conditions under a VOLUME_RANGE algorithm
    
    Signed-off-by: 付典 <[email protected]>
    
    ---------
    
    Signed-off-by: 付典 <[email protected]>
---
 .../ConditionValueCompareOperatorGenerator.java    | 18 ++++++-
 .../WhereClauseShardingConditionEngineTest.java    | 14 +++++
 ...ConditionValueCompareOperatorGeneratorTest.java | 61 ++++++++++++++++++++++
 .../fixture/ShardingRouteEngineFixtureBuilder.java | 16 ++++++
 .../standard/ShardingStandardRouteEngineTest.java  | 15 ++++++
 .../scenario/sharding/case/dml/select.xml          |  5 +-
 6 files changed, 125 insertions(+), 4 deletions(-)

diff --git 
a/features/sharding/core/src/main/java/org/apache/shardingsphere/sharding/route/engine/condition/generator/impl/ConditionValueCompareOperatorGenerator.java
 
b/features/sharding/core/src/main/java/org/apache/shardingsphere/sharding/route/engine/condition/generator/impl/ConditionValueCompareOperatorGenerator.java
index d793d90a88b..aedd7f2f223 100644
--- 
a/features/sharding/core/src/main/java/org/apache/shardingsphere/sharding/route/engine/condition/generator/impl/ConditionValueCompareOperatorGenerator.java
+++ 
b/features/sharding/core/src/main/java/org/apache/shardingsphere/sharding/route/engine/condition/generator/impl/ConditionValueCompareOperatorGenerator.java
@@ -28,6 +28,7 @@ import 
org.apache.shardingsphere.sharding.route.engine.condition.value.ShardingC
 import 
org.apache.shardingsphere.sql.parser.statement.core.segment.dml.column.ColumnSegment;
 import 
org.apache.shardingsphere.sql.parser.statement.core.segment.dml.expr.BinaryOperationExpression;
 import 
org.apache.shardingsphere.sql.parser.statement.core.segment.dml.expr.ExpressionSegment;
+import 
org.apache.shardingsphere.sql.parser.statement.core.segment.dml.expr.UnaryOperationExpression;
 import org.apache.shardingsphere.timeservice.core.rule.TimestampServiceRule;
 
 import java.util.ArrayList;
@@ -64,7 +65,15 @@ public final class ConditionValueCompareOperatorGenerator 
implements ConditionVa
         if (!isSupportedOperator(operator)) {
             return Optional.empty();
         }
-        ExpressionSegment valueExpression = predicate.getLeft() instanceof 
ColumnSegment ? predicate.getRight() : predicate.getLeft();
+        ExpressionSegment left = predicate.getLeft();
+        ExpressionSegment right = predicate.getRight();
+        // MySQL evaluates BINARY range comparisons byte-wise, and range 
routing prunes partitions on converted endpoints,
+        // so unwrapping BINARY is semantics-preserving only for equality 
predicates; range predicates keep broadcast routing.
+        if (EQUAL.equals(operator)) {
+            left = unwrapBinaryOperator(left);
+            right = unwrapBinaryOperator(right);
+        }
+        ExpressionSegment valueExpression = left instanceof ColumnSegment ? 
right : left;
         ConditionValue conditionValue = new ConditionValue(valueExpression, 
params);
         if (conditionValue.isNull()) {
             return generate(null, column, operator, 
conditionValue.getParameterMarkerIndex().orElse(-1));
@@ -106,4 +115,11 @@ public final class ConditionValueCompareOperatorGenerator 
implements ConditionVa
     private boolean isSupportedOperator(final String operator) {
         return OPERATORS.contains(operator);
     }
+    
+    private ExpressionSegment unwrapBinaryOperator(final ExpressionSegment 
segment) {
+        return segment instanceof UnaryOperationExpression
+                && "BINARY".equalsIgnoreCase(((UnaryOperationExpression) 
segment).getOperator())
+                        ? ((UnaryOperationExpression) segment).getExpression()
+                        : segment;
+    }
 }
diff --git 
a/features/sharding/core/src/test/java/org/apache/shardingsphere/sharding/route/engine/condition/engine/WhereClauseShardingConditionEngineTest.java
 
b/features/sharding/core/src/test/java/org/apache/shardingsphere/sharding/route/engine/condition/engine/WhereClauseShardingConditionEngineTest.java
index cebbd8a3fab..e5b76068a17 100644
--- 
a/features/sharding/core/src/test/java/org/apache/shardingsphere/sharding/route/engine/condition/engine/WhereClauseShardingConditionEngineTest.java
+++ 
b/features/sharding/core/src/test/java/org/apache/shardingsphere/sharding/route/engine/condition/engine/WhereClauseShardingConditionEngineTest.java
@@ -28,9 +28,11 @@ import 
org.apache.shardingsphere.sharding.route.engine.condition.value.RangeShar
 import org.apache.shardingsphere.sharding.rule.ShardingRule;
 import 
org.apache.shardingsphere.sql.parser.statement.core.segment.dml.column.ColumnSegment;
 import 
org.apache.shardingsphere.sql.parser.statement.core.segment.dml.expr.BetweenExpression;
+import 
org.apache.shardingsphere.sql.parser.statement.core.segment.dml.expr.BinaryOperationExpression;
 import 
org.apache.shardingsphere.sql.parser.statement.core.segment.dml.expr.ExpressionSegment;
 import 
org.apache.shardingsphere.sql.parser.statement.core.segment.dml.expr.InExpression;
 import 
org.apache.shardingsphere.sql.parser.statement.core.segment.dml.expr.ListExpression;
+import 
org.apache.shardingsphere.sql.parser.statement.core.segment.dml.expr.UnaryOperationExpression;
 import 
org.apache.shardingsphere.sql.parser.statement.core.segment.dml.expr.simple.LiteralExpressionSegment;
 import 
org.apache.shardingsphere.sql.parser.statement.core.segment.dml.predicate.WhereSegment;
 import 
org.apache.shardingsphere.sql.parser.statement.core.value.identifier.IdentifierValue;
@@ -48,6 +50,7 @@ import java.util.Optional;
 import static org.hamcrest.Matchers.is;
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.hamcrest.Matchers.isA;
+import static org.junit.jupiter.api.Assertions.assertTrue;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.when;
 
@@ -113,4 +116,15 @@ class WhereClauseShardingConditionEngineTest {
         assertThat(actual.get(0).getStartIndex(), is(0));
         assertThat(actual.get(0).getValues().get(0), 
isA(ListShardingConditionValue.class));
     }
+    
+    @Test
+    void assertCreateEmptyShardingConditionsForBinaryOperatorRangeStatement() {
+        ColumnSegment left = new ColumnSegment(0, 0, new 
IdentifierValue("foo_sharding_col"));
+        ExpressionSegment binaryColumn = new UnaryOperationExpression(0, 0, 
left, "BINARY", "BINARY foo_sharding_col");
+        BinaryOperationExpression expression = new 
BinaryOperationExpression(0, 0, binaryColumn, new LiteralExpressionSegment(0, 
0, "100"), ">", null);
+        when(whereSegment.getExpr()).thenReturn(expression);
+        when(rule.findShardingColumn("foo_sharding_col", 
"")).thenReturn(Optional.of("foo_sharding_col"));
+        List<ShardingCondition> actual = 
shardingConditionEngine.createShardingConditions(sqlStatementContext, 
Collections.emptyList());
+        assertTrue(actual.isEmpty());
+    }
 }
diff --git 
a/features/sharding/core/src/test/java/org/apache/shardingsphere/sharding/route/engine/condition/generator/impl/ConditionValueCompareOperatorGeneratorTest.java
 
b/features/sharding/core/src/test/java/org/apache/shardingsphere/sharding/route/engine/condition/generator/impl/ConditionValueCompareOperatorGeneratorTest.java
index a5f4bc23446..be876558175 100644
--- 
a/features/sharding/core/src/test/java/org/apache/shardingsphere/sharding/route/engine/condition/generator/impl/ConditionValueCompareOperatorGeneratorTest.java
+++ 
b/features/sharding/core/src/test/java/org/apache/shardingsphere/sharding/route/engine/condition/generator/impl/ConditionValueCompareOperatorGeneratorTest.java
@@ -24,6 +24,7 @@ import 
org.apache.shardingsphere.sharding.route.engine.condition.value.RangeShar
 import 
org.apache.shardingsphere.sharding.route.engine.condition.value.ShardingConditionValue;
 import 
org.apache.shardingsphere.sql.parser.statement.core.segment.dml.column.ColumnSegment;
 import 
org.apache.shardingsphere.sql.parser.statement.core.segment.dml.expr.BinaryOperationExpression;
+import 
org.apache.shardingsphere.sql.parser.statement.core.segment.dml.expr.UnaryOperationExpression;
 import 
org.apache.shardingsphere.sql.parser.statement.core.segment.dml.expr.complex.CommonExpressionSegment;
 import 
org.apache.shardingsphere.sql.parser.statement.core.segment.dml.expr.simple.LiteralExpressionSegment;
 import 
org.apache.shardingsphere.sql.parser.statement.core.segment.dml.expr.simple.ParameterMarkerExpressionSegment;
@@ -59,6 +60,66 @@ class ConditionValueCompareOperatorGeneratorTest {
         
assertTrue(shardingConditionValue.get().getParameterMarkerIndexes().isEmpty());
     }
     
+    @SuppressWarnings("unchecked")
+    @Test
+    void assertGenerateConditionValueWithBinaryOperatorPrefix() {
+        int value = 100;
+        BinaryOperationExpression predicate = new BinaryOperationExpression(0, 
0,
+                new UnaryOperationExpression(0, 0, mock(ColumnSegment.class), 
"BINARY", "BINARY id"),
+                new LiteralExpressionSegment(0, 0, value), "=", null);
+        Optional<ShardingConditionValue> shardingConditionValue = 
generator.generate(predicate, column, new LinkedList<>(), 
mock(TimestampServiceRule.class));
+        assertTrue(shardingConditionValue.isPresent());
+        assertTrue(((ListShardingConditionValue<Integer>) 
shardingConditionValue.get()).getValues().contains(value));
+    }
+    
+    @SuppressWarnings("unchecked")
+    @Test
+    void assertGenerateConditionValueWithBinaryOperatorValue() {
+        int value = 100;
+        BinaryOperationExpression predicate = new BinaryOperationExpression(0, 
0,
+                mock(ColumnSegment.class),
+                new UnaryOperationExpression(0, 0, new 
LiteralExpressionSegment(0, 0, value), "BINARY", "BINARY 100"), "=", null);
+        Optional<ShardingConditionValue> shardingConditionValue = 
generator.generate(predicate, column, new LinkedList<>(), 
mock(TimestampServiceRule.class));
+        assertTrue(shardingConditionValue.isPresent());
+        assertTrue(((ListShardingConditionValue<Integer>) 
shardingConditionValue.get()).getValues().contains(value));
+    }
+    
+    @Test
+    void 
assertGenerateEmptyConditionValueWithBinaryOperatorPrefixAndGreaterThanOperator()
 {
+        BinaryOperationExpression predicate = new BinaryOperationExpression(0, 
0,
+                new UnaryOperationExpression(0, 0, mock(ColumnSegment.class), 
"BINARY", "BINARY id"), new LiteralExpressionSegment(0, 0, "100"), ">", null);
+        assertFalse(generator.generate(predicate, column, new LinkedList<>(), 
mock(TimestampServiceRule.class)).isPresent());
+    }
+    
+    @Test
+    void 
assertGenerateEmptyConditionValueWithBinaryOperatorPrefixAndGreaterThanOrEqualOperator()
 {
+        BinaryOperationExpression predicate = new BinaryOperationExpression(0, 
0,
+                new UnaryOperationExpression(0, 0, mock(ColumnSegment.class), 
"BINARY", "BINARY id"), new LiteralExpressionSegment(0, 0, "100"), ">=", null);
+        assertFalse(generator.generate(predicate, column, new LinkedList<>(), 
mock(TimestampServiceRule.class)).isPresent());
+    }
+    
+    @Test
+    void 
assertGenerateEmptyConditionValueWithBinaryOperatorPrefixAndLessThanOperator() {
+        BinaryOperationExpression predicate = new BinaryOperationExpression(0, 
0,
+                new UnaryOperationExpression(0, 0, mock(ColumnSegment.class), 
"BINARY", "BINARY id"), new LiteralExpressionSegment(0, 0, "100"), "<", null);
+        assertFalse(generator.generate(predicate, column, new LinkedList<>(), 
mock(TimestampServiceRule.class)).isPresent());
+    }
+    
+    @Test
+    void 
assertGenerateEmptyConditionValueWithBinaryOperatorPrefixAndLessThanOrEqualOperator()
 {
+        BinaryOperationExpression predicate = new BinaryOperationExpression(0, 
0,
+                new UnaryOperationExpression(0, 0, mock(ColumnSegment.class), 
"BINARY", "BINARY id"), new LiteralExpressionSegment(0, 0, "100"), "<=", null);
+        assertFalse(generator.generate(predicate, column, new LinkedList<>(), 
mock(TimestampServiceRule.class)).isPresent());
+    }
+    
+    @Test
+    void 
assertGenerateEmptyConditionValueWithBinaryOperatorValueAndGreaterThanOperator()
 {
+        BinaryOperationExpression predicate = new BinaryOperationExpression(0, 
0,
+                mock(ColumnSegment.class),
+                new UnaryOperationExpression(0, 0, new 
LiteralExpressionSegment(0, 0, "100"), "BINARY", "BINARY '100'"), ">", null);
+        assertFalse(generator.generate(predicate, column, new LinkedList<>(), 
mock(TimestampServiceRule.class)).isPresent());
+    }
+    
     @SuppressWarnings("unchecked")
     @Test
     void assertGenerateNullConditionValue() {
diff --git 
a/features/sharding/core/src/test/java/org/apache/shardingsphere/sharding/route/engine/fixture/ShardingRouteEngineFixtureBuilder.java
 
b/features/sharding/core/src/test/java/org/apache/shardingsphere/sharding/route/engine/fixture/ShardingRouteEngineFixtureBuilder.java
index 9c60a54801e..a1c1a0cdb2c 100644
--- 
a/features/sharding/core/src/test/java/org/apache/shardingsphere/sharding/route/engine/fixture/ShardingRouteEngineFixtureBuilder.java
+++ 
b/features/sharding/core/src/test/java/org/apache/shardingsphere/sharding/route/engine/fixture/ShardingRouteEngineFixtureBuilder.java
@@ -31,6 +31,7 @@ import 
org.apache.shardingsphere.infra.spi.type.typed.TypedSPILoader;
 import org.apache.shardingsphere.infra.util.props.PropertiesBuilder;
 import org.apache.shardingsphere.infra.util.props.PropertiesBuilder.Property;
 import org.apache.shardingsphere.sharding.api.config.ShardingRuleConfiguration;
+import 
org.apache.shardingsphere.sharding.api.config.rule.ShardingAutoTableRuleConfiguration;
 import 
org.apache.shardingsphere.sharding.api.config.rule.ShardingTableReferenceRuleConfiguration;
 import 
org.apache.shardingsphere.sharding.api.config.rule.ShardingTableRuleConfiguration;
 import 
org.apache.shardingsphere.sharding.api.config.strategy.sharding.HintShardingStrategyConfiguration;
@@ -82,6 +83,21 @@ public final class ShardingRouteEngineFixtureBuilder {
         return new ShardingRule(shardingRuleConfig, createDataSources(), 
mock(ComputeNodeInstanceContext.class), Collections.emptyList());
     }
     
+    /**
+     * Create volume range sharding rule.
+     *
+     * @return created sharding rule
+     */
+    public static ShardingRule createVolumeRangeShardingRule() {
+        ShardingRuleConfiguration shardingRuleConfig = new 
ShardingRuleConfiguration();
+        ShardingAutoTableRuleConfiguration autoTableRuleConfig = new 
ShardingAutoTableRuleConfiguration("t_order", "ds_0");
+        autoTableRuleConfig.setShardingStrategy(new 
StandardShardingStrategyConfiguration("order_no", "t_order_volume_range"));
+        shardingRuleConfig.getAutoTables().add(autoTableRuleConfig);
+        shardingRuleConfig.getShardingAlgorithms().put("t_order_volume_range", 
new AlgorithmConfiguration("VOLUME_RANGE",
+                PropertiesBuilder.build(new Property("range-lower", "10"), new 
Property("range-upper", "1000"), new Property("sharding-volume", "330"))));
+        return new ShardingRule(shardingRuleConfig, createDataSources(), 
mock(ComputeNodeInstanceContext.class), Collections.emptyList());
+    }
+    
     /**
      * Create error sharding rule.
      *
diff --git 
a/features/sharding/core/src/test/java/org/apache/shardingsphere/sharding/route/engine/type/standard/ShardingStandardRouteEngineTest.java
 
b/features/sharding/core/src/test/java/org/apache/shardingsphere/sharding/route/engine/type/standard/ShardingStandardRouteEngineTest.java
index 218d9ae172a..392e8f86793 100644
--- 
a/features/sharding/core/src/test/java/org/apache/shardingsphere/sharding/route/engine/type/standard/ShardingStandardRouteEngineTest.java
+++ 
b/features/sharding/core/src/test/java/org/apache/shardingsphere/sharding/route/engine/type/standard/ShardingStandardRouteEngineTest.java
@@ -75,6 +75,21 @@ class ShardingStandardRouteEngineTest {
         
assertThat(routeUnits.get(3).getTableMappers().iterator().next().getLogicName(),
 is("t_order"));
     }
     
+    @Test
+    void assertRouteToAllTablesByNonConditionsUnderRangeAlgorithm() {
+        ShardingStandardRouteEngine routeEngine = 
createShardingStandardRouteEngine("t_order",
+                new ShardingConditions(Collections.emptyList(), 
mock(SQLStatementContext.class), mock(ShardingRule.class)), 
mock(SQLStatementContext.class), new HintValueContext());
+        RouteContext routeContext = 
routeEngine.route(ShardingRouteEngineFixtureBuilder.createVolumeRangeShardingRule());
+        List<RouteUnit> routeUnits = new 
ArrayList<>(routeContext.getRouteUnits());
+        assertThat(routeContext.getRouteUnits().size(), is(5));
+        for (int i = 0; i < 5; i++) {
+            
assertThat(routeUnits.get(i).getDataSourceMapper().getActualName(), is("ds_0"));
+            assertThat(routeUnits.get(i).getTableMappers().size(), is(1));
+            
assertThat(routeUnits.get(i).getTableMappers().iterator().next().getActualName(),
 is("t_order_" + i));
+            
assertThat(routeUnits.get(i).getTableMappers().iterator().next().getLogicName(),
 is("t_order"));
+        }
+    }
+    
     @Test
     void assertRouteByShardingConditions() {
         ShardingStandardRouteEngine routeEngine = 
createShardingStandardRouteEngine("t_order",
diff --git 
a/test/it/rewriter/src/test/resources/scenario/sharding/case/dml/select.xml 
b/test/it/rewriter/src/test/resources/scenario/sharding/case/dml/select.xml
index d142bd52d2b..ef93363bc94 100644
--- a/test/it/rewriter/src/test/resources/scenario/sharding/case/dml/select.xml
+++ b/test/it/rewriter/src/test/resources/scenario/sharding/case/dml/select.xml
@@ -529,8 +529,7 @@
         <output sql="SELECT account_id , CASE WHEN account_id > 0 AND 
account_id &lt;= 10 THEN '(0,10]' WHEN account_id > 10 THEN '(10,+∞)' ELSE '' 
END AS GROUP_BY_DERIVED_0 FROM t_account_1 GROUP BY CASE WHEN account_id > 0 
AND account_id &lt;= 10 THEN '(0,10]' WHEN account_id > 10 THEN '(10,+∞)' ELSE 
'' END ORDER BY CASE WHEN account_id > 0 AND account_id &lt;= 10 THEN '(0,10]' 
WHEN account_id > 10 THEN '(10,+∞)' ELSE '' END" parameters="100" />
     </rewrite-assertion>
 
-    <!-- FIXME -->
-    <!--<rewrite-assertion 
id="select_with_sharding_value_and_binary_column_for_parameters" 
db-types="MySQL">
+    <rewrite-assertion 
id="select_with_sharding_value_and_binary_column_for_parameters" 
db-types="MySQL">
         <input sql="SELECT * FROM t_account WHERE BINARY account_id = ?" 
parameters="100" />
         <output sql="SELECT * FROM t_account_0 WHERE BINARY account_id = ?" 
parameters="100" />
     </rewrite-assertion>
@@ -548,7 +547,7 @@
     <rewrite-assertion 
id="select_with_sharding_value_and_binary_value_for_literals" db-types="MySQL">
         <input sql="SELECT * FROM t_account WHERE account_id = BINARY 100" />
         <output sql="SELECT * FROM t_account_0 WHERE account_id = BINARY 100" 
/>
-    </rewrite-assertion>-->
+    </rewrite-assertion>
 
     <rewrite-assertion id="select_with_schema_name_in_shorthand_projection" 
db-types="MySQL">
         <input sql="SELECT sharding_db.t_account.* FROM t_account WHERE 
account_id = ?" parameters="100" />

Reply via email to