Skip to content

Commit e2a3f2c

Browse files
feat(bigquery): Implement getArray in BigQueryResultImpl (#3693)
* Implement getArray in BigQueryResultImpl * deps: update dependency com.google.apis:google-api-services-bigquery to v2-rev20250216-2.0.0 (#3688) * build(deps): update dependency org.apache.maven.plugins:maven-compiler-plugin to v3.14.0 (#3689) * deps: update ossf/scorecard-action action to v2.4.1 (#3690) * deps: update actions/upload-artifact action to v4.6.1 (#3691) * Remove public from BigQueryResultSet class def * Remove INTEGER_ARRAY_FIELD_LIST from IT test file * Add ReadApi IT test * Try old IT test values * Convert Time objects to strings for comparison * Fix Formatting --------- Co-authored-by: Mend Renovate <[email protected]>
1 parent 43b86e9 commit e2a3f2c

File tree

3 files changed

+249
-4
lines changed

3 files changed

+249
-4
lines changed

google-cloud-bigquery/src/main/java/com/google/cloud/bigquery/BigQueryResultImpl.java

+124
Original file line numberDiff line numberDiff line change
@@ -16,14 +16,19 @@
1616

1717
package com.google.cloud.bigquery;
1818

19+
import com.google.cloud.bigquery.FieldValue.Attribute;
1920
import java.math.BigDecimal;
2021
import java.sql.Date;
2122
import java.sql.ResultSet;
2223
import java.sql.SQLException;
24+
import java.sql.SQLFeatureNotSupportedException;
2325
import java.sql.Time;
2426
import java.sql.Timestamp;
2527
import java.time.LocalDateTime;
2628
import java.time.LocalTime;
29+
import java.util.ArrayList;
30+
import java.util.Arrays;
31+
import java.util.List;
2732
import java.util.Map;
2833
import java.util.concurrent.BlockingQueue;
2934
import java.util.concurrent.TimeUnit;
@@ -114,6 +119,77 @@ private class BigQueryResultSet extends AbstractJdbcResultSet {
114119
// curTup.isLast(). Ref: https://github.com/googleapis/java-bigquery/issues/2033
115120
private boolean wasNull = false;
116121

122+
private class BigQueryArrayResult implements java.sql.Array {
123+
List<Object> array;
124+
125+
public BigQueryArrayResult(Object array) {
126+
if (array instanceof Object[]) {
127+
this.array = new ArrayList<>(Arrays.asList((Object[]) array));
128+
} else if (array instanceof FieldValueList) {
129+
this.array = new ArrayList<>((FieldValueList) array);
130+
} else {
131+
this.array = (List<Object>) array;
132+
}
133+
}
134+
135+
@Override
136+
public String getBaseTypeName() throws SQLException {
137+
throw new SQLFeatureNotSupportedException();
138+
}
139+
140+
@Override
141+
public int getBaseType() throws SQLException {
142+
throw new SQLFeatureNotSupportedException();
143+
}
144+
145+
@Override
146+
public Object getArray() throws SQLException {
147+
return array;
148+
}
149+
150+
@Override
151+
public Object getArray(java.util.Map<String, Class<?>> map) throws SQLException {
152+
throw new SQLFeatureNotSupportedException();
153+
}
154+
155+
@Override
156+
public Object getArray(long index, int count) throws SQLException {
157+
throw new SQLFeatureNotSupportedException();
158+
}
159+
160+
@Override
161+
public Object getArray(long index, int count, java.util.Map<String, Class<?>> map)
162+
throws SQLException {
163+
throw new SQLFeatureNotSupportedException();
164+
}
165+
166+
@Override
167+
public ResultSet getResultSet() throws SQLException {
168+
throw new SQLFeatureNotSupportedException();
169+
}
170+
171+
@Override
172+
public ResultSet getResultSet(java.util.Map<String, Class<?>> map) throws SQLException {
173+
throw new SQLFeatureNotSupportedException();
174+
}
175+
176+
@Override
177+
public ResultSet getResultSet(long index, int count) throws SQLException {
178+
throw new SQLFeatureNotSupportedException();
179+
}
180+
181+
@Override
182+
public ResultSet getResultSet(long index, int count, java.util.Map<String, Class<?>> map)
183+
throws SQLException {
184+
throw new SQLFeatureNotSupportedException();
185+
}
186+
187+
@Override
188+
public void free() throws SQLException {
189+
throw new SQLFeatureNotSupportedException();
190+
}
191+
}
192+
117193
@Override
118194
/*Advances the result set to the next row, returning false if no such row exists. Potentially blocking operation*/
119195
public boolean next() throws SQLException {
@@ -160,6 +236,54 @@ private Object getCurrentValueForReadApiData(String fieldName) throws SQLExcepti
160236
return curRow.get(fieldName);
161237
}
162238

239+
@Override
240+
public java.sql.Array getArray(String fieldName) throws SQLException {
241+
if (fieldName == null) {
242+
throw new SQLException("fieldName can't be null");
243+
}
244+
if (cursor == null) {
245+
throw new BigQuerySQLException(NULL_CURSOR_MSG);
246+
} else if (cursor instanceof FieldValueList) {
247+
FieldValue fieldValue = ((FieldValueList) cursor).get(fieldName);
248+
if ((fieldValue == null || fieldValue.getValue() == null)) {
249+
wasNull = true;
250+
return null;
251+
}
252+
wasNull = false;
253+
if (fieldValue.getAttribute().equals(Attribute.REPEATED)) {
254+
return new BigQueryArrayResult(fieldValue.getValue());
255+
} else {
256+
wasNull = true;
257+
return null;
258+
}
259+
} else { // Data received from Read API (Arrow)
260+
Object currentVal = getCurrentValueForReadApiData(fieldName);
261+
if (currentVal == null) {
262+
wasNull = true;
263+
return null;
264+
}
265+
wasNull = false;
266+
return new BigQueryArrayResult(currentVal);
267+
}
268+
}
269+
270+
@Override
271+
public java.sql.Array getArray(int columnIndex) throws SQLException {
272+
if (cursor == null) {
273+
return null;
274+
} else if (cursor instanceof FieldValueList) {
275+
FieldValue fieldValue = ((FieldValueList) cursor).get(columnIndex);
276+
if (fieldValue == null || fieldValue.getValue() == null) {
277+
wasNull = true;
278+
return null;
279+
}
280+
wasNull = false;
281+
return new BigQueryArrayResult(fieldValue.getValue());
282+
} else {
283+
return getArray(schemaFieldList.get(columnIndex).getName());
284+
}
285+
}
286+
163287
@Override
164288
public Object getObject(String fieldName) throws SQLException {
165289
if (fieldName == null) {

google-cloud-bigquery/src/test/java/com/google/cloud/bigquery/BigQueryResultImplTest.java

+43-4
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,8 @@
3030
import java.sql.Timestamp;
3131
import java.time.LocalTime;
3232
import java.util.AbstractList;
33+
import java.util.ArrayList;
34+
import java.util.Arrays;
3335
import java.util.HashMap;
3436
import java.util.Map;
3537
import java.util.concurrent.BlockingQueue;
@@ -58,7 +60,13 @@ public class BigQueryResultImplTest {
5860
.setMode(Field.Mode.NULLABLE)
5961
.build(),
6062
Field.newBuilder("time", StandardSQLTypeName.TIME).setMode(Field.Mode.NULLABLE).build(),
61-
Field.newBuilder("date", StandardSQLTypeName.DATE).setMode(Field.Mode.NULLABLE).build());
63+
Field.newBuilder("date", StandardSQLTypeName.DATE).setMode(Field.Mode.NULLABLE).build(),
64+
Field.newBuilder("intArray", StandardSQLTypeName.INT64)
65+
.setMode(Field.Mode.REPEATED)
66+
.build(),
67+
Field.newBuilder("stringArray", StandardSQLTypeName.STRING)
68+
.setMode(Field.Mode.REPEATED)
69+
.build());
6270

6371
private static final FieldList FIELD_LIST_SCHEMA =
6472
FieldList.of(
@@ -69,7 +77,9 @@ public class BigQueryResultImplTest {
6977
Field.of("bytes", LegacySQLTypeName.BYTES),
7078
Field.of("timestamp", LegacySQLTypeName.TIMESTAMP),
7179
Field.of("time", LegacySQLTypeName.TIME),
72-
Field.of("date", LegacySQLTypeName.DATE));
80+
Field.of("date", LegacySQLTypeName.DATE),
81+
Field.of("intArray", LegacySQLTypeName.INTEGER),
82+
Field.of("stringArray", LegacySQLTypeName.STRING));
7383

7484
private static final byte[] BYTES = {0xD, 0xE, 0xA, 0xD};
7585
private static final String BYTES_BASE64 = BaseEncoding.base64().encode(BYTES);
@@ -79,6 +89,11 @@ public class BigQueryResultImplTest {
7989
private static final String DATE = "2020-01-21";
8090
private static final int DATE_INT = 0;
8191
private static final Date EXPECTED_DATE = java.sql.Date.valueOf(DATE);
92+
private static final ArrayList<Integer> EXPECTED_INT_ARRAY =
93+
new ArrayList<>(Arrays.asList(0, 1, 2, 3, 4));
94+
private static final String[] STRING_ARRAY = {"str1", "str2", "str3"};
95+
private static final ArrayList<String> EXPECTED_STRING_ARRAY =
96+
new ArrayList<>(Arrays.asList(STRING_ARRAY));
8297
private static final int BUFFER_SIZE = 10;
8398

8499
@Test
@@ -97,7 +112,9 @@ public void testResultSetFieldValueList() throws InterruptedException, SQLExcept
97112
Long.toString(EXPECTED_TIMESTAMP.getTime() / 1000),
98113
false), // getTime is in milliseconds.
99114
FieldValue.of(Attribute.PRIMITIVE, TIME),
100-
FieldValue.of(Attribute.PRIMITIVE, DATE)),
115+
FieldValue.of(Attribute.PRIMITIVE, DATE),
116+
FieldValue.of(Attribute.REPEATED, EXPECTED_INT_ARRAY),
117+
FieldValue.of(Attribute.REPEATED, STRING_ARRAY)),
101118
FIELD_LIST_SCHEMA);
102119
buffer.put(fieldValues);
103120

@@ -111,7 +128,9 @@ public void testResultSetFieldValueList() throws InterruptedException, SQLExcept
111128
FieldValue.of(Attribute.PRIMITIVE, null),
112129
FieldValue.of(Attribute.PRIMITIVE, null),
113130
FieldValue.of(Attribute.PRIMITIVE, null),
114-
FieldValue.of(Attribute.PRIMITIVE, null)),
131+
FieldValue.of(Attribute.PRIMITIVE, null),
132+
FieldValue.of(Attribute.REPEATED, null),
133+
FieldValue.of(Attribute.REPEATED, null)),
115134
FIELD_LIST_SCHEMA);
116135
buffer.put(nullValues);
117136

