@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.
@@ -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.fetchSchemaForSQLBlock(SQL_BLOCK_1, {});
215
+ await bq.fetchSchemaForSQLStruct(SQL_BLOCK_1, {});
216
216
  expect(getSQLBlockSchema).toBeCalledTimes(1);
217
217
  await new Promise(resolve => setTimeout(resolve));
218
- await bq.fetchSchemaForSQLBlock(SQL_BLOCK_1, {});
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.fetchSchemaForSQLBlock(SQL_BLOCK_2, {});
222
+ await bq.fetchSchemaForSQLStruct(SQL_BLOCK_2, {});
223
223
  expect(getSQLBlockSchema).toBeCalledTimes(1);
224
224
  await new Promise(resolve => setTimeout(resolve));
225
- await bq.fetchSchemaForSQLBlock(SQL_BLOCK_2, {
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: 'sqlBlock',
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: 'sqlBlock',
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, FetchSchemaOptions, MalloyQueryData, PersistSQLResults, QueryData, QueryDataRow, QueryOptionsReader, QueryRunStats, RunSQLOptions, SQLBlock, StreamingConnection, StructDef } from '@malloydata/malloy';
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: SQLBlock, options?: RunSQLOptions): Promise<{
69
+ runSQLBlockAndFetchResultSchema(sqlBlock: SQLSourceDef, options?: RunSQLOptions): Promise<{
72
70
  data: MalloyQueryData;
73
- schema: StructDef;
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
- private structDefFromTableSchema;
86
- private structDefFromSQLSchema;
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 = this.structDefFromSQLSchema(sqlBlock, schemaRaw);
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
- // check for an array
361
- if (field.mode === 'REPEATED' && !['STRUCT', 'RECORD'].includes(type)) {
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 innerStructDef = {
365
- type: 'struct',
366
- name,
367
- dialect: this.dialectName,
368
- structSource: { type: 'nested' },
369
- structRelationship: {
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(innerStructDef);
372
+ structDef.fields.push(arrayField);
377
373
  }
378
374
  }
379
- else if (['STRUCT', 'RECORD'].includes(type)) {
380
- const innerStructDef = {
381
- type: 'struct',
382
- name,
383
- dialect: this.dialectName,
384
- structSource: field.mode === 'REPEATED' ? { type: 'nested' } : { type: 'inline' },
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
- this.addFieldsToStructDef(innerStructDef, field);
391
- structDef.fields.push(innerStructDef);
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
- structDefFromTableSchema(tableKey, tablePath, schemaInfo) {
403
- const structDef = {
404
- type: 'struct',
405
- name: tableKey,
406
- dialect: this.dialectName,
407
- structSource: {
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
- if (schemaInfo.needsPartitionTimePseudoColumn) {
425
- structDef.fields.push({
426
- type: 'timestamp',
427
- name: '_PARTITIONTIME',
428
- });
407
+ catch (error) {
408
+ return error.message;
429
409
  }
430
- if (schemaInfo.needsPartitionDatePseudoColumn) {
431
- structDef.fields.push({
432
- type: 'date',
433
- name: '_PARTITIONDATE',
434
- });
435
- }
436
- return structDef;
437
- }
438
- structDefFromSQLSchema(sqlBlock, tableFieldSchema) {
439
- const structDef = {
440
- type: 'struct',
441
- name: sqlBlock.name,
442
- dialect: this.dialectName,
443
- structSource: {
444
- type: 'sql',
445
- method: 'subquery',
446
- sqlBlock,
447
- },
448
- structRelationship: {
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 (inCache.schema !== undefined) {
479
- schemas[tableKey] = inCache.schema;
430
+ if (tableFieldSchema.needsPartitionTimePseudoColumn) {
431
+ tableDef.fields.push({
432
+ type: 'timestamp',
433
+ name: '_PARTITIONTIME',
434
+ });
480
435
  }
481
- else {
482
- errors[tableKey] = inCache.error || 'Unknown schema fetch error';
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-dev241003204905",
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-dev241003204905",
28
+ "@malloydata/malloy": "^0.0.195",
29
29
  "gaxios": "^4.2.0"
30
30
  }
31
31
  }