Skip to content

Commit be77a7d

Browse files
committed
feat(bigquery): accelerate row-based query() with Arrow wire format
1 parent 21d547f commit be77a7d

2 files changed

Lines changed: 421 additions & 31 deletions

File tree

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

Lines changed: 84 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@
6767
import com.google.common.collect.Lists;
6868
import com.google.common.collect.Maps;
6969
import com.google.common.net.HostAndPort;
70+
import com.google.common.primitives.Longs;
7071
import io.grpc.ManagedChannelBuilder;
7172
import io.opentelemetry.api.common.Attributes;
7273
import io.opentelemetry.api.trace.Span;
@@ -75,6 +76,7 @@
7576
import java.net.URI;
7677
import java.util.ArrayDeque;
7778
import java.util.ArrayList;
79+
import java.util.Collection;
7880
import java.util.Collections;
7981
import java.util.Iterator;
8082
import java.util.List;
@@ -2110,7 +2112,7 @@ public TableDataList call() throws IOException {
21102112
}
21112113
}
21122114

2113-
private static Iterable<FieldValueList> transformTableData(
2115+
private static List<FieldValueList> transformTableData(
21142116
Iterable<TableRow> tableDataPb, final Schema schema, boolean useInt64Timestamps) {
21152117
return ImmutableList.copyOf(
21162118
Iterables.transform(
@@ -2349,8 +2351,21 @@ public com.google.api.services.bigquery.model.QueryResponse call()
23492351

23502352
long numRows;
23512353
Schema schema;
2352-
if (results.getJobComplete() && results.getSchema() != null) {
2353-
schema = Schema.fromPb(results.getSchema());
2354+
boolean isArrow = results.getArrowSchema() != null;
2355+
org.apache.arrow.vector.types.pojo.Schema arrowSchemaPojo = null;
2356+
byte[] arrowSchemaBytes = null;
2357+
if (results.getJobComplete() && (results.getSchema() != null || isArrow)) {
2358+
if (isArrow) {
2359+
arrowSchemaBytes = results.getArrowSchema().decodeSerializedSchema();
2360+
try {
2361+
arrowSchemaPojo = ArrowDeserializer.deserializeSchema(arrowSchemaBytes);
2362+
} catch (IOException e) {
2363+
throw new BigQueryException(0, "Failed to deserialize Arrow schema from response", e);
2364+
}
2365+
schema = ArrowPojoUtils.arrowSchemaToBigQuerySchema(arrowSchemaPojo);
2366+
} else {
2367+
schema = Schema.fromPb(results.getSchema());
2368+
}
23542369
if (results.getNumDmlAffectedRows() == null && results.getTotalRows() == null) {
23552370
numRows = 0L;
23562371
} else if (results.getNumDmlAffectedRows() != null) {
@@ -2378,25 +2393,75 @@ public com.google.api.services.bigquery.model.QueryResponse call()
23782393
SessionInfo sessionInfo =
23792394
results.getSessionInfo() != null ? SessionInfo.fromPb(results.getSessionInfo()) : null;
23802395

2381-
if (results.getPageToken() != null) {
2396+
Collection<FieldValueList> firstPageRows;
2397+
if (isArrow) {
2398+
if (results.getArrowRecordBatch() != null
2399+
&& results.getArrowRecordBatch().getSerializedRecordBatch() != null) {
2400+
try {
2401+
firstPageRows =
2402+
ArrowDeserializer.deserializeRecordBatch(
2403+
results.getArrowRecordBatch().decodeSerializedRecordBatch(),
2404+
schema,
2405+
arrowSchemaPojo);
2406+
} catch (IOException e) {
2407+
throw new BigQueryException(0, "Failed to deserialize Arrow record batch", e);
2408+
}
2409+
} else {
2410+
firstPageRows = ImmutableList.of();
2411+
}
2412+
} else {
2413+
firstPageRows =
2414+
results.getRows() != null
2415+
? transformTableData(
2416+
results.getRows(),
2417+
schema,
2418+
getOptions().getDataFormatOptions().useInt64Timestamp())
2419+
: ImmutableList.of();
2420+
}
2421+
2422+
boolean hasMorePages = results.getPageToken() != null;
2423+
long initialRowOffset = 0L;
2424+
if (hasMorePages && isArrow) {
2425+
Long parsedOffset = Longs.tryParse(results.getPageToken());
2426+
initialRowOffset = parsedOffset != null ? parsedOffset : firstPageRows.size();
2427+
if (content.getMaxResults() != null
2428+
&& (initialRowOffset >= content.getMaxResults()
2429+
|| firstPageRows.size() >= content.getMaxResults())) {
2430+
hasMorePages = false;
2431+
}
2432+
}
2433+
2434+
if (hasMorePages) {
23822435
JobId jobId = JobId.fromPb(results.getJobReference());
23832436
String cursor = results.getPageToken();
2437+
2438+
NextPageFetcher<FieldValueList> pageFetcher;
2439+
if (isArrow) {
2440+
pageFetcher =
2441+
new ArrowQueryPageFetcher(
2442+
jobId,
2443+
schema,
2444+
arrowSchemaBytes,
2445+
arrowSchemaPojo,
2446+
getOptions(),
2447+
initialRowOffset,
2448+
content.getMaxResults(),
2449+
optionMap(options));
2450+
} else {
2451+
pageFetcher = new QueryPageFetcher(jobId, schema, getOptions(), cursor, optionMap(options));
2452+
}
2453+
23842454
return TableResult.newBuilder()
23852455
.setSchema(schema)
23862456
.setTotalRows(numRows)
23872457
.setPageNoSchema(
23882458
new PageImpl<>(
23892459
// fetch next pages of results
2390-
new QueryPageFetcher(jobId, schema, getOptions(), cursor, optionMap(options)),
2391-
cursor,
2392-
transformTableData(
2393-
results.getRows(),
2394-
schema,
2395-
getOptions().getDataFormatOptions().useInt64Timestamp())))
2460+
pageFetcher, cursor, firstPageRows))
23962461
.setJobId(jobId)
23972462
.setQueryId(results.getQueryId())
23982463
.setJobCreationReason(JobCreationReason.fromPb(results.getJobCreationReason()))
2399-
.setRowsInPage(results.getRows() != null ? (long) results.getRows().size() : 0L)
2464+
.setRowsInPage((long) firstPageRows.size())
24002465
.setStatementType(statementType)
24012466
.setTotalBytesBilled(totalBytesBilled)
24022467
.setTotalBytesProcessed(totalBytesProcessed)
@@ -2413,16 +2478,13 @@ public com.google.api.services.bigquery.model.QueryResponse call()
24132478
new PageImpl<>(
24142479
new TableDataPageFetcher(null, schema, getOptions(), null, optionMap(options)),
24152480
null,
2416-
transformTableData(
2417-
results.getRows(),
2418-
schema,
2419-
getOptions().getDataFormatOptions().useInt64Timestamp())))
2481+
firstPageRows))
24202482
// Return the JobID of the successful job
24212483
.setJobId(
24222484
results.getJobReference() != null ? JobId.fromPb(results.getJobReference()) : null)
24232485
.setQueryId(results.getQueryId())
24242486
.setJobCreationReason(JobCreationReason.fromPb(results.getJobCreationReason()))
2425-
.setRowsInPage(results.getRows() != null ? (long) results.getRows().size() : 0L)
2487+
.setRowsInPage((long) firstPageRows.size())
24262488
.setStatementType(statementType)
24272489
.setTotalBytesBilled(totalBytesBilled)
24282490
.setTotalBytesProcessed(totalBytesProcessed)
@@ -2448,11 +2510,6 @@ public Object queryWithTimeout(
24482510
throws InterruptedException, JobException {
24492511
Job.checkNotDryRun(configuration, "query");
24502512

2451-
if (configuration.getQueryResultsFormat() == QueryResultsFormat.ARROW) {
2452-
throw new IllegalArgumentException(
2453-
"QueryResultsFormat.ARROW is not supported with query(). Use queryArrow() instead.");
2454-
}
2455-
24562513
// If JobCreationMode is not explicitly set, update it with default value;
24572514
if (configuration.getJobCreationMode() == null) {
24582515
configuration =
@@ -2507,6 +2564,12 @@ && getOptions().getOpenTelemetryTracer() != null) {
25072564

25082565
return queryRpc(projectId, content, options);
25092566
}
2567+
2568+
if (configuration.getQueryResultsFormat() == QueryResultsFormat.ARROW) {
2569+
throw new IllegalArgumentException(
2570+
"Arrow results format is only supported for fast query path execution (e.g. no destination table, no custom clustering, etc.).");
2571+
}
2572+
25102573
return create(JobInfo.of(jobId, configuration), options);
25112574
} finally {
25122575
if (querySpan != null) {

0 commit comments

Comments
 (0)