diff --git a/vertx-oracle-client/src/main/java/io/vertx/oracleclient/impl/Helper.java b/vertx-oracle-client/src/main/java/io/vertx/oracleclient/impl/Helper.java index d50a1c463..02526425d 100644 --- a/vertx-oracle-client/src/main/java/io/vertx/oracleclient/impl/Helper.java +++ b/vertx-oracle-client/src/main/java/io/vertx/oracleclient/impl/Helper.java @@ -12,6 +12,8 @@ import io.vertx.core.Future; import io.vertx.core.VertxException; +import io.vertx.core.internal.logging.Logger; +import io.vertx.core.internal.logging.LoggerFactory; import io.vertx.core.buffer.Buffer; import io.vertx.core.json.JsonArray; import io.vertx.core.json.JsonObject; @@ -30,6 +32,8 @@ public class Helper { + private static final Logger logger = LoggerFactory.getLogger(Helper.class); + public static void closeQuietly(AutoCloseable autoCloseable) { if (autoCloseable != null) { try { @@ -175,39 +179,52 @@ public static Object convertSqlValue(Object value) throws SQLException { } private static Object convertOracleJsonValue(OracleJsonValue oracleJson) { - if (oracleJson instanceof OracleJsonObject) { - OracleJsonObject obj = (OracleJsonObject) oracleJson; - Map map = new LinkedHashMap<>(obj.size()); - for (Map.Entry entry : obj.entrySet()) { - map.put(entry.getKey(), convertOracleJsonValue(entry.getValue())); - } - return new JsonObject(map); - } else if (oracleJson instanceof OracleJsonArray) { - OracleJsonArray arr = (OracleJsonArray) oracleJson; - List list = new ArrayList<>(arr.size()); - for (OracleJsonValue element : arr) { - list.add(convertOracleJsonValue(element)); + switch (oracleJson.getOracleJsonType()) { + case OBJECT: { + OracleJsonObject obj = (OracleJsonObject) oracleJson; + Map map = new LinkedHashMap<>(obj.size()); + for (Map.Entry entry : obj.entrySet()) { + map.put(entry.getKey(), convertOracleJsonValue(entry.getValue())); + } + return new JsonObject(map); } - return new JsonArray(list); - } else if (oracleJson instanceof OracleJsonString) { - return ((OracleJsonString) oracleJson).getString(); - } else if (oracleJson instanceof OracleJsonDecimal) { - return ((OracleJsonDecimal) oracleJson).bigDecimalValue(); - } else if (oracleJson instanceof OracleJsonDouble) { - return ((OracleJsonDouble) oracleJson).doubleValue(); - } else if (oracleJson instanceof OracleJsonFloat) { - return ((OracleJsonFloat) oracleJson).floatValue(); - } else { - switch (oracleJson.getOracleJsonType()) { - case TRUE: - return Boolean.TRUE; - case FALSE: - return Boolean.FALSE; - case NULL: - return Tuple.JSON_NULL; - default: - return null; + case ARRAY: { + OracleJsonArray arr = (OracleJsonArray) oracleJson; + List list = new ArrayList<>(arr.size()); + for (OracleJsonValue element : arr) { + list.add(convertOracleJsonValue(element)); + } + return new JsonArray(list); } + case STRING: + return ((OracleJsonString) oracleJson).getString(); + case DECIMAL: + return ((OracleJsonDecimal) oracleJson).bigDecimalValue(); + case DOUBLE: + return ((OracleJsonDouble) oracleJson).doubleValue(); + case FLOAT: + return ((OracleJsonFloat) oracleJson).floatValue(); + case TIMESTAMP: + return ((OracleJsonTimestamp) oracleJson).getLocalDateTime(); + case TIMESTAMPTZ: + return ((OracleJsonTimestampTZ) oracleJson).getOffsetDateTime(); + case DATE: + return ((OracleJsonDate) oracleJson).getLocalDateTime(); + case BINARY: + return Buffer.buffer(((OracleJsonBinary) oracleJson).getBytes()); + case INTERVALDS: + return ((OracleJsonIntervalDS) oracleJson).getDuration(); + case INTERVALYM: + return ((OracleJsonIntervalYM) oracleJson).getPeriod(); + case TRUE: + return Boolean.TRUE; + case FALSE: + return Boolean.FALSE; + case NULL: + return Tuple.JSON_NULL; + default: + logger.warn("Unsupported Oracle JSON type: " + oracleJson.getOracleJsonType()); + return null; } } diff --git a/vertx-oracle-client/src/main/java/io/vertx/oracleclient/impl/commands/OracleQueryCommand.java b/vertx-oracle-client/src/main/java/io/vertx/oracleclient/impl/commands/OracleQueryCommand.java index 10107ad2f..85b50b619 100644 --- a/vertx-oracle-client/src/main/java/io/vertx/oracleclient/impl/commands/OracleQueryCommand.java +++ b/vertx-oracle-client/src/main/java/io/vertx/oracleclient/impl/commands/OracleQueryCommand.java @@ -15,6 +15,7 @@ import io.vertx.core.buffer.Buffer; import io.vertx.core.internal.logging.Logger; import io.vertx.core.internal.logging.LoggerFactory; +import io.vertx.core.json.Json; import io.vertx.core.json.JsonArray; import io.vertx.core.json.JsonObject; import io.vertx.oracleclient.OraclePrepareOptions; @@ -30,9 +31,10 @@ import oracle.jdbc.OraclePreparedStatement; import oracle.sql.TIMESTAMPTZ; import oracle.sql.json.OracleJsonFactory; +import oracle.sql.json.OracleJsonGenerator; import oracle.sql.json.OracleJsonValue; -import java.io.StringReader; +import java.io.ByteArrayOutputStream; import java.sql.*; import java.time.Instant; import java.time.LocalDate; @@ -179,16 +181,28 @@ protected Object adaptType(Connection conn, Object value) throws SQLException { Buffer buffer = (Buffer) value; return buffer.getBytes(); } else if (value instanceof JsonObject) { - return JSON_FACTORY.createJsonTextValue(new StringReader(((JsonObject) value).encode())); + return encodeJsonBinary(value); } else if (value instanceof JsonArray) { - return JSON_FACTORY.createJsonTextValue(new StringReader(((JsonArray) value).encode())); - } else if (value == Tuple.JSON_NULL) { - return JSON_FACTORY.createNull(); + return encodeJsonBinary(value); + } else { + if (value == Tuple.JSON_NULL) { + return JSON_FACTORY.createNull(); + } } return value; } + private static OracleJsonValue encodeJsonBinary(Object value) { + ByteArrayOutputStream textBuf = new ByteArrayOutputStream(); + Json.encodeTo(value, textBuf); + ByteArrayOutputStream out = new ByteArrayOutputStream(); + try (OracleJsonGenerator gen = JSON_FACTORY.createJsonBinaryGenerator(out)) { + gen.write(JSON_FACTORY.createJsonTextValue(new java.io.ByteArrayInputStream(textBuf.toByteArray()))); + } + return JSON_FACTORY.createJsonBinaryValue(new java.io.ByteArrayInputStream(out.toByteArray())); + } + protected abstract Future doExecute(OraclePreparedStatement ps, boolean returnAutoGeneratedKeys); protected OracleResponse decode(Statement statement, boolean returnedResultSet, boolean returnedKeys) throws SQLException { diff --git a/vertx-oracle-client/src/test/java/tests/oracleclient/OracleJsonDataTypeTest.java b/vertx-oracle-client/src/test/java/tests/oracleclient/OracleJsonDataTypeTest.java index a3eb8da5f..b106defdf 100644 --- a/vertx-oracle-client/src/test/java/tests/oracleclient/OracleJsonDataTypeTest.java +++ b/vertx-oracle-client/src/test/java/tests/oracleclient/OracleJsonDataTypeTest.java @@ -24,6 +24,7 @@ import tests.oracleclient.junit.OracleRule; import java.math.BigDecimal; +import java.time.*; import java.util.ArrayList; import java.util.List; @@ -301,4 +302,84 @@ public void testInsertJsonBooleanWithSqlWorkaround(TestContext ctx) { ctx.assertEquals(Boolean.TRUE, rows.iterator().next().getJson(0)); })); } + + @Test + public void testDecodeOsonTimestamp(TestContext ctx) { + pool.query("INSERT INTO json_test (id, data) VALUES (50, JSON_OBJECT('ts' VALUE TIMESTAMP '2023-09-21 10:00:00' RETURNING JSON))") + .execute() + .compose(v -> pool.preparedQuery("SELECT data FROM json_test WHERE id = ?").execute(Tuple.of(50))) + .onComplete(ctx.asyncAssertSuccess(rows -> { + ctx.assertEquals(1, rows.size()); + Row row = rows.iterator().next(); + JsonObject obj = row.getJsonObject(0); + ctx.assertNotNull(obj); + Object ts = obj.getValue("ts"); + ctx.assertNotNull(ts); + ctx.assertTrue(ts instanceof LocalDateTime, "Expected LocalDateTime but got: " + (ts == null ? "null" : ts.getClass().getName())); + ctx.assertEquals(LocalDateTime.of(2023, 9, 21, 10, 0, 0), ts); + })); + } + + @Test + public void testDecodeOsonTimestampTZ(TestContext ctx) { + pool.query("INSERT INTO json_test (id, data) VALUES (51, JSON_OBJECT('ts' VALUE TIMESTAMP '2023-09-21 10:00:00 +02:00' RETURNING JSON))") + .execute() + .compose(v -> pool.preparedQuery("SELECT data FROM json_test WHERE id = ?").execute(Tuple.of(51))) + .onComplete(ctx.asyncAssertSuccess(rows -> { + ctx.assertEquals(1, rows.size()); + Row row = rows.iterator().next(); + JsonObject obj = row.getJsonObject(0); + ctx.assertNotNull(obj); + Object ts = obj.getValue("ts"); + ctx.assertNotNull(ts); + ctx.assertTrue(ts instanceof OffsetDateTime, "Expected OffsetDateTime or LocalDateTime but got: " + (ts == null ? "null" : ts.getClass().getName())); + })); + } + + @Test + public void testDecodeOsonDate(TestContext ctx) { + pool.query("INSERT INTO json_test (id, data) VALUES (52, JSON_OBJECT('d' VALUE DATE '2023-09-21' RETURNING JSON))") + .execute() + .compose(v -> pool.preparedQuery("SELECT data FROM json_test WHERE id = ?").execute(Tuple.of(52))) + .onComplete(ctx.asyncAssertSuccess(rows -> { + ctx.assertEquals(1, rows.size()); + Row row = rows.iterator().next(); + JsonObject obj = row.getJsonObject(0); + ctx.assertNotNull(obj); + Object d = obj.getValue("d"); + ctx.assertNotNull(d); + ctx.assertTrue(d instanceof LocalDateTime, "Expected LocalDateTime but got: " + (d == null ? "null" : d.getClass().getName())); + })); + } + + @Test + public void testDecodeOsonIntervalYearToMonth(TestContext ctx) { + pool.query("INSERT INTO json_test (id, data) VALUES (53, JSON_OBJECT('iv' VALUE INTERVAL '2-3' YEAR TO MONTH RETURNING JSON))") + .execute() + .compose(v -> pool.preparedQuery("SELECT data FROM json_test WHERE id = ?").execute(Tuple.of(53))) + .onComplete(ctx.asyncAssertSuccess(rows -> { + ctx.assertEquals(1, rows.size()); + JsonObject obj = rows.iterator().next().getJsonObject(0); + ctx.assertNotNull(obj); + Object iv = obj.getValue("iv"); + ctx.assertTrue(iv instanceof Period, "Expected Period but got: " + (iv == null ? "null" : iv.getClass().getName())); + ctx.assertEquals(Period.of(2, 3, 0), iv); + })); + } + + @Test + public void testDecodeOsonIntervalDayToSecond(TestContext ctx) { + pool.query("INSERT INTO json_test (id, data) VALUES (54, JSON_OBJECT('iv' VALUE INTERVAL '1 02:03:04.5' DAY TO SECOND RETURNING JSON))") + .execute() + .compose(v -> pool.preparedQuery("SELECT data FROM json_test WHERE id = ?").execute(Tuple.of(54))) + .onComplete(ctx.asyncAssertSuccess(rows -> { + ctx.assertEquals(1, rows.size()); + JsonObject obj = rows.iterator().next().getJsonObject(0); + ctx.assertNotNull(obj); + Object iv = obj.getValue("iv"); + ctx.assertTrue(iv instanceof Duration, "Expected Duration but got: " + (iv == null ? "null" : iv.getClass().getName())); + Duration expected = Duration.ofDays(1).plusHours(2).plusMinutes(3).plusSeconds(4).plusMillis(500); + ctx.assertEquals(expected, iv); + })); + } }