@malloydata/db-bigquery 0.0.195-dev241003204905 → 0.0.195
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.spec.js +12 -6
- package/dist/bigquery_connection.d.ts +5 -18
- package/dist/bigquery_connection.js +68 -127
- package/package.json +2 -2
package/dist/bigquery.spec.js
CHANGED
|
@@ -212,17 +212,17 @@ describe('db:BigQuery', () => {
|
|
|
212
212
|
expect(getTableFieldSchema).toBeCalledTimes(2);
|
|
213
213
|
});
|
|
214
214
|
it('caches sql schema', async () => {
|
|
215
|
-
await bq.
|
|
215
|
+
await bq.fetchSchemaForSQLStruct(SQL_BLOCK_1, {});
|
|
216
216
|
expect(getSQLBlockSchema).toBeCalledTimes(1);
|
|
217
217
|
await new Promise(resolve => setTimeout(resolve));
|
|
218
|
-
await bq.
|
|
218
|
+
await bq.fetchSchemaForSQLStruct(SQL_BLOCK_1, {});
|
|
219
219
|
expect(getSQLBlockSchema).toBeCalledTimes(1);
|
|
220
220
|
});
|
|
221
221
|
it('refreshes sql schema', async () => {
|
|
222
|
-
await bq.
|
|
222
|
+
await bq.fetchSchemaForSQLStruct(SQL_BLOCK_2, {});
|
|
223
223
|
expect(getSQLBlockSchema).toBeCalledTimes(1);
|
|
224
224
|
await new Promise(resolve => setTimeout(resolve));
|
|
225
|
-
await bq.
|
|
225
|
+
await bq.fetchSchemaForSQLStruct(SQL_BLOCK_2, {
|
|
226
226
|
refreshTimestamp: Date.now() + 10,
|
|
227
227
|
});
|
|
228
228
|
expect(getSQLBlockSchema).toBeCalledTimes(2);
|
|
@@ -230,8 +230,11 @@ describe('db:BigQuery', () => {
|
|
|
230
230
|
});
|
|
231
231
|
});
|
|
232
232
|
const SQL_BLOCK_1 = {
|
|
233
|
-
type: '
|
|
233
|
+
type: 'sql_select',
|
|
234
234
|
name: 'block1',
|
|
235
|
+
dialect: 'standardsql',
|
|
236
|
+
connection: 'bigquery',
|
|
237
|
+
fields: [],
|
|
235
238
|
selectStr: `
|
|
236
239
|
SELECT
|
|
237
240
|
created_at,
|
|
@@ -247,8 +250,11 @@ FROM "inventory_items.parquet"
|
|
|
247
250
|
`,
|
|
248
251
|
};
|
|
249
252
|
const SQL_BLOCK_2 = {
|
|
250
|
-
type: '
|
|
253
|
+
type: 'sql_select',
|
|
251
254
|
name: 'block2',
|
|
255
|
+
dialect: 'standardsql',
|
|
256
|
+
connection: 'bigquery',
|
|
257
|
+
fields: [],
|
|
252
258
|
selectStr: `
|
|
253
259
|
SELECT
|
|
254
260
|
created_at,
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { RowMetadata } from '@google-cloud/bigquery';
|
|
2
2
|
import bigquery from '@google-cloud/bigquery/build/src/types';
|
|
3
3
|
import { ResourceStream } from '@google-cloud/paginator';
|
|
4
|
-
import { Connection, ConnectionConfig,
|
|
4
|
+
import { Connection, ConnectionConfig, MalloyQueryData, PersistSQLResults, QueryData, QueryDataRow, QueryOptionsReader, QueryRunStats, RunSQLOptions, StreamingConnection, TableSourceDef, SQLSourceDef } from '@malloydata/malloy';
|
|
5
5
|
import { BaseConnection, TableMetadata } from '@malloydata/malloy/connection';
|
|
6
6
|
export interface BigQueryManagerOptions {
|
|
7
7
|
credentials?: {
|
|
@@ -54,8 +54,6 @@ export declare class BigQueryConnection extends BaseConnection implements Connec
|
|
|
54
54
|
private billingProjectId;
|
|
55
55
|
private temporaryTables;
|
|
56
56
|
private projectId;
|
|
57
|
-
private schemaCache;
|
|
58
|
-
private sqlSchemaCache;
|
|
59
57
|
private queryOptions?;
|
|
60
58
|
private config;
|
|
61
59
|
private location;
|
|
@@ -68,9 +66,9 @@ export declare class BigQueryConnection extends BaseConnection implements Connec
|
|
|
68
66
|
get supportsNesting(): boolean;
|
|
69
67
|
private _runSQL;
|
|
70
68
|
runSQL(sqlCommand: string, options?: RunSQLOptions, rowIndex?: number): Promise<MalloyQueryData>;
|
|
71
|
-
runSQLBlockAndFetchResultSchema(sqlBlock:
|
|
69
|
+
runSQLBlockAndFetchResultSchema(sqlBlock: SQLSourceDef, options?: RunSQLOptions): Promise<{
|
|
72
70
|
data: MalloyQueryData;
|
|
73
|
-
schema:
|
|
71
|
+
schema: SQLSourceDef;
|
|
74
72
|
}>;
|
|
75
73
|
downloadMalloyQuery(sqlCommand: string): Promise<ResourceStream<RowMetadata>>;
|
|
76
74
|
private dryRunSQLQuery;
|
|
@@ -82,20 +80,9 @@ export declare class BigQueryConnection extends BaseConnection implements Connec
|
|
|
82
80
|
manifestTemporaryTable(sqlCommand: string): Promise<string>;
|
|
83
81
|
manifestPermanentTable(sqlCommand: string, datasetName: string, tableName: string, overwriteExistingTable?: boolean, createDataset?: boolean): Promise<string>;
|
|
84
82
|
private addFieldsToStructDef;
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
fetchSchemaForTables(missing: Record<string, string>, { refreshTimestamp }: FetchSchemaOptions): Promise<{
|
|
88
|
-
schemas: Record<string, StructDef>;
|
|
89
|
-
errors: Record<string, string>;
|
|
90
|
-
}>;
|
|
83
|
+
fetchSelectSchema(sqlSource: SQLSourceDef): Promise<SQLSourceDef | string>;
|
|
84
|
+
fetchTableSchema(tableName: string, tablePath: string): Promise<TableSourceDef | string>;
|
|
91
85
|
private getSQLBlockSchema;
|
|
92
|
-
fetchSchemaForSQLBlock(sqlRef: SQLBlock, { refreshTimestamp }: FetchSchemaOptions): Promise<{
|
|
93
|
-
structDef: StructDef;
|
|
94
|
-
error?: undefined;
|
|
95
|
-
} | {
|
|
96
|
-
error: string;
|
|
97
|
-
structDef?: undefined;
|
|
98
|
-
}>;
|
|
99
86
|
private createBigQueryJobAndGetResults;
|
|
100
87
|
private createBigQueryJob;
|
|
101
88
|
initiateJobAndGetLinkToConsole(sqlCommand: string, dryRun?: boolean): Promise<string>;
|
|
@@ -93,8 +93,6 @@ class BigQueryConnection extends connection_1.BaseConnection {
|
|
|
93
93
|
super();
|
|
94
94
|
this.dialect = new malloy_1.StandardSQLDialect();
|
|
95
95
|
this.temporaryTables = new Map();
|
|
96
|
-
this.schemaCache = new Map();
|
|
97
|
-
this.sqlSchemaCache = new Map();
|
|
98
96
|
if (typeof arg === 'string') {
|
|
99
97
|
this.name = arg;
|
|
100
98
|
}
|
|
@@ -188,7 +186,8 @@ class BigQueryConnection extends connection_1.BaseConnection {
|
|
|
188
186
|
if (schemaRaw === undefined) {
|
|
189
187
|
throw new Error('Schema not present');
|
|
190
188
|
}
|
|
191
|
-
const schema =
|
|
189
|
+
const schema = { ...sqlBlock, fields: [] };
|
|
190
|
+
this.addFieldsToStructDef(schema, schemaRaw);
|
|
192
191
|
return { data, schema };
|
|
193
192
|
}
|
|
194
193
|
async downloadMalloyQuery(sqlCommand) {
|
|
@@ -357,38 +356,37 @@ class BigQueryConnection extends connection_1.BaseConnection {
|
|
|
357
356
|
// BigQuery SDK sets type & name to optional even though they are required, assume they exist
|
|
358
357
|
const type = field.type;
|
|
359
358
|
const name = field.name;
|
|
360
|
-
|
|
361
|
-
|
|
359
|
+
const isRecord = ['STRUCT', 'RECORD'].includes(type);
|
|
360
|
+
const structShared = { name, dialect: this.dialectName, fields: [] };
|
|
361
|
+
if (field.mode === 'REPEATED' && !isRecord) {
|
|
362
|
+
// Malloy treats repeated values as an array of scalars.
|
|
362
363
|
const malloyType = this.dialect.sqlTypeToMalloyType(type);
|
|
363
364
|
if (malloyType) {
|
|
364
|
-
const
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
type: 'nested',
|
|
371
|
-
fieldName: name,
|
|
372
|
-
isArray: true,
|
|
373
|
-
},
|
|
374
|
-
fields: [{ ...malloyType, name: 'value' }],
|
|
365
|
+
const arrayField = {
|
|
366
|
+
...structShared,
|
|
367
|
+
type: 'array',
|
|
368
|
+
elementTypeDef: malloyType,
|
|
369
|
+
join: 'many',
|
|
370
|
+
fields: (0, malloy_1.arrayEachFields)(malloyType),
|
|
375
371
|
};
|
|
376
|
-
structDef.fields.push(
|
|
372
|
+
structDef.fields.push(arrayField);
|
|
377
373
|
}
|
|
378
374
|
}
|
|
379
|
-
else if (
|
|
380
|
-
const
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
structRelationship: field.mode === 'REPEATED'
|
|
386
|
-
? { type: 'nested', fieldName: name, isArray: false }
|
|
387
|
-
: { type: 'inline' },
|
|
388
|
-
fields: [],
|
|
375
|
+
else if (isRecord) {
|
|
376
|
+
const ifRepeatedRecord = {
|
|
377
|
+
...structShared,
|
|
378
|
+
type: 'array',
|
|
379
|
+
elementTypeDef: { type: 'record_element' },
|
|
380
|
+
join: 'many',
|
|
389
381
|
};
|
|
390
|
-
|
|
391
|
-
|
|
382
|
+
const elseRecord = {
|
|
383
|
+
...structShared,
|
|
384
|
+
type: 'record',
|
|
385
|
+
join: 'one',
|
|
386
|
+
};
|
|
387
|
+
const recStruct = field.mode === 'REPEATED' ? ifRepeatedRecord : elseRecord;
|
|
388
|
+
this.addFieldsToStructDef(recStruct, field);
|
|
389
|
+
structDef.fields.push(recStruct);
|
|
392
390
|
}
|
|
393
391
|
else {
|
|
394
392
|
const malloyType = (_a = this.dialect.sqlTypeToMalloyType(type)) !== null && _a !== void 0 ? _a : {
|
|
@@ -399,90 +397,53 @@ class BigQueryConnection extends connection_1.BaseConnection {
|
|
|
399
397
|
}
|
|
400
398
|
}
|
|
401
399
|
}
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
type: 'table',
|
|
409
|
-
tablePath,
|
|
410
|
-
},
|
|
411
|
-
structRelationship: {
|
|
412
|
-
type: 'basetable',
|
|
413
|
-
connectionName: this.name,
|
|
414
|
-
},
|
|
415
|
-
fields: [],
|
|
416
|
-
};
|
|
417
|
-
this.addFieldsToStructDef(structDef, schemaInfo.schema);
|
|
418
|
-
if (schemaInfo.needsTableSuffixPseudoColumn) {
|
|
419
|
-
structDef.fields.push({
|
|
420
|
-
type: 'string',
|
|
421
|
-
name: '_TABLE_SUFFIX',
|
|
422
|
-
});
|
|
400
|
+
async fetchSelectSchema(sqlSource) {
|
|
401
|
+
try {
|
|
402
|
+
const ret = { ...sqlSource };
|
|
403
|
+
ret.fields = [];
|
|
404
|
+
this.addFieldsToStructDef(ret, await this.getSQLBlockSchema(sqlSource));
|
|
405
|
+
return ret;
|
|
423
406
|
}
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
type: 'timestamp',
|
|
427
|
-
name: '_PARTITIONTIME',
|
|
428
|
-
});
|
|
407
|
+
catch (error) {
|
|
408
|
+
return error.message;
|
|
429
409
|
}
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
type: 'basetable',
|
|
450
|
-
connectionName: this.name,
|
|
451
|
-
},
|
|
452
|
-
fields: [],
|
|
453
|
-
};
|
|
454
|
-
this.addFieldsToStructDef(structDef, tableFieldSchema);
|
|
455
|
-
return structDef;
|
|
456
|
-
}
|
|
457
|
-
async fetchSchemaForTables(missing, { refreshTimestamp }) {
|
|
458
|
-
const schemas = {};
|
|
459
|
-
const errors = {};
|
|
460
|
-
for (const tableKey in missing) {
|
|
461
|
-
let inCache = this.schemaCache.get(tableKey);
|
|
462
|
-
if (!inCache ||
|
|
463
|
-
(refreshTimestamp && refreshTimestamp > inCache.timestamp)) {
|
|
464
|
-
const timestamp = refreshTimestamp !== null && refreshTimestamp !== void 0 ? refreshTimestamp : Date.now();
|
|
465
|
-
const tablePath = this.normalizeTablePath(missing[tableKey]);
|
|
466
|
-
try {
|
|
467
|
-
const tableFieldSchema = await this.getTableFieldSchema(tablePath);
|
|
468
|
-
inCache = {
|
|
469
|
-
schema: this.structDefFromTableSchema(tableKey, tablePath, tableFieldSchema),
|
|
470
|
-
timestamp,
|
|
471
|
-
};
|
|
472
|
-
this.schemaCache.set(tableKey, inCache);
|
|
473
|
-
}
|
|
474
|
-
catch (error) {
|
|
475
|
-
inCache = { error: error.message, timestamp };
|
|
476
|
-
}
|
|
410
|
+
}
|
|
411
|
+
async fetchTableSchema(tableName, tablePath) {
|
|
412
|
+
tablePath = this.normalizeTablePath(tablePath);
|
|
413
|
+
try {
|
|
414
|
+
const tableFieldSchema = await this.getTableFieldSchema(tablePath);
|
|
415
|
+
const tableDef = {
|
|
416
|
+
type: 'table',
|
|
417
|
+
name: tableName,
|
|
418
|
+
dialect: this.dialectName,
|
|
419
|
+
tablePath,
|
|
420
|
+
connection: this.name,
|
|
421
|
+
fields: [],
|
|
422
|
+
};
|
|
423
|
+
this.addFieldsToStructDef(tableDef, tableFieldSchema.schema);
|
|
424
|
+
if (tableFieldSchema.needsTableSuffixPseudoColumn) {
|
|
425
|
+
tableDef.fields.push({
|
|
426
|
+
type: 'string',
|
|
427
|
+
name: '_TABLE_SUFFIX',
|
|
428
|
+
});
|
|
477
429
|
}
|
|
478
|
-
if (
|
|
479
|
-
|
|
430
|
+
if (tableFieldSchema.needsPartitionTimePseudoColumn) {
|
|
431
|
+
tableDef.fields.push({
|
|
432
|
+
type: 'timestamp',
|
|
433
|
+
name: '_PARTITIONTIME',
|
|
434
|
+
});
|
|
480
435
|
}
|
|
481
|
-
|
|
482
|
-
|
|
436
|
+
if (tableFieldSchema.needsPartitionDatePseudoColumn) {
|
|
437
|
+
tableDef.fields.push({
|
|
438
|
+
type: 'date',
|
|
439
|
+
name: '_PARTITIONDATE',
|
|
440
|
+
});
|
|
483
441
|
}
|
|
442
|
+
return tableDef;
|
|
443
|
+
}
|
|
444
|
+
catch (error) {
|
|
445
|
+
return error.message;
|
|
484
446
|
}
|
|
485
|
-
return { schemas, errors };
|
|
486
447
|
}
|
|
487
448
|
async getSQLBlockSchema(sqlRef) {
|
|
488
449
|
// We do a simple retry-loop here, as a temporary fix for a transient
|
|
@@ -506,26 +467,6 @@ class BigQueryConnection extends connection_1.BaseConnection {
|
|
|
506
467
|
}
|
|
507
468
|
throw lastFetchError;
|
|
508
469
|
}
|
|
509
|
-
async fetchSchemaForSQLBlock(sqlRef, { refreshTimestamp }) {
|
|
510
|
-
const key = sqlRef.name;
|
|
511
|
-
let inCache = this.sqlSchemaCache.get(key);
|
|
512
|
-
if (!inCache ||
|
|
513
|
-
(refreshTimestamp && refreshTimestamp > inCache.timestamp)) {
|
|
514
|
-
const timestamp = refreshTimestamp !== null && refreshTimestamp !== void 0 ? refreshTimestamp : Date.now();
|
|
515
|
-
try {
|
|
516
|
-
const tableFieldSchema = await this.getSQLBlockSchema(sqlRef);
|
|
517
|
-
inCache = {
|
|
518
|
-
structDef: this.structDefFromSQLSchema(sqlRef, tableFieldSchema),
|
|
519
|
-
timestamp,
|
|
520
|
-
};
|
|
521
|
-
}
|
|
522
|
-
catch (error) {
|
|
523
|
-
inCache = { error: error.message, timestamp };
|
|
524
|
-
}
|
|
525
|
-
this.sqlSchemaCache.set(key, inCache);
|
|
526
|
-
}
|
|
527
|
-
return inCache;
|
|
528
|
-
}
|
|
529
470
|
// TODO this needs to extend the wait for results using a timeout set by the user,
|
|
530
471
|
// and probably needs to loop to check for results - BQ docs now say that after ~2min of waiting,
|
|
531
472
|
// no matter what you set for timeoutMs, they will probably just return.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@malloydata/db-bigquery",
|
|
3
|
-
"version": "0.0.195
|
|
3
|
+
"version": "0.0.195",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
"@google-cloud/bigquery": "^7.3.0",
|
|
26
26
|
"@google-cloud/common": "^5.0.1",
|
|
27
27
|
"@google-cloud/paginator": "^5.0.0",
|
|
28
|
-
"@malloydata/malloy": "^0.0.195
|
|
28
|
+
"@malloydata/malloy": "^0.0.195",
|
|
29
29
|
"gaxios": "^4.2.0"
|
|
30
30
|
}
|
|
31
31
|
}
|