You are viewing a plain text version of this content. The canonical link for it is here.
Posted to notifications@shardingsphere.apache.org by "makssent (via GitHub)" <gi...@apache.org> on 2026/10/26 13:57:15 UTC

[I] Firebird Proxy does not check the statement handle of op_info_sql and op_free_statement as the Firebird server does [shardingsphere]

makssent opened a new issue, #40373:
URL: https://github.com/apache/shardingsphere/issues/40373

   ### Before reporting a bug
   
   - [x] I have searched open and closed [GitHub issues](https://github.com/apache/shardingsphere/issues).
   - [x] I have read the [ShardingSphere documentation](https://shardingsphere.apache.org/document/current/en/overview).
   
   ### ShardingSphere version
   
   master, commit [`f0b3e2fe124`](https://github.com/apache/shardingsphere/commit/f0b3e2fe1249b9a24d0319fe26677b85a496a8e8)
   
   ### ShardingSphere project
   
   ShardingSphere-Proxy
   
   ### Expected behavior
   
   The proxy should find the statement of [`op_info_sql`](https://github.com/FirebirdSQL/firebird/blob/v5.0.4/src/remote/server/server.cpp#L4709-L4715) and [`op_free_statement`](https://github.com/FirebirdSQL/firebird/blob/v5.0.4/src/remote/server/server.cpp#L3316-L3332) by its handle as the Firebird server does ([`getHandle`](https://github.com/FirebirdSQL/firebird/blob/v5.0.4/src/remote/remote.h#L1494-L1506)). For a handle that was never allocated or was dropped by [`DSQL_drop`](https://github.com/FirebirdSQL/firebird/blob/v5.0.4/src/remote/server/server.cpp#L3375-L3379), both operations answer [`isc_bad_req_handle`](https://github.com/FirebirdSQL/firebird/blob/v5.0.4/src/remote/remote.h#L771). For a statement that is allocated but not prepared, [`op_info_sql`](https://github.com/FirebirdSQL/firebird/blob/v5.0.4/src/remote/server/server.cpp#L4709-L4715) answers [`isc_unprepared_stmt`](https://github.com/FirebirdSQL/firebird/blob/v5.0.4/src/remote/server/server.cpp#L2257-L2261). On 
 a connection that accepted [`ptype_lazy_send`](https://github.com/FirebirdSQL/firebird/blob/v5.0.4/src/remote/server/server.cpp#L2038-L2039), the handle `0xFFFF` ([`INVALID_OBJECT`](https://github.com/FirebirdSQL/firebird/blob/v5.0.4/src/remote/protocol.h#L155)) means the last object allocated on the connection: a statement is used, any other object gives [`isc_bad_req_handle`](https://github.com/FirebirdSQL/firebird/blob/v5.0.4/src/remote/remote.h#L771). The error is sent in [`op_response`](https://github.com/FirebirdSQL/firebird/blob/v5.0.4/src/remote/server/server.cpp#L5351-L5361), and the connection and the transaction stay usable.
   
   Support of further [`op_info_sql`](https://github.com/FirebirdSQL/firebird/blob/v5.0.4/src/remote/server/server.cpp#L4709-L4715) items is tracked in #39939. This issue is only about the statement handle.
   
   ### Actual behavior
   
   The proxy answers every case with success, returns zero record counts from [`op_info_sql`](https://github.com/FirebirdSQL/firebird/blob/v5.0.4/src/remote/server/server.cpp#L4709-L4715) for a statement that does not exist or is not prepared, and does not replace the handle `0xFFFF`.
   
   The same packets were sent directly to the Firebird server and through the proxy on one lazy connection. [`op_info_sql`](https://github.com/FirebirdSQL/firebird/blob/v5.0.4/src/remote/server/server.cpp#L4709-L4715) requests [`isc_info_sql_records`](https://github.com/FirebirdSQL/firebird/blob/v5.0.4/src/dsql/dsql.cpp#L916-L920):
   
   | Steps | Firebird server | Proxy |
   | --- | --- | --- |
   | [`op_info_sql`](https://github.com/FirebirdSQL/firebird/blob/v5.0.4/src/remote/server/server.cpp#L4709-L4715) with the handle `0x7FFF` that was never allocated | Error [`isc_bad_req_handle`](https://github.com/FirebirdSQL/firebird/blob/v5.0.4/src/remote/remote.h#L771) "invalid request handle" | Success, insert count 0 |
   | [`op_free_statement`](https://github.com/FirebirdSQL/firebird/blob/v5.0.4/src/remote/server/server.cpp#L3316-L3332) with [`DSQL_drop`](https://github.com/FirebirdSQL/firebird/blob/v5.0.4/src/remote/server/server.cpp#L3375-L3379) and the handle `0x7FFF` | Error [`isc_bad_req_handle`](https://github.com/FirebirdSQL/firebird/blob/v5.0.4/src/remote/remote.h#L771) "invalid request handle" | Success |
   | [`op_allocate_statement`](https://github.com/FirebirdSQL/firebird/blob/v5.0.4/src/remote/server/server.cpp#L2303-L2307), then [`op_info_sql`](https://github.com/FirebirdSQL/firebird/blob/v5.0.4/src/remote/server/server.cpp#L4709-L4715) of the new statement | Error [`isc_unprepared_stmt`](https://github.com/FirebirdSQL/firebird/blob/v5.0.4/src/remote/server/server.cpp#L2257-L2261) "Attempt to execute an unprepared dynamic SQL statement." | Success, insert count 0 |
   | [`op_allocate_statement`](https://github.com/FirebirdSQL/firebird/blob/v5.0.4/src/remote/server/server.cpp#L2303-L2307), [`DSQL_drop`](https://github.com/FirebirdSQL/firebird/blob/v5.0.4/src/remote/server/server.cpp#L3375-L3379), then [`op_info_sql`](https://github.com/FirebirdSQL/firebird/blob/v5.0.4/src/remote/server/server.cpp#L4709-L4715) of the dropped statement | Error [`isc_bad_req_handle`](https://github.com/FirebirdSQL/firebird/blob/v5.0.4/src/remote/remote.h#L771) "invalid request handle" | Success, insert count 0 |
   | Prepare `INSERT INTO T_PLAIN (ID) VALUES (?)`, [`DSQL_unprepare`](https://github.com/FirebirdSQL/firebird/blob/v5.0.4/src/remote/server/server.cpp#L3362-L3373), then [`op_info_sql`](https://github.com/FirebirdSQL/firebird/blob/v5.0.4/src/remote/server/server.cpp#L4709-L4715) of the statement | Error [`isc_unprepared_stmt`](https://github.com/FirebirdSQL/firebird/blob/v5.0.4/src/remote/server/server.cpp#L2257-L2261) "Attempt to execute an unprepared dynamic SQL statement." | Success, insert count 0 |
   | [`op_allocate_statement`](https://github.com/FirebirdSQL/firebird/blob/v5.0.4/src/remote/server/server.cpp#L2303-L2307), [`op_transaction`](https://github.com/FirebirdSQL/firebird/blob/v5.0.4/src/remote/server/server.cpp#L4772-L4778), then [`DSQL_drop`](https://github.com/FirebirdSQL/firebird/blob/v5.0.4/src/remote/server/server.cpp#L3375-L3379) with the handle `0xFFFF` | Error [`isc_bad_req_handle`](https://github.com/FirebirdSQL/firebird/blob/v5.0.4/src/remote/remote.h#L771) "invalid request handle": the last object is the transaction, the statement stays | Success |
   | Execute `INSERT INTO T_PLAIN (ID, NAME) VALUES (4101, 'lazy')`, then [`op_info_sql`](https://github.com/FirebirdSQL/firebird/blob/v5.0.4/src/remote/server/server.cpp#L4709-L4715) with the handle `0xFFFF` | Success, insert count 1 | Success, insert count 0 |
   | [`op_allocate_statement`](https://github.com/FirebirdSQL/firebird/blob/v5.0.4/src/remote/server/server.cpp#L2303-L2307), [`DSQL_drop`](https://github.com/FirebirdSQL/firebird/blob/v5.0.4/src/remote/server/server.cpp#L3375-L3379) with the handle `0xFFFF`, then [`op_info_sql`](https://github.com/FirebirdSQL/firebird/blob/v5.0.4/src/remote/server/server.cpp#L4709-L4715) of the statement | The statement is dropped: [`op_info_sql`](https://github.com/FirebirdSQL/firebird/blob/v5.0.4/src/remote/server/server.cpp#L4709-L4715) answers [`isc_bad_req_handle`](https://github.com/FirebirdSQL/firebird/blob/v5.0.4/src/remote/remote.h#L771) "invalid request handle" | The statement is not dropped: [`op_info_sql`](https://github.com/FirebirdSQL/firebird/blob/v5.0.4/src/remote/server/server.cpp#L4709-L4715) answers success, insert count 0 |
   | Insert the row with ID = 4102, commit, and read the rows directly on the Firebird server | The rows with ID = 4101 and 4102 are saved | The rows with ID = 4101 and 4102 are saved |
   
   So through the proxy a client cannot tell that a statement handle is wrong or that a statement is not prepared: it gets zero record counts instead of an error. With the handle `0xFFFF` the client gets wrong record counts, and [`DSQL_drop`](https://github.com/FirebirdSQL/firebird/blob/v5.0.4/src/remote/server/server.cpp#L3375-L3379) leaves the statement allocated.
   
   ### Reason analysis
   
   - [`FirebirdSQLInfoExecutor#getRecordsInfo`](https://github.com/apache/shardingsphere/blob/f0b3e2fe1249b9a24d0319fe26677b85a496a8e8/proxy/frontend/dialect/firebird/src/main/java/org/apache/shardingsphere/proxy/frontend/firebird/command/query/info/FirebirdSQLInfoExecutor.java#L52-L63) looks up the prepared statement by the handle and returns zero counts when there is none, so a handle that was never allocated, a dropped statement and a statement that is not prepared all get a success answer. [`FirebirdFreeStatementCommandExecutor#execute`](https://github.com/apache/shardingsphere/blob/f0b3e2fe1249b9a24d0319fe26677b85a496a8e8/proxy/frontend/dialect/firebird/src/main/java/org/apache/shardingsphere/proxy/frontend/firebird/command/query/statement/free/FirebirdFreeStatementCommandExecutor.java#L45-L61) removes the statement and its resources by the handle and answers success in any case. The Firebird server checks the handle first ([`getHandle`](https://github.com/FirebirdSQL/firebird/b
 lob/v5.0.4/src/remote/remote.h#L1494-L1506)) and, in [`op_info_sql`](https://github.com/FirebirdSQL/firebird/blob/v5.0.4/src/remote/server/server.cpp#L4709-L4715), that the statement is prepared ([`checkIface`](https://github.com/FirebirdSQL/firebird/blob/v5.0.4/src/remote/server/server.cpp#L2257-L2261)).
   - The proxy does not know which statement handles are allocated. [`FirebirdStatementIdGenerator#nextStatementId`](https://github.com/apache/shardingsphere/blob/f0b3e2fe1249b9a24d0319fe26677b85a496a8e8/proxy/frontend/dialect/firebird/src/main/java/org/apache/shardingsphere/proxy/frontend/firebird/command/query/statement/FirebirdStatementIdGenerator.java#L62-L64) only increments a counter for [`FirebirdAllocateStatementCommandExecutor`](https://github.com/apache/shardingsphere/blob/f0b3e2fe1249b9a24d0319fe26677b85a496a8e8/proxy/frontend/dialect/firebird/src/main/java/org/apache/shardingsphere/proxy/frontend/firebird/command/query/statement/allocate/FirebirdAllocateStatementCommandExecutor.java#L40), and [`DSQL_drop`](https://github.com/FirebirdSQL/firebird/blob/v5.0.4/src/remote/server/server.cpp#L3375-L3379) does not release the handle.
   - The handle `0xFFFF` is not resolved in [`FirebirdSQLInfoExecutor`](https://github.com/apache/shardingsphere/blob/f0b3e2fe1249b9a24d0319fe26677b85a496a8e8/proxy/frontend/dialect/firebird/src/main/java/org/apache/shardingsphere/proxy/frontend/firebird/command/query/info/FirebirdSQLInfoExecutor.java#L52-L63) and [`FirebirdFreeStatementCommandExecutor`](https://github.com/apache/shardingsphere/blob/f0b3e2fe1249b9a24d0319fe26677b85a496a8e8/proxy/frontend/dialect/firebird/src/main/java/org/apache/shardingsphere/proxy/frontend/firebird/command/query/statement/free/FirebirdFreeStatementCommandExecutor.java#L45-L61). The Firebird server replaces it with [`port_last_object_id`](https://github.com/FirebirdSQL/firebird/blob/v5.0.4/src/remote/remote.h#L1497-L1500) only on a lazy connection. [`get_id`](https://github.com/FirebirdSQL/firebird/blob/v5.0.4/src/remote/remote.h#L1527-L1545) sets it for every object of the connection, for example a statement ([`op_allocate_statement`](https://githu
 b.com/FirebirdSQL/firebird/blob/v5.0.4/src/remote/server/server.cpp#L2303-L2307)) and a transaction ([`op_transaction`](https://github.com/FirebirdSQL/firebird/blob/v5.0.4/src/remote/server/server.cpp#L4772-L4778)), and an object of another kind gives [`isc_bad_req_handle`](https://github.com/FirebirdSQL/firebird/blob/v5.0.4/src/remote/remote.h#L811-L831). The proxy keeps neither of them: [`FirebirdAcceptPacket#write`](https://github.com/apache/shardingsphere/blob/f0b3e2fe1249b9a24d0319fe26677b85a496a8e8/database/protocol/dialect/firebird/src/main/java/org/apache/shardingsphere/database/protocol/firebird/packet/handshake/FirebirdAcceptPacket.java#L77-L79) accepts the lazy type without storing it, and statements, transactions ([`FirebirdTransactionIdGenerator`](https://github.com/apache/shardingsphere/blob/f0b3e2fe1249b9a24d0319fe26677b85a496a8e8/proxy/frontend/dialect/firebird/src/main/java/org/apache/shardingsphere/proxy/frontend/firebird/command/query/transaction/FirebirdTransacti
 onIdGenerator.java)) and BLOBs ([`FirebirdBlobHandleGenerator`](https://github.com/apache/shardingsphere/blob/f0b3e2fe1249b9a24d0319fe26677b85a496a8e8/proxy/frontend/dialect/firebird/src/main/java/org/apache/shardingsphere/proxy/frontend/firebird/command/query/blob/generator/FirebirdBlobHandleGenerator.java)) have separate handle generators. Only [`FirebirdPrepareStatementCommandExecutor#getStatementId`](https://github.com/apache/shardingsphere/blob/f0b3e2fe1249b9a24d0319fe26677b85a496a8e8/proxy/frontend/dialect/firebird/src/main/java/org/apache/shardingsphere/proxy/frontend/firebird/command/query/statement/prepare/FirebirdPrepareStatementCommandExecutor.java#L147-L149) replaces `0xFFFF`, with the latest statement handle. The Firebird server reads the handle of [`op_info_sql`](https://github.com/FirebirdSQL/firebird/blob/v5.0.4/src/remote/protocol.cpp#L512-L516) and [`op_free_statement`](https://github.com/FirebirdSQL/firebird/blob/v5.0.4/src/remote/protocol.cpp#L750-L752) as 16 bit
 s, so `0xFFFFFFFF` is the same handle.
   - An error thrown as an exception from these executors in an open transaction makes [`FirebirdFrontendEngine#handleException`](https://github.com/apache/shardingsphere/blob/f0b3e2fe1249b9a24d0319fe26677b85a496a8e8/proxy/frontend/dialect/firebird/src/main/java/org/apache/shardingsphere/proxy/frontend/firebird/FirebirdFrontendEngine.java#L49-L54) mark the transaction as failed, so the next statement is rejected and COMMIT does not save the rows. The Firebird server answers the error in [`op_response`](https://github.com/FirebirdSQL/firebird/blob/v5.0.4/src/remote/server/server.cpp#L5351-L5361) and keeps the transaction. [`FirebirdBatchReleaseCommandExecutor`](https://github.com/apache/shardingsphere/blob/f0b3e2fe1249b9a24d0319fe26677b85a496a8e8/proxy/frontend/dialect/firebird/src/main/java/org/apache/shardingsphere/proxy/frontend/firebird/command/query/batch/FirebirdBatchReleaseCommandExecutor.java#L47) returns its handle error as a response packet for this reason.
   
   ### Steps to reproduce
   
   #### Environment
   
   - Firebird server 5.0.
   - Client: Jaybird 6.0.7.
   
   #### Steps
   
   1. Create the table on the Firebird server:
   
   ```sql
   CREATE TABLE T_PLAIN (ID INTEGER NOT NULL PRIMARY KEY, NAME VARCHAR(20));
   ```
   
   2. Start ShardingSphere-Proxy with this configuration:
   
   ```yaml
   # global.yaml
   authority:
     users:
       - user: root@%
         password: root
     privilege:
       type: ALL_PERMITTED
   props:
     proxy-frontend-database-protocol-type: Firebird
   ```
   
   ```yaml
   # database-firebird.yaml
   databaseName: firebird_db
   dataSources:
     ds_0:
       url: jdbc:firebirdsql://<host>/<database file>?encoding=UTF8
       username: <user>
       password: <password>
   rules:
     - !SINGLE
       tables:
         - "*.*"
   ```
   
   3. Send the packets from the table under "Actual behavior" through the proxy and directly to the Firebird server, and compare the results. "Example code" sends all of them.
   
   ### Example code
   
   All cases on Jaybird 6.0.7 through its low-level wire API, one method per case; each packet is followed by [`op_batch_sync`](https://github.com/FirebirdSQL/firebird/blob/v5.0.4/src/remote/server/server.cpp#L5307-L5309). The rows are read directly on the Firebird server. The comment above each method shows what it prints against the Firebird server and through the proxy.
   
   ```java
   import org.firebirdsql.gds.ISCConstants;
   import org.firebirdsql.gds.TransactionParameterBuffer;
   import org.firebirdsql.gds.impl.wire.XdrInputStream;
   import org.firebirdsql.gds.ng.FbExceptionBuilder;
   import org.firebirdsql.gds.ng.FbStatement;
   import org.firebirdsql.gds.ng.FbTransaction;
   import org.firebirdsql.gds.ng.LockCloseable;
   import org.firebirdsql.gds.ng.fields.RowValue;
   import org.firebirdsql.gds.ng.wire.FbWireDatabase;
   import org.firebirdsql.gds.ng.wire.TransmitAction;
   import org.firebirdsql.jaybird.fb.constants.TpbItems;
   import org.firebirdsql.jdbc.FirebirdConnection;
   
   import java.io.IOException;
   import java.lang.reflect.Field;
   import java.nio.charset.StandardCharsets;
   import java.sql.Connection;
   import java.sql.DriverManager;
   import java.sql.ResultSet;
   import java.sql.SQLException;
   import java.sql.Statement;
   
   public class StatementHandleState {
   
       // The proxy; put the URL, user and password of the Firebird server here to compare
       static final String URL = "jdbc:firebirdsql://<proxy host>:<proxy port>/firebird_db?encoding=UTF8&wireCrypt=DISABLED";
   
       static final String USER = "root";
   
       static final String PASSWORD = "root";
   
       // The Firebird server, to read the saved rows directly
       static final String SERVER_URL = "jdbc:firebirdsql://<host>/<database file>?encoding=UTF8";
   
       static final String SERVER_USER = "<user>";
   
       static final String SERVER_PASSWORD = "<password>";
   
       static final int NEVER_ALLOCATED_HANDLE = 0x7FFF;
   
       // INVALID_OBJECT: on a lazy connection the Firebird server replaces it with the last object allocated on the connection
       static final int LAZY_HANDLE = 0xFFFF;
   
       static final int DSQL_DROP = 2;
   
       static final int DSQL_UNPREPARE = 4;
   
       static FbWireDatabase database;
   
       static FbTransaction transaction;
   
       public static void main(final String[] args) throws Exception {
           executeOnServer("DELETE FROM T_PLAIN WHERE ID IN (4101, 4102)");
           // Jaybird 6 offers the lazy protocol type, so the connection is lazy
           try (Connection connection = DriverManager.getConnection(URL, USER, PASSWORD)) {
               database = (FbWireDatabase) connection.unwrap(FirebirdConnection.class).getFbDatabase();
               infoOfNeverAllocatedHandle();
               dropNeverAllocatedHandle();
               infoOfUnpreparedStatement();
               infoAfterDrop();
               infoAfterUnprepare();
               lazyDropAfterTransaction();
               lazyInfoAfterInsert();
               lazyDropAfterAllocate();
               commitAfterErrors();
           }
           executeOnServer("DELETE FROM T_PLAIN WHERE ID IN (4101, 4102)");
       }
   
       // Firebird server: "op_info_sql, handle 0x7FFF never allocated: error isc_bad_req_handle "invalid request handle"";
       // proxy: "op_info_sql, handle 0x7FFF never allocated: success, insert count 0"
       static void infoOfNeverAllocatedHandle() throws Exception {
           print("op_info_sql, handle 0x7FFF never allocated", sqlInfo(NEVER_ALLOCATED_HANDLE));
       }
   
       // Firebird server: "op_free_statement DSQL_drop, handle 0x7FFF never allocated: error isc_bad_req_handle "invalid request handle"";
       // proxy: "op_free_statement DSQL_drop, handle 0x7FFF never allocated: success"
       static void dropNeverAllocatedHandle() throws Exception {
           print("op_free_statement DSQL_drop, handle 0x7FFF never allocated", freeStatement(NEVER_ALLOCATED_HANDLE, DSQL_DROP));
       }
   
       // Firebird server: "op_info_sql, statement allocated and not prepared: error isc_unprepared_stmt "Attempt to execute an unprepared dynamic SQL statement."";
       // proxy: "op_info_sql, statement allocated and not prepared: success, insert count 0"
       static void infoOfUnpreparedStatement() throws Exception {
           int handle = allocateStatement();
           print("op_info_sql, statement allocated and not prepared", sqlInfo(handle));
           freeStatement(handle, DSQL_DROP);
       }
   
       // Firebird server: "op_info_sql after DSQL_drop: error isc_bad_req_handle "invalid request handle"";
       // proxy: "op_info_sql after DSQL_drop: success, insert count 0"
       static void infoAfterDrop() throws Exception {
           int handle = allocateStatement();
           freeStatement(handle, DSQL_DROP);
           print("op_info_sql after DSQL_drop", sqlInfo(handle));
       }
   
       // Firebird server: "op_info_sql after DSQL_unprepare: error isc_unprepared_stmt "Attempt to execute an unprepared dynamic SQL statement."";
       // proxy: "op_info_sql after DSQL_unprepare: success, insert count 0"
       static void infoAfterUnprepare() throws Exception {
           FbTransaction prepareTransaction = database.startTransaction(tpb());
           FbStatement statement = database.createStatement(prepareTransaction);
           statement.prepare("INSERT INTO T_PLAIN (ID) VALUES (?)");
           freeStatement(statement.getHandle(), DSQL_UNPREPARE);
           print("op_info_sql after DSQL_unprepare", sqlInfo(statement.getHandle()));
           statement.close();
           prepareTransaction.commit();
       }
   
       // Starts the transaction of the next cases right after a statement, so the transaction is the last object of the connection.
       // Firebird server: "op_free_statement DSQL_drop, handle 0xFFFF after op_transaction: error isc_bad_req_handle "invalid request handle"; then DSQL_drop of the statement: success";
       // proxy: "op_free_statement DSQL_drop, handle 0xFFFF after op_transaction: success; then DSQL_drop of the statement: success"
       static void lazyDropAfterTransaction() throws Exception {
           int handle = allocateStatement();
           transaction = database.startTransaction(tpb());
           String lazyDrop = freeStatement(LAZY_HANDLE, DSQL_DROP);
           print("op_free_statement DSQL_drop, handle 0xFFFF after op_transaction", lazyDrop + "; then DSQL_drop of the statement: " + freeStatement(handle, DSQL_DROP));
       }
   
       // The statement of the INSERT is the last object of the connection.
       // Firebird server: "op_info_sql, handle 0xFFFF after INSERT: success, insert count 1";
       // proxy: "op_info_sql, handle 0xFFFF after INSERT: success, insert count 0"
       static void lazyInfoAfterInsert() throws Exception {
           FbStatement statement = database.createStatement(transaction);
           statement.prepare("INSERT INTO T_PLAIN (ID, NAME) VALUES (4101, 'lazy')");
           statement.execute(RowValue.EMPTY_ROW_VALUE);
           print("op_info_sql, handle 0xFFFF after INSERT", sqlInfo(LAZY_HANDLE));
           statement.close();
       }
   
       // Firebird server: "op_free_statement DSQL_drop, handle 0xFFFF after op_allocate_statement: success; then op_info_sql of the statement: error isc_bad_req_handle "invalid request handle"";
       // proxy: "op_free_statement DSQL_drop, handle 0xFFFF after op_allocate_statement: success; then op_info_sql of the statement: success, insert count 0"
       static void lazyDropAfterAllocate() throws Exception {
           int handle = allocateStatement();
           String lazyDrop = freeStatement(LAZY_HANDLE, DSQL_DROP);
           print("op_free_statement DSQL_drop, handle 0xFFFF after op_allocate_statement", lazyDrop + "; then op_info_sql of the statement: " + sqlInfo(handle));
       }
   
       // The errors above do not end the transaction.
       // Firebird server and proxy: "INSERT and COMMIT after the errors: success; rows with ID = 4101 and 4102 on the Firebird server: 2"
       static void commitAfterErrors() throws Exception {
           FbStatement statement = database.createStatement(transaction);
           statement.prepare("INSERT INTO T_PLAIN (ID, NAME) VALUES (4102, 'after')");
           statement.execute(RowValue.EMPTY_ROW_VALUE);
           statement.close();
           transaction.commit();
           print("INSERT and COMMIT after the errors", "success; rows with ID = 4101 and 4102 on the Firebird server: " + countRowsOnServer());
       }
   
       static TransactionParameterBuffer tpb() {
           TransactionParameterBuffer result = database.createTransactionParameterBuffer();
           result.addArgument(TpbItems.isc_tpb_read_committed);
           result.addArgument(TpbItems.isc_tpb_rec_version);
           result.addArgument(TpbItems.isc_tpb_write);
           result.addArgument(TpbItems.isc_tpb_nowait);
           return result;
       }
   
       // op_allocate_statement, then op_batch_sync; returns the new statement handle
       static int allocateStatement() throws Exception {
           send(out -> {
               out.writeInt(62); // op_allocate_statement
               out.writeInt(0); // p_rlse_object
               out.writeInt(110); // op_batch_sync
           });
           int[] handle = new int[1];
           readResponse(handle);
           readResponse(new int[1]);
           return handle[0];
       }
   
       // op_free_statement, then op_batch_sync: on a lazy connection the Firebird server defers the answer to op_free_statement
       static String freeStatement(final int handle, final int option) throws Exception {
           send(out -> {
               out.writeInt(67); // op_free_statement
               out.writeInt(handle); // p_sqlfree_statement
               out.writeInt(option); // p_sqlfree_option
               out.writeInt(110); // op_batch_sync
           });
           String result = readResponse(new int[1]);
           readResponse(new int[1]);
           return result;
       }
   
       // op_info_sql with isc_info_sql_records, then op_batch_sync
       static String sqlInfo(final int handle) throws Exception {
           send(out -> {
               out.writeInt(70); // op_info_sql
               out.writeInt(handle); // p_info_object
               out.writeInt(0); // p_info_incarnation
               out.writeBuffer(new byte[]{23}); // p_info_items: isc_info_sql_records
               out.writeInt(64); // p_info_buffer_length
               out.writeInt(110); // op_batch_sync
           });
           String result = readResponse(new int[1]);
           readResponse(new int[1]);
           return result;
       }
   
       static void send(final TransmitAction action) throws SQLException, IOException {
           try (LockCloseable ignored = database.withLock()) {
               database.completeDeferredActions();
               database.getXdrStreamAccess().withTransmitLock(out -> {
                   action.transmit(out);
                   out.flush();
               });
           }
       }
   
       // Reads one op_response: "success", "success, insert count N" for isc_info_sql_records, or "error <code name> "<message>""
       static String readResponse(final int[] handle) throws SQLException, IOException {
           XdrInputStream in = database.getXdrStreamAccess().getXdrIn();
           int operation;
           do {
               operation = in.readInt();
           } while (71 == operation); // op_dummy
           handle[0] = in.readInt();
           in.readLong(); // blob id
           byte[] data = in.readBuffer();
           String error = readStatusVector(in);
           if (null != error) {
               return error;
           }
           return 0 < data.length && 23 == data[0] ? "success, insert count " + insertCount(data) : "success";
       }
   
       // isc_info_sql_records: item length, then counters (code, length, value); isc_info_req_insert_count is 14
       static long insertCount(final byte[] data) {
           int end = 3 + ((data[1] & 0xFF) | (data[2] & 0xFF) << 8);
           int position = 3;
           while (position < end && 1 != data[position]) {
               int length = (data[position + 1] & 0xFF) | (data[position + 2] & 0xFF) << 8;
               if (14 == data[position]) {
                   long result = 0;
                   for (int i = length - 1; i >= 0; i--) {
                       result = result << 8 | data[position + 3 + i] & 0xFF;
                   }
                   return result;
               }
               position += 3 + length;
           }
           return -1;
       }
   
       // Returns null for a success status vector, otherwise the error code name and the message Jaybird builds for the client
       static String readStatusVector(final XdrInputStream in) throws IOException {
           FbExceptionBuilder builder = new FbExceptionBuilder();
           String name = null;
           for (int argument = in.readInt(); 0 != argument; argument = in.readInt()) {
               switch (argument) {
                   case 1 -> { // isc_arg_gds
                       int code = in.readInt();
                       if (0 != code) {
                           builder.exception(code);
                           name = null == name ? codeName(code) : name;
                       }
                   }
                   case 18 -> builder.warning(in.readInt()); // isc_arg_warning
                   case 4 -> builder.messageParameter(in.readInt()); // isc_arg_number
                   case 19 -> builder.sqlState(new String(in.readBuffer(), StandardCharsets.UTF_8)); // isc_arg_sql_state
                   default -> builder.messageParameter(new String(in.readBuffer(), StandardCharsets.UTF_8));
               }
           }
           if (null == name) {
               return null;
           }
           String message = String.valueOf(builder.toFlatSQLException().getMessage());
           int end = message.indexOf(" [SQLState:");
           return "error " + name + " \"" + (0 > end ? message : message.substring(0, end)) + "\"";
       }
   
       static String codeName(final int code) {
           for (Field each : ISCConstants.class.getFields()) {
               try {
                   if (each.getName().startsWith("isc_") && int.class == each.getType() && code == each.getInt(null)) {
                       return each.getName();
                   }
               } catch (final IllegalAccessException ignored) {
                   // the constants are public
               }
           }
           return String.valueOf(code);
       }
   
       static int countRowsOnServer() throws SQLException {
           try (
                   Connection connection = DriverManager.getConnection(SERVER_URL, SERVER_USER, SERVER_PASSWORD);
                   Statement statement = connection.createStatement();
                   ResultSet resultSet = statement.executeQuery("SELECT COUNT(*) FROM T_PLAIN WHERE ID IN (4101, 4102)")) {
               resultSet.next();
               return resultSet.getInt(1);
           }
       }
   
       static void executeOnServer(final String sql) throws SQLException {
           try (
                   Connection connection = DriverManager.getConnection(SERVER_URL, SERVER_USER, SERVER_PASSWORD);
                   Statement statement = connection.createStatement()) {
               statement.execute(sql);
           }
       }
   
       static void print(final String step, final String result) {
           System.out.println(step + ": " + result);
       }
   }
   ```


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@shardingsphere.apache.org.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


Re: [I] Firebird Proxy does not check the statement handle of op_info_sql and op_free_statement as the Firebird server does [shardingsphere]

Posted by "schizophrenicmaniac (via GitHub)" <gi...@apache.org>.
schizophrenicmaniac commented on issue #40373:
URL: https://github.com/apache/shardingsphere/issues/40373#issuecomment-6449577916

   Hi, I'd like to work on this. I plan to track allocated statement handles, their prepared state and the last object id (for lazy_send 0xFFFF) per connection, so FirebirdSQLInfoExecutor and FirebirdFreeStatementCommandExecutor return isc_bad_req_handle/isc_unprepared_stmt. I can open a PR within 5 days.


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@shardingsphere.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org