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) { 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 bbfe88a78a..8362eb7627 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; @@ -138,6 +140,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: + * + *
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 = 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) {
+ 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/AvaticaParameterBinder.java b/flight/flight-sql-jdbc-core/src/main/java/org/apache/arrow/driver/jdbc/utils/AvaticaParameterBinder.java
index 8c98ee4077..3c282a096f 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
@@ -40,11 +40,15 @@
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.driver.jdbc.converter.impl.Utf8ViewAvaticaParameterConverter;
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;
@@ -290,5 +294,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..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
@@ -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;
@@ -294,5 +298,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 5ba3957f8b..7982d5bc73 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,13 @@
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;
/** SQL Types utility functions. */
public class SqlTypes {
+
private static final Map 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..07751f0abc
--- /dev/null
+++ b/flight/flight-sql-jdbc-core/src/test/java/org/apache/arrow/driver/jdbc/converter/impl/UuidAvaticaParameterConverterTest.java
@@ -0,0 +1,160 @@
+/*
+ * 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.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("OTHER"));
+ assertThat(parameter.className, equalTo(UUID.class.getCanonicalName()));
+ }
+}
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