From 8749e4dbb46204cb61d2b6a989e97fcfd945ad2d Mon Sep 17 00:00:00 2001 From: Thomas Segismont Date: Wed, 9 Jun 2021 16:24:24 +0200 Subject: [PATCH 01/26] MS SQL Connection Options: same as JDBC Fixes #963 In particular, ODBC_ON implies ANSI_DEFAULTS which activates the following ISO behaviors: - ANSI_NULLS - CURSOR_CLOSE_ON_COMMIT - ANSI_NULL_DFLT_ON - IMPLICIT_TRANSACTIONS - ANSI_PADDING - QUOTED_IDENTIFIER - ANSI_WARNINGS Signed-off-by: Thomas Segismont --- .../mssqlclient/impl/codec/InitCommandCodec.java | 14 +++++++++++--- .../impl/protocol/client/login/LoginPacket.java | 2 +- .../io/vertx/mssqlclient/MSSQLQueriesTest.java | 13 +++++++++++++ 3 files changed, 25 insertions(+), 4 deletions(-) diff --git a/vertx-mssql-client/src/main/java/io/vertx/mssqlclient/impl/codec/InitCommandCodec.java b/vertx-mssql-client/src/main/java/io/vertx/mssqlclient/impl/codec/InitCommandCodec.java index b862195d4..1f24603bc 100644 --- a/vertx-mssql-client/src/main/java/io/vertx/mssqlclient/impl/codec/InitCommandCodec.java +++ b/vertx-mssql-client/src/main/java/io/vertx/mssqlclient/impl/codec/InitCommandCodec.java @@ -86,9 +86,17 @@ private void sendLoginMessage() { packet.writeIntLE(0x00); // ClientProgVer packet.writeIntLE(0x00); // ClientPID packet.writeIntLE(0x00); // ConnectionID - packet.writeByte(LoginPacket.DEFAULT_OPTION_FLAGS1 - | LoginPacket.OPTION_FLAGS1_DUMPLOAD_OFF); // OptionFlags1 - packet.writeByte(LoginPacket.DEFAULT_OPTION_FLAGS2); // OptionFlags2 + packet.writeByte(LoginPacket.DEFAULT_OPTION_FLAGS1 | + LoginPacket.OPTION_FLAGS1_ORDER_X86 | + LoginPacket.OPTION_FLAGS1_CHARSET_ASCII | + LoginPacket.OPTION_FLAGS1_FLOAT_IEEE_754 | + LoginPacket.OPTION_FLAGS1_USE_DB_OFF | + LoginPacket.OPTION_FLAGS1_INIT_DB_FATAL | + LoginPacket.OPTION_FLAGS1_SET_LANG_ON + ); // OptionFlags1 + packet.writeByte(LoginPacket.DEFAULT_OPTION_FLAGS2 | + LoginPacket.OPTION_FLAGS2_ODBC_ON + ); // OptionFlags2 packet.writeByte(LoginPacket.DEFAULT_TYPE_FLAGS); // TypeFlags packet.writeByte(LoginPacket.DEFAULT_OPTION_FLAGS3); // OptionFlags3 packet.writeIntLE(0x00); // ClientTimeZone 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 b0eef9b3a..3f7b5d6cd 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 @@ -37,7 +37,7 @@ public final class LoginPacket { public static final byte OPTION_FLAGS1_ORDER_X68000 = 0x01; public static final byte OPTION_FLAGS1_CHARSET_ASCII = 0x00; public static final byte OPTION_FLAGS1_CHARSET_EBCDIC = 0x02; - public static final byte OPTION_FALGS1_FLOAT_IEEE_754 = 0x00; + public static final byte OPTION_FLAGS1_FLOAT_IEEE_754 = 0x00; public static final byte OPTION_FALGS1_FLOAT_VAX = 0x04; public static final byte OPTION_FALGS1_ND5000 = 0x08; public static final byte OPTION_FLAGS1_DUMPLOAD_ON = 0x00; diff --git a/vertx-mssql-client/src/test/java/io/vertx/mssqlclient/MSSQLQueriesTest.java b/vertx-mssql-client/src/test/java/io/vertx/mssqlclient/MSSQLQueriesTest.java index 54ed353e2..823bb3486 100644 --- a/vertx-mssql-client/src/test/java/io/vertx/mssqlclient/MSSQLQueriesTest.java +++ b/vertx-mssql-client/src/test/java/io/vertx/mssqlclient/MSSQLQueriesTest.java @@ -17,6 +17,7 @@ import io.vertx.ext.unit.junit.RepeatRule; import io.vertx.ext.unit.junit.VertxUnitRunner; import io.vertx.sqlclient.Tuple; +import io.vertx.sqlclient.data.NullValue; import org.junit.After; import org.junit.Before; import org.junit.Rule; @@ -74,4 +75,16 @@ public void testQueryCurrentTimestamp(TestContext ctx) { ctx.assertTrue(Math.abs(localDateTime.until(start, ChronoUnit.SECONDS)) < 1); })); } + + @Test + public void testCreateTable(TestContext ctx) { + connnection.query("drop table if exists Basic") + .execute(ctx.asyncAssertSuccess(drop -> { + connnection.preparedQuery("create table Basic (id int, dessimal numeric(19,2), primary key (id))") + .execute(ctx.asyncAssertSuccess(create -> { + connnection.preparedQuery("INSERT INTO Basic (id, dessimal) values (3, @p1)") + .execute(Tuple.of(NullValue.BigDecimal), ctx.asyncAssertSuccess()); + })); + })); + } } From 785f090aac0252e38ff27a143845d49227b6ecf9 Mon Sep 17 00:00:00 2001 From: Thomas Segismont Date: Wed, 9 Jun 2021 17:59:39 +0200 Subject: [PATCH 02/26] MSSQL: Document identity column retrieval (#976) Signed-off-by: Thomas Segismont --- .../src/main/asciidoc/index.adoc | 9 ++++++ .../java/examples/MSSQLClientExamples.java | 11 +++++++ .../vertx/mssqlclient/MSSQLQueriesTest.java | 32 +++++++++++++------ .../src/test/resources/init.sql | 8 +++++ 4 files changed, 50 insertions(+), 10 deletions(-) diff --git a/vertx-mssql-client/src/main/asciidoc/index.adoc b/vertx-mssql-client/src/main/asciidoc/index.adoc index da9844872..02074ef4f 100644 --- a/vertx-mssql-client/src/main/asciidoc/index.adoc +++ b/vertx-mssql-client/src/main/asciidoc/index.adoc @@ -132,6 +132,15 @@ You can configure the client to retry when a connection fails to be established. include::queries.adoc[leveloffset=1] +== Working with `identity` columns + +You can retrieve the value of an `identity` column after inserting new data using the `OUTPUT` clause: + +[source,$lang] +---- +{@link examples.MSSQLClientExamples#identityColumn} +---- + include::connections.adoc[] include::transactions.adoc[] diff --git a/vertx-mssql-client/src/main/java/examples/MSSQLClientExamples.java b/vertx-mssql-client/src/main/java/examples/MSSQLClientExamples.java index 97fddbe53..91c98bc55 100644 --- a/vertx-mssql-client/src/main/java/examples/MSSQLClientExamples.java +++ b/vertx-mssql-client/src/main/java/examples/MSSQLClientExamples.java @@ -277,4 +277,15 @@ public void explicitNullHandling(SqlClient client) { // ... }); } + + public void identityColumn(SqlClient client) { + client + .preparedQuery("INSERT INTO movies (title) OUTPUT INSERTED.id VALUES (@p1)") + .execute(Tuple.of("The Man Who Knew Too Much"), res -> { + if (res.succeeded()) { + Row row = res.result().iterator().next(); + System.out.println(row.getLong("id")); + } + }); + } } diff --git a/vertx-mssql-client/src/test/java/io/vertx/mssqlclient/MSSQLQueriesTest.java b/vertx-mssql-client/src/test/java/io/vertx/mssqlclient/MSSQLQueriesTest.java index 823bb3486..50d0ac136 100644 --- a/vertx-mssql-client/src/test/java/io/vertx/mssqlclient/MSSQLQueriesTest.java +++ b/vertx-mssql-client/src/test/java/io/vertx/mssqlclient/MSSQLQueriesTest.java @@ -16,6 +16,7 @@ import io.vertx.ext.unit.junit.Repeat; import io.vertx.ext.unit.junit.RepeatRule; import io.vertx.ext.unit.junit.VertxUnitRunner; +import io.vertx.sqlclient.Row; import io.vertx.sqlclient.Tuple; import io.vertx.sqlclient.data.NullValue; import org.junit.After; @@ -34,32 +35,32 @@ public class MSSQLQueriesTest extends MSSQLTestBase { public RepeatRule rule = new RepeatRule(); Vertx vertx; - MSSQLConnection connnection; + MSSQLConnection connection; @Before public void setup(TestContext ctx) { vertx = Vertx.vertx(); options = new MSSQLConnectOptions(MSSQLTestBase.options); - MSSQLConnection.connect(vertx, options, ctx.asyncAssertSuccess(conn -> this.connnection = conn)); + MSSQLConnection.connect(vertx, options, ctx.asyncAssertSuccess(conn -> this.connection = conn)); } @After public void tearDown(TestContext ctx) { - if (connnection != null) { - connnection.close(ctx.asyncAssertSuccess()); + if (connection != null) { + connection.close(ctx.asyncAssertSuccess()); } vertx.close(ctx.asyncAssertSuccess()); } @Test public void testSimpleQueryOrderBy(TestContext ctx) { - connnection.query("SELECT message FROM immutable ORDER BY message DESC") + connection.query("SELECT message FROM immutable ORDER BY message DESC") .execute(ctx.asyncAssertSuccess(rs -> ctx.assertTrue(rs.size() > 1))); } @Test public void testPreparedQueryOrderBy(TestContext ctx) { - connnection.preparedQuery("SELECT message FROM immutable WHERE id BETWEEN @p1 AND @p2 ORDER BY message DESC") + connection.preparedQuery("SELECT message FROM immutable WHERE id BETWEEN @p1 AND @p2 ORDER BY message DESC") .execute(Tuple.of(4, 9), ctx.asyncAssertSuccess(rs -> ctx.assertEquals(6, rs.size()))); } @@ -67,7 +68,7 @@ public void testPreparedQueryOrderBy(TestContext ctx) { @Repeat(50) public void testQueryCurrentTimestamp(TestContext ctx) { LocalDateTime start = LocalDateTime.now(); - connnection.query("SELECT current_timestamp") + connection.query("SELECT current_timestamp") .execute(ctx.asyncAssertSuccess(rs -> { Object value = rs.iterator().next().getValue(0); ctx.assertTrue(value instanceof LocalDateTime); @@ -78,13 +79,24 @@ public void testQueryCurrentTimestamp(TestContext ctx) { @Test public void testCreateTable(TestContext ctx) { - connnection.query("drop table if exists Basic") + connection.query("drop table if exists Basic") .execute(ctx.asyncAssertSuccess(drop -> { - connnection.preparedQuery("create table Basic (id int, dessimal numeric(19,2), primary key (id))") + connection.preparedQuery("create table Basic (id int, dessimal numeric(19,2), primary key (id))") .execute(ctx.asyncAssertSuccess(create -> { - connnection.preparedQuery("INSERT INTO Basic (id, dessimal) values (3, @p1)") + connection.preparedQuery("INSERT INTO Basic (id, dessimal) values (3, @p1)") .execute(Tuple.of(NullValue.BigDecimal), ctx.asyncAssertSuccess()); })); })); } + + + @Test + public void testInsertReturning(TestContext ctx) { + connection.preparedQuery("insert into EntityWithIdentity (name) OUTPUT INSERTED.id, INSERTED.name VALUES (@p1)") + .execute(Tuple.of("John"), ctx.asyncAssertSuccess(result -> { + Row row = result.iterator().next(); + ctx.assertNotNull(row.getInteger("id")); + ctx.assertEquals("John", row.getString("name")); + })); + } } diff --git a/vertx-mssql-client/src/test/resources/init.sql b/vertx-mssql-client/src/test/resources/init.sql index 0e955ef39..263155d9e 100644 --- a/vertx-mssql-client/src/test/resources/init.sql +++ b/vertx-mssql-client/src/test/resources/init.sql @@ -188,3 +188,11 @@ INSERT INTO Fortune (id, message) VALUES (11, ''); INSERT INTO Fortune (id, message) VALUES (12, 'フレームワークのベンチマーク'); + +-- Table for testing OUTPUT +CREATE TABLE EntityWithIdentity +( + id bigint identity NOT NULL, + name varchar(255) + primary key (id) +); From f6547ae0056b7d5f2429b2266b4775a2bdf94f08 Mon Sep 17 00:00:00 2001 From: Davide D'Alto Date: Sun, 14 Feb 2021 17:25:36 +0000 Subject: [PATCH 03/26] Add 'generated' folders to .gitignore Signed-off-by: Davide D'Alto --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index c4297cc5d..3af989888 100644 --- a/.gitignore +++ b/.gitignore @@ -32,3 +32,4 @@ ScratchPad.java src/main/resources/ext-js/*.js src/main/java/io/vertx/java/**/*.java *.swp +generated/ From 14be7a37afb0bbccade9807ba9a4f7b78d4dea09 Mon Sep 17 00:00:00 2001 From: Davide D'Alto Date: Sun, 14 Feb 2021 17:28:48 +0000 Subject: [PATCH 04/26] Add bin folders to .gitignore Signed-off-by: Davide D'Alto --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 3af989888..f91e1ec34 100644 --- a/.gitignore +++ b/.gitignore @@ -33,3 +33,4 @@ src/main/resources/ext-js/*.js src/main/java/io/vertx/java/**/*.java *.swp generated/ +bin/ From 4e40cdba4c12a050e8d52482e6930fa9c8e40b2d Mon Sep 17 00:00:00 2001 From: Davide D'Alto Date: Sun, 14 Feb 2021 17:16:25 +0000 Subject: [PATCH 05/26] [#887] Add testcase for select with fetch first Signed-off-by: Davide D'Alto --- .../io/vertx/db2client/QueryVariationsTest.java | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/vertx-db2-client/src/test/java/io/vertx/db2client/QueryVariationsTest.java b/vertx-db2-client/src/test/java/io/vertx/db2client/QueryVariationsTest.java index 726310cb1..3265e0050 100644 --- a/vertx-db2-client/src/test/java/io/vertx/db2client/QueryVariationsTest.java +++ b/vertx-db2-client/src/test/java/io/vertx/db2client/QueryVariationsTest.java @@ -22,6 +22,23 @@ @RunWith(VertxUnitRunner.class) public class QueryVariationsTest extends DB2TestBase { + @Test + public void testFetchFirst(TestContext ctx) { + connect(ctx.asyncAssertSuccess(conn -> { + conn.query("select message from immutable order by id fetch first 1 rows only").execute( + ctx.asyncAssertSuccess(rowSet -> { + ctx.assertEquals(1, rowSet.size()); + ctx.assertEquals(Arrays.asList("message"), rowSet.columnsNames()); + RowIterator rows = rowSet.iterator(); + ctx.assertTrue(rows.hasNext()); + Row row = rows.next(); + ctx.assertEquals("fortune: No such file or directory", row.getString(0)); + ctx.assertFalse(rows.hasNext()); + conn.close(); + })); + })); + } + @Test public void testRenamedColumns(TestContext ctx) { connect(ctx.asyncAssertSuccess(conn -> { From 607206242df9a773e846a45bd33f7c31b660ec8a Mon Sep 17 00:00:00 2001 From: Mark Swatosh Date: Tue, 1 Jun 2021 14:00:16 -0500 Subject: [PATCH 06/26] Adding and handling QRYBLKFCT codepoint (0x215F) Signed-off-by: Mark Swatosh --- .../io/vertx/db2client/impl/drda/CodePoint.java | 5 +++++ .../db2client/impl/drda/DRDAQueryResponse.java | 15 +++++++++++++++ .../io/vertx/db2client/QueryVariationsTest.java | 2 +- 3 files changed, 21 insertions(+), 1 deletion(-) diff --git a/vertx-db2-client/src/main/java/io/vertx/db2client/impl/drda/CodePoint.java b/vertx-db2-client/src/main/java/io/vertx/db2client/impl/drda/CodePoint.java index 5aef36885..9f3b6edcc 100644 --- a/vertx-db2-client/src/main/java/io/vertx/db2client/impl/drda/CodePoint.java +++ b/vertx-db2-client/src/main/java/io/vertx/db2client/impl/drda/CodePoint.java @@ -428,6 +428,11 @@ public class CodePoint { */ public static final int QRYCLSIMP = 0x215D; + /** + * Query Blocking Factor (0x215F is also recognized as QRYOPTVAL) + */ + public static final int QRYBLKFCT = 0x215F; + /** * Query Scroll Orientation. */ diff --git a/vertx-db2-client/src/main/java/io/vertx/db2client/impl/drda/DRDAQueryResponse.java b/vertx-db2-client/src/main/java/io/vertx/db2client/impl/drda/DRDAQueryResponse.java index b09528246..5e0f15d2e 100644 --- a/vertx-db2-client/src/main/java/io/vertx/db2client/impl/drda/DRDAQueryResponse.java +++ b/vertx-db2-client/src/main/java/io/vertx/db2client/impl/drda/DRDAQueryResponse.java @@ -2217,6 +2217,15 @@ private void parseOPNQRYRM(boolean isOPNQRYreply) { ddmLength = adjustDdmLength(ddmLength, length); peekCP = peekCodePoint(); } + + if (peekCP == CodePoint.QRYBLKFCT) { + // @MJS added + foundInPass = true; + length = peekedLength_; + parseFastQRYBLKFCT(); + ddmLength = adjustDdmLength(ddmLength, length); + peekCP = peekCodePoint(); + } if (!foundInPass) { @@ -2328,6 +2337,12 @@ private int parseFastQRYATTISOL() { matchCodePoint(CodePoint.QRYATTISOL); return readUnsignedShort(); } + + private int parseFastQRYBLKFCT() { + //@MJS added + matchCodePoint(CodePoint.QRYBLKFCT); + return readFastInt(); + } private int parseFastQRYATTSNS() { matchCodePoint(CodePoint.QRYATTSNS); diff --git a/vertx-db2-client/src/test/java/io/vertx/db2client/QueryVariationsTest.java b/vertx-db2-client/src/test/java/io/vertx/db2client/QueryVariationsTest.java index 3265e0050..f631d3996 100644 --- a/vertx-db2-client/src/test/java/io/vertx/db2client/QueryVariationsTest.java +++ b/vertx-db2-client/src/test/java/io/vertx/db2client/QueryVariationsTest.java @@ -28,7 +28,7 @@ public void testFetchFirst(TestContext ctx) { conn.query("select message from immutable order by id fetch first 1 rows only").execute( ctx.asyncAssertSuccess(rowSet -> { ctx.assertEquals(1, rowSet.size()); - ctx.assertEquals(Arrays.asList("message"), rowSet.columnsNames()); + ctx.assertEquals(Arrays.asList("MESSAGE"), rowSet.columnsNames()); RowIterator rows = rowSet.iterator(); ctx.assertTrue(rows.hasNext()); Row row = rows.next(); From f0037a2dffb69e27a0f3f4fe2a4aaa38b0d84f7e Mon Sep 17 00:00:00 2001 From: Thomas Segismont Date: Fri, 11 Jun 2021 09:36:13 +0200 Subject: [PATCH 07/26] MSSQL: Fix datetime2 encoding (#982) Fixes #977 LocalTime, LocalDateTime and OffsetDateTime have nanosecond precision. Also, the maximum scale in MSSQL is 7 (hundreds of nanos). So when encoding time-related values, we will always send a value with maximum scale. The server will do round/truncate as necessary. Signed-off-by: Thomas Segismont --- .../impl/codec/ExtendedQueryCommandCodec.java | 92 +++++++------------ ...LPreparedQueryNotNullableDataTypeTest.java | 47 ++++++---- .../src/test/resources/init.sql | 2 +- 3 files changed, 62 insertions(+), 79 deletions(-) diff --git a/vertx-mssql-client/src/main/java/io/vertx/mssqlclient/impl/codec/ExtendedQueryCommandCodec.java b/vertx-mssql-client/src/main/java/io/vertx/mssqlclient/impl/codec/ExtendedQueryCommandCodec.java index e952368c5..f86b81ddd 100644 --- a/vertx-mssql-client/src/main/java/io/vertx/mssqlclient/impl/codec/ExtendedQueryCommandCodec.java +++ b/vertx-mssql-client/src/main/java/io/vertx/mssqlclient/impl/codec/ExtendedQueryCommandCodec.java @@ -285,11 +285,11 @@ private void encodeParamValue(ByteBuf payload, Object value) { } else if (value instanceof LocalDate) { encodeDateNParameter(payload, (LocalDate) value); } else if (value instanceof LocalTime) { - encodeTimeNParameter(payload, (LocalTime) value, (byte) 6); + encodeTimeNParameter(payload, (LocalTime) value); } else if (value instanceof LocalDateTime) { - encodeDateTimeNParameter(payload, (LocalDateTime) value, (byte) 6); + encodeDateTimeNParameter(payload, (LocalDateTime) value); } else if (value instanceof OffsetDateTime) { - encodeOffsetDateTimeNParameter(payload, (OffsetDateTime) value, (byte) 6); + encodeOffsetDateTimeNParameter(payload, (OffsetDateTime) value); } else if (value instanceof BigDecimal) { encodeDecimalParameter(payload, (BigDecimal) value); } else if (value instanceof Buffer) { @@ -365,102 +365,72 @@ private void encodeDateNParameter(ByteBuf payload, LocalDate date) { payload.writeByte(0); } else { payload.writeByte(3); - long days = ChronoUnit.DAYS.between(MSSQLDataTypeCodec.START_DATE, date); - payload.writeMediumLE((int) days); + encodeLocalDate(payload, date); } } - private void encodeTimeNParameter(ByteBuf payload, LocalTime time, byte scale) { + private void encodeTimeNParameter(ByteBuf payload, LocalTime time) { payload.writeByte(0x00); payload.writeByte(0x00); payload.writeByte(MSSQLDataTypeId.TIMENTYPE_ID); - payload.writeByte(scale); //FIXME scale? + payload.writeByte(7); // scale if (time == null) { payload.writeByte(0); } else { - int length; - if (scale <= 2) { - length = 3; - } else if (scale <= 4) { - length = 4; - } else { - length = 5; - } - payload.writeByte(length); - long nanos = time.getNano(); - int seconds = time.toSecondOfDay(); - long value = (long) ((long) seconds * Math.pow(10, scale) + nanos); - encodeInt40(payload, value); + payload.writeByte(5); // length + encodeLocalTime(payload, time); } } - private void encodeDateTimeNParameter(ByteBuf payload, LocalDateTime dateTime, byte scale) { + private void encodeDateTimeNParameter(ByteBuf payload, LocalDateTime dateTime) { payload.writeByte(0x00); payload.writeByte(0x00); payload.writeByte(MSSQLDataTypeId.DATETIME2NTYPE_ID); - payload.writeByte(scale); //FIXME scale? + payload.writeByte(7); // scale if (dateTime == null) { payload.writeByte(0); } else { - int length; - if (scale <= 2) { - length = 3; - } else if (scale <= 4) { - length = 4; - } else { - length = 5; - } - length += 3; - payload.writeByte(length); - LocalTime localTime = dateTime.toLocalTime(); - long nanos = localTime.getNano(); - int seconds = localTime.toSecondOfDay(); - long value = (long) ((long) seconds * Math.pow(10, scale) + nanos); - encodeInt40(payload, value); - long days = ChronoUnit.DAYS.between(MSSQLDataTypeCodec.START_DATE, dateTime.toLocalDate()); - payload.writeMediumLE((int) days); + payload.writeByte(8); // length + encodeLocalTime(payload, dateTime.toLocalTime()); + encodeLocalDate(payload, dateTime.toLocalDate()); } } - private void encodeOffsetDateTimeNParameter(ByteBuf payload, OffsetDateTime offsetDateTime, byte scale) { + private void encodeOffsetDateTimeNParameter(ByteBuf payload, OffsetDateTime offsetDateTime) { payload.writeByte(0x00); payload.writeByte(0x00); payload.writeByte(MSSQLDataTypeId.DATETIMEOFFSETNTYPE_ID); - payload.writeByte(scale); //FIXME scale? + payload.writeByte(7); if (offsetDateTime == null) { payload.writeByte(0); } else { - int length; - if (scale <= 2) { - length = 3; - } else if (scale <= 4) { - length = 4; - } else { - length = 5; - } - length += 5; - payload.writeByte(length); - int minutes = offsetDateTime.getOffset().getTotalSeconds() / 60; - LocalDateTime localDateTime = offsetDateTime.toLocalDateTime().minusMinutes(minutes); - LocalTime localTime = localDateTime.toLocalTime(); - long nanos = localTime.getNano(); - int seconds = localTime.toSecondOfDay(); - long value = (long) ((long) seconds * Math.pow(10, scale) + nanos); - encodeInt40(payload, value); - long days = ChronoUnit.DAYS.between(MSSQLDataTypeCodec.START_DATE, localDateTime.toLocalDate()); - payload.writeMediumLE((int) days); - payload.writeShortLE(minutes); + payload.writeByte(10); // length + int offsetMinutes = offsetDateTime.getOffset().getTotalSeconds() / 60; + LocalDateTime localDateTime = offsetDateTime.toLocalDateTime().minusMinutes(offsetMinutes); + encodeLocalTime(payload, localDateTime.toLocalTime()); + LocalDate localDate = localDateTime.toLocalDate(); + encodeLocalDate(payload, localDate); + payload.writeShortLE(offsetMinutes); } } + private void encodeLocalTime(ByteBuf payload, LocalTime localTime) { + encodeInt40(payload, localTime.toNanoOfDay() / 100); + } + private void encodeInt40(ByteBuf buffer, long value) { buffer.writeIntLE((int) (value % 0x100000000L)); buffer.writeByte((int) (value / 0x100000000L)); } + private void encodeLocalDate(ByteBuf payload, LocalDate localDate) { + long days = ChronoUnit.DAYS.between(MSSQLDataTypeCodec.START_DATE, localDate); + payload.writeMediumLE((int) days); + } + private void encodeDecimalParameter(ByteBuf payload, BigDecimal value) { payload.writeByte(0x00); payload.writeByte(0x00); diff --git a/vertx-mssql-client/src/test/java/io/vertx/mssqlclient/data/MSSQLPreparedQueryNotNullableDataTypeTest.java b/vertx-mssql-client/src/test/java/io/vertx/mssqlclient/data/MSSQLPreparedQueryNotNullableDataTypeTest.java index 752a8cac5..d053ddc29 100644 --- a/vertx-mssql-client/src/test/java/io/vertx/mssqlclient/data/MSSQLPreparedQueryNotNullableDataTypeTest.java +++ b/vertx-mssql-client/src/test/java/io/vertx/mssqlclient/data/MSSQLPreparedQueryNotNullableDataTypeTest.java @@ -13,10 +13,13 @@ import io.vertx.core.buffer.Buffer; import io.vertx.ext.unit.TestContext; +import io.vertx.ext.unit.junit.Repeat; +import io.vertx.ext.unit.junit.RepeatRule; import io.vertx.ext.unit.junit.VertxUnitRunner; import io.vertx.sqlclient.ColumnChecker; import io.vertx.sqlclient.Row; import io.vertx.sqlclient.Tuple; +import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; @@ -26,6 +29,10 @@ @RunWith(VertxUnitRunner.class) public class MSSQLPreparedQueryNotNullableDataTypeTest extends MSSQLNotNullableDataTypeTestBase { + + @Rule + public RepeatRule rule = new RepeatRule(); + @Test public void testEncodeTinyInt(TestContext ctx) { testEncodeNumber(ctx, "test_tinyint", (short) 255); @@ -111,39 +118,45 @@ public void testEncodeDate(TestContext ctx) { } @Test + @Repeat(100) public void testEncodeTime(TestContext ctx) { - testPreparedQueryEncodeGeneric(ctx, "not_nullable_datatype", "test_time", LocalTime.of(23, 10, 45), row -> { + LocalTime now = LocalTime.now(); + testPreparedQueryEncodeGeneric(ctx, "not_nullable_datatype", "test_time", now, row -> { ColumnChecker.checkColumn(0, "test_time") - .returns(Tuple::getValue, Row::getValue, LocalTime.of(23, 10, 45)) - .returns(Tuple::getLocalTime, Row::getLocalTime, LocalTime.of(23, 10, 45)) - .returns(LocalTime.class, LocalTime.of(23, 10, 45)) + .returns(Tuple::getValue, Row::getValue, now) + .returns(Tuple::getLocalTime, Row::getLocalTime, now) + .returns(LocalTime.class, now) .forRow(row); }); } @Test + @Repeat(100) public void testEncodeDateTime(TestContext ctx) { - testPreparedQueryEncodeGeneric(ctx, "not_nullable_datatype", "test_datetime2", LocalDateTime.of(1999, 12, 31, 23, 10, 45), row -> { + LocalDateTime now = LocalDateTime.now(); + testPreparedQueryEncodeGeneric(ctx, "not_nullable_datatype", "test_datetime2", now, row -> { ColumnChecker.checkColumn(0, "test_datetime2") - .returns(Tuple::getValue, Row::getValue, LocalDateTime.of(1999, 12, 31, 23, 10, 45)) - .returns(Tuple::getLocalDateTime, Row::getLocalDateTime, LocalDateTime.of(1999, 12, 31, 23, 10, 45)) - .returns(Tuple::getLocalDate, Row::getLocalDate, LocalDate.of(1999, 12, 31)) - .returns(Tuple::getLocalTime, Row::getLocalTime, LocalTime.of(23, 10, 45)) - .returns(LocalDateTime.class, LocalDateTime.of(1999, 12, 31, 23, 10, 45)) + .returns(Tuple::getValue, Row::getValue, now) + .returns(Tuple::getLocalDateTime, Row::getLocalDateTime, now) + .returns(Tuple::getLocalDate, Row::getLocalDate, now.toLocalDate()) + .returns(Tuple::getLocalTime, Row::getLocalTime, now.toLocalTime()) + .returns(LocalDateTime.class, now) .forRow(row); }); } @Test + @Repeat(100) public void testEncodeOffsetDateTime(TestContext ctx) { - testPreparedQueryEncodeGeneric(ctx, "not_nullable_datatype", "test_datetimeoffset", LocalDateTime.of(1999, 12, 31, 23, 10, 45).atOffset(ZoneOffset.ofHoursMinutes(-3, -15)), row -> { + OffsetDateTime now = LocalDateTime.now().atOffset(ZoneOffset.ofHoursMinutes(-3, -15)); + testPreparedQueryEncodeGeneric(ctx, "not_nullable_datatype", "test_datetimeoffset", now, row -> { ColumnChecker.checkColumn(0, "test_datetimeoffset") - .returns(Tuple::getValue, Row::getValue, LocalDateTime.of(1999, 12, 31, 23, 10, 45).atOffset(ZoneOffset.ofHoursMinutes(-3, -15))) - .returns(Tuple::getOffsetDateTime, Row::getOffsetDateTime, LocalDateTime.of(1999, 12, 31, 23, 10, 45).atOffset(ZoneOffset.ofHoursMinutes(-3, -15))) - .returns(Tuple::getLocalDateTime, Row::getLocalDateTime, LocalDateTime.of(1999, 12, 31, 23, 10, 45)) - .returns(Tuple::getLocalDate, Row::getLocalDate, LocalDate.of(1999, 12, 31)) - .returns(Tuple::getLocalTime, Row::getLocalTime, LocalTime.of(23, 10, 45)) - .returns(OffsetDateTime.class, LocalDateTime.of(1999, 12, 31, 23, 10, 45).atOffset(ZoneOffset.ofHoursMinutes(-3, -15))) + .returns(Tuple::getValue, Row::getValue, now) + .returns(Tuple::getOffsetDateTime, Row::getOffsetDateTime, now) + .returns(Tuple::getLocalDateTime, Row::getLocalDateTime, now.toLocalDateTime()) + .returns(Tuple::getLocalDate, Row::getLocalDate, now.toLocalDate()) + .returns(Tuple::getLocalTime, Row::getLocalTime, now.toLocalTime()) + .returns(OffsetDateTime.class, now) .forRow(row); }); } diff --git a/vertx-mssql-client/src/test/resources/init.sql b/vertx-mssql-client/src/test/resources/init.sql index 263155d9e..93c58d07f 100644 --- a/vertx-mssql-client/src/test/resources/init.sql +++ b/vertx-mssql-client/src/test/resources/init.sql @@ -136,7 +136,7 @@ CREATE TABLE not_nullable_datatype test_date DATE NOT NULL, test_time TIME(6) NOT NULL, test_datetime2 DATETIME2(7) NOT NULL, - test_datetimeoffset DATETIMEOFFSET(1) NOT NULL, + test_datetimeoffset DATETIMEOFFSET(5) NOT NULL, test_binary BINARY(20) NOT NULL, test_varbinary VARBINARY(20) NOT NULL ); From 452335ce79e4381938d9f348095fab198ba5fedf Mon Sep 17 00:00:00 2001 From: Thomas Segismont Date: Fri, 11 Jun 2021 09:39:49 +0200 Subject: [PATCH 08/26] Disable DB2SecureTest which fails consistently See #983 Temporarily disabling in order to let CI deploy snaphots of clients Signed-off-by: Thomas Segismont --- .../src/test/java/io/vertx/db2client/DB2SecureTest.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/vertx-db2-client/src/test/java/io/vertx/db2client/DB2SecureTest.java b/vertx-db2-client/src/test/java/io/vertx/db2client/DB2SecureTest.java index dbe378ba1..a9f7baca2 100644 --- a/vertx-db2-client/src/test/java/io/vertx/db2client/DB2SecureTest.java +++ b/vertx-db2-client/src/test/java/io/vertx/db2client/DB2SecureTest.java @@ -2,6 +2,7 @@ import org.junit.Before; import org.junit.ClassRule; +import org.junit.Ignore; import org.junit.Rule; import org.junit.rules.TestName; import org.junit.runner.RunWith; @@ -13,6 +14,7 @@ import io.vertx.sqlclient.tck.SimpleQueryTestBase; @RunWith(VertxUnitRunner.class) +@Ignore public class DB2SecureTest extends SimpleQueryTestBase { @ClassRule From ed5e15ded2382e001703f36c54981801c640ed12 Mon Sep 17 00:00:00 2001 From: Thomas Segismont Date: Fri, 11 Jun 2021 09:46:13 +0200 Subject: [PATCH 09/26] Update doc for external SQL Server test database Signed-off-by: Thomas Segismont --- vertx-mssql-client/README.adoc | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/vertx-mssql-client/README.adoc b/vertx-mssql-client/README.adoc index 5b618daf1..22269b3be 100644 --- a/vertx-mssql-client/README.adoc +++ b/vertx-mssql-client/README.adoc @@ -12,11 +12,18 @@ By default, the test suite runs SQL Server in a container using https://www.test ==== Testing with an external database -You can run tests with an external database: +You can start an external database: -[source] +[source,bash] ---- -> mvn test -Dconnection.uri=sqlserver://my_user:my_password@my_host:1433/my_db +docker run -t -i -p 1433:1433 -e TZ=Europe/Paris -e ACCEPT_EULA=Y -e SA_PASSWORD=A_Str0ng_Required_Password mcr.microsoft.com/mssql/server:2017-latest +---- + +Then run tests against it: + +[source,bash] +---- +mvn test -Dconnection.uri=sqlserver://SA:A_Str0ng_Required_Password@localhost:1433 ---- * `connection.uri`: configure the client to connect to the specified database From 7cdef9e9c692120c5eca31940960a4fe884e9ece Mon Sep 17 00:00:00 2001 From: Julien Viet Date: Mon, 14 Jun 2021 14:07:20 +0200 Subject: [PATCH 10/26] Remove dead code --- .../io/vertx/sqlclient/impl/PoolBase.java | 32 ------------------- 1 file changed, 32 deletions(-) diff --git a/vertx-sql-client/src/main/java/io/vertx/sqlclient/impl/PoolBase.java b/vertx-sql-client/src/main/java/io/vertx/sqlclient/impl/PoolBase.java index 94b00385d..e95408542 100644 --- a/vertx-sql-client/src/main/java/io/vertx/sqlclient/impl/PoolBase.java +++ b/vertx-sql-client/src/main/java/io/vertx/sqlclient/impl/PoolBase.java @@ -20,7 +20,6 @@ import io.vertx.core.*; import io.vertx.core.impl.CloseFuture; import io.vertx.core.impl.ContextInternal; -import io.vertx.core.impl.EventLoopContext; import io.vertx.core.impl.VertxInternal; import io.vertx.core.impl.future.PromiseInternal; import io.vertx.core.spi.metrics.ClientMetrics; @@ -165,37 +164,6 @@ private void acquire(ContextInternal context, long timeout, Handler> { - - protected abstract void onSuccess(Connection conn); - - protected abstract void onFailure(Throwable cause); - - @Override - public void handleEvent(Object event) { - // What should we do ? - } - - @Override - public void handle(AsyncResult ar) { - if (ar.succeeded()) { - Connection conn = ar.result(); - conn.init(this); - onSuccess(conn); - } else { - onFailure(ar.cause()); - } - } - - @Override - public void handleClosed() { - } - - @Override - public void handleException(Throwable err) { - } - } - protected abstract SqlConnectionImpl wrap(ContextInternal context, Connection conn); @Override From 043000af29c2ca0c439d6af6e758310632316900 Mon Sep 17 00:00:00 2001 From: Thomas Segismont Date: Mon, 14 Jun 2021 16:06:41 +0200 Subject: [PATCH 11/26] Implement batch queries for MSSQL Client (#985) * Implement batch queries for MSSQL Client Closes #607 Signed-off-by: Thomas Segismont * Tracing tests are now fully passing Signed-off-by: Thomas Segismont --- .../src/main/asciidoc/index.adoc | 3 +- .../codec/ExtendedBatchQueryCommandCodec.java | 72 +++ .../codec/ExtendedQueryCommandBaseCodec.java | 475 ++++++++++++++++++ .../impl/codec/ExtendedQueryCommandCodec.java | 444 +--------------- .../impl/codec/TdsMessageEncoder.java | 4 +- .../tck/MSSQLPreparedBatchTest.java | 40 ++ .../mssqlclient/tck/MSSQLTracingTest.java | 14 - 7 files changed, 599 insertions(+), 453 deletions(-) create mode 100644 vertx-mssql-client/src/main/java/io/vertx/mssqlclient/impl/codec/ExtendedBatchQueryCommandCodec.java create mode 100644 vertx-mssql-client/src/main/java/io/vertx/mssqlclient/impl/codec/ExtendedQueryCommandBaseCodec.java create mode 100644 vertx-mssql-client/src/test/java/io/vertx/mssqlclient/tck/MSSQLPreparedBatchTest.java diff --git a/vertx-mssql-client/src/main/asciidoc/index.adoc b/vertx-mssql-client/src/main/asciidoc/index.adoc index 02074ef4f..9c6f042d6 100644 --- a/vertx-mssql-client/src/main/asciidoc/index.adoc +++ b/vertx-mssql-client/src/main/asciidoc/index.adoc @@ -1,6 +1,5 @@ = Reactive MSSQL Client :PREPARED_PARAMS: `@1`, `@2`, etc…​ -:batching-unsupported: The Reactive MSSQL Client is a client for Microsoft SQL Server with a straightforward API focusing on scalability and low overhead. @@ -17,7 +16,7 @@ scalability and low overhead. *Not supported yet* * Prepared queries caching -* Batch and cursor +* Cursor * Row streaming * Some https://github.com/eclipse-vertx/vertx-sql-client/issues/608#issuecomment-629390027[data types] are not supported diff --git a/vertx-mssql-client/src/main/java/io/vertx/mssqlclient/impl/codec/ExtendedBatchQueryCommandCodec.java b/vertx-mssql-client/src/main/java/io/vertx/mssqlclient/impl/codec/ExtendedBatchQueryCommandCodec.java new file mode 100644 index 000000000..6cc28af67 --- /dev/null +++ b/vertx-mssql-client/src/main/java/io/vertx/mssqlclient/impl/codec/ExtendedBatchQueryCommandCodec.java @@ -0,0 +1,72 @@ +/* + * Copyright (c) 2011-2021 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.mssqlclient.impl.codec; + +import io.netty.buffer.ByteBuf; +import io.vertx.sqlclient.Tuple; +import io.vertx.sqlclient.impl.TupleInternal; +import io.vertx.sqlclient.impl.command.CommandResponse; +import io.vertx.sqlclient.impl.command.ExtendedQueryCommand; + +import java.util.List; + +class ExtendedBatchQueryCommandCodec extends ExtendedQueryCommandBaseCodec { + + private final List paramsList; + + private int paramsIdx; + private int messageDecoded; + + ExtendedBatchQueryCommandCodec(ExtendedQueryCommand cmd) { + super(cmd); + paramsList = cmd.paramsList(); + } + + @Override + void encode(TdsMessageEncoder encoder) { + if (paramsList.isEmpty()) { + completionHandler.handle(CommandResponse.failure("Can not execute batch query with 0 sets of batch parameters.")); + return; + } + super.encode(encoder); + } + + @Override + protected void handleMessageDecoded() { + if (paramsList.size() == 1 || ++messageDecoded == 2) { + complete(); + } else { + sendExecRequest(); + } + } + + @Override + protected TupleInternal prepexecRequestParams() { + paramsIdx = 1; + return (TupleInternal) paramsList.get(0); + } + + @Override + protected void writeRpcRequestBatch(ByteBuf packet) { + for (int initial = paramsIdx; paramsIdx < paramsList.size(); paramsIdx++) { + if (initial != paramsIdx) { + packet.writeByte(0xFF); // batch separator + } + super.writeRpcRequestBatch(packet); + } + } + + @Override + protected TupleInternal execRequestParams() { + return (TupleInternal) paramsList.get(paramsIdx); + } +} diff --git a/vertx-mssql-client/src/main/java/io/vertx/mssqlclient/impl/codec/ExtendedQueryCommandBaseCodec.java b/vertx-mssql-client/src/main/java/io/vertx/mssqlclient/impl/codec/ExtendedQueryCommandBaseCodec.java new file mode 100644 index 000000000..7386d6871 --- /dev/null +++ b/vertx-mssql-client/src/main/java/io/vertx/mssqlclient/impl/codec/ExtendedQueryCommandBaseCodec.java @@ -0,0 +1,475 @@ +/* + * Copyright (c) 2011-2021 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.mssqlclient.impl.codec; + +import io.netty.buffer.ByteBuf; +import io.netty.channel.ChannelHandlerContext; +import io.vertx.core.buffer.Buffer; +import io.vertx.mssqlclient.impl.protocol.MessageStatus; +import io.vertx.mssqlclient.impl.protocol.MessageType; +import io.vertx.mssqlclient.impl.protocol.TdsMessage; +import io.vertx.mssqlclient.impl.protocol.client.rpc.ProcId; +import io.vertx.mssqlclient.impl.protocol.datatype.MSSQLDataTypeId; +import io.vertx.mssqlclient.impl.protocol.server.DoneToken; +import io.vertx.mssqlclient.impl.protocol.token.DataPacketStreamTokenType; +import io.vertx.sqlclient.impl.TupleInternal; +import io.vertx.sqlclient.impl.command.ExtendedQueryCommand; + +import java.math.BigDecimal; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.time.OffsetDateTime; +import java.time.temporal.ChronoUnit; + +import static io.vertx.mssqlclient.impl.codec.MSSQLDataTypeCodec.inferenceParamDefinitionByValueType; + +abstract class ExtendedQueryCommandBaseCodec extends QueryCommandBaseCodec> { + + protected int rowCount; + + ExtendedQueryCommandBaseCodec(ExtendedQueryCommand cmd) { + super(cmd); + } + + @Override + void encode(TdsMessageEncoder encoder) { + super.encode(encoder); + MSSQLPreparedStatement ps = (MSSQLPreparedStatement) cmd.preparedStatement(); + if (ps.handle > 0) { + sendExecRequest(); + } else { + sendPrepexecRequest(); + } + } + + @Override + void decodeMessage(TdsMessage message, TdsMessageEncoder encoder) { + ByteBuf messageBody = message.content(); + while (messageBody.isReadable()) { + DataPacketStreamTokenType tokenType = DataPacketStreamTokenType.valueOf(messageBody.readUnsignedByte()); + if (tokenType == null) { + throw new UnsupportedOperationException("Unsupported token: " + tokenType); + } + MSSQLPreparedStatement ps = (MSSQLPreparedStatement) cmd.preparedStatement(); + switch (tokenType) { + case COLMETADATA_TOKEN: + MSSQLRowDesc rowDesc = decodeColmetadataToken(messageBody); + rowResultDecoder = new RowResultDecoder<>(cmd.collector(), rowDesc); + break; + case ROW_TOKEN: + handleRow(messageBody); + break; + case NBCROW_TOKEN: + handleNbcRow(messageBody); + break; + case DONE_TOKEN: + messageBody.skipBytes(12); // this should only be after ERROR_TOKEN? + break; + case INFO_TOKEN: + case ORDER_TOKEN: + int tokenLength = messageBody.readUnsignedShortLE(); + messageBody.skipBytes(tokenLength); + break; + case ERROR_TOKEN: + handleErrorToken(messageBody); + break; + case DONEPROC_TOKEN: + messageBody.skipBytes(12); + handleResultSetDone(rowCount); + break; + case DONEINPROC_TOKEN: + short status = messageBody.readShortLE(); + short curCmd = messageBody.readShortLE(); + long doneRowCount = messageBody.readLongLE(); + if ((status | DoneToken.STATUS_DONE_FINAL) != 0) { + rowCount += doneRowCount; + } else { + handleResultSetDone((int) doneRowCount); + } + break; + case RETURNSTATUS_TOKEN: + messageBody.skipBytes(4); + break; + case RETURNVALUE_TOKEN: + if (ps.handle == 0) { + messageBody.skipBytes(2); // skip ordinal position + messageBody.skipBytes(2 * messageBody.readUnsignedByte()); // skip param name + messageBody.skipBytes(1); // skip status + messageBody.skipBytes(4); // skip user type + messageBody.skipBytes(2); // skip flags + messageBody.skipBytes(1); // skip type id + messageBody.skipBytes(2); // max length and length + ps.handle = messageBody.readIntLE(); + } + messageBody.skipBytes(messageBody.readableBytes()); // FIXME + handleResultSetDone(rowCount); + break; + default: + throw new UnsupportedOperationException("Unsupported token: " + tokenType); + } + } + handleMessageDecoded(); + } + + @Override + protected void handleResultSetDone(int affectedRows) { + super.handleResultSetDone(rowCount); + rowCount = 0; + } + + protected abstract void handleMessageDecoded(); + + private void sendPrepexecRequest() { + ChannelHandlerContext chctx = encoder.chctx; + + ByteBuf packet = chctx.alloc().ioBuffer(); + + // packet header + packet.writeByte(MessageType.RPC.value()); + packet.writeByte(MessageStatus.NORMAL.value() | MessageStatus.END_OF_MESSAGE.value()); + int packetLenIdx = packet.writerIndex(); + packet.writeShort(0); // set length later + packet.writeShort(0x00); + packet.writeByte(0x00); // FIXME packet ID + packet.writeByte(0x00); + + int start = packet.writerIndex(); + packet.writeIntLE(0x00); // TotalLength for ALL_HEADERS + encodeTransactionDescriptor(packet); + // set TotalLength for ALL_HEADERS + packet.setIntLE(start, packet.writerIndex() - start); + + // RPCReqBatch + packet.writeShortLE(0xFFFF); + packet.writeShortLE(ProcId.Sp_PrepExec); + + // Option flags + packet.writeShortLE(0x0000); + + // Parameter + + // OUT Parameter + packet.writeByte(0x00); + packet.writeByte(0x01); // By reference + packet.writeByte(MSSQLDataTypeId.INTNTYPE_ID); + packet.writeByte(0x04); + packet.writeByte(0x04); + MSSQLPreparedStatement ps = (MSSQLPreparedStatement) cmd.ps; + packet.writeIntLE(ps.handle); + + TupleInternal params = prepexecRequestParams(); + + // Param definitions + String paramDefinitions = parseParamDefinitions(params); + encodeNVarcharParameter(packet, paramDefinitions); + + // SQL text + encodeNVarcharParameter(packet, cmd.sql()); + + // Param values + for (int i = 0; i < params.size(); i++) { + encodeParamValue(packet, params.getValue(i)); + } + + int packetLen = packet.writerIndex() - packetLenIdx + 2; + packet.setShort(packetLenIdx, packetLen); + + chctx.writeAndFlush(packet); + } + + protected abstract TupleInternal prepexecRequestParams(); + + void sendExecRequest() { + ChannelHandlerContext chctx = encoder.chctx; + + ByteBuf packet = chctx.alloc().ioBuffer(); + + // packet header + packet.writeByte(MessageType.RPC.value()); + packet.writeByte(MessageStatus.NORMAL.value() | MessageStatus.END_OF_MESSAGE.value()); + int packetLenIdx = packet.writerIndex(); + packet.writeShort(0); // set length later + packet.writeShort(0x00); + packet.writeByte(0x00); // FIXME packet ID + packet.writeByte(0x00); + + int start = packet.writerIndex(); + packet.writeIntLE(0x00); // TotalLength for ALL_HEADERS + encodeTransactionDescriptor(packet); + // set TotalLength for ALL_HEADERS + packet.setIntLE(start, packet.writerIndex() - start); + + writeRpcRequestBatch(packet); + + int packetLen = packet.writerIndex() - packetLenIdx + 2; + packet.setShort(packetLenIdx, packetLen); + + chctx.writeAndFlush(packet, encoder.chctx.voidPromise()); + } + + protected void writeRpcRequestBatch(ByteBuf packet) { + // RPCReqBatch + packet.writeShortLE(0xFFFF); + packet.writeShortLE(ProcId.Sp_Execute); + + // Option flags + packet.writeShortLE(0x0000); + + // Parameter + + // OUT Parameter + packet.writeByte(0x00); + packet.writeByte(0x00); + packet.writeByte(MSSQLDataTypeId.INTNTYPE_ID); + packet.writeByte(0x04); // Max length + packet.writeByte(0x04); // Length + MSSQLPreparedStatement ps = (MSSQLPreparedStatement) cmd.ps; + packet.writeIntLE(ps.handle); + + TupleInternal params = execRequestParams(); + + // Param values + for (int i = 0; i < params.size(); i++) { + encodeParamValue(packet, params.getValue(i)); + } + + } + + protected abstract TupleInternal execRequestParams(); + + private String parseParamDefinitions(TupleInternal params) { + StringBuilder stringBuilder = new StringBuilder(); + for (int i = 0; i < params.size(); i++) { + Object param = params.getValueInternal(i); + stringBuilder.append("@P").append(i + 1).append(" "); + stringBuilder.append(inferenceParamDefinitionByValueType(param)); + if (i != params.size() - 1) { + stringBuilder.append(","); + } + } + return stringBuilder.toString(); + } + + private void encodeNVarcharParameter(ByteBuf payload, String value) { + payload.writeByte(0x00); // name length + payload.writeByte(0x00); // status flags + payload.writeByte(MSSQLDataTypeId.NVARCHARTYPE_ID); + payload.writeShortLE(8000); // maximal length + payload.writeByte(0x09); + payload.writeByte(0x04); + payload.writeByte(0xd0); + payload.writeByte(0x00); + payload.writeByte(0x34); // Collation for param definitions TODO always this value? + writeUnsignedShortLenVarChar(payload, value); + } + + private void encodeParamValue(ByteBuf payload, Object value) { + if (value == null) { + encodeNullParameter(payload); + } else if (value instanceof Byte) { + encodeIntNParameter(payload, 1, value); + } else if (value instanceof Short) { + encodeIntNParameter(payload, 2, value); + } else if (value instanceof Integer) { + encodeIntNParameter(payload, 4, value); + } else if (value instanceof Long) { + encodeIntNParameter(payload, 8, value); + } else if (value instanceof Float) { + encodeFloat4Parameter(payload, (Float) value); + } else if (value instanceof Double) { + encodeFloat8Parameter(payload, (Double) value); + } else if (value instanceof String) { + encodeNVarcharParameter(payload, (String) value); + } else if (value instanceof Enum) { + encodeNVarcharParameter(payload, ((Enum) value).name()); + } else if (value instanceof Boolean) { + encodeBitNParameter(payload, (Boolean) value); + } else if (value instanceof LocalDate) { + encodeDateNParameter(payload, (LocalDate) value); + } else if (value instanceof LocalTime) { + encodeTimeNParameter(payload, (LocalTime) value); + } else if (value instanceof LocalDateTime) { + encodeDateTimeNParameter(payload, (LocalDateTime) value); + } else if (value instanceof OffsetDateTime) { + encodeOffsetDateTimeNParameter(payload, (OffsetDateTime) value); + } else if (value instanceof BigDecimal) { + encodeDecimalParameter(payload, (BigDecimal) value); + } else if (value instanceof Buffer) { + encodeBufferParameter(payload, (Buffer) value); + } else { + throw new UnsupportedOperationException("Unsupported type"); + } + } + + private void encodeNullParameter(ByteBuf payload) { + payload.writeByte(0x00); + payload.writeByte(0x00); + payload.writeByte(MSSQLDataTypeId.NULLTYPE_ID); + } + + private void encodeIntNParameter(ByteBuf payload, int n, Object value) { + payload.writeByte(0x00); + payload.writeByte(0x00); + payload.writeByte(MSSQLDataTypeId.INTNTYPE_ID); + payload.writeByte(n); + payload.writeByte(n); + switch (n) { + case 1: + payload.writeByte((Byte) value); + break; + case 2: + payload.writeShortLE((Short) value); + break; + case 4: + payload.writeIntLE((Integer) value); + break; + case 8: + payload.writeLongLE((Long) value); + break; + default: + throw new UnsupportedOperationException(); + } + } + + private void encodeBitNParameter(ByteBuf payload, Boolean bit) { + payload.writeByte(0x00); + payload.writeByte(0x00); + payload.writeByte(MSSQLDataTypeId.BITNTYPE_ID); + payload.writeByte(1); + payload.writeByte(1); + payload.writeBoolean(bit); + } + + private void encodeFloat4Parameter(ByteBuf payload, Float value) { + payload.writeByte(0x00); + payload.writeByte(0x00); + payload.writeByte(MSSQLDataTypeId.FLTNTYPE_ID); + payload.writeByte(4); + payload.writeByte(4); + payload.writeFloatLE(value); + } + + private void encodeFloat8Parameter(ByteBuf payload, Double value) { + payload.writeByte(0x00); + payload.writeByte(0x00); + payload.writeByte(MSSQLDataTypeId.FLTNTYPE_ID); + payload.writeByte(8); + payload.writeByte(8); + payload.writeDoubleLE(value); + } + + private void encodeDateNParameter(ByteBuf payload, LocalDate date) { + payload.writeByte(0x00); + payload.writeByte(0x00); + payload.writeByte(MSSQLDataTypeId.DATENTYPE_ID); + if (date == null) { + // null + payload.writeByte(0); + } else { + payload.writeByte(3); + encodeLocalDate(payload, date); + } + } + + private void encodeTimeNParameter(ByteBuf payload, LocalTime time) { + payload.writeByte(0x00); + payload.writeByte(0x00); + payload.writeByte(MSSQLDataTypeId.TIMENTYPE_ID); + + payload.writeByte(7); // scale + if (time == null) { + payload.writeByte(0); + } else { + payload.writeByte(5); // length + encodeLocalTime(payload, time); + } + } + + private void encodeDateTimeNParameter(ByteBuf payload, LocalDateTime dateTime) { + payload.writeByte(0x00); + payload.writeByte(0x00); + payload.writeByte(MSSQLDataTypeId.DATETIME2NTYPE_ID); + + payload.writeByte(7); // scale + if (dateTime == null) { + payload.writeByte(0); + } else { + payload.writeByte(8); // length + encodeLocalTime(payload, dateTime.toLocalTime()); + encodeLocalDate(payload, dateTime.toLocalDate()); + } + } + + private void encodeOffsetDateTimeNParameter(ByteBuf payload, OffsetDateTime offsetDateTime) { + payload.writeByte(0x00); + payload.writeByte(0x00); + payload.writeByte(MSSQLDataTypeId.DATETIMEOFFSETNTYPE_ID); + + payload.writeByte(7); + if (offsetDateTime == null) { + payload.writeByte(0); + } else { + payload.writeByte(10); // length + int offsetMinutes = offsetDateTime.getOffset().getTotalSeconds() / 60; + LocalDateTime localDateTime = offsetDateTime.toLocalDateTime().minusMinutes(offsetMinutes); + encodeLocalTime(payload, localDateTime.toLocalTime()); + LocalDate localDate = localDateTime.toLocalDate(); + encodeLocalDate(payload, localDate); + payload.writeShortLE(offsetMinutes); + } + } + + private void encodeLocalTime(ByteBuf payload, LocalTime localTime) { + encodeInt40(payload, localTime.toNanoOfDay() / 100); + } + + private void encodeInt40(ByteBuf buffer, long value) { + buffer.writeIntLE((int) (value % 0x100000000L)); + buffer.writeByte((int) (value / 0x100000000L)); + } + + private void encodeLocalDate(ByteBuf payload, LocalDate localDate) { + long days = ChronoUnit.DAYS.between(MSSQLDataTypeCodec.START_DATE, localDate); + payload.writeMediumLE((int) days); + } + + private void encodeDecimalParameter(ByteBuf payload, BigDecimal value) { + payload.writeByte(0x00); + payload.writeByte(0x00); + payload.writeByte(MSSQLDataTypeId.DECIMALNTYPE_ID); + + payload.writeByte(17); // maximum length + payload.writeByte(38); // maximum precision + + int sign = value.signum() < 0 ? 0 : 1; + byte[] bytes = (sign == 0 ? value.negate() : value).unscaledValue().toByteArray(); + + payload.writeByte(Math.max(0, value.scale())); + payload.writeByte(1 + bytes.length); + payload.writeByte(sign); + for (int i = bytes.length - 1; i >= 0; i--) { + payload.writeByte(bytes[i]); + } + } + + private void encodeBufferParameter(ByteBuf payload, Buffer value) { + payload.writeByte(0x00); + payload.writeByte(0x00); + payload.writeByte(MSSQLDataTypeId.BIGBINARYTYPE_ID); + + payload.writeShortLE(value.length()); // max length + payload.writeShortLE(value.length()); // length + + payload.writeBytes(value.getByteBuf()); + } +} diff --git a/vertx-mssql-client/src/main/java/io/vertx/mssqlclient/impl/codec/ExtendedQueryCommandCodec.java b/vertx-mssql-client/src/main/java/io/vertx/mssqlclient/impl/codec/ExtendedQueryCommandCodec.java index f86b81ddd..2af345cc3 100644 --- a/vertx-mssql-client/src/main/java/io/vertx/mssqlclient/impl/codec/ExtendedQueryCommandCodec.java +++ b/vertx-mssql-client/src/main/java/io/vertx/mssqlclient/impl/codec/ExtendedQueryCommandCodec.java @@ -11,453 +11,27 @@ package io.vertx.mssqlclient.impl.codec; -import io.netty.buffer.ByteBuf; -import io.netty.channel.ChannelHandlerContext; -import io.vertx.core.buffer.Buffer; -import io.vertx.mssqlclient.impl.protocol.MessageStatus; -import io.vertx.mssqlclient.impl.protocol.MessageType; -import io.vertx.mssqlclient.impl.protocol.TdsMessage; -import io.vertx.mssqlclient.impl.protocol.client.rpc.ProcId; -import io.vertx.mssqlclient.impl.protocol.datatype.MSSQLDataTypeId; -import io.vertx.mssqlclient.impl.protocol.server.DoneToken; -import io.vertx.mssqlclient.impl.protocol.token.DataPacketStreamTokenType; -import io.vertx.sqlclient.Tuple; import io.vertx.sqlclient.impl.TupleInternal; import io.vertx.sqlclient.impl.command.ExtendedQueryCommand; -import java.math.BigDecimal; -import java.time.LocalDate; -import java.time.LocalDateTime; -import java.time.LocalTime; -import java.time.OffsetDateTime; -import java.time.temporal.ChronoUnit; +class ExtendedQueryCommandCodec extends ExtendedQueryCommandBaseCodec { -import static io.vertx.mssqlclient.impl.codec.MSSQLDataTypeCodec.inferenceParamDefinitionByValueType; - -class ExtendedQueryCommandCodec extends QueryCommandBaseCodec> { - - private int rowCount; - - ExtendedQueryCommandCodec(ExtendedQueryCommand cmd) { + ExtendedQueryCommandCodec(ExtendedQueryCommand cmd) { super(cmd); } @Override - void encode(TdsMessageEncoder encoder) { - super.encode(encoder); - MSSQLPreparedStatement ps = (MSSQLPreparedStatement) cmd.preparedStatement(); - if (ps.handle > 0) { - sendExecRequest(); - } else { - sendPrepexecRequest(); - } - } - - @Override - void decodeMessage(TdsMessage message, TdsMessageEncoder encoder) { - ByteBuf messageBody = message.content(); - while (messageBody.isReadable()) { - DataPacketStreamTokenType tokenType = DataPacketStreamTokenType.valueOf(messageBody.readUnsignedByte()); - if (tokenType == null) { - throw new UnsupportedOperationException("Unsupported token: " + tokenType); - } - MSSQLPreparedStatement ps = (MSSQLPreparedStatement) cmd.preparedStatement(); - switch (tokenType) { - case COLMETADATA_TOKEN: - MSSQLRowDesc rowDesc = decodeColmetadataToken(messageBody); - rowResultDecoder = new RowResultDecoder<>(cmd.collector(), rowDesc); - break; - case ROW_TOKEN: - handleRow(messageBody); - break; - case NBCROW_TOKEN: - handleNbcRow(messageBody); - break; - case DONE_TOKEN: - messageBody.skipBytes(12); // this should only be after ERROR_TOKEN? - break; - case INFO_TOKEN: - case ORDER_TOKEN: - int tokenLength = messageBody.readUnsignedShortLE(); - messageBody.skipBytes(tokenLength); - break; - case ERROR_TOKEN: - handleErrorToken(messageBody); - break; - case DONEPROC_TOKEN: - messageBody.skipBytes(messageBody.readableBytes()); - handleResultSetDone(rowCount); - break; - case DONEINPROC_TOKEN: - short status = messageBody.readShortLE(); - short curCmd = messageBody.readShortLE(); - long doneRowCount = messageBody.readLongLE(); - if ((status | DoneToken.STATUS_DONE_FINAL) != 0) { - rowCount += doneRowCount; - } else { - handleResultSetDone((int) doneRowCount); - } - break; - case RETURNSTATUS_TOKEN: - messageBody.skipBytes(4); - break; - case RETURNVALUE_TOKEN: - if (ps.handle == 0) { - messageBody.skipBytes(2); // skip ordinal position - messageBody.skipBytes(2 * messageBody.readUnsignedByte()); // skip param name - messageBody.skipBytes(1); // skip status - messageBody.skipBytes(4); // skip user type - messageBody.skipBytes(2); // skip flags - messageBody.skipBytes(1); // skip type id - messageBody.skipBytes(2); // max length and length - ps.handle = messageBody.readIntLE(); - } - messageBody.skipBytes(messageBody.readableBytes()); // FIXME - handleResultSetDone(rowCount); - break; - default: - throw new UnsupportedOperationException("Unsupported token: " + tokenType); - } - } + protected void handleMessageDecoded() { complete(); } - private void sendPrepexecRequest() { - ChannelHandlerContext chctx = encoder.chctx; - - ByteBuf packet = chctx.alloc().ioBuffer(); - - // packet header - packet.writeByte(MessageType.RPC.value()); - packet.writeByte(MessageStatus.NORMAL.value() | MessageStatus.END_OF_MESSAGE.value()); - int packetLenIdx = packet.writerIndex(); - packet.writeShort(0); // set length later - packet.writeShort(0x00); - packet.writeByte(0x00); // FIXME packet ID - packet.writeByte(0x00); - - int start = packet.writerIndex(); - packet.writeIntLE(0x00); // TotalLength for ALL_HEADERS - encodeTransactionDescriptor(packet); - // set TotalLength for ALL_HEADERS - packet.setIntLE(start, packet.writerIndex() - start); - - /* - RPCReqBatch - */ - packet.writeShortLE(0xFFFF); - packet.writeShortLE(ProcId.Sp_PrepExec); - - // Option flags - packet.writeShortLE(0x0000); - - // Parameter - - // OUT Parameter - packet.writeByte(0x00); - packet.writeByte(0x01); // By reference - packet.writeByte(MSSQLDataTypeId.INTNTYPE_ID); - packet.writeByte(0x04); - packet.writeByte(0x04); - MSSQLPreparedStatement ps = (MSSQLPreparedStatement) cmd.ps; - packet.writeIntLE(ps.handle); - - Tuple params = cmd.params(); - - // Param definitions - String paramDefinitions = parseParamDefinitions((TupleInternal) params); - encodeNVarcharParameter(packet, paramDefinitions); - - // SQL text - encodeNVarcharParameter(packet, cmd.sql()); - - // Param values - for (int i = 0; i < params.size(); i++) { - encodeParamValue(packet, params.getValue(i)); - } - - int packetLen = packet.writerIndex() - packetLenIdx + 2; - packet.setShort(packetLenIdx, packetLen); - - chctx.writeAndFlush(packet); - } - - private void sendExecRequest() { - ChannelHandlerContext chctx = encoder.chctx; - - ByteBuf packet = chctx.alloc().ioBuffer(); - - // packet header - packet.writeByte(MessageType.RPC.value()); - packet.writeByte(MessageStatus.NORMAL.value() | MessageStatus.END_OF_MESSAGE.value()); - int packetLenIdx = packet.writerIndex(); - packet.writeShort(0); // set length later - packet.writeShort(0x00); - packet.writeByte(0x00); // FIXME packet ID - packet.writeByte(0x00); - - int start = packet.writerIndex(); - packet.writeIntLE(0x00); // TotalLength for ALL_HEADERS - encodeTransactionDescriptor(packet); - // set TotalLength for ALL_HEADERS - packet.setIntLE(start, packet.writerIndex() - start); - - /* - RPCReqBatch - */ - packet.writeShortLE(0xFFFF); - packet.writeShortLE(ProcId.Sp_Execute); - - // Option flags - packet.writeShortLE(0x0000); - - // Parameter - - // OUT Parameter - packet.writeByte(0x00); - packet.writeByte(0x00); - packet.writeByte(MSSQLDataTypeId.INTNTYPE_ID); - packet.writeByte(0x04); // Max length - packet.writeByte(0x04); // Length - MSSQLPreparedStatement ps = (MSSQLPreparedStatement) cmd.ps; - packet.writeIntLE(ps.handle); - - Tuple params = cmd.params(); - - // Param values - for (int i = 0; i < params.size(); i++) { - encodeParamValue(packet, params.getValue(i)); - } - - int packetLen = packet.writerIndex() - packetLenIdx + 2; - packet.setShort(packetLenIdx, packetLen); - - chctx.writeAndFlush(packet, encoder.chctx.voidPromise()); - } - - private String parseParamDefinitions(TupleInternal params) { - StringBuilder stringBuilder = new StringBuilder(); - for (int i = 0; i < params.size(); i++) { - Object param = params.getValueInternal(i); - stringBuilder.append("@P").append(i + 1).append(" "); - stringBuilder.append(inferenceParamDefinitionByValueType(param)); - if (i != params.size() - 1) { - stringBuilder.append(","); - } - } - return stringBuilder.toString(); - } - - private void encodeNVarcharParameter(ByteBuf payload, String value) { - payload.writeByte(0x00); // name length - payload.writeByte(0x00); // status flags - payload.writeByte(MSSQLDataTypeId.NVARCHARTYPE_ID); - payload.writeShortLE(8000); // maximal length - payload.writeByte(0x09); - payload.writeByte(0x04); - payload.writeByte(0xd0); - payload.writeByte(0x00); - payload.writeByte(0x34); // Collation for param definitions TODO always this value? - writeUnsignedShortLenVarChar(payload, value); - } - - private void encodeParamValue(ByteBuf payload, Object value) { - if (value == null) { - encodeNullParameter(payload); - } else if (value instanceof Byte) { - encodeIntNParameter(payload, 1, value); - } else if (value instanceof Short) { - encodeIntNParameter(payload, 2, value); - } else if (value instanceof Integer) { - encodeIntNParameter(payload, 4, value); - } else if (value instanceof Long) { - encodeIntNParameter(payload, 8, value); - } else if (value instanceof Float) { - encodeFloat4Parameter(payload, (Float) value); - } else if (value instanceof Double) { - encodeFloat8Parameter(payload, (Double) value); - } else if (value instanceof String) { - encodeNVarcharParameter(payload, (String) value); - } else if (value instanceof Enum){ - encodeNVarcharParameter(payload, ((Enum)value).name()); - } else if (value instanceof Boolean) { - encodeBitNParameter(payload, (Boolean) value); - } else if (value instanceof LocalDate) { - encodeDateNParameter(payload, (LocalDate) value); - } else if (value instanceof LocalTime) { - encodeTimeNParameter(payload, (LocalTime) value); - } else if (value instanceof LocalDateTime) { - encodeDateTimeNParameter(payload, (LocalDateTime) value); - } else if (value instanceof OffsetDateTime) { - encodeOffsetDateTimeNParameter(payload, (OffsetDateTime) value); - } else if (value instanceof BigDecimal) { - encodeDecimalParameter(payload, (BigDecimal) value); - } else if (value instanceof Buffer) { - encodeBufferParameter(payload, (Buffer) value); - } else { - throw new UnsupportedOperationException("Unsupported type"); - } - } - - private void encodeNullParameter(ByteBuf payload) { - payload.writeByte(0x00); - payload.writeByte(0x00); - payload.writeByte(MSSQLDataTypeId.NULLTYPE_ID); - } - - private void encodeIntNParameter(ByteBuf payload, int n, Object value) { - payload.writeByte(0x00); - payload.writeByte(0x00); - payload.writeByte(MSSQLDataTypeId.INTNTYPE_ID); - payload.writeByte(n); - payload.writeByte(n); - switch (n) { - case 1: - payload.writeByte((Byte) value); - break; - case 2: - payload.writeShortLE((Short) value); - break; - case 4: - payload.writeIntLE((Integer) value); - break; - case 8: - payload.writeLongLE((Long) value); - break; - default: - throw new UnsupportedOperationException(); - } - } - - private void encodeBitNParameter(ByteBuf payload, Boolean bit) { - payload.writeByte(0x00); - payload.writeByte(0x00); - payload.writeByte(MSSQLDataTypeId.BITNTYPE_ID); - payload.writeByte(1); - payload.writeByte(1); - payload.writeBoolean(bit); - } - - private void encodeFloat4Parameter(ByteBuf payload, Float value) { - payload.writeByte(0x00); - payload.writeByte(0x00); - payload.writeByte(MSSQLDataTypeId.FLTNTYPE_ID); - payload.writeByte(4); - payload.writeByte(4); - payload.writeFloatLE(value); - } - - private void encodeFloat8Parameter(ByteBuf payload, Double value) { - payload.writeByte(0x00); - payload.writeByte(0x00); - payload.writeByte(MSSQLDataTypeId.FLTNTYPE_ID); - payload.writeByte(8); - payload.writeByte(8); - payload.writeDoubleLE(value); - } - - private void encodeDateNParameter(ByteBuf payload, LocalDate date) { - payload.writeByte(0x00); - payload.writeByte(0x00); - payload.writeByte(MSSQLDataTypeId.DATENTYPE_ID); - if (date == null) { - // null - payload.writeByte(0); - } else { - payload.writeByte(3); - encodeLocalDate(payload, date); - } - } - - private void encodeTimeNParameter(ByteBuf payload, LocalTime time) { - payload.writeByte(0x00); - payload.writeByte(0x00); - payload.writeByte(MSSQLDataTypeId.TIMENTYPE_ID); - - payload.writeByte(7); // scale - if (time == null) { - payload.writeByte(0); - } else { - payload.writeByte(5); // length - encodeLocalTime(payload, time); - } - } - - private void encodeDateTimeNParameter(ByteBuf payload, LocalDateTime dateTime) { - payload.writeByte(0x00); - payload.writeByte(0x00); - payload.writeByte(MSSQLDataTypeId.DATETIME2NTYPE_ID); - - payload.writeByte(7); // scale - if (dateTime == null) { - payload.writeByte(0); - } else { - payload.writeByte(8); // length - encodeLocalTime(payload, dateTime.toLocalTime()); - encodeLocalDate(payload, dateTime.toLocalDate()); - } - } - - private void encodeOffsetDateTimeNParameter(ByteBuf payload, OffsetDateTime offsetDateTime) { - payload.writeByte(0x00); - payload.writeByte(0x00); - payload.writeByte(MSSQLDataTypeId.DATETIMEOFFSETNTYPE_ID); - - payload.writeByte(7); - if (offsetDateTime == null) { - payload.writeByte(0); - } else { - payload.writeByte(10); // length - int offsetMinutes = offsetDateTime.getOffset().getTotalSeconds() / 60; - LocalDateTime localDateTime = offsetDateTime.toLocalDateTime().minusMinutes(offsetMinutes); - encodeLocalTime(payload, localDateTime.toLocalTime()); - LocalDate localDate = localDateTime.toLocalDate(); - encodeLocalDate(payload, localDate); - payload.writeShortLE(offsetMinutes); - } - } - - private void encodeLocalTime(ByteBuf payload, LocalTime localTime) { - encodeInt40(payload, localTime.toNanoOfDay() / 100); - } - - private void encodeInt40(ByteBuf buffer, long value) { - buffer.writeIntLE((int) (value % 0x100000000L)); - buffer.writeByte((int) (value / 0x100000000L)); - } - - private void encodeLocalDate(ByteBuf payload, LocalDate localDate) { - long days = ChronoUnit.DAYS.between(MSSQLDataTypeCodec.START_DATE, localDate); - payload.writeMediumLE((int) days); - } - - private void encodeDecimalParameter(ByteBuf payload, BigDecimal value) { - payload.writeByte(0x00); - payload.writeByte(0x00); - payload.writeByte(MSSQLDataTypeId.DECIMALNTYPE_ID); - - payload.writeByte(17); // maximum length - payload.writeByte(38); // maximum precision - - int sign = value.signum() < 0 ? 0 : 1; - byte[] bytes = (sign == 0 ? value.negate() : value).unscaledValue().toByteArray(); - - payload.writeByte(Math.max(0, value.scale())); - payload.writeByte(1 + bytes.length); - payload.writeByte(sign); - for (int i = bytes.length - 1; i >= 0; i--) { - payload.writeByte(bytes[i]); - } + @Override + protected TupleInternal prepexecRequestParams() { + return (TupleInternal) cmd.params(); } - private void encodeBufferParameter(ByteBuf payload, Buffer value) { - payload.writeByte(0x00); - payload.writeByte(0x00); - payload.writeByte(MSSQLDataTypeId.BIGBINARYTYPE_ID); - - payload.writeShortLE(value.length()); // max length - payload.writeShortLE(value.length()); // length - - payload.writeBytes(value.getByteBuf()); + @Override + protected TupleInternal execRequestParams() { + return (TupleInternal) cmd.params(); } } diff --git a/vertx-mssql-client/src/main/java/io/vertx/mssqlclient/impl/codec/TdsMessageEncoder.java b/vertx-mssql-client/src/main/java/io/vertx/mssqlclient/impl/codec/TdsMessageEncoder.java index 34e0a69ee..0d23a51c8 100644 --- a/vertx-mssql-client/src/main/java/io/vertx/mssqlclient/impl/codec/TdsMessageEncoder.java +++ b/vertx-mssql-client/src/main/java/io/vertx/mssqlclient/impl/codec/TdsMessageEncoder.java @@ -67,9 +67,9 @@ void write(CommandBase cmd) { } else if (cmd instanceof ExtendedQueryCommand) { ExtendedQueryCommand queryCmd = (ExtendedQueryCommand) cmd; if (queryCmd.isBatch()) { - throw new UnsupportedOperationException(); + return new ExtendedBatchQueryCommandCodec<>(queryCmd); } else { - return new ExtendedQueryCommandCodec((ExtendedQueryCommand) cmd); + return new ExtendedQueryCommandCodec<>(queryCmd); } } else if (cmd instanceof CloseStatementCommand) { return new CloseStatementCommandCodec((CloseStatementCommand) cmd); diff --git a/vertx-mssql-client/src/test/java/io/vertx/mssqlclient/tck/MSSQLPreparedBatchTest.java b/vertx-mssql-client/src/test/java/io/vertx/mssqlclient/tck/MSSQLPreparedBatchTest.java new file mode 100644 index 000000000..d3be89f73 --- /dev/null +++ b/vertx-mssql-client/src/test/java/io/vertx/mssqlclient/tck/MSSQLPreparedBatchTest.java @@ -0,0 +1,40 @@ +package io.vertx.mssqlclient.tck; + +import io.vertx.ext.unit.TestContext; +import io.vertx.ext.unit.junit.VertxUnitRunner; +import io.vertx.mssqlclient.junit.MSSQLRule; +import io.vertx.sqlclient.tck.PreparedBatchTestBase; +import org.junit.ClassRule; +import org.junit.Ignore; +import org.junit.Test; +import org.junit.runner.RunWith; + +@RunWith(VertxUnitRunner.class) +public class MSSQLPreparedBatchTest extends PreparedBatchTestBase { + @ClassRule + public static MSSQLRule rule = MSSQLRule.SHARED_INSTANCE; + + @Override + protected String statement(String... parts) { + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < parts.length; i++) { + if (i > 0) { + sb.append("@p").append((i)); + } + sb.append(parts[i]); + } + return sb.toString(); + } + + @Override + protected void initConnector() { + connector = ClientConfig.CONNECT.connect(vertx, rule.options()); + } + + @Test + @Ignore + @Override + public void testIncorrectNumBatchArguments(TestContext ctx) { + super.testIncorrectNumBatchArguments(ctx); + } +} diff --git a/vertx-mssql-client/src/test/java/io/vertx/mssqlclient/tck/MSSQLTracingTest.java b/vertx-mssql-client/src/test/java/io/vertx/mssqlclient/tck/MSSQLTracingTest.java index 6084480af..b887b3e0e 100644 --- a/vertx-mssql-client/src/test/java/io/vertx/mssqlclient/tck/MSSQLTracingTest.java +++ b/vertx-mssql-client/src/test/java/io/vertx/mssqlclient/tck/MSSQLTracingTest.java @@ -46,18 +46,4 @@ protected String statement(String... parts) { } return sb.toString(); } - - @Ignore - @Test - @Override - public void testTraceBatchQuery(TestContext ctx) { - super.testTraceBatchQuery(ctx); - } - - @Ignore - @Test - @Override - public void testTracingFailure(TestContext ctx) { - super.testTracingFailure(ctx); - } } From f04135e6a711175cdfd3690d1ca6ce4ad0529791 Mon Sep 17 00:00:00 2001 From: Thomas Segismont Date: Wed, 16 Jun 2021 17:17:15 +0200 Subject: [PATCH 12/26] Simplify data type codec maintenance (#987) Moving all encoding, decoding, and metadata parsing logic to the same class. Also, put common buffer modification methods to utility Signed-off-by: Thomas Segismont --- .../impl/codec/BinaryDataType.java | 31 - .../codec/CloseStatementCommandCodec.java | 14 +- .../mssqlclient/impl/codec/ColumnData.java | 60 +- .../mssqlclient/impl/codec/DataType.java | 863 ++++++++++++++++++ .../codec/ExtendedQueryCommandBaseCodec.java | 279 +----- .../impl/codec/MSSQLCommandCodec.java | 23 +- .../impl/codec/MSSQLDataTypeCodec.java | 339 ------- .../mssqlclient/impl/codec/MSSQLRowDesc.java | 4 +- .../impl/codec/QueryCommandBaseCodec.java | 83 +- .../impl/codec/RowResultDecoder.java | 8 +- .../impl/codec/VarBinaryDataType.java | 31 - .../impl/protocol/datatype/BitNDataType.java | 21 - .../protocol/datatype/DateTime2NDataType.java | 28 - .../datatype/DateTimeOffsetNDataType.java | 28 - .../protocol/datatype/DecimalDataType.java | 34 - .../protocol/datatype/FixedLenDataType.java | 40 - .../protocol/datatype/FloatNDataType.java | 33 - .../impl/protocol/datatype/IntNDataType.java | 50 - .../impl/protocol/datatype/MSSQLDataType.java | 50 - .../protocol/datatype/MSSQLDataTypeId.java | 66 -- .../datatype/TextWithCollationDataType.java | 28 - .../impl/protocol/datatype/TimeNDataType.java | 28 - .../mssqlclient/impl/utils/ByteBufUtils.java | 58 ++ 23 files changed, 983 insertions(+), 1216 deletions(-) delete mode 100644 vertx-mssql-client/src/main/java/io/vertx/mssqlclient/impl/codec/BinaryDataType.java create mode 100644 vertx-mssql-client/src/main/java/io/vertx/mssqlclient/impl/codec/DataType.java delete mode 100644 vertx-mssql-client/src/main/java/io/vertx/mssqlclient/impl/codec/MSSQLDataTypeCodec.java delete mode 100644 vertx-mssql-client/src/main/java/io/vertx/mssqlclient/impl/codec/VarBinaryDataType.java delete mode 100644 vertx-mssql-client/src/main/java/io/vertx/mssqlclient/impl/protocol/datatype/BitNDataType.java delete mode 100644 vertx-mssql-client/src/main/java/io/vertx/mssqlclient/impl/protocol/datatype/DateTime2NDataType.java delete mode 100644 vertx-mssql-client/src/main/java/io/vertx/mssqlclient/impl/protocol/datatype/DateTimeOffsetNDataType.java delete mode 100644 vertx-mssql-client/src/main/java/io/vertx/mssqlclient/impl/protocol/datatype/DecimalDataType.java delete mode 100644 vertx-mssql-client/src/main/java/io/vertx/mssqlclient/impl/protocol/datatype/FixedLenDataType.java delete mode 100644 vertx-mssql-client/src/main/java/io/vertx/mssqlclient/impl/protocol/datatype/FloatNDataType.java delete mode 100644 vertx-mssql-client/src/main/java/io/vertx/mssqlclient/impl/protocol/datatype/IntNDataType.java delete mode 100644 vertx-mssql-client/src/main/java/io/vertx/mssqlclient/impl/protocol/datatype/MSSQLDataType.java delete mode 100644 vertx-mssql-client/src/main/java/io/vertx/mssqlclient/impl/protocol/datatype/MSSQLDataTypeId.java delete mode 100644 vertx-mssql-client/src/main/java/io/vertx/mssqlclient/impl/protocol/datatype/TextWithCollationDataType.java delete mode 100644 vertx-mssql-client/src/main/java/io/vertx/mssqlclient/impl/protocol/datatype/TimeNDataType.java create mode 100644 vertx-mssql-client/src/main/java/io/vertx/mssqlclient/impl/utils/ByteBufUtils.java diff --git a/vertx-mssql-client/src/main/java/io/vertx/mssqlclient/impl/codec/BinaryDataType.java b/vertx-mssql-client/src/main/java/io/vertx/mssqlclient/impl/codec/BinaryDataType.java deleted file mode 100644 index 2917cbbb5..000000000 --- a/vertx-mssql-client/src/main/java/io/vertx/mssqlclient/impl/codec/BinaryDataType.java +++ /dev/null @@ -1,31 +0,0 @@ -/* - * Copyright (c) 2011-2021 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.mssqlclient.impl.codec; - -import io.vertx.core.buffer.Buffer; -import io.vertx.mssqlclient.impl.protocol.datatype.MSSQLDataType; - -import java.sql.JDBCType; - -public class BinaryDataType extends MSSQLDataType { - - private final int length; - - public BinaryDataType(int id, int length) { - super(id, Buffer.class, JDBCType.BINARY); - this.length = length; - } - - public int getLength() { - return length; - } -} diff --git a/vertx-mssql-client/src/main/java/io/vertx/mssqlclient/impl/codec/CloseStatementCommandCodec.java b/vertx-mssql-client/src/main/java/io/vertx/mssqlclient/impl/codec/CloseStatementCommandCodec.java index 10b3d8569..29f081f81 100644 --- a/vertx-mssql-client/src/main/java/io/vertx/mssqlclient/impl/codec/CloseStatementCommandCodec.java +++ b/vertx-mssql-client/src/main/java/io/vertx/mssqlclient/impl/codec/CloseStatementCommandCodec.java @@ -17,11 +17,12 @@ import io.vertx.mssqlclient.impl.protocol.MessageType; import io.vertx.mssqlclient.impl.protocol.TdsMessage; import io.vertx.mssqlclient.impl.protocol.client.rpc.ProcId; -import io.vertx.mssqlclient.impl.protocol.datatype.MSSQLDataTypeId; import io.vertx.mssqlclient.impl.protocol.token.DataPacketStreamTokenType; import io.vertx.sqlclient.impl.command.CloseStatementCommand; import io.vertx.sqlclient.impl.command.CommandResponse; +import static io.vertx.mssqlclient.impl.codec.DataType.INTN; + class CloseStatementCommandCodec extends MSSQLCommandCodec { CloseStatementCommandCodec(CloseStatementCommand cmd) { @@ -93,7 +94,7 @@ private void sendUnprepareRequest() { // Option flags packet.writeShortLE(0x0000); - encodeIntNParameter(packet, ((MSSQLPreparedStatement) cmd.statement()).handle); + INTN.encodeParam(packet, null, false, ((MSSQLPreparedStatement) cmd.statement()).handle); int packetLen = packet.writerIndex() - packetLenIdx + 2; packet.setShort(packetLenIdx, packetLen); @@ -107,13 +108,4 @@ protected void encodeTransactionDescriptor(ByteBuf payload, long transactionDesc payload.writeLongLE(transactionDescriptor); payload.writeIntLE(outstandingRequestCount); } - - private void encodeIntNParameter(ByteBuf payload, Object value) { - payload.writeByte(0x00); - payload.writeByte(0x00); - payload.writeByte(MSSQLDataTypeId.INTNTYPE_ID); - payload.writeByte(4); - payload.writeByte(4); - payload.writeIntLE((Integer) value); - } } diff --git a/vertx-mssql-client/src/main/java/io/vertx/mssqlclient/impl/codec/ColumnData.java b/vertx-mssql-client/src/main/java/io/vertx/mssqlclient/impl/codec/ColumnData.java index 9442ebf07..604b8d5e7 100644 --- a/vertx-mssql-client/src/main/java/io/vertx/mssqlclient/impl/codec/ColumnData.java +++ b/vertx-mssql-client/src/main/java/io/vertx/mssqlclient/impl/codec/ColumnData.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2011-2019 Contributors to the Eclipse Foundation + * Copyright (c) 2011-2021 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 @@ -11,53 +11,33 @@ package io.vertx.mssqlclient.impl.codec; -import io.vertx.mssqlclient.impl.protocol.datatype.MSSQLDataType; import io.vertx.sqlclient.desc.ColumnDescriptor; import java.sql.JDBCType; public final class ColumnData implements ColumnDescriptor { - /* - Protocol reference: https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-tds/58880b9f-381c-43b2-bf8b-0727a98c4f4c - */ - private final long usertype; - private final int flags; - private final MSSQLDataType dataType; - private final String colName; - // CryptoMetaData support? - String tableName; + private final String name; + private final DataType dataType; + private final DataType.Metadata metadata; - public ColumnData(long usertype, int flags, MSSQLDataType dataType, String colName) { - this.usertype = usertype; - this.flags = flags; + public ColumnData(String name, DataType dataType, DataType.Metadata metadata) { this.dataType = dataType; - this.colName = colName; + this.name = name; + this.metadata = metadata; } - public long usertype() { - return usertype; - } - - public int flags() { - return flags; - } - - public MSSQLDataType dataType() { + public DataType dataType() { return dataType; } - public String colName() { - return colName; - } - - public String tableName() { - return tableName; + public DataType.Metadata metadata() { + return metadata; } @Override public String name() { - return colName; + return name; } @Override @@ -67,22 +47,6 @@ public boolean isArray() { @Override public JDBCType jdbcType() { - return dataType.jdbcType(); - } - - public static final class Flags { - public static final int NULLABLE = 0x0001; - public static final int CASESEN = 0x0002; - public static final int UPDATEABLE = 0x0004; - public static final int IDENTITY = 0x0010; - public static final int COMPUTED = 0x0020; - // 2-BIT RESERVED for ODBC - public static final int FIXED_LEN_CLR_TYPE = 0x0100; - // 1-BIT RESERVED - public static final int SPARSE_COLUMN_SET = 0x0400; - public static final int ENCRYPTED = 0x0800; - public static final int HIDDEN = 0x2000; - public static final int KEY = 0x4000; - public static final int NULLABLE_UNKNOWN = 0x8000; + return dataType.jdbcType(metadata); } } 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 new file mode 100644 index 000000000..8747c6e7c --- /dev/null +++ b/vertx-mssql-client/src/main/java/io/vertx/mssqlclient/impl/codec/DataType.java @@ -0,0 +1,863 @@ +/* + * Copyright (c) 2011-2021 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.mssqlclient.impl.codec; + +import io.netty.buffer.ByteBuf; +import io.netty.buffer.Unpooled; +import io.netty.util.collection.IntObjectHashMap; +import io.netty.util.collection.IntObjectMap; +import io.vertx.core.buffer.Buffer; + +import java.math.BigDecimal; +import java.math.BigInteger; +import java.nio.charset.StandardCharsets; +import java.sql.JDBCType; +import java.time.*; +import java.time.temporal.ChronoUnit; +import java.util.HashMap; +import java.util.Map; + +import static io.vertx.mssqlclient.impl.utils.ByteBufUtils.*; +import static java.util.concurrent.TimeUnit.MILLISECONDS; +import static java.util.concurrent.TimeUnit.NANOSECONDS; + +public enum DataType { + + // Zero-Length Data Types https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-tds/bc91c82f-8ee0-4256-98d9-c800bf9ae33b + NULL(0x1F) { + @Override + public Metadata decodeMetadata(ByteBuf byteBuf) { + return null; + } + + @Override + public String paramDefinition(Object value) { + return "nvarchar(4000)"; + } + + @Override + public void encodeParam(ByteBuf byteBuf, String name, boolean out, Object value) { + writeParamDescription(byteBuf, name, out, id); + } + }, + + // Fixed-Length Data Types https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-tds/859eb3d2-80d3-40f6-a637-414552c9c552 + INT1(0x30) { + @Override + public Metadata decodeMetadata(ByteBuf byteBuf) { + return null; + } + + @Override + public JDBCType jdbcType(Metadata metadata) { + return JDBCType.TINYINT; + } + + @Override + public Object decodeValue(ByteBuf byteBuf, Metadata metadata) { + return byteBuf.readUnsignedByte(); + } + }, + BIT(0x32) { + @Override + public Metadata decodeMetadata(ByteBuf byteBuf) { + return null; + } + + @Override + public JDBCType jdbcType(Metadata metadata) { + return JDBCType.BOOLEAN; + } + + @Override + public Object decodeValue(ByteBuf byteBuf, Metadata metadata) { + return byteBuf.readBoolean(); + } + }, + INT2(0x34) { + @Override + public Metadata decodeMetadata(ByteBuf byteBuf) { + return null; + } + + @Override + public JDBCType jdbcType(Metadata metadata) { + return JDBCType.SMALLINT; + } + + @Override + public Object decodeValue(ByteBuf byteBuf, Metadata metadata) { + return byteBuf.readShortLE(); + } + }, + INT4(0x38) { + @Override + public Metadata decodeMetadata(ByteBuf byteBuf) { + return null; + } + + @Override + public JDBCType jdbcType(Metadata metadata) { + return JDBCType.INTEGER; + } + + @Override + public Object decodeValue(ByteBuf byteBuf, Metadata metadata) { + return byteBuf.readIntLE(); + } + }, + DATETIM4(0x3A), + FLT4(0x3B) { + @Override + public Metadata decodeMetadata(ByteBuf byteBuf) { + return null; + } + + @Override + public JDBCType jdbcType(Metadata metadata) { + return JDBCType.REAL; + } + + @Override + public Object decodeValue(ByteBuf byteBuf, Metadata metadata) { + return byteBuf.readFloatLE(); + } + }, + MONEY(0x3C), + DATETIME(0x3D) { + @Override + public Metadata decodeMetadata(ByteBuf byteBuf) { + return null; + } + + @Override + public JDBCType jdbcType(Metadata metadata) { + return JDBCType.TIMESTAMP; + } + + @Override + public Object decodeValue(ByteBuf byteBuf, Metadata metadata) { + LocalDate localDate = START_DATE_DATETIME.plus(byteBuf.readIntLE(), ChronoUnit.DAYS); + long nanoOfDay = NANOSECONDS.convert(Math.round(byteBuf.readIntLE() * (3 + 1D / 3)), MILLISECONDS); + LocalTime localTime = LocalTime.ofNanoOfDay(nanoOfDay); + return LocalDateTime.of(localDate, localTime); + } + }, + FLT8(0x3E) { + @Override + public Metadata decodeMetadata(ByteBuf byteBuf) { + return null; + } + + @Override + public JDBCType jdbcType(Metadata metadata) { + return JDBCType.DOUBLE; + } + + @Override + public Object decodeValue(ByteBuf byteBuf, Metadata metadata) { + return byteBuf.readDoubleLE(); + } + }, + MONEY4(0x7A), + INT8(0x7F) { + @Override + public Metadata decodeMetadata(ByteBuf byteBuf) { + return null; + } + + @Override + public JDBCType jdbcType(Metadata metadata) { + return JDBCType.BIGINT; + } + + @Override + public Object decodeValue(ByteBuf byteBuf, Metadata metadata) { + return byteBuf.readLongLE(); + } + }, + DECIMAL(0x37), + NUMERIC(0x3F), + + // Variable-Length Data Types https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-tds/ce3183a6-9d89-47e8-a02f-de5a1a1303de + GUID(0x24), + INTN(0x26) { + @Override + public Metadata decodeMetadata(ByteBuf byteBuf) { + Metadata metadata = new Metadata(); + metadata.length = byteBuf.readByte(); + return metadata; + } + + @Override + public JDBCType jdbcType(Metadata metadata) { + if (metadata.length == 1) return JDBCType.TINYINT; + if (metadata.length == 2) return JDBCType.SMALLINT; + if (metadata.length == 4) return JDBCType.INTEGER; + if (metadata.length == 8) return JDBCType.BIGINT; + throw new IllegalArgumentException("Invalid length: " + metadata.length); + } + + @Override + public Object decodeValue(ByteBuf byteBuf, Metadata metadata) { + int length = byteBuf.readByte(); + if (length == 0) return null; + if (length == 1) return byteBuf.readUnsignedByte(); + if (length == 2) return byteBuf.readShortLE(); + if (length == 4) return byteBuf.readIntLE(); + if (length == 8) return byteBuf.readLongLE(); + throw new IllegalArgumentException("Invalid length: " + length); + } + + @Override + public String paramDefinition(Object value) { + return "bigint"; + } + + @Override + public void encodeParam(ByteBuf byteBuf, String name, boolean out, Object value) { + writeParamDescription(byteBuf, name, out, id); + if (value instanceof Byte) { + Byte bValue = (Byte) value; + byteBuf.writeByte(1); // max length + byteBuf.writeByte(1); // actual length + byteBuf.writeByte(bValue); + } else if (value instanceof Short) { + Short sValue = (Short) value; + byteBuf.writeByte(2); // max length + byteBuf.writeByte(2); // actual length + byteBuf.writeShortLE(sValue); + } else if (value instanceof Integer) { + Integer iValue = (Integer) value; + byteBuf.writeByte(4); // max length + byteBuf.writeByte(4); // actual length + byteBuf.writeIntLE(iValue); + } else if (value instanceof Long) { + Long lValue = (Long) value; + byteBuf.writeByte(8); // max length + byteBuf.writeByte(8); // actual length + byteBuf.writeLongLE(lValue); + } else throw new IllegalArgumentException(value.getClass().getName()); + } + }, + BITN(0x68) { + @Override + public Metadata decodeMetadata(ByteBuf byteBuf) { + Metadata metadata = new Metadata(); + metadata.length = byteBuf.readByte(); + return metadata; + } + + @Override + public JDBCType jdbcType(Metadata metadata) { + return JDBCType.BOOLEAN; + } + + @Override + public Object decodeValue(ByteBuf byteBuf, Metadata metadata) { + int length = byteBuf.readByte(); + if (length == 0) return null; + if (length == 1) return byteBuf.readBoolean(); + throw new IllegalArgumentException("Invalid length: " + length); + } + + @Override + public String paramDefinition(Object value) { + return "bit"; + } + + @Override + public void encodeParam(ByteBuf byteBuf, String name, boolean out, Object value) { + writeParamDescription(byteBuf, name, out, BITN.id); + byteBuf.writeByte(1); // max length + byteBuf.writeByte(1); // actual length + byteBuf.writeBoolean((Boolean) value); + } + }, + DECIMALN(0x6A) { + @Override + public Metadata decodeMetadata(ByteBuf byteBuf) { + Metadata metadata = new Metadata(); + metadata.length = byteBuf.readUnsignedByte(); + metadata.precision = byteBuf.readByte(); + metadata.scale = byteBuf.readByte(); + return metadata; + } + + @Override + public JDBCType jdbcType(Metadata metadata) { + return JDBCType.DECIMAL; + } + + @Override + public Object decodeValue(ByteBuf byteBuf, Metadata metadata) { + short length = byteBuf.readUnsignedByte(); + if (length == 0) return null; + byte sign = byteBuf.readByte(); + byte[] bytes = new byte[length - 1]; + for (int i = 0; i < bytes.length; i++) bytes[i] = byteBuf.getByte(byteBuf.readerIndex() + bytes.length - 1 - i); + byteBuf.skipBytes(bytes.length); + BigInteger bigInteger = new BigInteger(bytes); + BigDecimal bigDecimal = new BigDecimal(bigInteger, metadata.scale); + return sign == 0 ? bigDecimal.negate() : bigDecimal; + } + + @Override + public String paramDefinition(Object value) { + return "numeric(38," + (value == null ? 0 : Math.max(0, ((BigDecimal) value).scale())) + ")"; + } + + @Override + public void encodeParam(ByteBuf byteBuf, String name, boolean out, Object value) { + BigDecimal bigDecimal = (BigDecimal) value; + writeParamDescription(byteBuf, name, out, id); + byteBuf.writeByte(17); // maximum length + byteBuf.writeByte(38); // maximum precision + int sign = bigDecimal.signum() < 0 ? 0 : 1; + byte[] bytes = (sign == 0 ? bigDecimal.negate() : bigDecimal).unscaledValue().toByteArray(); + byteBuf.writeByte(Math.max(0, bigDecimal.scale())); + byteBuf.writeByte(1 + bytes.length); + byteBuf.writeByte(sign); + for (int i = bytes.length - 1; i >= 0; i--) byteBuf.writeByte(bytes[i]); + } + }, + NUMERICN(0x6C) { + @Override + public Metadata decodeMetadata(ByteBuf byteBuf) { + return DECIMALN.decodeMetadata(byteBuf); + } + + @Override + public JDBCType jdbcType(Metadata metadata) { + return JDBCType.DECIMAL; + } + + @Override + public Object decodeValue(ByteBuf byteBuf, Metadata metadata) { + return DECIMALN.decodeValue(byteBuf, metadata); + } + }, + FLTN(0x6D) { + @Override + public Metadata decodeMetadata(ByteBuf byteBuf) { + Metadata metadata = new Metadata(); + metadata.length = byteBuf.readByte(); + return metadata; + } + + @Override + public JDBCType jdbcType(Metadata metadata) { + if (metadata.length == 4) return JDBCType.REAL; + if (metadata.length == 8) return JDBCType.DOUBLE; + throw new IllegalArgumentException("Invalid length: " + metadata.length); + } + + @Override + public Object decodeValue(ByteBuf byteBuf, Metadata metadata) { + int length = byteBuf.readByte(); + if (length == 0) return null; + if (length == 4) return byteBuf.readFloatLE(); + if (length == 8) return byteBuf.readDoubleLE(); + throw new IllegalArgumentException("Invalid length: " + length); + } + + @Override + public String paramDefinition(Object value) { + return "float"; + } + + @Override + public void encodeParam(ByteBuf byteBuf, String name, boolean out, Object value) { + writeParamDescription(byteBuf, name, out, id); + if (value instanceof Float) { + Float fValue = (Float) value; + byteBuf.writeByte(4); // max length + byteBuf.writeByte(4); // actual length + byteBuf.writeFloatLE(fValue); + } else if (value instanceof Double) { + Double dValue = (Double) value; + byteBuf.writeByte(8); // max length + byteBuf.writeByte(8); // actual length + byteBuf.writeDoubleLE(dValue); + } else throw new IllegalArgumentException(); + } + }, + MONEYN(0x6E), + DATETIMN(0x6F), + DATEN(0x28) { + @Override + public Metadata decodeMetadata(ByteBuf byteBuf) { + return null; + } + + @Override + public JDBCType jdbcType(Metadata metadata) { + return JDBCType.DATE; + } + + @Override + public Object decodeValue(ByteBuf byteBuf, Metadata metadata) { + int length = byteBuf.readByte(); + if (length == 0) return null; + return decodeLocalDate(byteBuf, length); + } + + @Override + public String paramDefinition(Object value) { + return "date"; + } + + @Override + public void encodeParam(ByteBuf byteBuf, String name, boolean out, Object value) { + writeParamDescription(byteBuf, name, out, id); + byteBuf.writeByte(3); + byteBuf.writeMediumLE(daysFromStartDate((LocalDate) value)); + } + }, + TIMEN(0x29) { + @Override + public Metadata decodeMetadata(ByteBuf byteBuf) { + Metadata metadata = new Metadata(); + metadata.scale = byteBuf.readByte(); + return metadata; + } + + @Override + public JDBCType jdbcType(Metadata metadata) { + return JDBCType.TIME; + } + + @Override + public String paramDefinition(Object value) { + return "time"; + } + + @Override + public Object decodeValue(ByteBuf byteBuf, Metadata metadata) { + int length = byteBuf.readByte(); + if (length == 0) return null; + return decodeLocalTime(byteBuf, length, metadata.scale); + } + + @Override + public void encodeParam(ByteBuf byteBuf, String name, boolean out, Object value) { + writeParamDescription(byteBuf, name, out, id); + byteBuf.writeByte(7); // scale + byteBuf.writeByte(5); // length + writeUnsignedInt40LE(byteBuf, hundredsOfNanos((LocalTime) value)); + } + }, + DATETIME2N(0x2A) { + @Override + public Metadata decodeMetadata(ByteBuf byteBuf) { + Metadata metadata = new Metadata(); + metadata.scale = byteBuf.readByte(); + return metadata; + } + + @Override + public JDBCType jdbcType(Metadata metadata) { + return JDBCType.TIMESTAMP; + } + + @Override + public Object decodeValue(ByteBuf byteBuf, Metadata metadata) { + byte length = byteBuf.readByte(); + if (length == 0) return null; + LocalTime localTime = decodeLocalTime(byteBuf, length - 3, metadata.scale); + LocalDate localDate = decodeLocalDate(byteBuf, 3); + return LocalDateTime.of(localDate, localTime); + } + + @Override + public String paramDefinition(Object value) { + return "datetime2"; + } + + @Override + public void encodeParam(ByteBuf byteBuf, String name, boolean out, Object value) { + LocalDateTime localDateTime = (LocalDateTime) value; + writeParamDescription(byteBuf, name, out, id); + byteBuf.writeByte(7); // scale + byteBuf.writeByte(8); // length + writeUnsignedInt40LE(byteBuf, hundredsOfNanos(localDateTime.toLocalTime())); + byteBuf.writeMediumLE(daysFromStartDate(localDateTime.toLocalDate())); + } + }, + DATETIMEOFFSETN(0x2B) { + @Override + public Metadata decodeMetadata(ByteBuf byteBuf) { + Metadata metadata = new Metadata(); + metadata.scale = byteBuf.readByte(); + return metadata; + } + + @Override + public JDBCType jdbcType(Metadata metadata) { + return JDBCType.TIMESTAMP_WITH_TIMEZONE; + } + + @Override + public Object decodeValue(ByteBuf byteBuf, Metadata metadata) { + byte length = byteBuf.readByte(); + if (length == 0) return null; + LocalTime localTime = decodeLocalTime(byteBuf, length - 5, metadata.scale); + LocalDate localDate = decodeLocalDate(byteBuf, 3); + short minutes = byteBuf.readShortLE(); + return LocalDateTime.of(localDate, localTime).plusMinutes(minutes).atOffset(ZoneOffset.ofTotalSeconds(60 * minutes)); + } + + @Override + public String paramDefinition(Object value) { + return "datetimeoffset"; + } + + @Override + public void encodeParam(ByteBuf byteBuf, String name, boolean out, Object value) { + OffsetDateTime offsetDateTime = (OffsetDateTime) value; + writeParamDescription(byteBuf, name, out, id); + byteBuf.writeByte(7); // scale + byteBuf.writeByte(10); // length + int offsetMinutes = offsetDateTime.getOffset().getTotalSeconds() / 60; + LocalDateTime localDateTime = offsetDateTime.toLocalDateTime().minusMinutes(offsetMinutes); + writeUnsignedInt40LE(byteBuf, hundredsOfNanos(localDateTime.toLocalTime())); + byteBuf.writeMediumLE(daysFromStartDate(localDateTime.toLocalDate())); + byteBuf.writeShortLE(offsetMinutes); + } + }, + CHAR(0x2F), + VARCHAR(0x27), + BINARY(0x2D) { + @Override + public Metadata decodeMetadata(ByteBuf byteBuf) { + return BIGVARBINARY.decodeMetadata(byteBuf); + } + + @Override + public JDBCType jdbcType(Metadata metadata) { + return JDBCType.BINARY; + } + + @Override + public Object decodeValue(ByteBuf byteBuf, Metadata metadata) { + return BIGVARBINARY.decodeValue(byteBuf, metadata); + } + }, + VARBINARY(0x25) { + @Override + public Metadata decodeMetadata(ByteBuf byteBuf) { + return BIGVARBINARY.decodeMetadata(byteBuf); + } + + @Override + public JDBCType jdbcType(Metadata metadata) { + return JDBCType.VARBINARY; + } + + @Override + public Object decodeValue(ByteBuf byteBuf, Metadata metadata) { + return BIGVARBINARY.decodeValue(byteBuf, metadata); + } + }, + + BIGVARBINARY(0xA5) { + @Override + public Metadata decodeMetadata(ByteBuf byteBuf) { + Metadata metadata = new Metadata(); + metadata.length = byteBuf.readUnsignedShortLE(); + return metadata; + } + + @Override + public JDBCType jdbcType(Metadata metadata) { + return JDBCType.LONGVARBINARY; + } + + @Override + public Object decodeValue(ByteBuf byteBuf, Metadata metadata) { + int length = byteBuf.readUnsignedShortLE(); + ByteBuf res = Unpooled.buffer(length); + byteBuf.readBytes(res, 0, length); + res.writerIndex(length); + return Buffer.buffer(res); + } + + @Override + public String paramDefinition(Object value) { + return "binary(" + (value == null ? 1 : ((Buffer) value).length()) + ")"; + } + + @Override + public void encodeParam(ByteBuf byteBuf, String name, boolean out, Object value) { + Buffer buffer = (Buffer) value; + writeParamDescription(byteBuf, name, out, id); + byteBuf.writeShortLE(buffer.length()); // max length + byteBuf.writeShortLE(buffer.length()); // length + byteBuf.writeBytes(buffer.getByteBuf()); + } + }, + BIGVARCHAR(0xA7) { + @Override + public Metadata decodeMetadata(ByteBuf byteBuf) { + Metadata metadata = new Metadata(); + metadata.length = byteBuf.readUnsignedShortLE(); + byteBuf.skipBytes(2); // skip collate codepage + byteBuf.skipBytes(2); // skip collate flags + byteBuf.skipBytes(1); // skip collate charset id + return metadata; + } + + @Override + public JDBCType jdbcType(Metadata metadata) { + return JDBCType.VARCHAR; + } + + @Override + public Object decodeValue(ByteBuf byteBuf, Metadata metadata) { + int length = byteBuf.readUnsignedShortLE(); + // CHARBIN_NULL + if (length == 65535) return null; + return byteBuf.readCharSequence(length, StandardCharsets.UTF_8); + } + }, + BIGBINARY(0xAD) { + @Override + public Metadata decodeMetadata(ByteBuf byteBuf) { + return BIGVARBINARY.decodeMetadata(byteBuf); + } + + @Override + public JDBCType jdbcType(Metadata metadata) { + return JDBCType.BINARY; + } + + @Override + public Object decodeValue(ByteBuf byteBuf, Metadata metadata) { + return BIGVARBINARY.decodeValue(byteBuf, metadata); + } + }, + BIGCHAR(0xAF) { + @Override + public Metadata decodeMetadata(ByteBuf byteBuf) { + return BIGVARCHAR.decodeMetadata(byteBuf); + } + + @Override + public JDBCType jdbcType(Metadata metadata) { + return BIGVARCHAR.jdbcType(metadata); + } + + @Override + public Object decodeValue(ByteBuf byteBuf, Metadata metadata) { + return BIGVARCHAR.decodeValue(byteBuf, metadata); + } + }, + NVARCHAR(0xE7) { + @Override + public Metadata decodeMetadata(ByteBuf byteBuf) { + Metadata metadata = new Metadata(); + metadata.length = byteBuf.readUnsignedShortLE(); + byteBuf.skipBytes(2); // skip collate codepage + byteBuf.skipBytes(2); // skip collate flags + byteBuf.skipBytes(1); // skip collate charset id + return metadata; + } + + @Override + public JDBCType jdbcType(Metadata metadata) { + return JDBCType.VARCHAR; + } + + @Override + public Object decodeValue(ByteBuf byteBuf, Metadata metadata) { + int length = byteBuf.readUnsignedShortLE(); + // CHARBIN_NULL + if (length == 65535) return null; + return byteBuf.readCharSequence(length, StandardCharsets.UTF_16LE); + } + + @Override + public String paramDefinition(Object value) { + return "nvarchar(4000)"; + } + + @Override + public void encodeParam(ByteBuf byteBuf, String name, boolean out, Object value) { + writeParamDescription(byteBuf, name, out, id); + byteBuf.writeShortLE(8000); // maximal length + byteBuf.writeByte(0x09); + byteBuf.writeByte(0x04); + byteBuf.writeByte(0xd0); + byteBuf.writeByte(0x00); + byteBuf.writeByte(0x34); // Collation for param definitions TODO always this value? + writeUnsignedShortLengthString(byteBuf, value instanceof Enum ? ((Enum) value).name() : value.toString()); + } + }, + NCHAR(0xEF) { + @Override + public Metadata decodeMetadata(ByteBuf byteBuf) { + return NVARCHAR.decodeMetadata(byteBuf); + } + + @Override + public JDBCType jdbcType(Metadata metadata) { + return NVARCHAR.jdbcType(metadata); + } + + @Override + public Object decodeValue(ByteBuf byteBuf, Metadata metadata) { + return NVARCHAR.decodeValue(byteBuf, metadata); + } + }, + XML(0xF1), + UDT(0xF0), + + TEXT(0x23), + IMAGE(0x22), + NTEXT(0x63), + SSVARIANT(0x62); + + public final int id; + + DataType(int id) { + this.id = id; + } + + public static class Metadata { + private int length; + private byte precision; + private byte scale; + + public int length() { + return length; + } + + public byte precision() { + return precision; + } + + public byte scale() { + return scale; + } + + @Override + public String toString() { + return "Metadata{" + "length=" + length + ", precision=" + precision + ", scale=" + scale + '}'; + } + } + + public Metadata decodeMetadata(ByteBuf byteBuf) { + throw new UnsupportedOperationException("Unable to decode metadata for " + name()); + } + + public JDBCType jdbcType(Metadata metadata) { + throw new UnsupportedOperationException("Unable to determine jdbc type for " + name()); + } + + public Object decodeValue(ByteBuf byteBuf, Metadata metadata) { + throw new UnsupportedOperationException("Unable to decode value for " + name()); + } + + public String paramDefinition(Object value) { + throw new UnsupportedOperationException("Unable to generate param definition for " + name()); + } + + public void encodeParam(ByteBuf byteBuf, String name, boolean out, Object value) { + throw new UnsupportedOperationException("Unable to encode param for " + name()); + } + + private static final LocalDate START_DATE = LocalDate.of(1, 1, 1); + private static final LocalDate START_DATE_DATETIME = LocalDate.of(1900, 1, 1); + private static final IntObjectMap typesById; + private static final Map, DataType> typesByValueClass; + + static { + typesById = new IntObjectHashMap<>(values().length); + for (DataType dataType : values()) typesById.put(dataType.id, dataType); + typesByValueClass = new HashMap<>(); + typesByValueClass.put(Byte.class, INTN); + typesByValueClass.put(Short.class, INTN); + typesByValueClass.put(Integer.class, INTN); + typesByValueClass.put(Long.class, INTN); + typesByValueClass.put(Boolean.class, BITN); + typesByValueClass.put(Float.class, FLTN); + typesByValueClass.put(Double.class, FLTN); + typesByValueClass.put(BigDecimal.class, DECIMALN); + typesByValueClass.put(String.class, NVARCHAR); + typesByValueClass.put(LocalDate.class, DATEN); + typesByValueClass.put(LocalTime.class, TIMEN); + typesByValueClass.put(LocalDateTime.class, DATETIME2N); + typesByValueClass.put(OffsetDateTime.class, DATETIMEOFFSETN); + typesByValueClass.put(Buffer.class, BIGVARBINARY); + } + + public static DataType forId(int id) { + DataType dataType = typesById.get(id); + if (dataType == null) throw new IllegalArgumentException("Unknown data type: " + id); + return dataType; + } + + public static DataType forValueClass(Class valueClass) { + DataType dataType; + if (Buffer.class.isAssignableFrom(valueClass)) { + dataType = typesByValueClass.get(Buffer.class); + } else if (valueClass.isEnum()) { + dataType = typesByValueClass.get(String.class); + } else { + dataType = typesByValueClass.get(valueClass); + } + if (dataType == null) { + throw new IllegalArgumentException("Unsupported value class: " + valueClass); + } + return dataType; + } + + private static void writeParamDescription(ByteBuf buffer, String name, boolean out, int id) { + writeByteLengthString(buffer, name); + buffer.writeByte(out ? 1 : 0); + buffer.writeByte(id); + } + + private static LocalDate decodeLocalDate(ByteBuf byteBuf, int length) { + int days; + if (length == 3) { + days = byteBuf.readUnsignedMediumLE(); + return START_DATE.plus(days, ChronoUnit.DAYS); + } + throw new IllegalArgumentException("Invalid length: " + length); + } + + private static LocalTime decodeLocalTime(ByteBuf byteBuf, int length, int scale) { + long hundredNanos; + if (length == 3) { + hundredNanos = byteBuf.readUnsignedMediumLE(); + } else if (length == 4) { + hundredNanos = byteBuf.readUnsignedIntLE(); + } else if (length == 5) { + hundredNanos = readUnsignedInt40LE(byteBuf); + } else { + throw new IllegalArgumentException("Invalid length: " + length); + } + for (int i = scale; i < 7; i++) { + hundredNanos *= 10; + } + return LocalTime.ofNanoOfDay(100 * hundredNanos); + } + + private static int daysFromStartDate(LocalDate localDate) { + return (int) ChronoUnit.DAYS.between(START_DATE, localDate); + } + + private static long hundredsOfNanos(LocalTime localTime) { + return localTime.toNanoOfDay() / 100; + } +} diff --git a/vertx-mssql-client/src/main/java/io/vertx/mssqlclient/impl/codec/ExtendedQueryCommandBaseCodec.java b/vertx-mssql-client/src/main/java/io/vertx/mssqlclient/impl/codec/ExtendedQueryCommandBaseCodec.java index 7386d6871..bf331eef1 100644 --- a/vertx-mssql-client/src/main/java/io/vertx/mssqlclient/impl/codec/ExtendedQueryCommandBaseCodec.java +++ b/vertx-mssql-client/src/main/java/io/vertx/mssqlclient/impl/codec/ExtendedQueryCommandBaseCodec.java @@ -13,25 +13,17 @@ import io.netty.buffer.ByteBuf; import io.netty.channel.ChannelHandlerContext; -import io.vertx.core.buffer.Buffer; import io.vertx.mssqlclient.impl.protocol.MessageStatus; import io.vertx.mssqlclient.impl.protocol.MessageType; import io.vertx.mssqlclient.impl.protocol.TdsMessage; import io.vertx.mssqlclient.impl.protocol.client.rpc.ProcId; -import io.vertx.mssqlclient.impl.protocol.datatype.MSSQLDataTypeId; import io.vertx.mssqlclient.impl.protocol.server.DoneToken; import io.vertx.mssqlclient.impl.protocol.token.DataPacketStreamTokenType; +import io.vertx.sqlclient.data.NullValue; import io.vertx.sqlclient.impl.TupleInternal; import io.vertx.sqlclient.impl.command.ExtendedQueryCommand; -import java.math.BigDecimal; -import java.time.LocalDate; -import java.time.LocalDateTime; -import java.time.LocalTime; -import java.time.OffsetDateTime; -import java.time.temporal.ChronoUnit; - -import static io.vertx.mssqlclient.impl.codec.MSSQLDataTypeCodec.inferenceParamDefinitionByValueType; +import static io.vertx.mssqlclient.impl.codec.DataType.*; abstract class ExtendedQueryCommandBaseCodec extends QueryCommandBaseCodec> { @@ -159,27 +151,20 @@ private void sendPrepexecRequest() { // Parameter // OUT Parameter - packet.writeByte(0x00); - packet.writeByte(0x01); // By reference - packet.writeByte(MSSQLDataTypeId.INTNTYPE_ID); - packet.writeByte(0x04); - packet.writeByte(0x04); MSSQLPreparedStatement ps = (MSSQLPreparedStatement) cmd.ps; - packet.writeIntLE(ps.handle); + INTN.encodeParam(packet, null, true, ps.handle); TupleInternal params = prepexecRequestParams(); // Param definitions String paramDefinitions = parseParamDefinitions(params); - encodeNVarcharParameter(packet, paramDefinitions); + NVARCHAR.encodeParam(packet, null, false, paramDefinitions); // SQL text - encodeNVarcharParameter(packet, cmd.sql()); + NVARCHAR.encodeParam(packet, null, false, cmd.sql()); // Param values - for (int i = 0; i < params.size(); i++) { - encodeParamValue(packet, params.getValue(i)); - } + encodeParams(packet, params); int packetLen = packet.writerIndex() - packetLenIdx + 2; packet.setShort(packetLenIdx, packetLen); @@ -228,21 +213,11 @@ protected void writeRpcRequestBatch(ByteBuf packet) { // Parameter // OUT Parameter - packet.writeByte(0x00); - packet.writeByte(0x00); - packet.writeByte(MSSQLDataTypeId.INTNTYPE_ID); - packet.writeByte(0x04); // Max length - packet.writeByte(0x04); // Length MSSQLPreparedStatement ps = (MSSQLPreparedStatement) cmd.ps; - packet.writeIntLE(ps.handle); - - TupleInternal params = execRequestParams(); + INTN.encodeParam(packet, null, true, ps.handle); // Param values - for (int i = 0; i < params.size(); i++) { - encodeParamValue(packet, params.getValue(i)); - } - + encodeParams(packet, execRequestParams()); } protected abstract TupleInternal execRequestParams(); @@ -250,226 +225,36 @@ protected void writeRpcRequestBatch(ByteBuf packet) { private String parseParamDefinitions(TupleInternal params) { StringBuilder stringBuilder = new StringBuilder(); for (int i = 0; i < params.size(); i++) { - Object param = params.getValueInternal(i); - stringBuilder.append("@P").append(i + 1).append(" "); - stringBuilder.append(inferenceParamDefinitionByValueType(param)); - if (i != params.size() - 1) { + if (i > 0) { stringBuilder.append(","); } + stringBuilder.append("@P").append(i + 1).append(" "); + Object param = params.getValueInternal(i); + if (param == null) { + stringBuilder.append(NULL.paramDefinition(null)); + } else if (param instanceof NullValue) { + Class valueClass = ((NullValue) param).type(); + DataType dataType = forValueClass(valueClass); + stringBuilder.append(dataType.paramDefinition(null)); + } else { + Class valueClass = param.getClass(); + DataType dataType = forValueClass(valueClass); + stringBuilder.append(dataType.paramDefinition(param)); + } } return stringBuilder.toString(); } - private void encodeNVarcharParameter(ByteBuf payload, String value) { - payload.writeByte(0x00); // name length - payload.writeByte(0x00); // status flags - payload.writeByte(MSSQLDataTypeId.NVARCHARTYPE_ID); - payload.writeShortLE(8000); // maximal length - payload.writeByte(0x09); - payload.writeByte(0x04); - payload.writeByte(0xd0); - payload.writeByte(0x00); - payload.writeByte(0x34); // Collation for param definitions TODO always this value? - writeUnsignedShortLenVarChar(payload, value); - } - - private void encodeParamValue(ByteBuf payload, Object value) { - if (value == null) { - encodeNullParameter(payload); - } else if (value instanceof Byte) { - encodeIntNParameter(payload, 1, value); - } else if (value instanceof Short) { - encodeIntNParameter(payload, 2, value); - } else if (value instanceof Integer) { - encodeIntNParameter(payload, 4, value); - } else if (value instanceof Long) { - encodeIntNParameter(payload, 8, value); - } else if (value instanceof Float) { - encodeFloat4Parameter(payload, (Float) value); - } else if (value instanceof Double) { - encodeFloat8Parameter(payload, (Double) value); - } else if (value instanceof String) { - encodeNVarcharParameter(payload, (String) value); - } else if (value instanceof Enum) { - encodeNVarcharParameter(payload, ((Enum) value).name()); - } else if (value instanceof Boolean) { - encodeBitNParameter(payload, (Boolean) value); - } else if (value instanceof LocalDate) { - encodeDateNParameter(payload, (LocalDate) value); - } else if (value instanceof LocalTime) { - encodeTimeNParameter(payload, (LocalTime) value); - } else if (value instanceof LocalDateTime) { - encodeDateTimeNParameter(payload, (LocalDateTime) value); - } else if (value instanceof OffsetDateTime) { - encodeOffsetDateTimeNParameter(payload, (OffsetDateTime) value); - } else if (value instanceof BigDecimal) { - encodeDecimalParameter(payload, (BigDecimal) value); - } else if (value instanceof Buffer) { - encodeBufferParameter(payload, (Buffer) value); - } else { - throw new UnsupportedOperationException("Unsupported type"); - } - } - - private void encodeNullParameter(ByteBuf payload) { - payload.writeByte(0x00); - payload.writeByte(0x00); - payload.writeByte(MSSQLDataTypeId.NULLTYPE_ID); - } - - private void encodeIntNParameter(ByteBuf payload, int n, Object value) { - payload.writeByte(0x00); - payload.writeByte(0x00); - payload.writeByte(MSSQLDataTypeId.INTNTYPE_ID); - payload.writeByte(n); - payload.writeByte(n); - switch (n) { - case 1: - payload.writeByte((Byte) value); - break; - case 2: - payload.writeShortLE((Short) value); - break; - case 4: - payload.writeIntLE((Integer) value); - break; - case 8: - payload.writeLongLE((Long) value); - break; - default: - throw new UnsupportedOperationException(); - } - } - - private void encodeBitNParameter(ByteBuf payload, Boolean bit) { - payload.writeByte(0x00); - payload.writeByte(0x00); - payload.writeByte(MSSQLDataTypeId.BITNTYPE_ID); - payload.writeByte(1); - payload.writeByte(1); - payload.writeBoolean(bit); - } - - private void encodeFloat4Parameter(ByteBuf payload, Float value) { - payload.writeByte(0x00); - payload.writeByte(0x00); - payload.writeByte(MSSQLDataTypeId.FLTNTYPE_ID); - payload.writeByte(4); - payload.writeByte(4); - payload.writeFloatLE(value); - } - - private void encodeFloat8Parameter(ByteBuf payload, Double value) { - payload.writeByte(0x00); - payload.writeByte(0x00); - payload.writeByte(MSSQLDataTypeId.FLTNTYPE_ID); - payload.writeByte(8); - payload.writeByte(8); - payload.writeDoubleLE(value); - } - - private void encodeDateNParameter(ByteBuf payload, LocalDate date) { - payload.writeByte(0x00); - payload.writeByte(0x00); - payload.writeByte(MSSQLDataTypeId.DATENTYPE_ID); - if (date == null) { - // null - payload.writeByte(0); - } else { - payload.writeByte(3); - encodeLocalDate(payload, date); - } - } - - private void encodeTimeNParameter(ByteBuf payload, LocalTime time) { - payload.writeByte(0x00); - payload.writeByte(0x00); - payload.writeByte(MSSQLDataTypeId.TIMENTYPE_ID); - - payload.writeByte(7); // scale - if (time == null) { - payload.writeByte(0); - } else { - payload.writeByte(5); // length - encodeLocalTime(payload, time); - } - } - - private void encodeDateTimeNParameter(ByteBuf payload, LocalDateTime dateTime) { - payload.writeByte(0x00); - payload.writeByte(0x00); - payload.writeByte(MSSQLDataTypeId.DATETIME2NTYPE_ID); - - payload.writeByte(7); // scale - if (dateTime == null) { - payload.writeByte(0); - } else { - payload.writeByte(8); // length - encodeLocalTime(payload, dateTime.toLocalTime()); - encodeLocalDate(payload, dateTime.toLocalDate()); - } - } - - private void encodeOffsetDateTimeNParameter(ByteBuf payload, OffsetDateTime offsetDateTime) { - payload.writeByte(0x00); - payload.writeByte(0x00); - payload.writeByte(MSSQLDataTypeId.DATETIMEOFFSETNTYPE_ID); - - payload.writeByte(7); - if (offsetDateTime == null) { - payload.writeByte(0); - } else { - payload.writeByte(10); // length - int offsetMinutes = offsetDateTime.getOffset().getTotalSeconds() / 60; - LocalDateTime localDateTime = offsetDateTime.toLocalDateTime().minusMinutes(offsetMinutes); - encodeLocalTime(payload, localDateTime.toLocalTime()); - LocalDate localDate = localDateTime.toLocalDate(); - encodeLocalDate(payload, localDate); - payload.writeShortLE(offsetMinutes); - } - } - - private void encodeLocalTime(ByteBuf payload, LocalTime localTime) { - encodeInt40(payload, localTime.toNanoOfDay() / 100); - } - - private void encodeInt40(ByteBuf buffer, long value) { - buffer.writeIntLE((int) (value % 0x100000000L)); - buffer.writeByte((int) (value / 0x100000000L)); - } - - private void encodeLocalDate(ByteBuf payload, LocalDate localDate) { - long days = ChronoUnit.DAYS.between(MSSQLDataTypeCodec.START_DATE, localDate); - payload.writeMediumLE((int) days); - } - - private void encodeDecimalParameter(ByteBuf payload, BigDecimal value) { - payload.writeByte(0x00); - payload.writeByte(0x00); - payload.writeByte(MSSQLDataTypeId.DECIMALNTYPE_ID); - - payload.writeByte(17); // maximum length - payload.writeByte(38); // maximum precision - - int sign = value.signum() < 0 ? 0 : 1; - byte[] bytes = (sign == 0 ? value.negate() : value).unscaledValue().toByteArray(); - - payload.writeByte(Math.max(0, value.scale())); - payload.writeByte(1 + bytes.length); - payload.writeByte(sign); - for (int i = bytes.length - 1; i >= 0; i--) { - payload.writeByte(bytes[i]); + private void encodeParams(ByteBuf buffer, TupleInternal params) { + for (int i = 0; i < params.size(); i++) { + String name = "@P" + (i + 1); + Object value = params.getValue(i); + if (value == null) { + NULL.encodeParam(buffer, name, false, null); + } else { + DataType dataType = DataType.forValueClass(value.getClass()); + dataType.encodeParam(buffer, name, false, value); + } } } - - private void encodeBufferParameter(ByteBuf payload, Buffer value) { - payload.writeByte(0x00); - payload.writeByte(0x00); - payload.writeByte(MSSQLDataTypeId.BIGBINARYTYPE_ID); - - payload.writeShortLE(value.length()); // max length - payload.writeShortLE(value.length()); // length - - payload.writeBytes(value.getByteBuf()); - } } diff --git a/vertx-mssql-client/src/main/java/io/vertx/mssqlclient/impl/codec/MSSQLCommandCodec.java b/vertx-mssql-client/src/main/java/io/vertx/mssqlclient/impl/codec/MSSQLCommandCodec.java index a5930ec20..26f5048f7 100644 --- a/vertx-mssql-client/src/main/java/io/vertx/mssqlclient/impl/codec/MSSQLCommandCodec.java +++ b/vertx-mssql-client/src/main/java/io/vertx/mssqlclient/impl/codec/MSSQLCommandCodec.java @@ -22,7 +22,8 @@ import java.util.function.Consumer; -import static java.nio.charset.StandardCharsets.UTF_16LE; +import static io.vertx.mssqlclient.impl.utils.ByteBufUtils.readUnsignedByteLengthString; +import static io.vertx.mssqlclient.impl.utils.ByteBufUtils.readUnsignedShortLengthString; abstract class MSSQLCommandCodec> { final C cmd; @@ -52,9 +53,9 @@ void handleErrorToken(ByteBuf buffer) { int number = buffer.readIntLE(); byte state = buffer.readByte(); byte severity = buffer.readByte(); - String message = readUnsignedShortLenVarChar(buffer); - String serverName = readByteLenVarchar(buffer); - String procedureName = readByteLenVarchar(buffer); + String message = readUnsignedShortLengthString(buffer); + String serverName = readUnsignedByteLengthString(buffer); + String procedureName = readUnsignedByteLengthString(buffer); int lineNumber = buffer.readIntLE(); MSSQLException failure = new MSSQLException(number, state, severity, message, serverName, procedureName, lineNumber); @@ -76,18 +77,4 @@ void complete() { completionHandler.handle(resp); } - protected String readByteLenVarchar(ByteBuf buffer) { - int length = buffer.readUnsignedByte(); - return buffer.readCharSequence(length * 2, UTF_16LE).toString(); - } - - protected String readUnsignedShortLenVarChar(ByteBuf buffer) { - int length = buffer.readUnsignedShortLE(); - return buffer.readCharSequence(length * 2, UTF_16LE).toString(); - } - - protected void writeUnsignedShortLenVarChar(ByteBuf buffer, String value) { - buffer.writeShortLE(value.length() * 2); - buffer.writeCharSequence(value, UTF_16LE); - } } diff --git a/vertx-mssql-client/src/main/java/io/vertx/mssqlclient/impl/codec/MSSQLDataTypeCodec.java b/vertx-mssql-client/src/main/java/io/vertx/mssqlclient/impl/codec/MSSQLDataTypeCodec.java deleted file mode 100644 index 8c550b05a..000000000 --- a/vertx-mssql-client/src/main/java/io/vertx/mssqlclient/impl/codec/MSSQLDataTypeCodec.java +++ /dev/null @@ -1,339 +0,0 @@ -/* - * Copyright (c) 2011-2021 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.mssqlclient.impl.codec; - -import io.netty.buffer.ByteBuf; -import io.netty.buffer.Unpooled; -import io.vertx.core.buffer.Buffer; -import io.vertx.mssqlclient.impl.protocol.datatype.*; -import io.vertx.sqlclient.data.NullValue; - -import java.math.BigDecimal; -import java.math.BigInteger; -import java.nio.charset.StandardCharsets; -import java.time.*; -import java.time.temporal.ChronoUnit; -import java.util.HashMap; -import java.util.Map; - -import static java.util.concurrent.TimeUnit.MILLISECONDS; -import static java.util.concurrent.TimeUnit.NANOSECONDS; - -class MSSQLDataTypeCodec { - static LocalDate START_DATE = LocalDate.of(1, 1, 1); - static LocalDate START_DATE_DATETIME = LocalDate.of(1900, 1, 1); - - private static final Map, String> parameterDefinitionsMapping = new HashMap<>(); - - static { - parameterDefinitionsMapping.put(Byte.class, "tinyint"); - parameterDefinitionsMapping.put(Short.class, "smallint"); - parameterDefinitionsMapping.put(Integer.class, "int"); - parameterDefinitionsMapping.put(Long.class, "bigint"); - parameterDefinitionsMapping.put(Boolean.class, "bit"); - parameterDefinitionsMapping.put(Float.class, "float"); - parameterDefinitionsMapping.put(Double.class, "float"); - parameterDefinitionsMapping.put(String.class, "nvarchar(4000)"); - parameterDefinitionsMapping.put(LocalDate.class, "date"); - parameterDefinitionsMapping.put(LocalTime.class, "time"); - parameterDefinitionsMapping.put(LocalDateTime.class, "datetime2(7)"); - parameterDefinitionsMapping.put(OffsetDateTime.class, "datetimeoffset(7)"); - } - - static String inferenceParamDefinitionByValueType(Object value) { - if (value == null) { - return "nvarchar(4000)"; - } - boolean nullValue = value instanceof NullValue; - Class type = nullValue ? ((NullValue) value).type() : value.getClass(); - if (type == BigDecimal.class) { - return "numeric(38," + (nullValue ? 0 : Math.max(0, ((BigDecimal) value).scale())) + ")"; - } else if (Buffer.class.isAssignableFrom(type)) { - return "binary(" + (nullValue ? 1 : ((Buffer) value).length()) + ")"; - } else if (type.isEnum()) { - return parameterDefinitionsMapping.get(String.class); - } else { - String paramDefinition = parameterDefinitionsMapping.get(type); - if (paramDefinition != null) { - return paramDefinition; - } else { - throw new UnsupportedOperationException("Unsupported type: " + type.getSimpleName()); - } - } - } - - static Object decode(MSSQLDataType dataType, ByteBuf in) { - switch (dataType.id()) { - case MSSQLDataTypeId.INT1TYPE_ID: - return decodeTinyInt(in); - case MSSQLDataTypeId.INT2TYPE_ID: - return decodeSmallInt(in); - case MSSQLDataTypeId.INT4TYPE_ID: - return decodeInt(in); - case MSSQLDataTypeId.INT8TYPE_ID: - return decodeBigInt(in); - case MSSQLDataTypeId.NUMERICNTYPE_ID: - case MSSQLDataTypeId.DECIMALNTYPE_ID: - return decodeDecimal((DecimalDataType) dataType, in); - case MSSQLDataTypeId.INTNTYPE_ID: - return decodeIntN(in); - case MSSQLDataTypeId.FLT4TYPE_ID: - return decodeFloat4(in); - case MSSQLDataTypeId.FLT8TYPE_ID: - return decodeFloat8(in); - case MSSQLDataTypeId.FLTNTYPE_ID: - return decodeFltN(in); - case MSSQLDataTypeId.BITTYPE_ID: - return decodeBit(in); - case MSSQLDataTypeId.BITNTYPE_ID: - return decodeBitN(in); - case MSSQLDataTypeId.DATETIMETYPE_ID: - return decodeDateTime(in); - case MSSQLDataTypeId.DATENTYPE_ID: - return decodeDateN(in); - case MSSQLDataTypeId.TIMENTYPE_ID: - return decodeTimeN((TimeNDataType) dataType, in); - case MSSQLDataTypeId.DATETIME2NTYPE_ID: - return decodeDateTime2N((DateTime2NDataType) dataType, in); - case MSSQLDataTypeId.DATETIMEOFFSETNTYPE_ID: - return decodeDateTimeOffsetN((DateTimeOffsetNDataType) dataType, in); - case MSSQLDataTypeId.BIGVARCHRTYPE_ID: - case MSSQLDataTypeId.BIGCHARTYPE_ID: - return decodeVarchar(in); - case MSSQLDataTypeId.NCHARTYPE_ID: - case MSSQLDataTypeId.NVARCHARTYPE_ID: - return decodeNVarchar(in); - case MSSQLDataTypeId.BIGBINARYTYPE_ID: - case MSSQLDataTypeId.BINARYTYPE_ID: - case MSSQLDataTypeId.BIGVARBINTYPE_ID: - case MSSQLDataTypeId.VARBINARYTYPE_ID: - return decodeBinary(in); - default: - throw new UnsupportedOperationException("Unsupported datatype: " + dataType); - } - } - - private static LocalTime decodeTimeN(TimeNDataType dataType, ByteBuf in) { - byte length = in.readByte(); - if (length == 0) { - return null; - } - return decodeLocalTime(in, length, dataType.scale()); - } - - private static LocalTime decodeLocalTime(ByteBuf in, int length, int scale) { - long hundredNanos; - if (length == 3) { - hundredNanos = in.readUnsignedMediumLE(); - } else if (length == 4) { - hundredNanos = in.readUnsignedIntLE(); - } else if (length == 5) { - hundredNanos = readUnsignedInt40LE(in); - } else { - throw new IllegalArgumentException("Unexpected timeLength of [" + length + "]"); - } - for (int i = scale; i < 7; i++) { - hundredNanos *= 10; - } - return LocalTime.ofNanoOfDay(100 * hundredNanos); - } - - private static LocalDateTime decodeDateTime2N(DateTime2NDataType dataType, ByteBuf in) { - byte length = in.readByte(); - if (length == 0) { - return null; - } - LocalTime localTime = decodeLocalTime(in, length - 3, dataType.scale()); - LocalDate localDate = decodeLocalDate(in, 3); - return LocalDateTime.of(localDate, localTime); - } - - private static OffsetDateTime decodeDateTimeOffsetN(DateTimeOffsetNDataType dataType, ByteBuf in) { - byte length = in.readByte(); - if (length == 0) { - return null; - } - LocalTime localTime = decodeLocalTime(in, length - 5, dataType.scale()); - LocalDate localDate = decodeLocalDate(in, 3); - short minutes = in.readShortLE(); - return LocalDateTime.of(localDate, localTime).plusMinutes(minutes).atOffset(ZoneOffset.ofTotalSeconds(60 * minutes)); - } - - private static CharSequence decodeNVarchar(ByteBuf in) { - int length = in.readUnsignedShortLE(); - if (length == 65535) { - // CHARBIN_NULL - return null; - } - return in.readCharSequence(length, StandardCharsets.UTF_16LE); - } - - private static Buffer decodeBinary(ByteBuf in) { - int length = in.readUnsignedShortLE(); - ByteBuf byteBuf = Unpooled.buffer(length); - in.readBytes(byteBuf, 0, length); - byteBuf.writerIndex(length); - return Buffer.buffer(byteBuf); - } - - private static CharSequence decodeVarchar(ByteBuf in) { - int length = in.readUnsignedShortLE(); - if (length == 65535) { - // CHARBIN_NULL - return null; - } - return in.readCharSequence(length, StandardCharsets.UTF_8); - } - - private static LocalDateTime decodeDateTime(ByteBuf in) { - LocalDate localDate = START_DATE_DATETIME.plus(in.readIntLE(), ChronoUnit.DAYS); - long nanoOfDay = NANOSECONDS.convert(Math.round(in.readIntLE() * (3 + 1D / 3)), MILLISECONDS); - LocalTime localTime = LocalTime.ofNanoOfDay(nanoOfDay); - return LocalDateTime.of(localDate, localTime); - } - - private static LocalDate decodeDateN(ByteBuf in) { - byte length = in.readByte(); - if (length == 0) { - return null; - } - return decodeLocalDate(in, length); - } - - private static LocalDate decodeLocalDate(ByteBuf in, int length) { - int days; - if (length == 3) { - days = in.readUnsignedMediumLE(); - } else { - throw new IllegalArgumentException("Unexpected dateLength of [" + length + "]"); - } - return START_DATE.plus(days, ChronoUnit.DAYS); - } - - private static boolean decodeBit(ByteBuf in) { - return in.readBoolean(); - } - - private static double decodeFloat8(ByteBuf in) { - return in.readDoubleLE(); - } - - private static float decodeFloat4(ByteBuf in) { - return in.readFloatLE(); - } - - private static BigDecimal decodeDecimal(DecimalDataType dataType, ByteBuf in) { - int scale = dataType.scale(); - short length = in.readUnsignedByte(); - if (length == 0) { - return null; - } - byte sign = in.readByte(); - byte[] bytes = new byte[length - 1]; - for (int i = 0; i < bytes.length; i++) { - bytes[i] = in.getByte(in.readerIndex() + bytes.length - 1 - i); - } - in.skipBytes(bytes.length); - BigInteger bigInteger = new BigInteger(bytes); - BigDecimal bigDecimal = new BigDecimal(bigInteger, scale); - return sign == 0 ? bigDecimal.negate() : bigDecimal; - } - - private static long decodeBigInt(ByteBuf in) { - return in.readLongLE(); - } - - private static int decodeInt(ByteBuf in) { - return in.readIntLE(); - } - - private static short decodeSmallInt(ByteBuf in) { - return in.readShortLE(); - } - - private static short decodeTinyInt(ByteBuf in) { - return in.readUnsignedByte(); - } - - private static long readUnsignedInt40LE(ByteBuf buffer) { - long low = buffer.readUnsignedIntLE(); - short high = buffer.readUnsignedByte(); - return (0x100000000L * high) + low; - } - - private static BigInteger readUnsignedInt96LE(ByteBuf buffer) { - byte[] result = new byte[12]; - int readerIndex = buffer.readerIndex(); - for (int i = 0; i < 12; i++) { - result[i] = buffer.getByte(readerIndex + 11 - i); - } - buffer.skipBytes(12); - return new BigInteger(result); - } - - private static BigInteger readUnsignedInt128LE(ByteBuf buffer) { - byte[] result = new byte[16]; - int readerIndex = buffer.readerIndex(); - for (int i = 0; i < 16; i++) { - result[i] = buffer.getByte(readerIndex + 15 - i); - } - buffer.skipBytes(16); - return new BigInteger(result); - } - - private static Object decodeIntN(ByteBuf buffer) { - int intNDataTypeLength = buffer.readByte(); - switch (intNDataTypeLength) { - case 0: - // this means we read a NULL value(nullable data type). - return null; - case 1: - return buffer.readUnsignedByte(); - case 2: - return buffer.readShortLE(); - case 4: - return buffer.readIntLE(); - case 8: - return buffer.readLongLE(); - default: - throw new UnsupportedOperationException(String.format("SEVERE: Unsupported length=[%d] for decoding IntNDataType row value.", intNDataTypeLength)); - } - } - - private static Object decodeFltN(ByteBuf buffer) { - int fltNDataTypeLength = buffer.readByte(); - switch (fltNDataTypeLength) { - case 0: - // this means we read a NULL value(nullable data type). - return null; - case 4: - return buffer.readFloatLE(); - case 8: - return buffer.readDoubleLE(); - default: - throw new UnsupportedOperationException(String.format("SEVERE: Unsupported length=[%d] for decoding FLTNTYPE row value.", fltNDataTypeLength)); - } - } - - private static Object decodeBitN(ByteBuf buffer) { - int bitNDataTypeLength = buffer.readByte(); - switch (bitNDataTypeLength) { - case 0: - // this means we read a NULL value(nullable data type). - return null; - case 1: - return buffer.readBoolean(); - default: - throw new UnsupportedOperationException(String.format("SEVERE: Unsupported length=[%d] for decoding BITNTYPE row value.", bitNDataTypeLength)); - } - } -} diff --git a/vertx-mssql-client/src/main/java/io/vertx/mssqlclient/impl/codec/MSSQLRowDesc.java b/vertx-mssql-client/src/main/java/io/vertx/mssqlclient/impl/codec/MSSQLRowDesc.java index 7e880408a..7c9afe3ad 100644 --- a/vertx-mssql-client/src/main/java/io/vertx/mssqlclient/impl/codec/MSSQLRowDesc.java +++ b/vertx-mssql-client/src/main/java/io/vertx/mssqlclient/impl/codec/MSSQLRowDesc.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2011-2019 Contributors to the Eclipse Foundation + * Copyright (c) 2011-2021 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 @@ -22,7 +22,7 @@ class MSSQLRowDesc extends RowDesc { final ColumnData[] columnDatas; MSSQLRowDesc(ColumnData[] columnDatas) { - super(Stream.of(columnDatas).map(ColumnData::colName).collect(Collectors.toList()), Collections.unmodifiableList(Arrays.asList(columnDatas))); + super(Stream.of(columnDatas).map(ColumnData::name).collect(Collectors.toList()), Collections.unmodifiableList(Arrays.asList(columnDatas))); this.columnDatas = columnDatas; } } diff --git a/vertx-mssql-client/src/main/java/io/vertx/mssqlclient/impl/codec/QueryCommandBaseCodec.java b/vertx-mssql-client/src/main/java/io/vertx/mssqlclient/impl/codec/QueryCommandBaseCodec.java index cee4aee50..d16eb7422 100644 --- a/vertx-mssql-client/src/main/java/io/vertx/mssqlclient/impl/codec/QueryCommandBaseCodec.java +++ b/vertx-mssql-client/src/main/java/io/vertx/mssqlclient/impl/codec/QueryCommandBaseCodec.java @@ -12,16 +12,14 @@ package io.vertx.mssqlclient.impl.codec; import io.netty.buffer.ByteBuf; -import io.vertx.mssqlclient.impl.protocol.datatype.*; import io.vertx.sqlclient.Row; import io.vertx.sqlclient.impl.RowDesc; import io.vertx.sqlclient.impl.command.QueryCommandBase; -import java.math.BigDecimal; import java.util.stream.Collector; import static io.vertx.mssqlclient.impl.protocol.EnvChange.*; -import static io.vertx.mssqlclient.impl.protocol.datatype.MSSQLDataTypeId.*; +import static io.vertx.mssqlclient.impl.utils.ByteBufUtils.readUnsignedByteLengthString; abstract class QueryCommandBaseCodec> extends MSSQLCommandCodec { protected RowResultDecoder rowResultDecoder; @@ -49,9 +47,10 @@ protected MSSQLRowDesc decodeColmetadataToken(ByteBuf payload) { for (int i = 0; i < columnCount; i++) { long userType = payload.readUnsignedIntLE(); int flags = payload.readUnsignedShortLE(); - MSSQLDataType dataType = decodeDataTypeMetadata(payload); - String columnName = readByteLenVarchar(payload); - columnDatas[i] = new ColumnData(userType, flags, dataType, columnName); + DataType dataType = DataType.forId(payload.readUnsignedByte()); + DataType.Metadata metadata = dataType.decodeMetadata(payload); + String columnName = readUnsignedByteLengthString(payload); + columnDatas[i] = new ColumnData(columnName, dataType, metadata); } return new MSSQLRowDesc(columnDatas); @@ -86,78 +85,6 @@ protected void handleResultSetDone(int affectedRows) { cmd.resultHandler().handleResult(affectedRows, size, rowDesc, result, failure); } - private MSSQLDataType decodeDataTypeMetadata(ByteBuf payload) { - int typeInfo = payload.readUnsignedByte(); - byte scale; - switch (typeInfo) { - /* - * FixedLen DataType - */ - case INT1TYPE_ID: - return FixedLenDataType.INT1TYPE; - case INT2TYPE_ID: - return FixedLenDataType.INT2TYPE; - case INT4TYPE_ID: - return FixedLenDataType.INT4TYPE; - case INT8TYPE_ID: - return FixedLenDataType.INT8TYPE; - case FLT4TYPE_ID: - return FixedLenDataType.FLT4TYPE; - case FLT8TYPE_ID: - return FixedLenDataType.FLT8TYPE; - case BITTYPE_ID: - return FixedLenDataType.BITTYPE; - /* - * Variable Length Data Type - */ - case NUMERICNTYPE_ID: - case DECIMALNTYPE_ID: - short decimalTypeSize = payload.readUnsignedByte(); - byte decimalPrecision = payload.readByte(); - scale = payload.readByte(); - return new DecimalDataType(typeInfo, BigDecimal.class, decimalPrecision, scale); - case INTNTYPE_ID: - byte intNTypeLength = payload.readByte(); - return IntNDataType.valueOf(intNTypeLength); - case FLTNTYPE_ID: - byte fltNTypeLength = payload.readByte(); - return FloatNDataType.valueOf(fltNTypeLength); - case BITNTYPE_ID: - payload.skipBytes(1); // should only be 1 - return BitNDataType.BIT_1_DATA_TYPE; - case DATETIMETYPE_ID: - return FixedLenDataType.DATETIMETYPE; - case DATENTYPE_ID: - return FixedLenDataType.DATENTYPE; - case TIMENTYPE_ID: - scale = payload.readByte(); - return new TimeNDataType(scale); - case DATETIME2NTYPE_ID: - scale = payload.readByte(); - return new DateTime2NDataType(scale); - case DATETIMEOFFSETNTYPE_ID: - scale = payload.readByte(); - return new DateTimeOffsetNDataType(scale); - case BIGCHARTYPE_ID: - case BIGVARCHRTYPE_ID: - case NCHARTYPE_ID: - case NVARCHARTYPE_ID: - int size = payload.readUnsignedShortLE(); - short collateCodepage = payload.readShortLE(); - short collateFlags = payload.readShortLE(); - byte collateCharsetId = payload.readByte(); - return new TextWithCollationDataType(typeInfo, String.class, null); - case BIGBINARYTYPE_ID: - case BINARYTYPE_ID: - return new BinaryDataType(typeInfo, payload.readUnsignedShortLE()); - case BIGVARBINTYPE_ID: - case VARBINARYTYPE_ID: - return new VarBinaryDataType(typeInfo, payload.readUnsignedShortLE()); - default: - throw new UnsupportedOperationException("Unsupported type with typeinfo: " + typeInfo); - } - } - void handleEnvChangeToken(ByteBuf messageBody) { int totalLength = messageBody.readUnsignedShortLE(); int startPos = messageBody.readerIndex(); 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 2b3f08143..9f93c46d5 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 @@ -1,5 +1,5 @@ /* - * Copyright (c) 2011-2019 Contributors to the Eclipse Foundation + * Copyright (c) 2011-2021 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 @@ -55,10 +55,8 @@ public void handleNbcRow(int len, ByteBuf in) { private Row decodeMssqlRow(int len, ByteBuf in) { Row row = new MSSQLRowImpl(desc); for (int c = 0; c < len; c++) { - Object decoded = null; ColumnData columnData = desc.columnDatas[c]; - decoded = MSSQLDataTypeCodec.decode(columnData.dataType(), in); - row.addValue(decoded); + row.addValue(columnData.dataType().decodeValue(in, columnData.metadata())); } return row; } @@ -78,7 +76,7 @@ private Row decodeMssqlNbcRow(int len, ByteBuf in) { if ((nullByte & mask) == 0) { // not null ColumnData columnData = desc.columnDatas[c]; - decoded = MSSQLDataTypeCodec.decode(columnData.dataType(), in); + decoded = columnData.dataType().decodeValue(in, columnData.metadata()); } row.addValue(decoded); } diff --git a/vertx-mssql-client/src/main/java/io/vertx/mssqlclient/impl/codec/VarBinaryDataType.java b/vertx-mssql-client/src/main/java/io/vertx/mssqlclient/impl/codec/VarBinaryDataType.java deleted file mode 100644 index 501de462c..000000000 --- a/vertx-mssql-client/src/main/java/io/vertx/mssqlclient/impl/codec/VarBinaryDataType.java +++ /dev/null @@ -1,31 +0,0 @@ -/* - * Copyright (c) 2011-2021 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.mssqlclient.impl.codec; - -import io.vertx.core.buffer.Buffer; -import io.vertx.mssqlclient.impl.protocol.datatype.MSSQLDataType; - -import java.sql.JDBCType; - -public class VarBinaryDataType extends MSSQLDataType { - - private final int length; - - public VarBinaryDataType(int id, int length) { - super(id, Buffer.class, JDBCType.VARBINARY); - this.length = length; - } - - public int getLength() { - return length; - } -} diff --git a/vertx-mssql-client/src/main/java/io/vertx/mssqlclient/impl/protocol/datatype/BitNDataType.java b/vertx-mssql-client/src/main/java/io/vertx/mssqlclient/impl/protocol/datatype/BitNDataType.java deleted file mode 100644 index 9414dbf1f..000000000 --- a/vertx-mssql-client/src/main/java/io/vertx/mssqlclient/impl/protocol/datatype/BitNDataType.java +++ /dev/null @@ -1,21 +0,0 @@ -package io.vertx.mssqlclient.impl.protocol.datatype; - -import java.sql.JDBCType; - -/** - * BITNTYPE, the only valid lengths are 0x01 for non-null instances and 0x00 for NULL instances. - */ -public class BitNDataType extends MSSQLDataType { - public static final BitNDataType BIT_1_DATA_TYPE = new BitNDataType(MSSQLDataTypeId.BITNTYPE_ID, Boolean.class, 1); - - private final int length; - - public BitNDataType(int id, Class mappedJavaType, int length) { - super(id, mappedJavaType, JDBCType.BOOLEAN); - this.length = length; - } - - public int length() { - return length; - } -} diff --git a/vertx-mssql-client/src/main/java/io/vertx/mssqlclient/impl/protocol/datatype/DateTime2NDataType.java b/vertx-mssql-client/src/main/java/io/vertx/mssqlclient/impl/protocol/datatype/DateTime2NDataType.java deleted file mode 100644 index 5c917ba07..000000000 --- a/vertx-mssql-client/src/main/java/io/vertx/mssqlclient/impl/protocol/datatype/DateTime2NDataType.java +++ /dev/null @@ -1,28 +0,0 @@ -/* - * Copyright (c) 2011-2021 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.mssqlclient.impl.protocol.datatype; - -import java.sql.JDBCType; -import java.time.LocalDateTime; - -public class DateTime2NDataType extends MSSQLDataType { - private final byte scale; - - public DateTime2NDataType(byte scale) { - super(MSSQLDataTypeId.DATETIME2NTYPE_ID, LocalDateTime.class, JDBCType.TIMESTAMP); - this.scale = scale; - } - - public byte scale() { - return scale; - } -} diff --git a/vertx-mssql-client/src/main/java/io/vertx/mssqlclient/impl/protocol/datatype/DateTimeOffsetNDataType.java b/vertx-mssql-client/src/main/java/io/vertx/mssqlclient/impl/protocol/datatype/DateTimeOffsetNDataType.java deleted file mode 100644 index 9cfca5515..000000000 --- a/vertx-mssql-client/src/main/java/io/vertx/mssqlclient/impl/protocol/datatype/DateTimeOffsetNDataType.java +++ /dev/null @@ -1,28 +0,0 @@ -/* - * Copyright (c) 2011-2021 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.mssqlclient.impl.protocol.datatype; - -import java.sql.JDBCType; -import java.time.OffsetDateTime; - -public class DateTimeOffsetNDataType extends MSSQLDataType { - private final byte scale; - - public DateTimeOffsetNDataType(byte scale) { - super(MSSQLDataTypeId.DATETIMEOFFSETNTYPE_ID, OffsetDateTime.class, JDBCType.TIMESTAMP_WITH_TIMEZONE); - this.scale = scale; - } - - public byte scale() { - return scale; - } -} diff --git a/vertx-mssql-client/src/main/java/io/vertx/mssqlclient/impl/protocol/datatype/DecimalDataType.java b/vertx-mssql-client/src/main/java/io/vertx/mssqlclient/impl/protocol/datatype/DecimalDataType.java deleted file mode 100644 index de5d3bff7..000000000 --- a/vertx-mssql-client/src/main/java/io/vertx/mssqlclient/impl/protocol/datatype/DecimalDataType.java +++ /dev/null @@ -1,34 +0,0 @@ -/* - * Copyright (c) 2011-2021 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.mssqlclient.impl.protocol.datatype; - -import java.sql.JDBCType; - -// NUMERIC, NUMERICN, DECIMAL, or DECIMALN. -public class DecimalDataType extends MSSQLDataType { - private final int precision; - private final int scale; - - public DecimalDataType(int id, Class mappedJavaType, int precision, int scale) { - super(id, mappedJavaType, JDBCType.DECIMAL); - this.precision = precision; - this.scale = scale; - } - - public int precision() { - return precision; - } - - public int scale() { - return scale; - } -} diff --git a/vertx-mssql-client/src/main/java/io/vertx/mssqlclient/impl/protocol/datatype/FixedLenDataType.java b/vertx-mssql-client/src/main/java/io/vertx/mssqlclient/impl/protocol/datatype/FixedLenDataType.java deleted file mode 100644 index 3adf4e7da..000000000 --- a/vertx-mssql-client/src/main/java/io/vertx/mssqlclient/impl/protocol/datatype/FixedLenDataType.java +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Copyright (c) 2011-2019 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.mssqlclient.impl.protocol.datatype; - -import io.vertx.core.buffer.Buffer; - -import java.sql.JDBCType; -import java.time.LocalDate; -import java.time.LocalDateTime; - -public class FixedLenDataType extends MSSQLDataType { - public FixedLenDataType(int id, Class mappedJavaType, JDBCType jdbcType) { - super(id, mappedJavaType, jdbcType); - } - - public static FixedLenDataType NULLTYPE = new FixedLenDataType(MSSQLDataTypeId.NULLTYPE_ID, null, JDBCType.OTHER); - public static FixedLenDataType INT1TYPE = new FixedLenDataType(MSSQLDataTypeId.INT1TYPE_ID, Byte.class, JDBCType.TINYINT); - public static FixedLenDataType BITTYPE = new FixedLenDataType(MSSQLDataTypeId.BITTYPE_ID, Buffer.class, JDBCType.BIT); - public static FixedLenDataType INT2TYPE = new FixedLenDataType(MSSQLDataTypeId.INT2TYPE_ID, Short.class, JDBCType.SMALLINT); - public static FixedLenDataType INT4TYPE = new FixedLenDataType(MSSQLDataTypeId.INT4TYPE_ID, Integer.class, JDBCType.INTEGER); - public static FixedLenDataType DATETIM4TYPE = new FixedLenDataType(MSSQLDataTypeId.DATETIM4TYPE_ID, LocalDateTime.class, JDBCType.TIMESTAMP); - public static FixedLenDataType FLT4TYPE = new FixedLenDataType(MSSQLDataTypeId.FLT4TYPE_ID, Float.class, JDBCType.REAL); - public static FixedLenDataType MONEYTYPE = new FixedLenDataType(MSSQLDataTypeId.MONEYTYPE_ID, null, JDBCType.OTHER); //TODO - public static FixedLenDataType DATETIMETYPE = new FixedLenDataType(MSSQLDataTypeId.DATETIMETYPE_ID, LocalDateTime.class, JDBCType.TIMESTAMP); - public static FixedLenDataType FLT8TYPE = new FixedLenDataType(MSSQLDataTypeId.FLT8TYPE_ID, Double.class, JDBCType.DOUBLE); - public static FixedLenDataType MONEY4TYPE = new FixedLenDataType(MSSQLDataTypeId.MONEY4TYPE_ID, null, JDBCType.OTHER); //TODO - public static FixedLenDataType INT8TYPE = new FixedLenDataType(MSSQLDataTypeId.INT8TYPE_ID, Long.class, JDBCType.BIGINT); - - // DATENTYPE 0 or 3 length - public static FixedLenDataType DATENTYPE = new FixedLenDataType(MSSQLDataTypeId.DATENTYPE_ID, LocalDate.class, JDBCType.DATE); -} diff --git a/vertx-mssql-client/src/main/java/io/vertx/mssqlclient/impl/protocol/datatype/FloatNDataType.java b/vertx-mssql-client/src/main/java/io/vertx/mssqlclient/impl/protocol/datatype/FloatNDataType.java deleted file mode 100644 index 91c6aac3d..000000000 --- a/vertx-mssql-client/src/main/java/io/vertx/mssqlclient/impl/protocol/datatype/FloatNDataType.java +++ /dev/null @@ -1,33 +0,0 @@ -package io.vertx.mssqlclient.impl.protocol.datatype; - -import java.sql.JDBCType; - -/** - * FLTNTYPE, Variable-Length Data type, the only valid lengths are 0x04 and 0x08, which map to 7-digit precision float and 15-digit precision float SQL data types respectively. - */ -public class FloatNDataType extends MSSQLDataType { - public static final FloatNDataType FLT_4_DATA_TYPE = new FloatNDataType(MSSQLDataTypeId.FLTNTYPE_ID, Float.class, 4, JDBCType.REAL); - public static final FloatNDataType FLT_8_DATA_TYPE = new FloatNDataType(MSSQLDataTypeId.FLTNTYPE_ID, Double.class, 8, JDBCType.DOUBLE); - - private final int length; - - public FloatNDataType(int id, Class mappedJavaType, int length, JDBCType jdbcType) { - super(id, mappedJavaType, jdbcType); - this.length = length; - } - - public int length() { - return length; - } - - public static FloatNDataType valueOf(int length) { - switch (length) { - case 4: - return FLT_4_DATA_TYPE; - case 8: - return FLT_8_DATA_TYPE; - default: - throw new UnsupportedOperationException(String.format("SEVERE: Unsupported length=[%d] for decoding FLTNTYPE column metadata.", length)); - } - } -} diff --git a/vertx-mssql-client/src/main/java/io/vertx/mssqlclient/impl/protocol/datatype/IntNDataType.java b/vertx-mssql-client/src/main/java/io/vertx/mssqlclient/impl/protocol/datatype/IntNDataType.java deleted file mode 100644 index 4d06492c5..000000000 --- a/vertx-mssql-client/src/main/java/io/vertx/mssqlclient/impl/protocol/datatype/IntNDataType.java +++ /dev/null @@ -1,50 +0,0 @@ -/* - * Copyright (c) 2011-2020 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.mssqlclient.impl.protocol.datatype; - -import java.sql.JDBCType; - -/** - * Variable-Length Data type, length may be 0x01, 0x02, 0x04, and 0x08. - */ -public class IntNDataType extends MSSQLDataType { - public static final IntNDataType INT_1_DATA_TYPE = new IntNDataType(MSSQLDataTypeId.INTNTYPE_ID, Byte.class, 1, JDBCType.TINYINT); - public static final IntNDataType INT_2_DATA_TYPE = new IntNDataType(MSSQLDataTypeId.INTNTYPE_ID, Short.class, 2, JDBCType.SMALLINT); - public static final IntNDataType INT_4_DATA_TYPE = new IntNDataType(MSSQLDataTypeId.INTNTYPE_ID, Integer.class, 4, JDBCType.INTEGER); - public static final IntNDataType INT_8_DATA_TYPE = new IntNDataType(MSSQLDataTypeId.INTNTYPE_ID, Long.class, 8, JDBCType.BIGINT); - - private final int length; - - private IntNDataType(int id, Class mappedJavaType, int length, JDBCType jdbcType) { - super(id, mappedJavaType, jdbcType); - this.length = length; - } - - public int length() { - return length; - } - - public static IntNDataType valueOf(int length) { - switch (length) { - case 1: - return INT_1_DATA_TYPE; - case 2: - return INT_2_DATA_TYPE; - case 4: - return INT_4_DATA_TYPE; - case 8: - return INT_8_DATA_TYPE; - default: - throw new UnsupportedOperationException(String.format("SEVERE: Unsupported length=[%d] for decoding IntNDataType column metadata.", length)); - } - } -} diff --git a/vertx-mssql-client/src/main/java/io/vertx/mssqlclient/impl/protocol/datatype/MSSQLDataType.java b/vertx-mssql-client/src/main/java/io/vertx/mssqlclient/impl/protocol/datatype/MSSQLDataType.java deleted file mode 100644 index 0cb582ee6..000000000 --- a/vertx-mssql-client/src/main/java/io/vertx/mssqlclient/impl/protocol/datatype/MSSQLDataType.java +++ /dev/null @@ -1,50 +0,0 @@ -/* - * Copyright (c) 2011-2019 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.mssqlclient.impl.protocol.datatype; - -import java.sql.JDBCType; - -/* - DATE MUST NOT have a TYPE_VARLEN. - The value is either 3 bytes or 0 bytes (null). - TIME, DATETIME2, and DATETIMEOFFSET MUST NOT have a TYPE_VARLEN. The lengths are determined by the SCALE as indicated in section 2.2.5.4.2. - PRECISION and SCALE MUST occur if the type is NUMERIC, NUMERICN, DECIMAL, or DECIMALN. - SCALE (without PRECISION) MUST occur if the type is TIME, DATETIME2, or DATETIMEOFFSET (introduced in TDS 7.3). PRECISION MUST be less than or equal to decimal 38 and SCALE MUST be less than or equal to the precision value. - COLLATION occurs only if the type is BIGCHARTYPE, BIGVARCHRTYPE, TEXTTYPE, NTEXTTYPE, NCHARTYPE, or NVARCHARTYPE. - UDT_INFO always occurs if the type is UDTTYPE. - XML_INFO always occurs if the type is XMLTYPE. - USHORTMAXLEN does not occur if PARTLENTYPE is XMLTYPE or UDTTYPE. - */ -public abstract class MSSQLDataType { - protected final int id; - protected final Class mappedJavaType; - protected final JDBCType jdbcType; - - public MSSQLDataType(int id, Class mappedJavaType, JDBCType jdbcType) { - this.id = id; - this.mappedJavaType = mappedJavaType; - this.jdbcType = jdbcType; - } - - public int id() { - return id; - } - - public Class mappedJavaType() { - return mappedJavaType; - } - - public JDBCType jdbcType() { - return jdbcType; - } - -} diff --git a/vertx-mssql-client/src/main/java/io/vertx/mssqlclient/impl/protocol/datatype/MSSQLDataTypeId.java b/vertx-mssql-client/src/main/java/io/vertx/mssqlclient/impl/protocol/datatype/MSSQLDataTypeId.java deleted file mode 100644 index d3bf16c0d..000000000 --- a/vertx-mssql-client/src/main/java/io/vertx/mssqlclient/impl/protocol/datatype/MSSQLDataTypeId.java +++ /dev/null @@ -1,66 +0,0 @@ -/* - * Copyright (c) 2011-2019 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.mssqlclient.impl.protocol.datatype; - -public final class MSSQLDataTypeId { - /* - Fixed-Length Data Types - https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-tds/859eb3d2-80d3-40f6-a637-414552c9c552 - */ - public static final int NULLTYPE_ID = 0x1F; - public static final int INT1TYPE_ID = 0x30; - public static final int BITTYPE_ID = 0x32; - public static final int INT2TYPE_ID = 0x34; - public static final int INT4TYPE_ID = 0x38; - public static final int DATETIM4TYPE_ID = 0x3A; - public static final int FLT4TYPE_ID = 0x3B; - public static final int MONEYTYPE_ID = 0x3C; - public static final int DATETIMETYPE_ID = 0x3D; - public static final int FLT8TYPE_ID = 0x3E; - public static final int MONEY4TYPE_ID = 0x7A; - public static final int INT8TYPE_ID = 0x7F; - - /* - Variable-Length Data Types - https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-tds/ce3183a6-9d89-47e8-a02f-de5a1a1303de - */ - public static final int GUIDTYPE_ID = 0x24; - public static final int INTNTYPE_ID = 0x26; - public static final int DECIMALTYPE_ID = 0x37; - public static final int NUMERICTYPE_ID = 0x3F; - public static final int BITNTYPE_ID = 0x68; - public static final int DECIMALNTYPE_ID = 0x6A; - public static final int NUMERICNTYPE_ID = 0x6C; - public static final int FLTNTYPE_ID = 0x6D; - public static final int MONEYNTYPE_ID = 0x6E; - public static final int DATETIMNTYPE_ID = 0x6F; - public static final int DATENTYPE_ID = 0x28; - public static final int TIMENTYPE_ID = 0x29; - public static final int DATETIME2NTYPE_ID = 0x2A; - public static final int DATETIMEOFFSETNTYPE_ID = 0x2B; - public static final int CHARTYPE_ID = 0x2F; - public static final int VARCHARTYPE_ID = 0x27; - public static final int BINARYTYPE_ID = 0x2D; - public static final int VARBINARYTYPE_ID = 0x25; - public static final int BIGVARBINTYPE_ID = 0xA5; - public static final int BIGVARCHRTYPE_ID = 0xA7; - public static final int BIGBINARYTYPE_ID = 0xAD; - public static final int BIGCHARTYPE_ID = 0xAF; - public static final int NVARCHARTYPE_ID = 0xE7; - public static final int NCHARTYPE_ID = 0xEF; - public static final int XMLTYPE_ID = 0xF1; - public static final int UDTTYPE_ID = 0xF0; - public static final int TEXTTYPE_ID = 0x23; - public static final int IMAGETYPE_ID = 0x22; - public static final int NTEXTTYPE_ID = 0x63; - public static final int SSVARIANTTYPE_ID = 0x62; -} diff --git a/vertx-mssql-client/src/main/java/io/vertx/mssqlclient/impl/protocol/datatype/TextWithCollationDataType.java b/vertx-mssql-client/src/main/java/io/vertx/mssqlclient/impl/protocol/datatype/TextWithCollationDataType.java deleted file mode 100644 index 89e694785..000000000 --- a/vertx-mssql-client/src/main/java/io/vertx/mssqlclient/impl/protocol/datatype/TextWithCollationDataType.java +++ /dev/null @@ -1,28 +0,0 @@ -/* - * Copyright (c) 2011-2019 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.mssqlclient.impl.protocol.datatype; - -import java.sql.JDBCType; - -// BIGCHARTYPE, BIGVARCHRTYPE, TEXTTYPE, NTEXTTYPE, NCHARTYPE, or NVARCHARTYPE -public class TextWithCollationDataType extends MSSQLDataType { - private final String collation; - - public TextWithCollationDataType(int id, Class mappedJavaType, String collation) { - super(id, mappedJavaType, JDBCType.VARCHAR); - this.collation = collation; - } - - public String collation() { - return collation; - } -} diff --git a/vertx-mssql-client/src/main/java/io/vertx/mssqlclient/impl/protocol/datatype/TimeNDataType.java b/vertx-mssql-client/src/main/java/io/vertx/mssqlclient/impl/protocol/datatype/TimeNDataType.java deleted file mode 100644 index fabb0d6b4..000000000 --- a/vertx-mssql-client/src/main/java/io/vertx/mssqlclient/impl/protocol/datatype/TimeNDataType.java +++ /dev/null @@ -1,28 +0,0 @@ -/* - * Copyright (c) 2011-2019 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.mssqlclient.impl.protocol.datatype; - -import java.sql.JDBCType; -import java.time.LocalTime; - -public class TimeNDataType extends MSSQLDataType { - private byte scale; - - public TimeNDataType(byte scale) { - super(MSSQLDataTypeId.TIMENTYPE_ID, LocalTime.class, JDBCType.TIME); - this.scale = scale; - } - - public byte scale() { - return scale; - } -} diff --git a/vertx-mssql-client/src/main/java/io/vertx/mssqlclient/impl/utils/ByteBufUtils.java b/vertx-mssql-client/src/main/java/io/vertx/mssqlclient/impl/utils/ByteBufUtils.java new file mode 100644 index 000000000..c29fd65e2 --- /dev/null +++ b/vertx-mssql-client/src/main/java/io/vertx/mssqlclient/impl/utils/ByteBufUtils.java @@ -0,0 +1,58 @@ +/* + * Copyright (c) 2011-2021 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.mssqlclient.impl.utils; + +import io.netty.buffer.ByteBuf; + +import static java.nio.charset.StandardCharsets.UTF_16LE; + +public class ByteBufUtils { + + public static void writeByteLengthString(ByteBuf buffer, String value) { + if (value == null) { + buffer.writeByte(0); + } else { + buffer.writeByte(value.length()); + buffer.writeCharSequence(value, UTF_16LE); + } + } + + public static String readUnsignedByteLengthString(ByteBuf buffer) { + int length = buffer.readUnsignedByte(); + return buffer.readCharSequence(length * 2, UTF_16LE).toString(); + } + + public static String readUnsignedShortLengthString(ByteBuf buffer) { + int length = buffer.readUnsignedShortLE(); + return buffer.readCharSequence(length * 2, UTF_16LE).toString(); + } + + public static void writeUnsignedShortLengthString(ByteBuf buffer, String value) { + buffer.writeShortLE(value.length() * 2); + buffer.writeCharSequence(value, UTF_16LE); + } + + public static long readUnsignedInt40LE(ByteBuf buffer) { + long low = buffer.readUnsignedIntLE(); + short high = buffer.readUnsignedByte(); + return (0x100000000L * high) + low; + } + + public static void writeUnsignedInt40LE(ByteBuf buffer, long value) { + buffer.writeIntLE((int) (value % 0x100000000L)); + buffer.writeByte((int) (value / 0x100000000L)); + } + + private ByteBufUtils() { + // Utility + } +} From 5f82580e3a4840db56f457a23c87792ece43c80f Mon Sep 17 00:00:00 2001 From: Thomas Segismont Date: Wed, 16 Jun 2021 17:54:56 +0200 Subject: [PATCH 13/26] Run tests that were previously ignored Signed-off-by: Thomas Segismont --- .../mssqlclient/tck/MSSQLConnectionTest.java | 19 +------------------ .../tck/MSSQLNullValueEncodeTest.java | 12 ------------ 2 files changed, 1 insertion(+), 30 deletions(-) diff --git a/vertx-mssql-client/src/test/java/io/vertx/mssqlclient/tck/MSSQLConnectionTest.java b/vertx-mssql-client/src/test/java/io/vertx/mssqlclient/tck/MSSQLConnectionTest.java index 45f1f4e25..aee770488 100644 --- a/vertx-mssql-client/src/test/java/io/vertx/mssqlclient/tck/MSSQLConnectionTest.java +++ b/vertx-mssql-client/src/test/java/io/vertx/mssqlclient/tck/MSSQLConnectionTest.java @@ -38,30 +38,13 @@ public void tearDown(TestContext ctx) { super.tearDown(ctx); } - /* - TODO enable the tests when we support simple query - */ - @Ignore - @Test - @Override - public void testCloseWithErrorInProgress(TestContext ctx) { - super.testCloseWithErrorInProgress(ctx); - } - - @Ignore - @Test - @Override - public void testCloseWithQueryInProgress(TestContext ctx) { - super.testCloseWithQueryInProgress(ctx); - } - @Ignore @Test @Override public void testDatabaseMetaData(TestContext ctx) { super.testDatabaseMetaData(ctx); } - + @Override protected void validateDatabaseMetaData(TestContext ctx, DatabaseMetadata md) { // TODO implement this along with testDatabaseMetaData diff --git a/vertx-mssql-client/src/test/java/io/vertx/mssqlclient/tck/MSSQLNullValueEncodeTest.java b/vertx-mssql-client/src/test/java/io/vertx/mssqlclient/tck/MSSQLNullValueEncodeTest.java index 41edaf13e..158dff056 100644 --- a/vertx-mssql-client/src/test/java/io/vertx/mssqlclient/tck/MSSQLNullValueEncodeTest.java +++ b/vertx-mssql-client/src/test/java/io/vertx/mssqlclient/tck/MSSQLNullValueEncodeTest.java @@ -66,12 +66,6 @@ public void testEncodeNullArrayOfLocalDateTime(TestContext ctx) { public void testEncodeNullArrayOfBuffer(TestContext ctx) { } - @Test - @Ignore - @Override - public void testEncodeNullBuffer(TestContext ctx) { - } - @Test @Ignore @Override @@ -168,12 +162,6 @@ public void testEncodeNullArrayOfJsonArray(TestContext ctx) { public void testEncodeNullOffsetTime(TestContext ctx) { } - @Test - @Ignore - @Override - public void testEncodeNullBigDecimal(TestContext ctx) { - } - @Test @Ignore @Override From 00701b549e63906179b0229bc6ef746e6aff086c Mon Sep 17 00:00:00 2001 From: Thomas Segismont Date: Thu, 17 Jun 2021 12:03:05 +0200 Subject: [PATCH 14/26] Allow to configure database container with a fixed port (#988) * Allow to configure SQL Server container with a fixed port This simplifies debugging with tools like wireshark. Signed-off-by: Thomas Segismont * Allow to configure PostgreSQL Server container with a fixed port This simplifies debugging with tools like wireshark. Signed-off-by: Thomas Segismont * Update documentation Signed-off-by: Thomas Segismont --- vertx-mssql-client/README.adoc | 9 ++++++ .../io/vertx/mssqlclient/junit/MSSQLRule.java | 29 +++++++++++++++---- vertx-pg-client/README.adoc | 8 +++++ .../vertx/pgclient/junit/ContainerPgRule.java | 25 ++++++++++++++-- 4 files changed, 63 insertions(+), 8 deletions(-) diff --git a/vertx-mssql-client/README.adoc b/vertx-mssql-client/README.adoc index 22269b3be..15e8fc768 100644 --- a/vertx-mssql-client/README.adoc +++ b/vertx-mssql-client/README.adoc @@ -10,6 +10,15 @@ Documentation: By default, the test suite runs SQL Server in a container using https://www.testcontainers.org/[TestContainers]. +The container database binds to an arbitrary port to avoid conflicts. +Nevertheless, you can force the usage of the standard SQL Server port (1433) with a flag: + +[source,bash] +---- +mvn test -DcontainerFixedPort +---- + + ==== Testing with an external database You can start an external database: diff --git a/vertx-mssql-client/src/test/java/io/vertx/mssqlclient/junit/MSSQLRule.java b/vertx-mssql-client/src/test/java/io/vertx/mssqlclient/junit/MSSQLRule.java index 290e9c4f8..c0aa98e1d 100644 --- a/vertx-mssql-client/src/test/java/io/vertx/mssqlclient/junit/MSSQLRule.java +++ b/vertx-mssql-client/src/test/java/io/vertx/mssqlclient/junit/MSSQLRule.java @@ -13,12 +13,15 @@ import io.vertx.mssqlclient.MSSQLConnectOptions; import org.junit.rules.ExternalResource; +import org.testcontainers.containers.InternetProtocol; import org.testcontainers.containers.MSSQLServerContainer; import java.time.ZoneId; +import static org.testcontainers.containers.MSSQLServerContainer.MS_SQL_SERVER_PORT; + public class MSSQLRule extends ExternalResource { - private MSSQLServerContainer server; + private ServerContainer server; private MSSQLConnectOptions options; public static final MSSQLRule SHARED_INSTANCE = new MSSQLRule(); @@ -50,16 +53,20 @@ private MSSQLConnectOptions startMSSQL() { if (containerVersion == null || containerVersion.isEmpty()) { containerVersion = "2017-latest"; } - server = new MSSQLServerContainer<>("mcr.microsoft.com/mssql/server:" + containerVersion) + server = new ServerContainer<>("mcr.microsoft.com/mssql/server:" + containerVersion) .acceptLicense() .withEnv("TZ", ZoneId.systemDefault().toString()) - .withInitScript("init.sql") - .withExposedPorts(MSSQLServerContainer.MS_SQL_SERVER_PORT); + .withInitScript("init.sql"); + if (System.getProperties().containsKey("containerFixedPort")) { + server.withFixedExposedPort(MS_SQL_SERVER_PORT, MS_SQL_SERVER_PORT); + } else { + server.withExposedPorts(MS_SQL_SERVER_PORT); + } server.start(); return new MSSQLConnectOptions() .setHost(server.getContainerIpAddress()) - .setPort(server.getMappedPort(MSSQLServerContainer.MS_SQL_SERVER_PORT)) + .setPort(server.getMappedPort(MS_SQL_SERVER_PORT)) .setUser(server.getUsername()) .setPassword(server.getPassword()); } @@ -77,4 +84,16 @@ private void stopMSSQL() { public MSSQLConnectOptions options() { return new MSSQLConnectOptions(options); } + + private static class ServerContainer> extends MSSQLServerContainer { + + public ServerContainer(String dockerImageName) { + super(dockerImageName); + } + + public SELF withFixedExposedPort(int hostPort, int containerPort) { + super.addFixedExposedPort(hostPort, containerPort, InternetProtocol.TCP); + return self(); + } + } } diff --git a/vertx-pg-client/README.adoc b/vertx-pg-client/README.adoc index 1984dd843..e055c143e 100644 --- a/vertx-pg-client/README.adoc +++ b/vertx-pg-client/README.adoc @@ -282,6 +282,14 @@ The following versions of embedded Postgres are supported: - `10.6` (default) - `11.x` (Unix Domain Socket Test are ignored) +The embedded Postgres database binds to an arbitrary port by default to avoid conflicts. +Nevertheless, you can force the usage of the standard PostgreSQL port (5432) with a flag: + +[source,bash] +---- +mvn test -DcontainerFixedPort +---- + === Testing with an external database You can run tests with an external database: diff --git a/vertx-pg-client/src/test/java/io/vertx/pgclient/junit/ContainerPgRule.java b/vertx-pg-client/src/test/java/io/vertx/pgclient/junit/ContainerPgRule.java index cd31fb701..d935361f9 100644 --- a/vertx-pg-client/src/test/java/io/vertx/pgclient/junit/ContainerPgRule.java +++ b/vertx-pg-client/src/test/java/io/vertx/pgclient/junit/ContainerPgRule.java @@ -19,6 +19,7 @@ import io.vertx.pgclient.PgConnectOptions; import io.vertx.sqlclient.PoolOptions; import org.junit.rules.ExternalResource; +import org.testcontainers.containers.InternetProtocol; import org.testcontainers.containers.PostgreSQLContainer; import org.testcontainers.utility.MountableFile; @@ -29,6 +30,8 @@ import java.nio.file.Path; import java.nio.file.StandardCopyOption; +import static org.testcontainers.containers.PostgreSQLContainer.POSTGRESQL_PORT; + /** * Postgresql test database based on https://www.testcontainers.org * Require Docker @@ -41,7 +44,7 @@ public class ContainerPgRule extends ExternalResource { private static final String connectionUri = System.getProperty("connection.uri"); private static final String tlsConnectionUri = System.getProperty("tls.connection.uri"); - private PostgreSQLContainer server; + private ServerContainer server; private PgConnectOptions options; private String databaseVersion; private boolean ssl; @@ -63,7 +66,7 @@ public PoolOptions poolOptions() { private void initServer(String version) throws Exception { File setupFile = getTestResource("resources" + File.separator + "create-postgres.sql"); - server = (PostgreSQLContainer) new PostgreSQLContainer("postgres:" + version) + server = new ServerContainer<>("postgres:" + version) .withDatabaseName("postgres") .withUsername("postgres") .withPassword("postgres") @@ -73,6 +76,11 @@ private void initServer(String version) throws Exception { .withCopyFileToContainer(MountableFile.forHostPath(getTestResource("resources" + File.separator + "server.key").toPath()), "/server.key") .withCopyFileToContainer(MountableFile.forHostPath(getTestResource("ssl.sh").toPath()), "/docker-entrypoint-initdb.d/ssl.sh"); } + if (System.getProperties().containsKey("containerFixedPort")) { + server.withFixedExposedPort(POSTGRESQL_PORT, POSTGRESQL_PORT); + } else { + server.withExposedPorts(POSTGRESQL_PORT); + } } private static File getTestResource(String name) throws Exception { @@ -99,7 +107,7 @@ public synchronized PgConnectOptions startServer(String databaseVersion) throws server.start(); return new PgConnectOptions() - .setPort(server.getMappedPort(PostgreSQLContainer.POSTGRESQL_PORT)) + .setPort(server.getMappedPort(POSTGRESQL_PORT)) .setHost(server.getContainerIpAddress()) .setDatabase("postgres") .setUser("postgres") @@ -170,4 +178,15 @@ protected void after() { } } + private static class ServerContainer> extends PostgreSQLContainer { + + public ServerContainer(String dockerImageName) { + super(dockerImageName); + } + + public SELF withFixedExposedPort(int hostPort, int containerPort) { + super.addFixedExposedPort(hostPort, containerPort, InternetProtocol.TCP); + return self(); + } + } } From 21cc18c7b4ddfa78d52158b23df1088f49868dec Mon Sep 17 00:00:00 2001 From: Thomas Segismont Date: Thu, 17 Jun 2021 15:22:13 +0200 Subject: [PATCH 15/26] Converted TokenType enum to constants class Removes overhead of looking up value while no specific behavior is attached to it. Signed-off-by: Thomas Segismont --- .../codec/CloseStatementCommandCodec.java | 13 ++-- .../codec/ExtendedQueryCommandBaseCodec.java | 31 ++++----- .../impl/codec/InitCommandCodec.java | 17 ++--- .../impl/codec/SQLBatchCommandCodec.java | 26 ++++--- .../mssqlclient/impl/codec/TokenType.java | 43 ++++++++++++ .../token/DataPacketStreamTokenType.java | 67 ------------------- 6 files changed, 81 insertions(+), 116 deletions(-) create mode 100644 vertx-mssql-client/src/main/java/io/vertx/mssqlclient/impl/codec/TokenType.java delete mode 100644 vertx-mssql-client/src/main/java/io/vertx/mssqlclient/impl/protocol/token/DataPacketStreamTokenType.java diff --git a/vertx-mssql-client/src/main/java/io/vertx/mssqlclient/impl/codec/CloseStatementCommandCodec.java b/vertx-mssql-client/src/main/java/io/vertx/mssqlclient/impl/codec/CloseStatementCommandCodec.java index 29f081f81..47453b8e4 100644 --- a/vertx-mssql-client/src/main/java/io/vertx/mssqlclient/impl/codec/CloseStatementCommandCodec.java +++ b/vertx-mssql-client/src/main/java/io/vertx/mssqlclient/impl/codec/CloseStatementCommandCodec.java @@ -17,11 +17,11 @@ import io.vertx.mssqlclient.impl.protocol.MessageType; import io.vertx.mssqlclient.impl.protocol.TdsMessage; import io.vertx.mssqlclient.impl.protocol.client.rpc.ProcId; -import io.vertx.mssqlclient.impl.protocol.token.DataPacketStreamTokenType; import io.vertx.sqlclient.impl.command.CloseStatementCommand; import io.vertx.sqlclient.impl.command.CommandResponse; import static io.vertx.mssqlclient.impl.codec.DataType.INTN; +import static io.vertx.mssqlclient.impl.codec.TokenType.*; class CloseStatementCommandCodec extends MSSQLCommandCodec { @@ -44,18 +44,15 @@ void encode(TdsMessageEncoder encoder) { void decodeMessage(TdsMessage message, TdsMessageEncoder encoder) { ByteBuf messageBody = message.content(); while (messageBody.isReadable()) { - DataPacketStreamTokenType tokenType = DataPacketStreamTokenType.valueOf(messageBody.readUnsignedByte()); - if (tokenType == null) { - throw new UnsupportedOperationException("Unsupported token: " + tokenType); - } + int tokenType = messageBody.readUnsignedByte(); switch (tokenType) { - case ERROR_TOKEN: + case ERROR: handleErrorToken(messageBody); break; - case DONEPROC_TOKEN: + case DONEPROC: messageBody.skipBytes(12); break; - case RETURNSTATUS_TOKEN: + case RETURNSTATUS: messageBody.skipBytes(4); break; default: diff --git a/vertx-mssql-client/src/main/java/io/vertx/mssqlclient/impl/codec/ExtendedQueryCommandBaseCodec.java b/vertx-mssql-client/src/main/java/io/vertx/mssqlclient/impl/codec/ExtendedQueryCommandBaseCodec.java index bf331eef1..4d2a8dd4e 100644 --- a/vertx-mssql-client/src/main/java/io/vertx/mssqlclient/impl/codec/ExtendedQueryCommandBaseCodec.java +++ b/vertx-mssql-client/src/main/java/io/vertx/mssqlclient/impl/codec/ExtendedQueryCommandBaseCodec.java @@ -18,12 +18,12 @@ import io.vertx.mssqlclient.impl.protocol.TdsMessage; import io.vertx.mssqlclient.impl.protocol.client.rpc.ProcId; import io.vertx.mssqlclient.impl.protocol.server.DoneToken; -import io.vertx.mssqlclient.impl.protocol.token.DataPacketStreamTokenType; import io.vertx.sqlclient.data.NullValue; import io.vertx.sqlclient.impl.TupleInternal; import io.vertx.sqlclient.impl.command.ExtendedQueryCommand; import static io.vertx.mssqlclient.impl.codec.DataType.*; +import static io.vertx.mssqlclient.impl.codec.TokenType.*; abstract class ExtendedQueryCommandBaseCodec extends QueryCommandBaseCodec> { @@ -48,38 +48,35 @@ void encode(TdsMessageEncoder encoder) { void decodeMessage(TdsMessage message, TdsMessageEncoder encoder) { ByteBuf messageBody = message.content(); while (messageBody.isReadable()) { - DataPacketStreamTokenType tokenType = DataPacketStreamTokenType.valueOf(messageBody.readUnsignedByte()); - if (tokenType == null) { - throw new UnsupportedOperationException("Unsupported token: " + tokenType); - } + int tokenType = messageBody.readUnsignedByte(); MSSQLPreparedStatement ps = (MSSQLPreparedStatement) cmd.preparedStatement(); switch (tokenType) { - case COLMETADATA_TOKEN: + case COLMETADATA: MSSQLRowDesc rowDesc = decodeColmetadataToken(messageBody); rowResultDecoder = new RowResultDecoder<>(cmd.collector(), rowDesc); break; - case ROW_TOKEN: + case ROW: handleRow(messageBody); break; - case NBCROW_TOKEN: + case NBCROW: handleNbcRow(messageBody); break; - case DONE_TOKEN: - messageBody.skipBytes(12); // this should only be after ERROR_TOKEN? + case DONE: + messageBody.skipBytes(12); // this should only be after ERROR? break; - case INFO_TOKEN: - case ORDER_TOKEN: + case INFO: + case ORDER: int tokenLength = messageBody.readUnsignedShortLE(); messageBody.skipBytes(tokenLength); break; - case ERROR_TOKEN: + case ERROR: handleErrorToken(messageBody); break; - case DONEPROC_TOKEN: + case DONEPROC: messageBody.skipBytes(12); handleResultSetDone(rowCount); break; - case DONEINPROC_TOKEN: + case DONEINPROC: short status = messageBody.readShortLE(); short curCmd = messageBody.readShortLE(); long doneRowCount = messageBody.readLongLE(); @@ -89,10 +86,10 @@ void decodeMessage(TdsMessage message, TdsMessageEncoder encoder) { handleResultSetDone((int) doneRowCount); } break; - case RETURNSTATUS_TOKEN: + case RETURNSTATUS: messageBody.skipBytes(4); break; - case RETURNVALUE_TOKEN: + case RETURNVALUE: if (ps.handle == 0) { messageBody.skipBytes(2); // skip ordinal position messageBody.skipBytes(2 * messageBody.readUnsignedByte()); // skip param name diff --git a/vertx-mssql-client/src/main/java/io/vertx/mssqlclient/impl/codec/InitCommandCodec.java b/vertx-mssql-client/src/main/java/io/vertx/mssqlclient/impl/codec/InitCommandCodec.java index 1f24603bc..ce2b4d7fe 100644 --- a/vertx-mssql-client/src/main/java/io/vertx/mssqlclient/impl/codec/InitCommandCodec.java +++ b/vertx-mssql-client/src/main/java/io/vertx/mssqlclient/impl/codec/InitCommandCodec.java @@ -18,13 +18,13 @@ import io.vertx.mssqlclient.impl.protocol.MessageType; import io.vertx.mssqlclient.impl.protocol.TdsMessage; import io.vertx.mssqlclient.impl.protocol.client.login.LoginPacket; -import io.vertx.mssqlclient.impl.protocol.token.DataPacketStreamTokenType; import io.vertx.mssqlclient.impl.utils.Utils; import io.vertx.sqlclient.impl.Connection; import io.vertx.sqlclient.impl.command.InitCommand; import java.util.Map; +import static io.vertx.mssqlclient.impl.codec.TokenType.*; import static java.nio.charset.StandardCharsets.UTF_16LE; class InitCommandCodec extends MSSQLCommandCodec { @@ -42,23 +42,20 @@ void encode(TdsMessageEncoder encoder) { void decodeMessage(TdsMessage message, TdsMessageEncoder encoder) { ByteBuf messageBody = message.content(); while (messageBody.isReadable()) { - DataPacketStreamTokenType tokenType = DataPacketStreamTokenType.valueOf(messageBody.readUnsignedByte()); - if (tokenType == null) { - continue; - } + int tokenType = messageBody.readUnsignedByte(); switch (tokenType) { //FIXME complete all the logic here - case LOGINACK_TOKEN: + case LOGINACK: result = cmd.connection(); break; - case ERROR_TOKEN: + case ERROR: handleErrorToken(messageBody); break; - case INFO_TOKEN: + case INFO: break; - case ENVCHANGE_TOKEN: + case ENVCHANGE: break; - case DONE_TOKEN: + case DONE: break; } } diff --git a/vertx-mssql-client/src/main/java/io/vertx/mssqlclient/impl/codec/SQLBatchCommandCodec.java b/vertx-mssql-client/src/main/java/io/vertx/mssqlclient/impl/codec/SQLBatchCommandCodec.java index 97b92b85e..850ee8f37 100644 --- a/vertx-mssql-client/src/main/java/io/vertx/mssqlclient/impl/codec/SQLBatchCommandCodec.java +++ b/vertx-mssql-client/src/main/java/io/vertx/mssqlclient/impl/codec/SQLBatchCommandCodec.java @@ -16,11 +16,12 @@ import io.vertx.mssqlclient.impl.protocol.MessageStatus; import io.vertx.mssqlclient.impl.protocol.MessageType; import io.vertx.mssqlclient.impl.protocol.TdsMessage; -import io.vertx.mssqlclient.impl.protocol.token.DataPacketStreamTokenType; import io.vertx.sqlclient.impl.command.SimpleQueryCommand; import java.nio.charset.StandardCharsets; +import static io.vertx.mssqlclient.impl.codec.TokenType.*; + class SQLBatchCommandCodec extends QueryCommandBaseCodec> { SQLBatchCommandCodec(SimpleQueryCommand cmd) { super(cmd); @@ -36,37 +37,34 @@ void encode(TdsMessageEncoder encoder) { void decodeMessage(TdsMessage message, TdsMessageEncoder encoder) { ByteBuf messageBody = message.content(); while (messageBody.isReadable()) { - DataPacketStreamTokenType tokenType = DataPacketStreamTokenType.valueOf(messageBody.readUnsignedByte()); - if (tokenType == null) { - throw new UnsupportedOperationException("Unsupported token: " + tokenType); - } + int tokenType = messageBody.readUnsignedByte(); switch (tokenType) { - case COLMETADATA_TOKEN: + case COLMETADATA: MSSQLRowDesc rowDesc = decodeColmetadataToken(messageBody); rowResultDecoder = new RowResultDecoder<>(cmd.collector(), rowDesc); break; - case ROW_TOKEN: + case ROW: handleRow(messageBody); break; - case NBCROW_TOKEN: + case NBCROW: handleNbcRow(messageBody); break; - case DONE_TOKEN: - case DONEPROC_TOKEN: + case DONE: + case DONEPROC: short status = messageBody.readShortLE(); short curCmd = messageBody.readShortLE(); long doneRowCount = messageBody.readLongLE(); handleResultSetDone((int) doneRowCount); break; - case INFO_TOKEN: - case ORDER_TOKEN: + case INFO: + case ORDER: int tokenLength = messageBody.readUnsignedShortLE(); messageBody.skipBytes(tokenLength); break; - case ERROR_TOKEN: + case ERROR: handleErrorToken(messageBody); break; - case ENVCHANGE_TOKEN: + case ENVCHANGE: handleEnvChangeToken(messageBody); break; default: diff --git a/vertx-mssql-client/src/main/java/io/vertx/mssqlclient/impl/codec/TokenType.java b/vertx-mssql-client/src/main/java/io/vertx/mssqlclient/impl/codec/TokenType.java new file mode 100644 index 000000000..5d7fa28bb --- /dev/null +++ b/vertx-mssql-client/src/main/java/io/vertx/mssqlclient/impl/codec/TokenType.java @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2011-2021 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.mssqlclient.impl.codec; + +@SuppressWarnings("unused") +public class TokenType { + + public static final int ALTMETADATA = 0x88; + public static final int ALTROW = 0xD3; + public static final int COLMETADATA = 0x81; + public static final int COLINFO = 0xA5; + public static final int DONE = 0xFD; + public static final int DONEPROC = 0xFE; + public static final int DONEINPROC = 0xFF; + public static final int ENVCHANGE = 0xE3; + public static final int ERROR = 0xAA; + public static final int FEATUREEXTACK = 0xAE; + public static final int FEDAUTHINFO = 0xEE; + public static final int INFO = 0xAB; + public static final int LOGINACK = 0xAD; + public static final int NBCROW = 0xD2; + public static final int ORDER = 0xA9; + public static final int RETURNSTATUS = 0x79; + public static final int RETURNVALUE = 0xAC; + public static final int ROW = 0xD1; + public static final int SESSIONSTATE = 0xE4; + public static final int SSPI = 0xED; + public static final int TABNAME = 0xA4; + public static final int OFFSET = 0x78; + + private TokenType() { + // Constants class + } +} diff --git a/vertx-mssql-client/src/main/java/io/vertx/mssqlclient/impl/protocol/token/DataPacketStreamTokenType.java b/vertx-mssql-client/src/main/java/io/vertx/mssqlclient/impl/protocol/token/DataPacketStreamTokenType.java deleted file mode 100644 index 26c760fb7..000000000 --- a/vertx-mssql-client/src/main/java/io/vertx/mssqlclient/impl/protocol/token/DataPacketStreamTokenType.java +++ /dev/null @@ -1,67 +0,0 @@ -/* - * Copyright (c) 2011-2021 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.mssqlclient.impl.protocol.token; - -import io.netty.util.collection.IntObjectHashMap; -import io.netty.util.collection.IntObjectMap; - -public enum DataPacketStreamTokenType { - - ALTMETADATA_TOKEN(0x88), - ALTROW_TOKEN(0xD3), - COLMETADATA_TOKEN(0x81), - COLINFO_TOKEN(0xA5), - DONE_TOKEN(0xFD), - DONEPROC_TOKEN(0xFE), - DONEINPROC_TOKEN(0xFF), - ENVCHANGE_TOKEN(0xE3), - ERROR_TOKEN(0xAA), - FEATUREEXTACK(0xAE), - FEDAUTHINFO_TOKEN(0xEE), - INFO_TOKEN(0xAB), - LOGINACK_TOKEN(0xAD), - NBCROW_TOKEN(0xD2), - ORDER_TOKEN(0xA9), - RETURNSTATUS_TOKEN(0x79), - RETURNVALUE_TOKEN(0xAC), - ROW_TOKEN(0xD1), - SESSIONSTATE_TOKEN(0xE4), - SSPI_TOKEN(0xED), - TABNAME_TOKEN(0xA4), - OFFSET_TOKEN(0x78); - - private final int value; - - private static final IntObjectMap lookup; - - static { - IntObjectMap map = new IntObjectHashMap<>(); - for (DataPacketStreamTokenType dataPacketStreamTokenType : DataPacketStreamTokenType.values()) { - if (map.put(dataPacketStreamTokenType.value(), dataPacketStreamTokenType) != null) { - throw new IllegalStateException("Duplicate key"); - } - } - lookup = map; - } - - DataPacketStreamTokenType(int value) { - this.value = value; - } - - public static DataPacketStreamTokenType valueOf(int value) { - return lookup.get(value); - } - - public int value() { - return value; - } -} From 65efa002dfd65c0ce99a0c4c7eb5ccb514c69ae7 Mon Sep 17 00:00:00 2001 From: Thomas Segismont Date: Thu, 17 Jun 2021 15:24:32 +0200 Subject: [PATCH 16/26] Moved EnvChange to codec package Signed-off-by: Thomas Segismont --- .../mssqlclient/impl/{protocol => codec}/EnvChange.java | 5 +++-- .../vertx/mssqlclient/impl/codec/QueryCommandBaseCodec.java | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) rename vertx-mssql-client/src/main/java/io/vertx/mssqlclient/impl/{protocol => codec}/EnvChange.java (90%) diff --git a/vertx-mssql-client/src/main/java/io/vertx/mssqlclient/impl/protocol/EnvChange.java b/vertx-mssql-client/src/main/java/io/vertx/mssqlclient/impl/codec/EnvChange.java similarity index 90% rename from vertx-mssql-client/src/main/java/io/vertx/mssqlclient/impl/protocol/EnvChange.java rename to vertx-mssql-client/src/main/java/io/vertx/mssqlclient/impl/codec/EnvChange.java index e9ceae7ad..e3f4b8cd5 100644 --- a/vertx-mssql-client/src/main/java/io/vertx/mssqlclient/impl/protocol/EnvChange.java +++ b/vertx-mssql-client/src/main/java/io/vertx/mssqlclient/impl/codec/EnvChange.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2011-2020 Contributors to the Eclipse Foundation + * Copyright (c) 2011-2021 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 @@ -9,8 +9,9 @@ * SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 */ -package io.vertx.mssqlclient.impl.protocol; +package io.vertx.mssqlclient.impl.codec; +@SuppressWarnings("unused") public class EnvChange { public static final int DATABASE = 1; diff --git a/vertx-mssql-client/src/main/java/io/vertx/mssqlclient/impl/codec/QueryCommandBaseCodec.java b/vertx-mssql-client/src/main/java/io/vertx/mssqlclient/impl/codec/QueryCommandBaseCodec.java index d16eb7422..46110285c 100644 --- a/vertx-mssql-client/src/main/java/io/vertx/mssqlclient/impl/codec/QueryCommandBaseCodec.java +++ b/vertx-mssql-client/src/main/java/io/vertx/mssqlclient/impl/codec/QueryCommandBaseCodec.java @@ -18,7 +18,7 @@ import java.util.stream.Collector; -import static io.vertx.mssqlclient.impl.protocol.EnvChange.*; +import static io.vertx.mssqlclient.impl.codec.EnvChange.*; import static io.vertx.mssqlclient.impl.utils.ByteBufUtils.readUnsignedByteLengthString; abstract class QueryCommandBaseCodec> extends MSSQLCommandCodec { From 0b75180710e2062426961d5a5fea3acd1578a4ad Mon Sep 17 00:00:00 2001 From: Thomas Segismont Date: Thu, 17 Jun 2021 15:33:15 +0200 Subject: [PATCH 17/26] Converted MessageStatus enum to constants class Removes overhead of looking up value while no specific behavior is attached to it. Signed-off-by: Thomas Segismont --- .../codec/CloseStatementCommandCodec.java | 5 ++- .../codec/ExtendedQueryCommandBaseCodec.java | 7 ++-- .../impl/codec/InitCommandCodec.java | 5 ++- .../impl/codec/PreLoginCommandCodec.java | 12 +++--- .../impl/codec/SQLBatchCommandCodec.java | 5 ++- .../impl/codec/TdsPacketDecoder.java | 9 ++-- .../impl/protocol/MessageStatus.java | 42 +++++-------------- .../mssqlclient/impl/protocol/TdsMessage.java | 10 ++--- .../mssqlclient/impl/protocol/TdsPacket.java | 10 ++--- 9 files changed, 44 insertions(+), 61 deletions(-) diff --git a/vertx-mssql-client/src/main/java/io/vertx/mssqlclient/impl/codec/CloseStatementCommandCodec.java b/vertx-mssql-client/src/main/java/io/vertx/mssqlclient/impl/codec/CloseStatementCommandCodec.java index 47453b8e4..ba5fd68d5 100644 --- a/vertx-mssql-client/src/main/java/io/vertx/mssqlclient/impl/codec/CloseStatementCommandCodec.java +++ b/vertx-mssql-client/src/main/java/io/vertx/mssqlclient/impl/codec/CloseStatementCommandCodec.java @@ -13,7 +13,6 @@ import io.netty.buffer.ByteBuf; import io.netty.channel.ChannelHandlerContext; -import io.vertx.mssqlclient.impl.protocol.MessageStatus; import io.vertx.mssqlclient.impl.protocol.MessageType; import io.vertx.mssqlclient.impl.protocol.TdsMessage; import io.vertx.mssqlclient.impl.protocol.client.rpc.ProcId; @@ -22,6 +21,8 @@ import static io.vertx.mssqlclient.impl.codec.DataType.INTN; import static io.vertx.mssqlclient.impl.codec.TokenType.*; +import static io.vertx.mssqlclient.impl.protocol.MessageStatus.END_OF_MESSAGE; +import static io.vertx.mssqlclient.impl.protocol.MessageStatus.NORMAL; class CloseStatementCommandCodec extends MSSQLCommandCodec { @@ -69,7 +70,7 @@ private void sendUnprepareRequest() { // packet header packet.writeByte(MessageType.RPC.value()); - packet.writeByte(MessageStatus.NORMAL.value() | MessageStatus.END_OF_MESSAGE.value()); + packet.writeByte(NORMAL | END_OF_MESSAGE); int packetLenIdx = packet.writerIndex(); packet.writeShort(0); // set length later packet.writeShort(0x00); diff --git a/vertx-mssql-client/src/main/java/io/vertx/mssqlclient/impl/codec/ExtendedQueryCommandBaseCodec.java b/vertx-mssql-client/src/main/java/io/vertx/mssqlclient/impl/codec/ExtendedQueryCommandBaseCodec.java index 4d2a8dd4e..14248b615 100644 --- a/vertx-mssql-client/src/main/java/io/vertx/mssqlclient/impl/codec/ExtendedQueryCommandBaseCodec.java +++ b/vertx-mssql-client/src/main/java/io/vertx/mssqlclient/impl/codec/ExtendedQueryCommandBaseCodec.java @@ -13,7 +13,6 @@ import io.netty.buffer.ByteBuf; import io.netty.channel.ChannelHandlerContext; -import io.vertx.mssqlclient.impl.protocol.MessageStatus; import io.vertx.mssqlclient.impl.protocol.MessageType; import io.vertx.mssqlclient.impl.protocol.TdsMessage; import io.vertx.mssqlclient.impl.protocol.client.rpc.ProcId; @@ -24,6 +23,8 @@ import static io.vertx.mssqlclient.impl.codec.DataType.*; import static io.vertx.mssqlclient.impl.codec.TokenType.*; +import static io.vertx.mssqlclient.impl.protocol.MessageStatus.END_OF_MESSAGE; +import static io.vertx.mssqlclient.impl.protocol.MessageStatus.NORMAL; abstract class ExtendedQueryCommandBaseCodec extends QueryCommandBaseCodec> { @@ -125,7 +126,7 @@ private void sendPrepexecRequest() { // packet header packet.writeByte(MessageType.RPC.value()); - packet.writeByte(MessageStatus.NORMAL.value() | MessageStatus.END_OF_MESSAGE.value()); + packet.writeByte(NORMAL | END_OF_MESSAGE); int packetLenIdx = packet.writerIndex(); packet.writeShort(0); // set length later packet.writeShort(0x00); @@ -178,7 +179,7 @@ void sendExecRequest() { // packet header packet.writeByte(MessageType.RPC.value()); - packet.writeByte(MessageStatus.NORMAL.value() | MessageStatus.END_OF_MESSAGE.value()); + packet.writeByte(NORMAL | END_OF_MESSAGE); int packetLenIdx = packet.writerIndex(); packet.writeShort(0); // set length later packet.writeShort(0x00); diff --git a/vertx-mssql-client/src/main/java/io/vertx/mssqlclient/impl/codec/InitCommandCodec.java b/vertx-mssql-client/src/main/java/io/vertx/mssqlclient/impl/codec/InitCommandCodec.java index ce2b4d7fe..f4433354a 100644 --- a/vertx-mssql-client/src/main/java/io/vertx/mssqlclient/impl/codec/InitCommandCodec.java +++ b/vertx-mssql-client/src/main/java/io/vertx/mssqlclient/impl/codec/InitCommandCodec.java @@ -14,7 +14,6 @@ import io.netty.buffer.ByteBuf; import io.netty.channel.ChannelHandlerContext; import io.vertx.mssqlclient.MSSQLConnectOptions; -import io.vertx.mssqlclient.impl.protocol.MessageStatus; import io.vertx.mssqlclient.impl.protocol.MessageType; import io.vertx.mssqlclient.impl.protocol.TdsMessage; import io.vertx.mssqlclient.impl.protocol.client.login.LoginPacket; @@ -25,6 +24,8 @@ import java.util.Map; import static io.vertx.mssqlclient.impl.codec.TokenType.*; +import static io.vertx.mssqlclient.impl.protocol.MessageStatus.END_OF_MESSAGE; +import static io.vertx.mssqlclient.impl.protocol.MessageStatus.NORMAL; import static java.nio.charset.StandardCharsets.UTF_16LE; class InitCommandCodec extends MSSQLCommandCodec { @@ -69,7 +70,7 @@ private void sendLoginMessage() { // packet header packet.writeByte(MessageType.TDS7_LOGIN.value()); - packet.writeByte(MessageStatus.NORMAL.value() | MessageStatus.END_OF_MESSAGE.value()); + packet.writeByte(NORMAL | END_OF_MESSAGE); int packetLenIdx = packet.writerIndex(); packet.writeShort(0); // set length later packet.writeShort(0x00); diff --git a/vertx-mssql-client/src/main/java/io/vertx/mssqlclient/impl/codec/PreLoginCommandCodec.java b/vertx-mssql-client/src/main/java/io/vertx/mssqlclient/impl/codec/PreLoginCommandCodec.java index 4394e8d45..5aa69073e 100644 --- a/vertx-mssql-client/src/main/java/io/vertx/mssqlclient/impl/codec/PreLoginCommandCodec.java +++ b/vertx-mssql-client/src/main/java/io/vertx/mssqlclient/impl/codec/PreLoginCommandCodec.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2011-2019 Contributors to the Eclipse Foundation + * Copyright (c) 2011-2021 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 @@ -11,19 +11,21 @@ package io.vertx.mssqlclient.impl.codec; +import io.netty.buffer.ByteBuf; +import io.netty.channel.ChannelHandlerContext; import io.vertx.mssqlclient.impl.command.PreLoginCommand; -import io.vertx.mssqlclient.impl.protocol.MessageStatus; import io.vertx.mssqlclient.impl.protocol.MessageType; import io.vertx.mssqlclient.impl.protocol.TdsMessage; import io.vertx.mssqlclient.impl.protocol.client.prelogin.EncryptionOptionToken; import io.vertx.mssqlclient.impl.protocol.client.prelogin.OptionToken; import io.vertx.mssqlclient.impl.protocol.client.prelogin.VersionOptionToken; -import io.netty.buffer.ByteBuf; -import io.netty.channel.ChannelHandlerContext; import io.vertx.sqlclient.impl.command.CommandResponse; import java.util.List; +import static io.vertx.mssqlclient.impl.protocol.MessageStatus.END_OF_MESSAGE; +import static io.vertx.mssqlclient.impl.protocol.MessageStatus.NORMAL; + class PreLoginCommandCodec extends MSSQLCommandCodec { PreLoginCommandCodec(PreLoginCommand cmd) { @@ -49,7 +51,7 @@ private void sendPreLoginMessage() { // packet header packet.writeByte(MessageType.PRE_LOGIN.value()); - packet.writeByte(MessageStatus.NORMAL.value() | MessageStatus.END_OF_MESSAGE.value()); + packet.writeByte(NORMAL | END_OF_MESSAGE); int packetLenIdx = packet.writerIndex(); packet.writeShort(0); // set length later packet.writeShort(0x00); diff --git a/vertx-mssql-client/src/main/java/io/vertx/mssqlclient/impl/codec/SQLBatchCommandCodec.java b/vertx-mssql-client/src/main/java/io/vertx/mssqlclient/impl/codec/SQLBatchCommandCodec.java index 850ee8f37..eaa8990ed 100644 --- a/vertx-mssql-client/src/main/java/io/vertx/mssqlclient/impl/codec/SQLBatchCommandCodec.java +++ b/vertx-mssql-client/src/main/java/io/vertx/mssqlclient/impl/codec/SQLBatchCommandCodec.java @@ -13,7 +13,6 @@ import io.netty.buffer.ByteBuf; import io.netty.channel.ChannelHandlerContext; -import io.vertx.mssqlclient.impl.protocol.MessageStatus; import io.vertx.mssqlclient.impl.protocol.MessageType; import io.vertx.mssqlclient.impl.protocol.TdsMessage; import io.vertx.sqlclient.impl.command.SimpleQueryCommand; @@ -21,6 +20,8 @@ import java.nio.charset.StandardCharsets; import static io.vertx.mssqlclient.impl.codec.TokenType.*; +import static io.vertx.mssqlclient.impl.protocol.MessageStatus.END_OF_MESSAGE; +import static io.vertx.mssqlclient.impl.protocol.MessageStatus.NORMAL; class SQLBatchCommandCodec extends QueryCommandBaseCodec> { SQLBatchCommandCodec(SimpleQueryCommand cmd) { @@ -81,7 +82,7 @@ private void sendBatchClientRequest() { // packet header packet.writeByte(MessageType.SQL_BATCH.value()); - packet.writeByte(MessageStatus.NORMAL.value() | MessageStatus.END_OF_MESSAGE.value()); + packet.writeByte(NORMAL | END_OF_MESSAGE); int packetLenIdx = packet.writerIndex(); packet.writeShort(0); // set length later packet.writeShort(0x00); diff --git a/vertx-mssql-client/src/main/java/io/vertx/mssqlclient/impl/codec/TdsPacketDecoder.java b/vertx-mssql-client/src/main/java/io/vertx/mssqlclient/impl/codec/TdsPacketDecoder.java index 7fef1893c..824bca1a8 100644 --- a/vertx-mssql-client/src/main/java/io/vertx/mssqlclient/impl/codec/TdsPacketDecoder.java +++ b/vertx-mssql-client/src/main/java/io/vertx/mssqlclient/impl/codec/TdsPacketDecoder.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2011-2019 Contributors to the Eclipse Foundation + * Copyright (c) 2011-2021 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 @@ -11,12 +11,11 @@ package io.vertx.mssqlclient.impl.codec; -import io.vertx.mssqlclient.impl.protocol.MessageStatus; -import io.vertx.mssqlclient.impl.protocol.MessageType; -import io.vertx.mssqlclient.impl.protocol.TdsPacket; import io.netty.buffer.ByteBuf; import io.netty.channel.ChannelHandlerContext; import io.netty.handler.codec.ByteToMessageDecoder; +import io.vertx.mssqlclient.impl.protocol.MessageType; +import io.vertx.mssqlclient.impl.protocol.TdsPacket; import java.util.List; @@ -30,7 +29,7 @@ protected void decode(ChannelHandlerContext channelHandlerContext, ByteBuf in, L if (in.readableBytes() >= packetLen) { MessageType type = MessageType.valueOf(in.readUnsignedByte()); - MessageStatus status = MessageStatus.valueOf(in.readUnsignedByte()); + int status = in.readUnsignedByte(); in.skipBytes(2); // packet length int processId = in.readUnsignedShort(); short packetId = in.readUnsignedByte(); diff --git a/vertx-mssql-client/src/main/java/io/vertx/mssqlclient/impl/protocol/MessageStatus.java b/vertx-mssql-client/src/main/java/io/vertx/mssqlclient/impl/protocol/MessageStatus.java index a92f72d7c..5c0eb83a9 100644 --- a/vertx-mssql-client/src/main/java/io/vertx/mssqlclient/impl/protocol/MessageStatus.java +++ b/vertx-mssql-client/src/main/java/io/vertx/mssqlclient/impl/protocol/MessageStatus.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2011-2019 Contributors to the Eclipse Foundation + * Copyright (c) 2011-2021 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 @@ -11,38 +11,16 @@ package io.vertx.mssqlclient.impl.protocol; -public enum MessageStatus { +@SuppressWarnings("unused") +public class MessageStatus { - NORMAL(0x00), - END_OF_MESSAGE(0x01), - IGNORE_THIS_EVENT(0x02), - RESET_CONNECTION(0x08), - RESET_CONNECTION_SKIP_TRAN(0x10); + public static final int NORMAL = 0x00; + public static final int END_OF_MESSAGE = 0x01; + public static final int IGNORE_THIS_EVENT = 0x02; + public static final int RESET_CONNECTION = 0x08; + public static final int RESET_CONNECTION_SKIP_TRAN = 0x10; - private final int value; - - MessageStatus(int value) { - this.value = value; - } - - public static MessageStatus valueOf(int value) { - switch (value) { - case 0x00: - return NORMAL; - case 0x01: - return END_OF_MESSAGE; - case 0x02: - return IGNORE_THIS_EVENT; - case 0x08: - return RESET_CONNECTION; - case 0x10: - return RESET_CONNECTION_SKIP_TRAN; - default: - throw new IllegalArgumentException("Unknown message status value"); - } - } - - public int value() { - return this.value; + private MessageStatus() { + // Constants class } } diff --git a/vertx-mssql-client/src/main/java/io/vertx/mssqlclient/impl/protocol/TdsMessage.java b/vertx-mssql-client/src/main/java/io/vertx/mssqlclient/impl/protocol/TdsMessage.java index 6bacccbcd..108529f88 100644 --- a/vertx-mssql-client/src/main/java/io/vertx/mssqlclient/impl/protocol/TdsMessage.java +++ b/vertx-mssql-client/src/main/java/io/vertx/mssqlclient/impl/protocol/TdsMessage.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2011-2019 Contributors to the Eclipse Foundation + * Copyright (c) 2011-2021 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 @@ -16,17 +16,17 @@ public final class TdsMessage extends DefaultByteBufHolder { private final MessageType type; - private final MessageStatus status; + private final int status; private final int processId; - private TdsMessage(MessageType type, MessageStatus status, int processId, ByteBuf data) { + private TdsMessage(MessageType type, int status, int processId, ByteBuf data) { super(data); this.type = type; this.status = status; this.processId = processId; } - public static TdsMessage newTdsMessage(MessageType type, MessageStatus status, int processId, ByteBuf data) { + public static TdsMessage newTdsMessage(MessageType type, int status, int processId, ByteBuf data) { return new TdsMessage(type, status, processId, data); } @@ -39,7 +39,7 @@ public MessageType type() { return type; } - public MessageStatus status() { + public int status() { return status; } diff --git a/vertx-mssql-client/src/main/java/io/vertx/mssqlclient/impl/protocol/TdsPacket.java b/vertx-mssql-client/src/main/java/io/vertx/mssqlclient/impl/protocol/TdsPacket.java index 4d472fa52..1188e0b6a 100644 --- a/vertx-mssql-client/src/main/java/io/vertx/mssqlclient/impl/protocol/TdsPacket.java +++ b/vertx-mssql-client/src/main/java/io/vertx/mssqlclient/impl/protocol/TdsPacket.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2011-2019 Contributors to the Eclipse Foundation + * Copyright (c) 2011-2021 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 @@ -19,12 +19,12 @@ public final class TdsPacket extends DefaultByteBufHolder { public static final int MAX_PACKET_DATA_SIZE = 0xFFFF - 8; private final MessageType type; - private final MessageStatus status; + private final int status; private final int length; private final int processId; private final short packetId; - private TdsPacket(MessageType type, MessageStatus status, int length, int processId, short packetId, ByteBuf data) { + private TdsPacket(MessageType type, int status, int length, int processId, short packetId, ByteBuf data) { super(data); this.type = type; this.status = status; @@ -33,7 +33,7 @@ private TdsPacket(MessageType type, MessageStatus status, int length, int proces this.packetId = packetId; } - public static TdsPacket newTdsPacket(MessageType type, MessageStatus status, int length, int processId, short packetId, ByteBuf data) { + public static TdsPacket newTdsPacket(MessageType type, int status, int length, int processId, short packetId, ByteBuf data) { return new TdsPacket(type, status, length, processId, packetId, data); } @@ -41,7 +41,7 @@ public MessageType type() { return type; } - public MessageStatus status() { + public int status() { return status; } From 1f651134bb1777b66b430cd0547c5499a926159a Mon Sep 17 00:00:00 2001 From: Thomas Segismont Date: Thu, 17 Jun 2021 15:33:52 +0200 Subject: [PATCH 18/26] Fixup Signed-off-by: Thomas Segismont --- .../mssqlclient/impl/codec/CloseStatementCommandCodec.java | 4 ++-- .../mssqlclient/impl/codec/ExtendedQueryCommandBaseCodec.java | 4 ++-- .../io/vertx/mssqlclient/impl/codec/InitCommandCodec.java | 4 ++-- .../io/vertx/mssqlclient/impl/codec/MSSQLCommandCodec.java | 1 - .../mssqlclient/impl/{protocol => codec}/MessageStatus.java | 2 +- .../io/vertx/mssqlclient/impl/codec/PreLoginCommandCodec.java | 4 ++-- .../io/vertx/mssqlclient/impl/codec/SQLBatchCommandCodec.java | 4 ++-- .../io/vertx/mssqlclient/impl/codec/TdsMessageDecoder.java | 1 - 8 files changed, 11 insertions(+), 13 deletions(-) rename vertx-mssql-client/src/main/java/io/vertx/mssqlclient/impl/{protocol => codec}/MessageStatus.java (94%) diff --git a/vertx-mssql-client/src/main/java/io/vertx/mssqlclient/impl/codec/CloseStatementCommandCodec.java b/vertx-mssql-client/src/main/java/io/vertx/mssqlclient/impl/codec/CloseStatementCommandCodec.java index ba5fd68d5..f1fe9d592 100644 --- a/vertx-mssql-client/src/main/java/io/vertx/mssqlclient/impl/codec/CloseStatementCommandCodec.java +++ b/vertx-mssql-client/src/main/java/io/vertx/mssqlclient/impl/codec/CloseStatementCommandCodec.java @@ -21,8 +21,8 @@ import static io.vertx.mssqlclient.impl.codec.DataType.INTN; import static io.vertx.mssqlclient.impl.codec.TokenType.*; -import static io.vertx.mssqlclient.impl.protocol.MessageStatus.END_OF_MESSAGE; -import static io.vertx.mssqlclient.impl.protocol.MessageStatus.NORMAL; +import static io.vertx.mssqlclient.impl.codec.MessageStatus.END_OF_MESSAGE; +import static io.vertx.mssqlclient.impl.codec.MessageStatus.NORMAL; class CloseStatementCommandCodec extends MSSQLCommandCodec { diff --git a/vertx-mssql-client/src/main/java/io/vertx/mssqlclient/impl/codec/ExtendedQueryCommandBaseCodec.java b/vertx-mssql-client/src/main/java/io/vertx/mssqlclient/impl/codec/ExtendedQueryCommandBaseCodec.java index 14248b615..e6e528738 100644 --- a/vertx-mssql-client/src/main/java/io/vertx/mssqlclient/impl/codec/ExtendedQueryCommandBaseCodec.java +++ b/vertx-mssql-client/src/main/java/io/vertx/mssqlclient/impl/codec/ExtendedQueryCommandBaseCodec.java @@ -23,8 +23,8 @@ import static io.vertx.mssqlclient.impl.codec.DataType.*; import static io.vertx.mssqlclient.impl.codec.TokenType.*; -import static io.vertx.mssqlclient.impl.protocol.MessageStatus.END_OF_MESSAGE; -import static io.vertx.mssqlclient.impl.protocol.MessageStatus.NORMAL; +import static io.vertx.mssqlclient.impl.codec.MessageStatus.END_OF_MESSAGE; +import static io.vertx.mssqlclient.impl.codec.MessageStatus.NORMAL; abstract class ExtendedQueryCommandBaseCodec extends QueryCommandBaseCodec> { diff --git a/vertx-mssql-client/src/main/java/io/vertx/mssqlclient/impl/codec/InitCommandCodec.java b/vertx-mssql-client/src/main/java/io/vertx/mssqlclient/impl/codec/InitCommandCodec.java index f4433354a..edc52377d 100644 --- a/vertx-mssql-client/src/main/java/io/vertx/mssqlclient/impl/codec/InitCommandCodec.java +++ b/vertx-mssql-client/src/main/java/io/vertx/mssqlclient/impl/codec/InitCommandCodec.java @@ -24,8 +24,8 @@ import java.util.Map; import static io.vertx.mssqlclient.impl.codec.TokenType.*; -import static io.vertx.mssqlclient.impl.protocol.MessageStatus.END_OF_MESSAGE; -import static io.vertx.mssqlclient.impl.protocol.MessageStatus.NORMAL; +import static io.vertx.mssqlclient.impl.codec.MessageStatus.END_OF_MESSAGE; +import static io.vertx.mssqlclient.impl.codec.MessageStatus.NORMAL; import static java.nio.charset.StandardCharsets.UTF_16LE; class InitCommandCodec extends MSSQLCommandCodec { diff --git a/vertx-mssql-client/src/main/java/io/vertx/mssqlclient/impl/codec/MSSQLCommandCodec.java b/vertx-mssql-client/src/main/java/io/vertx/mssqlclient/impl/codec/MSSQLCommandCodec.java index 26f5048f7..cefa98b9a 100644 --- a/vertx-mssql-client/src/main/java/io/vertx/mssqlclient/impl/codec/MSSQLCommandCodec.java +++ b/vertx-mssql-client/src/main/java/io/vertx/mssqlclient/impl/codec/MSSQLCommandCodec.java @@ -14,7 +14,6 @@ import io.netty.buffer.ByteBuf; import io.vertx.core.Handler; import io.vertx.mssqlclient.MSSQLException; -import io.vertx.mssqlclient.impl.protocol.MessageStatus; import io.vertx.mssqlclient.impl.protocol.MessageType; import io.vertx.mssqlclient.impl.protocol.TdsMessage; import io.vertx.sqlclient.impl.command.CommandBase; diff --git a/vertx-mssql-client/src/main/java/io/vertx/mssqlclient/impl/protocol/MessageStatus.java b/vertx-mssql-client/src/main/java/io/vertx/mssqlclient/impl/codec/MessageStatus.java similarity index 94% rename from vertx-mssql-client/src/main/java/io/vertx/mssqlclient/impl/protocol/MessageStatus.java rename to vertx-mssql-client/src/main/java/io/vertx/mssqlclient/impl/codec/MessageStatus.java index 5c0eb83a9..c6d3a2907 100644 --- a/vertx-mssql-client/src/main/java/io/vertx/mssqlclient/impl/protocol/MessageStatus.java +++ b/vertx-mssql-client/src/main/java/io/vertx/mssqlclient/impl/codec/MessageStatus.java @@ -9,7 +9,7 @@ * SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 */ -package io.vertx.mssqlclient.impl.protocol; +package io.vertx.mssqlclient.impl.codec; @SuppressWarnings("unused") public class MessageStatus { diff --git a/vertx-mssql-client/src/main/java/io/vertx/mssqlclient/impl/codec/PreLoginCommandCodec.java b/vertx-mssql-client/src/main/java/io/vertx/mssqlclient/impl/codec/PreLoginCommandCodec.java index 5aa69073e..84b401557 100644 --- a/vertx-mssql-client/src/main/java/io/vertx/mssqlclient/impl/codec/PreLoginCommandCodec.java +++ b/vertx-mssql-client/src/main/java/io/vertx/mssqlclient/impl/codec/PreLoginCommandCodec.java @@ -23,8 +23,8 @@ import java.util.List; -import static io.vertx.mssqlclient.impl.protocol.MessageStatus.END_OF_MESSAGE; -import static io.vertx.mssqlclient.impl.protocol.MessageStatus.NORMAL; +import static io.vertx.mssqlclient.impl.codec.MessageStatus.END_OF_MESSAGE; +import static io.vertx.mssqlclient.impl.codec.MessageStatus.NORMAL; class PreLoginCommandCodec extends MSSQLCommandCodec { diff --git a/vertx-mssql-client/src/main/java/io/vertx/mssqlclient/impl/codec/SQLBatchCommandCodec.java b/vertx-mssql-client/src/main/java/io/vertx/mssqlclient/impl/codec/SQLBatchCommandCodec.java index eaa8990ed..cfdde2fc7 100644 --- a/vertx-mssql-client/src/main/java/io/vertx/mssqlclient/impl/codec/SQLBatchCommandCodec.java +++ b/vertx-mssql-client/src/main/java/io/vertx/mssqlclient/impl/codec/SQLBatchCommandCodec.java @@ -20,8 +20,8 @@ import java.nio.charset.StandardCharsets; import static io.vertx.mssqlclient.impl.codec.TokenType.*; -import static io.vertx.mssqlclient.impl.protocol.MessageStatus.END_OF_MESSAGE; -import static io.vertx.mssqlclient.impl.protocol.MessageStatus.NORMAL; +import static io.vertx.mssqlclient.impl.codec.MessageStatus.END_OF_MESSAGE; +import static io.vertx.mssqlclient.impl.codec.MessageStatus.NORMAL; class SQLBatchCommandCodec extends QueryCommandBaseCodec> { SQLBatchCommandCodec(SimpleQueryCommand cmd) { diff --git a/vertx-mssql-client/src/main/java/io/vertx/mssqlclient/impl/codec/TdsMessageDecoder.java b/vertx-mssql-client/src/main/java/io/vertx/mssqlclient/impl/codec/TdsMessageDecoder.java index 934d63800..c70d98c75 100644 --- a/vertx-mssql-client/src/main/java/io/vertx/mssqlclient/impl/codec/TdsMessageDecoder.java +++ b/vertx-mssql-client/src/main/java/io/vertx/mssqlclient/impl/codec/TdsMessageDecoder.java @@ -14,7 +14,6 @@ import io.netty.buffer.CompositeByteBuf; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelInboundHandlerAdapter; -import io.vertx.mssqlclient.impl.protocol.MessageStatus; import io.vertx.mssqlclient.impl.protocol.TdsMessage; import io.vertx.mssqlclient.impl.protocol.TdsPacket; import io.vertx.sqlclient.impl.command.CommandBase; From 0e5d3d3328b6b6f048ee02a054600c1a1c284469 Mon Sep 17 00:00:00 2001 From: Thomas Segismont Date: Thu, 17 Jun 2021 15:46:38 +0200 Subject: [PATCH 19/26] Converted MessageType enum to constants class Removes overhead of looking up value while no specific behavior is attached to it. Signed-off-by: Thomas Segismont --- .../codec/CloseStatementCommandCodec.java | 6 +- .../codec/ExtendedQueryCommandBaseCodec.java | 8 +-- .../impl/codec/InitCommandCodec.java | 6 +- .../impl/codec/MSSQLCommandCodec.java | 1 - .../mssqlclient/impl/codec/MessageType.java | 32 +++++++++ .../impl/codec/PreLoginCommandCodec.java | 4 +- .../impl/codec/SQLBatchCommandCodec.java | 6 +- .../impl/codec/TdsPacketDecoder.java | 3 +- .../impl/protocol/MessageType.java | 66 ------------------- .../mssqlclient/impl/protocol/TdsMessage.java | 8 +-- .../mssqlclient/impl/protocol/TdsPacket.java | 8 +-- 11 files changed, 56 insertions(+), 92 deletions(-) create mode 100644 vertx-mssql-client/src/main/java/io/vertx/mssqlclient/impl/codec/MessageType.java delete mode 100644 vertx-mssql-client/src/main/java/io/vertx/mssqlclient/impl/protocol/MessageType.java diff --git a/vertx-mssql-client/src/main/java/io/vertx/mssqlclient/impl/codec/CloseStatementCommandCodec.java b/vertx-mssql-client/src/main/java/io/vertx/mssqlclient/impl/codec/CloseStatementCommandCodec.java index f1fe9d592..38474f552 100644 --- a/vertx-mssql-client/src/main/java/io/vertx/mssqlclient/impl/codec/CloseStatementCommandCodec.java +++ b/vertx-mssql-client/src/main/java/io/vertx/mssqlclient/impl/codec/CloseStatementCommandCodec.java @@ -13,16 +13,16 @@ import io.netty.buffer.ByteBuf; import io.netty.channel.ChannelHandlerContext; -import io.vertx.mssqlclient.impl.protocol.MessageType; import io.vertx.mssqlclient.impl.protocol.TdsMessage; import io.vertx.mssqlclient.impl.protocol.client.rpc.ProcId; import io.vertx.sqlclient.impl.command.CloseStatementCommand; import io.vertx.sqlclient.impl.command.CommandResponse; import static io.vertx.mssqlclient.impl.codec.DataType.INTN; -import static io.vertx.mssqlclient.impl.codec.TokenType.*; import static io.vertx.mssqlclient.impl.codec.MessageStatus.END_OF_MESSAGE; import static io.vertx.mssqlclient.impl.codec.MessageStatus.NORMAL; +import static io.vertx.mssqlclient.impl.codec.MessageType.RPC; +import static io.vertx.mssqlclient.impl.codec.TokenType.*; class CloseStatementCommandCodec extends MSSQLCommandCodec { @@ -69,7 +69,7 @@ private void sendUnprepareRequest() { ByteBuf packet = chctx.alloc().ioBuffer(); // packet header - packet.writeByte(MessageType.RPC.value()); + packet.writeByte(RPC); packet.writeByte(NORMAL | END_OF_MESSAGE); int packetLenIdx = packet.writerIndex(); packet.writeShort(0); // set length later diff --git a/vertx-mssql-client/src/main/java/io/vertx/mssqlclient/impl/codec/ExtendedQueryCommandBaseCodec.java b/vertx-mssql-client/src/main/java/io/vertx/mssqlclient/impl/codec/ExtendedQueryCommandBaseCodec.java index e6e528738..b335822e2 100644 --- a/vertx-mssql-client/src/main/java/io/vertx/mssqlclient/impl/codec/ExtendedQueryCommandBaseCodec.java +++ b/vertx-mssql-client/src/main/java/io/vertx/mssqlclient/impl/codec/ExtendedQueryCommandBaseCodec.java @@ -13,7 +13,6 @@ import io.netty.buffer.ByteBuf; import io.netty.channel.ChannelHandlerContext; -import io.vertx.mssqlclient.impl.protocol.MessageType; import io.vertx.mssqlclient.impl.protocol.TdsMessage; import io.vertx.mssqlclient.impl.protocol.client.rpc.ProcId; import io.vertx.mssqlclient.impl.protocol.server.DoneToken; @@ -22,9 +21,10 @@ import io.vertx.sqlclient.impl.command.ExtendedQueryCommand; import static io.vertx.mssqlclient.impl.codec.DataType.*; -import static io.vertx.mssqlclient.impl.codec.TokenType.*; import static io.vertx.mssqlclient.impl.codec.MessageStatus.END_OF_MESSAGE; import static io.vertx.mssqlclient.impl.codec.MessageStatus.NORMAL; +import static io.vertx.mssqlclient.impl.codec.MessageType.RPC; +import static io.vertx.mssqlclient.impl.codec.TokenType.*; abstract class ExtendedQueryCommandBaseCodec extends QueryCommandBaseCodec> { @@ -125,7 +125,7 @@ private void sendPrepexecRequest() { ByteBuf packet = chctx.alloc().ioBuffer(); // packet header - packet.writeByte(MessageType.RPC.value()); + packet.writeByte(RPC); packet.writeByte(NORMAL | END_OF_MESSAGE); int packetLenIdx = packet.writerIndex(); packet.writeShort(0); // set length later @@ -178,7 +178,7 @@ void sendExecRequest() { ByteBuf packet = chctx.alloc().ioBuffer(); // packet header - packet.writeByte(MessageType.RPC.value()); + packet.writeByte(RPC); packet.writeByte(NORMAL | END_OF_MESSAGE); int packetLenIdx = packet.writerIndex(); packet.writeShort(0); // set length later diff --git a/vertx-mssql-client/src/main/java/io/vertx/mssqlclient/impl/codec/InitCommandCodec.java b/vertx-mssql-client/src/main/java/io/vertx/mssqlclient/impl/codec/InitCommandCodec.java index edc52377d..f05aa9140 100644 --- a/vertx-mssql-client/src/main/java/io/vertx/mssqlclient/impl/codec/InitCommandCodec.java +++ b/vertx-mssql-client/src/main/java/io/vertx/mssqlclient/impl/codec/InitCommandCodec.java @@ -14,7 +14,6 @@ import io.netty.buffer.ByteBuf; import io.netty.channel.ChannelHandlerContext; import io.vertx.mssqlclient.MSSQLConnectOptions; -import io.vertx.mssqlclient.impl.protocol.MessageType; import io.vertx.mssqlclient.impl.protocol.TdsMessage; import io.vertx.mssqlclient.impl.protocol.client.login.LoginPacket; import io.vertx.mssqlclient.impl.utils.Utils; @@ -23,9 +22,10 @@ import java.util.Map; -import static io.vertx.mssqlclient.impl.codec.TokenType.*; import static io.vertx.mssqlclient.impl.codec.MessageStatus.END_OF_MESSAGE; import static io.vertx.mssqlclient.impl.codec.MessageStatus.NORMAL; +import static io.vertx.mssqlclient.impl.codec.MessageType.TDS7_LOGIN; +import static io.vertx.mssqlclient.impl.codec.TokenType.*; import static java.nio.charset.StandardCharsets.UTF_16LE; class InitCommandCodec extends MSSQLCommandCodec { @@ -69,7 +69,7 @@ private void sendLoginMessage() { ByteBuf packet = chctx.alloc().ioBuffer(); // packet header - packet.writeByte(MessageType.TDS7_LOGIN.value()); + packet.writeByte(TDS7_LOGIN); packet.writeByte(NORMAL | END_OF_MESSAGE); int packetLenIdx = packet.writerIndex(); packet.writeShort(0); // set length later diff --git a/vertx-mssql-client/src/main/java/io/vertx/mssqlclient/impl/codec/MSSQLCommandCodec.java b/vertx-mssql-client/src/main/java/io/vertx/mssqlclient/impl/codec/MSSQLCommandCodec.java index cefa98b9a..5bd547377 100644 --- a/vertx-mssql-client/src/main/java/io/vertx/mssqlclient/impl/codec/MSSQLCommandCodec.java +++ b/vertx-mssql-client/src/main/java/io/vertx/mssqlclient/impl/codec/MSSQLCommandCodec.java @@ -14,7 +14,6 @@ import io.netty.buffer.ByteBuf; import io.vertx.core.Handler; import io.vertx.mssqlclient.MSSQLException; -import io.vertx.mssqlclient.impl.protocol.MessageType; import io.vertx.mssqlclient.impl.protocol.TdsMessage; import io.vertx.sqlclient.impl.command.CommandBase; import io.vertx.sqlclient.impl.command.CommandResponse; diff --git a/vertx-mssql-client/src/main/java/io/vertx/mssqlclient/impl/codec/MessageType.java b/vertx-mssql-client/src/main/java/io/vertx/mssqlclient/impl/codec/MessageType.java new file mode 100644 index 000000000..93f457678 --- /dev/null +++ b/vertx-mssql-client/src/main/java/io/vertx/mssqlclient/impl/codec/MessageType.java @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2011-2021 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.mssqlclient.impl.codec; + +@SuppressWarnings("unused") +public class MessageType { + + public static final int SQL_BATCH = 1; + public static final int PRE_TDS7_LOGIN = 2; + public static final int RPC = 3; + public static final int TABULAR_RESULT = 4; + public static final int ATTENTION_SIGNAL = 6; + public static final int BULK_LOAD_DATA = 7; + public static final int FEDERATED_AUTHENTICATION_TOKEN = 8; + public static final int TRANSACTION_MANAGER_REQUEST = 14; + public static final int TDS7_LOGIN = 16; + public static final int SSPI = 17; + public static final int PRE_LOGIN = 18; + + private MessageType() { + // Constants class + } +} diff --git a/vertx-mssql-client/src/main/java/io/vertx/mssqlclient/impl/codec/PreLoginCommandCodec.java b/vertx-mssql-client/src/main/java/io/vertx/mssqlclient/impl/codec/PreLoginCommandCodec.java index 84b401557..1d0d023d6 100644 --- a/vertx-mssql-client/src/main/java/io/vertx/mssqlclient/impl/codec/PreLoginCommandCodec.java +++ b/vertx-mssql-client/src/main/java/io/vertx/mssqlclient/impl/codec/PreLoginCommandCodec.java @@ -14,7 +14,6 @@ import io.netty.buffer.ByteBuf; import io.netty.channel.ChannelHandlerContext; import io.vertx.mssqlclient.impl.command.PreLoginCommand; -import io.vertx.mssqlclient.impl.protocol.MessageType; import io.vertx.mssqlclient.impl.protocol.TdsMessage; import io.vertx.mssqlclient.impl.protocol.client.prelogin.EncryptionOptionToken; import io.vertx.mssqlclient.impl.protocol.client.prelogin.OptionToken; @@ -25,6 +24,7 @@ import static io.vertx.mssqlclient.impl.codec.MessageStatus.END_OF_MESSAGE; import static io.vertx.mssqlclient.impl.codec.MessageStatus.NORMAL; +import static io.vertx.mssqlclient.impl.codec.MessageType.PRE_LOGIN; class PreLoginCommandCodec extends MSSQLCommandCodec { @@ -50,7 +50,7 @@ private void sendPreLoginMessage() { ByteBuf packet = chctx.alloc().ioBuffer(); // packet header - packet.writeByte(MessageType.PRE_LOGIN.value()); + packet.writeByte(PRE_LOGIN); packet.writeByte(NORMAL | END_OF_MESSAGE); int packetLenIdx = packet.writerIndex(); packet.writeShort(0); // set length later diff --git a/vertx-mssql-client/src/main/java/io/vertx/mssqlclient/impl/codec/SQLBatchCommandCodec.java b/vertx-mssql-client/src/main/java/io/vertx/mssqlclient/impl/codec/SQLBatchCommandCodec.java index cfdde2fc7..da7e0156d 100644 --- a/vertx-mssql-client/src/main/java/io/vertx/mssqlclient/impl/codec/SQLBatchCommandCodec.java +++ b/vertx-mssql-client/src/main/java/io/vertx/mssqlclient/impl/codec/SQLBatchCommandCodec.java @@ -13,15 +13,15 @@ import io.netty.buffer.ByteBuf; import io.netty.channel.ChannelHandlerContext; -import io.vertx.mssqlclient.impl.protocol.MessageType; import io.vertx.mssqlclient.impl.protocol.TdsMessage; import io.vertx.sqlclient.impl.command.SimpleQueryCommand; import java.nio.charset.StandardCharsets; -import static io.vertx.mssqlclient.impl.codec.TokenType.*; import static io.vertx.mssqlclient.impl.codec.MessageStatus.END_OF_MESSAGE; import static io.vertx.mssqlclient.impl.codec.MessageStatus.NORMAL; +import static io.vertx.mssqlclient.impl.codec.MessageType.SQL_BATCH; +import static io.vertx.mssqlclient.impl.codec.TokenType.*; class SQLBatchCommandCodec extends QueryCommandBaseCodec> { SQLBatchCommandCodec(SimpleQueryCommand cmd) { @@ -81,7 +81,7 @@ private void sendBatchClientRequest() { ByteBuf packet = chctx.alloc().ioBuffer(); // packet header - packet.writeByte(MessageType.SQL_BATCH.value()); + packet.writeByte(SQL_BATCH); packet.writeByte(NORMAL | END_OF_MESSAGE); int packetLenIdx = packet.writerIndex(); packet.writeShort(0); // set length later diff --git a/vertx-mssql-client/src/main/java/io/vertx/mssqlclient/impl/codec/TdsPacketDecoder.java b/vertx-mssql-client/src/main/java/io/vertx/mssqlclient/impl/codec/TdsPacketDecoder.java index 824bca1a8..22b2bf22d 100644 --- a/vertx-mssql-client/src/main/java/io/vertx/mssqlclient/impl/codec/TdsPacketDecoder.java +++ b/vertx-mssql-client/src/main/java/io/vertx/mssqlclient/impl/codec/TdsPacketDecoder.java @@ -14,7 +14,6 @@ import io.netty.buffer.ByteBuf; import io.netty.channel.ChannelHandlerContext; import io.netty.handler.codec.ByteToMessageDecoder; -import io.vertx.mssqlclient.impl.protocol.MessageType; import io.vertx.mssqlclient.impl.protocol.TdsPacket; import java.util.List; @@ -28,7 +27,7 @@ protected void decode(ChannelHandlerContext channelHandlerContext, ByteBuf in, L int packetLen = in.getUnsignedShort(packetStartIdx + 2); if (in.readableBytes() >= packetLen) { - MessageType type = MessageType.valueOf(in.readUnsignedByte()); + int type = in.readUnsignedByte(); int status = in.readUnsignedByte(); in.skipBytes(2); // packet length int processId = in.readUnsignedShort(); diff --git a/vertx-mssql-client/src/main/java/io/vertx/mssqlclient/impl/protocol/MessageType.java b/vertx-mssql-client/src/main/java/io/vertx/mssqlclient/impl/protocol/MessageType.java deleted file mode 100644 index 50de6a05c..000000000 --- a/vertx-mssql-client/src/main/java/io/vertx/mssqlclient/impl/protocol/MessageType.java +++ /dev/null @@ -1,66 +0,0 @@ -/* - * Copyright (c) 2011-2019 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.mssqlclient.impl.protocol; - -public enum MessageType { - - SQL_BATCH(1), - PRE_TDS7_LOGIN(2), - RPC(3), - TABULAR_RESULT(4), - ATTENTION_SIGNAL(6), - BULK_LOAD_DATA(7), - FEDERATED_AUTHENTICATION_TOKEN(8), - TRANSACTION_MANAGER_REQUEST(14), - TDS7_LOGIN(16), - SSPI(17), - PRE_LOGIN(18); - - private final int value; - - MessageType(int value) { - this.value = value; - } - - public static MessageType valueOf(int value) { - switch (value) { - case 1: - return SQL_BATCH; - case 2: - return PRE_TDS7_LOGIN; - case 3: - return RPC; - case 4: - return TABULAR_RESULT; - case 6: - return ATTENTION_SIGNAL; - case 7: - return BULK_LOAD_DATA; - case 8: - return FEDERATED_AUTHENTICATION_TOKEN; - case 14: - return TRANSACTION_MANAGER_REQUEST; - case 16: - return TDS7_LOGIN; - case 17: - return SSPI; - case 18: - return PRE_LOGIN; - default: - throw new IllegalArgumentException("Unknown message type value"); - } - } - - public int value() { - return this.value; - } -} diff --git a/vertx-mssql-client/src/main/java/io/vertx/mssqlclient/impl/protocol/TdsMessage.java b/vertx-mssql-client/src/main/java/io/vertx/mssqlclient/impl/protocol/TdsMessage.java index 108529f88..ef5883fa4 100644 --- a/vertx-mssql-client/src/main/java/io/vertx/mssqlclient/impl/protocol/TdsMessage.java +++ b/vertx-mssql-client/src/main/java/io/vertx/mssqlclient/impl/protocol/TdsMessage.java @@ -15,18 +15,18 @@ import io.netty.buffer.DefaultByteBufHolder; public final class TdsMessage extends DefaultByteBufHolder { - private final MessageType type; + private final int type; private final int status; private final int processId; - private TdsMessage(MessageType type, int status, int processId, ByteBuf data) { + private TdsMessage(int type, int status, int processId, ByteBuf data) { super(data); this.type = type; this.status = status; this.processId = processId; } - public static TdsMessage newTdsMessage(MessageType type, int status, int processId, ByteBuf data) { + public static TdsMessage newTdsMessage(int type, int status, int processId, ByteBuf data) { return new TdsMessage(type, status, processId, data); } @@ -35,7 +35,7 @@ public static TdsMessage newTdsMessageFromSinglePacket(TdsPacket tdsPacket) { return newTdsMessage(tdsPacket.type(), tdsPacket.status(), tdsPacket.processId(), packetData); } - public MessageType type() { + public int type() { return type; } diff --git a/vertx-mssql-client/src/main/java/io/vertx/mssqlclient/impl/protocol/TdsPacket.java b/vertx-mssql-client/src/main/java/io/vertx/mssqlclient/impl/protocol/TdsPacket.java index 1188e0b6a..dc58c44bb 100644 --- a/vertx-mssql-client/src/main/java/io/vertx/mssqlclient/impl/protocol/TdsPacket.java +++ b/vertx-mssql-client/src/main/java/io/vertx/mssqlclient/impl/protocol/TdsPacket.java @@ -18,13 +18,13 @@ public final class TdsPacket extends DefaultByteBufHolder { public static final int PACKET_HEADER_SIZE = 8; public static final int MAX_PACKET_DATA_SIZE = 0xFFFF - 8; - private final MessageType type; + private final int type; private final int status; private final int length; private final int processId; private final short packetId; - private TdsPacket(MessageType type, int status, int length, int processId, short packetId, ByteBuf data) { + private TdsPacket(int type, int status, int length, int processId, short packetId, ByteBuf data) { super(data); this.type = type; this.status = status; @@ -33,11 +33,11 @@ private TdsPacket(MessageType type, int status, int length, int processId, short this.packetId = packetId; } - public static TdsPacket newTdsPacket(MessageType type, int status, int length, int processId, short packetId, ByteBuf data) { + public static TdsPacket newTdsPacket(int type, int status, int length, int processId, short packetId, ByteBuf data) { return new TdsPacket(type, status, length, processId, packetId, data); } - public MessageType type() { + public int type() { return type; } From fa01c25e156f0005668b72424736ce8abd7990d0 Mon Sep 17 00:00:00 2001 From: Thomas Segismont Date: Thu, 17 Jun 2021 15:47:48 +0200 Subject: [PATCH 20/26] Moved DoneToken to codec package Signed-off-by: Thomas Segismont --- .../impl/{protocol/server => codec}/DoneToken.java | 12 +++++++++--- .../impl/codec/ExtendedQueryCommandBaseCodec.java | 1 - 2 files changed, 9 insertions(+), 4 deletions(-) rename vertx-mssql-client/src/main/java/io/vertx/mssqlclient/impl/{protocol/server => codec}/DoneToken.java (77%) diff --git a/vertx-mssql-client/src/main/java/io/vertx/mssqlclient/impl/protocol/server/DoneToken.java b/vertx-mssql-client/src/main/java/io/vertx/mssqlclient/impl/codec/DoneToken.java similarity index 77% rename from vertx-mssql-client/src/main/java/io/vertx/mssqlclient/impl/protocol/server/DoneToken.java rename to vertx-mssql-client/src/main/java/io/vertx/mssqlclient/impl/codec/DoneToken.java index 820644db4..ba17ec786 100644 --- a/vertx-mssql-client/src/main/java/io/vertx/mssqlclient/impl/protocol/server/DoneToken.java +++ b/vertx-mssql-client/src/main/java/io/vertx/mssqlclient/impl/codec/DoneToken.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2011-2019 Contributors to the Eclipse Foundation + * Copyright (c) 2011-2021 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 @@ -9,9 +9,11 @@ * SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 */ -package io.vertx.mssqlclient.impl.protocol.server; +package io.vertx.mssqlclient.impl.codec; + +@SuppressWarnings("unused") +public class DoneToken { -public final class DoneToken { public static final short STATUS_DONE_FINAL = 0x00; public static final short STATUS_DONE_MORE = 0x1; public static final short STATUS_DONE_ERROR = 0x2; @@ -19,4 +21,8 @@ public final class DoneToken { public static final short STATUS_DONE_COUNT = 0x10; public static final short STATUS_DONE_ATTN = 0x20; public static final short STATUS_DONE_SRVERROR = 0x100; + + private DoneToken() { + // Constants class + } } diff --git a/vertx-mssql-client/src/main/java/io/vertx/mssqlclient/impl/codec/ExtendedQueryCommandBaseCodec.java b/vertx-mssql-client/src/main/java/io/vertx/mssqlclient/impl/codec/ExtendedQueryCommandBaseCodec.java index b335822e2..88c0abfd2 100644 --- a/vertx-mssql-client/src/main/java/io/vertx/mssqlclient/impl/codec/ExtendedQueryCommandBaseCodec.java +++ b/vertx-mssql-client/src/main/java/io/vertx/mssqlclient/impl/codec/ExtendedQueryCommandBaseCodec.java @@ -15,7 +15,6 @@ import io.netty.channel.ChannelHandlerContext; import io.vertx.mssqlclient.impl.protocol.TdsMessage; import io.vertx.mssqlclient.impl.protocol.client.rpc.ProcId; -import io.vertx.mssqlclient.impl.protocol.server.DoneToken; import io.vertx.sqlclient.data.NullValue; import io.vertx.sqlclient.impl.TupleInternal; import io.vertx.sqlclient.impl.command.ExtendedQueryCommand; From ef411466afc5b0da2bd806637632b4f1aaf81e30 Mon Sep 17 00:00:00 2001 From: Thomas Segismont Date: Thu, 17 Jun 2021 15:52:17 +0200 Subject: [PATCH 21/26] Moved ProcId to codec package Signed-off-by: Thomas Segismont --- .../impl/codec/CloseStatementCommandCodec.java | 1 - .../impl/codec/ExtendedQueryCommandBaseCodec.java | 1 - .../impl/{protocol/client/rpc => codec}/ProcId.java | 12 +++++++++--- 3 files changed, 9 insertions(+), 5 deletions(-) rename vertx-mssql-client/src/main/java/io/vertx/mssqlclient/impl/{protocol/client/rpc => codec}/ProcId.java (84%) diff --git a/vertx-mssql-client/src/main/java/io/vertx/mssqlclient/impl/codec/CloseStatementCommandCodec.java b/vertx-mssql-client/src/main/java/io/vertx/mssqlclient/impl/codec/CloseStatementCommandCodec.java index 38474f552..2d53b5b2e 100644 --- a/vertx-mssql-client/src/main/java/io/vertx/mssqlclient/impl/codec/CloseStatementCommandCodec.java +++ b/vertx-mssql-client/src/main/java/io/vertx/mssqlclient/impl/codec/CloseStatementCommandCodec.java @@ -14,7 +14,6 @@ import io.netty.buffer.ByteBuf; import io.netty.channel.ChannelHandlerContext; import io.vertx.mssqlclient.impl.protocol.TdsMessage; -import io.vertx.mssqlclient.impl.protocol.client.rpc.ProcId; import io.vertx.sqlclient.impl.command.CloseStatementCommand; import io.vertx.sqlclient.impl.command.CommandResponse; diff --git a/vertx-mssql-client/src/main/java/io/vertx/mssqlclient/impl/codec/ExtendedQueryCommandBaseCodec.java b/vertx-mssql-client/src/main/java/io/vertx/mssqlclient/impl/codec/ExtendedQueryCommandBaseCodec.java index 88c0abfd2..6caeae8f1 100644 --- a/vertx-mssql-client/src/main/java/io/vertx/mssqlclient/impl/codec/ExtendedQueryCommandBaseCodec.java +++ b/vertx-mssql-client/src/main/java/io/vertx/mssqlclient/impl/codec/ExtendedQueryCommandBaseCodec.java @@ -14,7 +14,6 @@ import io.netty.buffer.ByteBuf; import io.netty.channel.ChannelHandlerContext; import io.vertx.mssqlclient.impl.protocol.TdsMessage; -import io.vertx.mssqlclient.impl.protocol.client.rpc.ProcId; import io.vertx.sqlclient.data.NullValue; import io.vertx.sqlclient.impl.TupleInternal; import io.vertx.sqlclient.impl.command.ExtendedQueryCommand; diff --git a/vertx-mssql-client/src/main/java/io/vertx/mssqlclient/impl/protocol/client/rpc/ProcId.java b/vertx-mssql-client/src/main/java/io/vertx/mssqlclient/impl/codec/ProcId.java similarity index 84% rename from vertx-mssql-client/src/main/java/io/vertx/mssqlclient/impl/protocol/client/rpc/ProcId.java rename to vertx-mssql-client/src/main/java/io/vertx/mssqlclient/impl/codec/ProcId.java index 3594b3dbd..a3d620384 100644 --- a/vertx-mssql-client/src/main/java/io/vertx/mssqlclient/impl/protocol/client/rpc/ProcId.java +++ b/vertx-mssql-client/src/main/java/io/vertx/mssqlclient/impl/codec/ProcId.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2011-2019 Contributors to the Eclipse Foundation + * Copyright (c) 2011-2021 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 @@ -9,12 +9,14 @@ * SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 */ -package io.vertx.mssqlclient.impl.protocol.client.rpc; +package io.vertx.mssqlclient.impl.codec; /** * The number identifying the special stored procedure to be executed. */ -public final class ProcId { +@SuppressWarnings("unused") +public class ProcId { + public static final int Sp_Cursor = 1; public static final int Sp_CursorOpen = 2; public static final int Sp_cursorPrepare = 3; @@ -30,4 +32,8 @@ public final class ProcId { public static final int Sp_PrepExec = 13; public static final int Sp_PrepExecRpc = 14; public static final int Sp_Unprepare = 15; + + private ProcId() { + // Constants class + } } From 9b3add80220f26fbd99d721ba3ae4828aad7a839 Mon Sep 17 00:00:00 2001 From: Julien Viet Date: Mon, 21 Jun 2021 17:21:25 +0200 Subject: [PATCH 22/26] Implement Inet PostgreSQL data type support - fixes #992 --- vertx-pg-client/src/main/asciidoc/index.adoc | 1 + .../java/io/vertx/pgclient/data/Inet.java | 50 ++++++++++ .../vertx/pgclient/impl/codec/DataType.java | 3 +- .../pgclient/impl/codec/DataTypeCodec.java | 96 +++++++++++++++++++ .../io/vertx/pgclient/data/InetCodecTest.java | 81 ++++++++++++++++ 5 files changed, 230 insertions(+), 1 deletion(-) create mode 100644 vertx-pg-client/src/main/java/io/vertx/pgclient/data/Inet.java create mode 100644 vertx-pg-client/src/test/java/io/vertx/pgclient/data/InetCodecTest.java diff --git a/vertx-pg-client/src/main/asciidoc/index.adoc b/vertx-pg-client/src/main/asciidoc/index.adoc index 7c3b2b32d..8aaba4c3d 100644 --- a/vertx-pg-client/src/main/asciidoc/index.adoc +++ b/vertx-pg-client/src/main/asciidoc/index.adoc @@ -298,6 +298,7 @@ Currently the client supports the following PostgreSQL types * CIRCLE (`io.vertx.pgclient.data.Circle`) * TSVECTOR (`java.lang.String`) * TSQUERY (`java.lang.String`) +* INET (`io.vertx.pgclient.data.Inet`) Tuple decoding uses the above types when storing values, it also performs on the flu conversion the actual value when possible: diff --git a/vertx-pg-client/src/main/java/io/vertx/pgclient/data/Inet.java b/vertx-pg-client/src/main/java/io/vertx/pgclient/data/Inet.java new file mode 100644 index 000000000..96cca7ae3 --- /dev/null +++ b/vertx-pg-client/src/main/java/io/vertx/pgclient/data/Inet.java @@ -0,0 +1,50 @@ +package io.vertx.pgclient.data; + +import java.net.InetAddress; + +/** + * A PosgreSQL inet network address. + */ +public class Inet { + + private InetAddress address; + private Integer netmask; + + /** + * @return the inet address + */ + public InetAddress getAddress() { + return address; + } + + /** + * Set the inet address + * @param address + * @return a reference to this, so the API can be used fluently + */ + public Inet setAddress(InetAddress address) { + this.address = address; + return this; + } + + /** + * @return the optional netmask + */ + public Integer getNetmask() { + return netmask; + } + + /** + * Set a netmask. + * + * @param netmask the netmask + * @return a reference to this, so the API can be used fluently + */ + public Inet setNetmask(Integer netmask) { + if (netmask != null && (netmask < 0 || netmask > 255)) { + throw new IllegalArgumentException("Invalid netmask: " + netmask); + } + this.netmask = netmask; + return this; + } +} diff --git a/vertx-pg-client/src/main/java/io/vertx/pgclient/impl/codec/DataType.java b/vertx-pg-client/src/main/java/io/vertx/pgclient/impl/codec/DataType.java index 8eb18e5ba..6ba643e8f 100644 --- a/vertx-pg-client/src/main/java/io/vertx/pgclient/impl/codec/DataType.java +++ b/vertx-pg-client/src/main/java/io/vertx/pgclient/impl/codec/DataType.java @@ -24,6 +24,7 @@ import io.vertx.core.json.JsonObject; import io.vertx.pgclient.data.Box; import io.vertx.pgclient.data.Circle; +import io.vertx.pgclient.data.Inet; import io.vertx.pgclient.data.Line; import io.vertx.pgclient.data.LineSegment; import io.vertx.sqlclient.Tuple; @@ -93,7 +94,7 @@ public enum DataType { BYTEA(17, true, Buffer.class, JDBCType.BINARY, Tuple::getBuffer), BYTEA_ARRAY(1001, true, Buffer[].class, JDBCType.BINARY, Tuple::getArrayOfBuffers), MACADDR(829, true, Object.class, JDBCType.OTHER), - INET(869, true, Object[].class, JDBCType.OTHER), + INET(869, true, Inet.class, JDBCType.OTHER), CIDR(650, true, Object.class, JDBCType.OTHER), MACADDR8(774, true, Object[].class, JDBCType.OTHER), UUID(2950, true, UUID.class, JDBCType.OTHER, Tuple::getUUID), diff --git a/vertx-pg-client/src/main/java/io/vertx/pgclient/impl/codec/DataTypeCodec.java b/vertx-pg-client/src/main/java/io/vertx/pgclient/impl/codec/DataTypeCodec.java index 6f5f2c56e..3d2fea963 100644 --- a/vertx-pg-client/src/main/java/io/vertx/pgclient/impl/codec/DataTypeCodec.java +++ b/vertx-pg-client/src/main/java/io/vertx/pgclient/impl/codec/DataTypeCodec.java @@ -32,6 +32,10 @@ import io.vertx.core.json.JsonObject; import io.vertx.sqlclient.impl.codec.CommonCodec; +import java.net.Inet4Address; +import java.net.Inet6Address; +import java.net.InetAddress; +import java.net.UnknownHostException; import java.nio.charset.StandardCharsets; import java.time.*; import java.time.format.DateTimeFormatter; @@ -51,6 +55,8 @@ /** * @author Julien Viet * @author Emad Alblueshi + * + * See also https://www.npgsql.org/doc/dev/type-representations.html */ public class DataTypeCodec { @@ -338,6 +344,9 @@ public static void encodeBinary(DataType id, Object value, ByteBuf buff) { case TS_VECTOR_ARRAY: binaryEncodeArray((String[]) value, DataType.TS_VECTOR, buff); break; + case INET: + binaryEncodeInet((Inet) value, buff); + break; default: logger.debug("Data type " + id + " does not support binary encoding"); defaultEncodeBinary(value, buff); @@ -467,6 +476,8 @@ public static Object decodeBinary(DataType id, int index, int len, ByteBuf buff) return binaryDecodeTsVector(index, len, buff); case TS_VECTOR_ARRAY: return binaryDecodeArray(STRING_ARRAY_FACTORY, DataType.TS_VECTOR, index, len, buff); + case INET: + return binaryDecodeInet(index, len, buff); default: logger.debug("Data type " + id + " does not support binary decoding"); return defaultDecodeBinary(index, len, buff); @@ -599,6 +610,8 @@ public static Object decodeText(DataType id, int index, int len, ByteBuf buff) { return textDecodeTsVector(index, len, buff); case TS_VECTOR_ARRAY: return textDecodeArray(STRING_ARRAY_FACTORY, DataType.TS_VECTOR, index, len, buff); + case INET: + return textDecodeInet(index, len, buff); default: return defaultDecodeText(index, len, buff); } @@ -1357,6 +1370,64 @@ private static void binaryEncodeTsVector(String value, ByteBuf buff) { buff.writeCharSequence(String.valueOf(value), StandardCharsets.UTF_8); } + private static Inet binaryDecodeInet(int index, int len, ByteBuf buff) { + byte family = buff.getByte(index); + byte netmask = buff.getByte(index + 1); + Integer val; + int size = buff.getByte(index + 3); + byte[] data = new byte[size]; + buff.getBytes(index + 4, data); + InetAddress address; + switch (family) { + case 2: + // IPV4 + try { + address = Inet4Address.getByAddress(data); + } catch (UnknownHostException e) { + throw new DecoderException(e); + } + val = netmask == 32 ? null : Byte.toUnsignedInt(netmask); + break; + case 3: + // IPV6 + try { + address = Inet6Address.getByAddress(data); + } catch (UnknownHostException e) { + throw new DecoderException(e); + } + val = netmask == -128 ? null : Byte.toUnsignedInt(netmask); + break; + default: + throw new DecoderException("Invalid ip family: " + family); + } + return new Inet().setAddress(address).setNetmask(val); + } + + private static void binaryEncodeInet(Inet value, ByteBuf buff) { + InetAddress address = value.getAddress(); + byte family; + byte[] data; + int netmask; + if (address instanceof Inet6Address) { + family = 3; + Inet6Address inet6Address = (Inet6Address) address; + data = inet6Address.getAddress(); + netmask = value.getNetmask() == null ? 128 : value.getNetmask(); + } else if (address instanceof Inet4Address) { + family = 2; + Inet4Address inet4Address = (Inet4Address) address; + data = inet4Address.getAddress(); + netmask = value.getNetmask() == null ? 32 : value.getNetmask(); + } else { + throw new DecoderException("Invalid inet address"); + } + buff.writeByte(family); + buff.writeByte(netmask); + buff.writeByte(0); // INET + buff.writeByte(data.length); + buff.writeBytes(data); + } + private static String binaryDecodeTsQuery(int index, int len, ByteBuf buff) { return buff.getCharSequence(index, len, StandardCharsets.UTF_8).toString(); } @@ -1373,6 +1444,31 @@ private static String textDecodeTsQuery(int index, int len, ByteBuf buff) { return buff.getCharSequence(index, len, StandardCharsets.UTF_8).toString(); } + private static Inet textDecodeInet(int index, int len, ByteBuf buff) { + Inet inet = new Inet(); + int sepIdx = buff.indexOf(index, index + len, (byte) '/'); + String s; + if (sepIdx == -1) { + s = textdecodeTEXT(index, len, buff); + } else { + s = textdecodeTEXT(index, sepIdx - index, buff); + String t = textdecodeTEXT(sepIdx + 1, len - ((sepIdx + 1 - index)), buff); + try { + int netmask = Integer.parseInt(t); + inet.setNetmask(netmask); + } catch (NumberFormatException e) { + throw new DecoderException(e); + } + } + try { + InetAddress v = InetAddress.getByName(s); + inet.setAddress(v); + } catch (UnknownHostException e) { + throw new DecoderException(e); + } + return inet; + } + /** * Decode the specified {@code buff} formatted as an hex string starting at the buffer readable index * with the specified {@code length} to a {@link Buffer}. diff --git a/vertx-pg-client/src/test/java/io/vertx/pgclient/data/InetCodecTest.java b/vertx-pg-client/src/test/java/io/vertx/pgclient/data/InetCodecTest.java new file mode 100644 index 000000000..7530a0cf3 --- /dev/null +++ b/vertx-pg-client/src/test/java/io/vertx/pgclient/data/InetCodecTest.java @@ -0,0 +1,81 @@ +package io.vertx.pgclient.data; + +import io.vertx.ext.unit.TestContext; +import io.vertx.pgclient.PgConnection; +import io.vertx.sqlclient.Query; +import io.vertx.sqlclient.Row; +import io.vertx.sqlclient.RowSet; +import io.vertx.sqlclient.SqlClient; +import io.vertx.sqlclient.Tuple; +import org.junit.Test; + +import java.net.Inet4Address; +import java.net.Inet6Address; +import java.net.InetAddress; +import java.util.function.BiFunction; + +public class InetCodecTest extends DataTypeTestBase { + + @Test + public void testBinaryDecodeINET(TestContext ctx) throws Exception { + testDecodeINET(ctx, SqlClient::preparedQuery); + } + + @Test + public void testTextDecodeINET(TestContext ctx) throws Exception { + testDecodeINET(ctx, SqlClient::query); + } + + private void testDecodeINET(TestContext ctx, BiFunction>> a) throws Exception { + InetAddress addr1 = Inet4Address.getByName("0.1.2.3"); + InetAddress addr2 = Inet6Address.getByName("2001:0db8:0a0b:12f0:0000:0000:0000:0001"); + PgConnection.connect(vertx, options, ctx.asyncAssertSuccess(conn -> { + a.apply(conn, "SELECT " + + "'0.1.2.3'::INET," + + "'0.1.2.3/4'::INET," + + "'2001:0db8:0a0b:12f0:0000:0000:0000:0001'::INET," + + "'2001:0db8:0a0b:12f0:0000:0000:0000:0001/4'::INET").execute(ctx.asyncAssertSuccess(rows -> { + ctx.assertEquals(1, rows.size()); + Row row = rows.iterator().next(); + Inet v1 = (Inet) row.getValue(0); + Inet v2 = (Inet) row.getValue(1); + Inet v3 = (Inet) row.getValue(2); + Inet v4 = (Inet) row.getValue(3); + ctx.assertEquals(addr1, v1.getAddress()); + ctx.assertNull(v1.getNetmask()); + ctx.assertEquals(addr1, v2.getAddress()); + ctx.assertEquals(4, v2.getNetmask()); + ctx.assertEquals(addr2, v3.getAddress()); + ctx.assertNull(v3.getNetmask()); + ctx.assertEquals(addr2, v4.getAddress()); + ctx.assertEquals(4, v4.getNetmask()); + })); + })); + } + + @Test + public void testBinaryEncodeINET(TestContext ctx) throws Exception { + InetAddress addr1 = Inet4Address.getByName("0.1.2.3"); + InetAddress addr2 = Inet6Address.getByName("2001:0db8:0a0b:12f0:0000:0000:0000:0001"); + PgConnection.connect(vertx, options, ctx.asyncAssertSuccess(conn -> { + conn.preparedQuery("SELECT ($1::INET)::VARCHAR, ($2::INET)::VARCHAR, ($3::INET)::VARCHAR, ($4::INET)::VARCHAR").execute(Tuple.of( + new Inet().setAddress(addr1), + new Inet().setAddress(addr1).setNetmask(4), + new Inet().setAddress(addr2), + new Inet().setAddress(addr2).setNetmask(4) + ), + ctx.asyncAssertSuccess(rows -> { + ctx.assertEquals(1, rows.size()); + Row row = rows.iterator().next(); + String v1 = row.getString(0); + String v2 = row.getString(1); + String v3 = row.getString(2); + String v4 = row.getString(3); + ctx.assertEquals("0.1.2.3/32", v1); + ctx.assertEquals("0.1.2.3/4", v2); + ctx.assertEquals("2001:db8:a0b:12f0::1/128", v3); + ctx.assertEquals("2001:db8:a0b:12f0::1/4", v4); + })); + })); + } +} From 4502b194ad357edbe76966903ffa29668feb8a41 Mon Sep 17 00:00:00 2001 From: Julien Viet Date: Mon, 21 Jun 2021 18:17:27 +0200 Subject: [PATCH 23/26] Support INET array --- .../vertx/pgclient/impl/codec/DataType.java | 1 + .../pgclient/impl/codec/DataTypeCodec.java | 9 +++++ .../io/vertx/pgclient/data/InetCodecTest.java | 38 +++++++++++++++++++ 3 files changed, 48 insertions(+) diff --git a/vertx-pg-client/src/main/java/io/vertx/pgclient/impl/codec/DataType.java b/vertx-pg-client/src/main/java/io/vertx/pgclient/impl/codec/DataType.java index 6ba643e8f..cfbbb4684 100644 --- a/vertx-pg-client/src/main/java/io/vertx/pgclient/impl/codec/DataType.java +++ b/vertx-pg-client/src/main/java/io/vertx/pgclient/impl/codec/DataType.java @@ -95,6 +95,7 @@ public enum DataType { BYTEA_ARRAY(1001, true, Buffer[].class, JDBCType.BINARY, Tuple::getArrayOfBuffers), MACADDR(829, true, Object.class, JDBCType.OTHER), INET(869, true, Inet.class, JDBCType.OTHER), + INET_ARRAY(1041, true, Inet[].class, JDBCType.OTHER), CIDR(650, true, Object.class, JDBCType.OTHER), MACADDR8(774, true, Object[].class, JDBCType.OTHER), UUID(2950, true, UUID.class, JDBCType.OTHER, Tuple::getUUID), diff --git a/vertx-pg-client/src/main/java/io/vertx/pgclient/impl/codec/DataTypeCodec.java b/vertx-pg-client/src/main/java/io/vertx/pgclient/impl/codec/DataTypeCodec.java index 3d2fea963..61478ad11 100644 --- a/vertx-pg-client/src/main/java/io/vertx/pgclient/impl/codec/DataTypeCodec.java +++ b/vertx-pg-client/src/main/java/io/vertx/pgclient/impl/codec/DataTypeCodec.java @@ -89,6 +89,7 @@ public class DataTypeCodec { private static final LocalDate LOCAL_DATE_EPOCH = LocalDate.of(2000, 1, 1); private static final LocalDateTime LOCAL_DATE_TIME_EPOCH = LocalDateTime.of(2000, 1, 1, 0, 0, 0); private static final OffsetDateTime OFFSET_DATE_TIME_EPOCH = LocalDateTime.of(2000, 1, 1, 0, 0, 0).atOffset(ZoneOffset.UTC); + private static final Inet[] empty_inet_array = new Inet[0]; // Sentinel used when an object is refused by the data type public static final Object REFUSED_SENTINEL = new Object(); @@ -117,6 +118,7 @@ public class DataTypeCodec { private static final IntFunction POLYGON_ARRAY_FACTORY = size -> size == 0 ? empty_polygon_array : new Polygon[size]; private static final IntFunction CIRCLE_ARRAY_FACTORY = size -> size == 0 ? empty_circle_array : new Circle[size]; private static final IntFunction INTERVAL_ARRAY_FACTORY = size -> size == 0 ? empty_interval_array : new Interval[size]; + private static final IntFunction INET_ARRAY_FACTORY = size -> size == 0 ? empty_inet_array : new Inet[size]; private static final java.time.format.DateTimeFormatter TIMETZ_FORMAT = new DateTimeFormatterBuilder() .parseCaseInsensitive() @@ -347,6 +349,9 @@ public static void encodeBinary(DataType id, Object value, ByteBuf buff) { case INET: binaryEncodeInet((Inet) value, buff); break; + case INET_ARRAY: + binaryEncodeArray((Inet[]) value, DataType.INET, buff); + break; default: logger.debug("Data type " + id + " does not support binary encoding"); defaultEncodeBinary(value, buff); @@ -478,6 +483,8 @@ public static Object decodeBinary(DataType id, int index, int len, ByteBuf buff) return binaryDecodeArray(STRING_ARRAY_FACTORY, DataType.TS_VECTOR, index, len, buff); case INET: return binaryDecodeInet(index, len, buff); + case INET_ARRAY: + return binaryDecodeArray(INET_ARRAY_FACTORY, DataType.INET, index, len, buff); default: logger.debug("Data type " + id + " does not support binary decoding"); return defaultDecodeBinary(index, len, buff); @@ -612,6 +619,8 @@ public static Object decodeText(DataType id, int index, int len, ByteBuf buff) { return textDecodeArray(STRING_ARRAY_FACTORY, DataType.TS_VECTOR, index, len, buff); case INET: return textDecodeInet(index, len, buff); + case INET_ARRAY: + return textDecodeArray(INET_ARRAY_FACTORY, DataType.INET, index, len, buff); default: return defaultDecodeText(index, len, buff); } diff --git a/vertx-pg-client/src/test/java/io/vertx/pgclient/data/InetCodecTest.java b/vertx-pg-client/src/test/java/io/vertx/pgclient/data/InetCodecTest.java index 7530a0cf3..93f93b37c 100644 --- a/vertx-pg-client/src/test/java/io/vertx/pgclient/data/InetCodecTest.java +++ b/vertx-pg-client/src/test/java/io/vertx/pgclient/data/InetCodecTest.java @@ -78,4 +78,42 @@ public void testBinaryEncodeINET(TestContext ctx) throws Exception { })); })); } + + @Test + public void testBinaryDecodeINETArray(TestContext ctx) throws Exception { + InetAddress addr1 = Inet4Address.getByName("0.1.2.3"); + PgConnection.connect(vertx, options, ctx.asyncAssertSuccess(conn -> { + conn.preparedQuery("SELECT ARRAY['0.1.2.3'::INET,'0.1.2.3/4'::INET]") + .execute(ctx.asyncAssertSuccess(rows -> { + ctx.assertEquals(1, rows.size()); + Row row = rows.iterator().next(); + Inet[] array = (Inet[]) row.getValue(0); + Inet v1 = array[0]; + Inet v2 = array[1]; + ctx.assertEquals(addr1, v1.getAddress()); + ctx.assertNull(v1.getNetmask()); + ctx.assertEquals(addr1, v2.getAddress()); + ctx.assertEquals(4, v2.getNetmask()); + })); + })); + } + + @Test + public void testBinaryEncodeINETArray(TestContext ctx) throws Exception { + InetAddress addr1 = Inet4Address.getByName("0.1.2.3"); + PgConnection.connect(vertx, options, ctx.asyncAssertSuccess(conn -> { + conn.preparedQuery("SELECT ($1::INET[])::VARCHAR[]").execute(Tuple.of( + new Inet[]{new Inet().setAddress(addr1), new Inet().setAddress(addr1).setNetmask(4)} + ), + ctx.asyncAssertSuccess(rows -> { + ctx.assertEquals(1, rows.size()); + Row row = rows.iterator().next(); + String[] array = row.getArrayOfStrings(0); + String v1 = array[0]; + String v2 = array[1]; + ctx.assertEquals("0.1.2.3/32", v1); + ctx.assertEquals("0.1.2.3/4", v2); + })); + })); + } } From 6f91a36b2be0ddb9f990699c2a1104300527d2f6 Mon Sep 17 00:00:00 2001 From: Sergey Prytkov Date: Mon, 8 Nov 2021 17:46:07 +0300 Subject: [PATCH 24/26] Multi-host connection factory with configurable server type preferences --- .../pgclient/PgConnectOptionsConverter.java | 6 + .../io/vertx/pgclient/PgConnectOptions.java | 25 ++ .../java/io/vertx/pgclient/PgConnection.java | 1 + .../pgclient/impl/PgConnectionFactory.java | 29 +- .../vertx/pgclient/impl/PgConnectionImpl.java | 1 + .../io/vertx/pgclient/impl/PgPoolImpl.java | 9 +- .../pgclient/impl/PgSocketConnection.java | 4 + .../pgclient/impl/codec/InitCommandCodec.java | 6 + .../io/vertx/pgclient/PgConnectionTest.java | 9 + .../java/io/vertx/pgclient/PgPoolTest.java | 1 + .../pgclient/PgPoolWithRequirementTest.java | 159 +++++++++ .../vertx/pgclient/junit/ContainerPgRule.java | 9 +- .../vertx/sqlclient/PoolOptionsConverter.java | 8 + .../java/io/vertx/sqlclient/PoolOptions.java | 27 +- .../io/vertx/sqlclient/ServerRequirement.java | 8 + .../java/io/vertx/sqlclient/ServerType.java | 7 + .../impl/pool/SqlConnectionPool.java | 2 +- .../sqlclient/spi/ConnectionFactory.java | 147 +++++++- .../sqlclient/spi/ConnectionFactoryTest.java | 322 ++++++++++++++++++ 19 files changed, 759 insertions(+), 21 deletions(-) create mode 100644 vertx-pg-client/src/test/java/io/vertx/pgclient/PgPoolWithRequirementTest.java create mode 100644 vertx-sql-client/src/main/java/io/vertx/sqlclient/ServerRequirement.java create mode 100644 vertx-sql-client/src/main/java/io/vertx/sqlclient/ServerType.java create mode 100644 vertx-sql-client/src/test/java/io/vertx/sqlclient/spi/ConnectionFactoryTest.java diff --git a/vertx-pg-client/src/main/generated/io/vertx/pgclient/PgConnectOptionsConverter.java b/vertx-pg-client/src/main/generated/io/vertx/pgclient/PgConnectOptionsConverter.java index 2f7b5ffbb..6a1bf27d0 100644 --- a/vertx-pg-client/src/main/generated/io/vertx/pgclient/PgConnectOptionsConverter.java +++ b/vertx-pg-client/src/main/generated/io/vertx/pgclient/PgConnectOptionsConverter.java @@ -25,6 +25,11 @@ public static void fromJson(Iterable> json, obj.setPipeliningLimit(((Number)member.getValue()).intValue()); } break; + case "shouldQueryServerType": + if (member.getValue() instanceof Boolean) { + obj.setShouldQueryServerType((Boolean)member.getValue()); + } + break; case "sslMode": if (member.getValue() instanceof String) { obj.setSslMode(io.vertx.pgclient.SslMode.valueOf((String)member.getValue())); @@ -42,6 +47,7 @@ public static void toJson(PgConnectOptions obj, JsonObject json) { public static void toJson(PgConnectOptions obj, java.util.Map json) { json.put("pipeliningLimit", obj.getPipeliningLimit()); + json.put("shouldQueryServerType", obj.getShouldQueryServerType()); if (obj.getSslMode() != null) { json.put("sslMode", obj.getSslMode().name()); } diff --git a/vertx-pg-client/src/main/java/io/vertx/pgclient/PgConnectOptions.java b/vertx-pg-client/src/main/java/io/vertx/pgclient/PgConnectOptions.java index 649704824..4cb1ad3fa 100644 --- a/vertx-pg-client/src/main/java/io/vertx/pgclient/PgConnectOptions.java +++ b/vertx-pg-client/src/main/java/io/vertx/pgclient/PgConnectOptions.java @@ -111,6 +111,7 @@ public static PgConnectOptions fromEnv() { public static final String DEFAULT_PASSWORD = "pass"; public static final int DEFAULT_PIPELINING_LIMIT = 256; public static final SslMode DEFAULT_SSLMODE = SslMode.DISABLE; + public static final boolean DEFAULT_SHOULD_QUERY_SERVER_TYPE = false; public static final Map DEFAULT_PROPERTIES; static { @@ -123,6 +124,7 @@ public static PgConnectOptions fromEnv() { } private int pipeliningLimit = DEFAULT_PIPELINING_LIMIT; + private boolean shouldQueryServerType = DEFAULT_SHOULD_QUERY_SERVER_TYPE; private SslMode sslMode = DEFAULT_SSLMODE; public PgConnectOptions() { @@ -224,6 +226,13 @@ public SslMode getSslMode() { return sslMode; } + /** + * @return the value of current shouldQueryServerType + */ + public boolean getShouldQueryServerType() { + return shouldQueryServerType; + } + /** * Set {@link SslMode} for the client, this option can be used to provide different levels of secure protection. * @@ -235,6 +244,19 @@ public PgConnectOptions setSslMode(SslMode sslmode) { return this; } + /** + * Set whether the client should query server type, + * In positive case, connection should issue implementation specific query + * to read {@link io.vertx.sqlclient.ServerType} of host being connected to + * + * @param shouldQueryServerType the value of shouldQueryServerType + * @return a reference to this, so the API can be used fluently + */ + public PgConnectOptions setShouldQueryServerType(boolean shouldQueryServerType) { + this.shouldQueryServerType = shouldQueryServerType; + return this; + } + @Override public PgConnectOptions setSendBufferSize(int sendBufferSize) { return (PgConnectOptions)super.setSendBufferSize(sendBufferSize); @@ -464,6 +486,7 @@ protected void init() { this.setUser(DEFAULT_USER); this.setPassword(DEFAULT_PASSWORD); this.setDatabase(DEFAULT_DATABASE); + this.setShouldQueryServerType(DEFAULT_SHOULD_QUERY_SERVER_TYPE); this.setProperties(new HashMap<>(DEFAULT_PROPERTIES)); } @@ -493,6 +516,7 @@ public boolean equals(Object o) { if (pipeliningLimit != that.pipeliningLimit) return false; if (sslMode != that.sslMode) return false; + if (shouldQueryServerType != that.shouldQueryServerType) return false; return true; } @@ -502,6 +526,7 @@ public int hashCode() { int result = super.hashCode(); result = 31 * result + pipeliningLimit; result = 31 * result + sslMode.hashCode(); + result = 31 * result + (shouldQueryServerType ? 1 : 0); return result; } diff --git a/vertx-pg-client/src/main/java/io/vertx/pgclient/PgConnection.java b/vertx-pg-client/src/main/java/io/vertx/pgclient/PgConnection.java index 1b684fa3c..ddbbd4a78 100644 --- a/vertx-pg-client/src/main/java/io/vertx/pgclient/PgConnection.java +++ b/vertx-pg-client/src/main/java/io/vertx/pgclient/PgConnection.java @@ -19,6 +19,7 @@ import io.vertx.core.impl.ContextInternal; import io.vertx.pgclient.impl.PgConnectionImpl; +import io.vertx.sqlclient.ServerType; import io.vertx.sqlclient.PreparedStatement; import io.vertx.sqlclient.SqlConnection; import io.vertx.codegen.annotations.Fluent; diff --git a/vertx-pg-client/src/main/java/io/vertx/pgclient/impl/PgConnectionFactory.java b/vertx-pg-client/src/main/java/io/vertx/pgclient/impl/PgConnectionFactory.java index 17fa28afb..0fe55ce6f 100644 --- a/vertx-pg-client/src/main/java/io/vertx/pgclient/impl/PgConnectionFactory.java +++ b/vertx-pg-client/src/main/java/io/vertx/pgclient/impl/PgConnectionFactory.java @@ -32,12 +32,18 @@ import io.vertx.core.net.impl.NetSocketInternal; import io.vertx.pgclient.PgConnectOptions; import io.vertx.pgclient.SslMode; +import io.vertx.sqlclient.ServerType; import io.vertx.sqlclient.SqlConnectOptions; import io.vertx.sqlclient.SqlConnection; import io.vertx.sqlclient.impl.Connection; import io.vertx.sqlclient.impl.ConnectionFactoryBase; import io.vertx.sqlclient.impl.tracing.QueryTracer; +import java.util.Collections; +import java.util.stream.Stream; + +import static io.vertx.sqlclient.ServerType.*; + /** * @author Julien Viet */ @@ -45,6 +51,8 @@ public class PgConnectionFactory extends ConnectionFactoryBase { private SslMode sslMode; private int pipeliningLimit; + private boolean shouldQueryServerType; + private ServerType serverType = UNDEFINED; public PgConnectionFactory(VertxInternal context, PgConnectOptions options) { super(context, options); @@ -55,6 +63,7 @@ protected void initializeConfiguration(SqlConnectOptions connectOptions) { PgConnectOptions options = (PgConnectOptions) connectOptions; this.pipeliningLimit = options.getPipeliningLimit(); this.sslMode = options.isUsingDomainSocket() ? SslMode.DISABLE : options.getSslMode(); + this.shouldQueryServerType = options.getShouldQueryServerType(); // check ssl mode here switch (sslMode) { @@ -151,16 +160,32 @@ public Future connect(Context context) { ContextInternal contextInternal = (ContextInternal) context; PromiseInternal promise = contextInternal.promise(); connect(asEventLoopContext(contextInternal)) - .map(conn -> { + .flatMap(conn -> { QueryTracer tracer = contextInternal.tracer() == null ? null : new QueryTracer(contextInternal.tracer(), options); PgConnectionImpl pgConn = new PgConnectionImpl(this, contextInternal, conn, tracer, null); conn.init(pgConn); - return (SqlConnection)pgConn; + serverType = ((PgSocketConnection) conn).serverType; + if (serverType == UNDEFINED && shouldQueryServerType) { + String paramStatus = conn.getDatabaseMetaData().majorVersion() >= 14 ? "in_hot_standby" : "transaction_read_only"; + return pgConn.query(String.format("SHOW %s;", paramStatus)).execute().map(pgRowSet -> { + pgRowSet.forEach(row -> + serverType = "off".equalsIgnoreCase(row.getString(paramStatus)) ? PRIMARY : REPLICA + ); + return pgConn; + }); + } else { + return Future.succeededFuture((SqlConnection) pgConn); + } }) .onComplete(promise); return promise.future(); } + @Override + public ServerType getServerType() { + return serverType; + } + private PgSocketConnection newSocketConnection(EventLoopContext context, NetSocketInternal socket) { return new PgSocketConnection(socket, cachePreparedStatements, preparedStatementCacheSize, preparedStatementCacheSqlFilter, pipeliningLimit, context); } diff --git a/vertx-pg-client/src/main/java/io/vertx/pgclient/impl/PgConnectionImpl.java b/vertx-pg-client/src/main/java/io/vertx/pgclient/impl/PgConnectionImpl.java index 5d9e1b6c2..1e6b2de5c 100644 --- a/vertx-pg-client/src/main/java/io/vertx/pgclient/impl/PgConnectionImpl.java +++ b/vertx-pg-client/src/main/java/io/vertx/pgclient/impl/PgConnectionImpl.java @@ -21,6 +21,7 @@ import io.vertx.pgclient.PgConnectOptions; import io.vertx.pgclient.PgConnection; import io.vertx.pgclient.PgNotification; +import io.vertx.sqlclient.ServerType; import io.vertx.sqlclient.impl.Connection; import io.vertx.sqlclient.impl.Notification; import io.vertx.sqlclient.impl.SqlConnectionImpl; diff --git a/vertx-pg-client/src/main/java/io/vertx/pgclient/impl/PgPoolImpl.java b/vertx-pg-client/src/main/java/io/vertx/pgclient/impl/PgPoolImpl.java index 4db91c730..dff572ae0 100644 --- a/vertx-pg-client/src/main/java/io/vertx/pgclient/impl/PgPoolImpl.java +++ b/vertx-pg-client/src/main/java/io/vertx/pgclient/impl/PgPoolImpl.java @@ -28,6 +28,7 @@ import io.vertx.pgclient.*; import io.vertx.pgclient.spi.PgDriver; import io.vertx.sqlclient.PoolOptions; +import io.vertx.sqlclient.ServerRequirement; import io.vertx.sqlclient.SqlConnectOptions; import io.vertx.sqlclient.SqlConnection; import io.vertx.sqlclient.impl.Connection; @@ -71,8 +72,12 @@ public static PgPoolImpl create(final VertxInternal vertx, boolean pipelined, Li int pipeliningLimit = pipelined ? baseConnectOptions.getPipeliningLimit() : 1; PgPoolImpl pool = new PgPoolImpl(vx, baseConnectOptions, tracer, metrics, pipeliningLimit, poolOptions); PgDriver driver = new PgDriver(); - List lst = servers.stream().map(options -> driver.createConnectionFactory(vx, options)).collect(Collectors.toList()); - ConnectionFactory factory = ConnectionFactory.roundRobinSelector(lst); + List lst = servers.stream() + .map(options -> driver.createConnectionFactory(vx, PgConnectOptions.wrap(options) + .setShouldQueryServerType(poolOptions.getServerRequirement() != ServerRequirement.ANY)) + ) + .collect(Collectors.toList()); + ConnectionFactory factory = ConnectionFactory.selector(lst, poolOptions.getServerRequirement()); pool.connectionProvider(factory::connect); pool.init(); CloseFuture closeFuture = pool.closeFuture(); diff --git a/vertx-pg-client/src/main/java/io/vertx/pgclient/impl/PgSocketConnection.java b/vertx-pg-client/src/main/java/io/vertx/pgclient/impl/PgSocketConnection.java index fbbd6d2e1..a286cf8d2 100644 --- a/vertx-pg-client/src/main/java/io/vertx/pgclient/impl/PgSocketConnection.java +++ b/vertx-pg-client/src/main/java/io/vertx/pgclient/impl/PgSocketConnection.java @@ -28,6 +28,7 @@ import io.vertx.core.net.impl.NetSocketInternal; import io.vertx.pgclient.PgException; import io.vertx.pgclient.impl.codec.PgCodec; +import io.vertx.sqlclient.ServerType; import io.vertx.pgclient.impl.codec.TxFailedEvent; import io.vertx.sqlclient.impl.*; import io.vertx.sqlclient.impl.command.*; @@ -36,6 +37,8 @@ import java.util.Map; import java.util.function.Predicate; +import static io.vertx.sqlclient.ServerType.UNDEFINED; + /** * @author Julien Viet */ @@ -45,6 +48,7 @@ public class PgSocketConnection extends SocketConnectionBase { public int processId; public int secretKey; public PgDatabaseMetadata dbMetaData; + public ServerType serverType = UNDEFINED; public PgSocketConnection(NetSocketInternal socket, boolean cachePreparedStatements, diff --git a/vertx-pg-client/src/main/java/io/vertx/pgclient/impl/codec/InitCommandCodec.java b/vertx-pg-client/src/main/java/io/vertx/pgclient/impl/codec/InitCommandCodec.java index 522d9b38d..ff9b803ac 100644 --- a/vertx-pg-client/src/main/java/io/vertx/pgclient/impl/codec/InitCommandCodec.java +++ b/vertx-pg-client/src/main/java/io/vertx/pgclient/impl/codec/InitCommandCodec.java @@ -27,6 +27,9 @@ import io.vertx.sqlclient.impl.command.CommandResponse; import io.vertx.sqlclient.impl.command.InitCommand; +import static io.vertx.sqlclient.ServerType.PRIMARY; +import static io.vertx.sqlclient.ServerType.REPLICA; + class InitCommandCodec extends PgCommandCodec { private PgEncoder encoder; @@ -87,6 +90,9 @@ public void handleParameterStatus(String key, String value) { if(key.equals("server_version")) { ((PgSocketConnection)cmd.connection()).dbMetaData = new PgDatabaseMetadata(value); } + if(key.equals("in_hot_standby")) { + ((PgSocketConnection)cmd.connection()).serverType = "off".equalsIgnoreCase(value) ? PRIMARY : REPLICA; + } } @Override diff --git a/vertx-pg-client/src/test/java/io/vertx/pgclient/PgConnectionTest.java b/vertx-pg-client/src/test/java/io/vertx/pgclient/PgConnectionTest.java index e85e5e276..096eb12e9 100644 --- a/vertx-pg-client/src/test/java/io/vertx/pgclient/PgConnectionTest.java +++ b/vertx-pg-client/src/test/java/io/vertx/pgclient/PgConnectionTest.java @@ -48,6 +48,15 @@ public void testSettingSchema(TestContext ctx) { })); } + @Test + public void testPrimary(TestContext ctx) { + connector.accept(ctx.asyncAssertSuccess(conn -> { + conn.query("SHOW transaction_read_only;").execute(ctx.asyncAssertSuccess(pgRowSet -> { + ctx.assertEquals("off", pgRowSet.iterator().next().getString("transaction_read_only")); + })); + })); + } + @Test public void testBatchUpdate(TestContext ctx) { Async async = ctx.async(); diff --git a/vertx-pg-client/src/test/java/io/vertx/pgclient/PgPoolTest.java b/vertx-pg-client/src/test/java/io/vertx/pgclient/PgPoolTest.java index 469a99fcd..159926c56 100644 --- a/vertx-pg-client/src/test/java/io/vertx/pgclient/PgPoolTest.java +++ b/vertx-pg-client/src/test/java/io/vertx/pgclient/PgPoolTest.java @@ -38,6 +38,7 @@ import java.util.concurrent.atomic.AtomicReference; import java.util.stream.Collector; +import static io.vertx.sqlclient.ServerRequirement.*; import static java.util.stream.Collectors.mapping; import static java.util.stream.Collectors.toList; diff --git a/vertx-pg-client/src/test/java/io/vertx/pgclient/PgPoolWithRequirementTest.java b/vertx-pg-client/src/test/java/io/vertx/pgclient/PgPoolWithRequirementTest.java new file mode 100644 index 000000000..215ab001b --- /dev/null +++ b/vertx-pg-client/src/test/java/io/vertx/pgclient/PgPoolWithRequirementTest.java @@ -0,0 +1,159 @@ +/* + * Copyright (C) 2017 Julien Viet + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package io.vertx.pgclient; + +import io.vertx.core.Vertx; +import io.vertx.ext.unit.Async; +import io.vertx.ext.unit.TestContext; +import io.vertx.ext.unit.junit.VertxUnitRunner; +import io.vertx.pgclient.junit.ContainerPgRule; +import io.vertx.sqlclient.PoolOptions; +import org.junit.After; +import org.junit.Before; +import org.junit.ClassRule; +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.util.HashSet; +import java.util.Set; + +import static io.vertx.sqlclient.ServerRequirement.*; + +@RunWith(VertxUnitRunner.class) +public class PgPoolWithRequirementTest { + + @ClassRule + public static ContainerPgRule pg14Rule = new ContainerPgRule().setPostgresVersion("14"); + @ClassRule + public static ContainerPgRule pg13Rule = new ContainerPgRule().setPostgresVersion("13"); + + protected PgConnectOptions pg14Options; + protected PgConnectOptions pg13Options; + + @Before + public void setup() throws Exception { + pg14Options = pg14Rule.options(); + pg13Options = pg13Rule.options(); + } + + private Set pools = new HashSet<>(); + + @After + public void tearDown(TestContext ctx) { + int size = pools.size(); + if (size > 0) { + Async async = ctx.async(size); + Set pools = this.pools; + this.pools = new HashSet<>(); + pools.forEach(pool -> { + pool.close(ar -> { + async.countDown(); + }); + }); + async.awaitSuccess(20_000); + } + } + + protected PgPool createPool(PgConnectOptions connectOptions, PoolOptions poolOptions) { + PgPool pool = PgPool.pool(Vertx.vertx(), connectOptions, poolOptions); + pools.add(pool); + return pool; + } + + @Test + public void testPrimaryRequirementForSinglePrimaryFactory14(TestContext ctx) { + Async async = ctx.async(); + PgPool pool = createPool(new PgConnectOptions(pg14Options), new PoolOptions().setServerRequirement(PRIMARY)); + pool.query("SELECT id, randomnumber from WORLD").execute(ctx.asyncAssertSuccess(v -> { + async.complete(); + })); + async.await(4000); + } + + @Test + public void testPrimaryRequirementForSinglePrimaryFactory13(TestContext ctx) { + Async async = ctx.async(); + PgPool pool = createPool(new PgConnectOptions(pg13Options), new PoolOptions().setServerRequirement(PRIMARY)); + pool.query("SELECT id, randomnumber from WORLD").execute(ctx.asyncAssertSuccess(v -> { + async.complete(); + })); + async.await(4000); + } + + @Test + public void testExplicitAnyRequirementForSinglePrimaryFactory14(TestContext ctx) { + Async async = ctx.async(); + PgPool pool = createPool(new PgConnectOptions(pg14Options), new PoolOptions().setServerRequirement(ANY)); + pool.query("SELECT id, randomnumber from WORLD").execute(ctx.asyncAssertSuccess(v -> { + async.complete(); + })); + async.await(4000); + } + + @Test + public void testExplicitAnyRequirementForSinglePrimaryFactory13(TestContext ctx) { + Async async = ctx.async(); + PgPool pool = createPool(new PgConnectOptions(pg13Options), new PoolOptions().setServerRequirement(ANY)); + pool.query("SELECT id, randomnumber from WORLD").execute(ctx.asyncAssertSuccess(v -> { + async.complete(); + })); + async.await(4000); + } + + @Test + public void testReplicaRequirementForSinglePrimaryFactory14(TestContext ctx) { + Async async = ctx.async(); + PgPool pool = createPool(new PgConnectOptions(pg14Options), new PoolOptions().setServerRequirement(REPLICA)); + pool.query("SELECT id, randomnumber from WORLD").execute(ctx.asyncAssertFailure(v -> { + ctx.assertEquals("No suitable server of type REPLICA was found", v.getMessage()); + async.complete(); + })); + async.await(4000); + } + + @Test + public void testReplicaRequirementForSinglePrimaryFactory13(TestContext ctx) { + Async async = ctx.async(); + PgPool pool = createPool(new PgConnectOptions(pg13Options), new PoolOptions().setServerRequirement(REPLICA)); + pool.query("SELECT id, randomnumber from WORLD").execute(ctx.asyncAssertFailure(v -> { + ctx.assertEquals("No suitable server of type REPLICA was found", v.getMessage()); + async.complete(); + })); + async.await(4000); + } + + @Test + public void testExplicitPreferReplicaRequirementForSinglePrimaryFactory14(TestContext ctx) { + Async async = ctx.async(); + PgPool pool = createPool(new PgConnectOptions(pg14Options), new PoolOptions().setServerRequirement(PREFER_REPLICA)); + pool.query("SELECT id, randomnumber from WORLD").execute(ctx.asyncAssertSuccess(v -> { + async.complete(); + })); + async.await(4000); + } + + @Test + public void testExplicitPreferReplicaRequirementForSinglePrimaryFactory13(TestContext ctx) { + Async async = ctx.async(); + PgPool pool = createPool(new PgConnectOptions(pg13Options), new PoolOptions().setServerRequirement(PREFER_REPLICA)); + pool.query("SELECT id, randomnumber from WORLD").execute(ctx.asyncAssertSuccess(v -> { + async.complete(); + })); + async.await(4000); + } +} diff --git a/vertx-pg-client/src/test/java/io/vertx/pgclient/junit/ContainerPgRule.java b/vertx-pg-client/src/test/java/io/vertx/pgclient/junit/ContainerPgRule.java index e1e97210d..f5993624f 100644 --- a/vertx-pg-client/src/test/java/io/vertx/pgclient/junit/ContainerPgRule.java +++ b/vertx-pg-client/src/test/java/io/vertx/pgclient/junit/ContainerPgRule.java @@ -55,6 +55,11 @@ public ContainerPgRule ssl(boolean ssl) { return this; } + public ContainerPgRule setPostgresVersion(String databaseVersion) { + this.databaseVersion = databaseVersion; + return this; + } + public PgConnectOptions options() { return new PgConnectOptions(options); } @@ -166,7 +171,9 @@ protected void before() throws Throwable { return; } - this.databaseVersion = getPostgresVersion(); + if (this.databaseVersion == null) { + this.databaseVersion = getPostgresVersion(); + } options = startServer(databaseVersion); } diff --git a/vertx-sql-client/src/main/generated/io/vertx/sqlclient/PoolOptionsConverter.java b/vertx-sql-client/src/main/generated/io/vertx/sqlclient/PoolOptionsConverter.java index 44272cbd3..f355482a2 100644 --- a/vertx-sql-client/src/main/generated/io/vertx/sqlclient/PoolOptionsConverter.java +++ b/vertx-sql-client/src/main/generated/io/vertx/sqlclient/PoolOptionsConverter.java @@ -55,6 +55,11 @@ public static void fromJson(Iterable> json, obj.setPoolCleanerPeriod(((Number)member.getValue()).intValue()); } break; + case "serverRequirement": + if (member.getValue() instanceof String) { + obj.setServerRequirement(io.vertx.sqlclient.ServerRequirement.valueOf((String)member.getValue())); + } + break; } } } @@ -75,5 +80,8 @@ public static void toJson(PoolOptions obj, java.util.Map json) { json.put("maxSize", obj.getMaxSize()); json.put("maxWaitQueueSize", obj.getMaxWaitQueueSize()); json.put("poolCleanerPeriod", obj.getPoolCleanerPeriod()); + if (obj.getServerRequirement() != null) { + json.put("serverRequirement", obj.getServerRequirement().name()); + } } } diff --git a/vertx-sql-client/src/main/java/io/vertx/sqlclient/PoolOptions.java b/vertx-sql-client/src/main/java/io/vertx/sqlclient/PoolOptions.java index ece6b3bfc..3ade5c31c 100644 --- a/vertx-sql-client/src/main/java/io/vertx/sqlclient/PoolOptions.java +++ b/vertx-sql-client/src/main/java/io/vertx/sqlclient/PoolOptions.java @@ -69,6 +69,11 @@ public class PoolOptions { */ public static final TimeUnit DEFAULT_CONNECTION_TIMEOUT_TIME_UNIT = TimeUnit.SECONDS; + /** + * Default server requirement in the pool + */ + public static final ServerRequirement DEFAULT_SERVER_REQUIREMENT = ServerRequirement.ANY; + private int maxSize = DEFAULT_MAX_SIZE; private int maxWaitQueueSize = DEFAULT_MAX_WAIT_QUEUE_SIZE; private int idleTimeout = DEFAULT_IDLE_TIMEOUT; @@ -76,6 +81,7 @@ public class PoolOptions { private int poolCleanerPeriod = DEFAULT_POOL_CLEANER_PERIOD; private int connectionTimeout = DEFAULT_CONNECTION_TIMEOUT; private TimeUnit connectionTimeoutUnit = DEFAULT_CONNECTION_TIMEOUT_TIME_UNIT; + private ServerRequirement serverRequirement = DEFAULT_SERVER_REQUIREMENT; public PoolOptions() { } @@ -89,6 +95,7 @@ public PoolOptions(PoolOptions other) { maxWaitQueueSize = other.maxWaitQueueSize; idleTimeout = other.idleTimeout; idleTimeoutUnit = other.idleTimeoutUnit; + serverRequirement = other.serverRequirement; } /** @@ -215,7 +222,7 @@ public int getConnectionTimeout() { * Set the amount of time a client will wait for a connection from the pool. If the time is exceeded * without a connection available, an exception is provided. * - * @param timeout the pool connection idle time unitq + * @param timeout the pool connection idle time unit * @return a reference to this, so the API can be used fluently */ public PoolOptions setConnectionTimeout(int timeout) { @@ -223,6 +230,24 @@ public PoolOptions setConnectionTimeout(int timeout) { return this; } + /** + * @return the pool connection server requirement. See {@link #setServerRequirement(ServerRequirement)} + */ + public ServerRequirement getServerRequirement() { + return serverRequirement; + } + + /** + * Set the server requirement + * + * @param serverRequirement the pool connection server requirement + * @return a reference to this, so the API can be used fluently + */ + public PoolOptions setServerRequirement(ServerRequirement serverRequirement) { + this.serverRequirement = serverRequirement; + return this; + } + public JsonObject toJson() { JsonObject json = new JsonObject(); PoolOptionsConverter.toJson(this, json); diff --git a/vertx-sql-client/src/main/java/io/vertx/sqlclient/ServerRequirement.java b/vertx-sql-client/src/main/java/io/vertx/sqlclient/ServerRequirement.java new file mode 100644 index 000000000..910fe604d --- /dev/null +++ b/vertx-sql-client/src/main/java/io/vertx/sqlclient/ServerRequirement.java @@ -0,0 +1,8 @@ +package io.vertx.sqlclient; + +public enum ServerRequirement { + ANY, + PRIMARY, + REPLICA, + PREFER_REPLICA; +} diff --git a/vertx-sql-client/src/main/java/io/vertx/sqlclient/ServerType.java b/vertx-sql-client/src/main/java/io/vertx/sqlclient/ServerType.java new file mode 100644 index 000000000..286f81898 --- /dev/null +++ b/vertx-sql-client/src/main/java/io/vertx/sqlclient/ServerType.java @@ -0,0 +1,7 @@ +package io.vertx.sqlclient; + +public enum ServerType { + UNDEFINED, + PRIMARY, + REPLICA +} diff --git a/vertx-sql-client/src/main/java/io/vertx/sqlclient/impl/pool/SqlConnectionPool.java b/vertx-sql-client/src/main/java/io/vertx/sqlclient/impl/pool/SqlConnectionPool.java index 1492f9d29..074930cc6 100644 --- a/vertx-sql-client/src/main/java/io/vertx/sqlclient/impl/pool/SqlConnectionPool.java +++ b/vertx-sql-client/src/main/java/io/vertx/sqlclient/impl/pool/SqlConnectionPool.java @@ -116,7 +116,7 @@ public void connect(EventLoopContext context, PoolConnector.Listener listener, H Future future = connectionProvider.apply(context); future.onComplete(ar -> { if (ar.succeeded()) { - SqlConnectionImpl res = (SqlConnectionImpl) ar.result(); + SqlConnectionImpl res = (SqlConnectionImpl) ar.result(); Connection conn = res.unwrap(); if (conn.isValid()) { PooledConnection pooled = new PooledConnection(res.factory(), conn, listener); diff --git a/vertx-sql-client/src/main/java/io/vertx/sqlclient/spi/ConnectionFactory.java b/vertx-sql-client/src/main/java/io/vertx/sqlclient/spi/ConnectionFactory.java index de84b0279..b3fadd46f 100644 --- a/vertx-sql-client/src/main/java/io/vertx/sqlclient/spi/ConnectionFactory.java +++ b/vertx-sql-client/src/main/java/io/vertx/sqlclient/spi/ConnectionFactory.java @@ -1,20 +1,31 @@ package io.vertx.sqlclient.spi; -import io.vertx.core.Closeable; -import io.vertx.core.CompositeFuture; -import io.vertx.core.Context; -import io.vertx.core.Future; -import io.vertx.core.Promise; +import io.vertx.core.*; +import io.vertx.sqlclient.ServerRequirement; +import io.vertx.sqlclient.ServerType; import io.vertx.sqlclient.SqlConnection; import java.util.ArrayList; import java.util.List; +import java.util.concurrent.CopyOnWriteArrayList; /** * A connection factory, can be obtained from {@link Driver#createConnectionFactory} */ public interface ConnectionFactory extends Closeable { + default void close(Promise promise, List factories) { + List list = new ArrayList<>(factories.size()); + for (ConnectionFactory factory : factories) { + Promise p = Promise.promise(); + factory.close(p); + list.add(p.future()); + } + CompositeFuture.all(list) + .mapEmpty() + .onComplete(promise); + } + /** * @return a connection factory that connects with a round-robin policy */ @@ -32,20 +43,114 @@ public Future connect(Context context) { } @Override public void close(Promise promise) { - List list = new ArrayList<>(factories.size()); - for (ConnectionFactory factory : factories) { - Promise p = Promise.promise(); - factory.close(p); - list.add(p.future()); - } - CompositeFuture.all(list) - .mapEmpty() - .onComplete(promise); + close(promise, factories); } }; } } + /** + * @return a connection factory that connects with respect to server requirement + */ + static ConnectionFactory selector(List factories, ServerRequirement serverRequirement) { + switch (serverRequirement) { + case ANY: + return roundRobinSelector(factories); + case PRIMARY: + return constrainedSelector(factories, ServerType.PRIMARY); + case REPLICA: + return constrainedSelector(factories, ServerType.REPLICA); + case PREFER_REPLICA: + return prioritySelector(factories, ServerType.REPLICA); + default: + throw new IllegalStateException("Unknown server requirement: " + serverRequirement); + } + } + + static ConnectionFactory constrainedSelector(List factories, ServerType serverType) { + return new ConnectionFactory() { + private int idx = 0; + + @Override + public Future connect(Context context) { + int oldIdx = idx; + idx = (idx + 1) % factories.size(); + return connectingRound(context, oldIdx, factories.size()); + } + + private Future connectingRound(Context context, int roundIdx, int attemptsLeft) { + if (attemptsLeft == 0) { + throw new IllegalStateException(String.format("No suitable server of type %s was found", serverType)); + } + ConnectionFactory f = factories.get(roundIdx); + if (f.getServerType() == serverType) { + return f.connect(context); + } else if (f.getServerType() == ServerType.UNDEFINED) { + return f.connect(context).flatMap(conn -> { + if (f.getServerType() == serverType) { + return Future.succeededFuture(conn); + } else { + return conn.close().flatMap(__ -> + connectingRound(context, (roundIdx + 1) % factories.size(), attemptsLeft - 1) + ); + } + }); + } else { + return connectingRound(context, (roundIdx + 1) % factories.size(), attemptsLeft - 1); + } + } + + @Override + public void close(Promise promise) { + close(promise, factories); + } + }; + } + + static ConnectionFactory prioritySelector(List factories, ServerType serverType) { + return new ConnectionFactory() { + private int idx = 0; + private CopyOnWriteArrayList prioritized = new CopyOnWriteArrayList<>(); + private CopyOnWriteArrayList fallback = new CopyOnWriteArrayList<>(); + + @Override + public Future connect(Context context) { + if (prioritized.size() + fallback.size() != factories.size()) { + return connectAndPrioritize(context); + } else { + return connectByPriority(context); + } + } + + private Future connectAndPrioritize(Context context) { + ConnectionFactory f = factories.get(idx); + idx = (idx + 1) % factories.size(); + if (f.getServerType() == serverType) { + prioritized.addIfAbsent(f); + return f.connect(context).recover(__ -> connect(context)); + } else if (f.getServerType() == ServerType.UNDEFINED) { + // this factory will be added to prioritized/fallback during some later invocation of this method + return f.connect(context).recover(__ -> connect(context)); + } else { + fallback.addIfAbsent(f); + return f.connect(context).recover(__ -> connect(context)); + } + } + + private Future connectByPriority(Context context) { + ConnectionFactory pf = prioritized.get(idx % prioritized.size()); + ConnectionFactory ff = fallback.get(idx % fallback.size()); + idx = (idx + 1) % factories.size(); + return pf.connect(context).recover(__ -> ff.connect(context)).recover(__ -> connect(context)); + } + + @Override + public void close(Promise promise) { + close(promise, factories); + } + }; + } + /** * Create a connection using the given {@code context}. * @@ -54,4 +159,18 @@ public void close(Promise promise) { */ Future connect(Context context); + + /** + * Server type could be updated asynchronously for instance, in Postgres case + * server type can be reported as ParamStatus message in response to explicit 'SHOW xxx' command + * as well as in response to Simple/Extended query in case of GUC_REPORT. + * Given that, there could be situations when concurrent write to particular factory's serverType is happening + * when this method is being called or just after this method has returned. + * Users should be aware that serverType nature is dynamic. + * + * @return server type of host this factory connected to. + */ + default ServerType getServerType() { + return ServerType.UNDEFINED; + } } diff --git a/vertx-sql-client/src/test/java/io/vertx/sqlclient/spi/ConnectionFactoryTest.java b/vertx-sql-client/src/test/java/io/vertx/sqlclient/spi/ConnectionFactoryTest.java new file mode 100644 index 000000000..fa6080c0e --- /dev/null +++ b/vertx-sql-client/src/test/java/io/vertx/sqlclient/spi/ConnectionFactoryTest.java @@ -0,0 +1,322 @@ +package io.vertx.sqlclient.spi; + +import io.vertx.core.*; +import io.vertx.sqlclient.*; +import org.junit.Test; + +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Stream; + +import static io.vertx.sqlclient.ServerType.*; +import static java.util.stream.Collectors.toCollection; +import static java.util.stream.Collectors.toList; +import static org.junit.Assert.assertEquals; + +public class ConnectionFactoryTest { + Context context = Vertx.vertx().getOrCreateContext(); + + static class DummySqlConnection implements SqlConnection { + + @Override + public Query> query(String sql) { + return null; + } + + @Override + public PreparedQuery> preparedQuery(String sql) { + return null; + } + + @Override + public PreparedQuery> preparedQuery(String sql, PrepareOptions options) { + return null; + } + + @Override + public Future close() { + return Future.succeededFuture(); + } + + @Override + public SqlConnection prepare(String sql, Handler> handler) { + return null; + } + + @Override + public Future prepare(String sql) { + return null; + } + + @Override + public SqlConnection prepare(String sql, PrepareOptions options, Handler> handler) { + return null; + } + + @Override + public Future prepare(String sql, PrepareOptions options) { + return null; + } + + @Override + public SqlConnection exceptionHandler(Handler handler) { + return null; + } + + @Override + public SqlConnection closeHandler(Handler handler) { + return null; + } + + @Override + public void begin(Handler> handler) { + + } + + @Override + public Future begin() { + return null; + } + + @Override + public boolean isSSL() { + return false; + } + + @Override + public void close(Handler> handler) { + + } + + @Override + public DatabaseMetadata databaseMetadata() { + return null; + } + } + + static class StaticFactory implements ConnectionFactory { + + public StaticFactory(ServerType serverType, SqlConnection connection) { + this(serverType, null, connection, false); + } + + public StaticFactory(ServerType serverType, SqlConnection connection, boolean failing) { + this(serverType, null, connection, failing); + } + + public StaticFactory(ServerType initialServerType, ServerType promotedServerType, SqlConnection connection) { + this(initialServerType, promotedServerType, connection, false); + } + + public StaticFactory(ServerType initialServerType, ServerType promotedServerType, SqlConnection connection, boolean failing) { + this.serverType = initialServerType; + this.promotedServerType = promotedServerType; + this.connection = connection; + this.failing = failing; + } + + private ServerType serverType; + private ServerType promotedServerType; + private final SqlConnection connection; + private final boolean failing; + + @Override + public ServerType getServerType() { + return serverType; + } + + @Override + public Future connect(Context context) { + if (promotedServerType != null) { + serverType = promotedServerType; + } + if (failing) { + return Future.failedFuture("unable to connect"); + } else { + return Future.succeededFuture(connection); + } + } + + @Override + public void close(Promise promise) {} + } + + @Test + public void testRoundRobinSelector() { + SqlConnection conn1 = new DummySqlConnection(); + SqlConnection conn2 = new DummySqlConnection(); + SqlConnection conn3 = new DummySqlConnection(); + ConnectionFactory factory = ConnectionFactory.roundRobinSelector( + Stream.of(conn1, conn2, conn3).map(conn -> new StaticFactory(UNDEFINED, conn)).collect(toList()) + ); + assertEquals(conn1, factory.connect(context).result()); + assertEquals(conn2, factory.connect(context).result()); + assertEquals(conn3, factory.connect(context).result()); + assertEquals(conn1, factory.connect(context).result()); + } + + @Test + public void testAnySelector() { + SqlConnection conn1 = new DummySqlConnection(); + SqlConnection conn2 = new DummySqlConnection(); + SqlConnection conn3 = new DummySqlConnection(); + ConnectionFactory factory = ConnectionFactory.selector( + Stream.of(conn1, conn2, conn3).map(conn -> new StaticFactory(UNDEFINED, conn)).collect(toList()), + ServerRequirement.ANY + ); + assertEquals(conn1, factory.connect(context).result()); + assertEquals(conn2, factory.connect(context).result()); + assertEquals(conn3, factory.connect(context).result()); + assertEquals(conn1, factory.connect(context).result()); + } + + @Test + public void testPrimarySelectorAmongUndefinedFactories() { + SqlConnection conn1 = new DummySqlConnection(); + SqlConnection conn2 = new DummySqlConnection(); + List factories = new ArrayList<>(); + Stream.of(conn1, conn2).map(conn -> new StaticFactory(UNDEFINED, conn)).collect(toCollection(() -> factories)); + SqlConnection primaryConn = new DummySqlConnection(); + factories.add(new StaticFactory(PRIMARY, primaryConn)); + ConnectionFactory factory = ConnectionFactory.selector(factories, ServerRequirement.PRIMARY); + assertEquals(primaryConn, factory.connect(context).result()); + assertEquals(primaryConn, factory.connect(context).result()); + assertEquals(primaryConn, factory.connect(context).result()); + assertEquals(primaryConn, factory.connect(context).result()); + } + + @Test + public void testPrimarySelectorAmongReplicaFactories() { + SqlConnection conn1 = new DummySqlConnection(); + SqlConnection conn2 = new DummySqlConnection(); + List factories = new ArrayList<>(); + Stream.of(conn1, conn2).map(conn -> new StaticFactory(REPLICA, conn)).collect(toCollection(() -> factories)); + SqlConnection primaryConn = new DummySqlConnection(); + factories.add(new StaticFactory(PRIMARY, primaryConn)); + ConnectionFactory factory = ConnectionFactory.selector(factories, ServerRequirement.PRIMARY); + assertEquals(primaryConn, factory.connect(context).result()); + assertEquals(primaryConn, factory.connect(context).result()); + assertEquals(primaryConn, factory.connect(context).result()); + assertEquals(primaryConn, factory.connect(context).result()); + } + + @Test + public void testPrimarySelectorAmongReplicaAndUndefinedFactories() { + SqlConnection conn1Replica = new DummySqlConnection(); + SqlConnection conn2Replica = new DummySqlConnection(); + List factories = new ArrayList<>(); + Stream.of(conn1Replica, conn2Replica).map(conn -> new StaticFactory(REPLICA, conn)).collect(toCollection(() -> factories)); + SqlConnection conn1Undefined = new DummySqlConnection(); + SqlConnection conn2Undefined = new DummySqlConnection(); + Stream.of(conn1Undefined, conn2Undefined).map(conn -> new StaticFactory(UNDEFINED, conn)).collect(toCollection(() -> factories)); + SqlConnection primaryConn = new DummySqlConnection(); + factories.add(new StaticFactory(PRIMARY, primaryConn)); + ConnectionFactory factory = ConnectionFactory.selector(factories, ServerRequirement.PRIMARY); + assertEquals(primaryConn, factory.connect(context).result()); + assertEquals(primaryConn, factory.connect(context).result()); + assertEquals(primaryConn, factory.connect(context).result()); + assertEquals(primaryConn, factory.connect(context).result()); + assertEquals(primaryConn, factory.connect(context).result()); + assertEquals(primaryConn, factory.connect(context).result()); + } + + @Test + public void testPrimarySelectorAmongUndefinedFactoriesWithOnePromotedToPrimary() { + SqlConnection conn1 = new DummySqlConnection(); + SqlConnection conn2 = new DummySqlConnection(); + List factories = new ArrayList<>(); + Stream.of(conn1, conn2).map(conn -> new StaticFactory(UNDEFINED, conn)).collect(toCollection(() -> factories)); + SqlConnection primaryConn = new DummySqlConnection(); + factories.add(new StaticFactory(UNDEFINED, PRIMARY, primaryConn)); + ConnectionFactory factory = ConnectionFactory.selector(factories, ServerRequirement.PRIMARY); + assertEquals(primaryConn, factory.connect(context).result()); + assertEquals(primaryConn, factory.connect(context).result()); + assertEquals(primaryConn, factory.connect(context).result()); + assertEquals(primaryConn, factory.connect(context).result()); + } + + @Test + public void testReplicaSelectorAmongUndefinedFactoriesWithOnePromotedToReplica() { + SqlConnection conn1 = new DummySqlConnection(); + SqlConnection conn2 = new DummySqlConnection(); + List factories = new ArrayList<>(); + Stream.of(conn1, conn2).map(conn -> new StaticFactory(UNDEFINED, conn)).collect(toCollection(() -> factories)); + SqlConnection replicaConn = new DummySqlConnection(); + factories.add(new StaticFactory(UNDEFINED, REPLICA, replicaConn)); + ConnectionFactory factory = ConnectionFactory.selector(factories, ServerRequirement.REPLICA); + assertEquals(replicaConn, factory.connect(context).result()); + assertEquals(replicaConn, factory.connect(context).result()); + assertEquals(replicaConn, factory.connect(context).result()); + assertEquals(replicaConn, factory.connect(context).result()); + } + + @Test + public void testReplicaSelectorAmongUndefinedFactories() { + List factories = new ArrayList<>(); + SqlConnection conn1Undefined = new DummySqlConnection(); + SqlConnection conn2Undefined = new DummySqlConnection(); + Stream.of(conn1Undefined, conn2Undefined).map(conn -> new StaticFactory(UNDEFINED, conn)).collect(toCollection(() -> factories)); + SqlConnection replicaConn1 = new DummySqlConnection(); + SqlConnection replicaConn2 = new DummySqlConnection(); + factories.add(new StaticFactory(REPLICA, replicaConn1)); + factories.add(new StaticFactory(REPLICA, replicaConn2)); + ConnectionFactory factory = ConnectionFactory.selector(factories, ServerRequirement.REPLICA); + // This is a pathological case for round-robin selector with filtering + // Due to multiple fallbacks first server with target type will be chosen more often + // Selector algorithm should be optimized. + assertEquals(replicaConn1, factory.connect(context).result()); + assertEquals(replicaConn1, factory.connect(context).result()); + assertEquals(replicaConn1, factory.connect(context).result()); + assertEquals(replicaConn2, factory.connect(context).result()); + assertEquals(replicaConn1, factory.connect(context).result()); + assertEquals(replicaConn1, factory.connect(context).result()); + assertEquals(replicaConn1, factory.connect(context).result()); + assertEquals(replicaConn2, factory.connect(context).result()); + } + + @Test + public void testPreferReplicaSelectorAmongUndefinedFactories() { + SqlConnection conn1 = new DummySqlConnection(); + SqlConnection conn2 = new DummySqlConnection(); + List factories = new ArrayList<>(); + Stream.of(conn1, conn2).map(conn -> new StaticFactory(UNDEFINED, PRIMARY, conn)).collect(toCollection(() -> factories)); + SqlConnection replicaConn1 = new DummySqlConnection(); + factories.add(new StaticFactory(UNDEFINED, REPLICA, replicaConn1)); + SqlConnection replicaConn2 = new DummySqlConnection(); + factories.add(new StaticFactory(UNDEFINED, REPLICA, replicaConn2)); + ConnectionFactory factory = ConnectionFactory.selector(factories, ServerRequirement.PREFER_REPLICA); + // first full round of probing of unknown hosts + assertEquals(conn1, factory.connect(context).result()); + assertEquals(conn2, factory.connect(context).result()); + assertEquals(replicaConn1, factory.connect(context).result()); + assertEquals(replicaConn2, factory.connect(context).result()); + // then round of prioritizing + assertEquals(conn1, factory.connect(context).result()); + assertEquals(conn2, factory.connect(context).result()); + assertEquals(replicaConn1, factory.connect(context).result()); + assertEquals(replicaConn2, factory.connect(context).result()); + // then always return prioritized connections + assertEquals(replicaConn1, factory.connect(context).result()); + assertEquals(replicaConn2, factory.connect(context).result()); + assertEquals(replicaConn1, factory.connect(context).result()); + assertEquals(replicaConn2, factory.connect(context).result()); + } + + @Test + public void testPreferReplicaSelectorAmongPrimaryAndUndefinedFactoriesWhenReplicaFails() { + List factories = new ArrayList<>(); + SqlConnection replicaConn1 = new DummySqlConnection(); + factories.add(new StaticFactory(UNDEFINED, REPLICA, replicaConn1, true)); + SqlConnection replicaConn2 = new DummySqlConnection(); + factories.add(new StaticFactory(UNDEFINED, REPLICA, replicaConn2, true)); + SqlConnection primaryConn = new DummySqlConnection(); + factories.add(new StaticFactory(UNDEFINED, PRIMARY, primaryConn, false)); + SqlConnection primaryConnFailing = new DummySqlConnection(); + factories.add(new StaticFactory(UNDEFINED, PRIMARY, primaryConnFailing, true)); + ConnectionFactory factory = ConnectionFactory.selector(factories, ServerRequirement.PREFER_REPLICA); + assertEquals(primaryConn, factory.connect(context).result()); + assertEquals(primaryConn, factory.connect(context).result()); + assertEquals(primaryConn, factory.connect(context).result()); + assertEquals(primaryConn, factory.connect(context).result()); + } +} From bfd8cfcc9528d9f0301c9503213e92d1f3d979d2 Mon Sep 17 00:00:00 2001 From: Sergey Prytkov Date: Mon, 8 Nov 2021 21:46:02 +0300 Subject: [PATCH 25/26] add license header --- .../io/vertx/pgclient/PgConnectionTest.java | 9 --------- .../java/io/vertx/pgclient/PgPoolTest.java | 1 - .../pgclient/PgPoolWithRequirementTest.java | 18 ++++++------------ .../io/vertx/sqlclient/ServerRequirement.java | 11 +++++++++++ .../java/io/vertx/sqlclient/ServerType.java | 11 +++++++++++ .../sqlclient/spi/ConnectionFactoryTest.java | 11 +++++++++++ 6 files changed, 39 insertions(+), 22 deletions(-) diff --git a/vertx-pg-client/src/test/java/io/vertx/pgclient/PgConnectionTest.java b/vertx-pg-client/src/test/java/io/vertx/pgclient/PgConnectionTest.java index 096eb12e9..e85e5e276 100644 --- a/vertx-pg-client/src/test/java/io/vertx/pgclient/PgConnectionTest.java +++ b/vertx-pg-client/src/test/java/io/vertx/pgclient/PgConnectionTest.java @@ -48,15 +48,6 @@ public void testSettingSchema(TestContext ctx) { })); } - @Test - public void testPrimary(TestContext ctx) { - connector.accept(ctx.asyncAssertSuccess(conn -> { - conn.query("SHOW transaction_read_only;").execute(ctx.asyncAssertSuccess(pgRowSet -> { - ctx.assertEquals("off", pgRowSet.iterator().next().getString("transaction_read_only")); - })); - })); - } - @Test public void testBatchUpdate(TestContext ctx) { Async async = ctx.async(); diff --git a/vertx-pg-client/src/test/java/io/vertx/pgclient/PgPoolTest.java b/vertx-pg-client/src/test/java/io/vertx/pgclient/PgPoolTest.java index 159926c56..469a99fcd 100644 --- a/vertx-pg-client/src/test/java/io/vertx/pgclient/PgPoolTest.java +++ b/vertx-pg-client/src/test/java/io/vertx/pgclient/PgPoolTest.java @@ -38,7 +38,6 @@ import java.util.concurrent.atomic.AtomicReference; import java.util.stream.Collector; -import static io.vertx.sqlclient.ServerRequirement.*; import static java.util.stream.Collectors.mapping; import static java.util.stream.Collectors.toList; diff --git a/vertx-pg-client/src/test/java/io/vertx/pgclient/PgPoolWithRequirementTest.java b/vertx-pg-client/src/test/java/io/vertx/pgclient/PgPoolWithRequirementTest.java index 215ab001b..5021121aa 100644 --- a/vertx-pg-client/src/test/java/io/vertx/pgclient/PgPoolWithRequirementTest.java +++ b/vertx-pg-client/src/test/java/io/vertx/pgclient/PgPoolWithRequirementTest.java @@ -1,18 +1,12 @@ /* - * Copyright (C) 2017 Julien Viet + * Copyright (c) 2011-2021 Contributors to the Eclipse Foundation * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. + * 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.pgclient; diff --git a/vertx-sql-client/src/main/java/io/vertx/sqlclient/ServerRequirement.java b/vertx-sql-client/src/main/java/io/vertx/sqlclient/ServerRequirement.java index 910fe604d..5bca85be7 100644 --- a/vertx-sql-client/src/main/java/io/vertx/sqlclient/ServerRequirement.java +++ b/vertx-sql-client/src/main/java/io/vertx/sqlclient/ServerRequirement.java @@ -1,3 +1,14 @@ +/* + * Copyright (c) 2011-2021 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.sqlclient; public enum ServerRequirement { diff --git a/vertx-sql-client/src/main/java/io/vertx/sqlclient/ServerType.java b/vertx-sql-client/src/main/java/io/vertx/sqlclient/ServerType.java index 286f81898..61c575f29 100644 --- a/vertx-sql-client/src/main/java/io/vertx/sqlclient/ServerType.java +++ b/vertx-sql-client/src/main/java/io/vertx/sqlclient/ServerType.java @@ -1,3 +1,14 @@ +/* + * Copyright (c) 2011-2021 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.sqlclient; public enum ServerType { diff --git a/vertx-sql-client/src/test/java/io/vertx/sqlclient/spi/ConnectionFactoryTest.java b/vertx-sql-client/src/test/java/io/vertx/sqlclient/spi/ConnectionFactoryTest.java index fa6080c0e..5328ac16b 100644 --- a/vertx-sql-client/src/test/java/io/vertx/sqlclient/spi/ConnectionFactoryTest.java +++ b/vertx-sql-client/src/test/java/io/vertx/sqlclient/spi/ConnectionFactoryTest.java @@ -1,3 +1,14 @@ +/* + * Copyright (c) 2011-2021 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.sqlclient.spi; import io.vertx.core.*; From 22ead9a6499b11d9343382fd8455d9c2b408f847 Mon Sep 17 00:00:00 2001 From: Sergey Prytkov Date: Tue, 9 Nov 2021 08:39:13 +0300 Subject: [PATCH 26/26] add javadocs --- .../io/vertx/pgclient/PgConnectOptions.java | 2 +- .../pgclient/impl/PgConnectionFactory.java | 3 ++ .../pgclient/impl/PgSocketConnection.java | 1 + .../io/vertx/sqlclient/ServerRequirement.java | 22 +++++++++++ .../java/io/vertx/sqlclient/ServerType.java | 12 ++++++ .../sqlclient/spi/ConnectionFactory.java | 39 ++++++++++++++++++- 6 files changed, 76 insertions(+), 3 deletions(-) diff --git a/vertx-pg-client/src/main/java/io/vertx/pgclient/PgConnectOptions.java b/vertx-pg-client/src/main/java/io/vertx/pgclient/PgConnectOptions.java index 4cb1ad3fa..99fa350f7 100644 --- a/vertx-pg-client/src/main/java/io/vertx/pgclient/PgConnectOptions.java +++ b/vertx-pg-client/src/main/java/io/vertx/pgclient/PgConnectOptions.java @@ -246,7 +246,7 @@ public PgConnectOptions setSslMode(SslMode sslmode) { /** * Set whether the client should query server type, - * In positive case, connection should issue implementation specific query + * If true, connection should issue implementation specific query * to read {@link io.vertx.sqlclient.ServerType} of host being connected to * * @param shouldQueryServerType the value of shouldQueryServerType diff --git a/vertx-pg-client/src/main/java/io/vertx/pgclient/impl/PgConnectionFactory.java b/vertx-pg-client/src/main/java/io/vertx/pgclient/impl/PgConnectionFactory.java index 0fe55ce6f..2fef0ec05 100644 --- a/vertx-pg-client/src/main/java/io/vertx/pgclient/impl/PgConnectionFactory.java +++ b/vertx-pg-client/src/main/java/io/vertx/pgclient/impl/PgConnectionFactory.java @@ -181,6 +181,9 @@ public Future connect(Context context) { return promise.future(); } + /** + * {@inheritDoc} + */ @Override public ServerType getServerType() { return serverType; diff --git a/vertx-pg-client/src/main/java/io/vertx/pgclient/impl/PgSocketConnection.java b/vertx-pg-client/src/main/java/io/vertx/pgclient/impl/PgSocketConnection.java index a286cf8d2..634ece21d 100644 --- a/vertx-pg-client/src/main/java/io/vertx/pgclient/impl/PgSocketConnection.java +++ b/vertx-pg-client/src/main/java/io/vertx/pgclient/impl/PgSocketConnection.java @@ -48,6 +48,7 @@ public class PgSocketConnection extends SocketConnectionBase { public int processId; public int secretKey; public PgDatabaseMetadata dbMetaData; + // TODO: consider defining getter on SocketConnectionBase public ServerType serverType = UNDEFINED; public PgSocketConnection(NetSocketInternal socket, diff --git a/vertx-sql-client/src/main/java/io/vertx/sqlclient/ServerRequirement.java b/vertx-sql-client/src/main/java/io/vertx/sqlclient/ServerRequirement.java index 5bca85be7..c1cc784c8 100644 --- a/vertx-sql-client/src/main/java/io/vertx/sqlclient/ServerRequirement.java +++ b/vertx-sql-client/src/main/java/io/vertx/sqlclient/ServerRequirement.java @@ -11,9 +11,31 @@ package io.vertx.sqlclient; +import io.vertx.core.Vertx; +import io.vertx.sqlclient.spi.Driver; + +import java.util.List; + +/** + * This option determines whether the session must have certain properties to be acceptable. + * It's typically used in combination with {@link Driver#createPool(Vertx, List, PoolOptions)} overload + * to select the first acceptable alternative among several hosts. + */ public enum ServerRequirement { + /** + * Any successful connection is acceptable (default) + */ ANY, + /** + * Server must not be in hot standby mode, usually but not necessary such server allows read-write connections + */ PRIMARY, + /** + * Server must be in hot standby mode, only read-only connections are allowed + */ REPLICA, + /** + * First try to find a standby server, but if none of the listed hosts is a standby server, try again in any mode + */ PREFER_REPLICA; } diff --git a/vertx-sql-client/src/main/java/io/vertx/sqlclient/ServerType.java b/vertx-sql-client/src/main/java/io/vertx/sqlclient/ServerType.java index 61c575f29..5bfb3efd8 100644 --- a/vertx-sql-client/src/main/java/io/vertx/sqlclient/ServerType.java +++ b/vertx-sql-client/src/main/java/io/vertx/sqlclient/ServerType.java @@ -11,8 +11,20 @@ package io.vertx.sqlclient; +/** + * Indicates a particular property of a session + */ public enum ServerType { + /** + * No certain properties are known about server yet (default) + */ UNDEFINED, + /** + * Server is in hot standby mode, usually but not necessary such server allows read-write connections + */ PRIMARY, + /** + * Server is in hot standby mode, only read-only connections are allowed + */ REPLICA } diff --git a/vertx-sql-client/src/main/java/io/vertx/sqlclient/spi/ConnectionFactory.java b/vertx-sql-client/src/main/java/io/vertx/sqlclient/spi/ConnectionFactory.java index b3fadd46f..6bc0ace24 100644 --- a/vertx-sql-client/src/main/java/io/vertx/sqlclient/spi/ConnectionFactory.java +++ b/vertx-sql-client/src/main/java/io/vertx/sqlclient/spi/ConnectionFactory.java @@ -67,6 +67,23 @@ static ConnectionFactory selector(List factories, ServerRequi } } + /** + * {@link ServerType} of a host to which particular factory opens connections is usually not known + * upon factory construction. First opportunity to reveal such server property is available when + * first {@link io.vertx.sqlclient.impl.SocketConnectionBase} is made. For instance Postgres 14 and newer + * has dedicated `GUC_REPORT` mechanism that reports a property `in_hot_standby` when connection being established. + * If there is no server-sent status mechanism available implementation specific `SHOW xxx` command could be used. + * + * If connection was established and its server type appeared to be mot compatible with requested one + * then the connection is closed right away and next factory is probed. If all factories are probed and no + * connection with desired server type found, IllegalStateException is thrown. + * + * @param factories list of factories to filter based on server type requirement + * @param serverType requirement for the host being connected through a factory + * @return a connection factory that load balances connections + * to a subset of hosts which satisfying requested server type + * @throws IllegalStateException when no suitable connection could be established + */ static ConnectionFactory constrainedSelector(List factories, ServerType serverType) { return new ConnectionFactory() { private int idx = 0; @@ -107,6 +124,24 @@ public void close(Promise promise) { }; } + /** + * {@link ServerType} of a host to which particular factory opens connections is usually not known + * upon factory construction. First opportunity to reveal such server property is available when + * first {@link io.vertx.sqlclient.impl.SocketConnectionBase} is made. For instance Postgres 14 and newer + * has dedicated `GUC_REPORT` mechanism that reports a property `in_hot_standby` when connection being established. + * If there is no server-sent status mechanism available implementation specific `SHOW xxx` command could be used. + * + * Upon first connection to host its server type is examinated and corresponding factory is marked either + * prioritized one or fallback. Connection factory is returned no matter of underlying host type. + * After all factories were probed, subsequent requests are optimistically fulfilled by the subset of prioritized + * factories. If prioritized factory could not establish the connection, fallback is made to a "fallback" factory. + * If a "fallback" factory fails as well, next pair of factories is chosen in round-robin manner. + * + * @param factories list of factories to filter based on server type requirement + * @param serverType preferred property of the host being connected through a factory + * @return a connection factory that load balances connections + * to a subset of hosts with bias towards requested server type + */ static ConnectionFactory prioritySelector(List factories, ServerType serverType) { return new ConnectionFactory() { private int idx = 0; @@ -161,9 +196,9 @@ public void close(Promise promise) { /** - * Server type could be updated asynchronously for instance, in Postgres case + * Server type could be updated asynchronously: for instance, in Postgres case * server type can be reported as ParamStatus message in response to explicit 'SHOW xxx' command - * as well as in response to Simple/Extended query in case of GUC_REPORT. + * as well as in response to Simple/Extended query if dedicated GUC_REPORT is supported. * Given that, there could be situations when concurrent write to particular factory's serverType is happening * when this method is being called or just after this method has returned. * Users should be aware that serverType nature is dynamic.