Skip to content

Commit 25b1ec5

Browse files
GH-838: Guard stale raw timestamps and add overwrite tests.
1 parent 3401348 commit 25b1ec5

File tree

3 files changed

+54
-1
lines changed

3 files changed

+54
-1
lines changed

flight/flight-sql-jdbc-core/src/main/java/org/apache/arrow/driver/jdbc/converter/impl/TimestampAvaticaParameterConverter.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
import org.apache.arrow.vector.types.pojo.ArrowType;
3030
import org.apache.arrow.vector.types.pojo.Field;
3131
import org.apache.calcite.avatica.AvaticaParameter;
32+
import org.apache.calcite.avatica.ColumnMetaData;
3233
import org.apache.calcite.avatica.remote.TypedValue;
3334
import org.checkerframework.checker.nullness.qual.Nullable;
3435

@@ -93,7 +94,8 @@ private long convertFromMillis(long epochMillis) {
9394
public boolean bindParameter(
9495
FieldVector vector, TypedValue typedValue, int index, @Nullable Timestamp rawTimestamp) {
9596
long value;
96-
if (rawTimestamp != null) {
97+
// Only use the raw timestamp if the TypedValue actually represents a timestamp.
98+
if (rawTimestamp != null && typedValue.type == ColumnMetaData.Rep.JAVA_SQL_TIMESTAMP) {
9799
value = convertFromTimestamp(rawTimestamp);
98100
} else {
99101
value = convertFromMillis((long) typedValue.toLocal());

flight/flight-sql-jdbc-core/src/test/java/org/apache/arrow/driver/jdbc/ArrowFlightPreparedStatementTest.java

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -388,6 +388,33 @@ public void testSetObjectAfterSetTimestampClearsRawTimestamp() throws SQLExcepti
388388
}
389389
}
390390

391+
@Test
392+
public void testSetLongAfterSetTimestampIgnoresRawTimestamp() throws SQLException {
393+
String query = "Fake timestamp setLong after setTimestamp";
394+
Schema parameterSchema =
395+
new Schema(
396+
Collections.singletonList(
397+
Field.nullable("ts", new ArrowType.Timestamp(TimeUnit.MICROSECOND, "UTC"))));
398+
399+
// setLong replaces the timestamp TypedValue with millis. The stale raw timestamp must be
400+
// ignored, so the long value 1000L becomes 1000000 epoch micros.
401+
List<List<Object>> expected = Collections.singletonList(Collections.singletonList(1000000L));
402+
403+
PRODUCER.addUpdateQuery(query, 1);
404+
PRODUCER.addExpectedParameters(query, parameterSchema, expected);
405+
406+
try (PreparedStatement stmt = connection.prepareStatement(query)) {
407+
Timestamp ts = new Timestamp(1730637909869L);
408+
ts.setNanos(869885001);
409+
stmt.setTimestamp(1, ts);
410+
411+
stmt.setLong(1, 1000L);
412+
413+
int updated = stmt.executeUpdate();
414+
assertEquals(1, updated);
415+
}
416+
}
417+
391418
@Test
392419
public void testSetTimestampAfterSetObjectPreservesSubMillis() throws SQLException {
393420
String query = "Fake timestamp setTimestamp after setObject";

flight/flight-sql-jdbc-core/src/test/java/org/apache/arrow/driver/jdbc/converter/impl/TimestampAvaticaParameterConverterTest.java

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -246,6 +246,30 @@ public void testNegativeEpochRawTimestampPreservesSubMillis() {
246246
}
247247
}
248248

249+
@Test
250+
public void testStaleRawTimestampIgnoredWhenTypedValueIsNotTimestamp() {
251+
BufferAllocator allocator = rootAllocatorTestExtension.getRootAllocator();
252+
ArrowType.Timestamp type = new ArrowType.Timestamp(TimeUnit.MICROSECOND, null);
253+
TimestampAvaticaParameterConverter converter = new TimestampAvaticaParameterConverter(type);
254+
255+
// Simulate stale map: rawTimestamp is present but TypedValue was set via setLong()
256+
Timestamp staleTs = new Timestamp(TEST_EPOCH_MILLIS_WITH_FRACTIONAL_SECONDS);
257+
staleTs.setNanos(869885001);
258+
259+
// A different value set via setLong — TypedValue type will be PRIMITIVE_LONG, not
260+
// JAVA_SQL_TIMESTAMP
261+
long longValue = 1774261392L;
262+
263+
try (TimeStampMicroVector vector = new TimeStampMicroVector("ts", allocator)) {
264+
vector.allocateNew(1);
265+
TypedValue typedValue = TypedValue.ofLocal(ColumnMetaData.Rep.LONG, longValue);
266+
// Even though staleTs is provided, the converter should ignore it because
267+
// typedValue.type != JAVA_SQL_TIMESTAMP, and fall back to convertFromMillis
268+
assertTrue(converter.bindParameter(vector, typedValue, 0, staleTs));
269+
assertEquals(longValue * 1_000L, vector.get(0));
270+
}
271+
}
272+
249273
private void assertBindConvertsMillis(TimeUnit unit, String tz, long expectedValue) {
250274
BufferAllocator allocator = rootAllocatorTestExtension.getRootAllocator();
251275
ArrowType.Timestamp type = new ArrowType.Timestamp(unit, tz);

0 commit comments

Comments
 (0)