Skip to content
Merged
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 @@ -122,6 +122,9 @@ Object getCoercedValue(int index) throws SQLException {
if (this.enableTimestampPicos && BigQueryTemporalUtility.isPicosecondTimestamp(this.schema)) {
return BigQueryTemporalUtility.formatTimestampValue(value, true);
}
if (BigQueryTemporalUtility.isRange(this.schema)) {
return BigQueryTemporalUtility.formatRange(this.schema, value, this.enableTimestampPicos);
}
return BigQueryTypeRegistry.convert(value, this.schema.getType().getStandardType(), null);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,7 @@
import java.math.BigDecimal;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Timestamp;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.util.ArrayList;
import java.util.List;
Expand Down Expand Up @@ -410,6 +408,9 @@ protected Object getObject(int columnIndex, ZoneId zone) throws SQLException {
if (this.enableTimestampPicos && BigQueryTemporalUtility.isPicosecondTimestamp(arrayField)) {
return BigQueryTemporalUtility.formatTimestampValue(value, true);
}
if (BigQueryTemporalUtility.isRange(arrayField)) {
return BigQueryTemporalUtility.formatRange(arrayField, value, this.enableTimestampPicos);
}
return BigQueryTypeRegistry.convert(value, arrayField.getType().getStandardType(), zone);
}

Expand All @@ -431,29 +432,6 @@ protected Object getObject(int columnIndex, ZoneId zone) throws SQLException {
return new BigQueryArrowArray(
fieldSchema, newList, this.enableTimestampPicos, this.LOG.getArrowArrayLogger());
}
if (elementTypeName == StandardSQLTypeName.RANGE) {
JsonStringArrayList<String> newList = new JsonStringArrayList<>();
for (Object item : originalList) {
if (item == null) {
newList.add(null);
continue;
}
JsonStringHashMap<?, ?> rangeMap = (JsonStringHashMap<?, ?>) item;
Object start = rangeMap.get("start");
Object end = rangeMap.get("end");

Object representativeElement = (start != null) ? start : end;
StandardSQLTypeName rangeElementType =
getRangeElementType(fieldSchema, representativeElement);

String formattedStart = formatRangeElement(start, rangeElementType);
String formattedEnd = formatRangeElement(end, rangeElementType);

newList.add(String.format("[%s, %s)", formattedStart, formattedEnd));
}
return new BigQueryArrowArray(
fieldSchema, newList, this.enableTimestampPicos, this.LOG.getArrowArrayLogger());
}
return new BigQueryArrowArray(
fieldSchema, originalList, this.enableTimestampPicos, this.LOG.getArrowArrayLogger());
}
Expand All @@ -466,18 +444,8 @@ protected Object getObject(int columnIndex, ZoneId zone) throws SQLException {
this.LOG.getArrowStructLogger());
}

