@malloydata/malloy 0.0.138-dev240328203849 → 0.0.138-dev240331175328

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,11 +1,19 @@
1
1
  import { DialectFragment, Expr, ExtractUnit, Sampling, StructDef, TimeFieldType, TimeValue, TimestampUnit, TypecastFragment, FieldAtomicTypeDef } from '../model/malloy_types';
2
2
  import { DialectFunctionOverloadDef } from './functions';
3
+ type DialectFieldTypes = string | 'struct';
3
4
  interface DialectField {
4
- type: string;
5
+ type: DialectFieldTypes;
5
6
  sqlExpression: string;
6
7
  rawName: string;
7
8
  sqlOutputName: string;
8
9
  }
10
+ export interface DialectFieldTypeStruct extends DialectField {
11
+ type: 'struct';
12
+ nestedStruct: DialectFieldList;
13
+ isArray: boolean;
14
+ }
15
+ export declare function isDialectFieldStruct(d: DialectField): d is DialectFieldTypeStruct;
16
+ export type DialectFieldList = DialectField[];
9
17
  /**
10
18
  * Data which dialect methods need in order to correctly generate SQL.
11
19
  * Initially this is just timezone related, but I made this an interface
@@ -20,7 +28,7 @@ export interface QueryInfo {
20
28
  export declare const dayIndex: number;
21
29
  export declare function inDays(units: string): boolean;
22
30
  export declare function qtz(qi: QueryInfo): string | undefined;
23
- export type DialectFieldList = DialectField[];
31
+ export type OrderByClauseType = 'output_name' | 'ordinal';
24
32
  export declare abstract class Dialect {
25
33
  abstract name: string;
26
34
  abstract defaultNumberType: string;
@@ -42,6 +50,7 @@ export declare abstract class Dialect {
42
50
  supportsPipelinesInViews: boolean;
43
51
  supportsArraysInData: boolean;
44
52
  readsNestedData: boolean;
53
+ orderByClause: OrderByClauseType;
45
54
  abstract getGlobalFunctionDef(name: string): DialectFunctionOverloadDef[] | undefined;
46
55
  abstract quoteTablePath(tablePath: string): string;
47
56
  abstract sqlGroupSetTable(groupSetCount: number): string;
@@ -22,8 +22,12 @@
22
22
  * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
23
23
  */
24
24
  Object.defineProperty(exports, "__esModule", { value: true });
25
- exports.Dialect = exports.qtz = exports.inDays = exports.dayIndex = void 0;
25
+ exports.Dialect = exports.qtz = exports.inDays = exports.dayIndex = exports.isDialectFieldStruct = void 0;
26
26
  const malloy_types_1 = require("../model/malloy_types");
