Skip to content

HashModShardingAlgorithm depends on shardingValue.hashCode() might cause some issues #37282

@sandynz

Description

@sandynz

Related code:

HashModShardingAlgorithm.java

    private long hashShardingValue(final Object shardingValue) {
        return Math.abs((long) shardingValue.hashCode());
    }

Possible issue 1

java.lang.Number sub-classes might have different hashCode() implementation, e.g.

println("Integer.valueOf(-1).hashCode(): " + Integer.valueOf(-1).hashCode())
println("Long.valueOf(-1).hashCode(): " + Long.valueOf(-1).hashCode())
println("BigInteger.valueOf(-1).hashCode(): " + BigInteger.valueOf(-1).hashCode())

-- result
Integer.valueOf(-1).hashCode(): -1
Long.valueOf(-1).hashCode(): 0
BigInteger.valueOf(-1).hashCode(): -1

Eexample table: t_order.
Sharding rule:

rules:
  - !SHARDING
    autoTables:
      t_order:
        actualDataSources: ds_0,ds_1
        shardingStrategy:
          standard:
            shardingColumn: order_id
            shardingAlgorithmName: auto_mod
        keyGenerateStrategy:
          column: user_id
          keyGeneratorName: snowflake
    shardingAlgorithms:
      auto_mod:
        type: HASH_MOD
        props:
          sharding-count: 2
    
    keyGenerators:
      snowflake:
        type: SNOWFLAKE

Insert record with Integer sharding key value, then query with Long sharding key value, ResultSet return empty result.

Test code:

    @Test
    void assertHashModSetLongOnIntColumnWorks() throws SQLException {
        try (Connection connection = DriverManager.getConnection("jdbc:shardingsphere:classpath:config/driver/foo-driver-fixture.yaml")) {
            assertThat(connection, isA(ShardingSphereConnection.class));
            try (Statement statement = connection.createStatement()) {
                statement.execute("DROP TABLE IF EXISTS t_order");
                statement.execute("CREATE TABLE t_order (order_id INT PRIMARY KEY, user_id INT)");
            }
            int value = -1;
            try (PreparedStatement preparedStatement = connection.prepareStatement("INSERT INTO t_order (order_id, user_id) VALUES (?, ?)")) {
                preparedStatement.setObject(1, value); // Insert with Integer value
                preparedStatement.setObject(2, 101);
                int updatedCount = preparedStatement.executeUpdate();
                assertThat(updatedCount, is(1));
            }
            try (PreparedStatement preparedStatement = connection.prepareStatement("SELECT * FROM t_order WHERE order_id = ?")) {
                preparedStatement.setObject(1, value); // Query with Integer value, related record could be queried out
                ResultSet resultSet = preparedStatement.executeQuery();
                assertTrue(resultSet.next()); // Pass
                assertThat(resultSet.getInt(1), is(value)); // Pass
                resultSet.close();
            }
            try (PreparedStatement preparedStatement = connection.prepareStatement("SELECT * FROM t_order WHERE order_id = ?")) {
                preparedStatement.setObject(1, (long) value); // Query with Long value, related record could NOT be queried out
                ResultSet resultSet = preparedStatement.executeQuery();
                assertTrue(resultSet.next()); // WRONG
                assertThat(resultSet.getInt(1), is(value));
                resultSet.close();
            }
        }
    }

It was submitted in https://github.com/apache/shardingsphere/pull/37281/files

The similar test code, use raw MySQL JDBC driver, test on MySQL, it works.

Possible issue 2

If different vendors JRE or different versions of JRE have different hashCode() implementation, then when JRE vendor or version is changed, it'll cause different routing for the same sharding value.

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions