Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -111,10 +111,7 @@ public void free() {
@Override
Object getCoercedValue(int index) throws SQLException {
LOG.finestTrace("getCoercedValue");
Object value = this.values.get(index);
if (value instanceof Text) {
value = value.toString();
}
Object value = getRawElement(index);
if (this.arrayOfStruct) {
return new BigQueryArrowStruct(
schema.getSubFields(),
Expand All @@ -127,4 +124,10 @@ Object getCoercedValue(int index) throws SQLException {
}
return BigQueryTypeRegistry.convert(value, this.schema.getType().getStandardType(), null);
}

@Override
Object getRawElement(int index) {
Object value = this.values.get(index);
return (value instanceof Text) ? value.toString() : value;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -323,7 +323,8 @@ else if (this.currentBatchRowIndex < this.vectorSchemaRoot.getRowCount()) {
return false;
}

private Object getObjectInternal(int columnIndex) throws SQLException {
@Override
Object getObjectInternal(int columnIndex) throws SQLException {
LOG.finestTrace("getObjectInternal");
checkClosed();
Object value;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,7 @@
class BigQueryArrowStruct extends BigQueryBaseStruct {

private final FieldList schema;

private final JsonStringHashMap<?, ?> values;
private final List<?> values;

BigQueryArrowStruct(FieldList schema, JsonStringHashMap<?, ?> values) {
this(schema, values, false, BigQueryJdbcResultSetLogger.getLogger(BigQueryArrowStruct.class));
Expand All @@ -50,7 +49,7 @@ class BigQueryArrowStruct extends BigQueryBaseStruct {
BigQueryJdbcResultSetLogger log) {
super(enableTimestampPicos, log);
this.schema = schema;
this.values = values;
this.values = values == null ? null : new ArrayList<>(values.values());
}

@Override
Expand All @@ -67,22 +66,17 @@ public Object[] getAttributes() throws SQLException {
if (this.values == null) {
return attributes;
}
List<?> structValues = new ArrayList<>(this.values.values());

for (int index = 0; index < size; index++) {
Field currentSchema = this.schema.get(index);
Object currentValue = structValues.get(index);
Object coercedValue = getValue(currentSchema, currentValue);
Object coercedValue = getValue(currentSchema, getRawAttribute(index));
Array.set(attributes, index, coercedValue);
}
return attributes;
}

private Object getValue(Field currentSchema, Object currentValue) throws SQLException {
LOG.finestTrace("getValue");
if (currentValue instanceof Text) {
currentValue = currentValue.toString();
}
if (isArray(currentSchema)) {
return new BigQueryArrowArray(
currentSchema,
Expand All @@ -107,4 +101,10 @@ private Object getValue(Field currentSchema, Object currentValue) throws SQLExce
return BigQueryTypeRegistry.convert(
currentValue, currentSchema.getType().getStandardType(), null);
}

@Override
Object getRawAttribute(int index) {
Object currentValue = this.values.get(index);
return (currentValue instanceof Text) ? currentValue.toString() : currentValue;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,8 @@ protected Class<?> getTargetClass() {

abstract Object getCoercedValue(int index) throws SQLException;

abstract Object getRawElement(int index);

static boolean isArray(Field currentSchema) {
return currentSchema.getMode() == REPEATED;
}
Expand Down Expand Up @@ -191,6 +193,21 @@ public String toString() {
})
.collect(Collectors.joining(", ", "[", "]"));
}
if (this.schema.getType().getStandardType() == StandardSQLTypeName.TIME) {
StringBuilder sb = new StringBuilder("[");
for (int i = 0; i < array.length; i++) {
if (i > 0) {
sb.append(", ");
}
sb.append(
array[i] == null
? "null"
: BigQueryTypeRegistry.convert(
getRawElement(i), StandardSQLTypeName.TIME, String.class, null));
}
sb.append("]");
return sb.toString();
}
return Arrays.deepToString(array);
} catch (SQLException e) {
LOG.warning("Error converting array to string");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@
import java.sql.Statement;
import java.sql.Time;
import java.sql.Timestamp;
import java.time.LocalTime;
import java.time.ZoneId;
import java.util.Calendar;
import java.util.List;
Expand Down Expand Up @@ -310,10 +311,18 @@ protected Object getObject(int columnIndex, ZoneId zone) throws SQLException {
return getObject(columnIndex);
}

abstract Object getObjectInternal(int columnIndex) throws SQLException;

@Override
public <T> T getObject(int columnIndex, Class<T> type) throws SQLException {
LOG.finestTrace("getObject");
try {
if (type == LocalTime.class || type == String.class) {
StandardSQLTypeName bqType = getStandardSQLTypeName(columnIndex);
if (bqType == StandardSQLTypeName.TIME) {
return BigQueryTypeRegistry.convert(getObjectInternal(columnIndex), bqType, type, null);
}
}
Object value = getObject(columnIndex);
if (value == null) {
return null;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,8 @@ abstract class BigQueryBaseStruct implements java.sql.Struct {

abstract FieldList getSchema();

abstract Object getRawAttribute(int index);

@Override
public final String getSQLTypeName() throws SQLException {
return StandardSQLTypeName.STRUCT.name();
Expand Down Expand Up @@ -95,6 +97,13 @@ public String toString() {
sb.append("\"")
.append(BigQueryTemporalUtility.formatTimestampToUtcString((Timestamp) value))
.append("\"");
} else if (value instanceof Time
&& field.getType().getStandardType() == StandardSQLTypeName.TIME) {
sb.append("\"")
.append(
BigQueryTypeRegistry.convert(
getRawAttribute(i), StandardSQLTypeName.TIME, String.class, null))
.append("\"");
} else if (value instanceof Timestamp || value instanceof Date || value instanceof Time) {
sb.append("\"").append(value.toString()).append("\"");
} else if (value instanceof byte[]) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -112,8 +112,8 @@ public void free() {
@Override
Object getCoercedValue(int index) throws SQLException {
LOG.finestTrace("getCoercedValue");
FieldValue fieldValue = this.values.get(index);
if (fieldValue == null || fieldValue.isNull()) {
FieldValue fieldValue = getRawElement(index);
if (fieldValue == null) {
return null;
}
if (this.arrayOfStruct) {
Expand All @@ -131,4 +131,10 @@ Object getCoercedValue(int index) throws SQLException {
}
return BigQueryTypeRegistry.convert(fieldValue, this.schema.getType().getStandardType(), null);
}

@Override
FieldValue getRawElement(int index) {
FieldValue fieldValue = this.values.get(index);
return (fieldValue == null || fieldValue.isNull()) ? null : fieldValue;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -338,7 +338,8 @@ static boolean isRangeTimestamp(Field field) {
* @param columnIndex the first column is 1, the second is 2, ...
* @return an instance of {@link FieldValue} represents value at <code>columnIndex</code> column.
*/
private FieldValue getObjectInternal(int columnIndex) throws SQLException {
@Override
FieldValue getObjectInternal(int columnIndex) throws SQLException {
checkClosed();
LOG.finestTrace("getObjectInternal");
FieldValue value;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,19 +59,21 @@ public Object[] getAttributes() throws SQLException {
LOG.finestTrace("getAttributes");
int size = schema.size();
Object[] attributes = (Object[]) Array.newInstance(Object.class, size);
if (this.values == null) {
return attributes;
}

for (int index = 0; index < size; index++) {
Field currentSchema = schema.get(index);
FieldValue currentValue = values == null ? null : values.get(index);
Object coercedValue = getValue(currentSchema, currentValue);
Object coercedValue = getValue(currentSchema, getRawAttribute(index));
Array.set(attributes, index, coercedValue);
}
return attributes;
}

private Object getValue(Field currentSchema, FieldValue currentValue) throws SQLException {
LOG.finestTrace("getValue");
if (currentValue == null || currentValue.isNull()) {
if (currentValue == null) {
return null;
}
if (isArray(currentSchema)) {
Expand All @@ -94,4 +96,10 @@ private Object getValue(Field currentSchema, FieldValue currentValue) throws SQL
return BigQueryTypeRegistry.convert(
currentValue, currentSchema.getType().getStandardType(), null);
}

@Override
FieldValue getRawAttribute(int index) {
FieldValue currentValue = this.values.get(index);
return (currentValue == null || currentValue.isNull()) ? null : currentValue;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,11 @@ final class BigQueryTemporalUtility {
.appendFraction(ChronoField.NANO_OF_SECOND, 1, 9, false)
.toFormatter()
.withZone(ZoneOffset.UTC);
static final DateTimeFormatter TIME_FORMATTER =
new DateTimeFormatterBuilder()
.appendPattern("HH:mm:ss")
.appendFraction(ChronoField.NANO_OF_SECOND, 0, 9, true)
.toFormatter();
static final String TIMESTAMP_PICOSECONDS_TYPE_NAME = "TIMESTAMP_PICOSECONDS";

private BigQueryTemporalUtility() {}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,8 +58,6 @@ final class BigQueryTypeRegistry {

private static final DateTimeFormatter TIMESTAMP_FORMATTER =
DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSSSSS");
private static final DateTimeFormatter TIME_FORMATTER =
DateTimeFormatter.ofPattern("HH:mm:ss.SSS");

static {
DESCRIPTORS_BY_ORDINAL = new TypeDescriptor<?>[StandardSQLTypeName.values().length];
Expand Down Expand Up @@ -122,7 +120,10 @@ static TypeDescriptor<?> createStringDescriptor() {
return TIMESTAMP_FORMATTER.format(((Timestamp) val).toLocalDateTime());
}
if (val instanceof Time) {
return TIME_FORMATTER.format(((Time) val).toLocalTime());
return BigQueryTemporalUtility.TIME_FORMATTER.format(
Instant.ofEpochMilli(((Time) val).getTime())
.atZone(ZoneId.systemDefault())
.toLocalTime());
}
Comment thread
keshavdandeva marked this conversation as resolved.
return String.valueOf(val);
});
Expand Down Expand Up @@ -402,16 +403,31 @@ static TypeDescriptor<?> createTimeDescriptor() {
StandardSQLTypeName.TIME,
Arrays.asList(Time.class, LocalTime.class),
(val, targetClass, zone) -> {
if (targetClass == LocalTime.class) {
if (targetClass == LocalTime.class || targetClass == String.class) {
LocalTime localTime = null;
if (val instanceof LocalTime) {
return val;
localTime = (LocalTime) val;
} else if (val instanceof String) {
// Fast Path: Parse directly to LocalTime to preserve microsecond precision
return LocalTime.parse((String) val);
localTime =
LocalTime.parse(
BigQueryTemporalUtility.truncateFractionalSeconds((String) val, 9));
} else if (val instanceof Long) {
localTime = LocalTime.ofNanoOfDay((Long) val * 1_000L);
} else if (val instanceof LocalDateTime) {
return ((LocalDateTime) val).toLocalTime();
localTime = ((LocalDateTime) val).toLocalTime();
} else if (val instanceof Timestamp) {
return ((Timestamp) val).toLocalDateTime().toLocalTime();
localTime = ((Timestamp) val).toLocalDateTime().toLocalTime();
} else if (val instanceof Time) {
localTime =
Instant.ofEpochMilli(((Time) val).getTime())
.atZone(ZoneId.systemDefault())
.toLocalTime();
}
Comment thread
keshavdandeva marked this conversation as resolved.
if (localTime != null) {
return targetClass == String.class
? BigQueryTemporalUtility.TIME_FORMATTER.format(localTime)
: localTime;
}
}

Expand All @@ -421,7 +437,7 @@ else if (val instanceof Timestamp) {
ZoneId targetZone = zone != null ? zone : ZoneId.systemDefault();
LocalTime localTime = ((Timestamp) val).toInstant().atZone(targetZone).toLocalTime();
sqlTime = BigQueryTemporalUtility.boxTime(localTime, zone);
} else if (val instanceof java.sql.Date) {
} else if (val instanceof Date) {
throw new BigQueryJdbcException("Cannot convert to TIME: " + val);
} else if (val instanceof java.util.Date)
sqlTime = new Time(((java.util.Date) val).getTime());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -472,4 +472,30 @@ public void testArrowArrayTimestampPicosDisabled() throws SQLException {
assertThat(rs.getString(2)).isEqualTo("2026-04-08 10:00:00.123456");
assertThat(rs.getObject(2)).isEqualTo(expectedTs);
}

@Test
public void testArrowArrayTimePreservesMicroseconds() throws SQLException {
long t1Micros = (14L * 3600 + 30L * 60) * 1_000_000L + 123_456L;
long t2Micros = (23L * 3600 + 59L * 60 + 59L) * 1_000_000L + 999_999L;
long t3Micros = 3600L * 1_000_000L; // 01:00:00
Tuple<Field, JsonStringArrayList<Object>> schemaAndValue =
arrowArraySchemaAndValue(TIME, t1Micros, t2Micros, t3Micros);
BigQueryArrowArray array = new BigQueryArrowArray(schemaAndValue.x(), schemaAndValue.y());

assertThat(array.toString()).isEqualTo("[14:30:00.123456, 23:59:59.999999, 01:00:00]");

try (ResultSet rs = array.getResultSet()) {
assertThat(rs.next()).isTrue();
assertThat(rs.getString(2)).isEqualTo("14:30:00.123456");
assertThat(rs.getObject(2, LocalTime.class)).isEqualTo(LocalTime.of(14, 30, 0, 123_456_000));

assertThat(rs.next()).isTrue();
assertThat(rs.getString(2)).isEqualTo("23:59:59.999999");
assertThat(rs.getObject(2, LocalTime.class)).isEqualTo(LocalTime.of(23, 59, 59, 999_999_000));

assertThat(rs.next()).isTrue();
assertThat(rs.getString(2)).isEqualTo("01:00:00");
assertThat(rs.getObject(2, LocalTime.class)).isEqualTo(LocalTime.of(1, 0, 0));
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -278,4 +278,17 @@ public void testArrowStructTimestampPicosDisabled() throws SQLException {
Timestamp expectedTs = Timestamp.valueOf("2026-04-08 10:00:00.123456789");
assertThat(attributes).isEqualTo(new Object[] {expectedTs});
}

@Test
public void testArrowStructTimePreservesMicroseconds() {
Field timeField = Field.of("t", TIME);
FieldList schema = FieldList.of(timeField);
long timeMicros = (14L * 3600 + 30L * 60) * 1_000_000L + 123_456L;
JsonStringHashMap<String, Object> values = new JsonStringHashMap<>();
values.put("t", timeMicros);

BigQueryArrowStruct struct = new BigQueryArrowStruct(schema, values);

assertThat(struct.toString()).isEqualTo("{\"t\":\"14:30:00.123456\"}");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -458,4 +458,27 @@ public void testJsonArrayTimestampPicosDisabled() throws SQLException {
assertThat(rs.getString(2)).isEqualTo("2023-03-30 11:14:19.123456");
assertThat(rs.getObject(2)).isEqualTo(expectedTs);
}

@Test
public void testJsonArrayTimePreservesMicroseconds() throws SQLException {
Tuple<Field, FieldValue> schemaAndValue =
arraySchemaAndValue(TIME, "14:30:00.123456", "23:59:59.999999", "01:00:00");
BigQueryJsonArray array = new BigQueryJsonArray(schemaAndValue.x(), schemaAndValue.y());

assertThat(array.toString()).isEqualTo("[14:30:00.123456, 23:59:59.999999, 01:00:00]");

try (ResultSet rs = array.getResultSet()) {
assertThat(rs.next()).isTrue();
assertThat(rs.getString(2)).isEqualTo("14:30:00.123456");
assertThat(rs.getObject(2, LocalTime.class)).isEqualTo(LocalTime.of(14, 30, 0, 123_456_000));

assertThat(rs.next()).isTrue();
assertThat(rs.getString(2)).isEqualTo("23:59:59.999999");
assertThat(rs.getObject(2, LocalTime.class)).isEqualTo(LocalTime.of(23, 59, 59, 999_999_000));

assertThat(rs.next()).isTrue();
assertThat(rs.getString(2)).isEqualTo("01:00:00");
assertThat(rs.getObject(2, LocalTime.class)).isEqualTo(LocalTime.of(1, 0, 0));
}
}
}
Loading
Loading