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

zhangliang 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 88f305a0a59 Fix HASH_MOD routing mismatch for same negative numeric 
value (#38327)
88f305a0a59 is described below

commit 88f305a0a598da754f15884dc991672b5ff74ac8
Author: ym0506 <[email protected]>
AuthorDate: Wed Mar 4 18:01:09 2026 +0900

    Fix HASH_MOD routing mismatch for same negative numeric value (#38327)
    
    * Fix HASH_MOD routing mismatch for same negative numeric value
    
    * Add compatibility switch for HASH_MOD numeric normalization
---
 .../common-config/builtin-algorithm/sharding.cn.md |  1 +
 .../common-config/builtin-algorithm/sharding.en.md |  1 +
 .../sharding/mod/HashModShardingAlgorithm.java     | 30 +++++++++++++
 .../sharding/mod/HashModShardingAlgorithmTest.java | 51 ++++++++++++++++++++++
 .../driver/ShardingSphereDriverTest.java           |  3 +-
 .../config/driver/driver-fixture-h2-mysql.yaml     |  1 +
 6 files changed, 85 insertions(+), 2 deletions(-)

diff --git 
a/docs/document/content/user-manual/common-config/builtin-algorithm/sharding.cn.md
 
b/docs/document/content/user-manual/common-config/builtin-algorithm/sharding.cn.md
index de4979ec3de..a383af9d1c3 100644
--- 
a/docs/document/content/user-manual/common-config/builtin-algorithm/sharding.cn.md
+++ 
b/docs/document/content/user-manual/common-config/builtin-algorithm/sharding.cn.md
@@ -32,6 +32,7 @@ ShardingSphere 内置提供了多种分片算法,按照类型可以划分为
 | *属性名称*         | *数据类型* | *说明* |
 |----------------|--------|------|
 | sharding-count | int    | 分片数量 |
+| normalize-numeric-int-range (?) | boolean | 是否将整型范围内的 `Long` 和 `BigInteger` 
按 `Integer` 语义统一计算,以保证相同数值跨类型路由一致 | false |
 
 #### 基于分片容量的范围分片算法
 
diff --git 
a/docs/document/content/user-manual/common-config/builtin-algorithm/sharding.en.md
 
b/docs/document/content/user-manual/common-config/builtin-algorithm/sharding.en.md
index c5987af34f1..1f2c4169da0 100644
--- 
a/docs/document/content/user-manual/common-config/builtin-algorithm/sharding.en.md
+++ 
b/docs/document/content/user-manual/common-config/builtin-algorithm/sharding.en.md
@@ -34,6 +34,7 @@ Attributes:
 | *Name*         | *DataType* | *Description*  |
 |----------------|------------|----------------|
 | sharding-count | int        | Sharding count |
+| normalize-numeric-int-range (?) | boolean | Whether to normalize `Long` and 
`BigInteger` values in integer range to integer semantics for consistent 
routing across numeric types | false |
 
 #### Volume Based Range Sharding Algorithm
 
diff --git 
a/features/sharding/core/src/main/java/org/apache/shardingsphere/sharding/algorithm/sharding/mod/HashModShardingAlgorithm.java
 
b/features/sharding/core/src/main/java/org/apache/shardingsphere/sharding/algorithm/sharding/mod/HashModShardingAlgorithm.java
index adbdc7dda6a..60a8c26312e 100644
--- 
a/features/sharding/core/src/main/java/org/apache/shardingsphere/sharding/algorithm/sharding/mod/HashModShardingAlgorithm.java
+++ 
b/features/sharding/core/src/main/java/org/apache/shardingsphere/sharding/algorithm/sharding/mod/HashModShardingAlgorithm.java
@@ -26,6 +26,7 @@ import 
org.apache.shardingsphere.sharding.api.sharding.standard.RangeShardingVal
 import 
org.apache.shardingsphere.sharding.api.sharding.standard.StandardShardingAlgorithm;
 import 
org.apache.shardingsphere.sharding.exception.data.NullShardingValueException;
 
+import java.math.BigInteger;
 import java.util.Collection;
 import java.util.Properties;
 
@@ -36,11 +37,20 @@ public final class HashModShardingAlgorithm implements 
StandardShardingAlgorithm
     
     private static final String SHARDING_COUNT_KEY = "sharding-count";
     
+    private static final String NORMALIZE_NUMERIC_INT_RANGE_KEY = 
"normalize-numeric-int-range";
+    
+    private static final BigInteger INTEGER_MIN_VALUE = 
BigInteger.valueOf(Integer.MIN_VALUE);
+    
+    private static final BigInteger INTEGER_MAX_VALUE = 
BigInteger.valueOf(Integer.MAX_VALUE);
+    
     private int shardingCount;
     
+    private boolean normalizeNumericIntRange;
+    
     @Override
     public void init(final Properties props) {
         shardingCount = getShardingCount(props);
+        normalizeNumericIntRange = isNormalizeNumericIntRange(props);
     }
     
     private int getShardingCount(final Properties props) {
@@ -50,6 +60,10 @@ public final class HashModShardingAlgorithm implements 
StandardShardingAlgorithm
         return result;
     }
     
+    private boolean isNormalizeNumericIntRange(final Properties props) {
+        return 
Boolean.parseBoolean(String.valueOf(props.getProperty(NORMALIZE_NUMERIC_INT_RANGE_KEY,
 Boolean.FALSE.toString())));
+    }
+    
     @Override
     public String doSharding(final Collection<String> availableTargetNames, 
final PreciseShardingValue<Comparable<?>> shardingValue) {
         ShardingSpherePreconditions.checkNotNull(shardingValue.getValue(), 
NullShardingValueException::new);
@@ -63,9 +77,25 @@ public final class HashModShardingAlgorithm implements 
StandardShardingAlgorithm
     }
     
     private long hashShardingValue(final Object shardingValue) {
+        if (normalizeNumericIntRange) {
+            if (shardingValue instanceof Long && isIntegerRange((Long) 
shardingValue)) {
+                return Math.abs((long) ((Long) shardingValue).intValue());
+            }
+            if (shardingValue instanceof BigInteger && 
isIntegerRange((BigInteger) shardingValue)) {
+                return Math.abs((long) ((BigInteger) 
shardingValue).intValue());
+            }
+        }
         return Math.abs((long) shardingValue.hashCode());
     }
     
+    private boolean isIntegerRange(final long shardingValue) {
+        return shardingValue >= Integer.MIN_VALUE && shardingValue <= 
Integer.MAX_VALUE;
+    }
+    
+    private boolean isIntegerRange(final BigInteger shardingValue) {
+        return shardingValue.compareTo(INTEGER_MIN_VALUE) >= 0 && 
shardingValue.compareTo(INTEGER_MAX_VALUE) <= 0;
+    }
+    
     @Override
     public int getAutoTablesAmount() {
         return shardingCount;
diff --git 
a/features/sharding/core/src/test/java/org/apache/shardingsphere/sharding/algorithm/sharding/mod/HashModShardingAlgorithmTest.java
 
b/features/sharding/core/src/test/java/org/apache/shardingsphere/sharding/algorithm/sharding/mod/HashModShardingAlgorithmTest.java
index ac54d49432f..a265a443893 100644
--- 
a/features/sharding/core/src/test/java/org/apache/shardingsphere/sharding/algorithm/sharding/mod/HashModShardingAlgorithmTest.java
+++ 
b/features/sharding/core/src/test/java/org/apache/shardingsphere/sharding/algorithm/sharding/mod/HashModShardingAlgorithmTest.java
@@ -29,6 +29,7 @@ import 
org.apache.shardingsphere.sharding.spi.ShardingAlgorithm;
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
 
+import java.math.BigInteger;
 import java.util.Arrays;
 import java.util.Collection;
 import java.util.List;
@@ -40,13 +41,19 @@ import static org.junit.jupiter.api.Assertions.assertThrows;
 
 class HashModShardingAlgorithmTest {
     
+    private static final String NORMALIZE_NUMERIC_INT_RANGE_KEY = 
"normalize-numeric-int-range";
+    
     private static final DataNodeInfo DATA_NODE_INFO = new 
DataNodeInfo("t_order_", 1, '0');
     
     private HashModShardingAlgorithm shardingAlgorithm;
     
+    private HashModShardingAlgorithm normalizedShardingAlgorithm;
+    
     @BeforeEach
     void setup() {
         shardingAlgorithm = (HashModShardingAlgorithm) 
TypedSPILoader.getService(ShardingAlgorithm.class, "HASH_MOD", 
PropertiesBuilder.build(new Property("sharding-count", "4")));
+        normalizedShardingAlgorithm = (HashModShardingAlgorithm) 
TypedSPILoader.getService(
+                ShardingAlgorithm.class, "HASH_MOD", 
PropertiesBuilder.build(new Property("sharding-count", "4"), new 
Property(NORMALIZE_NUMERIC_INT_RANGE_KEY, Boolean.TRUE.toString())));
     }
     
     @Test
@@ -55,6 +62,50 @@ class HashModShardingAlgorithmTest {
         assertThat(shardingAlgorithm.doSharding(availableTargetNames, new 
PreciseShardingValue<>("t_order", "order_type", DATA_NODE_INFO, "a")), 
is("t_order_1"));
     }
     
+    @Test
+    void assertPreciseDoShardingWithSameNegativeNumberValueForLegacyMode() {
+        List<String> availableTargetNames = Arrays.asList("t_order_0", 
"t_order_1", "t_order_2", "t_order_3");
+        assertThat(shardingAlgorithm.doSharding(availableTargetNames, new 
PreciseShardingValue<>("t_order", "order_id", DATA_NODE_INFO, -1)), 
is("t_order_1"));
+        assertThat(shardingAlgorithm.doSharding(availableTargetNames, new 
PreciseShardingValue<>("t_order", "order_id", DATA_NODE_INFO, -1L)), 
is("t_order_0"));
+        assertThat(shardingAlgorithm.doSharding(availableTargetNames, new 
PreciseShardingValue<>("t_order", "order_id", DATA_NODE_INFO, 
BigInteger.valueOf(-1L))), is("t_order_1"));
+    }
+    
+    @Test
+    void assertPreciseDoShardingWithSameNegativeNumberValueForNormalizedMode() 
{
+        List<String> availableTargetNames = Arrays.asList("t_order_0", 
"t_order_1", "t_order_2", "t_order_3");
+        
assertThat(normalizedShardingAlgorithm.doSharding(availableTargetNames, new 
PreciseShardingValue<>("t_order", "order_id", DATA_NODE_INFO, -1)), 
is("t_order_1"));
+        
assertThat(normalizedShardingAlgorithm.doSharding(availableTargetNames, new 
PreciseShardingValue<>("t_order", "order_id", DATA_NODE_INFO, -1L)), 
is("t_order_1"));
+        
assertThat(normalizedShardingAlgorithm.doSharding(availableTargetNames, new 
PreciseShardingValue<>("t_order", "order_id", DATA_NODE_INFO, 
BigInteger.valueOf(-1L))), is("t_order_1"));
+    }
+    
+    @Test
+    void 
assertPreciseDoShardingWithLongValueOutOfIntegerRangeForNormalizedMode() {
+        List<String> availableTargetNames = Arrays.asList("t_order_0", 
"t_order_1", "t_order_2", "t_order_3");
+        
assertThat(normalizedShardingAlgorithm.doSharding(availableTargetNames, new 
PreciseShardingValue<>("t_order", "order_id", DATA_NODE_INFO, Long.MAX_VALUE)), 
is("t_order_0"));
+    }
+    
+    @Test
+    void 
assertPreciseDoShardingWithBigIntegerBoundaryValuesForNormalizedMode() {
+        List<String> availableTargetNames = Arrays.asList("t_order_0", 
"t_order_1", "t_order_2", "t_order_3");
+        assertThat(normalizedShardingAlgorithm.doSharding(
+                availableTargetNames, new PreciseShardingValue<>("t_order", 
"order_id", DATA_NODE_INFO, BigInteger.valueOf(Integer.MIN_VALUE))), 
is("t_order_0"));
+        assertThat(normalizedShardingAlgorithm.doSharding(
+                availableTargetNames, new PreciseShardingValue<>("t_order", 
"order_id", DATA_NODE_INFO, BigInteger.valueOf(Integer.MAX_VALUE))), 
is("t_order_3"));
+    }
+    
+    @Test
+    void 
assertPreciseDoShardingWithBigIntegerOutOfIntegerRangeForNormalizedMode() {
+        List<String> availableTargetNames = Arrays.asList("t_order_0", 
"t_order_1", "t_order_2", "t_order_3");
+        BigInteger greaterThanIntegerMax = 
BigInteger.valueOf(Integer.MAX_VALUE).add(BigInteger.ONE);
+        BigInteger lessThanIntegerMin = 
BigInteger.valueOf(Integer.MIN_VALUE).subtract(BigInteger.ONE);
+        assertThat(normalizedShardingAlgorithm.doSharding(
+                availableTargetNames, new PreciseShardingValue<>("t_order", 
"order_id", DATA_NODE_INFO, greaterThanIntegerMax)),
+                is(shardingAlgorithm.doSharding(availableTargetNames, new 
PreciseShardingValue<>("t_order", "order_id", DATA_NODE_INFO, 
greaterThanIntegerMax))));
+        assertThat(normalizedShardingAlgorithm.doSharding(
+                availableTargetNames, new PreciseShardingValue<>("t_order", 
"order_id", DATA_NODE_INFO, lessThanIntegerMin)),
+                is(shardingAlgorithm.doSharding(availableTargetNames, new 
PreciseShardingValue<>("t_order", "order_id", DATA_NODE_INFO, 
lessThanIntegerMin))));
+    }
+    
     @Test
     void assertRangeDoSharding() {
         List<String> availableTargetNames = Arrays.asList("t_order_0", 
"t_order_1", "t_order_2", "t_order_3");
diff --git 
a/jdbc/src/test/java/org/apache/shardingsphere/driver/ShardingSphereDriverTest.java
 
b/jdbc/src/test/java/org/apache/shardingsphere/driver/ShardingSphereDriverTest.java
index 627a8fed04a..0f34872b274 100644
--- 
a/jdbc/src/test/java/org/apache/shardingsphere/driver/ShardingSphereDriverTest.java
+++ 
b/jdbc/src/test/java/org/apache/shardingsphere/driver/ShardingSphereDriverTest.java
@@ -83,8 +83,7 @@ class ShardingSphereDriverTest {
                 statement.execute("DROP TABLE IF EXISTS t_order");
                 statement.execute("CREATE TABLE t_order (order_id INT PRIMARY 
KEY, user_id INT)");
             }
-            // TODO Replace 1 to -1 after HASH_MOD algorithm improved
-            int value = 1;
+            int value = -1;
             try (PreparedStatement preparedStatement = 
connection.prepareStatement("INSERT INTO t_order (order_id, user_id) VALUES (?, 
?)")) {
                 preparedStatement.setObject(1, value);
                 preparedStatement.setObject(2, 101);
diff --git a/jdbc/src/test/resources/config/driver/driver-fixture-h2-mysql.yaml 
b/jdbc/src/test/resources/config/driver/driver-fixture-h2-mysql.yaml
index c8c5a35c297..dbe6ce97cd5 100644
--- a/jdbc/src/test/resources/config/driver/driver-fixture-h2-mysql.yaml
+++ b/jdbc/src/test/resources/config/driver/driver-fixture-h2-mysql.yaml
@@ -48,6 +48,7 @@ rules:
         type: HASH_MOD
         props:
           sharding-count: 2
+          normalize-numeric-int-range: true
     
     keyGenerators:
       snowflake:

Reply via email to