if (fieldSchema.getType().getStandardType() == StandardSQLTypeName.RANGE) {
JsonStringHashMap<?, ?> rangeMap = (JsonStringHashMap<?, ?>) value;
Object start = rangeMap.get("start");
Object end = rangeMap.get("end");

Object representativeElement = (start != null) ? start : end;
StandardSQLTypeName elementType = getRangeElementType(fieldSchema, representativeElement);

String formattedStart = formatRangeElement(start, elementType);
String formattedEnd = formatRangeElement(end, elementType);

return String.format("[%s, %s)", formattedStart, formattedEnd);
if (BigQueryTemporalUtility.isRange(fieldSchema)) {
return BigQueryTemporalUtility.formatRange(fieldSchema, value, this.enableTimestampPicos);
}

if ((fieldSchema.getType().getStandardType() == StandardSQLTypeName.NUMERIC
Expand All @@ -493,59 +461,6 @@ protected Object getObject(int columnIndex, ZoneId zone) throws SQLException {
return BigQueryTypeRegistry.convert(value, fieldSchema.getType().getStandardType(), zone);
}

private StandardSQLTypeName getRangeElementType(Field field, Object representativeElement) {
if (field == null
|| field.getRangeElementType() == null
|| field.getRangeElementType().getType() == null) {
return getElementTypeFromValue(representativeElement);
}
try {
return StandardSQLTypeName.valueOf(field.getRangeElementType().getType());
} catch (IllegalArgumentException ignored) {
return getElementTypeFromValue(representativeElement);
}
}

private StandardSQLTypeName getElementTypeFromValue(Object element) {
if (element == null) {
return StandardSQLTypeName.STRING;
}
if (element instanceof Integer) {
return StandardSQLTypeName.DATE;
}
if (element instanceof Long) {
return StandardSQLTypeName.TIMESTAMP;
}
if (element instanceof LocalDateTime) {
return StandardSQLTypeName.DATETIME;
}
return StandardSQLTypeName.STRING;
}

private String formatRangeElement(Object element, StandardSQLTypeName elementType)
throws SQLException {
if (element == null) {
return "UNBOUNDED";
}
if (element instanceof Text) {
element = element.toString();
}
switch (elementType) {
case DATE:
// Arrow gives DATE as an Integer (days since epoch)
return LocalDate.ofEpochDay(((Integer) element).longValue()).toString();
case DATETIME:
// Arrow gives DATETIME as a LocalDateTime
Timestamp dtTs = Timestamp.valueOf((LocalDateTime) element);
return BigQueryTypeRegistry.convert(dtTs, String.class);
case TIMESTAMP:
return BigQueryTemporalUtility.formatTimestampValue(element, this.enableTimestampPicos);
default:
// Fallback for any other unexpected type
return element.toString();
}
}

@Override
public void close() {
if (this.isClosed) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,10 @@ private Object getValue(Field currentSchema, Object currentValue) throws SQLExce
if (this.enableTimestampPicos && BigQueryTemporalUtility.isPicosecondTimestamp(currentSchema)) {
return BigQueryTemporalUtility.formatTimestampValue(currentValue, true);
}
if (BigQueryTemporalUtility.isRange(currentSchema)) {
return BigQueryTemporalUtility.formatRange(
currentSchema, currentValue, this.enableTimestampPicos);
}
return BigQueryTypeRegistry.convert(
currentValue, currentSchema.getType().getStandardType(), null);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -126,8 +126,9 @@ Object getCoercedValue(int index) throws SQLException {
if (this.enableTimestampPicos && BigQueryTemporalUtility.isPicosecondTimestamp(this.schema)) {
return BigQueryTemporalUtility.formatTimestampValue(fieldValue.getStringValue(), true);
}
if (this.enableTimestampPicos && BigQueryJsonResultSet.isRangeTimestamp(this.schema)) {
return BigQueryJsonResultSet.formatRangeTimestamp(fieldValue);
if (BigQueryTemporalUtility.isRange(this.schema)) {
return BigQueryTemporalUtility.formatRange(
this.schema, fieldValue, this.enableTimestampPicos);
}
return BigQueryTypeRegistry.convert(fieldValue, this.schema.getType().getStandardType(), null);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,7 @@
import com.google.cloud.bigquery.FieldValue;
import com.google.cloud.bigquery.FieldValue.Attribute;
import com.google.cloud.bigquery.Job;
import com.google.cloud.bigquery.Range;
import com.google.cloud.bigquery.Schema;
import com.google.cloud.bigquery.StandardSQLTypeName;
import com.google.cloud.bigquery.exception.BigQueryJdbcException;
import java.sql.ResultSet;
import java.sql.SQLException;
Expand Down Expand Up @@ -285,8 +283,8 @@ protected Object getObject(int columnIndex, ZoneId zone) throws SQLException {
if (this.enableTimestampPicos && BigQueryTemporalUtility.isPicosecondTimestamp(arrayField)) {
return BigQueryTemporalUtility.formatTimestampValue(value.getStringValue(), true);
}
if (this.enableTimestampPicos && isRangeTimestamp(arrayField)) {
return formatRangeTimestamp(value);
if (BigQueryTemporalUtility.isRange(arrayField)) {
return BigQueryTemporalUtility.formatRange(arrayField, value, this.enableTimestampPicos);
}
return BigQueryTypeRegistry.convert(value, arrayField.getType().getStandardType(), zone);
}
Expand All @@ -303,38 +301,15 @@ protected Object getObject(int columnIndex, ZoneId zone) throws SQLException {
this.enableTimestampPicos,
this.LOG.getJsonStructLogger());
}
if (this.enableTimestampPicos && isRangeTimestamp(fieldSchema)) {
return formatRangeTimestamp(value);
if (BigQueryTemporalUtility.isRange(fieldSchema)) {
return BigQueryTemporalUtility.formatRange(fieldSchema, value, this.enableTimestampPicos);
}
if (this.enableTimestampPicos && BigQueryTemporalUtility.isPicosecondTimestamp(fieldSchema)) {
return BigQueryTemporalUtility.formatTimestampValue(value.getStringValue(), true);
}
return BigQueryTypeRegistry.convert(value, fieldSchema.getType().getStandardType(), zone);
}

static String formatRangeTimestamp(FieldValue value) throws SQLException {
Range range = value.getRangeValue();
String start =
range.getStart().isNull()
? "UNBOUNDED"
: BigQueryTemporalUtility.formatTimestampValue(range.getStart().getStringValue(), true);
String end =
range.getEnd().isNull()
? "UNBOUNDED"
: BigQueryTemporalUtility.formatTimestampValue(range.getEnd().getStringValue(), true);
return String.format("[%s, %s)", start, end);
}

static boolean isRangeTimestamp(Field field) {
return field != null
&& field.getRangeElementType() != null
&& field.getRangeElementType().getType() != null
&& field
.getRangeElementType()
.getType()
.equalsIgnoreCase(StandardSQLTypeName.TIMESTAMP.name());
}

/**
* This method will be called by every other getter of this {@link java.sql.ResultSet}, including
* {@link #getObject(int)} to get the value in its rawest form i.e. {@link FieldValue} to coerce
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -90,8 +90,9 @@ private Object getValue(Field currentSchema, FieldValue currentValue) throws SQL
if (this.enableTimestampPicos && BigQueryTemporalUtility.isPicosecondTimestamp(currentSchema)) {
return BigQueryTemporalUtility.formatTimestampValue(currentValue.getStringValue(), true);
}
if (this.enableTimestampPicos && BigQueryJsonResultSet.isRangeTimestamp(currentSchema)) {
return BigQueryJsonResultSet.formatRangeTimestamp(currentValue);
if (BigQueryTemporalUtility.isRange(currentSchema)) {
return BigQueryTemporalUtility.formatRange(
currentSchema, currentValue, this.enableTimestampPicos);
}
return BigQueryTypeRegistry.convert(
currentValue, currentSchema.getType().getStandardType(), null);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@
package com.google.cloud.bigquery.jdbc;

import com.google.cloud.bigquery.Field;
import com.google.cloud.bigquery.FieldValue;
import com.google.cloud.bigquery.Range;
import com.google.cloud.bigquery.StandardSQLTypeName;
import com.google.cloud.bigquery.exception.BigQueryJdbcException;
import java.math.BigDecimal;
Expand All @@ -37,6 +39,7 @@
import java.time.temporal.ChronoField;
import java.util.Calendar;
import java.util.GregorianCalendar;
import java.util.Map;
import java.util.TimeZone;

/**
Expand Down Expand Up @@ -421,6 +424,93 @@ static boolean isPicosecondTimestamp(Field field) {
&& field.getTimestampPrecision() > 6;
}

static boolean isRange(Field field) {
return field != null
&& field.getType() != null
&& field.getType().getStandardType() == StandardSQLTypeName.RANGE;
}

/**
* Formats a RANGE value (a REST {@link FieldValue} or an Arrow {@code start}/{@code end} map) as
* {@code [start, end)}. Each bound reads like {@code getString} on its element type; null bounds
* read as {@code UNBOUNDED}.
*/
static String formatRange(Field field, Object value, boolean enableTimestampPicos)
throws BigQueryJdbcException {
Object start;
Object end;
if (value instanceof FieldValue) {
Range range = ((FieldValue) value).getRangeValue();
start = range.getStart().isNull() ? null : range.getStart().getStringValue();
end = range.getEnd().isNull() ? null : range.getEnd().getStringValue();
Comment thread
keshavdandeva marked this conversation as resolved.
Resolved
} else if (value instanceof Map) {
start = ((Map<?, ?>) value).get("start");
end = ((Map<?, ?>) value).get("end");
} else {
return value == null ? null : value.toString();
}
StandardSQLTypeName elementType = getRangeElementType(field, start != null ? start : end);
return "["
+ formatRangeBound(start, elementType, enableTimestampPicos)
+ ", "
+ formatRangeBound(end, elementType, enableTimestampPicos)
+ ")";
}

private static StandardSQLTypeName getRangeElementType(Field field, Object bound) {
if (field != null
&& field.getRangeElementType() != null
&& field.getRangeElementType().getType() != null) {
try {
return StandardSQLTypeName.valueOf(field.getRangeElementType().getType());
} catch (IllegalArgumentException ignored) {
}
}
// Arrow bound classes: DATE -> Integer epoch days, DATETIME -> LocalDateTime, TIMESTAMP -> Long
// epoch micros.
if (bound instanceof Integer) {
return StandardSQLTypeName.DATE;
}
if (bound instanceof LocalDateTime) {
return StandardSQLTypeName.DATETIME;
}
if (bound instanceof Long) {
return StandardSQLTypeName.TIMESTAMP;
}
return StandardSQLTypeName.STRING;
}

private static String formatRangeBound(
Object bound, StandardSQLTypeName elementType, boolean enableTimestampPicos)
throws BigQueryJdbcException {
if (bound == null) {
return "UNBOUNDED";
}
try {
switch (elementType) {
case DATE:
return bound instanceof Integer
? LocalDate.ofEpochDay((Integer) bound).toString()
: bound.toString();
case DATETIME:
LocalDateTime ldt =
bound instanceof LocalDateTime
? (LocalDateTime) bound
: LocalDateTime.parse(
truncateFractionalSeconds(bound.toString().replace(' ', 'T'), 9));
StringBuilder sb = formatDateTimeBase(ldt, 6);
appendPadded(sb, ldt.getNano() / 1_000, 6);
return sb.toString();
case TIMESTAMP:
return formatTimestampValue(bound, enableTimestampPicos);
default:
return bound.toString();
}
} catch (DateTimeException e) {
throw new BigQueryJdbcException("Invalid RANGE bound: " + bound, e);
}
}

private static StringBuilder formatDateTimeBase(LocalDateTime dt, int scale) {
StringBuilder sb = new StringBuilder(scale == 12 ? 32 : 26);
BASE_FORMATTER.formatTo(dt, sb);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@
package com.google.cloud.bigquery.jdbc;

import com.google.cloud.bigquery.FieldValue;
import com.google.cloud.bigquery.Range;
import com.google.cloud.bigquery.StandardSQLTypeName;
import com.google.cloud.bigquery.exception.BigQueryJdbcException;
import com.google.cloud.bigquery.exception.BigQueryJdbcSqlFeatureNotSupportedException;
Expand Down Expand Up @@ -656,17 +655,7 @@ static TypeDescriptor<?> createRangeDescriptor() {
String.class,
StandardSQLTypeName.RANGE,
Arrays.asList(String.class),
(val, targetClass, zone) -> {
if (val == null) return null;
if (val instanceof Range) {
Range range = (Range) val;
String start =
range.getStart().isNull() ? "UNBOUNDED" : range.getStart().getStringValue();
String end = range.getEnd().isNull() ? "UNBOUNDED" : range.getEnd().getStringValue();
return String.format("[%s, %s)", start, end);
}
return String.valueOf(val);
});
(val, targetClass, zone) -> val == null ? null : String.valueOf(val));
}

private static void register(TypeDescriptor<?> descriptor) {
Expand Down
Loading
Loading