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/29 14:11:04 UTC
[I] Firebird Proxy returns an incomplete status vector for an invalid batch message length [shardingsphere]
makssent opened a new issue, #40408:
URL: https://github.com/apache/shardingsphere/issues/40408
### 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 [ab8624a369dac2a9a84762a24e1f078a57ee75e6](https://github.com/apache/shardingsphere/commit/ab8624a369dac2a9a84762a24e1f078a57ee75e6) (daily baseline)
### ShardingSphere project
ShardingSphere-Proxy
### Expected behavior
The proxy should return the same response as the Firebird server for the same batch creation request and preserve the connection when the server does.
### Actual behavior
| Step | Firebird server | Proxy |
|---|---|---|
| Create a batch with message length 4 for a BLR format requiring 6 | Returns [`isc_sqlerr`](https://github.com/FirebirdSQL/firebird/blob/v5.0.4/src/include/firebird/impl/msg/jrd.h#L117) with -804 followed by [`isc_dsql_sqlda_err`](https://github.com/FirebirdSQL/firebird/blob/v5.0.4/src/include/firebird/impl/msg/jrd.h#L264) | Returns only [`isc_dsql_sqlda_err`](https://github.com/FirebirdSQL/firebird/blob/v5.0.4/src/include/firebird/impl/msg/jrd.h#L264) |
| Synchronize the connection | Succeeds | Succeeds |
| Roll back the transaction | Succeeds | Succeeds |
### Reason analysis
The server processes the batch message metadata in [rem_port::batch_create](https://github.com/FirebirdSQL/firebird/blob/v5.0.4/src/remote/server/server.cpp#L3588-L3598). [FirebirdBatchCreateCommandExecutor.execute](https://github.com/apache/shardingsphere/blob/ab8624a369dac2a9a84762a24e1f078a57ee75e6/proxy/frontend/dialect/firebird/src/main/java/org/apache/shardingsphere/proxy/frontend/firebird/command/query/batch/FirebirdBatchCreateCommandExecutor.java#L68-L84) raises [`InvalidBatchMessageFormatException`](https://github.com/apache/shardingsphere/blob/ab8624a369dac2a9a84762a24e1f078a57ee75e6/database/exception/dialect/firebird/src/main/java/org/apache/shardingsphere/database/exception/firebird/exception/protocol/InvalidBatchMessageFormatException.java#L27) for the length mismatch; the response produced on the tested proxy omits the [`isc_sqlerr`](https://github.com/FirebirdSQL/firebird/blob/v5.0.4/src/include/firebird/impl/msg/jrd.h#L117) segment and its numeric argument.
The basic batch creation operation is tracked as implemented in [#39941](https://github.com/apache/shardingsphere/issues/39941); this issue describes the specific behavior above.
### Steps to reproduce
#### Environment
- A Firebird 5 compatible server.
- Client: Jaybird 6.0.7.
#### Steps
1. Create the table:
```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>
username: <user>
password: <password>
rules:
- !SINGLE
tables:
- "*.*"
```
3. Set `SERVER_URL`, `SERVER_USER`, `SERVER_PASSWORD`, `PROXY_URL`, `PROXY_USER`, and `PROXY_PASSWORD` to the JDBC connection values. Run the example with `server` and then `proxy` as its argument and compare the output.
### Example code
```java
import org.firebirdsql.gds.ISCConstants;
import org.firebirdsql.gds.JaybirdErrorCodes;
import org.firebirdsql.gds.TransactionParameterBuffer;
import org.firebirdsql.gds.impl.wire.XdrInputStream;
import org.firebirdsql.gds.impl.wire.XdrOutputStream;
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.ByteArrayOutputStream;
import java.io.EOFException;
import java.io.IOException;
import java.lang.reflect.Field;
import java.net.SocketException;
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;
import java.util.ArrayList;
import java.util.HexFormat;
import java.util.List;
import java.util.Properties;
public class BatchCreateErrorVectors {
static final String SERVER_URL = System.getenv("SERVER_URL");
static final String PROXY_URL = System.getenv("PROXY_URL");
static final String BLR_INT = "05020400020008000700ff4c";
static final String PB = "01020400000001000000";
static boolean closed;
public static void main(String[] args) throws Exception {
java.util.logging.LogManager.getLogManager().reset();
boolean server = 0 < args.length && "server".equals(args[0]);
Properties props = new Properties();
props.setProperty("user", System.getenv(server ? "SERVER_USER" : "PROXY_USER"));
props.setProperty("password", System.getenv(server ? "SERVER_PASSWORD" : "PROXY_PASSWORD"));
props.setProperty("encoding", "UTF8");
props.setProperty("wireCrypt", "DISABLED");
try (Connection connection = DriverManager.getConnection(server ? SERVER_URL : PROXY_URL, props)) {
FbWireDatabase db = (FbWireDatabase) connection.unwrap(FirebirdConnection.class).getFbDatabase();
run(connection, db);
} catch (SQLException | IOException ex) {
System.out.println("connection -> " + error(ex));
}
}
static void run(Connection connection, FbWireDatabase db) throws Exception {
FbTransaction tx = db.startTransaction(tpb(db));
createBatch(db, tx, "message length 4 instead of 6", BLR_INT, 4);
step("ROLLBACK", tx::rollback);
}
static void createBatch(FbWireDatabase db, FbTransaction tx, String label, String blr, int messageLength) throws Exception {
FbStatement statement = db.createStatement(tx);
statement.prepare("INSERT INTO T_PLAIN(ID) VALUES (?)");
int h = statement.getHandle();
send(db, out -> {
batchCreate(out, h, blr, messageLength, PB);
sync(out);
});
System.out.println("op_batch_create (" + label + ") -> " + read(db));
System.out.println("op_batch_sync -> " + read(db));
step("close statement", statement::close);
}
static TransactionParameterBuffer tpb(FbWireDatabase db) {
TransactionParameterBuffer result = db.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;
}
static void execute(Connection connection, String sql) throws SQLException {
try (Statement statement = connection.createStatement()) {
statement.executeUpdate(sql);
}
}
static void send(FbWireDatabase db, TransmitAction action) throws SQLException, IOException {
if (closed) {
throw new EOFException("connection closed");
}
try (LockCloseable ignored = db.withLock()) {
db.completeDeferredActions();
db.getXdrStreamAccess().withTransmitLock(out -> {
action.transmit(out);
out.flush();
});
}
}
interface Action {
void run() throws Exception;
}
static void step(String label, Action action) {
try {
action.run();
System.out.println(label + " -> success");
} catch (Exception ex) {
System.out.println(label + " -> " + error(ex));
}
}
record Response(int op, int object, long blobId, byte[] data, String text) {
@Override
public String toString() {
return text;
}
}
static Response read(FbWireDatabase db) throws SQLException, IOException {
if (closed) {
return new Response(-1, 0, 0, new byte[0], "no response, connection closed");
}
try {
XdrInputStream in = db.getXdrStreamAccess().getXdrIn();
int op;
do {
op = in.readInt();
} while (op == 71);
if (op == 103) {
return new Response(op, 0, 0, new byte[0], batchCompletion(in));
}
if (op != 9) {
return new Response(op, 0, 0, new byte[0], "packet " + op + " instead of op_response");
}
int object = in.readInt();
long blobId = in.readLong();
byte[] data = in.readBuffer();
return new Response(op, object, blobId, data, status(in));
} catch (EOFException | SocketException ex) {
closed = true;
return new Response(-1, 0, 0, new byte[0], "no response, connection closed");
}
}
static String batchCompletion(XdrInputStream in) throws IOException {
in.readInt();
int records = in.readInt();
int updates = in.readInt();
int vectors = in.readInt();
int errors = in.readInt();
List<String> counts = new ArrayList<>();
for (int i = 0; i < updates; i++) {
counts.add(String.valueOf(in.readInt()));
}
StringBuilder result = new StringBuilder("op_batch_cs: records " + records + ", counts " + compact(counts) + ", vectors " + vectors + ", errors without vectors " + errors);
int first = -1;
int last = -1;
String previous = null;
for (int i = 0; i < vectors; i++) {
int record = in.readInt();
String text = status(in);
if (text.equals(previous) && record == last + 1) {
last = record;
continue;
}
appendVectors(result, first, last, previous);
first = record;
last = record;
previous = text;
}
appendVectors(result, first, last, previous);
List<String> plain = new ArrayList<>();
for (int i = 0; i < errors; i++) {
plain.add(String.valueOf(in.readInt()));
}
if (!plain.isEmpty()) {
result.append("; without vector: records ").append(compact(plain));
}
return result.toString();
}
static void appendVectors(StringBuilder result, int first, int last, String text) {
if (text != null) {
result.append(first == last ? "; record " + first : "; records " + first + "-" + last).append(": ").append(text);
}
}
static String compact(List<String> values) {
List<String> result = new ArrayList<>();
for (int i = 0; i < values.size(); ) {
int j = i;
while (j < values.size() && values.get(j).equals(values.get(i))) {
j++;
}
result.add(j - i > 2 ? values.get(i) + " ×" + (j - i) : String.join(", ", values.subList(i, j)));
i = j;
}
return "[" + String.join(", ", result) + "]";
}
static String status(XdrInputStream in) throws IOException {
FbExceptionBuilder builder = new FbExceptionBuilder();
List<String> codes = new ArrayList<>();
List<String> items = new ArrayList<>();
for (int arg = in.readInt(); arg != 0; arg = in.readInt()) {
switch (arg) {
case 1 -> {
int code = in.readInt();
if (code != 0) {
builder.exception(code);
codes.add(code + " " + name(code));
items.add("gds " + code);
}
}
case 18 -> {
int code = in.readInt();
if (code != 0) {
builder.warning(code);
codes.add("warning " + code + " " + name(code));
items.add("warning " + code);
}
}
case 4 -> {
int value = in.readInt();
builder.messageParameter(value);
items.add("number " + value);
}
case 19 -> {
String value = new String(in.readBuffer(), StandardCharsets.UTF_8);
builder.sqlState(value);
items.add("sqlstate " + value);
}
default -> {
String value = new String(in.readBuffer(), StandardCharsets.UTF_8);
builder.messageParameter(value);
items.add("string '" + value.replace("\n", "\\n") + "'");
}
}
}
if (codes.isEmpty()) {
return "success";
}
String message = String.valueOf(builder.toFlatSQLException().getMessage());
int end = message.indexOf(" [SQLState:");
return "error " + String.join(", ", codes) + " «" + (end < 0 ? message : message.substring(0, end)).replace("\n", " ") + "» " + items;
}
static String error(Exception ex) {
if (ex instanceof EOFException || ex instanceof SocketException || ex.getCause() instanceof EOFException || ex.getCause() instanceof SocketException) {
return "connection closed";
}
if (ex instanceof SQLException sql) {
String message = String.valueOf(sql.getMessage()).split("\n")[0];
int end = message.indexOf(" [SQLState:");
String name = name(sql.getErrorCode());
return "error " + sql.getErrorCode() + (name.isEmpty() ? "" : " " + name) + " «" + (end < 0 ? message : message.substring(0, end)) + "»";
}
return "error " + ex;
}
static String name(int code) {
if (code == 0) {
return "";
}
for (Class<?> each : new Class<?>[]{ISCConstants.class, JaybirdErrorCodes.class}) {
for (Field field : each.getFields()) {
try {
if (field.getType() == int.class && field.getInt(null) == code && (field.getName().startsWith("isc_") || field.getName().startsWith("jb_"))) {
return field.getName();
}
} catch (IllegalAccessException ignored) {
}
}
}
return "(unknown name)";
}
static byte[] xdr(Object... values) {
ByteArrayOutputStream result = new ByteArrayOutputStream();
for (Object each : values) {
byte[] bytes;
if (each instanceof Integer value) {
bytes = new byte[]{(byte) (value >>> 24), (byte) (value >>> 16), (byte) (value >>> 8), (byte) (int) value};
} else if (each instanceof Long value) {
bytes = new byte[8];
for (int i = 0; i < 8; i++) {
bytes[i] = (byte) (value >>> (56 - 8 * i));
}
} else if (each instanceof String value) {
byte[] text = value.getBytes(StandardCharsets.UTF_8);
result.writeBytes(xdr(text.length));
bytes = text;
} else {
bytes = (byte[]) each;
}
result.writeBytes(bytes);
result.writeBytes(new byte[(4 - bytes.length % 4) % 4]);
}
return result.toByteArray();
}
static void batchCreate(XdrOutputStream out, int h, String blr, int messageLength, String pb) throws IOException {
out.writeInt(99);
out.writeInt(h);
out.writeBuffer(HexFormat.of().parseHex(blr));
out.writeInt(messageLength);
out.writeBuffer(HexFormat.of().parseHex(pb));
}
static void batchMsg(XdrOutputStream out, int h, int count, byte[] messages) throws IOException {
out.writeInt(100);
out.writeInt(h);
out.writeInt(count);
out.write(messages, 0, messages.length);
}
static void batchExec(XdrOutputStream out, int h, int transaction) throws IOException {
out.writeInt(101);
out.writeInt(h);
out.writeInt(transaction);
}
static void handleOp(XdrOutputStream out, int op, int h) throws IOException {
out.writeInt(op);
out.writeInt(h);
}
static void sync(XdrOutputStream out) throws IOException {
out.writeInt(110);
}
}
```
--
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 returns an incomplete status vector for an invalid batch message length [shardingsphere]
Posted by "rys888 (via GitHub) via notifications" <no...@shardingsphere.apache.org>.
rys888 commented on issue #40408:
URL: https://github.com/apache/shardingsphere/issues/40408#issuecomment-6556838720
I am working on a fix for this issue, with a focused regression test for the missing SQL error segment and numeric -804 argument.
--
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
Re: [I] Firebird Proxy returns an incomplete status vector for an invalid batch message length [shardingsphere]
Posted by "terrymanu (via GitHub) via notifications" <no...@shardingsphere.apache.org>.
terrymanu closed issue #40408: Firebird Proxy returns an incomplete status vector for an invalid batch message length
URL: https://github.com/apache/shardingsphere/issues/40408
--
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