Skip to content

feat(bigquery): support jobs.insert in createQueryArrowStream - #9600

Open
ohmayr wants to merge 1 commit into
feat/bigquery-arrow-job-waitfrom
feat/bigquery-arrow-jobs-insert
Open

ohmayr wants to merge 1 commit into
feat/bigquery-arrow-job-waitfrom
feat/bigquery-arrow-jobs-insert

Conversation

@ohmayr

@ohmayr ohmayr commented Oct 13, 2026 •

Copy link
Copy Markdown
Contributor

Some query options only work with jobs.insert (for example jobId, labels, dryRun, or jobTimeoutMs), so createQueryArrowStream failed for those queries. This runs them through createQueryJob instead.

  • Used when buildQueryRequest_ returns undefined (a jobs.insert-only option, or FAST_QUERY_PATH=DISABLED). timeoutMs and jobCreationMode only apply to jobs.query, so they stay out of the job configuration; timeoutMs bounds the wait instead.
  • Sets stream.jobReference and emits the job event.
  • A dryRun emits one 0-row, 0-column RecordBatch without waiting or calling ReadRows; the job's metadata holds the dry-run statistics.
  • Otherwise waits for the job to finish and streams from streams/_default at offset = 0. Errors thrown synchronously by createQueryJob destroy the stream.

@product-auto-label product-auto-label Bot added the api: bigquery Issues related to the BigQuery API. label Oct 13, 2026

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request implements a fallback to route queries that cannot be executed via jobs.query through jobs.insert (createQueryJob) in createQueryArrowStream_, and adds comprehensive unit and system tests to verify this behavior. Feedback was provided to ensure type safety by guarding against an undefined job object before calling waitForJobCompletion and to use the idiomatic stream.push(null) instead of stream.end() to signal EOF.

Comment thread handwritten/bigquery/src/arrow.ts Outdated
@ohmayr
ohmayr added this pull request to stack #9597 October 13, 2026 02:55
@ohmayr
ohmayr force-pushed the feat/bigquery-arrow-jobs-insert branch from 2e029c1 to 86c5363 Compare October 13, 2026 04:30
@ohmayr
ohmayr force-pushed the feat/bigquery-arrow-jobs-insert branch from 86c5363 to 3d6c4a9 Compare October 13, 2026 08:41
@ohmayr
ohmayr force-pushed the feat/bigquery-arrow-jobs-insert branch from 3d6c4a9 to 799d417 Compare October 13, 2026 09:33
@ohmayr
ohmayr force-pushed the feat/bigquery-arrow-jobs-insert branch from 799d417 to 90cb922 Compare October 13, 2026 10:27
@ohmayr
ohmayr force-pushed the feat/bigquery-arrow-jobs-insert branch 2 times, most recently from 03aec32 to 9853b3a Compare October 13, 2026 11:30
@ohmayr
ohmayr force-pushed the feat/bigquery-arrow-jobs-insert branch from 9853b3a to 0bba2d3 Compare October 13, 2026 12:00
@ohmayr
ohmayr force-pushed the feat/bigquery-arrow-jobs-insert branch 2 times, most recently from 2410242 to d689335 Compare October 15, 2026 09:55
Comment on lines 550 to 554
{
autoPaginate: false,
maxResults: 0,
...(timeoutMs !== undefined && {timeoutMs}),
},

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If timeoutMs: 0 is passed, Job#getQueryResults (src/job.ts:627) sends timeoutMs=0 (returning jobComplete: false immediately in 0ms) and does not error because if (timeoutOverride) is falsy for 0, causing poll() to loop with zero delay. Only forward timeoutMs when it's truthy (> 0):

Suggested change
{
autoPaginate: false,
maxResults: 0,
...(timeoutMs !== undefined && {timeoutMs}),
},
{
autoPaginate: false,
maxResults: 0,
...(timeoutMs ? {timeoutMs} : {}),
},

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done, timeoutMs is only forwarded when it's non-zero.

Comment thread handwritten/bigquery/src/arrow.ts Outdated
Comment on lines +647 to +652
// clustering). jobs.insert does not accept queryResultsFormat or
// jobCreationMode.
// timeoutMs only bounds the wait below, so keep it out of the job
// configuration sent to jobs.insert.
const {timeoutMs, ...jobQuery} = queryObj;
bq.createQueryJob(jobQuery, (err, job, resp) => {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Strip jobCreationMode alongside timeoutMs (as noted in the comment above), and wrap bq.createQueryJob in a try / catch routing synchronous errors (such as invalid params or destination) to stream.destroy(err as Error):

Suggested change
// clustering). jobs.insert does not accept queryResultsFormat or
// jobCreationMode.
// timeoutMs only bounds the wait below, so keep it out of the job
// configuration sent to jobs.insert.
const {timeoutMs, ...jobQuery} = queryObj;
bq.createQueryJob(jobQuery, (err, job, resp) => {
// clustering). jobs.insert does not accept queryResultsFormat or
// jobCreationMode.
// timeoutMs only bounds the wait below, so keep it out of the job
// configuration sent to jobs.insert.
const {timeoutMs, jobCreationMode: _jobCreationMode, ...jobQuery} =
queryObj;
bq.createQueryJob(jobQuery, (err, job, resp) => {

@ohmayr ohmayr Oct 15, 2026 •

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wrapped createQueryJob in a try/catch. I left jobCreationMode in because jobs.insert accepts and drops it (checked with a live createQueryJob({jobCreationMode: 'JOB_CREATION_OPTIONAL', ...}); it isn't in the returned configuration.query), and BigQuery#query passes it through the same way.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Update: I dropped jobCreationMode from the jobs.insert configuration after all, to match Python, which only sends it on jobs.query. BigQuery ignores it on jobs.insert, so nothing changes for callers; the unit test now sets it and checks it's left out.

@ohmayr
ohmayr force-pushed the feat/bigquery-arrow-jobs-insert branch from d689335 to 97f41ff Compare October 15, 2026 18:15
@ohmayr
ohmayr force-pushed the feat/bigquery-arrow-jobs-insert branch from 97f41ff to 7539631 Compare October 15, 2026 18:27
@ohmayr
ohmayr force-pushed the feat/bigquery-arrow-jobs-insert branch 2 times, most recently from df7043a to 3469bd1 Compare October 16, 2026 01:12
Routes queries that cannot use jobs.query (!queryReq, such as jobId,
dryRun, jobTimeoutMs, or other jobs.insert-only options) through
createQueryJob:
- Keeps timeoutMs and jobCreationMode, which only apply to jobs.query,
  out of the job configuration.
- Populates stream.jobReference and emits the job event once created.
- Emits a single 0-row, 0-column RecordBatch immediately when dryRun is
  true without polling getQueryResults or calling ReadRows.
- Otherwise waits for job completion via job.getQueryResults
  (maxResults: 0), bounded by timeoutMs like BigQuery#query, and
  streams Arrow batches from the anonymous default job stream
  (streams/_default) at offset = 0.
@ohmayr
ohmayr force-pushed the feat/bigquery-arrow-jobs-insert branch from 3469bd1 to 919d8fd Compare October 16, 2026 02:19
@ohmayr
ohmayr marked this pull request as ready for review October 16, 2026 02:33

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

api: bigquery Issues related to the BigQuery API.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants