diff --git a/flight/flight-sql-jdbc-core/src/main/java/org/apache/arrow/driver/jdbc/converter/impl/DateAvaticaParameterConverter.java b/flight/flight-sql-jdbc-core/src/main/java/org/apache/arrow/driver/jdbc/converter/impl/DateAvaticaParameterConverter.java index 8795213530..b31641e58f 100644 --- a/flight/flight-sql-jdbc-core/src/main/java/org/apache/arrow/driver/jdbc/converter/impl/DateAvaticaParameterConverter.java +++ b/flight/flight-sql-jdbc-core/src/main/java/org/apache/arrow/driver/jdbc/converter/impl/DateAvaticaParameterConverter.java @@ -16,6 +16,7 @@ */ package org.apache.arrow.driver.jdbc.converter.impl; +import java.time.LocalDate; import org.apache.arrow.vector.DateDayVector; import org.apache.arrow.vector.DateMilliVector; import org.apache.arrow.vector.FieldVector; @@ -31,12 +32,22 @@ public DateAvaticaParameterConverter(ArrowType.Date type) {} @Override public boolean bindParameter(FieldVector vector, TypedValue typedValue, int index) { - int value = (int) typedValue.toLocal(); + Object value = typedValue.value; + int days; + if (value instanceof String) { + LocalDate localDate = LocalDate.parse((String) value); + days = (int) localDate.toEpochDay(); + } else if (value instanceof Integer) { + days = (Integer) value; + } else { + return false; + } + if (vector instanceof DateMilliVector) { - ((DateMilliVector) vector).setSafe(index, value); + ((DateMilliVector) vector).setSafe(index, days * 24 * 60 * 60 * 1000); return true; } else if (vector instanceof DateDayVector) { - ((DateDayVector) vector).setSafe(index, value); + ((DateDayVector) vector).setSafe(index, days); return true; } return false; diff --git a/flight/flight-sql-jdbc-core/src/main/java/org/apache/arrow/driver/jdbc/converter/impl/TimeAvaticaParameterConverter.java b/flight/flight-sql-jdbc-core/src/main/java/org/apache/arrow/driver/jdbc/converter/impl/TimeAvaticaParameterConverter.java index 0340eb6099..33add1c461 100644 --- a/flight/flight-sql-jdbc-core/src/main/java/org/apache/arrow/driver/jdbc/converter/impl/TimeAvaticaParameterConverter.java +++ b/flight/flight-sql-jdbc-core/src/main/java/org/apache/arrow/driver/jdbc/converter/impl/TimeAvaticaParameterConverter.java @@ -33,15 +33,48 @@ public TimeAvaticaParameterConverter(ArrowType.Time type) {} @Override public boolean bindParameter(FieldVector vector, TypedValue typedValue, int index) { - int value = (int) typedValue.toLocal(); - if (vector instanceof TimeMicroVector) { - ((TimeMicroVector) vector).setSafe(index, value); + Object value = typedValue.value; + if (value instanceof String) { + return bindTimeAsString(vector, (String) value, index); + } else if (value instanceof Integer) { + return bindTimeAsInt(vector, (int) value, index); + } else { + return false; + } + } + + private boolean bindTimeAsString(FieldVector vector, String value, int index) { + java.time.LocalTime localTime = java.time.LocalTime.parse(value); + long nanos = localTime.toNanoOfDay(); + if (vector instanceof TimeMilliVector) { + int v = (int) (nanos / 1_000_000); + ((TimeMilliVector) vector).setSafe(index, v); + return true; + } else if (vector instanceof TimeMicroVector) { + long v = nanos / 1_000; + ((TimeMicroVector) vector).setSafe(index, v); return true; - } else if (vector instanceof TimeMilliVector) { + } else if (vector instanceof TimeNanoVector) { + long v = nanos; + ((TimeNanoVector) vector).setSafe(index, v); + return true; + } else if (vector instanceof TimeSecVector) { + int v = (int) (nanos / 1_000_000_000); + ((TimeSecVector) vector).setSafe(index, v); + return true; + } + return false; + } + + private boolean bindTimeAsInt(FieldVector vector, int value, int index) { + if (vector instanceof TimeMilliVector) { ((TimeMilliVector) vector).setSafe(index, value); return true; + } else if (vector instanceof TimeMicroVector) { + ((TimeMicroVector) vector).setSafe(index, (long) value); + return true; } else if (vector instanceof TimeNanoVector) { - ((TimeNanoVector) vector).setSafe(index, value); + ((TimeNanoVector) vector).setSafe(index, (long) value); return true; } else if (vector instanceof TimeSecVector) { ((TimeSecVector) vector).setSafe(index, value); diff --git a/flight/flight-sql-jdbc-core/src/main/java/org/apache/arrow/driver/jdbc/converter/impl/TimestampAvaticaParameterConverter.java b/flight/flight-sql-jdbc-core/src/main/java/org/apache/arrow/driver/jdbc/converter/impl/TimestampAvaticaParameterConverter.java index add3e30598..02e0b5f1b6 100644 --- a/flight/flight-sql-jdbc-core/src/main/java/org/apache/arrow/driver/jdbc/converter/impl/TimestampAvaticaParameterConverter.java +++ b/flight/flight-sql-jdbc-core/src/main/java/org/apache/arrow/driver/jdbc/converter/impl/TimestampAvaticaParameterConverter.java @@ -16,6 +16,7 @@ */ package org.apache.arrow.driver.jdbc.converter.impl; +import java.time.Instant; import org.apache.arrow.vector.FieldVector; import org.apache.arrow.vector.TimeStampMicroTZVector; import org.apache.arrow.vector.TimeStampMicroVector; @@ -37,7 +38,34 @@ public TimestampAvaticaParameterConverter(ArrowType.Timestamp type) {} @Override public boolean bindParameter(FieldVector vector, TypedValue typedValue, int index) { - long value = (long) typedValue.toLocal(); + Object valueObj = typedValue.toLocal(); + if (valueObj instanceof String) { + return bindTimestampAsString(vector, (String) valueObj, index); + } else if (valueObj instanceof Long) { + return bindTimestampAsLong(vector, (Long) valueObj, index); + } else { + return false; + } + } + + private boolean bindTimestampAsString(FieldVector vector, String value, int index) { + Instant instant = Instant.parse(value); + long result; + if (vector instanceof TimeStampSecVector || vector instanceof TimeStampSecTZVector) { + result = instant.getEpochSecond(); + } else if (vector instanceof TimeStampMilliVector || vector instanceof TimeStampMilliTZVector) { + result = instant.toEpochMilli(); + } else if (vector instanceof TimeStampMicroVector || vector instanceof TimeStampMicroTZVector) { + result = instant.toEpochMilli() * 1000; + } else if (vector instanceof TimeStampNanoVector || vector instanceof TimeStampNanoTZVector) { + result = instant.toEpochMilli() * 1000000; + } else { + return false; + } + return bindTimestampAsLong(vector, result, index); + } + + private boolean bindTimestampAsLong(FieldVector vector, long value, int index) { if (vector instanceof TimeStampSecVector) { ((TimeStampSecVector) vector).setSafe(index, value); return true; diff --git a/flight/flight-sql-jdbc-core/src/test/java/org/apache/arrow/driver/jdbc/converter/impl/DateAvaticaParameterConverterTest.java b/flight/flight-sql-jdbc-core/src/test/java/org/apache/arrow/driver/jdbc/converter/impl/DateAvaticaParameterConverterTest.java new file mode 100644 index 0000000000..e2b9c100b0 --- /dev/null +++ b/flight/flight-sql-jdbc-core/src/test/java/org/apache/arrow/driver/jdbc/converter/impl/DateAvaticaParameterConverterTest.java @@ -0,0 +1,97 @@ +/* + * 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.junit.jupiter.api.Assertions.*; + +import java.time.LocalDate; +import org.apache.arrow.memory.RootAllocator; +import org.apache.arrow.vector.DateDayVector; +import org.apache.arrow.vector.DateMilliVector; +import org.apache.arrow.vector.IntVector; +import org.apache.arrow.vector.types.DateUnit; +import org.apache.arrow.vector.types.pojo.ArrowType; +import org.apache.calcite.avatica.ColumnMetaData; +import org.apache.calcite.avatica.remote.TypedValue; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +public class DateAvaticaParameterConverterTest { + private RootAllocator allocator; + + @BeforeEach + void setUp() { + allocator = new RootAllocator(Long.MAX_VALUE); + } + + @AfterEach + void tearDown() { + allocator.close(); + } + + @Test + void testBindParameterWithStringDateMilliVector() { + try (DateMilliVector vector = new DateMilliVector("date", allocator)) { + DateAvaticaParameterConverter converter = + new DateAvaticaParameterConverter(new ArrowType.Date(DateUnit.DAY)); + String dateStr = "2023-09-15"; + TypedValue typedValue = TypedValue.create(ColumnMetaData.Rep.STRING.toString(), dateStr); + boolean result = converter.bindParameter(vector, typedValue, 0); + assertTrue(result); + assertEquals( + (int) (LocalDate.parse(dateStr).toEpochDay() * 24 * 60 * 60 * 1000), vector.get(0)); + } + } + + @Test + void testBindParameterWithStringDateDayVector() { + try (DateDayVector vector = new DateDayVector("date", allocator)) { + DateAvaticaParameterConverter converter = + new DateAvaticaParameterConverter(new ArrowType.Date(DateUnit.DAY)); + String dateStr = "2023-09-15"; + TypedValue typedValue = TypedValue.create(ColumnMetaData.Rep.STRING.toString(), dateStr); + boolean result = converter.bindParameter(vector, typedValue, 0); + assertTrue(result); + assertEquals((int) LocalDate.parse(dateStr).toEpochDay(), vector.get(0)); + } + } + + @Test + void testBindParameterWithEpochDays() { + try (DateDayVector vector = new DateDayVector("date", allocator)) { + DateAvaticaParameterConverter converter = + new DateAvaticaParameterConverter(new ArrowType.Date(DateUnit.DAY)); + int days = 19500; + TypedValue typedValue = TypedValue.create(ColumnMetaData.Rep.INTEGER.toString(), days); + boolean result = converter.bindParameter(vector, typedValue, 0); + assertTrue(result); + assertEquals(days, vector.get(0)); + } + } + + @Test + void testBindParameterWithUnsupportedVector() { + try (IntVector vector = new IntVector("int", allocator)) { + DateAvaticaParameterConverter converter = + new DateAvaticaParameterConverter(new ArrowType.Date(DateUnit.DAY)); + TypedValue typedValue = TypedValue.create(ColumnMetaData.Rep.STRING.toString(), "2023-09-15"); + boolean result = converter.bindParameter(vector, typedValue, 0); + assertFalse(result); + } + } +} diff --git a/flight/flight-sql-jdbc-core/src/test/java/org/apache/arrow/driver/jdbc/converter/impl/TimeAvaticaParameterConverterTest.java b/flight/flight-sql-jdbc-core/src/test/java/org/apache/arrow/driver/jdbc/converter/impl/TimeAvaticaParameterConverterTest.java new file mode 100644 index 0000000000..9c7516a71f --- /dev/null +++ b/flight/flight-sql-jdbc-core/src/test/java/org/apache/arrow/driver/jdbc/converter/impl/TimeAvaticaParameterConverterTest.java @@ -0,0 +1,133 @@ +/* + * 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.junit.jupiter.api.Assertions.*; + +import org.apache.arrow.memory.BufferAllocator; +import org.apache.arrow.memory.RootAllocator; +import org.apache.arrow.vector.TimeMicroVector; +import org.apache.arrow.vector.TimeMilliVector; +import org.apache.arrow.vector.TimeNanoVector; +import org.apache.arrow.vector.TimeSecVector; +import org.apache.arrow.vector.types.TimeUnit; +import org.apache.arrow.vector.types.pojo.ArrowType; +import org.apache.calcite.avatica.ColumnMetaData; +import org.apache.calcite.avatica.remote.TypedValue; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +public class TimeAvaticaParameterConverterTest { + private BufferAllocator allocator; + + @BeforeEach + void setUp() { + allocator = new RootAllocator(Long.MAX_VALUE); + } + + @AfterEach + void tearDown() { + allocator.close(); + } + + @Test + void testBindParameterWithIsoStringMilli() { + TimeMilliVector vector = new TimeMilliVector("t", allocator); + vector.allocateNew(1); + TimeAvaticaParameterConverter converter = + new TimeAvaticaParameterConverter(new ArrowType.Time(TimeUnit.MILLISECOND, 32)); + boolean result = + converter.bindParameter( + vector, TypedValue.create(ColumnMetaData.Rep.STRING.toString(), "21:39:50"), 0); + assertTrue(result); + assertEquals(77990000, vector.get(0)); + vector.close(); + } + + @Test + void testBindParameterWithIsoStringMicro() { + TimeMicroVector vector = new TimeMicroVector("t", allocator); + vector.allocateNew(1); + TimeAvaticaParameterConverter converter = + new TimeAvaticaParameterConverter(new ArrowType.Time(TimeUnit.MICROSECOND, 32)); + boolean result = + converter.bindParameter( + vector, TypedValue.create(ColumnMetaData.Rep.STRING.toString(), "21:39:50.123456"), 0); + assertTrue(result); + assertEquals(77990123456L, (long) vector.get(0)); + vector.close(); + } + + @Test + void testBindParameterWithIsoStringNano() { + TimeNanoVector vector = new TimeNanoVector("t", allocator); + vector.allocateNew(1); + TimeAvaticaParameterConverter converter = + new TimeAvaticaParameterConverter(new ArrowType.Time(TimeUnit.NANOSECOND, 64)); + boolean result = + converter.bindParameter( + vector, + TypedValue.create(ColumnMetaData.Rep.STRING.toString(), "21:39:50.123456789"), + 0); + assertTrue(result); + assertEquals(77990123456789L, (long) vector.get(0)); + vector.close(); + } + + @Test + void testBindParameterWithIsoStringSec() { + TimeSecVector vector = new TimeSecVector("t", allocator); + vector.allocateNew(1); + TimeAvaticaParameterConverter converter = + new TimeAvaticaParameterConverter(new ArrowType.Time(TimeUnit.SECOND, 32)); + boolean result = + converter.bindParameter( + vector, TypedValue.create(ColumnMetaData.Rep.STRING.toString(), "21:39:50"), 0); + assertTrue(result); + assertEquals(77990, vector.get(0)); + vector.close(); + } + + @Test + void testBindParameterWithIntValueMilli() { + TimeMilliVector vector = new TimeMilliVector("t", allocator); + vector.allocateNew(1); + TimeAvaticaParameterConverter converter = + new TimeAvaticaParameterConverter(new ArrowType.Time(TimeUnit.MILLISECOND, 32)); + boolean result = + converter.bindParameter( + vector, TypedValue.create(ColumnMetaData.Rep.INTEGER.toString(), 123456), 0); + assertTrue(result); + assertEquals(123456, vector.get(0)); + vector.close(); + } + + @Test + void testBindParameterWithIntValueSec() { + TimeSecVector vector = new TimeSecVector("t", allocator); + vector.allocateNew(1); + TimeAvaticaParameterConverter converter = + new TimeAvaticaParameterConverter(new ArrowType.Time(TimeUnit.SECOND, 32)); + boolean result = + converter.bindParameter( + vector, TypedValue.create(ColumnMetaData.Rep.INTEGER.toString(), 42), 0); + assertTrue(result); + assertEquals(42, vector.get(0)); + vector.close(); + } +} diff --git a/flight/flight-sql-jdbc-core/src/test/java/org/apache/arrow/driver/jdbc/converter/impl/TimestampAvaticaParameterConverterTest.java b/flight/flight-sql-jdbc-core/src/test/java/org/apache/arrow/driver/jdbc/converter/impl/TimestampAvaticaParameterConverterTest.java new file mode 100644 index 0000000000..0954fed3b3 --- /dev/null +++ b/flight/flight-sql-jdbc-core/src/test/java/org/apache/arrow/driver/jdbc/converter/impl/TimestampAvaticaParameterConverterTest.java @@ -0,0 +1,82 @@ +/* + * 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.junit.jupiter.api.Assertions.*; + +import java.time.Instant; +import org.apache.arrow.memory.BufferAllocator; +import org.apache.arrow.memory.RootAllocator; +import org.apache.arrow.vector.TimeStampMilliVector; +import org.apache.arrow.vector.types.TimeUnit; +import org.apache.arrow.vector.types.pojo.ArrowType; +import org.apache.calcite.avatica.ColumnMetaData; +import org.apache.calcite.avatica.remote.TypedValue; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +public class TimestampAvaticaParameterConverterTest { + private BufferAllocator allocator; + + @BeforeEach + void setUp() { + allocator = new RootAllocator(Long.MAX_VALUE); + } + + @AfterEach + void tearDown() { + allocator.close(); + } + + @Test + void testBindIsoStringToMilliVector() { + TimeStampMilliVector vector = new TimeStampMilliVector("ts", allocator); + vector.allocateNew(); + TimestampAvaticaParameterConverter converter = + new TimestampAvaticaParameterConverter(new ArrowType.Timestamp(TimeUnit.MILLISECOND, null)); + String isoString = "2025-08-14T15:53:00.000Z"; + TypedValue typedValue = TypedValue.create(ColumnMetaData.Rep.STRING.toString(), isoString); + assertTrue(converter.bindParameter(vector, typedValue, 0)); + assertEquals(Instant.parse(isoString).toEpochMilli(), vector.get(0)); + vector.close(); + } + + @Test + void testBindLongToMilliVector() { + TimeStampMilliVector vector = new TimeStampMilliVector("ts", allocator); + vector.allocateNew(); + TimestampAvaticaParameterConverter converter = + new TimestampAvaticaParameterConverter(new ArrowType.Timestamp(TimeUnit.MILLISECOND, null)); + long millis = 1755253980000L; + TypedValue typedValue = TypedValue.create(ColumnMetaData.Rep.LONG.toString(), millis); + assertTrue(converter.bindParameter(vector, typedValue, 0)); + assertEquals(millis, vector.get(0)); + vector.close(); + } + + @Test + void testUnsupportedValueType() { + TimeStampMilliVector vector = new TimeStampMilliVector("ts", allocator); + vector.allocateNew(); + TimestampAvaticaParameterConverter converter = + new TimestampAvaticaParameterConverter(new ArrowType.Timestamp(TimeUnit.MILLISECOND, null)); + TypedValue typedValue = TypedValue.create(ColumnMetaData.Rep.DOUBLE.toString(), 3.14); + assertFalse(converter.bindParameter(vector, typedValue, 0)); + vector.close(); + } +}