diff --git a/RELEASE-NOTES.md b/RELEASE-NOTES.md index 99cbdb0275b94..80a040f7137d1 100644 --- a/RELEASE-NOTES.md +++ b/RELEASE-NOTES.md @@ -25,6 +25,7 @@ 1. Pipeline: Fix MySQL zero-value temporal binlog decoding with fractional precision in migration - [#38629](https://github.com/apache/shardingsphere/pull/38629) 1. Pipeline: Fix escape MySQL JSON binlog control characters - [#38800](https://github.com/apache/shardingsphere/pull/38800) 1. Sharding: Support ORDER BY MySQL VARBINARY column by wrapping byte[] values in a Comparable adapter - [#38699](https://github.com/apache/shardingsphere/pull/38699) +1. Proxy: Fix incorrect generated key handling for explicit auto-increment values - [#38810](https://github.com/apache/shardingsphere/pull/38810) ### Enhancements diff --git a/proxy/backend/core/src/main/java/org/apache/shardingsphere/proxy/backend/connector/StandardDatabaseProxyConnector.java b/proxy/backend/core/src/main/java/org/apache/shardingsphere/proxy/backend/connector/StandardDatabaseProxyConnector.java index 3ad376a8aaed6..3ac98712c8886 100644 --- a/proxy/backend/core/src/main/java/org/apache/shardingsphere/proxy/backend/connector/StandardDatabaseProxyConnector.java +++ b/proxy/backend/core/src/main/java/org/apache/shardingsphere/proxy/backend/connector/StandardDatabaseProxyConnector.java @@ -330,7 +330,9 @@ private UpdateResponseHeader processExecuteUpdate(final Collection ? ((InsertStatementContext) queryContext.getSqlStatementContext()).getGeneratedKeyContext() : Optional.empty(); Collection> autoIncrementGeneratedValues = generatedKeyContext.filter(GeneratedKeyContext::isSupportAutoIncrement) + .filter(GeneratedKeyContext::isGenerated) .map(GeneratedKeyContext::getGeneratedValues).orElseGet(Collections::emptyList); + UpdateResponseHeader result = new UpdateResponseHeader(queryContext.getSqlStatementContext().getSqlStatement(), updateResults, autoIncrementGeneratedValues); if (isNeedAccumulate()) { result.mergeUpdateCount(); diff --git a/proxy/backend/core/src/test/java/org/apache/shardingsphere/proxy/backend/connector/StandardDatabaseProxyConnectorTest.java b/proxy/backend/core/src/test/java/org/apache/shardingsphere/proxy/backend/connector/StandardDatabaseProxyConnectorTest.java index d7e2c9e0ad912..775f6514881e0 100644 --- a/proxy/backend/core/src/test/java/org/apache/shardingsphere/proxy/backend/connector/StandardDatabaseProxyConnectorTest.java +++ b/proxy/backend/core/src/test/java/org/apache/shardingsphere/proxy/backend/connector/StandardDatabaseProxyConnectorTest.java @@ -890,6 +890,58 @@ void assertCloseWithNullSQLFederationEngine() throws SQLException { assertTrue(cachedResultSets.isEmpty()); } + @Test + void assertExecuteWithExplicitAutoIncrementValueShouldNotReturnGeneratedKey() throws SQLException { + InsertStatementContext sqlStatementContext = mock(InsertStatementContext.class, RETURNS_DEEP_STUBS); + InsertStatement insertStatement = InsertStatement.builder().databaseType(databaseType).build(); + when(sqlStatementContext.getSqlStatement()).thenReturn(insertStatement); + when(sqlStatementContext.getTablesContext().getDatabaseNames()).thenReturn(Collections.emptyList()); + when(sqlStatementContext.getTablesContext().getSchemaNames()).thenReturn(Collections.emptyList()); + when(sqlStatementContext.getTablesContext().getTableNames()).thenReturn(Collections.singleton("t_order")); + + GeneratedKeyContext generatedKeyContext = new GeneratedKeyContext("order_id", false); + generatedKeyContext.setSupportAutoIncrement(true); + generatedKeyContext.getGeneratedValues().add(-3L); + when(sqlStatementContext.getGeneratedKeyContext()).thenReturn(Optional.of(generatedKeyContext)); + + DataNodeRuleAttribute dataNodeRuleAttribute = mock(DataNodeRuleAttribute.class); + when(dataNodeRuleAttribute.isNeedAccumulate(any())).thenReturn(true); + + ShardingSphereDatabase database = mockDatabase(); + when(database.getRuleMetaData().getAttributes(DataNodeRuleAttribute.class)).thenReturn(Collections.singleton(dataNodeRuleAttribute)); + + DatabaseProxyConnector engine = createDatabaseProxyConnector(JDBCDriverType.STATEMENT, createQueryContext(sqlStatementContext, database)); + setField(engine, "proxySQLExecutor", mock(ProxySQLExecutor.class, RETURNS_DEEP_STUBS)); + + ExecutionContext executionContext = mock(ExecutionContext.class, RETURNS_DEEP_STUBS); + when(executionContext.getExecutionUnits()).thenReturn(Collections.singletonList(mock(ExecutionUnit.class))); + when(executionContext.getSqlStatementContext()).thenReturn(sqlStatementContext); + when(executionContext.getRouteContext().getRouteUnits()).thenReturn(Collections.emptyList()); + + AdvancedProxySQLExecutor advancedProxySQLExecutor = mock(AdvancedProxySQLExecutor.class); + when(advancedProxySQLExecutor.execute(any(ExecutionContext.class), any(ContextManager.class), any(ShardingSphereDatabase.class), any(DatabaseProxyConnector.class))) + .thenReturn(Collections.singletonList(new UpdateResult(1, 0L))); + + try ( + MockedConstruction mockedKernelProcessor = mockConstruction(KernelProcessor.class, + (mock, context) -> when(mock.generateExecutionContext(any(QueryContext.class), any(RuleMetaData.class), any(ConfigurationProperties.class))).thenReturn(executionContext)); + MockedConstruction mockedDatabaseTypeRegistry = mockConstruction(DatabaseTypeRegistry.class, + (mock, context) -> when(mock.getDialectDatabaseMetaData()).thenReturn(mock(DialectDatabaseMetaData.class))); + MockedConstruction mockedPushDownMetaDataRefreshEngine = mockConstruction(PushDownMetaDataRefreshEngine.class, + (mock, context) -> when(mock.isNeedRefresh()).thenReturn(true)); + MockedStatic serviceLoader = mockStatic(ShardingSphereServiceLoader.class)) { + serviceLoader.when(() -> ShardingSphereServiceLoader.getServiceInstances(AdvancedProxySQLExecutor.class)) + .thenReturn(Collections.singleton(advancedProxySQLExecutor)); + + UpdateResponseHeader actual = (UpdateResponseHeader) engine.execute(); + assertThat(actual.getUpdateCount(), is(1L)); + assertThat(actual.getLastInsertId(), is(0L)); + assertThat(mockedKernelProcessor.constructed().size(), is(1)); + assertThat(mockedDatabaseTypeRegistry.constructed().size(), is(1)); + assertThat(mockedPushDownMetaDataRefreshEngine.constructed().size(), is(1)); + } + } + private SQLStatementContext createSQLStatementContext(final SQLStatement sqlStatement) { SQLStatementContext result = mock(SQLStatementContext.class, RETURNS_DEEP_STUBS); when(result.getSqlStatement()).thenReturn(sqlStatement);