From 963e8d31d9b8435816b28731cea656851b4c3e3e Mon Sep 17 00:00:00 2001 From: Thomas Segismont Date: Mon, 6 Jul 2026 17:59:11 +0200 Subject: [PATCH 1/2] JSON support for MSSQL Closes #1680 Signed-off-by: Thomas Segismont --- .../src/main/asciidoc/index.adoc | 29 +++ .../java/examples/MSSQLClientExamples.java | 40 ++++ .../io/vertx/mssqlclient/impl/MSSQLRow.java | 8 +- .../mssqlclient/impl/codec/DataType.java | 41 +++- .../impl/codec/InitMSSQLCommandMessage.java | 23 ++- .../impl/codec/MSSQLCommandMessage.java | 16 +- .../protocol/client/login/LoginPacket.java | 3 +- .../data/MSSQLJsonDataTypeTest.java | 195 ++++++++++++++++++ .../tests/mssqlclient/junit/MSSQLRule.java | 4 + 9 files changed, 349 insertions(+), 10 deletions(-) create mode 100644 vertx-mssql-client/src/test/java/io/vertx/tests/mssqlclient/data/MSSQLJsonDataTypeTest.java diff --git a/vertx-mssql-client/src/main/asciidoc/index.adoc b/vertx-mssql-client/src/main/asciidoc/index.adoc index 22fff170e..5f4ebd847 100644 --- a/vertx-mssql-client/src/main/asciidoc/index.adoc +++ b/vertx-mssql-client/src/main/asciidoc/index.adoc @@ -250,6 +250,10 @@ Currently the client supports the following SQL Server types: | `GUID` | `java.util.UUID` | + +| `json` +| `io.vertx.core.json.JsonObject` / `io.vertx.core.json.JsonArray` +| Native JSON type (SQL Server 2025+). See <> |=== Tuple decoding uses the above types when storing values. @@ -290,6 +294,31 @@ Otherwise, you should declare the type explicitly using one of the {@link io.ver {@link examples.MSSQLClientExamples#explicitNullHandling} ---- +[[handling-json]] +=== Handling JSON + +SQL Server 2025 introduces a native `json` data type for storing JSON documents. + +IMPORTANT: The native `json` type requires SQL Server 2025 or later and only supports JSON objects and arrays. +Scalars, booleans, and the JSON null literal are not supported. + +The `json` data type is represented by the following Java types: + +- `io.vertx.core.json.JsonObject` +- `io.vertx.core.json.JsonArray` + +[source,$lang] +---- +{@link examples.MSSQLClientExamples#jsonExample(io.vertx.sqlclient.SqlClient)} +---- + +[source,$lang] +---- +{@link examples.MSSQLClientExamples#jsonArrayExample(io.vertx.sqlclient.SqlClient)} +---- + +NOTE: Attempting to insert scalar values, booleans, or {@link io.vertx.sqlclient.Tuple#JSON_NULL} will result in an error. + == Collector queries You can use Java collectors with the query API: diff --git a/vertx-mssql-client/src/main/java/examples/MSSQLClientExamples.java b/vertx-mssql-client/src/main/java/examples/MSSQLClientExamples.java index 9038ad147..c3784f528 100644 --- a/vertx-mssql-client/src/main/java/examples/MSSQLClientExamples.java +++ b/vertx-mssql-client/src/main/java/examples/MSSQLClientExamples.java @@ -12,6 +12,8 @@ package examples; import io.vertx.core.Vertx; +import io.vertx.core.json.JsonArray; +import io.vertx.core.json.JsonObject; import io.vertx.core.net.ClientSSLOptions; import io.vertx.core.net.PemTrustOptions; import io.vertx.docgen.Source; @@ -353,4 +355,42 @@ public void infoHandler(MSSQLConnection connection) { System.out.println("Received info " + info.getSeverity() + "" + info.getMessage()); }); } + + public void jsonExample(SqlClient client) { + JsonObject json = new JsonObject() + .put("name", "Alice") + .put("age", 30); + + client + .preparedQuery("INSERT INTO users (id, data) VALUES (@p1, @p2)") + .execute(Tuple.of(1, json)) + .compose(v -> client + .preparedQuery("SELECT data FROM users WHERE id = @p1") + .execute(Tuple.of(1))) + .onComplete(ar -> { + if (ar.succeeded()) { + Row row = ar.result().iterator().next(); + JsonObject data = row.getJsonObject(0); + System.out.println("Received: " + data.getString("name")); + } + }); + } + + public void jsonArrayExample(SqlClient client) { + JsonArray tags = new JsonArray().add("admin").add("user"); + + client + .preparedQuery("INSERT INTO users (id, data) VALUES (@p1, @p2)") + .execute(Tuple.of(2, tags)) + .compose(v -> client + .preparedQuery("SELECT data FROM users WHERE id = @p1") + .execute(Tuple.of(2))) + .onComplete(ar -> { + if (ar.succeeded()) { + Row row = ar.result().iterator().next(); + JsonArray data = row.getJsonArray(0); + System.out.println("Received: " + data); + } + }); + } } diff --git a/vertx-mssql-client/src/main/java/io/vertx/mssqlclient/impl/MSSQLRow.java b/vertx-mssql-client/src/main/java/io/vertx/mssqlclient/impl/MSSQLRow.java index bcd03e4c2..3bf2b842e 100644 --- a/vertx-mssql-client/src/main/java/io/vertx/mssqlclient/impl/MSSQLRow.java +++ b/vertx-mssql-client/src/main/java/io/vertx/mssqlclient/impl/MSSQLRow.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2011-2021 Contributors to the Eclipse Foundation + * Copyright (c) 2011-2026 Contributors to the Eclipse Foundation * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at @@ -12,6 +12,8 @@ package io.vertx.mssqlclient.impl; import io.vertx.core.buffer.Buffer; +import io.vertx.core.json.JsonArray; +import io.vertx.core.json.JsonObject; import io.vertx.sqlclient.impl.RowBase; import io.vertx.sqlclient.internal.RowDescriptorBase; @@ -59,6 +61,10 @@ public T get(Class type, int position) { return type.cast(getOffsetDateTime(position)); } else if (type == Buffer.class) { return type.cast(getBuffer(position)); + } else if (type == JsonObject.class) { + return type.cast(getJsonObject(position)); + } else if (type == JsonArray.class) { + return type.cast(getJsonArray(position)); } else if (type == UUID.class) { return type.cast(getValue(position)); } else if (type == Object.class) { diff --git a/vertx-mssql-client/src/main/java/io/vertx/mssqlclient/impl/codec/DataType.java b/vertx-mssql-client/src/main/java/io/vertx/mssqlclient/impl/codec/DataType.java index 9c2ff6c33..de0b20cb4 100644 --- a/vertx-mssql-client/src/main/java/io/vertx/mssqlclient/impl/codec/DataType.java +++ b/vertx-mssql-client/src/main/java/io/vertx/mssqlclient/impl/codec/DataType.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2011-2022 Contributors to the Eclipse Foundation + * Copyright (c) 2011-2026 Contributors to the Eclipse Foundation * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at @@ -18,6 +18,9 @@ import io.netty.util.collection.IntObjectMap; import io.vertx.core.buffer.Buffer; import io.vertx.core.internal.buffer.BufferInternal; +import io.vertx.core.json.Json; +import io.vertx.core.json.JsonArray; +import io.vertx.core.json.JsonObject; import java.math.BigDecimal; import java.math.BigInteger; @@ -969,6 +972,40 @@ public Object decodeValue(ByteBuf byteBuf, TypeInfo typeInfo) { return result; } }, + JSON(0xF4) { + @Override + public TypeInfo decodeTypeInfo(ByteBuf byteBuf) { + return new TypeInfo().maxLength(0xFFFF); + } + + @Override + public JDBCType jdbcType(TypeInfo typeInfo) { + return JDBCType.OTHER; + } + + @Override + public Object decodeValue(ByteBuf byteBuf, TypeInfo typeInfo) { + long payloadLength = byteBuf.readLongLE(); + if (isPLPNull(payloadLength)) { + return null; + } + String jsonString = readPLP(byteBuf).toString(StandardCharsets.UTF_16LE); + return Json.decodeValue(jsonString); + } + + @Override + public String paramDefinition(Object value) { + // SQL Server expects text for JSON params + return NVARCHAR.paramDefinition(value); + } + + @Override + public void encodeParam(ByteBuf byteBuf, String name, boolean out, Object value) { + // SQL Server expects text for JSON params + NVARCHAR.encodeParam(byteBuf, name, out, value); + } + }, + SSVARIANT(0x62) { @Override public TypeInfo decodeTypeInfo(ByteBuf byteBuf) { @@ -1108,6 +1145,8 @@ public void encodeParam(ByteBuf byteBuf, String name, boolean out, Object value) typesByValueClass.put(OffsetDateTime.class, DATETIMEOFFSETN); typesByValueClass.put(UUID.class, GUID); typesByValueClass.put(Buffer.class, BIGVARBINARY); + typesByValueClass.put(JsonObject.class, JSON); + typesByValueClass.put(JsonArray.class, JSON); } public static DataType forId(int id) { diff --git a/vertx-mssql-client/src/main/java/io/vertx/mssqlclient/impl/codec/InitMSSQLCommandMessage.java b/vertx-mssql-client/src/main/java/io/vertx/mssqlclient/impl/codec/InitMSSQLCommandMessage.java index e3bee07a9..86ecddc17 100644 --- a/vertx-mssql-client/src/main/java/io/vertx/mssqlclient/impl/codec/InitMSSQLCommandMessage.java +++ b/vertx-mssql-client/src/main/java/io/vertx/mssqlclient/impl/codec/InitMSSQLCommandMessage.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2011-2021 Contributors to the Eclipse Foundation + * Copyright (c) 2011-2026 Contributors to the Eclipse Foundation * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at @@ -56,7 +56,7 @@ void encode() { LoginPacket.OPTION_FLAGS2_ODBC_ON ); // OptionFlags2 content.writeByte(LoginPacket.DEFAULT_TYPE_FLAGS); // TypeFlags - content.writeByte(LoginPacket.DEFAULT_OPTION_FLAGS3); // OptionFlags3 + content.writeByte(LoginPacket.DEFAULT_OPTION_FLAGS3 | LoginPacket.OPTION_FLAGS3_EXTENSION); // OptionFlags3 content.writeZero(8); // ClientTimeZone + ClientLCID /* @@ -98,9 +98,10 @@ void encode() { content.writeZero(2); // offset content.writeShortLE(serverName.length()); - // Unused or Extension - int unusedOffsetLengthIdx = content.writerIndex(); - content.writeZero(4); + // Extension + int extensionOffsetLengthIdx = content.writerIndex(); + content.writeZero(2); // offset + content.writeShortLE(4); // CltIntName CharSequence interfaceLibraryName = properties.get("clientInterfaceName"); @@ -158,7 +159,9 @@ void encode() { content.setShortLE(serverNameOffsetLengthIdx, content.writerIndex() - startIdx); content.writeCharSequence(serverName, UTF_16LE); - content.setShortLE(unusedOffsetLengthIdx, content.writerIndex() - startIdx); + content.setShortLE(extensionOffsetLengthIdx, content.writerIndex() - startIdx); + int featureExtOffsetIdx = content.writerIndex(); + content.writeIntLE(0); content.setShortLE(cltIntNameOffsetLengthIdx, content.writerIndex() - startIdx); content.writeCharSequence(interfaceLibraryName, UTF_16LE); @@ -174,6 +177,14 @@ void encode() { content.setShortLE(changePasswordOffsetLengthIdx, content.writerIndex() - startIdx); + content.setIntLE(featureExtOffsetIdx, content.writerIndex() - startIdx); + // TDS_FEATURE_EXT_JSONSUPPORT + content.writeByte(0x0D); // feature ID + content.writeIntLE(1); // data length + content.writeByte(0x01); // JSON support version 1 + // FEATUREEXT terminator + content.writeByte(0xFF); + // set length content.setIntLE(startIdx, content.writerIndex() - startIdx); diff --git a/vertx-mssql-client/src/main/java/io/vertx/mssqlclient/impl/codec/MSSQLCommandMessage.java b/vertx-mssql-client/src/main/java/io/vertx/mssqlclient/impl/codec/MSSQLCommandMessage.java index 5de708baa..d101d7515 100644 --- a/vertx-mssql-client/src/main/java/io/vertx/mssqlclient/impl/codec/MSSQLCommandMessage.java +++ b/vertx-mssql-client/src/main/java/io/vertx/mssqlclient/impl/codec/MSSQLCommandMessage.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2011-2024 Contributors to the Eclipse Foundation + * Copyright (c) 2011-2026 Contributors to the Eclipse Foundation * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at @@ -73,6 +73,9 @@ void decode(ByteBuf payload) { payload.skipBytes(payload.readUnsignedShortLE()); handleLoginAck(); break; + case FEATUREEXTACK: + handleFeatureExtAck(payload); + break; case COLMETADATA: handleColumnMetadata(payload); break; @@ -129,6 +132,17 @@ protected void handleInfo(ByteBuf payload) { tdsMessageCodec.chctx().fireChannelRead(info); } + private void handleFeatureExtAck(ByteBuf payload) { + while (payload.isReadable()) { + short featureId = payload.readUnsignedByte(); + if (featureId == (0xFF)) { // no more features + break; + } + // We don't keep track of supported features (e.g. JSON) + payload.skipBytes((int) payload.readUnsignedIntLE()); + } + } + protected void handleLoginAck() { } diff --git a/vertx-mssql-client/src/main/java/io/vertx/mssqlclient/impl/protocol/client/login/LoginPacket.java b/vertx-mssql-client/src/main/java/io/vertx/mssqlclient/impl/protocol/client/login/LoginPacket.java index ddc7b32c9..80f76165a 100644 --- a/vertx-mssql-client/src/main/java/io/vertx/mssqlclient/impl/protocol/client/login/LoginPacket.java +++ b/vertx-mssql-client/src/main/java/io/vertx/mssqlclient/impl/protocol/client/login/LoginPacket.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2011-2021 Contributors to the Eclipse Foundation + * Copyright (c) 2011-2026 Contributors to the Eclipse Foundation * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at @@ -90,6 +90,7 @@ public final class LoginPacket { 3FRESERVEDBIT */ public static final byte DEFAULT_OPTION_FLAGS3 = 0x00; + public static final byte OPTION_FLAGS3_EXTENSION = 0x10; private byte typeFlags = 0x00; diff --git a/vertx-mssql-client/src/test/java/io/vertx/tests/mssqlclient/data/MSSQLJsonDataTypeTest.java b/vertx-mssql-client/src/test/java/io/vertx/tests/mssqlclient/data/MSSQLJsonDataTypeTest.java new file mode 100644 index 000000000..a0759e5dd --- /dev/null +++ b/vertx-mssql-client/src/test/java/io/vertx/tests/mssqlclient/data/MSSQLJsonDataTypeTest.java @@ -0,0 +1,195 @@ +/* + * Copyright (c) 2011-2026 Contributors to the Eclipse Foundation + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 + * which is available at https://www.apache.org/licenses/LICENSE-2.0. + * + * SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 + */ +package io.vertx.tests.mssqlclient.data; + +import io.vertx.core.Vertx; +import io.vertx.core.json.JsonArray; +import io.vertx.core.json.JsonObject; +import io.vertx.ext.unit.Async; +import io.vertx.ext.unit.TestContext; +import io.vertx.ext.unit.junit.VertxUnitRunner; +import io.vertx.mssqlclient.MSSQLConnectOptions; +import io.vertx.mssqlclient.MSSQLConnection; +import io.vertx.mssqlclient.MSSQLException; +import io.vertx.sqlclient.Row; +import io.vertx.sqlclient.Tuple; +import io.vertx.tests.mssqlclient.MSSQLTestBase; +import org.junit.After; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; +import org.junit.runner.RunWith; + +import static org.junit.Assume.assumeTrue; + +@RunWith(VertxUnitRunner.class) +public class MSSQLJsonDataTypeTest extends MSSQLTestBase { + + private Vertx vertx; + private MSSQLConnectOptions connectOptions; + + @BeforeClass + public static void beforeClass() { + assumeTrue("JSON type requires SQL Server 2025+", rule.supportsJsonType()); + } + + @Before + public void setUp(TestContext ctx) { + vertx = Vertx.vertx(); + connectOptions = new MSSQLConnectOptions(MSSQLTestBase.options); + Async async = ctx.async(); + MSSQLConnection.connect(vertx, connectOptions).onComplete(ctx.asyncAssertSuccess(conn -> { + conn.query("DROP TABLE IF EXISTS json_test").execute() + .compose(v -> conn.query("CREATE TABLE json_test (id INT PRIMARY KEY, data json)").execute()) + .onComplete(ctx.asyncAssertSuccess(v -> { + conn.close(); + async.complete(); + })); + })); + } + + @After + public void tearDown(TestContext ctx) { + MSSQLConnection.connect(vertx, connectOptions).onComplete(ctx.asyncAssertSuccess(conn -> { + conn.query("DROP TABLE IF EXISTS json_test").execute() + .onComplete(ctx.asyncAssertSuccess(v -> { + conn.close(); + vertx.close().onComplete(ctx.asyncAssertSuccess()); + })); + })); + } + + @Test + public void testDecodeJsonObject(TestContext ctx) { + JsonObject expected = new JsonObject() + .put("name", "Alice") + .put("age", 30); + MSSQLConnection.connect(vertx, connectOptions).onComplete(ctx.asyncAssertSuccess(conn -> { + conn.preparedQuery("INSERT INTO json_test (id, data) VALUES (@p1, @p2)") + .execute(Tuple.of(1, expected)) + .compose(v -> conn.preparedQuery("SELECT data FROM json_test WHERE id = @p1").execute(Tuple.of(1))) + .onComplete(ctx.asyncAssertSuccess(rows -> { + ctx.assertEquals(1, rows.size()); + Row row = rows.iterator().next(); + ctx.assertEquals(expected, row.getJson(0)); + ctx.assertEquals(expected, row.getJsonObject(0)); + conn.close(); + })); + })); + } + + @Test + public void testDecodeJsonArray(TestContext ctx) { + JsonArray expected = new JsonArray().add(1).add(2).add(3); + MSSQLConnection.connect(vertx, connectOptions).onComplete(ctx.asyncAssertSuccess(conn -> { + conn.preparedQuery("INSERT INTO json_test (id, data) VALUES (@p1, @p2)") + .execute(Tuple.of(2, expected)) + .compose(v -> conn.preparedQuery("SELECT data FROM json_test WHERE id = @p1").execute(Tuple.of(2))) + .onComplete(ctx.asyncAssertSuccess(rows -> { + ctx.assertEquals(1, rows.size()); + Row row = rows.iterator().next(); + ctx.assertEquals(expected, row.getJson(0)); + ctx.assertEquals(expected, row.getJsonArray(0)); + conn.close(); + })); + })); + } + + @Test + public void testDecodeSqlNull(TestContext ctx) { + MSSQLConnection.connect(vertx, connectOptions).onComplete(ctx.asyncAssertSuccess(conn -> { + conn.preparedQuery("INSERT INTO json_test (id, data) VALUES (@p1, @p2)") + .execute(Tuple.of(3, (Object) null)) + .compose(v -> conn.preparedQuery("SELECT data FROM json_test WHERE id = @p1").execute(Tuple.of(3))) + .onComplete(ctx.asyncAssertSuccess(rows -> { + ctx.assertEquals(1, rows.size()); + Row row = rows.iterator().next(); + ctx.assertNull(row.getJson(0)); + ctx.assertNull(row.getJsonObject(0)); + ctx.assertNull(row.getJsonArray(0)); + conn.close(); + })); + })); + } + + @Test + public void testDecodeNestedJsonObject(TestContext ctx) { + JsonObject expected = new JsonObject() + .put("address", new JsonObject() + .put("city", "New York") + .put("zip", "10001")) + .put("tags", new JsonArray().add("admin").add("user")); + MSSQLConnection.connect(vertx, connectOptions).onComplete(ctx.asyncAssertSuccess(conn -> { + conn.preparedQuery("INSERT INTO json_test (id, data) VALUES (@p1, @p2)") + .execute(Tuple.of(4, expected)) + .compose(v -> conn.preparedQuery("SELECT data FROM json_test WHERE id = @p1").execute(Tuple.of(4))) + .onComplete(ctx.asyncAssertSuccess(rows -> { + ctx.assertEquals(1, rows.size()); + Row row = rows.iterator().next(); + ctx.assertEquals(expected, row.getJsonObject(0)); + conn.close(); + })); + })); + } + + @Test + public void testScalarRejection(TestContext ctx) { + MSSQLConnection.connect(vertx, connectOptions).onComplete(ctx.asyncAssertSuccess(conn -> { + conn.preparedQuery("INSERT INTO json_test (id, data) VALUES (@p1, @p2)") + .execute(Tuple.of(5, 42)) + .onComplete(ctx.asyncAssertFailure(err -> { + conn.close(); + if (err instanceof MSSQLException) { + MSSQLException exception = (MSSQLException) err; + ctx.assertEquals("S0002", exception.getSqlState()); + ctx.assertEquals(206, exception.getErrorCode()); + } else { + ctx.fail(err); + } + })); + })); + } + + @Test + public void testDecodeUnicode(TestContext ctx) { + JsonObject expected = new JsonObject() + .put("emoji", "😀") + .put("japanese", "フレームワーク") + .put("arabic", "مرحبا"); + MSSQLConnection.connect(vertx, connectOptions).onComplete(ctx.asyncAssertSuccess(conn -> { + conn.preparedQuery("INSERT INTO json_test (id, data) VALUES (@p1, @p2)") + .execute(Tuple.of(7, expected)) + .compose(v -> conn.preparedQuery("SELECT data FROM json_test WHERE id = @p1").execute(Tuple.of(7))) + .onComplete(ctx.asyncAssertSuccess(rows -> { + ctx.assertEquals(1, rows.size()); + Row row = rows.iterator().next(); + ctx.assertEquals(expected, row.getJsonObject(0)); + conn.close(); + })); + })); + } + + @Test + public void testSimpleQueryDecodeJsonObject(TestContext ctx) { + JsonObject expected = new JsonObject().put("key", "value"); + MSSQLConnection.connect(vertx, connectOptions).onComplete(ctx.asyncAssertSuccess(conn -> { + conn.preparedQuery("INSERT INTO json_test (id, data) VALUES (@p1, @p2)") + .execute(Tuple.of(6, expected)) + .compose(v -> conn.query("SELECT data FROM json_test WHERE id = 6").execute()) + .onComplete(ctx.asyncAssertSuccess(rows -> { + ctx.assertEquals(1, rows.size()); + Row row = rows.iterator().next(); + ctx.assertEquals(expected, row.getJsonObject(0)); + conn.close(); + })); + })); + } +} diff --git a/vertx-mssql-client/src/test/java/io/vertx/tests/mssqlclient/junit/MSSQLRule.java b/vertx-mssql-client/src/test/java/io/vertx/tests/mssqlclient/junit/MSSQLRule.java index 38d0143f9..fb08fd393 100644 --- a/vertx-mssql-client/src/test/java/io/vertx/tests/mssqlclient/junit/MSSQLRule.java +++ b/vertx-mssql-client/src/test/java/io/vertx/tests/mssqlclient/junit/MSSQLRule.java @@ -242,6 +242,10 @@ public MSSQLConnectOptions options() { return new MSSQLConnectOptions(options); } + public boolean supportsJsonType() { + return serverVersion == null || serverVersion == ServerVersion.MSSQL_2025; + } + private static class ServerContainer> extends GenericContainer { public ServerContainer(String dockerImageName) { From b391db972165afb0b14e35a1d9a136e689dabfcc Mon Sep 17 00:00:00 2001 From: Thomas Segismont Date: Thu, 16 Jul 2026 16:14:30 +0200 Subject: [PATCH 2/2] Avoid redundant JSON to string conversion When creating the param definition, the JSON payload was converted to string. And then converted again when encoding the param. In fact, MSSQL allows to send JSON as NVARCHAR(MAX) regardless of the size. And then we can have a single conversion when encoding the param. Signed-off-by: Thomas Segismont --- .../mssqlclient/impl/codec/DataType.java | 23 +++++++++++-------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/vertx-mssql-client/src/main/java/io/vertx/mssqlclient/impl/codec/DataType.java b/vertx-mssql-client/src/main/java/io/vertx/mssqlclient/impl/codec/DataType.java index de0b20cb4..c4bc90a0e 100644 --- a/vertx-mssql-client/src/main/java/io/vertx/mssqlclient/impl/codec/DataType.java +++ b/vertx-mssql-client/src/main/java/io/vertx/mssqlclient/impl/codec/DataType.java @@ -873,11 +873,6 @@ public void encodeParam(ByteBuf byteBuf, String name, boolean out, Object value) byteBuf.writeCharSequence(val, StandardCharsets.UTF_16LE); } } - - private void writeCollation(ByteBuf byteBuf) { - byteBuf.writeInt(0x0904d000); - byteBuf.writeByte(0x34); - } }, NCHAR(0xEF) { @Override @@ -995,14 +990,19 @@ public Object decodeValue(ByteBuf byteBuf, TypeInfo typeInfo) { @Override public String paramDefinition(Object value) { - // SQL Server expects text for JSON params - return NVARCHAR.paramDefinition(value); + return "nvarchar(max)"; } @Override public void encodeParam(ByteBuf byteBuf, String name, boolean out, Object value) { - // SQL Server expects text for JSON params - NVARCHAR.encodeParam(byteBuf, name, out, value); + writeParamDescription(byteBuf, name, out, NVARCHAR.id); + String val = value.toString(); + byteBuf.writeShortLE(0xFFFF); + writeCollation(byteBuf); + byteBuf.writeLongLE(val.length() * 2L); + byteBuf.writeIntLE(val.length() * 2); + byteBuf.writeCharSequence(val, StandardCharsets.UTF_16LE); + byteBuf.writeIntLE(0); } }, @@ -1212,4 +1212,9 @@ private static int daysFromStartDate(LocalDate localDate) { private static long hundredsOfNanos(LocalTime localTime) { return localTime.toNanoOfDay() / 100; } + + private static void writeCollation(ByteBuf byteBuf) { + byteBuf.writeInt(0x0904d000); + byteBuf.writeByte(0x34); + } }