@@ -143,6 +162,10 @@ public void testResultSetFieldValueList() throws InterruptedException, SQLExcept
143162
assertThat(resultSet.wasNull()).isFalse();
144163
assertThat(resultSet.getDate("date").getTime()).isEqualTo(EXPECTED_DATE.getTime());
145164
assertThat(resultSet.wasNull()).isFalse();
165+
assertThat(resultSet.getArray("intArray").getArray()).isEqualTo(EXPECTED_INT_ARRAY);
166+
assertThat(resultSet.wasNull()).isFalse();
167+
assertThat(resultSet.getArray("stringArray").getArray()).isEqualTo(EXPECTED_STRING_ARRAY);
168+
assertThat(resultSet.wasNull()).isFalse();
146169

147170
assertThat(resultSet.next()).isTrue();
148171
assertThat(resultSet.getObject("string")).isNull();
@@ -167,6 +190,10 @@ public void testResultSetFieldValueList() throws InterruptedException, SQLExcept
167190
assertThat(resultSet.wasNull()).isTrue();
168191
assertThat(resultSet.getDate("date")).isNull();
169192
assertThat(resultSet.wasNull()).isTrue();
193+
assertThat(resultSet.getArray("intArray")).isNull();
194+
assertThat(resultSet.wasNull()).isTrue();
195+
assertThat(resultSet.getArray("stringArray")).isNull();
196+
assertThat(resultSet.wasNull()).isTrue();
170197