27
+ function isDialectFieldStruct(d) {
28
+ return d.type === 'struct';
29
+ }
30
+ exports.isDialectFieldStruct = isDialectFieldStruct;
27
31
  const allUnits = [
28
32
  'microsecond',
29
33
  'millisecond',
@@ -64,6 +68,8 @@ class Dialect {
64
68
  this.supportsArraysInData = true;
65
69
  // can read some version of ga_sample
66
70
  this.readsNestedData = true;
71
+ // ORDER BY 1 DESC
72
+ this.orderByClause = 'ordinal';
67
73
  }
68
74
  sqlFinalStage(_lastStageName, _fields) {
69
75
  throw new Error('Dialect has no final Stage but called Anyway');
@@ -1,6 +1,6 @@
1
- import { Expr, ExtractUnit, Sampling, TimeValue, TimestampUnit, TypecastFragment, FieldAtomicTypeDef } from '../../model/malloy_types';
1
+ import { Expr, ExtractUnit, Sampling, TimeValue, TimestampUnit, TypecastFragment, FieldAtomicTypeDef, DialectFragment } from '../../model/malloy_types';
2
2
  import { DialectFunctionOverloadDef } from '../functions';
3
- import { Dialect, DialectFieldList, QueryInfo } from '../dialect';
3
+ import { Dialect, DialectFieldList, OrderByClauseType, QueryInfo } from '../dialect';
4
4
  export declare class TrinoDialect extends Dialect {
5
5
  name: string;
6
6
  defaultNumberType: string;
@@ -21,9 +21,12 @@ export declare class TrinoDialect extends Dialect {
21
21
  supportsSafeCast: boolean;
22
22
  supportsNesting: boolean;
23
23
  cantPartitionWindowFunctionsOnExpressions: boolean;
24
+ orderByClause: OrderByClauseType;
24
25
  quoteTablePath(tablePath: string): string;
25
26
  sqlGroupSetTable(groupSetCount: number): string;
27
+ dialectExpr(qi: QueryInfo, df: DialectFragment): Expr;
26
28
  sqlAnyValue(groupSet: number, fieldName: string): string;
29
+ buildTypeExpression(fieldList: DialectFieldList): string;
27
30
  sqlAggregateTurtle(groupSet: number, fieldList: DialectFieldList, orderBy: string | undefined, limit: number | undefined): string;
28
31
  sqlAnyValueTurtle(groupSet: number, fieldList: DialectFieldList): string;
29
32
  sqlAnyValueLastTurtle(name: string, groupSet: number, sqlName: string): string;
@@ -17,7 +17,7 @@
17
17
  * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
18
  * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
19
19
  * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
20
- * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
20
+ * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,p
21
21
  * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
22
22
  * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
23
23
  */
@@ -54,15 +54,19 @@ function qtz(qi) {
54
54
  return tz;
55
55
  }
56
56
  }
57
+ const trinoTypeMap = {
58
+ 'string': 'VARCHAR',
59
+ 'number': 'DOUBLE',
60
+ };
57
61
  class TrinoDialect extends dialect_1.Dialect {
58
62
  constructor() {
59
63
  super(...arguments);
60
64
  this.name = 'trino';
61
- this.defaultNumberType = 'FLOAT64';
62
- this.defaultDecimalType = 'NUMERIC';
65
+ this.defaultNumberType = 'DOUBLE';
66
+ this.defaultDecimalType = 'DECIMAL';
63
67
  this.udfPrefix = '__udf';
64
68
  this.hasFinalStage = false;
65
- this.divisionIsInteger = false;
69
+ this.divisionIsInteger = true;
66
70
  this.supportsSumDistinctFunction = false;
67
71
  this.unnestWithNumbers = false;
68
72
  this.defaultSampling = { enable: false };
@@ -74,6 +78,7 @@ class TrinoDialect extends dialect_1.Dialect {
74
78
  this.supportsSafeCast = true;
75
79
  this.supportsNesting = true;
76
80
  this.cantPartitionWindowFunctionsOnExpressions = true;
81
+ this.orderByClause = 'output_name';
77
82
  // TODO(figutierrez): update.
78
83
  this.keywords = `
79
84
  ALL
@@ -174,24 +179,48 @@ class TrinoDialect extends dialect_1.Dialect {
174
179
  }
175
180
  quoteTablePath(tablePath) {
176
181
  // TODO: look into escaping.
177
- return `${tablePath.replace(/malloytest/g, 'malloy_demo.faa')}`;
182
+ //return `${tablePath.replace(/malloytest/g, 'malloy_demo.malloytest')}`;
183
+ return tablePath;
178
184
  }
179
185
  sqlGroupSetTable(groupSetCount) {
180
186
  return `CROSS JOIN (SELECT row_number() OVER() -1 group_set FROM UNNEST(SEQUENCE(0,${groupSetCount})))`;
181
187
  }
188
+ dialectExpr(qi, df) {
189
+ switch (df.function) {
190
+ case 'div': {
191
+ return (0, malloy_types_1.mkExpr) `CAST(${df.numerator} AS DOUBLE)/${df.denominator}`;
192
+ }
193
+ }
194
+ return super.dialectExpr(qi, df);
195
+ }
182
196
  sqlAnyValue(groupSet, fieldName) {
183
197
  return `ANY_VALUE(CASE WHEN group_set=${groupSet} THEN ${fieldName} END)`;
184
198
  }
199
+ buildTypeExpression(fieldList) {
200
+ const fields = [];
201
+ for (const f of fieldList) {
202
+ if ((0, dialect_1.isDialectFieldStruct)(f)) {
203
+ let s = `ROW(${this.buildTypeExpression(f.nestedStruct)})`;
204
+ if (f.isArray) {
205
+ s = `array(${s})`;
206
+ }
207
+ fields.push(s);
208
+ }
209
+ else {
210
+ fields.push(`${f.sqlOutputName} ${trinoTypeMap[f.type] || f.type}`);
211
+ }
212
+ }
213
+ return fields.join(', \n');
214
+ }
185
215
  // can array agg or any_value a struct...
186
216
  sqlAggregateTurtle(groupSet, fieldList, orderBy, limit) {
187
- let tail = '';
217
+ const expressions = fieldList.map(f => f.sqlExpression).join(',\n ');
218
+ const definitions = this.buildTypeExpression(fieldList);
219
+ let ret = `ARRAY_AGG(CASE WHEN group_set=${groupSet} THEN CAST(ROW(${expressions}) AS ROW(${definitions})) END ${orderBy})`;
188
220
  if (limit !== undefined) {
189
- tail += ` LIMIT ${limit}`;
221
+ ret = `SLICE(${ret}, 1, ${limit})`;
190
222
  }
191
- const fields = fieldList
192
- .map(f => `\n '${f.sqlOutputName}' VALUE ${f.sqlExpression}`)
193
- .join(', ');
194
- return `cast(COALESCE(SLICE(COALESCE(ARRAY_AGG(CASE WHEN group_set=${groupSet} THEN JSON_PARSE(JSON_OBJECT(${fields})) END \n ${orderBy} -- ${tail}\n), ARRAY[]), 1, ${limit}), ARRAY[]) as json)`;
223
+ return ret;
195
224
  }
196
225
  sqlAnyValueTurtle(groupSet, fieldList) {
197
226
  const fields = fieldList
@@ -235,10 +264,9 @@ class TrinoDialect extends dialect_1.Dialect {
235
264
  }
236
265
  }
237
266
  sqlSumDistinctHashedKey(sqlDistinctKey) {
238
- sqlDistinctKey = `CAST(${sqlDistinctKey} AS STRING)`;
239
- const upperPart = `cast(cast(concat('0x', substr(to_hex(md5(${sqlDistinctKey})), 1, 15)) as int64) as numeric) * 4294967296`;
240
- const lowerPart = `cast(cast(concat('0x', substr(to_hex(md5(${sqlDistinctKey})), 16, 8)) as int64) as numeric)`;
241
- // See the comment below on `sql_sum_distinct` for why we multiply by this decimal
267
+ sqlDistinctKey = `CAST(${sqlDistinctKey} AS VARCHAR)`;
268
+ const upperPart = `cast(from_base(substr(to_hex(md5(to_utf8(${sqlDistinctKey}))), 1, 15),16) as DECIMAL) * DECIMAL '4294967296' `;
269
+ const lowerPart = `cast(from_base(substr(to_hex(md5(to_utf8(${sqlDistinctKey}))), 16, 8),16) as DECIMAL) `;
242
270
  const precisionShiftMultiplier = '0.000000001';
243
271
  return `(${upperPart} + ${lowerPart}) * ${precisionShiftMultiplier}`;
244
272
  }
@@ -425,12 +453,15 @@ ${(0, utils_1.indent)(sql)}
425
453
  malloyTypeToSQLType(malloyType) {
426
454
  if (malloyType.type === 'number') {
427
455
  if (malloyType.numberType === 'integer') {
428
- return 'INT64';
456
+ return 'BIGINT';
429
457
  }
430
458
  else {
431
- return 'FLOAT64';
459
+ return 'DOUBLE';
432
460
  }
433
461
  }
462
+ else if (malloyType.type === 'string') {
463
+ return 'VARCHAR';
464
+ }
434
465
  return malloyType.type;
435
466
  }
436
467
  sqlTypeToMalloyType(_sqlType) {
@@ -438,7 +469,7 @@ ${(0, utils_1.indent)(sql)}
438
469
  return undefined;
439
470
  }
440
471
  castToString(expression) {
441
- return `CAST(${expression} as STRING)`;
472
+ return `CAST(${expression} as VARCHAR)`;
442
473
  }
443
474
  concat(...values) {
444
475
  return values.join(' || ');
@@ -268,6 +268,7 @@ declare class QueryQuery extends QueryField {
268
268
  generateDepthNFields(depth: number, resultSet: FieldInstanceResult, output: StageOutputContext, stageWriter: StageWriter): void;
269
269
  generateSQLDepthN(depth: number, stageWriter: StageWriter, stageName: string): string;
270
270
  genereateSQLCombineTurtles(stageWriter: StageWriter, stage0Name: string): string;
271
+ buildDialectFieldList(resultStruct: FieldInstanceResult): DialectFieldList;
271
272
  generateTurtleSQL(resultStruct: FieldInstanceResult, stageWriter: StageWriter, sqlFieldName: string, outputPipelinedSQL: OutputPipelinedSQL[]): string;
272
273
  generateTurtlePipelineSQL(fi: FieldInstanceResult, stageWriter: StageWriter, sourceSQLExpression: string): {
273
274
  structDef: StructDef;
@@ -2101,14 +2101,25 @@ class QueryQuery extends QueryField {
2101
2101
  // convert name to an index
2102
2102
  const fi = resultStruct.getField(f.field);
2103
2103
  if (fi && fi.fieldUsage.type === 'result') {
2104
- o.push(`${fi.fieldUsage.resultIndex} ${f.dir || 'ASC'}`);
2104
+ if (this.parent.dialect.orderByClause === 'ordinal') {
2105
+ o.push(`${fi.fieldUsage.resultIndex} ${f.dir || 'ASC'}`);
2106
+ }
2107
+ else if (this.parent.dialect.orderByClause === 'output_name') {
2108
+ o.push(`${f.field}${f.dir || 'ASC'}`);
2109
+ }
2105
2110
  }
2106
2111
  else {
2107
2112
  throw new Error(`Unknown field in ORDER BY ${f.field}`);
2108
2113
  }
2109
2114
  }
2110
2115
  else {
2111
- o.push(`${f.field} ${f.dir || 'ASC'}`);
2116
+ if (this.parent.dialect.orderByClause === 'ordinal') {
2117
+ o.push(`${f.field} ${f.dir || 'ASC'}`);
2118
+ }
2119
+ else if (this.parent.dialect.orderByClause === 'output_name') {
2120
+ const orderingField = resultStruct.getFieldByNumber(f.field);
2121
+ o.push(`${this.parent.dialect.sqlMaybeQuoteIdentifier(orderingField.name)} ${f.dir || 'ASC'}`);
2122
+ }
2112
2123
  }
2113
2124
  }
2114
2125
  if (o.length > 0) {
@@ -2446,9 +2457,49 @@ class QueryQuery extends QueryField {
2446
2457
  this.resultStage = this.generatePipelinedStages(outputPipelinedSQL, this.resultStage, stageWriter);
2447
2458
  return this.resultStage;
2448
2459
  }
2460
+ // create a simplified version of the StructDef for dialects.
2461
+ buildDialectFieldList(resultStruct) {
2462
+ const dialectFieldList = [];
2463
+ for (const [name, field] of resultStruct.allFields) {
2464
+ const sqlName = this.parent.dialect.sqlMaybeQuoteIdentifier(name);
2465
+ //
2466
+ if (resultStruct.firstSegment.type === 'reduce' &&
2467
+ field instanceof FieldInstanceResult) {
2468
+ const d = {
2469
+ type: 'struct',
2470
+ sqlExpression: this.parent.dialect.sqlMaybeQuoteIdentifier(`${name}__${resultStruct.groupSet}`),
2471
+ rawName: name,
2472
+ sqlOutputName: sqlName,
2473
+ isArray: field.getRepeatedResultType() === 'nested',
2474
+ nestedStruct: this.buildDialectFieldList(field),
2475
+ };
2476
+ dialectFieldList.push(d);
2477
+ }
2478
+ else if (resultStruct.firstSegment.type === 'reduce' &&
2479
+ field instanceof FieldInstanceField &&
2480
+ field.fieldUsage.type === 'result') {
2481
+ dialectFieldList.push({
2482
+ type: field.f.fieldDef.type,
2483
+ sqlExpression: this.parent.dialect.sqlMaybeQuoteIdentifier(`${name}__${resultStruct.groupSet}`),
2484
+ rawName: name,
2485
+ sqlOutputName: sqlName,
2486
+ });
2487
+ }
2488
+ else if (resultStruct.firstSegment.type === 'project' &&
2489
+ field instanceof FieldInstanceField &&
2490
+ field.fieldUsage.type === 'result') {
2491
+ dialectFieldList.push({
2492
+ type: field.type,
2493
+ sqlExpression: field.f.generateExpression(resultStruct),
2494
+ rawName: name,
2495
+ sqlOutputName: sqlName,
2496
+ });
2497
+ }
2498
+ }
2499
+ return dialectFieldList;
2500
+ }
2449
2501
  generateTurtleSQL(resultStruct, stageWriter, sqlFieldName, outputPipelinedSQL) {
2450
2502
  // let fieldsSQL: string[] = [];
2451
- const dialectFieldList = [];
2452
2503
  let orderBy = '';
2453
2504
  const limit = (0, malloy_types_1.isRawSegment)(resultStruct.firstSegment)
2454
2505
  ? undefined
@@ -2480,38 +2531,7 @@ class QueryQuery extends QueryField {
2480
2531
  if (obSQL.length > 0) {
2481
2532
  orderBy = ' ' + this.parent.dialect.sqlOrderBy(obSQL);
2482
2533
  }
2483
- for (const [name, field] of resultStruct.allFields) {
2484
- const sqlName = this.parent.dialect.sqlMaybeQuoteIdentifier(name);
2485
- //
2486
- if (resultStruct.firstSegment.type === 'reduce' &&
2487
- (field instanceof FieldInstanceResult ||
2488
- (field instanceof FieldInstanceField &&
2489
- field.fieldUsage.type === 'result'))) {
2490
- // fieldsSQL.push(`${name}__${resultStruct.groupSet} as ${sqlName}`);
2491
- // outputFieldNames.push(name);
2492
- dialectFieldList.push({
2493
- type: field instanceof FieldInstanceField
2494
- ? field.f.fieldDef.type
2495
- : 'struct',
2496
- sqlExpression: this.parent.dialect.sqlMaybeQuoteIdentifier(`${name}__${resultStruct.groupSet}`),
2497
- rawName: name,
2498
- sqlOutputName: sqlName,
2499
- });
2500
- }
2501
- else if (resultStruct.firstSegment.type === 'project' &&
2502
- field instanceof FieldInstanceField &&
2503
- field.fieldUsage.type === 'result') {
2504
- // fieldsSQL.push(
2505
- // `${field.f.generateExpression(resultStruct)} as ${sqlName}`
2506
- // );
2507
- dialectFieldList.push({
2508
- type: field.type,
2509
- sqlExpression: field.f.generateExpression(resultStruct),
2510
- rawName: name,
2511
- sqlOutputName: sqlName,
2512
- });
2513
- }
2514
- }
2534
+ const dialectFieldList = this.buildDialectFieldList(resultStruct);
2515
2535
  let resultType;
2516
2536
  let ret;
2517
2537
  if ((resultType = resultStruct.getRepeatedResultType()) !== 'nested') {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@malloydata/malloy",
3
- "version": "0.0.138-dev240328203849",
3
+ "version": "0.0.138-dev240331175328",
4
4
  "license": "MIT",
5
5
  "exports": {
6
6
  ".": "./dist/index.js",