From 92022400ce51eecee392eff24b55d28b6d9dc2dc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9lder=20Greg=C3=B3rio?= Date: Thu, 4 Dec 2025 13:09:25 +0000 Subject: [PATCH 1/9] working prototype --- .../ArrowFlightJdbcAccessorFactory.java | 5 + .../ArrowFlightJdbcUuidVectorAccessor.java | 88 ++++++++ .../impl/UuidAvaticaParameterConverter.java | 109 ++++++++++ .../jdbc/utils/AvaticaParameterBinder.java | 14 ++ .../arrow/driver/jdbc/utils/ConvertUtils.java | 19 +- .../arrow/driver/jdbc/utils/SqlTypes.java | 45 +++++ .../ArrowFlightJdbcAccessorFactoryTest.java | 13 ++ ...ArrowFlightJdbcUuidVectorAccessorTest.java | 188 ++++++++++++++++++ .../UuidAvaticaParameterConverterTest.java | 161 +++++++++++++++ .../utils/RootAllocatorTestExtension.java | 22 ++ .../arrow/driver/jdbc/utils/SqlTypesTest.java | 48 +++++ 11 files changed, 709 insertions(+), 3 deletions(-) create mode 100644 flight/flight-sql-jdbc-core/src/main/java/org/apache/arrow/driver/jdbc/accessor/impl/binary/ArrowFlightJdbcUuidVectorAccessor.java create mode 100644 flight/flight-sql-jdbc-core/src/main/java/org/apache/arrow/driver/jdbc/converter/impl/UuidAvaticaParameterConverter.java create mode 100644 flight/flight-sql-jdbc-core/src/test/java/org/apache/arrow/driver/jdbc/accessor/impl/binary/ArrowFlightJdbcUuidVectorAccessorTest.java create mode 100644 flight/flight-sql-jdbc-core/src/test/java/org/apache/arrow/driver/jdbc/converter/impl/UuidAvaticaParameterConverterTest.java diff --git a/flight/flight-sql-jdbc-core/src/main/java/org/apache/arrow/driver/jdbc/accessor/ArrowFlightJdbcAccessorFactory.java b/flight/flight-sql-jdbc-core/src/main/java/org/apache/arrow/driver/jdbc/accessor/ArrowFlightJdbcAccessorFactory.java index dad1fa5f73..4f086ab66f 100644 --- a/flight/flight-sql-jdbc-core/src/main/java/org/apache/arrow/driver/jdbc/accessor/ArrowFlightJdbcAccessorFactory.java +++ b/flight/flight-sql-jdbc-core/src/main/java/org/apache/arrow/driver/jdbc/accessor/ArrowFlightJdbcAccessorFactory.java @@ -19,6 +19,7 @@ import java.util.function.IntSupplier; import org.apache.arrow.driver.jdbc.accessor.impl.ArrowFlightJdbcNullVectorAccessor; import org.apache.arrow.driver.jdbc.accessor.impl.binary.ArrowFlightJdbcBinaryVectorAccessor; +import org.apache.arrow.driver.jdbc.accessor.impl.binary.ArrowFlightJdbcUuidVectorAccessor; import org.apache.arrow.driver.jdbc.accessor.impl.calendar.ArrowFlightJdbcDateVectorAccessor; import org.apache.arrow.driver.jdbc.accessor.impl.calendar.ArrowFlightJdbcDurationVectorAccessor; import org.apache.arrow.driver.jdbc.accessor.impl.calendar.ArrowFlightJdbcIntervalVectorAccessor; @@ -65,6 +66,7 @@ import org.apache.arrow.vector.UInt2Vector; import org.apache.arrow.vector.UInt4Vector; import org.apache.arrow.vector.UInt8Vector; +import org.apache.arrow.vector.UuidVector; import org.apache.arrow.vector.ValueVector; import org.apache.arrow.vector.VarBinaryVector; import org.apache.arrow.vector.VarCharVector; @@ -133,6 +135,9 @@ public static ArrowFlightJdbcAccessor createAccessor( } else if (vector instanceof LargeVarBinaryVector) { return new ArrowFlightJdbcBinaryVectorAccessor( (LargeVarBinaryVector) vector, getCurrentRow, setCursorWasNull); + } else if (vector instanceof UuidVector) { + return new ArrowFlightJdbcUuidVectorAccessor( + (UuidVector) vector, getCurrentRow, setCursorWasNull); } else if (vector instanceof FixedSizeBinaryVector) { return new ArrowFlightJdbcBinaryVectorAccessor( (FixedSizeBinaryVector) vector, getCurrentRow, setCursorWasNull); diff --git a/flight/flight-sql-jdbc-core/src/main/java/org/apache/arrow/driver/jdbc/accessor/impl/binary/ArrowFlightJdbcUuidVectorAccessor.java b/flight/flight-sql-jdbc-core/src/main/java/org/apache/arrow/driver/jdbc/accessor/impl/binary/ArrowFlightJdbcUuidVectorAccessor.java new file mode 100644 index 0000000000..4bdbcbb63d --- /dev/null +++ b/flight/flight-sql-jdbc-core/src/main/java/org/apache/arrow/driver/jdbc/accessor/impl/binary/ArrowFlightJdbcUuidVectorAccessor.java @@ -0,0 +1,88 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.arrow.driver.jdbc.accessor.impl.binary; + +import java.util.UUID; +import java.util.function.IntSupplier; +import org.apache.arrow.driver.jdbc.accessor.ArrowFlightJdbcAccessor; +import org.apache.arrow.driver.jdbc.accessor.ArrowFlightJdbcAccessorFactory; +import org.apache.arrow.vector.UuidVector; +import org.apache.arrow.vector.util.UuidUtility; + +/** + * Accessor for the Arrow UUID extension type ({@link UuidVector}). + * + *

This accessor provides JDBC-compatible access to UUID values stored in Arrow's canonical UUID + * extension type ('arrow.uuid'). It follows PostgreSQL JDBC driver conventions: + * + *

+ */ +public class ArrowFlightJdbcUuidVectorAccessor extends ArrowFlightJdbcAccessor { + + private final UuidVector vector; + + /** + * Creates a new accessor for a UUID vector. + * + * @param vector the UUID vector to access + * @param currentRowSupplier supplier for the current row index + * @param setCursorWasNull consumer to set the wasNull flag + */ + public ArrowFlightJdbcUuidVectorAccessor( + UuidVector vector, + IntSupplier currentRowSupplier, + ArrowFlightJdbcAccessorFactory.WasNullConsumer setCursorWasNull) { + super(currentRowSupplier, setCursorWasNull); + this.vector = vector; + } + + @Override + public Object getObject() { + UUID uuid = vector.getObject(getCurrentRow()); + this.wasNull = uuid == null; + this.wasNullConsumer.setWasNull(this.wasNull); + return uuid; + } + + @Override + public Class getObjectClass() { + return UUID.class; + } + + @Override + public String getString() { + UUID uuid = (UUID) getObject(); + if (uuid == null) { + return null; + } + return uuid.toString(); + } + + @Override + public byte[] getBytes() { + UUID uuid = (UUID) getObject(); + if (uuid == null) { + return null; + } + return UuidUtility.getBytesFromUUID(uuid); + } +} diff --git a/flight/flight-sql-jdbc-core/src/main/java/org/apache/arrow/driver/jdbc/converter/impl/UuidAvaticaParameterConverter.java b/flight/flight-sql-jdbc-core/src/main/java/org/apache/arrow/driver/jdbc/converter/impl/UuidAvaticaParameterConverter.java new file mode 100644 index 0000000000..b007298d36 --- /dev/null +++ b/flight/flight-sql-jdbc-core/src/main/java/org/apache/arrow/driver/jdbc/converter/impl/UuidAvaticaParameterConverter.java @@ -0,0 +1,109 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.arrow.driver.jdbc.converter.impl; + +import java.nio.ByteBuffer; +import java.sql.Types; +import java.util.UUID; +import org.apache.arrow.driver.jdbc.converter.AvaticaParameterConverter; +import org.apache.arrow.driver.jdbc.utils.SqlTypes; +import org.apache.arrow.vector.FieldVector; +import org.apache.arrow.vector.UuidVector; +import org.apache.arrow.vector.types.pojo.Field; +import org.apache.arrow.vector.util.UuidUtility; +import org.apache.calcite.avatica.AvaticaParameter; +import org.apache.calcite.avatica.remote.TypedValue; +import org.apache.calcite.avatica.util.ByteString; + +/** + * AvaticaParameterConverter for UUID Arrow extension type. + * + *

Handles conversion of UUID values from JDBC parameters to Arrow's UUID extension type. Accepts + * both {@link UUID} objects and String representations of UUIDs. + */ +public class UuidAvaticaParameterConverter implements AvaticaParameterConverter { + + public UuidAvaticaParameterConverter() {} + + @Override + public boolean bindParameter(FieldVector vector, TypedValue typedValue, int index) { + if (!(vector instanceof UuidVector)) { + return false; + } + + UuidVector uuidVector = (UuidVector) vector; + Object value = typedValue.toJdbc(null); + + if (value == null) { + uuidVector.setNull(index); + return true; + } + + UUID uuid; + if (value instanceof UUID) { + uuid = (UUID) value; + } else if (value instanceof String) { + uuid = UUID.fromString((String) value); + } else if (value instanceof byte[]) { + byte[] bytes = (byte[]) value; + if (bytes.length != 16) { + throw new IllegalArgumentException("UUID byte array must be 16 bytes, got " + bytes.length); + } + uuid = uuidFromBytes(bytes); + } else if (value instanceof ByteString) { + byte[] bytes = ((ByteString) value).getBytes(); + if (bytes.length != 16) { + throw new IllegalArgumentException("UUID byte array must be 16 bytes, got " + bytes.length); + } + uuid = uuidFromBytes(bytes); + } else { + throw new IllegalArgumentException( + "Cannot convert " + value.getClass().getName() + " to UUID"); + } + + uuidVector.setSafe(index, UuidUtility.getBytesFromUUID(uuid)); + return true; + } + + @Override + public AvaticaParameter createParameter(Field field) { + final String name = field.getName(); + final int jdbcType = Types.OTHER; + final String typeName = SqlTypes.UUID_TYPE_NAME; + final String className = UUID.class.getCanonicalName(); + return new AvaticaParameter(false, 0, 0, jdbcType, typeName, className, name); + } + + private static UUID uuidFromBytes(byte[] bytes) { +// long msb = 0; +// long lsb = 0; +// for (int i = 0; i < 8; i++) { +// msb = (msb << 8) | (bytes[i] & 0xff); +// } +// for (int i = 8; i < 16; i++) { +// lsb = (lsb << 8) | (bytes[i] & 0xff); +// } + + final long mostSignificantBits; + final long leastSignificantBits; + ByteBuffer bb = ByteBuffer.wrap(bytes); + mostSignificantBits = bb.getLong(); + leastSignificantBits = bb.getLong(); + + return new UUID(mostSignificantBits, leastSignificantBits); + } +} diff --git a/flight/flight-sql-jdbc-core/src/main/java/org/apache/arrow/driver/jdbc/utils/AvaticaParameterBinder.java b/flight/flight-sql-jdbc-core/src/main/java/org/apache/arrow/driver/jdbc/utils/AvaticaParameterBinder.java index 0fd99de539..a7eb675d6a 100644 --- a/flight/flight-sql-jdbc-core/src/main/java/org/apache/arrow/driver/jdbc/utils/AvaticaParameterBinder.java +++ b/flight/flight-sql-jdbc-core/src/main/java/org/apache/arrow/driver/jdbc/utils/AvaticaParameterBinder.java @@ -39,10 +39,14 @@ import org.apache.arrow.driver.jdbc.converter.impl.TimestampAvaticaParameterConverter; import org.apache.arrow.driver.jdbc.converter.impl.UnionAvaticaParameterConverter; import org.apache.arrow.driver.jdbc.converter.impl.Utf8AvaticaParameterConverter; +import org.apache.arrow.driver.jdbc.converter.impl.UuidAvaticaParameterConverter; import org.apache.arrow.memory.BufferAllocator; import org.apache.arrow.vector.FieldVector; import org.apache.arrow.vector.VectorSchemaRoot; +import org.apache.arrow.vector.extension.UuidType; import org.apache.arrow.vector.types.pojo.ArrowType; +import org.apache.arrow.vector.types.pojo.ArrowType.ArrowTypeVisitor; +import org.apache.arrow.vector.types.pojo.ArrowType.ExtensionType; import org.apache.calcite.avatica.remote.TypedValue; import org.checkerframework.checker.nullness.qual.Nullable; @@ -288,5 +292,15 @@ public Boolean visit(ArrowType.RunEndEncoded type) { throw new UnsupportedOperationException( "No Avatica parameter binder implemented for type " + type); } + + @Override + public Boolean visit(ExtensionType type) { + if (type instanceof UuidType) { + return new UuidAvaticaParameterConverter().bindParameter(vector, typedValue, index); + } + + // fallback to default implementation + return ArrowTypeVisitor.super.visit(type); + } } } diff --git a/flight/flight-sql-jdbc-core/src/main/java/org/apache/arrow/driver/jdbc/utils/ConvertUtils.java b/flight/flight-sql-jdbc-core/src/main/java/org/apache/arrow/driver/jdbc/utils/ConvertUtils.java index 5dd4c69c73..a3b822a356 100644 --- a/flight/flight-sql-jdbc-core/src/main/java/org/apache/arrow/driver/jdbc/utils/ConvertUtils.java +++ b/flight/flight-sql-jdbc-core/src/main/java/org/apache/arrow/driver/jdbc/utils/ConvertUtils.java @@ -43,8 +43,12 @@ import org.apache.arrow.driver.jdbc.converter.impl.UnionAvaticaParameterConverter; import org.apache.arrow.driver.jdbc.converter.impl.Utf8AvaticaParameterConverter; import org.apache.arrow.driver.jdbc.converter.impl.Utf8ViewAvaticaParameterConverter; +import org.apache.arrow.driver.jdbc.converter.impl.UuidAvaticaParameterConverter; import org.apache.arrow.flight.sql.FlightSqlColumnMetadata; +import org.apache.arrow.vector.extension.UuidType; import org.apache.arrow.vector.types.pojo.ArrowType; +import org.apache.arrow.vector.types.pojo.ArrowType.ArrowTypeVisitor; +import org.apache.arrow.vector.types.pojo.ArrowType.ExtensionType; import org.apache.arrow.vector.types.pojo.Field; import org.apache.calcite.avatica.AvaticaParameter; import org.apache.calcite.avatica.ColumnMetaData; @@ -68,7 +72,6 @@ public static List convertArrowFieldsToColumnMetaDataList( .map( index -> { final Field field = fields.get(index); - final ArrowType fieldType = field.getType(); final Common.ColumnMetaData.Builder builder = Common.ColumnMetaData.newBuilder() @@ -80,8 +83,8 @@ public static List convertArrowFieldsToColumnMetaDataList( builder.setType( Common.AvaticaType.newBuilder() - .setId(SqlTypes.getSqlTypeIdFromArrowType(fieldType)) - .setName(SqlTypes.getSqlTypeNameFromArrowType(fieldType)) + .setId(SqlTypes.getSqlTypeIdFromField(field)) + .setName(SqlTypes.getSqlTypeNameFromField(field)) .build()); return ColumnMetaData.fromProto(builder.build()); @@ -294,5 +297,15 @@ public AvaticaParameter visit(ArrowType.RunEndEncoded type) { throw new UnsupportedOperationException( "No Avatica parameter binder implemented for type " + type); } + + @Override + public AvaticaParameter visit(ExtensionType type) { + if (type instanceof UuidType) { + return new UuidAvaticaParameterConverter().createParameter(field); + } + + // fallback to default implementation + return ArrowTypeVisitor.super.visit(type); + } } } diff --git a/flight/flight-sql-jdbc-core/src/main/java/org/apache/arrow/driver/jdbc/utils/SqlTypes.java b/flight/flight-sql-jdbc-core/src/main/java/org/apache/arrow/driver/jdbc/utils/SqlTypes.java index 1b76ca0c95..221a5850fe 100644 --- a/flight/flight-sql-jdbc-core/src/main/java/org/apache/arrow/driver/jdbc/utils/SqlTypes.java +++ b/flight/flight-sql-jdbc-core/src/main/java/org/apache/arrow/driver/jdbc/utils/SqlTypes.java @@ -20,11 +20,16 @@ import java.sql.Types; import java.util.HashMap; import java.util.Map; +import org.apache.arrow.vector.extension.UuidType; import org.apache.arrow.vector.types.FloatingPointPrecision; import org.apache.arrow.vector.types.pojo.ArrowType; +import org.apache.arrow.vector.types.pojo.Field; /** SQL Types utility functions. */ public class SqlTypes { + /** SQL type name for UUID (matches PostgreSQL JDBC driver convention). */ + public static final String UUID_TYPE_NAME = "uuid"; + private static final Map typeIdToName = new HashMap<>(); static { @@ -162,4 +167,44 @@ public static int getSqlTypeIdFromArrowType(ArrowType arrowType) { throw new IllegalArgumentException("Unsupported ArrowType " + arrowType); } + + /** + * Convert given {@link Field} to its corresponding SQL type ID, handling extension types. + * + * @param field field to convert from + * @return corresponding SQL type ID. + * @see java.sql.Types + */ + public static int getSqlTypeIdFromField(Field field) { + ArrowType arrowType = field.getType(); + if (arrowType instanceof UuidType) { + return Types.OTHER; + } + return getSqlTypeIdFromArrowType(arrowType); + } + + /** + * Convert given {@link Field} to its corresponding SQL type name, handling extension types. + * + * @param field field to convert from + * @return corresponding SQL type name. + * @see java.sql.Types + */ + public static String getSqlTypeNameFromField(Field field) { + ArrowType arrowType = field.getType(); + if (arrowType instanceof UuidType) { + return UUID_TYPE_NAME; + } + return getSqlTypeNameFromArrowType(arrowType); + } + + /** + * Check if the given field represents a UUID type. + * + * @param field field to check + * @return true if the field is a UUID extension type + */ + public static boolean isUuidField(Field field) { + return field.getType() instanceof UuidType; + } } diff --git a/flight/flight-sql-jdbc-core/src/test/java/org/apache/arrow/driver/jdbc/accessor/ArrowFlightJdbcAccessorFactoryTest.java b/flight/flight-sql-jdbc-core/src/test/java/org/apache/arrow/driver/jdbc/accessor/ArrowFlightJdbcAccessorFactoryTest.java index b56bf3c63d..3e166d898d 100644 --- a/flight/flight-sql-jdbc-core/src/test/java/org/apache/arrow/driver/jdbc/accessor/ArrowFlightJdbcAccessorFactoryTest.java +++ b/flight/flight-sql-jdbc-core/src/test/java/org/apache/arrow/driver/jdbc/accessor/ArrowFlightJdbcAccessorFactoryTest.java @@ -16,10 +16,12 @@ */ package org.apache.arrow.driver.jdbc.accessor; +import static org.junit.jupiter.api.Assertions.assertInstanceOf; import static org.junit.jupiter.api.Assertions.assertTrue; import java.util.function.IntSupplier; import org.apache.arrow.driver.jdbc.accessor.impl.binary.ArrowFlightJdbcBinaryVectorAccessor; +import org.apache.arrow.driver.jdbc.accessor.impl.binary.ArrowFlightJdbcUuidVectorAccessor; import org.apache.arrow.driver.jdbc.accessor.impl.calendar.ArrowFlightJdbcDateVectorAccessor; import org.apache.arrow.driver.jdbc.accessor.impl.calendar.ArrowFlightJdbcDurationVectorAccessor; import org.apache.arrow.driver.jdbc.accessor.impl.calendar.ArrowFlightJdbcIntervalVectorAccessor; @@ -471,4 +473,15 @@ public void createAccessorForMapVector() { assertTrue(accessor instanceof ArrowFlightJdbcMapVectorAccessor); } } + + @Test + public void createAccessorForUuidVector() { + try (ValueVector valueVector = rootAllocatorTestExtension.createUuidVector()) { + ArrowFlightJdbcAccessor accessor = + ArrowFlightJdbcAccessorFactory.createAccessor( + valueVector, GET_CURRENT_ROW, (boolean wasNull) -> {}); + + assertInstanceOf(ArrowFlightJdbcUuidVectorAccessor.class, accessor); + } + } } diff --git a/flight/flight-sql-jdbc-core/src/test/java/org/apache/arrow/driver/jdbc/accessor/impl/binary/ArrowFlightJdbcUuidVectorAccessorTest.java b/flight/flight-sql-jdbc-core/src/test/java/org/apache/arrow/driver/jdbc/accessor/impl/binary/ArrowFlightJdbcUuidVectorAccessorTest.java new file mode 100644 index 0000000000..b7f341240c --- /dev/null +++ b/flight/flight-sql-jdbc-core/src/test/java/org/apache/arrow/driver/jdbc/accessor/impl/binary/ArrowFlightJdbcUuidVectorAccessorTest.java @@ -0,0 +1,188 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.arrow.driver.jdbc.accessor.impl.binary; + +import static org.hamcrest.CoreMatchers.equalTo; +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.CoreMatchers.nullValue; +import static org.hamcrest.MatcherAssert.assertThat; + +import java.util.UUID; +import org.apache.arrow.driver.jdbc.accessor.ArrowFlightJdbcAccessorFactory; +import org.apache.arrow.driver.jdbc.utils.RootAllocatorTestExtension; +import org.apache.arrow.vector.UuidVector; +import org.apache.arrow.vector.util.UuidUtility; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +/** + * Tests for {@link ArrowFlightJdbcUuidVectorAccessor}. + * + *

Verifies that the accessor correctly handles UUID values from Arrow's UUID extension type, + * following PostgreSQL JDBC driver conventions. + */ +public class ArrowFlightJdbcUuidVectorAccessorTest { + + @RegisterExtension + public static RootAllocatorTestExtension rootAllocatorTestExtension = + new RootAllocatorTestExtension(); + + private static final UUID UUID_1 = UUID.fromString("550e8400-e29b-41d4-a716-446655440000"); + private static final UUID UUID_2 = UUID.fromString("6ba7b810-9dad-11d1-80b4-00c04fd430c8"); + private static final UUID UUID_3 = UUID.fromString("f47ac10b-58cc-4372-a567-0e02b2c3d479"); + + private UuidVector vector; + private ArrowFlightJdbcUuidVectorAccessor accessor; + private boolean wasNullCalled; + private boolean wasNullValue; + + @BeforeEach + public void setUp() { + vector = rootAllocatorTestExtension.createUuidVector(); + wasNullCalled = false; + wasNullValue = false; + ArrowFlightJdbcAccessorFactory.WasNullConsumer wasNullConsumer = + (wasNull) -> { + wasNullCalled = true; + wasNullValue = wasNull; + }; + accessor = new ArrowFlightJdbcUuidVectorAccessor(vector, () -> 0, wasNullConsumer); + } + + @AfterEach + public void tearDown() { + vector.close(); + } + + @Test + public void testGetObjectReturnsUuid() { + accessor = new ArrowFlightJdbcUuidVectorAccessor(vector, () -> 0, (wasNull) -> {}); + Object result = accessor.getObject(); + assertThat(result, is(UUID_1)); + assertThat(accessor.wasNull(), is(false)); + } + + @Test + public void testGetObjectReturnsCorrectUuidForEachRow() { + accessor = new ArrowFlightJdbcUuidVectorAccessor(vector, () -> 0, (wasNull) -> {}); + assertThat(accessor.getObject(), is(UUID_1)); + + accessor = new ArrowFlightJdbcUuidVectorAccessor(vector, () -> 1, (wasNull) -> {}); + assertThat(accessor.getObject(), is(UUID_2)); + + accessor = new ArrowFlightJdbcUuidVectorAccessor(vector, () -> 2, (wasNull) -> {}); + assertThat(accessor.getObject(), is(UUID_3)); + } + + @Test + public void testGetObjectReturnsNullForNullValue() { + vector.reset(); + vector.allocateNew(1); + vector.setNull(0); + vector.setValueCount(1); + + accessor = new ArrowFlightJdbcUuidVectorAccessor(vector, () -> 0, (wasNull) -> {}); + Object result = accessor.getObject(); + assertThat(result, nullValue()); + assertThat(accessor.wasNull(), is(true)); + } + + @Test + public void testGetObjectClassReturnsUuidClass() { + assertThat(accessor.getObjectClass(), equalTo(UUID.class)); + } + + @Test + public void testGetStringReturnsHyphenatedFormat() { + accessor = new ArrowFlightJdbcUuidVectorAccessor(vector, () -> 0, (wasNull) -> {}); + String result = accessor.getString(); + assertThat(result, is("550e8400-e29b-41d4-a716-446655440000")); + assertThat(accessor.wasNull(), is(false)); + } + + @Test + public void testGetStringReturnsNullForNullValue() { + vector.reset(); + vector.allocateNew(1); + vector.setNull(0); + vector.setValueCount(1); + + accessor = new ArrowFlightJdbcUuidVectorAccessor(vector, () -> 0, (wasNull) -> {}); + String result = accessor.getString(); + assertThat(result, nullValue()); + assertThat(accessor.wasNull(), is(true)); + } + + @Test + public void testGetBytesReturns16ByteArray() { + accessor = new ArrowFlightJdbcUuidVectorAccessor(vector, () -> 0, (wasNull) -> {}); + byte[] result = accessor.getBytes(); + assertThat(result.length, is(16)); + assertThat(result, is(UuidUtility.getBytesFromUUID(UUID_1))); + assertThat(accessor.wasNull(), is(false)); + } + + @Test + public void testGetBytesReturnsNullForNullValue() { + vector.reset(); + vector.allocateNew(1); + vector.setNull(0); + vector.setValueCount(1); + + accessor = new ArrowFlightJdbcUuidVectorAccessor(vector, () -> 0, (wasNull) -> {}); + byte[] result = accessor.getBytes(); + assertThat(result, nullValue()); + assertThat(accessor.wasNull(), is(true)); + } + + @Test + public void testWasNullConsumerIsCalled() { + accessor = + new ArrowFlightJdbcUuidVectorAccessor( + vector, + () -> 0, + (wasNull) -> { + wasNullCalled = true; + wasNullValue = wasNull; + }); + accessor.getObject(); + assertThat(wasNullCalled, is(true)); + assertThat(wasNullValue, is(false)); + } + + @Test + public void testWasNullConsumerIsCalledWithTrueForNull() { + vector.reset(); + vector.allocateNew(1); + vector.setNull(0); + vector.setValueCount(1); + + accessor = + new ArrowFlightJdbcUuidVectorAccessor( + vector, + () -> 0, + (wasNull) -> { + wasNullCalled = true; + wasNullValue = wasNull; + }); + accessor.getObject(); + assertThat(wasNullCalled, is(true)); + assertThat(wasNullValue, is(true)); + } +} diff --git a/flight/flight-sql-jdbc-core/src/test/java/org/apache/arrow/driver/jdbc/converter/impl/UuidAvaticaParameterConverterTest.java b/flight/flight-sql-jdbc-core/src/test/java/org/apache/arrow/driver/jdbc/converter/impl/UuidAvaticaParameterConverterTest.java new file mode 100644 index 0000000000..d184e29f8e --- /dev/null +++ b/flight/flight-sql-jdbc-core/src/test/java/org/apache/arrow/driver/jdbc/converter/impl/UuidAvaticaParameterConverterTest.java @@ -0,0 +1,161 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.arrow.driver.jdbc.converter.impl; + +import static org.hamcrest.CoreMatchers.equalTo; +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.CoreMatchers.nullValue; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.sql.Types; +import java.util.UUID; +import org.apache.arrow.driver.jdbc.utils.RootAllocatorTestExtension; +import org.apache.arrow.driver.jdbc.utils.SqlTypes; +import org.apache.arrow.vector.UuidVector; +import org.apache.arrow.vector.extension.UuidType; +import org.apache.arrow.vector.types.pojo.Field; +import org.apache.arrow.vector.types.pojo.FieldType; +import org.apache.arrow.vector.util.UuidUtility; +import org.apache.calcite.avatica.AvaticaParameter; +import org.apache.calcite.avatica.ColumnMetaData; +import org.apache.calcite.avatica.remote.TypedValue; +import org.apache.calcite.avatica.util.ByteString; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +/** + * Tests for {@link UuidAvaticaParameterConverter}. + * + *

Verifies that the converter correctly handles UUID parameter binding from JDBC to Arrow's UUID + * extension type. + */ +public class UuidAvaticaParameterConverterTest { + + @RegisterExtension + public static RootAllocatorTestExtension rootAllocatorTestExtension = + new RootAllocatorTestExtension(); + + private static final UUID TEST_UUID = UUID.fromString("550e8400-e29b-41d4-a716-446655440000"); + + private UuidVector vector; + private UuidAvaticaParameterConverter converter; + + @BeforeEach + public void setUp() { + vector = new UuidVector("uuid_param", rootAllocatorTestExtension.getRootAllocator()); + vector.allocateNew(5); + converter = new UuidAvaticaParameterConverter(); + } + + @AfterEach + public void tearDown() { + vector.close(); + } + + @Test + public void testBindParameterWithUuidObject() { + TypedValue typedValue = TypedValue.ofLocal(ColumnMetaData.Rep.OBJECT, TEST_UUID); + + boolean result = converter.bindParameter(vector, typedValue, 0); + + assertTrue(result); + assertThat(vector.getObject(0), is(TEST_UUID)); + } + + @Test + public void testBindParameterWithUuidString() { + String uuidString = "550e8400-e29b-41d4-a716-446655440000"; + TypedValue typedValue = TypedValue.ofLocal(ColumnMetaData.Rep.STRING, uuidString); + + boolean result = converter.bindParameter(vector, typedValue, 0); + + assertTrue(result); + assertThat(vector.getObject(0), is(TEST_UUID)); + } + + @Test + public void testBindParameterWithByteArray() { + byte[] uuidBytes = UuidUtility.getBytesFromUUID(TEST_UUID); + ByteString byteString = new ByteString(uuidBytes); + TypedValue typedValue = TypedValue.ofLocal(ColumnMetaData.Rep.BYTE_STRING, byteString); + + boolean result = converter.bindParameter(vector, typedValue, 0); + + assertTrue(result); + assertThat(vector.getObject(0), is(TEST_UUID)); + } + + @Test + public void testBindParameterWithNullValue() { + TypedValue typedValue = TypedValue.ofLocal(ColumnMetaData.Rep.OBJECT, null); + + boolean result = converter.bindParameter(vector, typedValue, 0); + + assertTrue(result); + assertTrue(vector.isNull(0)); + assertThat(vector.getObject(0), nullValue()); + } + + @Test + public void testBindParameterWithInvalidByteArrayLength() { + byte[] invalidBytes = new byte[8]; // Should be 16 bytes + ByteString byteString = new ByteString(invalidBytes); + TypedValue typedValue = TypedValue.ofLocal(ColumnMetaData.Rep.BYTE_STRING, byteString); + + assertThrows( + IllegalArgumentException.class, () -> converter.bindParameter(vector, typedValue, 0)); + } + + @Test + public void testBindParameterWithInvalidType() { + TypedValue typedValue = TypedValue.ofLocal(ColumnMetaData.Rep.INTEGER, 12345); + + assertThrows( + IllegalArgumentException.class, () -> converter.bindParameter(vector, typedValue, 0)); + } + + @Test + public void testBindParameterMultipleValues() { + UUID uuid1 = UUID.fromString("550e8400-e29b-41d4-a716-446655440000"); + UUID uuid2 = UUID.fromString("6ba7b810-9dad-11d1-80b4-00c04fd430c8"); + UUID uuid3 = UUID.fromString("f47ac10b-58cc-4372-a567-0e02b2c3d479"); + + converter.bindParameter(vector, TypedValue.ofLocal(ColumnMetaData.Rep.OBJECT, uuid1), 0); + converter.bindParameter(vector, TypedValue.ofLocal(ColumnMetaData.Rep.OBJECT, uuid2), 1); + converter.bindParameter(vector, TypedValue.ofLocal(ColumnMetaData.Rep.OBJECT, uuid3), 2); + + assertThat(vector.getObject(0), is(uuid1)); + assertThat(vector.getObject(1), is(uuid2)); + assertThat(vector.getObject(2), is(uuid3)); + } + + @Test + public void testCreateParameter() { + Field uuidField = new Field("uuid_col", new FieldType(true, UuidType.INSTANCE, null), null); + + AvaticaParameter parameter = converter.createParameter(uuidField); + + assertThat(parameter.name, is("uuid_col")); + assertThat(parameter.parameterType, is(Types.OTHER)); + assertThat(parameter.typeName, is(SqlTypes.UUID_TYPE_NAME)); + assertThat(parameter.className, equalTo(UUID.class.getCanonicalName())); + } +} diff --git a/flight/flight-sql-jdbc-core/src/test/java/org/apache/arrow/driver/jdbc/utils/RootAllocatorTestExtension.java b/flight/flight-sql-jdbc-core/src/test/java/org/apache/arrow/driver/jdbc/utils/RootAllocatorTestExtension.java index 347e92a16c..4b299d63e0 100644 --- a/flight/flight-sql-jdbc-core/src/test/java/org/apache/arrow/driver/jdbc/utils/RootAllocatorTestExtension.java +++ b/flight/flight-sql-jdbc-core/src/test/java/org/apache/arrow/driver/jdbc/utils/RootAllocatorTestExtension.java @@ -19,6 +19,7 @@ import java.math.BigDecimal; import java.nio.charset.StandardCharsets; import java.util.Random; +import java.util.UUID; import java.util.concurrent.TimeUnit; import java.util.stream.IntStream; import org.apache.arrow.memory.BufferAllocator; @@ -53,6 +54,7 @@ import org.apache.arrow.vector.UInt2Vector; import org.apache.arrow.vector.UInt4Vector; import org.apache.arrow.vector.UInt8Vector; +import org.apache.arrow.vector.UuidVector; import org.apache.arrow.vector.VarBinaryVector; import org.apache.arrow.vector.complex.FixedSizeListVector; import org.apache.arrow.vector.complex.LargeListVector; @@ -60,6 +62,7 @@ import org.apache.arrow.vector.complex.impl.UnionFixedSizeListWriter; import org.apache.arrow.vector.complex.impl.UnionLargeListWriter; import org.apache.arrow.vector.complex.impl.UnionListWriter; +import org.apache.arrow.vector.util.UuidUtility; import org.junit.jupiter.api.extension.AfterAllCallback; import org.junit.jupiter.api.extension.BeforeAllCallback; import org.junit.jupiter.api.extension.ExtensionContext; @@ -811,4 +814,23 @@ public FixedSizeListVector createFixedSizeListVector() { return valueVector; } + + /** + * Create a UuidVector to be used in the accessor tests. + * + * @return UuidVector + */ + public UuidVector createUuidVector() { + UuidVector valueVector = new UuidVector("", this.getRootAllocator()); + valueVector.allocateNew(3); + valueVector.setSafe( + 0, UuidUtility.getBytesFromUUID(UUID.fromString("550e8400-e29b-41d4-a716-446655440000"))); + valueVector.setSafe( + 1, UuidUtility.getBytesFromUUID(UUID.fromString("6ba7b810-9dad-11d1-80b4-00c04fd430c8"))); + valueVector.setSafe( + 2, UuidUtility.getBytesFromUUID(UUID.fromString("f47ac10b-58cc-4372-a567-0e02b2c3d479"))); + valueVector.setValueCount(3); + + return valueVector; + } } diff --git a/flight/flight-sql-jdbc-core/src/test/java/org/apache/arrow/driver/jdbc/utils/SqlTypesTest.java b/flight/flight-sql-jdbc-core/src/test/java/org/apache/arrow/driver/jdbc/utils/SqlTypesTest.java index a6dd6b3275..d9228acd19 100644 --- a/flight/flight-sql-jdbc-core/src/test/java/org/apache/arrow/driver/jdbc/utils/SqlTypesTest.java +++ b/flight/flight-sql-jdbc-core/src/test/java/org/apache/arrow/driver/jdbc/utils/SqlTypesTest.java @@ -17,16 +17,24 @@ package org.apache.arrow.driver.jdbc.utils; import static org.apache.arrow.driver.jdbc.utils.SqlTypes.getSqlTypeIdFromArrowType; +import static org.apache.arrow.driver.jdbc.utils.SqlTypes.getSqlTypeIdFromField; import static org.apache.arrow.driver.jdbc.utils.SqlTypes.getSqlTypeNameFromArrowType; +import static org.apache.arrow.driver.jdbc.utils.SqlTypes.getSqlTypeNameFromField; +import static org.apache.arrow.driver.jdbc.utils.SqlTypes.isUuidField; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; import java.sql.Types; +import org.apache.arrow.vector.extension.UuidType; import org.apache.arrow.vector.types.DateUnit; import org.apache.arrow.vector.types.FloatingPointPrecision; import org.apache.arrow.vector.types.IntervalUnit; import org.apache.arrow.vector.types.TimeUnit; import org.apache.arrow.vector.types.UnionMode; import org.apache.arrow.vector.types.pojo.ArrowType; +import org.apache.arrow.vector.types.pojo.Field; +import org.apache.arrow.vector.types.pojo.FieldType; import org.junit.jupiter.api.Test; public class SqlTypesTest { @@ -137,4 +145,44 @@ public void testGetSqlTypeNameFromArrowType() { assertEquals("NULL", getSqlTypeNameFromArrowType(new ArrowType.Null())); } + + @Test + public void testGetSqlTypeIdFromFieldForUuid() { + Field uuidField = new Field("uuid_col", new FieldType(true, UuidType.INSTANCE, null), null); + assertEquals(Types.OTHER, getSqlTypeIdFromField(uuidField)); + } + + @Test + public void testGetSqlTypeNameFromFieldForUuid() { + Field uuidField = new Field("uuid_col", new FieldType(true, UuidType.INSTANCE, null), null); + assertEquals("uuid", getSqlTypeNameFromField(uuidField)); + } + + @Test + public void testIsUuidFieldReturnsTrue() { + Field uuidField = new Field("uuid_col", new FieldType(true, UuidType.INSTANCE, null), null); + assertTrue(isUuidField(uuidField)); + } + + @Test + public void testIsUuidFieldReturnsFalseForNonUuid() { + Field intField = new Field("int_col", FieldType.nullable(new ArrowType.Int(32, true)), null); + assertFalse(isUuidField(intField)); + + Field binaryField = + new Field("binary_col", FieldType.nullable(new ArrowType.FixedSizeBinary(16)), null); + assertFalse(isUuidField(binaryField)); + } + + @Test + public void testGetSqlTypeIdFromFieldForNonUuid() { + Field intField = new Field("int_col", FieldType.nullable(new ArrowType.Int(32, true)), null); + assertEquals(Types.INTEGER, getSqlTypeIdFromField(intField)); + } + + @Test + public void testGetSqlTypeNameFromFieldForNonUuid() { + Field intField = new Field("int_col", FieldType.nullable(new ArrowType.Int(32, true)), null); + assertEquals("INTEGER", getSqlTypeNameFromField(intField)); + } } From b6de51f393062bac10fbffabf89744b574190944 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9lder=20Greg=C3=B3rio?= Date: Thu, 4 Dec 2025 13:38:34 +0000 Subject: [PATCH 2/9] simplified convertion from arrow types to sql types --- .../arrow/driver/jdbc/utils/ConvertUtils.java | 5 ++- .../arrow/driver/jdbc/utils/SqlTypes.java | 43 ++----------------- .../arrow/driver/jdbc/utils/SqlTypesTest.java | 15 ++++--- 3 files changed, 14 insertions(+), 49 deletions(-) diff --git a/flight/flight-sql-jdbc-core/src/main/java/org/apache/arrow/driver/jdbc/utils/ConvertUtils.java b/flight/flight-sql-jdbc-core/src/main/java/org/apache/arrow/driver/jdbc/utils/ConvertUtils.java index a3b822a356..dd51ee5361 100644 --- a/flight/flight-sql-jdbc-core/src/main/java/org/apache/arrow/driver/jdbc/utils/ConvertUtils.java +++ b/flight/flight-sql-jdbc-core/src/main/java/org/apache/arrow/driver/jdbc/utils/ConvertUtils.java @@ -72,6 +72,7 @@ public static List convertArrowFieldsToColumnMetaDataList( .map( index -> { final Field field = fields.get(index); + final ArrowType fieldType = field.getType(); final Common.ColumnMetaData.Builder builder = Common.ColumnMetaData.newBuilder() @@ -83,8 +84,8 @@ public static List convertArrowFieldsToColumnMetaDataList( builder.setType( Common.AvaticaType.newBuilder() - .setId(SqlTypes.getSqlTypeIdFromField(field)) - .setName(SqlTypes.getSqlTypeNameFromField(field)) + .setId(SqlTypes.getSqlTypeIdFromArrowType(fieldType)) + .setName(SqlTypes.getSqlTypeNameFromArrowType(fieldType)) .build()); return ColumnMetaData.fromProto(builder.build()); diff --git a/flight/flight-sql-jdbc-core/src/main/java/org/apache/arrow/driver/jdbc/utils/SqlTypes.java b/flight/flight-sql-jdbc-core/src/main/java/org/apache/arrow/driver/jdbc/utils/SqlTypes.java index 221a5850fe..ffd06f23a3 100644 --- a/flight/flight-sql-jdbc-core/src/main/java/org/apache/arrow/driver/jdbc/utils/SqlTypes.java +++ b/flight/flight-sql-jdbc-core/src/main/java/org/apache/arrow/driver/jdbc/utils/SqlTypes.java @@ -114,6 +114,9 @@ public static int getSqlTypeIdFromArrowType(ArrowType arrowType) { case Binary: return Types.VARBINARY; case FixedSizeBinary: + if (arrowType instanceof UuidType) { + return Types.OTHER; + } return Types.BINARY; case LargeBinary: return Types.LONGVARBINARY; @@ -167,44 +170,4 @@ public static int getSqlTypeIdFromArrowType(ArrowType arrowType) { throw new IllegalArgumentException("Unsupported ArrowType " + arrowType); } - - /** - * Convert given {@link Field} to its corresponding SQL type ID, handling extension types. - * - * @param field field to convert from - * @return corresponding SQL type ID. - * @see java.sql.Types - */ - public static int getSqlTypeIdFromField(Field field) { - ArrowType arrowType = field.getType(); - if (arrowType instanceof UuidType) { - return Types.OTHER; - } - return getSqlTypeIdFromArrowType(arrowType); - } - - /** - * Convert given {@link Field} to its corresponding SQL type name, handling extension types. - * - * @param field field to convert from - * @return corresponding SQL type name. - * @see java.sql.Types - */ - public static String getSqlTypeNameFromField(Field field) { - ArrowType arrowType = field.getType(); - if (arrowType instanceof UuidType) { - return UUID_TYPE_NAME; - } - return getSqlTypeNameFromArrowType(arrowType); - } - - /** - * Check if the given field represents a UUID type. - * - * @param field field to check - * @return true if the field is a UUID extension type - */ - public static boolean isUuidField(Field field) { - return field.getType() instanceof UuidType; - } } diff --git a/flight/flight-sql-jdbc-core/src/test/java/org/apache/arrow/driver/jdbc/utils/SqlTypesTest.java b/flight/flight-sql-jdbc-core/src/test/java/org/apache/arrow/driver/jdbc/utils/SqlTypesTest.java index d9228acd19..c75ace3513 100644 --- a/flight/flight-sql-jdbc-core/src/test/java/org/apache/arrow/driver/jdbc/utils/SqlTypesTest.java +++ b/flight/flight-sql-jdbc-core/src/test/java/org/apache/arrow/driver/jdbc/utils/SqlTypesTest.java @@ -17,10 +17,7 @@ package org.apache.arrow.driver.jdbc.utils; import static org.apache.arrow.driver.jdbc.utils.SqlTypes.getSqlTypeIdFromArrowType; -import static org.apache.arrow.driver.jdbc.utils.SqlTypes.getSqlTypeIdFromField; import static org.apache.arrow.driver.jdbc.utils.SqlTypes.getSqlTypeNameFromArrowType; -import static org.apache.arrow.driver.jdbc.utils.SqlTypes.getSqlTypeNameFromField; -import static org.apache.arrow.driver.jdbc.utils.SqlTypes.isUuidField; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; @@ -149,13 +146,13 @@ public void testGetSqlTypeNameFromArrowType() { @Test public void testGetSqlTypeIdFromFieldForUuid() { Field uuidField = new Field("uuid_col", new FieldType(true, UuidType.INSTANCE, null), null); - assertEquals(Types.OTHER, getSqlTypeIdFromField(uuidField)); + assertEquals(Types.OTHER, getSqlTypeIdFromArrowType(uuidField.getType())); } @Test public void testGetSqlTypeNameFromFieldForUuid() { Field uuidField = new Field("uuid_col", new FieldType(true, UuidType.INSTANCE, null), null); - assertEquals("uuid", getSqlTypeNameFromField(uuidField)); + assertEquals("OTHER", getSqlTypeNameFromArrowType(uuidField.getType())); } @Test @@ -177,12 +174,16 @@ public void testIsUuidFieldReturnsFalseForNonUuid() { @Test public void testGetSqlTypeIdFromFieldForNonUuid() { Field intField = new Field("int_col", FieldType.nullable(new ArrowType.Int(32, true)), null); - assertEquals(Types.INTEGER, getSqlTypeIdFromField(intField)); + assertEquals(Types.INTEGER, getSqlTypeIdFromArrowType(intField.getType())); } @Test public void testGetSqlTypeNameFromFieldForNonUuid() { Field intField = new Field("int_col", FieldType.nullable(new ArrowType.Int(32, true)), null); - assertEquals("INTEGER", getSqlTypeNameFromField(intField)); + assertEquals("INTEGER", getSqlTypeNameFromArrowType(intField.getType())); + } + + static boolean isUuidField(Field field) { + return field.getType() instanceof UuidType; } } From 6a169eaf8069ec7732b1c3aae0afa040379f7d7d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9lder=20Greg=C3=B3rio?= Date: Thu, 4 Dec 2025 22:36:37 +0000 Subject: [PATCH 3/9] removed custom SQLType for uuid --- .../impl/UuidAvaticaParameterConverter.java | 19 ++++++++----------- .../arrow/driver/jdbc/utils/SqlTypes.java | 3 --- .../UuidAvaticaParameterConverterTest.java | 2 +- 3 files changed, 9 insertions(+), 15 deletions(-) diff --git a/flight/flight-sql-jdbc-core/src/main/java/org/apache/arrow/driver/jdbc/converter/impl/UuidAvaticaParameterConverter.java b/flight/flight-sql-jdbc-core/src/main/java/org/apache/arrow/driver/jdbc/converter/impl/UuidAvaticaParameterConverter.java index b007298d36..654790d07c 100644 --- a/flight/flight-sql-jdbc-core/src/main/java/org/apache/arrow/driver/jdbc/converter/impl/UuidAvaticaParameterConverter.java +++ b/flight/flight-sql-jdbc-core/src/main/java/org/apache/arrow/driver/jdbc/converter/impl/UuidAvaticaParameterConverter.java @@ -21,8 +21,12 @@ import java.util.UUID; import org.apache.arrow.driver.jdbc.converter.AvaticaParameterConverter; import org.apache.arrow.driver.jdbc.utils.SqlTypes; +import static org.apache.arrow.driver.jdbc.utils.SqlTypes.getSqlTypeIdFromArrowType; +import static org.apache.arrow.driver.jdbc.utils.SqlTypes.getSqlTypeNameFromArrowType; import org.apache.arrow.vector.FieldVector; import org.apache.arrow.vector.UuidVector; +import org.apache.arrow.vector.extension.UuidType; +import org.apache.arrow.vector.types.pojo.ExtensionTypeRegistry; import org.apache.arrow.vector.types.pojo.Field; import org.apache.arrow.vector.util.UuidUtility; import org.apache.calcite.avatica.AvaticaParameter; @@ -82,26 +86,19 @@ public boolean bindParameter(FieldVector vector, TypedValue typedValue, int inde @Override public AvaticaParameter createParameter(Field field) { final String name = field.getName(); - final int jdbcType = Types.OTHER; - final String typeName = SqlTypes.UUID_TYPE_NAME; + final int jdbcType = getSqlTypeIdFromArrowType(field.getType()); + final String typeName = getSqlTypeNameFromArrowType(field.getType()); final String className = UUID.class.getCanonicalName(); return new AvaticaParameter(false, 0, 0, jdbcType, typeName, className, name); } private static UUID uuidFromBytes(byte[] bytes) { -// long msb = 0; -// long lsb = 0; -// for (int i = 0; i < 8; i++) { -// msb = (msb << 8) | (bytes[i] & 0xff); -// } -// for (int i = 8; i < 16; i++) { -// lsb = (lsb << 8) | (bytes[i] & 0xff); -// } - final long mostSignificantBits; final long leastSignificantBits; ByteBuffer bb = ByteBuffer.wrap(bytes); + // Reads the first eight bytes mostSignificantBits = bb.getLong(); + // Reads the first eight bytes at this buffer's current leastSignificantBits = bb.getLong(); return new UUID(mostSignificantBits, leastSignificantBits); diff --git a/flight/flight-sql-jdbc-core/src/main/java/org/apache/arrow/driver/jdbc/utils/SqlTypes.java b/flight/flight-sql-jdbc-core/src/main/java/org/apache/arrow/driver/jdbc/utils/SqlTypes.java index ffd06f23a3..2a4380abb8 100644 --- a/flight/flight-sql-jdbc-core/src/main/java/org/apache/arrow/driver/jdbc/utils/SqlTypes.java +++ b/flight/flight-sql-jdbc-core/src/main/java/org/apache/arrow/driver/jdbc/utils/SqlTypes.java @@ -23,12 +23,9 @@ import org.apache.arrow.vector.extension.UuidType; import org.apache.arrow.vector.types.FloatingPointPrecision; import org.apache.arrow.vector.types.pojo.ArrowType; -import org.apache.arrow.vector.types.pojo.Field; /** SQL Types utility functions. */ public class SqlTypes { - /** SQL type name for UUID (matches PostgreSQL JDBC driver convention). */ - public static final String UUID_TYPE_NAME = "uuid"; private static final Map typeIdToName = new HashMap<>(); diff --git a/flight/flight-sql-jdbc-core/src/test/java/org/apache/arrow/driver/jdbc/converter/impl/UuidAvaticaParameterConverterTest.java b/flight/flight-sql-jdbc-core/src/test/java/org/apache/arrow/driver/jdbc/converter/impl/UuidAvaticaParameterConverterTest.java index d184e29f8e..839f561bca 100644 --- a/flight/flight-sql-jdbc-core/src/test/java/org/apache/arrow/driver/jdbc/converter/impl/UuidAvaticaParameterConverterTest.java +++ b/flight/flight-sql-jdbc-core/src/test/java/org/apache/arrow/driver/jdbc/converter/impl/UuidAvaticaParameterConverterTest.java @@ -155,7 +155,7 @@ public void testCreateParameter() { assertThat(parameter.name, is("uuid_col")); assertThat(parameter.parameterType, is(Types.OTHER)); - assertThat(parameter.typeName, is(SqlTypes.UUID_TYPE_NAME)); + assertThat(parameter.typeName, is("OTHER")); assertThat(parameter.className, equalTo(UUID.class.getCanonicalName())); } } From 8eac1cc1f73bf5fda1ed33fd1a958ee73be76be4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9lder=20Greg=C3=B3rio?= Date: Thu, 4 Dec 2025 22:41:43 +0000 Subject: [PATCH 4/9] register UUID type for jdbc driver --- .../org/apache/arrow/driver/jdbc/ArrowDatabaseMetadata.java | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/flight/flight-sql-jdbc-core/src/main/java/org/apache/arrow/driver/jdbc/ArrowDatabaseMetadata.java b/flight/flight-sql-jdbc-core/src/main/java/org/apache/arrow/driver/jdbc/ArrowDatabaseMetadata.java index 7185ddfe01..502270e1cd 100644 --- a/flight/flight-sql-jdbc-core/src/main/java/org/apache/arrow/driver/jdbc/ArrowDatabaseMetadata.java +++ b/flight/flight-sql-jdbc-core/src/main/java/org/apache/arrow/driver/jdbc/ArrowDatabaseMetadata.java @@ -75,10 +75,12 @@ import org.apache.arrow.vector.VarBinaryVector; import org.apache.arrow.vector.VarCharVector; import org.apache.arrow.vector.VectorSchemaRoot; +import org.apache.arrow.vector.extension.UuidType; import org.apache.arrow.vector.ipc.ReadChannel; import org.apache.arrow.vector.ipc.message.MessageSerializer; import org.apache.arrow.vector.types.Types; import org.apache.arrow.vector.types.pojo.ArrowType; +import org.apache.arrow.vector.types.pojo.ExtensionTypeRegistry; import org.apache.arrow.vector.types.pojo.Field; import org.apache.arrow.vector.types.pojo.Schema; import org.apache.arrow.vector.util.Text; @@ -164,6 +166,9 @@ public class ArrowDatabaseMetadata extends AvaticaDatabaseMetaData { LONGNVARCHAR, SqlSupportsConvert.SQL_CONVERT_LONGVARCHAR_VALUE); sqlTypesToFlightEnumConvertTypes.put(DATE, SqlSupportsConvert.SQL_CONVERT_DATE_VALUE); sqlTypesToFlightEnumConvertTypes.put(TIMESTAMP, SqlSupportsConvert.SQL_CONVERT_TIMESTAMP_VALUE); + + // Register the UUID extension type so it is always available for the driver + ExtensionTypeRegistry.register(UuidType.INSTANCE); } ArrowDatabaseMetadata(final AvaticaConnection connection) { From 9806e941fb47bbbc39199626c6fbd6402307ac79 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9lder=20Greg=C3=B3rio?= Date: Thu, 4 Dec 2025 23:22:29 +0000 Subject: [PATCH 5/9] simplify SqlTypesTest --- .../arrow/driver/jdbc/utils/SqlTypesTest.java | 50 ++----------------- 1 file changed, 3 insertions(+), 47 deletions(-) diff --git a/flight/flight-sql-jdbc-core/src/test/java/org/apache/arrow/driver/jdbc/utils/SqlTypesTest.java b/flight/flight-sql-jdbc-core/src/test/java/org/apache/arrow/driver/jdbc/utils/SqlTypesTest.java index c75ace3513..65099baf98 100644 --- a/flight/flight-sql-jdbc-core/src/test/java/org/apache/arrow/driver/jdbc/utils/SqlTypesTest.java +++ b/flight/flight-sql-jdbc-core/src/test/java/org/apache/arrow/driver/jdbc/utils/SqlTypesTest.java @@ -19,8 +19,6 @@ import static org.apache.arrow.driver.jdbc.utils.SqlTypes.getSqlTypeIdFromArrowType; import static org.apache.arrow.driver.jdbc.utils.SqlTypes.getSqlTypeNameFromArrowType; import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertTrue; import java.sql.Types; import org.apache.arrow.vector.extension.UuidType; @@ -30,8 +28,6 @@ import org.apache.arrow.vector.types.TimeUnit; import org.apache.arrow.vector.types.UnionMode; import org.apache.arrow.vector.types.pojo.ArrowType; -import org.apache.arrow.vector.types.pojo.Field; -import org.apache.arrow.vector.types.pojo.FieldType; import org.junit.jupiter.api.Test; public class SqlTypesTest { @@ -88,6 +84,8 @@ public void testGetSqlTypeIdFromArrowType() { assertEquals(Types.JAVA_OBJECT, getSqlTypeIdFromArrowType(new ArrowType.Map(true))); assertEquals(Types.NULL, getSqlTypeIdFromArrowType(new ArrowType.Null())); + + assertEquals(Types.OTHER, getSqlTypeIdFromArrowType(new UuidType())); } @Test @@ -141,49 +139,7 @@ public void testGetSqlTypeNameFromArrowType() { assertEquals("JAVA_OBJECT", getSqlTypeNameFromArrowType(new ArrowType.Map(true))); assertEquals("NULL", getSqlTypeNameFromArrowType(new ArrowType.Null())); - } - - @Test - public void testGetSqlTypeIdFromFieldForUuid() { - Field uuidField = new Field("uuid_col", new FieldType(true, UuidType.INSTANCE, null), null); - assertEquals(Types.OTHER, getSqlTypeIdFromArrowType(uuidField.getType())); - } - - @Test - public void testGetSqlTypeNameFromFieldForUuid() { - Field uuidField = new Field("uuid_col", new FieldType(true, UuidType.INSTANCE, null), null); - assertEquals("OTHER", getSqlTypeNameFromArrowType(uuidField.getType())); - } - - @Test - public void testIsUuidFieldReturnsTrue() { - Field uuidField = new Field("uuid_col", new FieldType(true, UuidType.INSTANCE, null), null); - assertTrue(isUuidField(uuidField)); - } - - @Test - public void testIsUuidFieldReturnsFalseForNonUuid() { - Field intField = new Field("int_col", FieldType.nullable(new ArrowType.Int(32, true)), null); - assertFalse(isUuidField(intField)); - - Field binaryField = - new Field("binary_col", FieldType.nullable(new ArrowType.FixedSizeBinary(16)), null); - assertFalse(isUuidField(binaryField)); - } - - @Test - public void testGetSqlTypeIdFromFieldForNonUuid() { - Field intField = new Field("int_col", FieldType.nullable(new ArrowType.Int(32, true)), null); - assertEquals(Types.INTEGER, getSqlTypeIdFromArrowType(intField.getType())); - } - - @Test - public void testGetSqlTypeNameFromFieldForNonUuid() { - Field intField = new Field("int_col", FieldType.nullable(new ArrowType.Int(32, true)), null); - assertEquals("INTEGER", getSqlTypeNameFromArrowType(intField.getType())); - } - static boolean isUuidField(Field field) { - return field.getType() instanceof UuidType; + assertEquals("OTHER", getSqlTypeNameFromArrowType(new UuidType())); } } From 9bb959b7765b36864f35f6a042aff5960749f65e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9lder=20Greg=C3=B3rio?= Date: Fri, 5 Dec 2025 17:06:58 +0000 Subject: [PATCH 6/9] add test application --- .../driver/jdbc/example/UuidSupportDemo.java | 726 ++++++++++++++++++ 1 file changed, 726 insertions(+) create mode 100644 flight/flight-sql-jdbc-core/src/test/java/org/apache/arrow/driver/jdbc/example/UuidSupportDemo.java diff --git a/flight/flight-sql-jdbc-core/src/test/java/org/apache/arrow/driver/jdbc/example/UuidSupportDemo.java b/flight/flight-sql-jdbc-core/src/test/java/org/apache/arrow/driver/jdbc/example/UuidSupportDemo.java new file mode 100644 index 0000000000..8356d4dc74 --- /dev/null +++ b/flight/flight-sql-jdbc-core/src/test/java/org/apache/arrow/driver/jdbc/example/UuidSupportDemo.java @@ -0,0 +1,726 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.arrow.driver.jdbc.example; + +import java.sql.Connection; +import java.sql.DriverManager; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.ResultSetMetaData; +import java.sql.SQLException; +import java.sql.Statement; +import java.sql.Types; +import java.util.ArrayList; +import java.util.List; +import java.util.Properties; +import java.util.UUID; +import org.apache.arrow.driver.jdbc.ArrowFlightJdbcDriver; + +/** + * Demonstrates UUID support in the Arrow Flight JDBC driver. + * + *

This example connects to a Flight SQL server and comprehensively tests UUID support using both + * Statement and PreparedStatement JDBC primitives for all DML operations (INSERT, UPDATE, DELETE, + * SELECT). + * + *

Prerequisites

+ * + * + * + *

Building and Running the Demo

+ * + *

Important: Arrow's memory management requires access to Java's internal NIO classes. + * The JVM must be started with the following option: + * + *

{@code
+ * --add-opens=java.base/java.nio=org.apache.arrow.memory.core,ALL-UNNAMED
+ * }
+ * + *

From the arrow-java root directory: + * + *

{@code
+ * # Step 1: Build the project (includes main and test classes)
+ * mvn install -DskipTests -pl flight/flight-sql-jdbc-core -am
+ *
+ * # Step 2: Compile test classes (required since demo is in src/test/java)
+ * mvn test-compile -pl flight/flight-sql-jdbc-core
+ *
+ * # Step 3: Run the demo using Maven
+ * MAVEN_OPTS="--add-opens=java.base/java.nio=ALL-UNNAMED" \
+ *   mvn exec:java -pl flight/flight-sql-jdbc-core \
+ *   -Dexec.mainClass="org.apache.arrow.driver.jdbc.example.UuidSupportDemo" \
+ *   -Dexec.classpathScope=test
+ * }
+ * + *

Or as a single command: + * + *

{@code
+ * mvn install -DskipTests -pl flight/flight-sql-jdbc-core -am && \
+ *   mvn test-compile -pl flight/flight-sql-jdbc-core && \
+ *   MAVEN_OPTS="--add-opens=java.base/java.nio=ALL-UNNAMED" \
+ *   mvn exec:java -pl flight/flight-sql-jdbc-core \
+ *   -Dexec.mainClass="org.apache.arrow.driver.jdbc.example.UuidSupportDemo" \
+ *   -Dexec.classpathScope=test
+ * }
+ * + *

Known Limitations

+ * + *

UUID Parameter Binding: When using PreparedStatement with UUID parameters, use {@code + * setString(index, uuid.toString())} instead of {@code setObject(index, uuid)}. This is because + * Flight SQL servers typically don't include UUID extension type information in the parameter + * schema, so the driver cannot determine that the parameter should be a UUID. The server (e.g., + * Dremio) will implicitly cast the UUID string to the UUID type. + */ +public class UuidSupportDemo { + + private static final String JDBC_URL = "jdbc:arrow-flight-sql://:"; + private static final String USERNAME = ""; + private static final String PASSWORD = ""; + + // Table schema: CREATE TABLE nas.uuid_table (id INT, uuid UUID) + private static final String TABLE_NAME = "nas.uuid_table"; + + // Well-known test UUIDs for consistent testing + private static final UUID UUID_1 = UUID.fromString("550e8400-e29b-41d4-a716-446655440000"); + private static final UUID UUID_2 = UUID.fromString("6ba7b810-9dad-11d1-80b4-00c04fd430c8"); + private static final UUID UUID_3 = UUID.fromString("f47ac10b-58cc-4372-a567-0e02b2c3d479"); + private static final UUID UUID_4 = UUID.fromString("a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11"); + private static final UUID UUID_5 = UUID.fromString("00000000-0000-0000-0000-000000000000"); + + public static void main(String[] args) { + System.out.println("=== Arrow Flight JDBC UUID Support Demo ===\n"); + + try { + Class.forName(ArrowFlightJdbcDriver.class.getName()); + } catch (ClassNotFoundException e) { + System.err.println("Arrow Flight JDBC driver not found on classpath"); + e.printStackTrace(); + return; + } + + Properties properties = new Properties(); + properties.put("user", USERNAME); + properties.put("password", PASSWORD); + properties.put("useEncryption", "false"); + + try (Connection connection = DriverManager.getConnection(JDBC_URL, properties)) { + // Run all demo sections + demonstrateInsertWithStatement(connection); + demonstrateInsertWithPreparedStatement(connection); + demonstrateSelectWithStatement(connection); + demonstrateSelectWithPreparedStatement(connection); + demonstrateUpdateWithStatement(connection); + demonstrateUpdateWithPreparedStatement(connection); + demonstrateDeleteWithStatement(connection); + demonstrateDeleteWithPreparedStatement(connection); + demonstrateNullUuidHandling(connection); + demonstrateBatchOperations(connection); + demonstrateUuidComparisons(connection); + demonstrateUuidRetrieval(connection); + + } catch (SQLException e) { + System.err.println("Error: " + e.getMessage()); + e.printStackTrace(); + } + } + + // ==================== INSERT Operations ==================== + + private static void demonstrateInsertWithStatement(Connection connection) throws SQLException { + System.out.println("=== INSERT with Statement ===\n"); + + try (Statement stmt = connection.createStatement()) { + // Insert single row with UUID literal + String sql1 = + String.format( + "INSERT INTO %s (id, uuid) VALUES (1000, UUID '%s')", TABLE_NAME, UUID_1.toString()); + int rows1 = stmt.executeUpdate(sql1); + System.out.println("Inserted row with UUID literal: " + rows1 + " row(s) affected"); + System.out.println(" ID: 1000, UUID: " + UUID_1); + + // Insert row with NULL UUID + String sql2 = String.format("INSERT INTO %s (id, uuid) VALUES (1001, NULL)", TABLE_NAME); + int rows2 = stmt.executeUpdate(sql2); + System.out.println("Inserted row with NULL UUID: " + rows2 + " row(s) affected"); + System.out.println(" ID: 1001, UUID: NULL"); + + // Insert multiple rows in single statement (if supported) + String sql3 = + String.format( + "INSERT INTO %s (id, uuid) VALUES (1002, UUID '%s')", TABLE_NAME, UUID_2.toString()); + int rows3 = stmt.executeUpdate(sql3); + System.out.println("Inserted additional row: " + rows3 + " row(s) affected"); + System.out.println(" ID: 1002, UUID: " + UUID_2); + } + System.out.println(); + } + + private static void demonstrateInsertWithPreparedStatement(Connection connection) + throws SQLException { + System.out.println("=== INSERT with PreparedStatement ===\n"); + + String insertSql = String.format("INSERT INTO %s (id, uuid) VALUES (?, ?)", TABLE_NAME); + + try (PreparedStatement pstmt = connection.prepareStatement(insertSql)) { + // Insert using setObject() with UUID + pstmt.setInt(1, 2000); + pstmt.setObject(2, UUID_3); + int rows1 = pstmt.executeUpdate(); + System.out.println("Insert with setObject(UUID): " + rows1 + " row(s) affected"); + System.out.println(" ID: 2000, UUID: " + UUID_3); + + // Insert using setString() with UUID string representation + pstmt.setInt(1, 2001); + pstmt.setString(2, UUID_4.toString()); + int rows2 = pstmt.executeUpdate(); + System.out.println("Insert with setString(UUID.toString()): " + rows2 + " row(s) affected"); + System.out.println(" ID: 2001, UUID: " + UUID_4); + + // Insert NULL UUID using setNull() + pstmt.setInt(1, 2002); + pstmt.setNull(2, Types.OTHER); + int rows3 = pstmt.executeUpdate(); + System.out.println("Insert with setNull(): " + rows3 + " row(s) affected"); + System.out.println(" ID: 2002, UUID: NULL"); + + // Insert NULL UUID using setObject(null) + pstmt.setInt(1, 2003); + pstmt.setObject(2, null); + int rows4 = pstmt.executeUpdate(); + System.out.println("Insert with setObject(null): " + rows4 + " row(s) affected"); + System.out.println(" ID: 2003, UUID: NULL"); + + // Insert zero UUID (all zeros) + pstmt.setInt(1, 2004); + pstmt.setObject(2, UUID_5); + int rows5 = pstmt.executeUpdate(); + System.out.println("Insert with zero UUID: " + rows5 + " row(s) affected"); + System.out.println(" ID: 2004, UUID: " + UUID_5); + } + System.out.println(); + } + + // ==================== SELECT Operations ==================== + + private static void demonstrateSelectWithStatement(Connection connection) throws SQLException { + System.out.println("=== SELECT with Statement ===\n"); + + try (Statement stmt = connection.createStatement()) { + // Select all rows + String sql1 = String.format("SELECT id, uuid FROM %s ORDER BY id LIMIT 10", TABLE_NAME); + try (ResultSet rs = stmt.executeQuery(sql1)) { + System.out.println("SELECT all rows (limited to 10):"); + printResultSet(rs); + } + + // Select with UUID equality condition + String sql2 = + String.format( + "SELECT id, uuid FROM %s WHERE uuid = UUID '%s'", TABLE_NAME, UUID_1.toString()); + try (ResultSet rs = stmt.executeQuery(sql2)) { + System.out.println("SELECT with UUID equality (uuid = " + UUID_1 + "):"); + printResultSet(rs); + } + + // Select NULL UUIDs + String sql3 = String.format("SELECT id, uuid FROM %s WHERE uuid IS NULL", TABLE_NAME); + try (ResultSet rs = stmt.executeQuery(sql3)) { + System.out.println("SELECT rows with NULL UUID:"); + printResultSet(rs); + } + + // Select non-NULL UUIDs + String sql4 = + String.format("SELECT id, uuid FROM %s WHERE uuid IS NOT NULL LIMIT 5", TABLE_NAME); + try (ResultSet rs = stmt.executeQuery(sql4)) { + System.out.println("SELECT rows with non-NULL UUID (limited to 5):"); + printResultSet(rs); + } + } + System.out.println(); + } + + private static void demonstrateSelectWithPreparedStatement(Connection connection) + throws SQLException { + System.out.println("=== SELECT with PreparedStatement ===\n"); + + // Select by UUID using setObject() + String sql1 = String.format("SELECT id, uuid FROM %s WHERE uuid = ?", TABLE_NAME); + try (PreparedStatement pstmt = connection.prepareStatement(sql1)) { + pstmt.setObject(1, UUID_1); + try (ResultSet rs = pstmt.executeQuery()) { + System.out.println("SELECT with setObject(UUID) parameter:"); + printResultSet(rs); + } + } + + // Select by UUID using setString() + try (PreparedStatement pstmt = connection.prepareStatement(sql1)) { + pstmt.setString(1, UUID_3.toString()); + try (ResultSet rs = pstmt.executeQuery()) { + System.out.println("SELECT with setString(UUID.toString()) parameter:"); + printResultSet(rs); + } + } + + // Select by ID range and retrieve UUID + String sql2 = + String.format("SELECT id, uuid FROM %s WHERE id >= ? AND id <= ? ORDER BY id", TABLE_NAME); + try (PreparedStatement pstmt = connection.prepareStatement(sql2)) { + pstmt.setInt(1, 2000); + pstmt.setInt(2, 2004); + try (ResultSet rs = pstmt.executeQuery()) { + System.out.println("SELECT by ID range (2000-2004):"); + printResultSet(rs); + } + } + System.out.println(); + } + + // ==================== UPDATE Operations ==================== + + private static void demonstrateUpdateWithStatement(Connection connection) throws SQLException { + System.out.println("=== UPDATE with Statement ===\n"); + + try (Statement stmt = connection.createStatement()) { + // Update UUID value by ID + String sql1 = + String.format( + "UPDATE %s SET uuid = UUID '%s' WHERE id = 1000", TABLE_NAME, UUID_4.toString()); + int rows1 = stmt.executeUpdate(sql1); + System.out.println("Update UUID by ID: " + rows1 + " row(s) affected"); + System.out.println(" Updated ID 1000 to UUID: " + UUID_4); + + // Update UUID to NULL + String sql2 = String.format("UPDATE %s SET uuid = NULL WHERE id = 1002", TABLE_NAME); + int rows2 = stmt.executeUpdate(sql2); + System.out.println("Update UUID to NULL: " + rows2 + " row(s) affected"); + System.out.println(" Updated ID 1002 to UUID: NULL"); + + // Update based on UUID condition + String sql3 = + String.format( + "UPDATE %s SET uuid = UUID '%s' WHERE uuid = UUID '%s'", + TABLE_NAME, UUID_1.toString(), UUID_4.toString()); + int rows3 = stmt.executeUpdate(sql3); + System.out.println("Update UUID based on UUID condition: " + rows3 + " row(s) affected"); + } + System.out.println(); + } + + private static void demonstrateUpdateWithPreparedStatement(Connection connection) + throws SQLException { + System.out.println("=== UPDATE with PreparedStatement ===\n"); + + // Update UUID using setObject() + String sql1 = String.format("UPDATE %s SET uuid = ? WHERE id = 2001", TABLE_NAME); + try (PreparedStatement pstmt = connection.prepareStatement(sql1)) { + pstmt.setObject(1, UUID_2); + pstmt.setInt(2, 2000); + int rows1 = pstmt.executeUpdate(); + System.out.println("Update with setObject(UUID): " + rows1 + " row(s) affected"); + System.out.println(" Updated ID 2000 to UUID: " + UUID_2); + } + +// Update UUID using setString() + try (PreparedStatement pstmt = connection.prepareStatement(sql1)) { + pstmt.setString(1, UUID_3.toString()); + pstmt.setInt(2, 2001); + int rows2 = pstmt.executeUpdate(); + System.out.println("Update with setString(UUID.toString()): " + rows2 + " row(s) affected"); + System.out.println(" Updated ID 2001 to UUID: " + UUID_3); + } + +// Update UUID to NULL using setNull() + try (PreparedStatement pstmt = connection.prepareStatement(sql1)) { + pstmt.setNull(1, Types.OTHER); + pstmt.setInt(2, 2004); + int rows3 = pstmt.executeUpdate(); + System.out.println("Update with setNull(): " + rows3 + " row(s) affected"); + System.out.println(" Updated ID 2004 to UUID: NULL"); + } + + // Update based on UUID parameter condition + String sql2 = String.format("UPDATE %s SET uuid = ? WHERE uuid = ?", TABLE_NAME); + try (PreparedStatement pstmt = connection.prepareStatement(sql2)) { + pstmt.setObject(1, UUID_5); + pstmt.setObject(2, UUID_2); + int rows4 = pstmt.executeUpdate(); + System.out.println("Update UUID based on UUID condition: " + rows4 + " row(s) affected"); + System.out.println(" Changed UUID " + UUID_2 + " to " + UUID_5); + } + System.out.println(); + } + + // ==================== DELETE Operations ==================== + + private static void demonstrateDeleteWithStatement(Connection connection) throws SQLException { + System.out.println("=== DELETE with Statement ===\n"); + + try (Statement stmt = connection.createStatement()) { + // Delete by specific UUID + String sql1 = + String.format("DELETE FROM %s WHERE uuid = UUID '%s'", TABLE_NAME, UUID_5.toString()); + int rows1 = stmt.executeUpdate(sql1); + System.out.println("Delete by UUID: " + rows1 + " row(s) affected"); + System.out.println(" Deleted rows with UUID: " + UUID_5); + + // Delete rows with NULL UUID + String sql2 = String.format("DELETE FROM %s WHERE uuid IS NULL AND id >= 1000", TABLE_NAME); + int rows2 = stmt.executeUpdate(sql2); + System.out.println("Delete rows with NULL UUID (id >= 1000): " + rows2 + " row(s) affected"); + + // Delete by ID (cleanup) + String sql3 = String.format("DELETE FROM %s WHERE id = 1001", TABLE_NAME); + int rows3 = stmt.executeUpdate(sql3); + System.out.println("Delete by ID: " + rows3 + " row(s) affected"); + } + System.out.println(); + } + + private static void demonstrateDeleteWithPreparedStatement(Connection connection) + throws SQLException { + System.out.println("=== DELETE with PreparedStatement ===\n"); + + // Delete by UUID using setObject() + String sql1 = String.format("DELETE FROM %s WHERE uuid = ?", TABLE_NAME); + try (PreparedStatement pstmt = connection.prepareStatement(sql1)) { + pstmt.setObject(1, UUID_3); + int rows1 = pstmt.executeUpdate(); + System.out.println("Delete with setObject(UUID): " + rows1 + " row(s) affected"); + System.out.println(" Deleted rows with UUID: " + UUID_3); + } + + // Delete by UUID using setString() + try (PreparedStatement pstmt = connection.prepareStatement(sql1)) { + pstmt.setString(1, UUID_1.toString()); + int rows2 = pstmt.executeUpdate(); + System.out.println("Delete with setString(UUID.toString()): " + rows2 + " row(s) affected"); + System.out.println(" Deleted rows with UUID: " + UUID_1); + } + + // Delete by ID range (cleanup) + String sql2 = String.format("DELETE FROM %s WHERE id >= ? AND id <= ?", TABLE_NAME); + try (PreparedStatement pstmt = connection.prepareStatement(sql2)) { + pstmt.setInt(1, 1000); + pstmt.setInt(2, 2999); + int rows3 = pstmt.executeUpdate(); + System.out.println("Delete by ID range (cleanup): " + rows3 + " row(s) affected"); + } + System.out.println(); + } + + // ==================== NULL UUID Handling ==================== + + private static void demonstrateNullUuidHandling(Connection connection) throws SQLException { + System.out.println("=== NULL UUID Handling ===\n"); + + // Insert rows with NULL UUIDs for testing + String insertSql = String.format("INSERT INTO %s (id, uuid) VALUES (?, ?)", TABLE_NAME); + try (PreparedStatement pstmt = connection.prepareStatement(insertSql)) { + pstmt.setInt(1, 3000); + pstmt.setNull(2, Types.OTHER); + pstmt.executeUpdate(); + + pstmt.setInt(1, 3001); + pstmt.setObject(2, null); + pstmt.executeUpdate(); + System.out.println("Inserted 2 rows with NULL UUIDs (IDs: 3000, 3001)"); + } + + // Query and verify NULL handling + String selectSql = + String.format("SELECT id, uuid FROM %s WHERE id >= 3000 AND id <= 3001", TABLE_NAME); + try (Statement stmt = connection.createStatement(); + ResultSet rs = stmt.executeQuery(selectSql)) { + System.out.println("\nRetrieving NULL UUID values:"); + while (rs.next()) { + int id = rs.getInt("id"); + + // Test getObject() + Object uuidObj = rs.getObject("uuid"); + boolean wasNull1 = rs.wasNull(); + + // Test getString() + String uuidStr = rs.getString("uuid"); + boolean wasNull2 = rs.wasNull(); + + // Test getObject(UUID.class) + UUID uuid = rs.getObject("uuid", UUID.class); + boolean wasNull3 = rs.wasNull(); + + System.out.println(" ID: " + id); + System.out.println(" getObject(): " + uuidObj + " (wasNull: " + wasNull1 + ")"); + System.out.println(" getString(): " + uuidStr + " (wasNull: " + wasNull2 + ")"); + System.out.println(" getObject(UUID.class): " + uuid + " (wasNull: " + wasNull3 + ")"); + } + } + + // Cleanup + String deleteSql = String.format("DELETE FROM %s WHERE id >= 3000 AND id <= 3001", TABLE_NAME); + try (Statement stmt = connection.createStatement()) { + stmt.executeUpdate(deleteSql); + System.out.println("\nCleaned up NULL UUID test rows"); + } + System.out.println(); + } + + // ==================== Batch Operations ==================== + + private static void demonstrateBatchOperations(Connection connection) throws SQLException { + System.out.println("=== Batch Operations with PreparedStatement ===\n"); + + String insertSql = String.format("INSERT INTO %s (id, uuid) VALUES (?, ?)", TABLE_NAME); + + try (PreparedStatement pstmt = connection.prepareStatement(insertSql)) { + // Add multiple rows to batch + List batchUuids = new ArrayList<>(); + for (int i = 0; i < 5; i++) { + UUID uuid = UUID.randomUUID(); + batchUuids.add(uuid); + pstmt.setInt(1, 4000 + i); + pstmt.setObject(2, uuid); + pstmt.addBatch(); + System.out.println("Added to batch: ID=" + (4000 + i) + ", UUID=" + uuid); + } + + // Execute batch + int[] results = pstmt.executeBatch(); + System.out.println("\nBatch execution results:"); + int totalAffected = 0; + for (int i = 0; i < results.length; i++) { + System.out.println(" Statement " + (i + 1) + ": " + results[i] + " row(s)"); + totalAffected += results[i]; + } + System.out.println("Total rows affected: " + totalAffected); + + // Verify batch insert + String selectSql = + String.format( + "SELECT id, uuid FROM %s WHERE id >= 4000 AND id <= 4004 ORDER BY id", TABLE_NAME); + try (Statement stmt = connection.createStatement(); + ResultSet rs = stmt.executeQuery(selectSql)) { + System.out.println("\nVerifying batch insert:"); + int idx = 0; + while (rs.next()) { + int id = rs.getInt("id"); + UUID retrievedUuid = rs.getObject("uuid", UUID.class); + UUID expectedUuid = batchUuids.get(idx); + boolean match = expectedUuid.equals(retrievedUuid); + System.out.println( + " ID: " + id + ", UUID: " + retrievedUuid + " (match: " + match + ")"); + idx++; + } + } + } + + // Cleanup batch test data + String deleteSql = String.format("DELETE FROM %s WHERE id >= 4000 AND id <= 4004", TABLE_NAME); + try (Statement stmt = connection.createStatement()) { + int deleted = stmt.executeUpdate(deleteSql); + System.out.println("\nCleaned up batch test rows: " + deleted + " row(s)"); + } + System.out.println(); + } + + // ==================== UUID Comparisons ==================== + + private static void demonstrateUuidComparisons(Connection connection) throws SQLException { + System.out.println("=== UUID Comparisons in WHERE Clauses ===\n"); + + // Insert test data + String insertSql = String.format("INSERT INTO %s (id, uuid) VALUES (?, ?)", TABLE_NAME); + try (PreparedStatement pstmt = connection.prepareStatement(insertSql)) { + pstmt.setInt(1, 5000); + pstmt.setObject(2, UUID_1); + pstmt.executeUpdate(); + + pstmt.setInt(1, 5001); + pstmt.setObject(2, UUID_2); + pstmt.executeUpdate(); + + pstmt.setInt(1, 5002); + pstmt.setObject(2, UUID_1); // Duplicate UUID + pstmt.executeUpdate(); + + pstmt.setInt(1, 5003); + pstmt.setNull(2, Types.OTHER); + pstmt.executeUpdate(); + System.out.println("Inserted test data for comparison tests"); + } + + // Equality comparison + String sql1 = String.format("SELECT id, uuid FROM %s WHERE uuid = ?", TABLE_NAME); + try (PreparedStatement pstmt = connection.prepareStatement(sql1)) { + pstmt.setObject(1, UUID_1); + try (ResultSet rs = pstmt.executeQuery()) { + System.out.println("\nEquality (uuid = " + UUID_1 + "):"); + printResultSet(rs); + } + } + + // Inequality comparison + String sql2 = + String.format( + "SELECT id, uuid FROM %s WHERE uuid <> ? AND id >= 5000 AND id <= 5003", TABLE_NAME); + try (PreparedStatement pstmt = connection.prepareStatement(sql2)) { + pstmt.setObject(1, UUID_1); + try (ResultSet rs = pstmt.executeQuery()) { + System.out.println("Inequality (uuid <> " + UUID_1 + "):"); + printResultSet(rs); + } + } + + // IS NULL comparison + String sql3 = + String.format( + "SELECT id, uuid FROM %s WHERE uuid IS NULL AND id >= 5000 AND id <= 5003", TABLE_NAME); + try (Statement stmt = connection.createStatement(); + ResultSet rs = stmt.executeQuery(sql3)) { + System.out.println("IS NULL comparison:"); + printResultSet(rs); + } + + // IS NOT NULL comparison + String sql4 = + String.format( + "SELECT id, uuid FROM %s WHERE uuid IS NOT NULL AND id >= 5000 AND id <= 5003", + TABLE_NAME); + try (Statement stmt = connection.createStatement(); + ResultSet rs = stmt.executeQuery(sql4)) { + System.out.println("IS NOT NULL comparison:"); + printResultSet(rs); + } + + // IN clause with UUIDs + String sql5 = + String.format( + "SELECT id, uuid FROM %s WHERE uuid IN (UUID '%s', UUID '%s') AND id >= 5000", + TABLE_NAME, UUID_1.toString(), UUID_2.toString()); + try (Statement stmt = connection.createStatement(); + ResultSet rs = stmt.executeQuery(sql5)) { + System.out.println("IN clause (uuid IN (" + UUID_1 + ", " + UUID_2 + ")):"); + printResultSet(rs); + } + + // Cleanup + String deleteSql = String.format("DELETE FROM %s WHERE id >= 5000 AND id <= 5003", TABLE_NAME); + try (Statement stmt = connection.createStatement()) { + stmt.executeUpdate(deleteSql); + System.out.println("Cleaned up comparison test rows"); + } + System.out.println(); + } + + // ==================== UUID Retrieval Methods ==================== + + private static void demonstrateUuidRetrieval(Connection connection) throws SQLException { + System.out.println("=== UUID Retrieval Methods ===\n"); + + // Insert test row + String insertSql = String.format("INSERT INTO %s (id, uuid) VALUES (?, ?)", TABLE_NAME); + try (PreparedStatement pstmt = connection.prepareStatement(insertSql)) { + pstmt.setInt(1, 6000); + pstmt.setObject(2, UUID_1); + pstmt.executeUpdate(); + System.out.println("Inserted test row: ID=6000, UUID=" + UUID_1); + } + + // Demonstrate all retrieval methods + String selectSql = String.format("SELECT id, uuid FROM %s WHERE id = 6000", TABLE_NAME); + try (Statement stmt = connection.createStatement(); + ResultSet rs = stmt.executeQuery(selectSql)) { + if (rs.next()) { + System.out.println("\nRetrieval methods for UUID column:"); + + // Method 1: getObject() - returns java.util.UUID + Object obj = rs.getObject("uuid"); + System.out.println(" getObject(): " + obj); + System.out.println(" Type: " + (obj != null ? obj.getClass().getName() : "null")); + + // Method 2: getObject(UUID.class) - returns java.util.UUID + UUID uuid = rs.getObject("uuid", UUID.class); + System.out.println(" getObject(UUID.class): " + uuid); + + // Method 3: getString() - returns hyphenated string format + String str = rs.getString("uuid"); + System.out.println(" getString(): " + str); + + // Method 4: getBytes() - returns 16-byte array + byte[] bytes = rs.getBytes("uuid"); + System.out.println(" getBytes(): " + bytesToHex(bytes) + " (" + bytes.length + " bytes)"); + + // Verify consistency + System.out.println("\nConsistency checks:"); + System.out.println(" getObject() instanceof UUID: " + (obj instanceof UUID)); + System.out.println(" getObject().equals(UUID_1): " + UUID_1.equals(obj)); + System.out.println( + " UUID.fromString(getString()).equals(UUID_1): " + + UUID_1.equals(UUID.fromString(str))); + + // Verify metadata + ResultSetMetaData meta = rs.getMetaData(); + System.out.println("\nColumn metadata:"); + System.out.println(" Column name: " + meta.getColumnName(2)); + System.out.println( + " Column type: " + meta.getColumnType(2) + " (Types.OTHER=" + Types.OTHER + ")"); + System.out.println(" Column type name: " + meta.getColumnTypeName(2)); + System.out.println(" Column class name: " + meta.getColumnClassName(2)); + } + } + + // Cleanup + String deleteSql = String.format("DELETE FROM %s WHERE id = 6000", TABLE_NAME); + try (Statement stmt = connection.createStatement()) { + stmt.executeUpdate(deleteSql); + System.out.println("\nCleaned up retrieval test row"); + } + System.out.println(); + } + + // ==================== Helper Methods ==================== + + private static void printResultSet(ResultSet rs) throws SQLException { + int count = 0; + while (rs.next()) { + count++; + int id = rs.getInt("id"); + UUID uuid = rs.getObject("uuid", UUID.class); + boolean wasNull = rs.wasNull(); + System.out.println(" Row " + count + ": ID=" + id + ", UUID=" + (wasNull ? "NULL" : uuid)); + } + if (count == 0) { + System.out.println(" (no rows)"); + } + System.out.println(); + } + + private static String bytesToHex(byte[] bytes) { + if (bytes == null) { + return "null"; + } + StringBuilder sb = new StringBuilder(); + for (byte b : bytes) { + sb.append(String.format("%02x", b)); + } + return sb.toString(); + } +} From cad2091c947cfbf4d00cbaca80f91da8456e17b9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9lder=20Greg=C3=B3rio?= Date: Fri, 5 Dec 2025 18:46:22 +0000 Subject: [PATCH 7/9] added jdbc api tests --- .../impl/UuidAvaticaParameterConverter.java | 9 +- .../arrow/driver/jdbc/ResultSetTest.java | 174 ++++++++++++++++++ .../UuidAvaticaParameterConverterTest.java | 1 - .../jdbc/utils/CoreMockedSqlProducers.java | 119 ++++++++++++ 4 files changed, 296 insertions(+), 7 deletions(-) diff --git a/flight/flight-sql-jdbc-core/src/main/java/org/apache/arrow/driver/jdbc/converter/impl/UuidAvaticaParameterConverter.java b/flight/flight-sql-jdbc-core/src/main/java/org/apache/arrow/driver/jdbc/converter/impl/UuidAvaticaParameterConverter.java index 654790d07c..b2157890cf 100644 --- a/flight/flight-sql-jdbc-core/src/main/java/org/apache/arrow/driver/jdbc/converter/impl/UuidAvaticaParameterConverter.java +++ b/flight/flight-sql-jdbc-core/src/main/java/org/apache/arrow/driver/jdbc/converter/impl/UuidAvaticaParameterConverter.java @@ -16,17 +16,14 @@ */ package org.apache.arrow.driver.jdbc.converter.impl; +import static org.apache.arrow.driver.jdbc.utils.SqlTypes.getSqlTypeIdFromArrowType; +import static org.apache.arrow.driver.jdbc.utils.SqlTypes.getSqlTypeNameFromArrowType; + import java.nio.ByteBuffer; -import java.sql.Types; import java.util.UUID; import org.apache.arrow.driver.jdbc.converter.AvaticaParameterConverter; -import org.apache.arrow.driver.jdbc.utils.SqlTypes; -import static org.apache.arrow.driver.jdbc.utils.SqlTypes.getSqlTypeIdFromArrowType; -import static org.apache.arrow.driver.jdbc.utils.SqlTypes.getSqlTypeNameFromArrowType; import org.apache.arrow.vector.FieldVector; import org.apache.arrow.vector.UuidVector; -import org.apache.arrow.vector.extension.UuidType; -import org.apache.arrow.vector.types.pojo.ExtensionTypeRegistry; import org.apache.arrow.vector.types.pojo.Field; import org.apache.arrow.vector.util.UuidUtility; import org.apache.calcite.avatica.AvaticaParameter; diff --git a/flight/flight-sql-jdbc-core/src/test/java/org/apache/arrow/driver/jdbc/ResultSetTest.java b/flight/flight-sql-jdbc-core/src/test/java/org/apache/arrow/driver/jdbc/ResultSetTest.java index 569b5495fe..1e8b996551 100644 --- a/flight/flight-sql-jdbc-core/src/test/java/org/apache/arrow/driver/jdbc/ResultSetTest.java +++ b/flight/flight-sql-jdbc-core/src/test/java/org/apache/arrow/driver/jdbc/ResultSetTest.java @@ -22,8 +22,10 @@ import static org.hamcrest.CoreMatchers.allOf; import static org.hamcrest.CoreMatchers.anyOf; import static org.hamcrest.CoreMatchers.containsString; +import static org.hamcrest.CoreMatchers.equalTo; import static org.hamcrest.CoreMatchers.instanceOf; import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.CoreMatchers.nullValue; import static org.hamcrest.MatcherAssert.assertThat; import static org.junit.jupiter.api.Assertions.*; @@ -31,7 +33,9 @@ import java.nio.charset.StandardCharsets; import java.sql.Connection; import java.sql.DriverManager; +import java.sql.PreparedStatement; import java.sql.ResultSet; +import java.sql.ResultSetMetaData; import java.sql.SQLException; import java.sql.SQLTimeoutException; import java.sql.Statement; @@ -42,6 +46,7 @@ import java.util.List; import java.util.Random; import java.util.Set; +import java.util.UUID; import java.util.concurrent.CountDownLatch; import org.apache.arrow.driver.jdbc.utils.CoreMockedSqlProducers; import org.apache.arrow.driver.jdbc.utils.FallbackFlightSqlProducer; @@ -61,6 +66,7 @@ import org.apache.arrow.vector.types.pojo.ArrowType; import org.apache.arrow.vector.types.pojo.Field; import org.apache.arrow.vector.types.pojo.Schema; +import org.apache.arrow.vector.util.UuidUtility; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; @@ -795,4 +801,172 @@ public void testResultSetAppMetadata() throws Exception { "foo".getBytes(StandardCharsets.UTF_8)); } } + + @Test + public void testSelectQueryWithUuidColumn() throws SQLException { + // Expectations + final int expectedRowCount = 4; + final UUID[] expectedUuids = new UUID[] { + CoreMockedSqlProducers.UUID_1, + CoreMockedSqlProducers.UUID_2, + CoreMockedSqlProducers.UUID_3, + null + }; + + final Integer[] expectedIds = new Integer[] {1, 2, 3, 4}; + + final List actualUuids = new ArrayList<>(expectedRowCount); + final List actualIds = new ArrayList<>(expectedRowCount); + + + // Query + int actualRowCount = 0; + try (Statement statement = connection.createStatement(); + ResultSet resultSet = statement.executeQuery(CoreMockedSqlProducers.UUID_SQL_CMD)) { + for (; resultSet.next(); actualRowCount++) { + actualIds.add((Integer) resultSet.getObject("id")); + actualUuids.add((UUID) resultSet.getObject("uuid_col")); + } + } + + // Assertions + int finalActualRowCount = actualRowCount; + assertAll( + "UUID ResultSet values are as expected", + () -> assertThat(finalActualRowCount, is(equalTo(expectedRowCount))), + () -> assertThat(actualIds.toArray(new Integer[0]), is(expectedIds)), + () -> assertThat(actualUuids.toArray(new UUID[0]), is(expectedUuids))); + } + + @Test + public void testGetObjectReturnsUuid() throws SQLException { + try (Statement statement = connection.createStatement(); + ResultSet resultSet = statement.executeQuery(CoreMockedSqlProducers.UUID_SQL_CMD)) { + resultSet.next(); + Object result = resultSet.getObject("uuid_col"); + assertThat(result, instanceOf(UUID.class)); + assertThat(result, is(CoreMockedSqlProducers.UUID_1)); + } + } + + @Test + public void testGetObjectByIndexReturnsUuid() throws SQLException { + try (Statement statement = connection.createStatement(); + ResultSet resultSet = statement.executeQuery(CoreMockedSqlProducers.UUID_SQL_CMD)) { + resultSet.next(); + Object result = resultSet.getObject(2); + assertThat(result, instanceOf(UUID.class)); + assertThat(result, is(CoreMockedSqlProducers.UUID_1)); + } + } + + @Test + public void testGetStringReturnsHyphenatedFormat() throws SQLException { + try (Statement statement = connection.createStatement(); + ResultSet resultSet = statement.executeQuery(CoreMockedSqlProducers.UUID_SQL_CMD)) { + resultSet.next(); + String result = resultSet.getString("uuid_col"); + assertThat(result, is(CoreMockedSqlProducers.UUID_1.toString())); + } + } + + @Test + public void testGetBytesReturns16ByteArray() throws SQLException { + try (Statement statement = connection.createStatement(); + ResultSet resultSet = statement.executeQuery(CoreMockedSqlProducers.UUID_SQL_CMD)) { + resultSet.next(); + byte[] result = resultSet.getBytes("uuid_col"); + assertThat(result.length, is(16)); + assertThat(result, is(UuidUtility.getBytesFromUUID(CoreMockedSqlProducers.UUID_1))); + } + } + + @Test + public void testNullUuidHandling() throws SQLException { + try (Statement statement = connection.createStatement(); + ResultSet resultSet = statement.executeQuery(CoreMockedSqlProducers.UUID_SQL_CMD)) { + // Skip to row 4 which has NULL UUID + resultSet.next(); // row 1 + resultSet.next(); // row 2 + resultSet.next(); // row 3 + resultSet.next(); // row 4 (NULL UUID) + + Object objResult = resultSet.getObject("uuid_col"); + assertThat(objResult, nullValue()); + assertThat(resultSet.wasNull(), is(true)); + + String strResult = resultSet.getString("uuid_col"); + assertThat(strResult, nullValue()); + assertThat(resultSet.wasNull(), is(true)); + + byte[] bytesResult = resultSet.getBytes("uuid_col"); + assertThat(bytesResult, nullValue()); + assertThat(resultSet.wasNull(), is(true)); + } + } + + @Test + public void testMultipleUuidRows() throws SQLException { + try (Statement statement = connection.createStatement(); + ResultSet resultSet = statement.executeQuery(CoreMockedSqlProducers.UUID_SQL_CMD)) { + resultSet.next(); + assertThat(resultSet.getObject("uuid_col"), is(CoreMockedSqlProducers.UUID_1)); + + resultSet.next(); + assertThat(resultSet.getObject("uuid_col"), is(CoreMockedSqlProducers.UUID_2)); + + resultSet.next(); + assertThat(resultSet.getObject("uuid_col"), is(CoreMockedSqlProducers.UUID_3)); + + resultSet.next(); + assertThat(resultSet.getObject("uuid_col"), nullValue()); + } + } + + @Test + public void testUuidExtensionTypeInSchema() throws SQLException { + try (Statement statement = connection.createStatement(); + ResultSet resultSet = statement.executeQuery(CoreMockedSqlProducers.UUID_SQL_CMD)) { + ResultSetMetaData metaData = resultSet.getMetaData(); + + assertThat(metaData.getColumnCount(), is(2)); + assertThat(metaData.getColumnName(1), is("id")); + assertThat(metaData.getColumnName(2), is("uuid_col")); + + assertThat(metaData.getColumnType(2), is(java.sql.Types.OTHER)); + } + } + + @Test + public void testPreparedStatementWithUuidParameter() throws SQLException { + try (PreparedStatement pstmt = + connection.prepareStatement(CoreMockedSqlProducers.UUID_PREPARED_SELECT_SQL_CMD)) { + pstmt.setObject(1, CoreMockedSqlProducers.UUID_1); + try (ResultSet rs = pstmt.executeQuery()) { + rs.next(); + assertThat(rs.getObject("uuid_col"), is(CoreMockedSqlProducers.UUID_1)); + } + } + } + + @Test + public void testPreparedStatementWithUuidStringParameter() throws SQLException { + try (PreparedStatement pstmt = connection.prepareStatement(CoreMockedSqlProducers.UUID_PREPARED_SELECT_SQL_CMD)) { + pstmt.setString(1, CoreMockedSqlProducers.UUID_1.toString()); + try (ResultSet rs = pstmt.executeQuery()) { + rs.next(); + assertThat(rs.getObject("uuid_col"), is(CoreMockedSqlProducers.UUID_1)); + } + } + } + + @Test + public void testPreparedStatementUpdateWithUuid() throws SQLException { + try (PreparedStatement pstmt = connection.prepareStatement(CoreMockedSqlProducers.UUID_PREPARED_UPDATE_SQL_CMD)) { + pstmt.setObject(1, CoreMockedSqlProducers.UUID_3); + pstmt.setInt(2, 1); + int updated = pstmt.executeUpdate(); + assertThat(updated, is(1)); + } + } } diff --git a/flight/flight-sql-jdbc-core/src/test/java/org/apache/arrow/driver/jdbc/converter/impl/UuidAvaticaParameterConverterTest.java b/flight/flight-sql-jdbc-core/src/test/java/org/apache/arrow/driver/jdbc/converter/impl/UuidAvaticaParameterConverterTest.java index 839f561bca..07751f0abc 100644 --- a/flight/flight-sql-jdbc-core/src/test/java/org/apache/arrow/driver/jdbc/converter/impl/UuidAvaticaParameterConverterTest.java +++ b/flight/flight-sql-jdbc-core/src/test/java/org/apache/arrow/driver/jdbc/converter/impl/UuidAvaticaParameterConverterTest.java @@ -26,7 +26,6 @@ import java.sql.Types; import java.util.UUID; import org.apache.arrow.driver.jdbc.utils.RootAllocatorTestExtension; -import org.apache.arrow.driver.jdbc.utils.SqlTypes; import org.apache.arrow.vector.UuidVector; import org.apache.arrow.vector.extension.UuidType; import org.apache.arrow.vector.types.pojo.Field; diff --git a/flight/flight-sql-jdbc-core/src/test/java/org/apache/arrow/driver/jdbc/utils/CoreMockedSqlProducers.java b/flight/flight-sql-jdbc-core/src/test/java/org/apache/arrow/driver/jdbc/utils/CoreMockedSqlProducers.java index 8197d7d95f..70f3c26cd8 100644 --- a/flight/flight-sql-jdbc-core/src/test/java/org/apache/arrow/driver/jdbc/utils/CoreMockedSqlProducers.java +++ b/flight/flight-sql-jdbc-core/src/test/java/org/apache/arrow/driver/jdbc/utils/CoreMockedSqlProducers.java @@ -17,6 +17,7 @@ package org.apache.arrow.driver.jdbc.utils; import static java.lang.String.format; +import java.util.Arrays; import static org.hamcrest.CoreMatchers.equalTo; import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.MatcherAssert.assertThat; @@ -30,6 +31,7 @@ import java.util.ArrayList; import java.util.Collections; import java.util.List; +import java.util.UUID; import java.util.function.Consumer; import java.util.stream.IntStream; import org.apache.arrow.flight.FlightProducer.ServerStreamListener; @@ -40,10 +42,13 @@ import org.apache.arrow.vector.DateDayVector; import org.apache.arrow.vector.Float4Vector; import org.apache.arrow.vector.Float8Vector; +import org.apache.arrow.vector.IntVector; import org.apache.arrow.vector.TimeStampMilliVector; import org.apache.arrow.vector.UInt4Vector; +import org.apache.arrow.vector.UuidVector; import org.apache.arrow.vector.VarCharVector; import org.apache.arrow.vector.VectorSchemaRoot; +import org.apache.arrow.vector.extension.UuidType; import org.apache.arrow.vector.types.DateUnit; import org.apache.arrow.vector.types.FloatingPointPrecision; import org.apache.arrow.vector.types.TimeUnit; @@ -52,6 +57,7 @@ import org.apache.arrow.vector.types.pojo.FieldType; import org.apache.arrow.vector.types.pojo.Schema; import org.apache.arrow.vector.util.Text; +import org.apache.arrow.vector.util.UuidUtility; /** Standard {@link MockFlightSqlProducer} instances for tests. */ // TODO Remove this once all tests are refactor to use only the queries they need. @@ -62,6 +68,20 @@ public final class CoreMockedSqlProducers { public static final String LEGACY_CANCELLATION_SQL_CMD = "SELECT * FROM TAKES_FOREVER"; public static final String LEGACY_REGULAR_WITH_EMPTY_SQL_CMD = "SELECT * FROM TEST_EMPTIES"; + public static final String UUID_SQL_CMD = "SELECT * FROM UUID_TABLE"; + public static final String UUID_PREPARED_SELECT_SQL_CMD = "SELECT * FROM UUID_TABLE WHERE uuid_col = ?"; + public static final String UUID_PREPARED_UPDATE_SQL_CMD = "UPDATE UUID_TABLE SET uuid_col = ? WHERE id = ?"; + + public static final UUID UUID_1 = UUID.fromString("550e8400-e29b-41d4-a716-446655440000"); + public static final UUID UUID_2 = UUID.fromString("6ba7b810-9dad-11d1-80b4-00c04fd430c8"); + public static final UUID UUID_3 = UUID.fromString("f47ac10b-58cc-4372-a567-0e02b2c3d479"); + + public static final Schema UUID_SCHEMA = + new Schema( + ImmutableList.of( + new Field("id", new FieldType(true, new ArrowType.Int(32, true), null), null), + new Field("uuid_col", new FieldType(true, UuidType.INSTANCE, null), null))); + private CoreMockedSqlProducers() { // Prevent instantiation. } @@ -78,9 +98,108 @@ public static MockFlightSqlProducer getLegacyProducer() { addLegacyMetadataSqlCmdSupport(producer); addLegacyCancellationSqlCmdSupport(producer); addQueryWithEmbeddedEmptyRoot(producer); + addUuidSqlCmdSupport(producer); + addUuidPreparedSelectSqlCmdSupport(producer); + addUuidPreparedUpdateSqlCmdSupport(producer); return producer; } + /** + * Gets a {@link MockFlightSqlProducer} configured with UUID test data. + * + * @return a new producer with UUID support. + */ + public static MockFlightSqlProducer getUuidProducer() { + final MockFlightSqlProducer producer = new MockFlightSqlProducer(); + addUuidSqlCmdSupport(producer); + return producer; + } + private static void addUuidPreparedUpdateSqlCmdSupport(final MockFlightSqlProducer producer) { + final String query = "UPDATE UUID_TABLE SET uuid_col = ? WHERE id = ?"; + final Schema parameterSchema = + new Schema( + Arrays.asList( + new Field("", new FieldType(true, UuidType.INSTANCE, null), null), + Field.nullable("", new ArrowType.Int(32, true)))); + + producer.addUpdateQuery(query, 1); + producer.addExpectedParameters( + UUID_PREPARED_UPDATE_SQL_CMD, + parameterSchema, + Collections.singletonList(Arrays.asList(CoreMockedSqlProducers.UUID_3, 1))); + } + + private static void addUuidPreparedSelectSqlCmdSupport(final MockFlightSqlProducer producer) { + final Schema parameterSchema = + new Schema( + Collections.singletonList( + new Field("", new FieldType(true, UuidType.INSTANCE, null), null))); + + final Consumer uuidResultProvider = + listener -> { + try (final BufferAllocator allocator = new RootAllocator(Long.MAX_VALUE); + final VectorSchemaRoot root = VectorSchemaRoot.create(UUID_SCHEMA, allocator)) { + root.allocateNew(); + IntVector idVector = (IntVector) root.getVector("id"); + UuidVector uuidVector = (UuidVector) root.getVector("uuid_col"); + idVector.setSafe(0, 1); + uuidVector.setSafe(0, UuidUtility.getBytesFromUUID(CoreMockedSqlProducers.UUID_1)); + root.setRowCount(1); + listener.start(root); + listener.putNext(); + } catch (final Throwable throwable) { + listener.error(throwable); + } finally { + listener.completed(); + } + }; + + producer.addSelectQuery( + UUID_PREPARED_SELECT_SQL_CMD, UUID_SCHEMA, Collections.singletonList(uuidResultProvider)); + producer.addExpectedParameters( + UUID_PREPARED_SELECT_SQL_CMD, + parameterSchema, + Collections.singletonList(Collections.singletonList(CoreMockedSqlProducers.UUID_1))); + } + + private static void addUuidSqlCmdSupport(final MockFlightSqlProducer producer) { + final Consumer uuidResultProvider = + listener -> { + try (final BufferAllocator allocator = new RootAllocator(Long.MAX_VALUE); + final VectorSchemaRoot root = VectorSchemaRoot.create(UUID_SCHEMA, allocator)) { + root.allocateNew(); + + IntVector idVector = (IntVector) root.getVector("id"); + UuidVector uuidVector = (UuidVector) root.getVector("uuid_col"); + + // Row 0: id=1, uuid=UUID_1 + idVector.setSafe(0, 1); + uuidVector.setSafe(0, UuidUtility.getBytesFromUUID(UUID_1)); + + // Row 1: id=2, uuid=UUID_2 + idVector.setSafe(1, 2); + uuidVector.setSafe(1, UuidUtility.getBytesFromUUID(UUID_2)); + + // Row 2: id=3, uuid=UUID_3 + idVector.setSafe(2, 3); + uuidVector.setSafe(2, UuidUtility.getBytesFromUUID(UUID_3)); + + // Row 3: id=4, uuid=NULL + idVector.setSafe(3, 4); + uuidVector.setNull(3); + + root.setRowCount(4); + listener.start(root); + listener.putNext(); + } finally { + listener.completed(); + } + }; + + producer.addSelectQuery( + UUID_SQL_CMD, UUID_SCHEMA, Collections.singletonList(uuidResultProvider)); + } + private static void addQueryWithEmbeddedEmptyRoot(final MockFlightSqlProducer producer) { final Schema querySchema = new Schema( From 446e52e7ffd9d04b2400bb776e7c89aecd3a4903 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9lder=20Greg=C3=B3rio?= Date: Fri, 5 Dec 2025 18:46:46 +0000 Subject: [PATCH 8/9] spotless --- .../driver/jdbc/example/UuidSupportDemo.java | 71 ++++++++++--------- 1 file changed, 36 insertions(+), 35 deletions(-) diff --git a/flight/flight-sql-jdbc-core/src/test/java/org/apache/arrow/driver/jdbc/example/UuidSupportDemo.java b/flight/flight-sql-jdbc-core/src/test/java/org/apache/arrow/driver/jdbc/example/UuidSupportDemo.java index 8356d4dc74..b85bb0aac3 100644 --- a/flight/flight-sql-jdbc-core/src/test/java/org/apache/arrow/driver/jdbc/example/UuidSupportDemo.java +++ b/flight/flight-sql-jdbc-core/src/test/java/org/apache/arrow/driver/jdbc/example/UuidSupportDemo.java @@ -91,7 +91,8 @@ */ public class UuidSupportDemo { - private static final String JDBC_URL = "jdbc:arrow-flight-sql://:"; + private static final String JDBC_URL = + "jdbc:arrow-flight-sql://:"; private static final String USERNAME = ""; private static final String PASSWORD = ""; @@ -310,10 +311,10 @@ private static void demonstrateUpdateWithStatement(Connection connection) throws System.out.println(" Updated ID 1000 to UUID: " + UUID_4); // Update UUID to NULL - String sql2 = String.format("UPDATE %s SET uuid = NULL WHERE id = 1002", TABLE_NAME); - int rows2 = stmt.executeUpdate(sql2); - System.out.println("Update UUID to NULL: " + rows2 + " row(s) affected"); - System.out.println(" Updated ID 1002 to UUID: NULL"); + String sql2 = String.format("UPDATE %s SET uuid = NULL WHERE id = 1002", TABLE_NAME); + int rows2 = stmt.executeUpdate(sql2); + System.out.println("Update UUID to NULL: " + rows2 + " row(s) affected"); + System.out.println(" Updated ID 1002 to UUID: NULL"); // Update based on UUID condition String sql3 = @@ -332,41 +333,41 @@ private static void demonstrateUpdateWithPreparedStatement(Connection connection // Update UUID using setObject() String sql1 = String.format("UPDATE %s SET uuid = ? WHERE id = 2001", TABLE_NAME); - try (PreparedStatement pstmt = connection.prepareStatement(sql1)) { - pstmt.setObject(1, UUID_2); - pstmt.setInt(2, 2000); - int rows1 = pstmt.executeUpdate(); - System.out.println("Update with setObject(UUID): " + rows1 + " row(s) affected"); - System.out.println(" Updated ID 2000 to UUID: " + UUID_2); - } + try (PreparedStatement pstmt = connection.prepareStatement(sql1)) { + pstmt.setObject(1, UUID_2); + pstmt.setInt(2, 2000); + int rows1 = pstmt.executeUpdate(); + System.out.println("Update with setObject(UUID): " + rows1 + " row(s) affected"); + System.out.println(" Updated ID 2000 to UUID: " + UUID_2); + } -// Update UUID using setString() - try (PreparedStatement pstmt = connection.prepareStatement(sql1)) { - pstmt.setString(1, UUID_3.toString()); - pstmt.setInt(2, 2001); - int rows2 = pstmt.executeUpdate(); - System.out.println("Update with setString(UUID.toString()): " + rows2 + " row(s) affected"); - System.out.println(" Updated ID 2001 to UUID: " + UUID_3); - } + // Update UUID using setString() + try (PreparedStatement pstmt = connection.prepareStatement(sql1)) { + pstmt.setString(1, UUID_3.toString()); + pstmt.setInt(2, 2001); + int rows2 = pstmt.executeUpdate(); + System.out.println("Update with setString(UUID.toString()): " + rows2 + " row(s) affected"); + System.out.println(" Updated ID 2001 to UUID: " + UUID_3); + } -// Update UUID to NULL using setNull() - try (PreparedStatement pstmt = connection.prepareStatement(sql1)) { - pstmt.setNull(1, Types.OTHER); - pstmt.setInt(2, 2004); - int rows3 = pstmt.executeUpdate(); - System.out.println("Update with setNull(): " + rows3 + " row(s) affected"); - System.out.println(" Updated ID 2004 to UUID: NULL"); - } + // Update UUID to NULL using setNull() + try (PreparedStatement pstmt = connection.prepareStatement(sql1)) { + pstmt.setNull(1, Types.OTHER); + pstmt.setInt(2, 2004); + int rows3 = pstmt.executeUpdate(); + System.out.println("Update with setNull(): " + rows3 + " row(s) affected"); + System.out.println(" Updated ID 2004 to UUID: NULL"); + } // Update based on UUID parameter condition String sql2 = String.format("UPDATE %s SET uuid = ? WHERE uuid = ?", TABLE_NAME); - try (PreparedStatement pstmt = connection.prepareStatement(sql2)) { - pstmt.setObject(1, UUID_5); - pstmt.setObject(2, UUID_2); - int rows4 = pstmt.executeUpdate(); - System.out.println("Update UUID based on UUID condition: " + rows4 + " row(s) affected"); - System.out.println(" Changed UUID " + UUID_2 + " to " + UUID_5); - } + try (PreparedStatement pstmt = connection.prepareStatement(sql2)) { + pstmt.setObject(1, UUID_5); + pstmt.setObject(2, UUID_2); + int rows4 = pstmt.executeUpdate(); + System.out.println("Update UUID based on UUID condition: " + rows4 + " row(s) affected"); + System.out.println(" Changed UUID " + UUID_2 + " to " + UUID_5); + } System.out.println(); } From b6d0942a909435cf0efbb7493a78f8f8d34141b4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9lder=20Greg=C3=B3rio?= Date: Wed, 10 Dec 2025 10:39:23 +0000 Subject: [PATCH 9/9] delete uuid tester --- .../driver/jdbc/example/UuidSupportDemo.java | 727 ------------------ 1 file changed, 727 deletions(-) delete mode 100644 flight/flight-sql-jdbc-core/src/test/java/org/apache/arrow/driver/jdbc/example/UuidSupportDemo.java diff --git a/flight/flight-sql-jdbc-core/src/test/java/org/apache/arrow/driver/jdbc/example/UuidSupportDemo.java b/flight/flight-sql-jdbc-core/src/test/java/org/apache/arrow/driver/jdbc/example/UuidSupportDemo.java deleted file mode 100644 index b85bb0aac3..0000000000 --- a/flight/flight-sql-jdbc-core/src/test/java/org/apache/arrow/driver/jdbc/example/UuidSupportDemo.java +++ /dev/null @@ -1,727 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You 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 org.apache.arrow.driver.jdbc.example; - -import java.sql.Connection; -import java.sql.DriverManager; -import java.sql.PreparedStatement; -import java.sql.ResultSet; -import java.sql.ResultSetMetaData; -import java.sql.SQLException; -import java.sql.Statement; -import java.sql.Types; -import java.util.ArrayList; -import java.util.List; -import java.util.Properties; -import java.util.UUID; -import org.apache.arrow.driver.jdbc.ArrowFlightJdbcDriver; - -/** - * Demonstrates UUID support in the Arrow Flight JDBC driver. - * - *

This example connects to a Flight SQL server and comprehensively tests UUID support using both - * Statement and PreparedStatement JDBC primitives for all DML operations (INSERT, UPDATE, DELETE, - * SELECT). - * - *

Prerequisites

- * - * - * - *

Building and Running the Demo

- * - *

Important: Arrow's memory management requires access to Java's internal NIO classes. - * The JVM must be started with the following option: - * - *

{@code
- * --add-opens=java.base/java.nio=org.apache.arrow.memory.core,ALL-UNNAMED
- * }
- * - *

From the arrow-java root directory: - * - *

{@code
- * # Step 1: Build the project (includes main and test classes)
- * mvn install -DskipTests -pl flight/flight-sql-jdbc-core -am
- *
- * # Step 2: Compile test classes (required since demo is in src/test/java)
- * mvn test-compile -pl flight/flight-sql-jdbc-core
- *
- * # Step 3: Run the demo using Maven
- * MAVEN_OPTS="--add-opens=java.base/java.nio=ALL-UNNAMED" \
- *   mvn exec:java -pl flight/flight-sql-jdbc-core \
- *   -Dexec.mainClass="org.apache.arrow.driver.jdbc.example.UuidSupportDemo" \
- *   -Dexec.classpathScope=test
- * }
- * - *

Or as a single command: - * - *

{@code
- * mvn install -DskipTests -pl flight/flight-sql-jdbc-core -am && \
- *   mvn test-compile -pl flight/flight-sql-jdbc-core && \
- *   MAVEN_OPTS="--add-opens=java.base/java.nio=ALL-UNNAMED" \
- *   mvn exec:java -pl flight/flight-sql-jdbc-core \
- *   -Dexec.mainClass="org.apache.arrow.driver.jdbc.example.UuidSupportDemo" \
- *   -Dexec.classpathScope=test
- * }
- * - *

Known Limitations

- * - *

UUID Parameter Binding: When using PreparedStatement with UUID parameters, use {@code - * setString(index, uuid.toString())} instead of {@code setObject(index, uuid)}. This is because - * Flight SQL servers typically don't include UUID extension type information in the parameter - * schema, so the driver cannot determine that the parameter should be a UUID. The server (e.g., - * Dremio) will implicitly cast the UUID string to the UUID type. - */ -public class UuidSupportDemo { - - private static final String JDBC_URL = - "jdbc:arrow-flight-sql://:"; - private static final String USERNAME = ""; - private static final String PASSWORD = ""; - - // Table schema: CREATE TABLE nas.uuid_table (id INT, uuid UUID) - private static final String TABLE_NAME = "nas.uuid_table"; - - // Well-known test UUIDs for consistent testing - private static final UUID UUID_1 = UUID.fromString("550e8400-e29b-41d4-a716-446655440000"); - private static final UUID UUID_2 = UUID.fromString("6ba7b810-9dad-11d1-80b4-00c04fd430c8"); - private static final UUID UUID_3 = UUID.fromString("f47ac10b-58cc-4372-a567-0e02b2c3d479"); - private static final UUID UUID_4 = UUID.fromString("a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11"); - private static final UUID UUID_5 = UUID.fromString("00000000-0000-0000-0000-000000000000"); - - public static void main(String[] args) { - System.out.println("=== Arrow Flight JDBC UUID Support Demo ===\n"); - - try { - Class.forName(ArrowFlightJdbcDriver.class.getName()); - } catch (ClassNotFoundException e) { - System.err.println("Arrow Flight JDBC driver not found on classpath"); - e.printStackTrace(); - return; - } - - Properties properties = new Properties(); - properties.put("user", USERNAME); - properties.put("password", PASSWORD); - properties.put("useEncryption", "false"); - - try (Connection connection = DriverManager.getConnection(JDBC_URL, properties)) { - // Run all demo sections - demonstrateInsertWithStatement(connection); - demonstrateInsertWithPreparedStatement(connection); - demonstrateSelectWithStatement(connection); - demonstrateSelectWithPreparedStatement(connection); - demonstrateUpdateWithStatement(connection); - demonstrateUpdateWithPreparedStatement(connection); - demonstrateDeleteWithStatement(connection); - demonstrateDeleteWithPreparedStatement(connection); - demonstrateNullUuidHandling(connection); - demonstrateBatchOperations(connection); - demonstrateUuidComparisons(connection); - demonstrateUuidRetrieval(connection); - - } catch (SQLException e) { - System.err.println("Error: " + e.getMessage()); - e.printStackTrace(); - } - } - - // ==================== INSERT Operations ==================== - - private static void demonstrateInsertWithStatement(Connection connection) throws SQLException { - System.out.println("=== INSERT with Statement ===\n"); - - try (Statement stmt = connection.createStatement()) { - // Insert single row with UUID literal - String sql1 = - String.format( - "INSERT INTO %s (id, uuid) VALUES (1000, UUID '%s')", TABLE_NAME, UUID_1.toString()); - int rows1 = stmt.executeUpdate(sql1); - System.out.println("Inserted row with UUID literal: " + rows1 + " row(s) affected"); - System.out.println(" ID: 1000, UUID: " + UUID_1); - - // Insert row with NULL UUID - String sql2 = String.format("INSERT INTO %s (id, uuid) VALUES (1001, NULL)", TABLE_NAME); - int rows2 = stmt.executeUpdate(sql2); - System.out.println("Inserted row with NULL UUID: " + rows2 + " row(s) affected"); - System.out.println(" ID: 1001, UUID: NULL"); - - // Insert multiple rows in single statement (if supported) - String sql3 = - String.format( - "INSERT INTO %s (id, uuid) VALUES (1002, UUID '%s')", TABLE_NAME, UUID_2.toString()); - int rows3 = stmt.executeUpdate(sql3); - System.out.println("Inserted additional row: " + rows3 + " row(s) affected"); - System.out.println(" ID: 1002, UUID: " + UUID_2); - } - System.out.println(); - } - - private static void demonstrateInsertWithPreparedStatement(Connection connection) - throws SQLException { - System.out.println("=== INSERT with PreparedStatement ===\n"); - - String insertSql = String.format("INSERT INTO %s (id, uuid) VALUES (?, ?)", TABLE_NAME); - - try (PreparedStatement pstmt = connection.prepareStatement(insertSql)) { - // Insert using setObject() with UUID - pstmt.setInt(1, 2000); - pstmt.setObject(2, UUID_3); - int rows1 = pstmt.executeUpdate(); - System.out.println("Insert with setObject(UUID): " + rows1 + " row(s) affected"); - System.out.println(" ID: 2000, UUID: " + UUID_3); - - // Insert using setString() with UUID string representation - pstmt.setInt(1, 2001); - pstmt.setString(2, UUID_4.toString()); - int rows2 = pstmt.executeUpdate(); - System.out.println("Insert with setString(UUID.toString()): " + rows2 + " row(s) affected"); - System.out.println(" ID: 2001, UUID: " + UUID_4); - - // Insert NULL UUID using setNull() - pstmt.setInt(1, 2002); - pstmt.setNull(2, Types.OTHER); - int rows3 = pstmt.executeUpdate(); - System.out.println("Insert with setNull(): " + rows3 + " row(s) affected"); - System.out.println(" ID: 2002, UUID: NULL"); - - // Insert NULL UUID using setObject(null) - pstmt.setInt(1, 2003); - pstmt.setObject(2, null); - int rows4 = pstmt.executeUpdate(); - System.out.println("Insert with setObject(null): " + rows4 + " row(s) affected"); - System.out.println(" ID: 2003, UUID: NULL"); - - // Insert zero UUID (all zeros) - pstmt.setInt(1, 2004); - pstmt.setObject(2, UUID_5); - int rows5 = pstmt.executeUpdate(); - System.out.println("Insert with zero UUID: " + rows5 + " row(s) affected"); - System.out.println(" ID: 2004, UUID: " + UUID_5); - } - System.out.println(); - } - - // ==================== SELECT Operations ==================== - - private static void demonstrateSelectWithStatement(Connection connection) throws SQLException { - System.out.println("=== SELECT with Statement ===\n"); - - try (Statement stmt = connection.createStatement()) { - // Select all rows - String sql1 = String.format("SELECT id, uuid FROM %s ORDER BY id LIMIT 10", TABLE_NAME); - try (ResultSet rs = stmt.executeQuery(sql1)) { - System.out.println("SELECT all rows (limited to 10):"); - printResultSet(rs); - } - - // Select with UUID equality condition - String sql2 = - String.format( - "SELECT id, uuid FROM %s WHERE uuid = UUID '%s'", TABLE_NAME, UUID_1.toString()); - try (ResultSet rs = stmt.executeQuery(sql2)) { - System.out.println("SELECT with UUID equality (uuid = " + UUID_1 + "):"); - printResultSet(rs); - } - - // Select NULL UUIDs - String sql3 = String.format("SELECT id, uuid FROM %s WHERE uuid IS NULL", TABLE_NAME); - try (ResultSet rs = stmt.executeQuery(sql3)) { - System.out.println("SELECT rows with NULL UUID:"); - printResultSet(rs); - } - - // Select non-NULL UUIDs - String sql4 = - String.format("SELECT id, uuid FROM %s WHERE uuid IS NOT NULL LIMIT 5", TABLE_NAME); - try (ResultSet rs = stmt.executeQuery(sql4)) { - System.out.println("SELECT rows with non-NULL UUID (limited to 5):"); - printResultSet(rs); - } - } - System.out.println(); - } - - private static void demonstrateSelectWithPreparedStatement(Connection connection) - throws SQLException { - System.out.println("=== SELECT with PreparedStatement ===\n"); - - // Select by UUID using setObject() - String sql1 = String.format("SELECT id, uuid FROM %s WHERE uuid = ?", TABLE_NAME); - try (PreparedStatement pstmt = connection.prepareStatement(sql1)) { - pstmt.setObject(1, UUID_1); - try (ResultSet rs = pstmt.executeQuery()) { - System.out.println("SELECT with setObject(UUID) parameter:"); - printResultSet(rs); - } - } - - // Select by UUID using setString() - try (PreparedStatement pstmt = connection.prepareStatement(sql1)) { - pstmt.setString(1, UUID_3.toString()); - try (ResultSet rs = pstmt.executeQuery()) { - System.out.println("SELECT with setString(UUID.toString()) parameter:"); - printResultSet(rs); - } - } - - // Select by ID range and retrieve UUID - String sql2 = - String.format("SELECT id, uuid FROM %s WHERE id >= ? AND id <= ? ORDER BY id", TABLE_NAME); - try (PreparedStatement pstmt = connection.prepareStatement(sql2)) { - pstmt.setInt(1, 2000); - pstmt.setInt(2, 2004); - try (ResultSet rs = pstmt.executeQuery()) { - System.out.println("SELECT by ID range (2000-2004):"); - printResultSet(rs); - } - } - System.out.println(); - } - - // ==================== UPDATE Operations ==================== - - private static void demonstrateUpdateWithStatement(Connection connection) throws SQLException { - System.out.println("=== UPDATE with Statement ===\n"); - - try (Statement stmt = connection.createStatement()) { - // Update UUID value by ID - String sql1 = - String.format( - "UPDATE %s SET uuid = UUID '%s' WHERE id = 1000", TABLE_NAME, UUID_4.toString()); - int rows1 = stmt.executeUpdate(sql1); - System.out.println("Update UUID by ID: " + rows1 + " row(s) affected"); - System.out.println(" Updated ID 1000 to UUID: " + UUID_4); - - // Update UUID to NULL - String sql2 = String.format("UPDATE %s SET uuid = NULL WHERE id = 1002", TABLE_NAME); - int rows2 = stmt.executeUpdate(sql2); - System.out.println("Update UUID to NULL: " + rows2 + " row(s) affected"); - System.out.println(" Updated ID 1002 to UUID: NULL"); - - // Update based on UUID condition - String sql3 = - String.format( - "UPDATE %s SET uuid = UUID '%s' WHERE uuid = UUID '%s'", - TABLE_NAME, UUID_1.toString(), UUID_4.toString()); - int rows3 = stmt.executeUpdate(sql3); - System.out.println("Update UUID based on UUID condition: " + rows3 + " row(s) affected"); - } - System.out.println(); - } - - private static void demonstrateUpdateWithPreparedStatement(Connection connection) - throws SQLException { - System.out.println("=== UPDATE with PreparedStatement ===\n"); - - // Update UUID using setObject() - String sql1 = String.format("UPDATE %s SET uuid = ? WHERE id = 2001", TABLE_NAME); - try (PreparedStatement pstmt = connection.prepareStatement(sql1)) { - pstmt.setObject(1, UUID_2); - pstmt.setInt(2, 2000); - int rows1 = pstmt.executeUpdate(); - System.out.println("Update with setObject(UUID): " + rows1 + " row(s) affected"); - System.out.println(" Updated ID 2000 to UUID: " + UUID_2); - } - - // Update UUID using setString() - try (PreparedStatement pstmt = connection.prepareStatement(sql1)) { - pstmt.setString(1, UUID_3.toString()); - pstmt.setInt(2, 2001); - int rows2 = pstmt.executeUpdate(); - System.out.println("Update with setString(UUID.toString()): " + rows2 + " row(s) affected"); - System.out.println(" Updated ID 2001 to UUID: " + UUID_3); - } - - // Update UUID to NULL using setNull() - try (PreparedStatement pstmt = connection.prepareStatement(sql1)) { - pstmt.setNull(1, Types.OTHER); - pstmt.setInt(2, 2004); - int rows3 = pstmt.executeUpdate(); - System.out.println("Update with setNull(): " + rows3 + " row(s) affected"); - System.out.println(" Updated ID 2004 to UUID: NULL"); - } - - // Update based on UUID parameter condition - String sql2 = String.format("UPDATE %s SET uuid = ? WHERE uuid = ?", TABLE_NAME); - try (PreparedStatement pstmt = connection.prepareStatement(sql2)) { - pstmt.setObject(1, UUID_5); - pstmt.setObject(2, UUID_2); - int rows4 = pstmt.executeUpdate(); - System.out.println("Update UUID based on UUID condition: " + rows4 + " row(s) affected"); - System.out.println(" Changed UUID " + UUID_2 + " to " + UUID_5); - } - System.out.println(); - } - - // ==================== DELETE Operations ==================== - - private static void demonstrateDeleteWithStatement(Connection connection) throws SQLException { - System.out.println("=== DELETE with Statement ===\n"); - - try (Statement stmt = connection.createStatement()) { - // Delete by specific UUID - String sql1 = - String.format("DELETE FROM %s WHERE uuid = UUID '%s'", TABLE_NAME, UUID_5.toString()); - int rows1 = stmt.executeUpdate(sql1); - System.out.println("Delete by UUID: " + rows1 + " row(s) affected"); - System.out.println(" Deleted rows with UUID: " + UUID_5); - - // Delete rows with NULL UUID - String sql2 = String.format("DELETE FROM %s WHERE uuid IS NULL AND id >= 1000", TABLE_NAME); - int rows2 = stmt.executeUpdate(sql2); - System.out.println("Delete rows with NULL UUID (id >= 1000): " + rows2 + " row(s) affected"); - - // Delete by ID (cleanup) - String sql3 = String.format("DELETE FROM %s WHERE id = 1001", TABLE_NAME); - int rows3 = stmt.executeUpdate(sql3); - System.out.println("Delete by ID: " + rows3 + " row(s) affected"); - } - System.out.println(); - } - - private static void demonstrateDeleteWithPreparedStatement(Connection connection) - throws SQLException { - System.out.println("=== DELETE with PreparedStatement ===\n"); - - // Delete by UUID using setObject() - String sql1 = String.format("DELETE FROM %s WHERE uuid = ?", TABLE_NAME); - try (PreparedStatement pstmt = connection.prepareStatement(sql1)) { - pstmt.setObject(1, UUID_3); - int rows1 = pstmt.executeUpdate(); - System.out.println("Delete with setObject(UUID): " + rows1 + " row(s) affected"); - System.out.println(" Deleted rows with UUID: " + UUID_3); - } - - // Delete by UUID using setString() - try (PreparedStatement pstmt = connection.prepareStatement(sql1)) { - pstmt.setString(1, UUID_1.toString()); - int rows2 = pstmt.executeUpdate(); - System.out.println("Delete with setString(UUID.toString()): " + rows2 + " row(s) affected"); - System.out.println(" Deleted rows with UUID: " + UUID_1); - } - - // Delete by ID range (cleanup) - String sql2 = String.format("DELETE FROM %s WHERE id >= ? AND id <= ?", TABLE_NAME); - try (PreparedStatement pstmt = connection.prepareStatement(sql2)) { - pstmt.setInt(1, 1000); - pstmt.setInt(2, 2999); - int rows3 = pstmt.executeUpdate(); - System.out.println("Delete by ID range (cleanup): " + rows3 + " row(s) affected"); - } - System.out.println(); - } - - // ==================== NULL UUID Handling ==================== - - private static void demonstrateNullUuidHandling(Connection connection) throws SQLException { - System.out.println("=== NULL UUID Handling ===\n"); - - // Insert rows with NULL UUIDs for testing - String insertSql = String.format("INSERT INTO %s (id, uuid) VALUES (?, ?)", TABLE_NAME); - try (PreparedStatement pstmt = connection.prepareStatement(insertSql)) { - pstmt.setInt(1, 3000); - pstmt.setNull(2, Types.OTHER); - pstmt.executeUpdate(); - - pstmt.setInt(1, 3001); - pstmt.setObject(2, null); - pstmt.executeUpdate(); - System.out.println("Inserted 2 rows with NULL UUIDs (IDs: 3000, 3001)"); - } - - // Query and verify NULL handling - String selectSql = - String.format("SELECT id, uuid FROM %s WHERE id >= 3000 AND id <= 3001", TABLE_NAME); - try (Statement stmt = connection.createStatement(); - ResultSet rs = stmt.executeQuery(selectSql)) { - System.out.println("\nRetrieving NULL UUID values:"); - while (rs.next()) { - int id = rs.getInt("id"); - - // Test getObject() - Object uuidObj = rs.getObject("uuid"); - boolean wasNull1 = rs.wasNull(); - - // Test getString() - String uuidStr = rs.getString("uuid"); - boolean wasNull2 = rs.wasNull(); - - // Test getObject(UUID.class) - UUID uuid = rs.getObject("uuid", UUID.class); - boolean wasNull3 = rs.wasNull(); - - System.out.println(" ID: " + id); - System.out.println(" getObject(): " + uuidObj + " (wasNull: " + wasNull1 + ")"); - System.out.println(" getString(): " + uuidStr + " (wasNull: " + wasNull2 + ")"); - System.out.println(" getObject(UUID.class): " + uuid + " (wasNull: " + wasNull3 + ")"); - } - } - - // Cleanup - String deleteSql = String.format("DELETE FROM %s WHERE id >= 3000 AND id <= 3001", TABLE_NAME); - try (Statement stmt = connection.createStatement()) { - stmt.executeUpdate(deleteSql); - System.out.println("\nCleaned up NULL UUID test rows"); - } - System.out.println(); - } - - // ==================== Batch Operations ==================== - - private static void demonstrateBatchOperations(Connection connection) throws SQLException { - System.out.println("=== Batch Operations with PreparedStatement ===\n"); - - String insertSql = String.format("INSERT INTO %s (id, uuid) VALUES (?, ?)", TABLE_NAME); - - try (PreparedStatement pstmt = connection.prepareStatement(insertSql)) { - // Add multiple rows to batch - List batchUuids = new ArrayList<>(); - for (int i = 0; i < 5; i++) { - UUID uuid = UUID.randomUUID(); - batchUuids.add(uuid); - pstmt.setInt(1, 4000 + i); - pstmt.setObject(2, uuid); - pstmt.addBatch(); - System.out.println("Added to batch: ID=" + (4000 + i) + ", UUID=" + uuid); - } - - // Execute batch - int[] results = pstmt.executeBatch(); - System.out.println("\nBatch execution results:"); - int totalAffected = 0; - for (int i = 0; i < results.length; i++) { - System.out.println(" Statement " + (i + 1) + ": " + results[i] + " row(s)"); - totalAffected += results[i]; - } - System.out.println("Total rows affected: " + totalAffected); - - // Verify batch insert - String selectSql = - String.format( - "SELECT id, uuid FROM %s WHERE id >= 4000 AND id <= 4004 ORDER BY id", TABLE_NAME); - try (Statement stmt = connection.createStatement(); - ResultSet rs = stmt.executeQuery(selectSql)) { - System.out.println("\nVerifying batch insert:"); - int idx = 0; - while (rs.next()) { - int id = rs.getInt("id"); - UUID retrievedUuid = rs.getObject("uuid", UUID.class); - UUID expectedUuid = batchUuids.get(idx); - boolean match = expectedUuid.equals(retrievedUuid); - System.out.println( - " ID: " + id + ", UUID: " + retrievedUuid + " (match: " + match + ")"); - idx++; - } - } - } - - // Cleanup batch test data - String deleteSql = String.format("DELETE FROM %s WHERE id >= 4000 AND id <= 4004", TABLE_NAME); - try (Statement stmt = connection.createStatement()) { - int deleted = stmt.executeUpdate(deleteSql); - System.out.println("\nCleaned up batch test rows: " + deleted + " row(s)"); - } - System.out.println(); - } - - // ==================== UUID Comparisons ==================== - - private static void demonstrateUuidComparisons(Connection connection) throws SQLException { - System.out.println("=== UUID Comparisons in WHERE Clauses ===\n"); - - // Insert test data - String insertSql = String.format("INSERT INTO %s (id, uuid) VALUES (?, ?)", TABLE_NAME); - try (PreparedStatement pstmt = connection.prepareStatement(insertSql)) { - pstmt.setInt(1, 5000); - pstmt.setObject(2, UUID_1); - pstmt.executeUpdate(); - - pstmt.setInt(1, 5001); - pstmt.setObject(2, UUID_2); - pstmt.executeUpdate(); - - pstmt.setInt(1, 5002); - pstmt.setObject(2, UUID_1); // Duplicate UUID - pstmt.executeUpdate(); - - pstmt.setInt(1, 5003); - pstmt.setNull(2, Types.OTHER); - pstmt.executeUpdate(); - System.out.println("Inserted test data for comparison tests"); - } - - // Equality comparison - String sql1 = String.format("SELECT id, uuid FROM %s WHERE uuid = ?", TABLE_NAME); - try (PreparedStatement pstmt = connection.prepareStatement(sql1)) { - pstmt.setObject(1, UUID_1); - try (ResultSet rs = pstmt.executeQuery()) { - System.out.println("\nEquality (uuid = " + UUID_1 + "):"); - printResultSet(rs); - } - } - - // Inequality comparison - String sql2 = - String.format( - "SELECT id, uuid FROM %s WHERE uuid <> ? AND id >= 5000 AND id <= 5003", TABLE_NAME); - try (PreparedStatement pstmt = connection.prepareStatement(sql2)) { - pstmt.setObject(1, UUID_1); - try (ResultSet rs = pstmt.executeQuery()) { - System.out.println("Inequality (uuid <> " + UUID_1 + "):"); - printResultSet(rs); - } - } - - // IS NULL comparison - String sql3 = - String.format( - "SELECT id, uuid FROM %s WHERE uuid IS NULL AND id >= 5000 AND id <= 5003", TABLE_NAME); - try (Statement stmt = connection.createStatement(); - ResultSet rs = stmt.executeQuery(sql3)) { - System.out.println("IS NULL comparison:"); - printResultSet(rs); - } - - // IS NOT NULL comparison - String sql4 = - String.format( - "SELECT id, uuid FROM %s WHERE uuid IS NOT NULL AND id >= 5000 AND id <= 5003", - TABLE_NAME); - try (Statement stmt = connection.createStatement(); - ResultSet rs = stmt.executeQuery(sql4)) { - System.out.println("IS NOT NULL comparison:"); - printResultSet(rs); - } - - // IN clause with UUIDs - String sql5 = - String.format( - "SELECT id, uuid FROM %s WHERE uuid IN (UUID '%s', UUID '%s') AND id >= 5000", - TABLE_NAME, UUID_1.toString(), UUID_2.toString()); - try (Statement stmt = connection.createStatement(); - ResultSet rs = stmt.executeQuery(sql5)) { - System.out.println("IN clause (uuid IN (" + UUID_1 + ", " + UUID_2 + ")):"); - printResultSet(rs); - } - - // Cleanup - String deleteSql = String.format("DELETE FROM %s WHERE id >= 5000 AND id <= 5003", TABLE_NAME); - try (Statement stmt = connection.createStatement()) { - stmt.executeUpdate(deleteSql); - System.out.println("Cleaned up comparison test rows"); - } - System.out.println(); - } - - // ==================== UUID Retrieval Methods ==================== - - private static void demonstrateUuidRetrieval(Connection connection) throws SQLException { - System.out.println("=== UUID Retrieval Methods ===\n"); - - // Insert test row - String insertSql = String.format("INSERT INTO %s (id, uuid) VALUES (?, ?)", TABLE_NAME); - try (PreparedStatement pstmt = connection.prepareStatement(insertSql)) { - pstmt.setInt(1, 6000); - pstmt.setObject(2, UUID_1); - pstmt.executeUpdate(); - System.out.println("Inserted test row: ID=6000, UUID=" + UUID_1); - } - - // Demonstrate all retrieval methods - String selectSql = String.format("SELECT id, uuid FROM %s WHERE id = 6000", TABLE_NAME); - try (Statement stmt = connection.createStatement(); - ResultSet rs = stmt.executeQuery(selectSql)) { - if (rs.next()) { - System.out.println("\nRetrieval methods for UUID column:"); - - // Method 1: getObject() - returns java.util.UUID - Object obj = rs.getObject("uuid"); - System.out.println(" getObject(): " + obj); - System.out.println(" Type: " + (obj != null ? obj.getClass().getName() : "null")); - - // Method 2: getObject(UUID.class) - returns java.util.UUID - UUID uuid = rs.getObject("uuid", UUID.class); - System.out.println(" getObject(UUID.class): " + uuid); - - // Method 3: getString() - returns hyphenated string format - String str = rs.getString("uuid"); - System.out.println(" getString(): " + str); - - // Method 4: getBytes() - returns 16-byte array - byte[] bytes = rs.getBytes("uuid"); - System.out.println(" getBytes(): " + bytesToHex(bytes) + " (" + bytes.length + " bytes)"); - - // Verify consistency - System.out.println("\nConsistency checks:"); - System.out.println(" getObject() instanceof UUID: " + (obj instanceof UUID)); - System.out.println(" getObject().equals(UUID_1): " + UUID_1.equals(obj)); - System.out.println( - " UUID.fromString(getString()).equals(UUID_1): " - + UUID_1.equals(UUID.fromString(str))); - - // Verify metadata - ResultSetMetaData meta = rs.getMetaData(); - System.out.println("\nColumn metadata:"); - System.out.println(" Column name: " + meta.getColumnName(2)); - System.out.println( - " Column type: " + meta.getColumnType(2) + " (Types.OTHER=" + Types.OTHER + ")"); - System.out.println(" Column type name: " + meta.getColumnTypeName(2)); - System.out.println(" Column class name: " + meta.getColumnClassName(2)); - } - } - - // Cleanup - String deleteSql = String.format("DELETE FROM %s WHERE id = 6000", TABLE_NAME); - try (Statement stmt = connection.createStatement()) { - stmt.executeUpdate(deleteSql); - System.out.println("\nCleaned up retrieval test row"); - } - System.out.println(); - } - - // ==================== Helper Methods ==================== - - private static void printResultSet(ResultSet rs) throws SQLException { - int count = 0; - while (rs.next()) { - count++; - int id = rs.getInt("id"); - UUID uuid = rs.getObject("uuid", UUID.class); - boolean wasNull = rs.wasNull(); - System.out.println(" Row " + count + ": ID=" + id + ", UUID=" + (wasNull ? "NULL" : uuid)); - } - if (count == 0) { - System.out.println(" (no rows)"); - } - System.out.println(); - } - - private static String bytesToHex(byte[] bytes) { - if (bytes == null) { - return "null"; - } - StringBuilder sb = new StringBuilder(); - for (byte b : bytes) { - sb.append(String.format("%02x", b)); - } - return sb.toString(); - } -}