171198
assertThat(resultSet.next()).isFalse();
172199
}
@@ -184,6 +211,8 @@ public void testResultSetReadApi() throws InterruptedException, SQLException {
184211
rowValues.put("timestamp", EXPECTED_TIMESTAMP.getTime() * 1000);
185212
rowValues.put("time", EXPECTED_TIME.getTime() * 1000);
186213
rowValues.put("date", DATE_INT);
214+
rowValues.put("intArray", EXPECTED_INT_ARRAY);
215+
rowValues.put("stringArray", STRING_ARRAY);
187216
buffer.put(new BigQueryResultImpl.Row(rowValues));
188217

189218
Map<String, Object> nullValues = new HashMap<>();
@@ -195,6 +224,8 @@ public void testResultSetReadApi() throws InterruptedException, SQLException {
195224
nullValues.put("timestamp", null);
196225
nullValues.put("time", null);
197226
nullValues.put("date", null);
227+
nullValues.put("intArray", null);
228+
nullValues.put("stringArray", null);
198229
buffer.put(new BigQueryResultImpl.Row(nullValues));
199230

200231
buffer.put(new BigQueryResultImpl.Row(null, true)); // End of buffer marker.
@@ -227,6 +258,10 @@ public void testResultSetReadApi() throws InterruptedException, SQLException {
227258
// JVM default timezone which causes flakes in non-UTC zones.
228259
assertThat(resultSet.getDate("date")).isNotNull();
229260
assertThat(resultSet.wasNull()).isFalse();
261+
assertThat(resultSet.getArray("intArray")).isNotNull();
262+
assertThat(resultSet.wasNull()).isFalse();
263+
assertThat(resultSet.getArray("stringArray")).isNotNull();
264+
assertThat(resultSet.wasNull()).isFalse();
230265

231266
assertThat(resultSet.next()).isTrue();
232267
assertThat(resultSet.getObject("string")).isNull();
@@ -251,6 +286,10 @@ public void testResultSetReadApi() throws InterruptedException, SQLException {
251286
assertThat(resultSet.wasNull()).isTrue();
252287
assertThat(resultSet.getDate("date")).isNull();
253288
assertThat(resultSet.wasNull()).isTrue();
289+
assertThat(resultSet.getArray("intArray")).isNull();
290+
assertThat(resultSet.wasNull()).isTrue();
291+
assertThat(resultSet.getArray("stringArray")).isNull();
292+
assertThat(resultSet.wasNull()).isTrue();
254293

255294
assertThat(resultSet.next()).isFalse();
256295
}

google-cloud-bigquery/src/test/java/com/google/cloud/bigquery/it/ITBigQueryTest.java

+82
Original file line numberDiff line numberDiff line change
@@ -3925,6 +3925,75 @@ public void testExecuteSelectSinglePageTableRow() throws SQLException {
39253925
(com.google.cloud.bigquery.FieldValueList) rs.getObject("IntegerArrayField");
39263926
assertEquals(4, integerArrayFieldValue.size()); // Array has 4 elements
39273927
assertEquals(3, (integerArrayFieldValue.get(2).getNumericValue()).intValue());
3928+
List<FieldValue> integerArrayFieldValueList =
3929+
(List<FieldValue>) rs.getArray("IntegerArrayField").getArray();
3930+
assertEquals(4, integerArrayFieldValueList.size());
3931+
assertEquals(3, integerArrayFieldValueList.get(2).getNumericValue().intValue());
3932+
3933+
assertFalse(rs.next()); // no 3rd row in the table
3934+
}
3935+
3936+
@Test
3937+
public void testExecuteSelectSinglePageTableRowWithReadAPI() throws SQLException {
3938+
String query =
3939+
"select StringField, BigNumericField, BooleanField, BytesField, IntegerField, TimestampField, FloatField, "
3940+
+ "NumericField, TimeField, DateField, DateTimeField , GeographyField, RecordField.BytesField, RecordField.BooleanField, IntegerArrayField from "
3941+
+ TABLE_ID_FASTQUERY_BQ_RESULTSET.getTable()
3942+
+ " order by TimestampField";
3943+
ConnectionSettings connectionSettings =
3944+
ConnectionSettings.newBuilder()
3945+
.setDefaultDataset(DatasetId.of(DATASET))
3946+
.setUseReadAPI(true)
3947+
.setMinResultSize(1)
3948+
.setTotalToPageRowCountRatio(1)
3949+
.build();
3950+
Connection connection = bigquery.createConnection(connectionSettings);
3951+
BigQueryResult bigQueryResult = connection.executeSelect(query);
3952+
assertTrue(bigQueryResult.getBigQueryResultStats().getQueryStatistics().getUseReadApi());
3953+
ResultSet rs = bigQueryResult.getResultSet();
3954+
Schema sc = bigQueryResult.getSchema();
3955+
3956+
assertEquals(BQ_RESULTSET_EXPECTED_SCHEMA, sc); // match the schema
3957+
assertEquals(2, bigQueryResult.getTotalRows()); // Expecting 2 rows
3958+
3959+
assertTrue(rs.next()); // first row
3960+
// checking for the null or 0 column values
3961+
assertNull(rs.getString("StringField"));
3962+
assertTrue(rs.getDouble("BigNumericField") == 0.0d);
3963+
assertFalse(rs.getBoolean("BooleanField"));
3964+
assertNull(rs.getBytes("BytesField"));
3965+
assertEquals(rs.getInt("IntegerField"), 0);
3966+
assertNull(rs.getTimestamp("TimestampField"));
3967+
assertNull(rs.getDate("DateField"));
3968+
assertTrue(rs.getDouble("FloatField") == 0.0d);
3969+
assertTrue(rs.getDouble("NumericField") == 0.0d);
3970+
assertNull(rs.getTime("TimeField"));
3971+
assertNull(rs.getString("DateTimeField"));
3972+
assertNull(rs.getString("GeographyField"));
3973+
assertNull(rs.getBytes("BytesField_1"));
3974+
assertFalse(rs.getBoolean("BooleanField_1"));
3975+
3976+
assertTrue(rs.next()); // second row
3977+
// second row is non null, comparing the values
3978+
assertEquals("StringValue1", rs.getString("StringField"));
3979+
assertTrue(rs.getDouble("BigNumericField") == 0.3333333333333333d);
3980+
assertFalse(rs.getBoolean("BooleanField"));
3981+
assertNotNull(rs.getBytes("BytesField"));
3982+
assertEquals(1, rs.getInt("IntegerField"));
3983+
assertEquals(1534680695123L, rs.getTimestamp("TimestampField").getTime());
3984+
assertEquals(java.sql.Date.valueOf("2018-08-19"), rs.getDate("DateField"));
3985+
assertTrue(rs.getDouble("FloatField") == 10.1d);
3986+
assertTrue(rs.getDouble("NumericField") == 100.0d);
3987+
assertEquals(
3988+
Time.valueOf(LocalTime.of(12, 11, 35, 123456)).toString(),
3989+
rs.getTime("TimeField").toString());
3990+
assertEquals("2018-08-19T12:11:35.123456", rs.getString("DateTimeField"));
3991+
assertEquals("POINT(-122.35022 47.649154)", rs.getString("GeographyField"));
3992+
assertNotNull(rs.getBytes("BytesField_1"));
3993+
assertTrue(rs.getBoolean("BooleanField_1"));
3994+
List<BigDecimal> integerArray = (List<BigDecimal>) rs.getArray("IntegerArrayField").getArray();
3995+
assertEquals(4, integerArray.size());
3996+
assertEquals(3, integerArray.get(2).intValue());
39283997

39293998
assertFalse(rs.next()); // no 3rd row in the table
39303999
}
@@ -4278,6 +4347,19 @@ public void testExecuteSelectSinglePageTableRowColInd() throws SQLException {
42784347
(integerArrayFieldValue.get(2).getNumericValue()).intValue(),
42794348
(integerArrayFieldValueColInd.get(2).getNumericValue()).intValue());
42804349
}
4350+
4351+
List<FieldValue> integerArrayFieldValueList =
4352+
(List<FieldValue>) rs.getArray("IntegerArrayField").getArray();
4353+
List<FieldValue> integerArrayFieldValueListColInd =
4354+
(List<FieldValue>) rs.getArray(14).getArray();
4355+
assertEquals(
4356+
integerArrayFieldValueList.size(),
4357+
integerArrayFieldValueListColInd.size()); // Array has 4 elements
4358+
if (integerArrayFieldValueList.size() == 4) { // as we are picking the third index
4359+
assertEquals(
4360+
(integerArrayFieldValueList.get(2).getNumericValue()).intValue(),
4361+
(integerArrayFieldValueListColInd.get(2).getNumericValue()).intValue());
4362+
}
42814363
}
42824364
}
42834365

0 commit comments

Comments
 (0)