google-cloud-bigquery
diff --git a/google-cloud-bigquery/src/main/java/com/google/cloud/bigquery/BigQueryResultImpl.java b/google-cloud-bigquery/src/main/java/com/google/cloud/bigquery/BigQueryResultImpl.java
index a1bb4d406..e1e129eae 100644
--- a/google-cloud-bigquery/src/main/java/com/google/cloud/bigquery/BigQueryResultImpl.java
+++ b/google-cloud-bigquery/src/main/java/com/google/cloud/bigquery/BigQueryResultImpl.java
@@ -24,14 +24,17 @@
import java.sql.Timestamp;
import java.time.LocalDateTime;
import java.time.LocalTime;
-import java.time.ZoneId;
import java.util.Map;
-import java.util.TimeZone;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.TimeUnit;
import org.apache.arrow.vector.util.JsonStringArrayList;
import org.apache.arrow.vector.util.Text;
+/**
+ * An implementation of BigQueryResult.
+ *
+ * This class and the ResultSet it returns is not thread-safe.
+ */
public class BigQueryResultImpl implements BigQueryResult {
private static final String NULL_CURSOR_MSG =
@@ -109,6 +112,7 @@ private class BigQueryResultSet extends AbstractJdbcResultSet {
private boolean hasReachedEnd =
false; // flag which will be set to true when we have encountered a EndOfStream or when
// curTup.isLast(). Ref: https://github.com/googleapis/java-bigquery/issues/2033
+ private boolean wasNull = false;
@Override
/*Advances the result set to the next row, returning false if no such row exists. Potentially blocking operation*/
@@ -148,6 +152,14 @@ private boolean isEndOfStream(T cursor) {
return cursor instanceof ConnectionImpl.EndOfFieldValueList;
}
+ private Object getCurrentValueForReadApiData(String fieldName) throws SQLException {
+ Row curRow = (Row) cursor;
+ if (!curRow.hasField(fieldName)) {
+ throw new SQLException(String.format("Field %s not found", fieldName));
+ }
+ return curRow.get(fieldName);
+ }
+
@Override
public Object getObject(String fieldName) throws SQLException {
if (fieldName == null) {
@@ -157,13 +169,20 @@ public Object getObject(String fieldName) throws SQLException {
throw new BigQuerySQLException(NULL_CURSOR_MSG);
} else if (cursor instanceof FieldValueList) {
FieldValue fieldValue = ((FieldValueList) cursor).get(fieldName);
- return (fieldValue == null || fieldValue.getValue() == null) ? null : fieldValue.getValue();
+ if (fieldValue == null || fieldValue.getValue() == null) {
+ wasNull = true;
+ return null;
+ }
+ wasNull = false;
+ return fieldValue.getValue();
} else { // Data received from Read API (Arrow)
- Row curRow = (Row) cursor;
- if (!curRow.hasField(fieldName)) {
- throw new SQLException(String.format("Field %s not found", fieldName));
+ Object curVal = getCurrentValueForReadApiData(fieldName);
+ if (curVal == null) {
+ wasNull = true;
+ return null;
}
- return curRow.get(fieldName);
+ wasNull = false;
+ return curVal;
}
}
@@ -173,7 +192,12 @@ public Object getObject(int columnIndex) throws SQLException {
return null;
} else if (cursor instanceof FieldValueList) {
FieldValue fieldValue = ((FieldValueList) cursor).get(columnIndex);
- return (fieldValue == null || fieldValue.getValue() == null) ? null : fieldValue.getValue();
+ if (fieldValue == null || fieldValue.getValue() == null) {
+ wasNull = true;
+ return null;
+ }
+ wasNull = false;
+ return fieldValue.getValue();
} else { // Data received from Read API (Arrow)
return getObject(schemaFieldList.get(columnIndex).getName());
}
@@ -189,23 +213,23 @@ public String getString(String fieldName) throws SQLException {
} else if (cursor instanceof FieldValueList) {
FieldValue fieldValue = ((FieldValueList) cursor).get(fieldName);
if ((fieldValue == null || fieldValue.getValue() == null)) {
+ wasNull = true;
return null;
- } else if (fieldValue
- .getAttribute()
- .equals(FieldValue.Attribute.REPEATED)) { // Case for Arrays
+ }
+ wasNull = false;
+ if (fieldValue.getAttribute().equals(FieldValue.Attribute.REPEATED)) { // Case for Arrays
return fieldValue.getValue().toString();
} else {
return fieldValue.getStringValue();
}
} else { // Data received from Read API (Arrow)
- Row curRow = (Row) cursor;
- if (!curRow.hasField(fieldName)) {
- throw new SQLException(String.format("Field %s not found", fieldName));
- }
- Object currentVal = curRow.get(fieldName);
+ Object currentVal = getCurrentValueForReadApiData(fieldName);
if (currentVal == null) {
+ wasNull = true;
return null;
- } else if (currentVal instanceof JsonStringArrayList) { // arrays
+ }
+ wasNull = false;
+ if (currentVal instanceof JsonStringArrayList) { // arrays
JsonStringArrayList jsnAry = (JsonStringArrayList) currentVal;
return jsnAry.toString();
} else if (currentVal instanceof LocalDateTime) {
@@ -224,9 +248,12 @@ public String getString(int columnIndex) throws SQLException {
return null;
} else if (cursor instanceof FieldValueList) {
FieldValue fieldValue = ((FieldValueList) cursor).get(columnIndex);
- return (fieldValue == null || fieldValue.getValue() == null)
- ? null
- : fieldValue.getStringValue();
+ if (fieldValue == null || fieldValue.getValue() == null) {
+ wasNull = true;
+ return null;
+ }
+ wasNull = false;
+ return fieldValue.getStringValue();
} else { // Data received from Read API (Arrow)
return getString(schemaFieldList.get(columnIndex).getName());
}
@@ -242,27 +269,27 @@ public int getInt(String fieldName) throws SQLException {
// java.sql.ResultSet definition
} else if (cursor instanceof FieldValueList) {
FieldValue fieldValue = ((FieldValueList) cursor).get(fieldName);
- return (fieldValue == null || fieldValue.getValue() == null)
- ? 0
- : fieldValue.getNumericValue().intValue();
- } else { // Data received from Read API (Arrow)
-
- Row curRow = (Row) cursor;
- if (!curRow.hasField(fieldName)) {
- throw new SQLException(String.format("Field %s not found", fieldName));
+ if ((fieldValue == null || fieldValue.getValue() == null)) {
+ wasNull = true;
+ return 0;
}
- Object curVal = curRow.get(fieldName);
- if (curVal == null) {
+ wasNull = false;
+ return fieldValue.getNumericValue().intValue();
+ } else { // Data received from Read API (Arrow)
+ Object currentVal = getCurrentValueForReadApiData(fieldName);
+ if (currentVal == null) {
+ wasNull = true;
return 0;
}
- if (curVal instanceof Text) { // parse from text to int
- return Integer.parseInt(((Text) curVal).toString());
- } else if (curVal
+ wasNull = false;
+ if (currentVal instanceof Text) { // parse from text to int
+ return Integer.parseInt((currentVal).toString());
+ } else if (currentVal
instanceof
Long) { // incase getInt is called for a Long value. Loss of precision might occur
- return ((Long) curVal).intValue();
+ return ((Long) currentVal).intValue();
}
- return ((BigDecimal) curVal).intValue();
+ return ((BigDecimal) currentVal).intValue();
}
}
@@ -273,9 +300,11 @@ public int getInt(int columnIndex) throws SQLException {
// java.sql.ResultSet definition
} else if (cursor instanceof FieldValueList) {
FieldValue fieldValue = ((FieldValueList) cursor).get(columnIndex);
- return (fieldValue == null || fieldValue.getValue() == null)
- ? 0
- : fieldValue.getNumericValue().intValue();
+ if (fieldValue == null || fieldValue.getValue() == null) {
+ wasNull = true;
+ return 0;
+ }
+ return fieldValue.getNumericValue().intValue();
} else { // Data received from Read API (Arrow)
return getInt(schemaFieldList.get(columnIndex).getName());
}
@@ -290,20 +319,21 @@ public long getLong(String fieldName) throws SQLException {
throw new BigQuerySQLException(NULL_CURSOR_MSG);
} else if (cursor instanceof FieldValueList) {
FieldValue fieldValue = ((FieldValueList) cursor).get(fieldName);
- return (fieldValue == null || fieldValue.getValue() == null)
- ? 0L
- : fieldValue.getNumericValue().longValue();
- } else { // Data received from Read API (Arrow)
- Row curRow = (Row) cursor;
- if (!curRow.hasField(fieldName)) {
- throw new SQLException(String.format("Field %s not found", fieldName));
+ if ((fieldValue == null || fieldValue.getValue() == null)) {
+ wasNull = true;
+ return 0L;
}
- Object curVal = curRow.get(fieldName);
+ wasNull = false;
+ return fieldValue.getNumericValue().longValue();
+ } else { // Data received from Read API (Arrow)
+ Object curVal = getCurrentValueForReadApiData(fieldName);
if (curVal == null) {
+ wasNull = true;
return 0L;
- } else { // value will be Long or BigDecimal, but are Number
- return ((Number) curVal).longValue();
}
+ wasNull = false;
+ // value will be Long or BigDecimal, but are Number
+ return ((Number) curVal).longValue();
}
}
@@ -314,9 +344,12 @@ public long getLong(int columnIndex) throws SQLException {
// java.sql.ResultSet definition
} else if (cursor instanceof FieldValueList) {
FieldValue fieldValue = ((FieldValueList) cursor).get(columnIndex);
- return (fieldValue == null || fieldValue.getValue() == null)
- ? 0L
- : fieldValue.getNumericValue().longValue();
+ if ((fieldValue == null || fieldValue.getValue() == null)) {
+ wasNull = true;
+ return 0L;
+ }
+ wasNull = false;
+ return fieldValue.getNumericValue().longValue();
} else { // Data received from Read API (Arrow)
return getInt(schemaFieldList.get(columnIndex).getName());
}
@@ -331,16 +364,20 @@ public double getDouble(String fieldName) throws SQLException {
throw new BigQuerySQLException(NULL_CURSOR_MSG);
} else if (cursor instanceof FieldValueList) {
FieldValue fieldValue = ((FieldValueList) cursor).get(fieldName);
- return (fieldValue == null || fieldValue.getValue() == null)
- ? 0d
- : fieldValue.getNumericValue().doubleValue();
+ if ((fieldValue == null || fieldValue.getValue() == null)) {
+ wasNull = true;
+ return 0.0d;
+ }
+ wasNull = false;
+ return fieldValue.getNumericValue().doubleValue();
} else { // Data received from Read API (Arrow)
- Row curRow = (Row) cursor;
- if (!curRow.hasField(fieldName)) {
- throw new SQLException(String.format("Field %s not found", fieldName));
+ Object curVal = getCurrentValueForReadApiData(fieldName);
+ if (curVal == null) {
+ wasNull = true;
+ return 0.0d;
}
- Object curVal = curRow.get(fieldName);
- return curVal == null ? 0.0d : new BigDecimal(curVal.toString()).doubleValue();
+ wasNull = false;
+ return new BigDecimal(curVal.toString()).doubleValue();
}
}
@@ -351,9 +388,12 @@ public double getDouble(int columnIndex) throws SQLException {
// java.sql.ResultSet definition
} else if (cursor instanceof FieldValueList) {
FieldValue fieldValue = ((FieldValueList) cursor).get(columnIndex);
- return (fieldValue == null || fieldValue.getValue() == null)
- ? 0d
- : fieldValue.getNumericValue().doubleValue();
+ if ((fieldValue == null || fieldValue.getValue() == null)) {
+ wasNull = true;
+ return 0.0d;
+ }
+ wasNull = false;
+ return fieldValue.getNumericValue().doubleValue();
} else { // Data received from Read API (Arrow)
return getDouble(schemaFieldList.get(columnIndex).getName());
}
@@ -368,10 +408,19 @@ public BigDecimal getBigDecimal(String fieldName) throws SQLException {
throw new BigQuerySQLException(NULL_CURSOR_MSG);
} else if (cursor instanceof FieldValueList) {
FieldValue fieldValue = ((FieldValueList) cursor).get(fieldName);
- return (fieldValue == null || fieldValue.getValue() == null)
- ? null
- : BigDecimal.valueOf(fieldValue.getNumericValue().doubleValue());
+ if ((fieldValue == null || fieldValue.getValue() == null)) {
+ wasNull = true;
+ return null;
+ }
+ wasNull = false;
+ return BigDecimal.valueOf(fieldValue.getNumericValue().doubleValue());
} else { // Data received from Read API (Arrow)
+ Object curVal = getCurrentValueForReadApiData(fieldName);
+ if (curVal == null) {
+ wasNull = true;
+ return null;
+ }
+ wasNull = false;
return BigDecimal.valueOf(getDouble(fieldName));
}
}
@@ -382,9 +431,12 @@ public BigDecimal getBigDecimal(int columnIndex) throws SQLException {
throw new BigQuerySQLException(NULL_CURSOR_MSG);
} else if (cursor instanceof FieldValueList) {
FieldValue fieldValue = ((FieldValueList) cursor).get(columnIndex);
- return (fieldValue == null || fieldValue.getValue() == null)
- ? null
- : BigDecimal.valueOf(fieldValue.getNumericValue().doubleValue());
+ if ((fieldValue == null || fieldValue.getValue() == null)) {
+ wasNull = true;
+ return null;
+ }
+ wasNull = false;
+ return BigDecimal.valueOf(fieldValue.getNumericValue().doubleValue());
} else { // Data received from Read API (Arrow)
return getBigDecimal(schemaFieldList.get(columnIndex).getName());
}
@@ -399,14 +451,20 @@ public boolean getBoolean(String fieldName) throws SQLException {
throw new BigQuerySQLException(NULL_CURSOR_MSG);
} else if (cursor instanceof FieldValueList) {
FieldValue fieldValue = ((FieldValueList) cursor).get(fieldName);
- return fieldValue.getValue() != null && fieldValue.getBooleanValue();
+ if ((fieldValue == null || fieldValue.getValue() == null)) {
+ wasNull = true;
+ return false;
+ }
+ wasNull = false;
+ return fieldValue.getBooleanValue();
} else { // Data received from Read API (Arrow)
- Row curRow = (Row) cursor;
- if (!curRow.hasField(fieldName)) {
- throw new SQLException(String.format("Field %s not found", fieldName));
+ Object curVal = getCurrentValueForReadApiData(fieldName);
+ if (curVal == null) {
+ wasNull = true;
+ return false;
}
- Object curVal = curRow.get(fieldName);
- return curVal != null && (Boolean) curVal;
+ wasNull = false;
+ return (Boolean) curVal;
}
}
@@ -416,7 +474,12 @@ public boolean getBoolean(int columnIndex) throws SQLException {
throw new BigQuerySQLException(NULL_CURSOR_MSG);
} else if (cursor instanceof FieldValueList) {
FieldValue fieldValue = ((FieldValueList) cursor).get(columnIndex);
- return fieldValue.getValue() != null && fieldValue.getBooleanValue();
+ if ((fieldValue == null || fieldValue.getValue() == null)) {
+ wasNull = true;
+ return false;
+ }
+ wasNull = false;
+ return fieldValue.getBooleanValue();
} else { // Data received from Read API (Arrow)
return getBoolean(schemaFieldList.get(columnIndex).getName());
}
@@ -431,16 +494,20 @@ public byte[] getBytes(String fieldName) throws SQLException {
throw new BigQuerySQLException(NULL_CURSOR_MSG);
} else if (cursor instanceof FieldValueList) {
FieldValue fieldValue = ((FieldValueList) cursor).get(fieldName);
- return (fieldValue == null || fieldValue.getValue() == null)
- ? null
- : fieldValue.getBytesValue();
+ if ((fieldValue == null || fieldValue.getValue() == null)) {
+ wasNull = true;
+ return null;
+ }
+ wasNull = false;
+ return fieldValue.getBytesValue();
} else { // Data received from Read API (Arrow)
- Row curRow = (Row) cursor;
- if (!curRow.hasField(fieldName)) {
- throw new SQLException(String.format("Field %s not found", fieldName));
+ Object curVal = getCurrentValueForReadApiData(fieldName);
+ if (curVal == null) {
+ wasNull = true;
+ return null;
}
- Object curVal = curRow.get(fieldName);
- return curVal == null ? null : (byte[]) curVal;
+ wasNull = false;
+ return (byte[]) curVal;
}
}
@@ -450,9 +517,12 @@ public byte[] getBytes(int columnIndex) throws SQLException {
return null; // if the value is SQL NULL, the value returned is null
} else if (cursor instanceof FieldValueList) {
FieldValue fieldValue = ((FieldValueList) cursor).get(columnIndex);
- return (fieldValue == null || fieldValue.getValue() == null)
- ? null
- : fieldValue.getBytesValue();
+ if ((fieldValue == null || fieldValue.getValue() == null)) {
+ wasNull = true;
+ return null;
+ }
+ wasNull = false;
+ return fieldValue.getBytesValue();
} else { // Data received from Read API (Arrow)
return getBytes(schemaFieldList.get(columnIndex).getName());
}
@@ -467,21 +537,23 @@ public Timestamp getTimestamp(String fieldName) throws SQLException {
return null; // if the value is SQL NULL, the value returned is null
} else if (cursor instanceof FieldValueList) {
FieldValue fieldValue = ((FieldValueList) cursor).get(fieldName);
- return (fieldValue == null || fieldValue.getValue() == null)
- ? null
- : new Timestamp(
- fieldValue.getTimestampValue()
- / 1000); // getTimestampValue returns time in microseconds, and TimeStamp
- // expects it in millis
+ if ((fieldValue == null || fieldValue.getValue() == null)) {
+ wasNull = true;
+ return null;
+ }
+ wasNull = false;
+ return new Timestamp(
+ fieldValue.getTimestampValue()
+ / 1000); // getTimestampValue returns time in microseconds, and TimeStamp expects it
+ // in millis
} else {
- Row curRow = (Row) cursor;
- if (!curRow.hasField(fieldName)) {
- throw new SQLException(String.format("Field %s not found", fieldName));
+ Object curVal = getCurrentValueForReadApiData(fieldName);
+ if (curVal == null) {
+ wasNull = true;
+ return null;
}
- Object timeStampVal = curRow.get(fieldName);
- return timeStampVal == null
- ? null
- : new Timestamp((Long) timeStampVal / 1000); // Timestamp is represented as a Long
+ wasNull = false;
+ return new Timestamp((Long) curVal / 1000); // Timestamp is represented as a Long
}
}
@@ -491,12 +563,15 @@ public Timestamp getTimestamp(int columnIndex) throws SQLException {
throw new BigQuerySQLException(NULL_CURSOR_MSG);
} else if (cursor instanceof FieldValueList) {
FieldValue fieldValue = ((FieldValueList) cursor).get(columnIndex);
- return (fieldValue == null || fieldValue.getValue() == null)
- ? null
- : new Timestamp(
- fieldValue.getTimestampValue()
- / 1000); // getTimestampValue returns time in microseconds, and TimeStamp
- // expects it in millis
+ if ((fieldValue == null || fieldValue.getValue() == null)) {
+ wasNull = true;
+ return null;
+ }
+ wasNull = false;
+ return new Timestamp(
+ fieldValue.getTimestampValue()
+ / 1000); // getTimestampValue returns time in microseconds, and TimeStamp expects it
+ // in millis
} else { // Data received from Read API (Arrow)
return getTimestamp(schemaFieldList.get(columnIndex).getName());
}
@@ -511,61 +586,62 @@ public Time getTime(String fieldName) throws SQLException {
return null; // if the value is SQL NULL, the value returned is null
} else if (cursor instanceof FieldValueList) {
FieldValue fieldValue = ((FieldValueList) cursor).get(fieldName);
+ if ((fieldValue == null || fieldValue.getValue() == null)) {
+ wasNull = true;
+ return null;
+ }
+ wasNull = false;
return getTimeFromFieldVal(fieldValue);
} else { // Data received from Read API (Arrow)
- Row curRow = (Row) cursor;
- if (!curRow.hasField(fieldName)) {
- throw new SQLException(String.format("Field %s not found", fieldName));
+ Object curVal = getCurrentValueForReadApiData(fieldName);
+ if (curVal == null) {
+ wasNull = true;
+ return null;
}
- Object timeStampObj = curRow.get(fieldName);
- return timeStampObj == null
- ? null
- : new Time(
- ((Long) timeStampObj)
- / 1000); // Time.toString() will return 12:11:35 in GMT as 17:41:35 in
- // (GMT+5:30). This can be offset using getTimeZoneOffset
+ wasNull = false;
+ return new Time(
+ ((Long) curVal)
+ / 1000); // Time.toString() will return 12:11:35 in GMT as 17:41:35 in (GMT+5:30).
+ // This can be offset using getTimeZoneOffset
}
}
- private int getTimeZoneOffset() {
- TimeZone timeZone = TimeZone.getTimeZone(ZoneId.systemDefault());
- return timeZone.getOffset(new java.util.Date().getTime()); // offset in seconds
- }
-
@Override
public Time getTime(int columnIndex) throws SQLException {
if (cursor == null) {
throw new BigQuerySQLException(NULL_CURSOR_MSG);
} else if (cursor instanceof FieldValueList) {
FieldValue fieldValue = ((FieldValueList) cursor).get(columnIndex);
+ if ((fieldValue == null || fieldValue.getValue() == null)) {
+ wasNull = true;
+ return null;
+ }
+ wasNull = false;
return getTimeFromFieldVal(fieldValue);
} else { // Data received from Read API (Arrow)
return getTime(schemaFieldList.get(columnIndex).getName());
}
}
+ // Expects fieldValue.getValue() != null.
private Time getTimeFromFieldVal(FieldValue fieldValue) throws SQLException {
- if (fieldValue.getValue() != null) {
- // Time ranges from 00:00:00 to 23:59:59.99999. in BigQuery. Parsing it to java.sql.Time
- String strTime = fieldValue.getStringValue();
- String[] timeSplt = strTime.split(":");
- if (timeSplt.length != 3) {
- throw new SQLException("Can not parse the value " + strTime + " to java.sql.Time");
- }
- int hr = Integer.parseInt(timeSplt[0]);
- int min = Integer.parseInt(timeSplt[1]);
- int sec = 0, nanoSec = 0;
- if (timeSplt[2].contains(".")) {
- String[] secSplt = timeSplt[2].split("\\.");
- sec = Integer.parseInt(secSplt[0]);
- nanoSec = Integer.parseInt(secSplt[1]);
- } else {
- sec = Integer.parseInt(timeSplt[2]);
- }
- return Time.valueOf(LocalTime.of(hr, min, sec, nanoSec));
+ // Time ranges from 00:00:00 to 23:59:59.99999. in BigQuery. Parsing it to java.sql.Time
+ String strTime = fieldValue.getStringValue();
+ String[] timeSplt = strTime.split(":");
+ if (timeSplt.length != 3) {
+ throw new SQLException("Can not parse the value " + strTime + " to java.sql.Time");
+ }
+ int hr = Integer.parseInt(timeSplt[0]);
+ int min = Integer.parseInt(timeSplt[1]);
+ int sec, nanoSec = 0;
+ if (timeSplt[2].contains(".")) {
+ String[] secSplt = timeSplt[2].split("\\.");
+ sec = Integer.parseInt(secSplt[0]);
+ nanoSec = Integer.parseInt(secSplt[1]);
} else {
- return null;
+ sec = Integer.parseInt(timeSplt[2]);
}
+ return Time.valueOf(LocalTime.of(hr, min, sec, nanoSec));
}
@Override
@@ -577,26 +653,26 @@ public Date getDate(String fieldName) throws SQLException {
throw new BigQuerySQLException(NULL_CURSOR_MSG);
} else if (cursor instanceof FieldValueList) {
FieldValue fieldValue = ((FieldValueList) cursor).get(fieldName);
- return (fieldValue == null || fieldValue.getValue() == null)
- ? null
- : Date.valueOf(fieldValue.getStringValue());
- } else { // Data received from Read API (Arrow)
- Row curRow = (Row) cursor;
- if (!curRow.hasField(fieldName)) {
- throw new SQLException(String.format("Field %s not found", fieldName));
+ if ((fieldValue == null || fieldValue.getValue() == null)) {
+ wasNull = true;
+ return null;
}
- Object dateObj = curRow.get(fieldName);
- if (dateObj == null) {
+ wasNull = false;
+ return Date.valueOf(fieldValue.getStringValue());
+ } else { // Data received from Read API (Arrow)
+ Object curVal = getCurrentValueForReadApiData(fieldName);
+ if (curVal == null) {
+ wasNull = true;
return null;
- } else {
- Integer dateInt = (Integer) dateObj;
- long dateInMillis =
- TimeUnit.DAYS.toMillis(
- Long.valueOf(
- dateInt)); // For example int 18993 represents 2022-01-01, converting time to
- // milli seconds
- return new Date(dateInMillis);
}
+ wasNull = false;
+ Integer dateInt = (Integer) curVal;
+ long dateInMillis =
+ TimeUnit.DAYS.toMillis(
+ Long.valueOf(
+ dateInt)); // For example int 18993 represents 2022-01-01, converting time to
+ // milli seconds
+ return new Date(dateInMillis);
}
}
@@ -606,13 +682,26 @@ public Date getDate(int columnIndex) throws SQLException {
throw new BigQuerySQLException(NULL_CURSOR_MSG);
} else if (cursor instanceof FieldValueList) {
FieldValue fieldValue = ((FieldValueList) cursor).get(columnIndex);
- return (fieldValue == null || fieldValue.getValue() == null)
- ? null
- : Date.valueOf(fieldValue.getStringValue());
+ if ((fieldValue == null || fieldValue.getValue() == null)) {
+ wasNull = true;
+ return null;
+ }
+ wasNull = false;
+ return Date.valueOf(fieldValue.getStringValue());
} else { // Data received from Read API (Arrow)
return getDate(schemaFieldList.get(columnIndex).getName());
}
}
+
+ /**
+ * Returns whether the last column read had a value of SQL NULL. Note that you must first call
+ * one of the getter methods on a column to try to read its value and then call the method
+ * wasNull to see if the value read was SQL NULL. *
+ */
+ @Override
+ public boolean wasNull() {
+ return wasNull;
+ }
}
@Override
diff --git a/google-cloud-bigquery/src/test/java/com/google/cloud/bigquery/BigQueryResultImplTest.java b/google-cloud-bigquery/src/test/java/com/google/cloud/bigquery/BigQueryResultImplTest.java
new file mode 100644
index 000000000..6431673e3
--- /dev/null
+++ b/google-cloud-bigquery/src/test/java/com/google/cloud/bigquery/BigQueryResultImplTest.java
@@ -0,0 +1,257 @@
+/*
+ * Copyright 2025 Google LLC
+ *
+ * Licensed 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 com.google.cloud.bigquery;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import com.google.cloud.bigquery.ConnectionImpl.EndOfFieldValueList;
+import com.google.cloud.bigquery.FieldValue.Attribute;
+import com.google.common.collect.ImmutableList;
+import com.google.common.io.BaseEncoding;
+import java.math.BigDecimal;
+import java.sql.Date;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.sql.Time;
+import java.sql.Timestamp;
+import java.time.LocalTime;
+import java.util.AbstractList;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.LinkedBlockingDeque;
+import org.apache.arrow.vector.util.Text;
+import org.junit.Test;
+
+public class BigQueryResultImplTest {
+
+ private static final Schema SCHEMA =
+ Schema.of(
+ Field.newBuilder("boolean", StandardSQLTypeName.BOOL)
+ .setMode(Field.Mode.NULLABLE)
+ .build(),
+ Field.newBuilder("long", StandardSQLTypeName.NUMERIC)
+ .setMode(Field.Mode.NULLABLE)
+ .build(),
+ Field.newBuilder("double", StandardSQLTypeName.NUMERIC)
+ .setMode(Field.Mode.NULLABLE)
+ .build(),
+ Field.newBuilder("string", StandardSQLTypeName.STRING)
+ .setMode(Field.Mode.NULLABLE)
+ .build(),
+ Field.newBuilder("bytes", StandardSQLTypeName.BYTES).setMode(Field.Mode.NULLABLE).build(),
+ Field.newBuilder("timestamp", StandardSQLTypeName.TIMESTAMP)
+ .setMode(Field.Mode.NULLABLE)
+ .build(),
+ Field.newBuilder("time", StandardSQLTypeName.TIME).setMode(Field.Mode.NULLABLE).build(),
+ Field.newBuilder("date", StandardSQLTypeName.DATE).setMode(Field.Mode.NULLABLE).build());
+
+ private static final FieldList FIELD_LIST_SCHEMA =
+ FieldList.of(
+ Field.of("boolean", LegacySQLTypeName.BOOLEAN),
+ Field.of("long", LegacySQLTypeName.INTEGER),
+ Field.of("double", LegacySQLTypeName.FLOAT),
+ Field.of("string", LegacySQLTypeName.STRING),
+ Field.of("bytes", LegacySQLTypeName.BYTES),
+ Field.of("timestamp", LegacySQLTypeName.TIMESTAMP),
+ Field.of("time", LegacySQLTypeName.TIME),
+ Field.of("date", LegacySQLTypeName.DATE));
+
+ private static final byte[] BYTES = {0xD, 0xE, 0xA, 0xD};
+ private static final String BYTES_BASE64 = BaseEncoding.base64().encode(BYTES);
+ private static final Timestamp EXPECTED_TIMESTAMP = Timestamp.valueOf("2025-01-02 03:04:05.0");
+ private static final String TIME = "20:21:22";
+ private static final Time EXPECTED_TIME = Time.valueOf(LocalTime.of(20, 21, 22));
+ private static final String DATE = "2020-01-21";
+ private static final int DATE_INT = 0;
+ private static final Date EXPECTED_DATE = java.sql.Date.valueOf(DATE);
+ private static final int BUFFER_SIZE = 10;
+
+ @Test
+ public void testResultSetFieldValueList() throws InterruptedException, SQLException {
+ BlockingQueue> buffer = new LinkedBlockingDeque<>(BUFFER_SIZE);
+ FieldValueList fieldValues =
+ FieldValueList.of(
+ ImmutableList.of(
+ FieldValue.of(Attribute.PRIMITIVE, "false"),
+ FieldValue.of(Attribute.PRIMITIVE, "1"),
+ FieldValue.of(Attribute.PRIMITIVE, "1.5"),
+ FieldValue.of(Attribute.PRIMITIVE, "string_value"),
+ FieldValue.of(Attribute.PRIMITIVE, BYTES_BASE64),
+ FieldValue.of(
+ Attribute.PRIMITIVE,
+ Long.toString(EXPECTED_TIMESTAMP.getTime() / 1000),
+ false), // getTime is in milliseconds.
+ FieldValue.of(Attribute.PRIMITIVE, TIME),
+ FieldValue.of(Attribute.PRIMITIVE, DATE)),
+ FIELD_LIST_SCHEMA);
+ buffer.put(fieldValues);
+
+ FieldValueList nullValues =
+ FieldValueList.of(
+ ImmutableList.of(
+ FieldValue.of(Attribute.PRIMITIVE, null),
+ FieldValue.of(Attribute.PRIMITIVE, null),
+ FieldValue.of(Attribute.PRIMITIVE, null),
+ FieldValue.of(Attribute.PRIMITIVE, null),
+ FieldValue.of(Attribute.PRIMITIVE, null),
+ FieldValue.of(Attribute.PRIMITIVE, null),
+ FieldValue.of(Attribute.PRIMITIVE, null),
+ FieldValue.of(Attribute.PRIMITIVE, null)),
+ FIELD_LIST_SCHEMA);
+ buffer.put(nullValues);
+
+ buffer.put(new EndOfFieldValueList()); // End of buffer marker.
+
+ BigQueryResultImpl> bigQueryResult =
+ new BigQueryResultImpl<>(SCHEMA, 1, buffer, null);
+ ResultSet resultSet = bigQueryResult.getResultSet();
+ assertThat(resultSet.next()).isTrue();
+ assertThat(resultSet.getObject("string")).isEqualTo("string_value");
+ assertThat(resultSet.wasNull()).isFalse();
+ assertThat(resultSet.getString("string")).isEqualTo("string_value");
+ assertThat(resultSet.wasNull()).isFalse();
+ assertThat(resultSet.getInt("long")).isEqualTo(1);
+ assertThat(resultSet.wasNull()).isFalse();
+ assertThat(resultSet.getLong("long")).isEqualTo(1);
+ assertThat(resultSet.wasNull()).isFalse();
+ assertThat(resultSet.getDouble("double")).isEqualTo(1.5);
+ assertThat(resultSet.wasNull()).isFalse();
+ assertThat(resultSet.getBigDecimal("double")).isEqualTo(BigDecimal.valueOf(1.5));
+ assertThat(resultSet.wasNull()).isFalse();
+ assertThat(resultSet.getBoolean("boolean")).isFalse();
+ assertThat(resultSet.wasNull()).isFalse();
+ assertThat(resultSet.getBytes("bytes")).isEqualTo(BYTES);
+ assertThat(resultSet.wasNull()).isFalse();
+ assertThat(resultSet.getTimestamp("timestamp")).isEqualTo(EXPECTED_TIMESTAMP);
+ assertThat(resultSet.wasNull()).isFalse();
+ assertThat(resultSet.getTime("time").getTime()).isEqualTo(EXPECTED_TIME.getTime());
+ assertThat(resultSet.wasNull()).isFalse();
+ assertThat(resultSet.getDate("date").getTime()).isEqualTo(EXPECTED_DATE.getTime());
+ assertThat(resultSet.wasNull()).isFalse();
+
+ assertThat(resultSet.next()).isTrue();
+ assertThat(resultSet.getObject("string")).isNull();
+ assertThat(resultSet.wasNull()).isTrue();
+ assertThat(resultSet.getString("string")).isNull();
+ assertThat(resultSet.wasNull()).isTrue();
+ assertThat(resultSet.getInt("long")).isEqualTo(0);
+ assertThat(resultSet.wasNull()).isTrue();
+ assertThat(resultSet.getLong("long")).isEqualTo(0);
+ assertThat(resultSet.wasNull()).isTrue();
+ assertThat(resultSet.getDouble("double")).isEqualTo(0.0);
+ assertThat(resultSet.wasNull()).isTrue();
+ assertThat(resultSet.getBigDecimal("double")).isNull();
+ assertThat(resultSet.wasNull()).isTrue();
+ assertThat(resultSet.getBoolean("boolean")).isFalse();
+ assertThat(resultSet.wasNull()).isTrue();
+ assertThat(resultSet.getBytes("bytes")).isNull();
+ assertThat(resultSet.wasNull()).isTrue();
+ assertThat(resultSet.getTimestamp("timestamp")).isNull();
+ assertThat(resultSet.wasNull()).isTrue();
+ assertThat(resultSet.getTime("time")).isNull();
+ assertThat(resultSet.wasNull()).isTrue();
+ assertThat(resultSet.getDate("date")).isNull();
+ assertThat(resultSet.wasNull()).isTrue();
+
+ assertThat(resultSet.next()).isFalse();
+ }
+
+ @Test
+ public void testResultSetReadApi() throws InterruptedException, SQLException {
+ BlockingQueue buffer = new LinkedBlockingDeque<>(BUFFER_SIZE);
+
+ Map rowValues = new HashMap<>();
+ rowValues.put("boolean", false);
+ rowValues.put("long", 1L);
+ rowValues.put("double", 1.5);
+ rowValues.put("string", new Text("string_value"));
+ rowValues.put("bytes", BYTES);
+ rowValues.put("timestamp", EXPECTED_TIMESTAMP.getTime() * 1000);
+ rowValues.put("time", EXPECTED_TIME.getTime() * 1000);
+ rowValues.put("date", DATE_INT);
+ buffer.put(new BigQueryResultImpl.Row(rowValues));
+
+ Map nullValues = new HashMap<>();
+ nullValues.put("boolean", null);
+ nullValues.put("long", null);
+ nullValues.put("double", null);
+ nullValues.put("string", null);
+ nullValues.put("bytes", null);
+ nullValues.put("timestamp", null);
+ nullValues.put("time", null);
+ nullValues.put("date", null);
+ buffer.put(new BigQueryResultImpl.Row(nullValues));
+
+ buffer.put(new BigQueryResultImpl.Row(null, true)); // End of buffer marker.
+
+ BigQueryResultImpl bigQueryResult =
+ new BigQueryResultImpl<>(SCHEMA, 1, buffer, null);
+ ResultSet resultSet = bigQueryResult.getResultSet();
+ assertThat(resultSet.next()).isTrue();
+ assertThat(resultSet.getObject("string")).isEqualTo(new Text("string_value"));
+ assertThat(resultSet.wasNull()).isFalse();
+ assertThat(resultSet.getString("string")).isEqualTo("string_value");
+ assertThat(resultSet.wasNull()).isFalse();
+ assertThat(resultSet.getInt("long")).isEqualTo(1);
+ assertThat(resultSet.wasNull()).isFalse();
+ assertThat(resultSet.getLong("long")).isEqualTo(1);
+ assertThat(resultSet.wasNull()).isFalse();
+ assertThat(resultSet.getDouble("double")).isEqualTo(1.5);
+ assertThat(resultSet.wasNull()).isFalse();
+ assertThat(resultSet.getBigDecimal("double")).isEqualTo(BigDecimal.valueOf(1.5));
+ assertThat(resultSet.wasNull()).isFalse();
+ assertThat(resultSet.getBoolean("boolean")).isFalse();
+ assertThat(resultSet.wasNull()).isFalse();
+ assertThat(resultSet.getBytes("bytes")).isEqualTo(BYTES);
+ assertThat(resultSet.wasNull()).isFalse();
+ assertThat(resultSet.getTimestamp("timestamp")).isEqualTo(EXPECTED_TIMESTAMP);
+ assertThat(resultSet.wasNull()).isFalse();
+ assertThat(resultSet.getTime("time").getTime()).isEqualTo(EXPECTED_TIME.getTime());
+ assertThat(resultSet.wasNull()).isFalse();
+ // Do not check date value as Date object do not have timezone but its toString() applies the
+ // JVM default timezone which causes flakes in non-UTC zones.
+ assertThat(resultSet.getDate("date")).isNotNull();
+ assertThat(resultSet.wasNull()).isFalse();
+
+ assertThat(resultSet.next()).isTrue();
+ assertThat(resultSet.getObject("string")).isNull();
+ assertThat(resultSet.wasNull()).isTrue();
+ assertThat(resultSet.getString("string")).isNull();
+ assertThat(resultSet.wasNull()).isTrue();
+ assertThat(resultSet.getInt("long")).isEqualTo(0);
+ assertThat(resultSet.wasNull()).isTrue();
+ assertThat(resultSet.getLong("long")).isEqualTo(0);
+ assertThat(resultSet.wasNull()).isTrue();
+ assertThat(resultSet.getDouble("double")).isEqualTo(0.0);
+ assertThat(resultSet.wasNull()).isTrue();
+ assertThat(resultSet.getBigDecimal("double")).isNull();
+ assertThat(resultSet.wasNull()).isTrue();
+ assertThat(resultSet.getBoolean("boolean")).isFalse();
+ assertThat(resultSet.wasNull()).isTrue();
+ assertThat(resultSet.getBytes("bytes")).isNull();
+ assertThat(resultSet.wasNull()).isTrue();
+ assertThat(resultSet.getTimestamp("timestamp")).isNull();
+ assertThat(resultSet.wasNull()).isTrue();
+ assertThat(resultSet.getTime("time")).isNull();
+ assertThat(resultSet.wasNull()).isTrue();
+ assertThat(resultSet.getDate("date")).isNull();
+ assertThat(resultSet.wasNull()).isTrue();
+
+ assertThat(resultSet.next()).isFalse();
+ }
+}
diff --git a/pom.xml b/pom.xml
index d6826d566..12b44c359 100644
--- a/pom.xml
+++ b/pom.xml
@@ -4,7 +4,7 @@
com.google.cloud
google-cloud-bigquery-parent
pom
- 2.47.0
+ 2.48.0
BigQuery Parent
https://github.com/googleapis/java-bigquery
@@ -14,7 +14,7 @@
com.google.cloud
sdk-platform-java-config
- 3.42.0
+ 3.43.0
@@ -54,7 +54,7 @@
UTF-8
github
google-cloud-bigquery-parent
- v2-rev20250112-2.0.0
+ v2-rev20250128-2.0.0