6767import com .google .common .collect .Lists ;
6868import com .google .common .collect .Maps ;
6969import com .google .common .net .HostAndPort ;
70+ import com .google .common .primitives .Longs ;
7071import io .grpc .ManagedChannelBuilder ;
7172import io .opentelemetry .api .common .Attributes ;
7273import io .opentelemetry .api .trace .Span ;
7576import java .net .URI ;
7677import java .util .ArrayDeque ;
7778import java .util .ArrayList ;
79+ import java .util .Collection ;
7880import java .util .Collections ;
7981import java .util .Iterator ;
8082import 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