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 11e948a4e96 Fix firebird batch rls cancel without batch (#40098)
11e948a4e96 is described below
commit 11e948a4e9680dfee35442170cacdb5eeab486a4
Author: Maxim Igoshin <[email protected]>
AuthorDate: Sun Oct 18 06:36:42 2026 +0300
Fix firebird batch rls cancel without batch (#40098)
* Reject Firebird op_batch_rls and op_batch_cancel without an open batch
The server checks that the statement has a batch and answers
isc_bad_batch_handle otherwise, keeping the connection.
* Keep Firebird transaction usable after op_batch_rls or op_batch_cancel
without a batch
Return the isc_bad_batch_handle response from the batch release and cancel
executors instead of throwing InvalidBatchHandleException, so the frontend
engine does not mark the transaction as failed and a following COMMIT still
commits the changes made before, as the Firebird server does.
* Add TODO to restore checkNotNull in Firebird batch release and cancel
executors
* Fix CODE_OF_CONDUCT violations in Firebird batch release and cancel tests
* Trigger CI
---
.../batch/FirebirdBatchCancelCommandExecutor.java | 8 ++-
.../batch/FirebirdBatchReleaseCommandExecutor.java | 9 ++-
.../FirebirdBatchCancelCommandExecutorTest.java | 78 +++++++++++++++++++--
.../FirebirdBatchReleaseCommandExecutorTest.java | 80 +++++++++++++++++++++-
4 files changed, 165 insertions(+), 10 deletions(-)
diff --git
a/proxy/frontend/dialect/firebird/src/main/java/org/apache/shardingsphere/proxy/frontend/firebird/command/query/batch/FirebirdBatchCancelCommandExecutor.java
b/proxy/frontend/dialect/firebird/src/main/java/org/apache/shardingsphere/proxy/frontend/firebird/command/query/batch/FirebirdBatchCancelCommandExecutor.java
index 704e69556ea..9ad52c4dfaa 100644
---
a/proxy/frontend/dialect/firebird/src/main/java/org/apache/shardingsphere/proxy/frontend/firebird/command/query/batch/FirebirdBatchCancelCommandExecutor.java
+++
b/proxy/frontend/dialect/firebird/src/main/java/org/apache/shardingsphere/proxy/frontend/firebird/command/query/batch/FirebirdBatchCancelCommandExecutor.java
@@ -18,6 +18,8 @@
package org.apache.shardingsphere.proxy.frontend.firebird.command.query.batch;
import lombok.RequiredArgsConstructor;
+import
org.apache.shardingsphere.database.exception.firebird.exception.protocol.InvalidBatchHandleException;
+import
org.apache.shardingsphere.database.protocol.firebird.err.FirebirdErrorPacketFactory;
import
org.apache.shardingsphere.database.protocol.firebird.packet.command.query.batch.FirebirdBatchCancelCommandPacket;
import
org.apache.shardingsphere.database.protocol.firebird.packet.command.query.batch.FirebirdBatchStatement;
import
org.apache.shardingsphere.database.protocol.firebird.packet.generic.FirebirdGenericResponsePacket;
@@ -43,9 +45,11 @@ public final class FirebirdBatchCancelCommandExecutor
implements CommandExecutor
public Collection<DatabasePacket> execute() throws SQLException {
FirebirdBatchStatementManager batchStatementManager =
FirebirdBatchStatementManager.getInstance();
FirebirdBatchStatement batchStatement =
batchStatementManager.getBatchStatement(connectionSession.getConnectionId(),
packet.getStatementHandle());
- if (null != batchStatement) {
- batchStatementManager.resetBatchStatement(batchStatement);
+ // TODO restore checkNotNull when FirebirdFrontendEngine stops marking
the transaction as failed for InvalidBatchHandleException
+ if (null == batchStatement) {
+ return
Collections.singleton(FirebirdErrorPacketFactory.newInstance(new
InvalidBatchHandleException(packet.getStatementHandle())));
}
+ batchStatementManager.resetBatchStatement(batchStatement);
return Collections.singleton(new FirebirdGenericResponsePacket());
}
}
diff --git
a/proxy/frontend/dialect/firebird/src/main/java/org/apache/shardingsphere/proxy/frontend/firebird/command/query/batch/FirebirdBatchReleaseCommandExecutor.java
b/proxy/frontend/dialect/firebird/src/main/java/org/apache/shardingsphere/proxy/frontend/firebird/command/query/batch/FirebirdBatchReleaseCommandExecutor.java
index d49b6da76fb..35ded6134fc 100644
---
a/proxy/frontend/dialect/firebird/src/main/java/org/apache/shardingsphere/proxy/frontend/firebird/command/query/batch/FirebirdBatchReleaseCommandExecutor.java
+++
b/proxy/frontend/dialect/firebird/src/main/java/org/apache/shardingsphere/proxy/frontend/firebird/command/query/batch/FirebirdBatchReleaseCommandExecutor.java
@@ -18,6 +18,8 @@
package org.apache.shardingsphere.proxy.frontend.firebird.command.query.batch;
import lombok.RequiredArgsConstructor;
+import
org.apache.shardingsphere.database.exception.firebird.exception.protocol.InvalidBatchHandleException;
+import
org.apache.shardingsphere.database.protocol.firebird.err.FirebirdErrorPacketFactory;
import
org.apache.shardingsphere.database.protocol.firebird.packet.command.query.batch.FirebirdBatchReleaseCommandPacket;
import
org.apache.shardingsphere.database.protocol.firebird.packet.generic.FirebirdGenericResponsePacket;
import org.apache.shardingsphere.database.protocol.packet.DatabasePacket;
@@ -40,7 +42,12 @@ public final class FirebirdBatchReleaseCommandExecutor
implements CommandExecuto
@Override
public Collection<DatabasePacket> execute() throws SQLException {
-
FirebirdBatchStatementManager.getInstance().unregisterBatchStatement(connectionSession.getConnectionId(),
packet.getStatementHandle());
+ FirebirdBatchStatementManager batchStatementManager =
FirebirdBatchStatementManager.getInstance();
+ // TODO restore checkNotNull when FirebirdFrontendEngine stops marking
the transaction as failed for InvalidBatchHandleException
+ if (null ==
batchStatementManager.getBatchStatement(connectionSession.getConnectionId(),
packet.getStatementHandle())) {
+ return
Collections.singleton(FirebirdErrorPacketFactory.newInstance(new
InvalidBatchHandleException(packet.getStatementHandle())));
+ }
+
batchStatementManager.unregisterBatchStatement(connectionSession.getConnectionId(),
packet.getStatementHandle());
return Collections.singleton(new FirebirdGenericResponsePacket());
}
}
diff --git
a/proxy/frontend/dialect/firebird/src/test/java/org/apache/shardingsphere/proxy/frontend/firebird/command/query/batch/FirebirdBatchCancelCommandExecutorTest.java
b/proxy/frontend/dialect/firebird/src/test/java/org/apache/shardingsphere/proxy/frontend/firebird/command/query/batch/FirebirdBatchCancelCommandExecutorTest.java
index 0f5d02f249a..2ebaf036a33 100644
---
a/proxy/frontend/dialect/firebird/src/test/java/org/apache/shardingsphere/proxy/frontend/firebird/command/query/batch/FirebirdBatchCancelCommandExecutorTest.java
+++
b/proxy/frontend/dialect/firebird/src/test/java/org/apache/shardingsphere/proxy/frontend/firebird/command/query/batch/FirebirdBatchCancelCommandExecutorTest.java
@@ -17,30 +17,59 @@
package org.apache.shardingsphere.proxy.frontend.firebird.command.query.batch;
+import io.netty.buffer.Unpooled;
+import io.netty.channel.ChannelHandlerContext;
+import io.netty.util.DefaultAttributeMap;
+import
org.apache.shardingsphere.database.connector.core.spi.DatabaseTypedSPILoader;
+import org.apache.shardingsphere.database.connector.core.type.DatabaseType;
+import org.apache.shardingsphere.database.protocol.constant.CommonConstants;
+import
org.apache.shardingsphere.database.protocol.firebird.packet.command.FirebirdCommandPacketType;
import
org.apache.shardingsphere.database.protocol.firebird.packet.command.query.batch.FirebirdBatchCancelCommandPacket;
import
org.apache.shardingsphere.database.protocol.firebird.packet.command.query.batch.FirebirdBatchRegistry;
import
org.apache.shardingsphere.database.protocol.firebird.packet.command.query.batch.FirebirdBatchStatement;
import
org.apache.shardingsphere.database.protocol.firebird.packet.generic.FirebirdGenericResponsePacket;
import org.apache.shardingsphere.database.protocol.packet.DatabasePacket;
+import org.apache.shardingsphere.infra.config.props.ConfigurationProperties;
+import org.apache.shardingsphere.infra.metadata.ShardingSphereMetaData;
+import org.apache.shardingsphere.infra.metadata.database.rule.RuleMetaData;
+import org.apache.shardingsphere.infra.metadata.user.Grantee;
+import org.apache.shardingsphere.infra.spi.type.typed.TypedSPILoader;
+import org.apache.shardingsphere.proxy.backend.context.ProxyContext;
import org.apache.shardingsphere.proxy.backend.session.ConnectionSession;
+import org.apache.shardingsphere.proxy.frontend.command.CommandExecutorTask;
+import
org.apache.shardingsphere.proxy.frontend.firebird.command.query.transaction.FirebirdTransactionIdGenerator;
+import
org.apache.shardingsphere.proxy.frontend.spi.DatabaseProtocolFrontendEngine;
+import
org.apache.shardingsphere.test.infra.framework.extension.mock.AutoMockExtension;
+import
org.apache.shardingsphere.test.infra.framework.extension.mock.StaticMockSettings;
+import org.apache.shardingsphere.transaction.api.TransactionType;
+import org.apache.shardingsphere.transaction.rule.TransactionRule;
+import org.firebirdsql.gds.ISCConstants;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Answers;
+import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
-import org.mockito.junit.jupiter.MockitoExtension;
+import java.nio.charset.StandardCharsets;
+import java.sql.Connection;
import java.sql.SQLException;
import java.util.Arrays;
import java.util.Collection;
+import java.util.Collections;
+import java.util.Properties;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.isA;
-import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
-@ExtendWith(MockitoExtension.class)
+@ExtendWith(AutoMockExtension.class)
+@StaticMockSettings(ProxyContext.class)
class FirebirdBatchCancelCommandExecutorTest {
private static final int CONNECTION_ID = 2;
@@ -53,6 +82,12 @@ class FirebirdBatchCancelCommandExecutorTest {
@Mock
private FirebirdBatchCancelCommandPacket packet;
+ @Mock(answer = Answers.RETURNS_DEEP_STUBS)
+ private ChannelHandlerContext channelHandlerContext;
+
+ @Mock
+ private Connection backendConnection;
+
@Test
void assertExecute() throws SQLException {
FirebirdBatchRegistry.getInstance().registerConnection(CONNECTION_ID);
@@ -71,9 +106,42 @@ class FirebirdBatchCancelCommandExecutorTest {
}
@Test
- void assertExecuteWhenBatchNotFound() {
+ void assertExecuteWhenBatchNotFound() throws SQLException {
when(connectionSession.getConnectionId()).thenReturn(CONNECTION_ID);
when(packet.getStatementHandle()).thenReturn(STATEMENT_ID);
- assertDoesNotThrow(() -> new
FirebirdBatchCancelCommandExecutor(packet, connectionSession).execute());
+ Collection<DatabasePacket> actual = new
FirebirdBatchCancelCommandExecutor(packet, connectionSession).execute();
+ assertThat(actual.size(), is(1));
+ assertThat(((FirebirdGenericResponsePacket)
actual.iterator().next()).getErrorCode(),
is(ISCConstants.isc_bad_batch_handle));
+ }
+
+ @Test
+ void assertExecuteWhenBatchNotFoundInTransaction() throws SQLException {
+ ConnectionSession transactionSession =
createConnectionSessionInTransaction();
+ int transactionHandle =
FirebirdTransactionIdGenerator.getInstance().nextTransactionId(CONNECTION_ID);
+ DatabaseProtocolFrontendEngine engine =
DatabaseTypedSPILoader.getService(DatabaseProtocolFrontendEngine.class,
TypedSPILoader.getService(DatabaseType.class, "Firebird"));
+ new CommandExecutorTask(engine, transactionSession,
channelHandlerContext,
Unpooled.buffer().writeInt(FirebirdCommandPacketType.BATCH_CANCEL.getValue()).writeInt(STATEMENT_ID)).run();
+ new CommandExecutorTask(engine, transactionSession,
channelHandlerContext,
Unpooled.buffer().writeInt(FirebirdCommandPacketType.COMMIT.getValue()).writeInt(transactionHandle)).run();
+
FirebirdTransactionIdGenerator.getInstance().unregisterConnection(CONNECTION_ID);
+ ArgumentCaptor<FirebirdGenericResponsePacket> responseCaptor =
ArgumentCaptor.forClass(FirebirdGenericResponsePacket.class);
+ verify(channelHandlerContext,
times(2)).write(responseCaptor.capture());
+ assertThat(responseCaptor.getAllValues().get(0).getErrorCode(),
is(ISCConstants.isc_bad_batch_handle));
+ verify(backendConnection).commit();
+ verify(backendConnection, never()).rollback();
+ }
+
+ private ConnectionSession createConnectionSessionInTransaction() {
+ TransactionRule transactionRule = mock(TransactionRule.class);
+
when(transactionRule.getDefaultType()).thenReturn(TransactionType.LOCAL);
+
when(ProxyContext.getInstance().getContextManager().getMetaDataContexts().getMetaData()).thenReturn(
+ new ShardingSphereMetaData(Collections.emptyList(), mock(),
new RuleMetaData(Collections.singleton(transactionRule)), new
ConfigurationProperties(new Properties())));
+
when(channelHandlerContext.channel().attr(CommonConstants.CHARSET_ATTRIBUTE_KEY).get()).thenReturn(StandardCharsets.UTF_8);
+ ConnectionSession result = new
ConnectionSession(TypedSPILoader.getService(DatabaseType.class, "Firebird"),
new DefaultAttributeMap());
+ result.setConnectionId(CONNECTION_ID);
+ result.setGrantee(new Grantee("foo_user", "%"));
+ result.setAutoCommit(false);
+ result.getDatabaseConnectionManager().handleAutoCommit();
+
result.getDatabaseConnectionManager().getCachedConnections().put("foo_db.foo_ds",
backendConnection);
+
FirebirdTransactionIdGenerator.getInstance().registerConnection(CONNECTION_ID);
+ return result;
}
}
diff --git
a/proxy/frontend/dialect/firebird/src/test/java/org/apache/shardingsphere/proxy/frontend/firebird/command/query/batch/FirebirdBatchReleaseCommandExecutorTest.java
b/proxy/frontend/dialect/firebird/src/test/java/org/apache/shardingsphere/proxy/frontend/firebird/command/query/batch/FirebirdBatchReleaseCommandExecutorTest.java
index 3a3b961fe00..50458f8ce63 100644
---
a/proxy/frontend/dialect/firebird/src/test/java/org/apache/shardingsphere/proxy/frontend/firebird/command/query/batch/FirebirdBatchReleaseCommandExecutorTest.java
+++
b/proxy/frontend/dialect/firebird/src/test/java/org/apache/shardingsphere/proxy/frontend/firebird/command/query/batch/FirebirdBatchReleaseCommandExecutorTest.java
@@ -17,28 +17,58 @@
package org.apache.shardingsphere.proxy.frontend.firebird.command.query.batch;
+import io.netty.buffer.Unpooled;
+import io.netty.channel.ChannelHandlerContext;
+import io.netty.util.DefaultAttributeMap;
+import
org.apache.shardingsphere.database.connector.core.spi.DatabaseTypedSPILoader;
+import org.apache.shardingsphere.database.connector.core.type.DatabaseType;
+import org.apache.shardingsphere.database.protocol.constant.CommonConstants;
+import
org.apache.shardingsphere.database.protocol.firebird.packet.command.FirebirdCommandPacketType;
import
org.apache.shardingsphere.database.protocol.firebird.packet.command.query.batch.FirebirdBatchRegistry;
import
org.apache.shardingsphere.database.protocol.firebird.packet.command.query.batch.FirebirdBatchReleaseCommandPacket;
import
org.apache.shardingsphere.database.protocol.firebird.packet.command.query.batch.FirebirdBatchStatement;
import
org.apache.shardingsphere.database.protocol.firebird.packet.generic.FirebirdGenericResponsePacket;
import org.apache.shardingsphere.database.protocol.packet.DatabasePacket;
+import org.apache.shardingsphere.infra.config.props.ConfigurationProperties;
+import org.apache.shardingsphere.infra.metadata.ShardingSphereMetaData;
+import org.apache.shardingsphere.infra.metadata.database.rule.RuleMetaData;
+import org.apache.shardingsphere.infra.metadata.user.Grantee;
+import org.apache.shardingsphere.infra.spi.type.typed.TypedSPILoader;
+import org.apache.shardingsphere.proxy.backend.context.ProxyContext;
import org.apache.shardingsphere.proxy.backend.session.ConnectionSession;
+import org.apache.shardingsphere.proxy.frontend.command.CommandExecutorTask;
+import
org.apache.shardingsphere.proxy.frontend.firebird.command.query.transaction.FirebirdTransactionIdGenerator;
+import
org.apache.shardingsphere.proxy.frontend.spi.DatabaseProtocolFrontendEngine;
+import
org.apache.shardingsphere.test.infra.framework.extension.mock.AutoMockExtension;
+import
org.apache.shardingsphere.test.infra.framework.extension.mock.StaticMockSettings;
+import org.apache.shardingsphere.transaction.api.TransactionType;
+import org.apache.shardingsphere.transaction.rule.TransactionRule;
+import org.firebirdsql.gds.ISCConstants;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Answers;
+import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
-import org.mockito.junit.jupiter.MockitoExtension;
+import java.nio.charset.StandardCharsets;
+import java.sql.Connection;
import java.sql.SQLException;
import java.util.Collection;
+import java.util.Collections;
+import java.util.Properties;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.isA;
import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
-@ExtendWith(MockitoExtension.class)
+@ExtendWith(AutoMockExtension.class)
+@StaticMockSettings(ProxyContext.class)
class FirebirdBatchReleaseCommandExecutorTest {
private static final int CONNECTION_ID = 2;
@@ -51,6 +81,12 @@ class FirebirdBatchReleaseCommandExecutorTest {
@Mock
private FirebirdBatchReleaseCommandPacket packet;
+ @Mock(answer = Answers.RETURNS_DEEP_STUBS)
+ private ChannelHandlerContext channelHandlerContext;
+
+ @Mock
+ private Connection backendConnection;
+
@Test
void assertExecute() throws SQLException {
FirebirdBatchRegistry.getInstance().registerConnection(CONNECTION_ID);
@@ -63,4 +99,44 @@ class FirebirdBatchReleaseCommandExecutorTest {
assertNull(FirebirdBatchRegistry.getInstance().getBatchStatement(CONNECTION_ID,
STATEMENT_ID));
FirebirdBatchRegistry.getInstance().unregisterConnection(CONNECTION_ID);
}
+
+ @Test
+ void assertExecuteWhenBatchNotFound() throws SQLException {
+ when(connectionSession.getConnectionId()).thenReturn(CONNECTION_ID);
+ when(packet.getStatementHandle()).thenReturn(STATEMENT_ID);
+ Collection<DatabasePacket> actual = new
FirebirdBatchReleaseCommandExecutor(packet, connectionSession).execute();
+ assertThat(actual.size(), is(1));
+ assertThat(((FirebirdGenericResponsePacket)
actual.iterator().next()).getErrorCode(),
is(ISCConstants.isc_bad_batch_handle));
+ }
+
+ @Test
+ void assertExecuteWhenBatchNotFoundInTransaction() throws SQLException {
+ ConnectionSession transactionSession =
createConnectionSessionInTransaction();
+ int transactionHandle =
FirebirdTransactionIdGenerator.getInstance().nextTransactionId(CONNECTION_ID);
+ DatabaseProtocolFrontendEngine engine =
DatabaseTypedSPILoader.getService(DatabaseProtocolFrontendEngine.class,
TypedSPILoader.getService(DatabaseType.class, "Firebird"));
+ new CommandExecutorTask(engine, transactionSession,
channelHandlerContext,
Unpooled.buffer().writeInt(FirebirdCommandPacketType.BATCH_RLS.getValue()).writeInt(STATEMENT_ID)).run();
+ new CommandExecutorTask(engine, transactionSession,
channelHandlerContext,
Unpooled.buffer().writeInt(FirebirdCommandPacketType.COMMIT.getValue()).writeInt(transactionHandle)).run();
+
FirebirdTransactionIdGenerator.getInstance().unregisterConnection(CONNECTION_ID);
+ ArgumentCaptor<FirebirdGenericResponsePacket> responseCaptor =
ArgumentCaptor.forClass(FirebirdGenericResponsePacket.class);
+ verify(channelHandlerContext,
times(2)).write(responseCaptor.capture());
+ assertThat(responseCaptor.getAllValues().get(0).getErrorCode(),
is(ISCConstants.isc_bad_batch_handle));
+ verify(backendConnection).commit();
+ verify(backendConnection, never()).rollback();
+ }
+
+ private ConnectionSession createConnectionSessionInTransaction() {
+ TransactionRule transactionRule = mock(TransactionRule.class);
+
when(transactionRule.getDefaultType()).thenReturn(TransactionType.LOCAL);
+
when(ProxyContext.getInstance().getContextManager().getMetaDataContexts().getMetaData()).thenReturn(
+ new ShardingSphereMetaData(Collections.emptyList(), mock(),
new RuleMetaData(Collections.singleton(transactionRule)), new
ConfigurationProperties(new Properties())));
+
when(channelHandlerContext.channel().attr(CommonConstants.CHARSET_ATTRIBUTE_KEY).get()).thenReturn(StandardCharsets.UTF_8);
+ ConnectionSession result = new
ConnectionSession(TypedSPILoader.getService(DatabaseType.class, "Firebird"),
new DefaultAttributeMap());
+ result.setConnectionId(CONNECTION_ID);
+ result.setGrantee(new Grantee("foo_user", "%"));
+ result.setAutoCommit(false);
+ result.getDatabaseConnectionManager().handleAutoCommit();
+
result.getDatabaseConnectionManager().getCachedConnections().put("foo_db.foo_ds",
backendConnection);
+
FirebirdTransactionIdGenerator.getInstance().registerConnection(CONNECTION_ID);
+ return result;
+ }
}