@malloydata/db-bigquery 0.0.425 → 0.0.427
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bigquery_connection.d.ts +36 -2
- package/dist/bigquery_connection.js +254 -39
- package/package.json +2 -2
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { RowMetadata } from '@google-cloud/bigquery';
|
|
1
|
+
import type { Job, PagedResponse, Query, QueryResultsOptions, RowMetadata } from '@google-cloud/bigquery';
|
|
2
2
|
import type bigquery from '@google-cloud/bigquery/build/src/types';
|
|
3
3
|
import type { ResourceStream } from '@google-cloud/paginator';
|
|
4
4
|
import type { Connection, ConnectionConfig, ConnectionParameterValue, MalloyQueryData, PersistSQLResults, QueryData, QueryRecord, QueryOptionsReader, QueryRunStats, RunSQLOptions, StreamingConnection, TableSourceDef, SQLSourceDef, SQLSourceRequest } from '@malloydata/malloy';
|
|
@@ -54,6 +54,31 @@ interface SchemaInfo {
|
|
|
54
54
|
export declare class BigQueryAuthenticationError extends Error {
|
|
55
55
|
constructor(message: string);
|
|
56
56
|
}
|
|
57
|
+
/**
|
|
58
|
+
* Fetch a job's query results, polling until the job completes, `deadlineMs`
|
|
59
|
+
* elapses, or the caller aborts. A single getQueryResults call cannot wait out a
|
|
60
|
+
* slow query: BigQuery bounds how long one call blocks server-side (its docs
|
|
61
|
+
* note the call typically returns after ~200s even when a larger timeoutMs is
|
|
62
|
+
* requested) and then reports `jobComplete: false` while the job is still
|
|
63
|
+
* running normally. So for a query that runs longer than one call's server wait
|
|
64
|
+
* we re-issue getQueryResults until it finishes.
|
|
65
|
+
*
|
|
66
|
+
* `deadlineMs` bounds the total wait (the connection's configured timeoutMs).
|
|
67
|
+
* On reaching it we cancel the job before throwing: BigQuery's jobTimeoutMs
|
|
68
|
+
* should already be cancelling it server-side, but if that timeout error has not
|
|
69
|
+
* come back yet, cancelling here stops the job from running (and billing) past
|
|
70
|
+
* the point we stopped waiting. `abortSignal` lets the loop exit promptly on an
|
|
71
|
+
* external cancel (the caller cancels the job in that case). A bounded number of
|
|
72
|
+
* retries absorbs the transient access-denied error BigQuery intermittently
|
|
73
|
+
* returns on first fetch. Between polls a minimum interval is enforced so a poll
|
|
74
|
+
* that returns sooner than requested does not spin the loop. `now` and `wait`
|
|
75
|
+
* are injectable for testing.
|
|
76
|
+
*/
|
|
77
|
+
export declare function getQueryResultsUntilComplete(job: Pick<Job, 'getQueryResults' | 'cancel'>, getQueryResultsOptions: QueryResultsOptions, deadlineMs: number, { abortSignal, now, wait, }?: {
|
|
78
|
+
abortSignal?: AbortSignal;
|
|
79
|
+
now?: () => number;
|
|
80
|
+
wait?: (ms: number, abortSignal?: AbortSignal) => Promise<void>;
|
|
81
|
+
}): Promise<PagedResponse<RowMetadata, Query, bigquery.IGetQueryResultsResponse>>;
|
|
57
82
|
export declare class BigQueryConnection extends BaseConnection implements Connection, PersistSQLResults, StreamingConnection {
|
|
58
83
|
readonly name: string;
|
|
59
84
|
private readonly dialect;
|
|
@@ -97,10 +122,19 @@ export declare class BigQueryConnection extends BaseConnection implements Connec
|
|
|
97
122
|
private encodeProjectSegment;
|
|
98
123
|
fetchTableSchema(tableName: string, tablePath: string): Promise<TableSourceDef | string>;
|
|
99
124
|
private getSQLBlockSchema;
|
|
125
|
+
/**
|
|
126
|
+
* The effective query timeout in milliseconds. A configured value that is
|
|
127
|
+
* unset, blank, non-numeric, zero, or negative falls back to TIMEOUT_MS; only
|
|
128
|
+
* a positive number overrides the default. The result bounds both the
|
|
129
|
+
* BigQuery job (jobTimeoutMs) and the getQueryResultsUntilComplete poll
|
|
130
|
+
* deadline, so it must be positive: a zero or negative would make the job
|
|
131
|
+
* cancel on the first poll instead of running.
|
|
132
|
+
*/
|
|
133
|
+
private resolvedTimeoutMs;
|
|
100
134
|
private createBigQueryJobAndGetResults;
|
|
101
135
|
private createBigQueryJob;
|
|
102
136
|
initiateJobAndGetLinkToConsole(sqlCommand: string, dryRun?: boolean): Promise<string>;
|
|
103
|
-
runSQLStream(sqlCommand: string, { rowLimit, abortSignal }?: RunSQLOptions): AsyncIterableIterator<QueryRecord>;
|
|
137
|
+
runSQLStream(sqlCommand: string, { rowLimit, abortSignal, queryMetadata }?: RunSQLOptions): AsyncIterableIterator<QueryRecord>;
|
|
104
138
|
fetchTableMetadata(tablePath: string): Promise<TableMetadata>;
|
|
105
139
|
close(): Promise<void>;
|
|
106
140
|
}
|
|
@@ -38,11 +38,41 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
38
38
|
})();
|
|
39
39
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
40
40
|
exports.BigQueryConnection = exports.BigQueryAuthenticationError = void 0;
|
|
41
|
+
exports.getQueryResultsUntilComplete = getQueryResultsUntilComplete;
|
|
41
42
|
const bigquery_1 = require("@google-cloud/bigquery");
|
|
42
43
|
const googleCommon = __importStar(require("@google-cloud/common"));
|
|
43
44
|
const gaxios_1 = require("gaxios");
|
|
44
45
|
const malloy_1 = require("@malloydata/malloy");
|
|
45
46
|
const connection_1 = require("@malloydata/malloy/connection");
|
|
47
|
+
// BigQuery label grammar: keys and values are lowercase, <=63 chars, and
|
|
48
|
+
// [a-z0-9_-]; keys must start with a lowercase letter. Values are transformed
|
|
49
|
+
// to fit (lowercase, disallowed chars -> '_', truncate); a key that can't be
|
|
50
|
+
// made valid (e.g. it starts with a digit) is dropped. BigQuery's 64-label cap
|
|
51
|
+
// needs no check here: the contract allows fewer properties than that.
|
|
52
|
+
const BQ_MAX_LEN = 63;
|
|
53
|
+
function sanitizeBigQueryValue(value) {
|
|
54
|
+
return value
|
|
55
|
+
.toLowerCase()
|
|
56
|
+
.replace(/[^a-z0-9_-]/g, '_')
|
|
57
|
+
.slice(0, BQ_MAX_LEN);
|
|
58
|
+
}
|
|
59
|
+
function sanitizeBigQueryKey(key) {
|
|
60
|
+
const sanitized = sanitizeBigQueryValue(key);
|
|
61
|
+
return /^[a-z]/.test(sanitized) ? sanitized : undefined;
|
|
62
|
+
}
|
|
63
|
+
/** Render a query's metadata as BigQuery job labels, in BQ's grammar. */
|
|
64
|
+
function toBigQueryLabels(labels) {
|
|
65
|
+
if (labels === undefined)
|
|
66
|
+
return undefined;
|
|
67
|
+
const out = {};
|
|
68
|
+
for (const [key, value] of Object.entries(labels)) {
|
|
69
|
+
const sanitizedKey = sanitizeBigQueryKey(key);
|
|
70
|
+
if (sanitizedKey === undefined)
|
|
71
|
+
continue; // can't be a valid BQ key; drop
|
|
72
|
+
out[sanitizedKey] = sanitizeBigQueryValue(value);
|
|
73
|
+
}
|
|
74
|
+
return Object.keys(out).length > 0 ? out : undefined;
|
|
75
|
+
}
|
|
46
76
|
// BigQuery SDK apparently throws various authentication errors from Gaxios (https://github.com/googleapis/gaxios) and from
|
|
47
77
|
// @google-cloud/common (https://www.npmjs.com/package/@google-cloud/common)
|
|
48
78
|
// catching and rewriting here a single kind of authentication error we can consistently catch further up the stack
|
|
@@ -75,9 +105,185 @@ const maybeRewriteError = (e) => {
|
|
|
75
105
|
*/
|
|
76
106
|
const MAXIMUM_BYTES_BILLED = String(25 * 1024 * 1024 * 1024);
|
|
77
107
|
/**
|
|
78
|
-
* Default timeoutMs
|
|
108
|
+
* Default connection timeoutMs, 10 minutes. Bounds both the BigQuery job
|
|
109
|
+
* (jobTimeoutMs) and, via getQueryResultsUntilComplete, how long results are
|
|
110
|
+
* polled. When this connector runs behind an HTTP layer (e.g. the Malloy
|
|
111
|
+
* Publisher), set the connection's timeoutMs below that layer's socket/request
|
|
112
|
+
* timeout so a long query is ended by this deadline with a clear error, rather
|
|
113
|
+
* than racing an opaque socket reset.
|
|
79
114
|
*/
|
|
80
115
|
const TIMEOUT_MS = 1000 * 60 * 10;
|
|
116
|
+
/**
|
|
117
|
+
* How long each getQueryResults call asks to wait for the job to finish before
|
|
118
|
+
* returning. BigQuery returns after at most ~200s regardless of the requested
|
|
119
|
+
* value, so this is kept under that ceiling; queries that take longer are
|
|
120
|
+
* covered by polling across multiple calls (see getQueryResultsUntilComplete).
|
|
121
|
+
*/
|
|
122
|
+
const GET_QUERY_RESULTS_POLL_MS = 1000 * 60 * 2;
|
|
123
|
+
/**
|
|
124
|
+
* Minimum spacing between getQueryResults polls. Each call is asked to block
|
|
125
|
+
* server-side for up to GET_QUERY_RESULTS_POLL_MS, but BigQuery may return
|
|
126
|
+
* `jobComplete: false` sooner than requested; this floor keeps the loop from
|
|
127
|
+
* busy-polling in that case. Only the shortfall below it is waited out, so a
|
|
128
|
+
* poll that already blocked longer adds nothing.
|
|
129
|
+
*/
|
|
130
|
+
const GET_QUERY_RESULTS_MIN_POLL_INTERVAL_MS = 1000;
|
|
131
|
+
/**
|
|
132
|
+
* Issue one getQueryResults call via the callback overload so we can read the
|
|
133
|
+
* structured `jobComplete` flag rather than pattern-matching the client's error
|
|
134
|
+
* string. On the still-running path the client invokes the callback with a
|
|
135
|
+
* synthetic "did not complete before ..." Error *and* an apiResponse whose
|
|
136
|
+
* `jobComplete === false`; we key off that boolean, which does not drift across
|
|
137
|
+
* client releases the way the message can.
|
|
138
|
+
*
|
|
139
|
+
* `autoPaginate: false` is forced, not optional: getQueryResults is
|
|
140
|
+
* paginator-wrapped, and at the default the paginator hands the callback the
|
|
141
|
+
* error alone, dropping the apiResponse read here — so every still-running poll
|
|
142
|
+
* would be misread as a fetch error. It does not change what a completed fetch
|
|
143
|
+
* returns.
|
|
144
|
+
*
|
|
145
|
+
* Resolves promptly if `abortSignal` fires, so a caller's timeout/cancel is not
|
|
146
|
+
* left blocked behind BigQuery's server-side wait.
|
|
147
|
+
*/
|
|
148
|
+
function pollQueryResults(job, options, abortSignal) {
|
|
149
|
+
return new Promise(resolve => {
|
|
150
|
+
let settled = false;
|
|
151
|
+
const settle = (outcome) => {
|
|
152
|
+
if (settled) {
|
|
153
|
+
return;
|
|
154
|
+
}
|
|
155
|
+
settled = true;
|
|
156
|
+
abortSignal === null || abortSignal === void 0 ? void 0 : abortSignal.removeEventListener('abort', onAbort);
|
|
157
|
+
resolve(outcome);
|
|
158
|
+
};
|
|
159
|
+
const onAbort = () => settle({ kind: 'aborted' });
|
|
160
|
+
if (abortSignal === null || abortSignal === void 0 ? void 0 : abortSignal.aborted) {
|
|
161
|
+
settle({ kind: 'aborted' });
|
|
162
|
+
return;
|
|
163
|
+
}
|
|
164
|
+
abortSignal === null || abortSignal === void 0 ? void 0 : abortSignal.addEventListener('abort', onAbort);
|
|
165
|
+
const pollOptions = { ...options, autoPaginate: false };
|
|
166
|
+
job.getQueryResults(pollOptions, (err, rows, nextQuery, apiResponse) => {
|
|
167
|
+
if ((apiResponse === null || apiResponse === void 0 ? void 0 : apiResponse.jobComplete) === false) {
|
|
168
|
+
settle({ kind: 'stillRunning' });
|
|
169
|
+
}
|
|
170
|
+
else if (err) {
|
|
171
|
+
settle({ kind: 'error', error: err });
|
|
172
|
+
}
|
|
173
|
+
else {
|
|
174
|
+
settle({
|
|
175
|
+
kind: 'complete',
|
|
176
|
+
value: [
|
|
177
|
+
rows !== null && rows !== void 0 ? rows : [],
|
|
178
|
+
nextQuery !== null && nextQuery !== void 0 ? nextQuery : null,
|
|
179
|
+
apiResponse,
|
|
180
|
+
],
|
|
181
|
+
});
|
|
182
|
+
}
|
|
183
|
+
});
|
|
184
|
+
});
|
|
185
|
+
}
|
|
186
|
+
/** Cancel a job without letting a cancel failure mask the caller's real error. */
|
|
187
|
+
async function cancelJobQuietly(job) {
|
|
188
|
+
try {
|
|
189
|
+
await job.cancel();
|
|
190
|
+
}
|
|
191
|
+
catch {
|
|
192
|
+
// Best-effort: the job may already be finishing or gone; the caller still
|
|
193
|
+
// reports the timeout that prompted the cancel.
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
/**
|
|
197
|
+
* A delay used to space out polls; resolves early if `abortSignal` fires, and is
|
|
198
|
+
* injectable so tests need not wait real time.
|
|
199
|
+
*/
|
|
200
|
+
function delay(ms, abortSignal) {
|
|
201
|
+
return new Promise(resolve => {
|
|
202
|
+
if (abortSignal === null || abortSignal === void 0 ? void 0 : abortSignal.aborted) {
|
|
203
|
+
resolve();
|
|
204
|
+
return;
|
|
205
|
+
}
|
|
206
|
+
const timer = setTimeout(() => {
|
|
207
|
+
abortSignal === null || abortSignal === void 0 ? void 0 : abortSignal.removeEventListener('abort', onAbort);
|
|
208
|
+
resolve();
|
|
209
|
+
}, ms);
|
|
210
|
+
const onAbort = () => {
|
|
211
|
+
clearTimeout(timer);
|
|
212
|
+
resolve();
|
|
213
|
+
};
|
|
214
|
+
abortSignal === null || abortSignal === void 0 ? void 0 : abortSignal.addEventListener('abort', onAbort, { once: true });
|
|
215
|
+
});
|
|
216
|
+
}
|
|
217
|
+
/**
|
|
218
|
+
* Fetch a job's query results, polling until the job completes, `deadlineMs`
|
|
219
|
+
* elapses, or the caller aborts. A single getQueryResults call cannot wait out a
|
|
220
|
+
* slow query: BigQuery bounds how long one call blocks server-side (its docs
|
|
221
|
+
* note the call typically returns after ~200s even when a larger timeoutMs is
|
|
222
|
+
* requested) and then reports `jobComplete: false` while the job is still
|
|
223
|
+
* running normally. So for a query that runs longer than one call's server wait
|
|
224
|
+
* we re-issue getQueryResults until it finishes.
|
|
225
|
+
*
|
|
226
|
+
* `deadlineMs` bounds the total wait (the connection's configured timeoutMs).
|
|
227
|
+
* On reaching it we cancel the job before throwing: BigQuery's jobTimeoutMs
|
|
228
|
+
* should already be cancelling it server-side, but if that timeout error has not
|
|
229
|
+
* come back yet, cancelling here stops the job from running (and billing) past
|
|
230
|
+
* the point we stopped waiting. `abortSignal` lets the loop exit promptly on an
|
|
231
|
+
* external cancel (the caller cancels the job in that case). A bounded number of
|
|
232
|
+
* retries absorbs the transient access-denied error BigQuery intermittently
|
|
233
|
+
* returns on first fetch. Between polls a minimum interval is enforced so a poll
|
|
234
|
+
* that returns sooner than requested does not spin the loop. `now` and `wait`
|
|
235
|
+
* are injectable for testing.
|
|
236
|
+
*/
|
|
237
|
+
async function getQueryResultsUntilComplete(job, getQueryResultsOptions, deadlineMs, { abortSignal, now = Date.now, wait = delay, } = {}) {
|
|
238
|
+
const startedAt = now();
|
|
239
|
+
let transientRetries = 0;
|
|
240
|
+
for (;;) {
|
|
241
|
+
if (abortSignal === null || abortSignal === void 0 ? void 0 : abortSignal.aborted) {
|
|
242
|
+
throw new Error('BigQuery getQueryResults was aborted before the query completed.');
|
|
243
|
+
}
|
|
244
|
+
const remainingMs = deadlineMs - (now() - startedAt);
|
|
245
|
+
if (remainingMs <= 0) {
|
|
246
|
+
await cancelJobQuietly(job);
|
|
247
|
+
throw new Error(`BigQuery query did not complete within the configured timeout of ${deadlineMs}ms. ` +
|
|
248
|
+
"Raise the connection's timeoutMs to allow longer-running queries.");
|
|
249
|
+
}
|
|
250
|
+
// Clamp the per-poll server wait to what's left of the deadline: a short
|
|
251
|
+
// timeout then fails fast, and the overall deadline can't be overshot by a
|
|
252
|
+
// full poll interval. timeoutMs is applied last so a caller-supplied
|
|
253
|
+
// timeoutMs cannot override the poll interval.
|
|
254
|
+
const pollTimeoutMs = Math.max(1, Math.min(GET_QUERY_RESULTS_POLL_MS, remainingMs));
|
|
255
|
+
const polledAt = now();
|
|
256
|
+
const outcome = await pollQueryResults(job, { ...getQueryResultsOptions, timeoutMs: pollTimeoutMs }, abortSignal);
|
|
257
|
+
switch (outcome.kind) {
|
|
258
|
+
case 'complete':
|
|
259
|
+
return outcome.value;
|
|
260
|
+
case 'stillRunning': {
|
|
261
|
+
// BigQuery can return `jobComplete: false` sooner than the timeoutMs we
|
|
262
|
+
// asked for; without a floor between polls the loop would busy-poll and
|
|
263
|
+
// hammer the API. Wait out only the shortfall below a minimum interval,
|
|
264
|
+
// bounded by the remaining deadline, so a poll that already blocked adds
|
|
265
|
+
// no latency. The abort/deadline checks at the top of the loop then
|
|
266
|
+
// decide whether to continue, and a still-running poll never consumes
|
|
267
|
+
// the transient-retry budget.
|
|
268
|
+
const backoffMs = Math.min(GET_QUERY_RESULTS_MIN_POLL_INTERVAL_MS - (now() - polledAt), deadlineMs - (now() - startedAt));
|
|
269
|
+
if (backoffMs > 0) {
|
|
270
|
+
await wait(backoffMs, abortSignal);
|
|
271
|
+
}
|
|
272
|
+
continue;
|
|
273
|
+
}
|
|
274
|
+
case 'aborted':
|
|
275
|
+
throw new Error('BigQuery getQueryResults was aborted before the query completed.');
|
|
276
|
+
case 'error':
|
|
277
|
+
// A (possibly transient) fetch error — e.g. the intermittent
|
|
278
|
+
// access-denied BigQuery returns on first fetch. Retry a bounded number
|
|
279
|
+
// of times, then surface the real error.
|
|
280
|
+
if (transientRetries++ < 3) {
|
|
281
|
+
continue;
|
|
282
|
+
}
|
|
283
|
+
throw outcome.error;
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
}
|
|
81
287
|
// manage access to BQ, control costs, enforce global data/API limits
|
|
82
288
|
class BigQueryConnection extends connection_1.BaseConnection {
|
|
83
289
|
constructor(arg, queryOptions, config = {}) {
|
|
@@ -153,20 +359,23 @@ class BigQueryConnection extends connection_1.BaseConnection {
|
|
|
153
359
|
get supportsNesting() {
|
|
154
360
|
return true;
|
|
155
361
|
}
|
|
156
|
-
async _runSQL(sqlCommand,
|
|
362
|
+
async _runSQL(sqlCommand, options = {}, rowIndex = 0) {
|
|
157
363
|
var _a, _b, _c;
|
|
364
|
+
const { rowLimit, abortSignal } = options;
|
|
158
365
|
const defaultOptions = this.readQueryOptions();
|
|
159
366
|
const pageSize = rowLimit !== null && rowLimit !== void 0 ? rowLimit : defaultOptions.rowLimit;
|
|
367
|
+
const perCallLabels = toBigQueryLabels(this.queryMetadataBag(options.queryMetadata));
|
|
160
368
|
try {
|
|
161
369
|
const queryResultsOptions = {
|
|
162
370
|
maxResults: pageSize,
|
|
163
371
|
startIndex: rowIndex.toString(),
|
|
164
372
|
};
|
|
165
|
-
const jobResult = await this.createBigQueryJobAndGetResults(sqlCommand, undefined, queryResultsOptions, abortSignal);
|
|
373
|
+
const jobResult = await this.createBigQueryJobAndGetResults(sqlCommand, perCallLabels ? { labels: perCallLabels } : undefined, queryResultsOptions, abortSignal);
|
|
166
374
|
const totalRows = +(((_a = jobResult[2]) === null || _a === void 0 ? void 0 : _a.totalRows)
|
|
167
375
|
? jobResult[2].totalRows
|
|
168
376
|
: '0');
|
|
169
|
-
//
|
|
377
|
+
// jobComplete is guaranteed here: getQueryResultsUntilComplete only
|
|
378
|
+
// returns once BigQuery reports the job complete (it polls otherwise).
|
|
170
379
|
const queryCostBytes = (_b = jobResult[2]) === null || _b === void 0 ? void 0 : _b.totalBytesProcessed;
|
|
171
380
|
const data = {
|
|
172
381
|
rows: jobResult[0],
|
|
@@ -511,9 +720,18 @@ class BigQueryConnection extends connection_1.BaseConnection {
|
|
|
511
720
|
}
|
|
512
721
|
throw lastFetchError;
|
|
513
722
|
}
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
723
|
+
/**
|
|
724
|
+
* The effective query timeout in milliseconds. A configured value that is
|
|
725
|
+
* unset, blank, non-numeric, zero, or negative falls back to TIMEOUT_MS; only
|
|
726
|
+
* a positive number overrides the default. The result bounds both the
|
|
727
|
+
* BigQuery job (jobTimeoutMs) and the getQueryResultsUntilComplete poll
|
|
728
|
+
* deadline, so it must be positive: a zero or negative would make the job
|
|
729
|
+
* cancel on the first poll instead of running.
|
|
730
|
+
*/
|
|
731
|
+
resolvedTimeoutMs() {
|
|
732
|
+
const configured = Number(this.config.timeoutMs);
|
|
733
|
+
return configured > 0 ? configured : TIMEOUT_MS;
|
|
734
|
+
}
|
|
517
735
|
async createBigQueryJobAndGetResults(sqlCommand, createQueryJobOptions, getQueryResultsOptions, abortSignal) {
|
|
518
736
|
try {
|
|
519
737
|
const job = await this.createBigQueryJob({
|
|
@@ -524,37 +742,29 @@ class BigQueryConnection extends connection_1.BaseConnection {
|
|
|
524
742
|
job.cancel();
|
|
525
743
|
};
|
|
526
744
|
abortSignal === null || abortSignal === void 0 ? void 0 : abortSignal.addEventListener('abort', cancel);
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
return num;
|
|
543
|
-
}
|
|
544
|
-
return String(val);
|
|
545
|
-
},
|
|
745
|
+
try {
|
|
746
|
+
// Poll for results until the job completes or the connection's
|
|
747
|
+
// configured timeout elapses; a single getQueryResults call can't wait
|
|
748
|
+
// out a long-running query (see getQueryResultsUntilComplete). The
|
|
749
|
+
// deadline is the same resolved timeout that bounds the job's
|
|
750
|
+
// jobTimeoutMs, so the client stops waiting right about when BigQuery
|
|
751
|
+
// stops running the job.
|
|
752
|
+
return await getQueryResultsUntilComplete(job, {
|
|
753
|
+
wrapIntegers: {
|
|
754
|
+
integerTypeCastFunction: (val) => {
|
|
755
|
+
const num = Number(val);
|
|
756
|
+
if (Number.isSafeInteger(num)) {
|
|
757
|
+
return num;
|
|
758
|
+
}
|
|
759
|
+
return String(val);
|
|
546
760
|
},
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
}
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
finally {
|
|
554
|
-
abortSignal === null || abortSignal === void 0 ? void 0 : abortSignal.removeEventListener('abort', cancel);
|
|
555
|
-
}
|
|
761
|
+
},
|
|
762
|
+
...getQueryResultsOptions,
|
|
763
|
+
}, this.resolvedTimeoutMs(), { abortSignal });
|
|
764
|
+
}
|
|
765
|
+
finally {
|
|
766
|
+
abortSignal === null || abortSignal === void 0 ? void 0 : abortSignal.removeEventListener('abort', cancel);
|
|
556
767
|
}
|
|
557
|
-
throw lastFetchError;
|
|
558
768
|
}
|
|
559
769
|
catch (e) {
|
|
560
770
|
throw maybeRewriteError(e);
|
|
@@ -568,7 +778,10 @@ class BigQueryConnection extends connection_1.BaseConnection {
|
|
|
568
778
|
const [job] = await this.bigQuery.createQueryJob({
|
|
569
779
|
location: this.location,
|
|
570
780
|
maximumBytesBilled: this.config.maximumBytesBilled || MAXIMUM_BYTES_BILLED,
|
|
571
|
-
|
|
781
|
+
// Shadow the resolved timeout to the job's server-side limit so BigQuery
|
|
782
|
+
// cancels a runaway job on its own; getQueryResultsUntilComplete polls up
|
|
783
|
+
// to the same deadline on the client side.
|
|
784
|
+
jobTimeoutMs: this.resolvedTimeoutMs(),
|
|
572
785
|
...options,
|
|
573
786
|
});
|
|
574
787
|
return job;
|
|
@@ -581,7 +794,8 @@ class BigQueryConnection extends connection_1.BaseConnection {
|
|
|
581
794
|
const url = `https://console.cloud.google.com/bigquery?project=${this.billingProjectId}&j=bq:${job.location}:${job.id}&page=queryresults`;
|
|
582
795
|
return url;
|
|
583
796
|
}
|
|
584
|
-
runSQLStream(sqlCommand, { rowLimit, abortSignal } = {}) {
|
|
797
|
+
runSQLStream(sqlCommand, { rowLimit, abortSignal, queryMetadata } = {}) {
|
|
798
|
+
const labels = toBigQueryLabels(this.queryMetadataBag(queryMetadata));
|
|
585
799
|
const streamBigQuery = (onError, onData, onEnd) => {
|
|
586
800
|
let index = 0;
|
|
587
801
|
function handleData(rowMetadata) {
|
|
@@ -592,8 +806,9 @@ class BigQueryConnection extends connection_1.BaseConnection {
|
|
|
592
806
|
this.end();
|
|
593
807
|
}
|
|
594
808
|
}
|
|
809
|
+
const query = this.prependSetupSQL(sqlCommand);
|
|
595
810
|
this.bigQuery
|
|
596
|
-
.createQueryStream(
|
|
811
|
+
.createQueryStream(labels ? { query, labels } : query)
|
|
597
812
|
.on('error', onError)
|
|
598
813
|
.on('data', handleData)
|
|
599
814
|
.on('end', onEnd);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@malloydata/db-bigquery",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.427",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
"@google-cloud/bigquery": "7.9.4",
|
|
27
27
|
"@google-cloud/common": "5.0.2",
|
|
28
28
|
"@google-cloud/paginator": "5.0.2",
|
|
29
|
-
"@malloydata/malloy": "0.0.
|
|
29
|
+
"@malloydata/malloy": "0.0.427",
|
|
30
30
|
"gaxios": "^4.2.0"
|
|
31
31
|
}
|
|
32
32
|
}
|