From 6c2a36c649ae3b292744ec643600187587610b91 Mon Sep 17 00:00:00 2001 From: Thomas Segismont Date: Tue, 2 Dec 2025 12:53:28 +0100 Subject: [PATCH] Sql Server Client may report an IndexOutOfBoundsException when using cursor (#1583) See #1582 When using cursors, and the number of columns in the query result is a multiple of 8, and some of them have the NULL value, the client may report an IndexOutOfBoundsException. This happens because with cursors, there is an extra ROWSTAT column on the wire that wasn't accounted for by the NBCROW decoder. Signed-off-by: Thomas Segismont --- .../impl/codec/RowResultDecoder.java | 2 +- .../tck/MSSQLPreparedQueryTestBase.java | 23 +++++++++++++++++++ .../src/test/resources/init.sql | 21 +++++++++++++++++ 3 files changed, 45 insertions(+), 1 deletion(-) diff --git a/vertx-mssql-client/src/main/java/io/vertx/mssqlclient/impl/codec/RowResultDecoder.java b/vertx-mssql-client/src/main/java/io/vertx/mssqlclient/impl/codec/RowResultDecoder.java index 6673433d3..0a22173e0 100644 --- a/vertx-mssql-client/src/main/java/io/vertx/mssqlclient/impl/codec/RowResultDecoder.java +++ b/vertx-mssql-client/src/main/java/io/vertx/mssqlclient/impl/codec/RowResultDecoder.java @@ -68,7 +68,7 @@ private boolean ifNotMissing(ByteBuf in, Row row) { private boolean decodeMssqlNbcRow(ByteBuf in, Row row) { int len = desc.size(); - int nullBitmapByteCount = ((len - 1) >> 3) + 1; + int nullBitmapByteCount = (len - (desc.hasRowStat() ? 0 : 1) >> 3) + 1; int nullBitMapStartIdx = in.readerIndex(); in.skipBytes(nullBitmapByteCount); diff --git a/vertx-mssql-client/src/test/java/io/vertx/tests/mssqlclient/tck/MSSQLPreparedQueryTestBase.java b/vertx-mssql-client/src/test/java/io/vertx/tests/mssqlclient/tck/MSSQLPreparedQueryTestBase.java index eb7a6b8de..a54c454f1 100644 --- a/vertx-mssql-client/src/test/java/io/vertx/tests/mssqlclient/tck/MSSQLPreparedQueryTestBase.java +++ b/vertx-mssql-client/src/test/java/io/vertx/tests/mssqlclient/tck/MSSQLPreparedQueryTestBase.java @@ -101,5 +101,28 @@ public void failureWhenPreparingCursor(TestContext ctx) { })); })); } + + @Test + public void testNbcRowWithCursor(TestContext ctx) { + Async async = ctx.async(); + connect(ctx.asyncAssertSuccess(conn -> { + conn.prepare("SELECT * FROM nbcrow_with_rowstat").onComplete(ctx.asyncAssertSuccess(ps -> { + ps.createStream(50) + .exceptionHandler(ctx::fail) + .handler(row -> { + // Make sure NbcRow handling is correct when the number of columns is a multiple of 8 + ctx.assertEquals(0, row.size() % 8); + for (int i = 1; i <= 8; i++) { + if (i % 2 != 0) { + ctx.assertEquals(String.valueOf(i), row.getString(i - 1)); + } else { + ctx.assertNull(row.getString(i - 1)); + } + } + }) + .endHandler(v -> async.complete()); + })); + })); + } } diff --git a/vertx-mssql-client/src/test/resources/init.sql b/vertx-mssql-client/src/test/resources/init.sql index 89ae41bc4..ce25d5f79 100644 --- a/vertx-mssql-client/src/test/resources/init.sql +++ b/vertx-mssql-client/src/test/resources/init.sql @@ -310,3 +310,24 @@ VALUES (2, 32767, 2147483647, 9223372036854775807, 123.456, 1.234567, 'hello,wor GO -- TCK usage -- + +-- Table for testing NBCROW with a cursor +DROP TABLE IF EXISTS nbcrow_with_rowstat; +CREATE TABLE nbcrow_with_rowstat +( + test_varchar_1 VARCHAR(20), + test_varchar_2 VARCHAR(20), + test_varchar_3 VARCHAR(20), + test_varchar_4 VARCHAR(20), + test_varchar_5 VARCHAR(20), + test_varchar_6 VARCHAR(20), + test_varchar_7 VARCHAR(20), + test_varchar_8 VARCHAR(20), +); + +INSERT INTO nbcrow_with_rowstat +VALUES ('1', NULL, '3', NULL, '5', NULL, '7', NULL); + +GO + +-- Table for testing NBCROW with a cursor