@malloydata/db-trino 0.0.220-dev241203204946 → 0.0.220-dev241205013400

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.
@@ -1,4 +1,4 @@
1
- import { Connection, ConnectionConfig, MalloyQueryData, PersistSQLResults, QueryData, QueryOptionsReader, QueryRunStats, RunSQLOptions, StructDef, TableSourceDef, SQLSourceDef, AtomicTypeDef } from '@malloydata/malloy';
1
+ import { Connection, ConnectionConfig, MalloyQueryData, PersistSQLResults, QueryData, QueryOptionsReader, QueryRunStats, RunSQLOptions, TrinoDialect, StructDef, TableSourceDef, SQLSourceDef, AtomicTypeDef } from '@malloydata/malloy';
2
2
  import { BaseConnection } from '@malloydata/malloy/connection';
3
3
  export interface TrinoManagerOptions {
4
4
  credentials?: {
@@ -31,7 +31,7 @@ export interface BaseRunner {
31
31
  }
32
32
  export declare abstract class TrinoPrestoConnection extends BaseConnection implements Connection, PersistSQLResults {
33
33
  name: string;
34
- private readonly dialect;
34
+ protected readonly dialect: TrinoDialect;
35
35
  static DEFAULT_QUERY_OPTIONS: RunSQLOptions;
36
36
  private queryOptions?;
37
37
  private client;
@@ -45,6 +45,7 @@ export declare abstract class TrinoPrestoConnection extends BaseConnection imple
45
45
  convertRow(structDef: StructDef, rawRow: unknown): {};
46
46
  convertNest(structDef: StructDef, _data: unknown): unknown[];
47
47
  runSQL(sqlCommand: string, options?: RunSQLOptions, _rowIndex?: number): Promise<MalloyQueryData>;
48
+ private resultRow;
48
49
  runSQLBlockAndFetchResultSchema(_sqlBlock: SQLSourceDef, _options?: RunSQLOptions): Promise<{
49
50
  data: MalloyQueryData;
50
51
  schema: StructDef;
@@ -194,30 +194,39 @@ class TrinoPrestoConnection extends connection_1.BaseConnection {
194
194
  for (let i = 0; i < columns.length; i++) {
195
195
  const column = columns[i];
196
196
  const schemaColumn = malloyColumns[i];
197
- if (schemaColumn.type === 'record') {
198
- malloyRow[column.name] = this.convertRow(schemaColumn, row[i]);
199
- }
200
- else if (schemaColumn.type === 'array') {
201
- malloyRow[column.name] = this.convertNest(schemaColumn, row[i]);
202
- }
203
- else if (schemaColumn.type === 'number' &&
204
- typeof row[i] === 'string') {
205
- // decimal numbers come back as strings
206
- malloyRow[column.name] = Number(row[i]);
207
- }
208
- else if (schemaColumn.type === 'timestamp' &&
209
- typeof row[i] === 'string') {
210
- // timestamps come back as strings
211
- malloyRow[column.name] = new Date(row[i]);
212
- }
213
- else {
214
- malloyRow[column.name] = row[i];
215
- }
197
+ malloyRow[column.name] = this.resultRow(schemaColumn, row[i]);
216
198
  }
217
199
  malloyRows.push(malloyRow);
218
200
  }
219
201
  return { rows: malloyRows, totalRows: malloyRows.length };
220
202
  }
203
+ resultRow(colSchema, rawRow) {
204
+ if (colSchema.type === 'record') {
205
+ return this.convertRow(colSchema, rawRow);
206
+ }
207
+ else if (colSchema.type === 'array') {
208
+ const elType = colSchema.elementTypeDef;
209
+ if (elType.type === 'record_element') {
210
+ return this.convertNest(colSchema, rawRow);
211
+ }
212
+ let theArray = this.unpackArray(rawRow);
213
+ if (elType.type === 'array') {
214
+ theArray = theArray.map(el => this.resultRow(elType, el));
215
+ }
216
+ return theArray;
217
+ }
218
+ else if (colSchema.type === 'number' && typeof rawRow === 'string') {
219
+ // decimal numbers come back as strings
220
+ return Number(rawRow);
221
+ }
222
+ else if (colSchema.type === 'timestamp' && typeof rawRow === 'string') {
223
+ // timestamps come back as strings
224
+ return new Date(rawRow);
225
+ }
226
+ else {
227
+ return rawRow;
228
+ }
229
+ }
221
230
  async runSQLBlockAndFetchResultSchema(_sqlBlock, _options) {
222
231
  throw new Error('Not implemented 3');
223
232
  }
@@ -413,32 +422,8 @@ class PrestoConnection extends TrinoPrestoConnection {
413
422
  if ((lines === null || lines === void 0 ? void 0 : lines.length) === 0) {
414
423
  throw new Error('Received invalid explain result when trying to fetch schema.');
415
424
  }
416
- let outputLine = lines[0];
417
- const namesIndex = outputLine.indexOf('][');
418
- outputLine = outputLine.substring(namesIndex + 2);
419
- const lineParts = outputLine.split('] => [');
420
- if (lineParts.length !== 2) {
421
- throw new Error('There was a problem parsing schema from Explain.');
422
- }
423
- const fieldNamesPart = lineParts[0];
424
- const fieldNames = fieldNamesPart.split(',').map(e => e.trim());
425
- let schemaData = lineParts[1];
426
- schemaData = schemaData.substring(0, schemaData.length - 1);
427
- const rawFieldsTarget = schemaData
428
- .split(',')
429
- .map(e => e.trim())
430
- .map(e => e.split(':'));
431
- if (rawFieldsTarget.length !== fieldNames.length) {
432
- throw new Error('There was a problem parsing schema from Explain. Field names size do not match target fields with types.');
433
- }
434
- for (let index = 0; index < fieldNames.length; index++) {
435
- const name = fieldNames[index];
436
- const type = rawFieldsTarget[index][1];
437
- structDef.fields.push({
438
- name,
439
- ...this.malloyTypeFromTrinoType(name, type),
440
- });
441
- }
425
+ const schemaDesc = new PrestoExplainParser(lines[0], this.dialect);
426
+ structDef.fields = schemaDesc.parseExplain();
442
427
  }
443
428
  unpackArray(data) {
444
429
  return JSON.parse(data);
@@ -456,4 +441,124 @@ class TrinoConnection extends TrinoPrestoConnection {
456
441
  }
457
442
  }
458
443
  exports.TrinoConnection = TrinoConnection;
444
+ /**
445
+ * A hand built parser for schema lines, roughly this grammar
446
+ * SCHEMA_LINE: - Output [PlanName N] [NAME_LIST] => [TYPE_LIST]
447
+ * NAME_LIST: NAME (, NAME)*
448
+ * TYPE_LIST: TYPE_SPEC (, TYPE_SPEC)*
449
+ * TYPE_SPEC: exprN ':' TYPE
450
+ * TYPE: REC_TYPE | ARRAY_TYPE | SQL_TYPE
451
+ * ARRAY_TYPE: ARRAY '(' TYPE ')'
452
+ * REC_TYPE: REC '(' "name" TYPE (, "name" TYPE)* ')'
453
+ */
454
+ class PrestoExplainParser extends malloy_1.TinyParser {
455
+ constructor(input, dialect) {
456
+ super(input, {
457
+ space: /^\s+/,
458
+ arrow: /^=>/,
459
+ char: /^[,:[\]()-]/,
460
+ id: /^\w+/,
461
+ quoted_name: /^"\w+"/,
462
+ });
463
+ this.input = input;
464
+ this.dialect = dialect;
465
+ }
466
+ fieldNameList() {
467
+ this.skipTo(']'); // Skip to end of plan
468
+ this.next('['); // Expect start of name list
469
+ const fieldNames = [];
470
+ for (;;) {
471
+ const nmToken = this.next('id');
472
+ fieldNames.push(nmToken.text);
473
+ const sep = this.next();
474
+ if (sep.type === ',') {
475
+ continue;
476
+ }
477
+ if (sep.type !== ']') {
478
+ throw this.parseError(`Unexpected '${sep.text}' while getting field name list`);
479
+ }
480
+ break;
481
+ }
482
+ return fieldNames;
483
+ }
484
+ parseExplain() {
485
+ const fieldNames = this.fieldNameList();
486
+ const fields = [];
487
+ this.next('arrow', '[');
488
+ for (let nameIndex = 0;; nameIndex += 1) {
489
+ const name = fieldNames[nameIndex];
490
+ this.next('id', ':');
491
+ const nextType = this.typeDef();
492
+ fields.push({ ...nextType, name });
493
+ const sep = this.next();
494
+ if (sep.text === ',') {
495
+ continue;
496
+ }
497
+ if (sep.text !== ']') {
498
+ throw this.parseError(`Unexpected '${sep.text}' between field types`);
499
+ }
500
+ break;
501
+ }
502
+ if (fields.length !== fieldNames.length) {
503
+ throw new Error(`Presto schema error mismatched ${fields.length} types and ${fieldNames.length} fields`);
504
+ }
505
+ return fields;
506
+ }
507
+ typeDef() {
508
+ const typToken = this.next();
509
+ if (typToken.type === 'eof') {
510
+ throw this.parseError('Unexpected EOF parsing type, expected a type name');
511
+ }
512
+ else if (typToken.text === 'row' && this.next('(')) {
513
+ const fields = [];
514
+ for (;;) {
515
+ const name = this.next('quoted_name');
516
+ const getDef = this.typeDef();
517
+ fields.push({ ...getDef, name: name.text });
518
+ const sep = this.next();
519
+ if (sep.text === ')') {
520
+ break;
521
+ }
522
+ if (sep.text === ',') {
523
+ continue;
524
+ }
525
+ }
526
+ const def = {
527
+ type: 'record',
528
+ name: '',
529
+ join: 'one',
530
+ dialect: this.dialect.name,
531
+ fields,
532
+ };
533
+ return def;
534
+ }
535
+ else if (typToken.text === 'array' && this.next('(')) {
536
+ const elType = this.typeDef();
537
+ this.next(')');
538
+ const def = {
539
+ type: 'array',
540
+ name: '',
541
+ dialect: this.dialect.name,
542
+ join: 'many',
543
+ elementTypeDef: elType.type === 'record' ? { type: 'record_element' } : elType,
544
+ fields: elType.type === 'record' ? elType.fields : (0, malloy_1.arrayEachFields)(elType),
545
+ };
546
+ return def;
547
+ }
548
+ else if (typToken.type === 'id') {
549
+ const sqlType = typToken.text;
550
+ const def = this.dialect.sqlTypeToMalloyType(sqlType);
551
+ if (def === undefined) {
552
+ throw this.parseError(`Can't parse presto type ${sqlType}`);
553
+ }
554
+ if (sqlType === 'varchar') {
555
+ if (this.peek().type === '(') {
556
+ this.next('(', 'id', ')');
557
+ }
558
+ }
559
+ return def;
560
+ }
561
+ throw this.parseError(`'${typToken.text}' unexpected while looking for a type`);
562
+ }
563
+ }
459
564
  //# sourceMappingURL=trino_connection.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@malloydata/db-trino",
3
- "version": "0.0.220-dev241203204946",
3
+ "version": "0.0.220-dev241205013400",
4
4
  "license": "MIT",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -22,7 +22,7 @@
22
22
  "prepublishOnly": "npm run build"
23
23
  },
24
24
  "dependencies": {
25
- "@malloydata/malloy": "^0.0.220-dev241203204946",
25
+ "@malloydata/malloy": "^0.0.220-dev241205013400",
26
26
  "@prestodb/presto-js-client": "^1.0.0",
27
27
  "gaxios": "^4.2.0",
28
28
  "trino-client": "^0.2.2"