@malloydata/malloy 0.0.2 → 0.0.4

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.
Files changed (67) hide show
  1. package/README.md +12 -11
  2. package/dist/dialect/duckdb.d.ts +1 -1
  3. package/dist/dialect/postgres.d.ts +1 -1
  4. package/dist/index.d.ts +4 -4
  5. package/dist/lang/ast/ast-expr.d.ts +0 -1
  6. package/dist/lang/field-space.d.ts +9 -7
  7. package/dist/lang/lib/Malloy/MalloyLexer.d.ts +108 -106
  8. package/dist/lang/lib/Malloy/MalloyLexer.js +1019 -1006
  9. package/dist/lang/lib/Malloy/MalloyParser.d.ts +581 -590
  10. package/dist/lang/lib/Malloy/MalloyParser.js +1141 -1202
  11. package/dist/lang/lib/Malloy/MalloyParserListener.d.ts +2015 -0
  12. package/dist/lang/lib/Malloy/MalloyParserListener.js +4 -0
  13. package/dist/lang/lib/Malloy/MalloyParserVisitor.d.ts +1269 -0
  14. package/dist/lang/lib/Malloy/MalloyParserVisitor.js +4 -0
  15. package/dist/lang/parse-to-ast.d.ts +3 -4
  16. package/dist/lang/parse-to-ast.js +1 -1
  17. package/dist/lang/parse-tree-walkers/document-completion-walker.js +2 -0
  18. package/dist/lang/parse-tree-walkers/explore-query-walker.d.ts +2 -2
  19. package/dist/malloy.d.ts +1 -1
  20. package/dist/md5.d.ts +1 -0
  21. package/dist/md5.js +30 -0
  22. package/dist/model/malloy_types.d.ts +3 -1
  23. package/package.json +9 -14
  24. package/dist/connection_utils.js +0 -56
  25. package/dist/dialect/dialect-map.d.ts +0 -3
  26. package/dist/dialect/dialect-map.js +0 -33
  27. package/dist/dialect/dialect.js +0 -77
  28. package/dist/dialect/dialect_map.js +0 -35
  29. package/dist/dialect/duckdb.js +0 -349
  30. package/dist/dialect/index.js +0 -27
  31. package/dist/dialect/postgres.js +0 -308
  32. package/dist/dialect/standardsql.js +0 -361
  33. package/dist/index.js +0 -47
  34. package/dist/lang/ast/apply-expr.js +0 -231
  35. package/dist/lang/ast/ast-expr.js +0 -1051
  36. package/dist/lang/ast/ast-main.js +0 -2086
  37. package/dist/lang/ast/ast-time-expr.js +0 -549
  38. package/dist/lang/ast/ast-types.js +0 -215
  39. package/dist/lang/ast/index.js +0 -34
  40. package/dist/lang/ast/time-utils.js +0 -94
  41. package/dist/lang/field-space.js +0 -629
  42. package/dist/lang/field-utils.js +0 -33
  43. package/dist/lang/index.js +0 -22
  44. package/dist/lang/parse-log.js +0 -41
  45. package/dist/lang/reference-list.js +0 -80
  46. package/dist/lang/space-field.js +0 -346
  47. package/dist/lang/test/document-help-context-walker.spec.js +0 -45
  48. package/dist/lang/test/document-symbol-walker.spec.js +0 -100
  49. package/dist/lang/test/field-symbols.spec.js +0 -172
  50. package/dist/lang/test/parse.spec.js +0 -1936
  51. package/dist/lang/test/test-translator.js +0 -334
  52. package/dist/lang/zone.js +0 -97
  53. package/dist/malloy.js +0 -2354
  54. package/dist/model/index.js +0 -34
  55. package/dist/model/malloy-query.d.ts +0 -313
  56. package/dist/model/malloy-query.js +0 -2603
  57. package/dist/model/malloy-types.d.ts +0 -404
  58. package/dist/model/malloy-types.js +0 -242
  59. package/dist/model/malloy_query.js +0 -2996
  60. package/dist/model/malloy_types.js +0 -283
  61. package/dist/model/sql-block.d.ts +0 -11
  62. package/dist/model/sql-block.js +0 -38
  63. package/dist/model/sql_block.js +0 -41
  64. package/dist/model/utils.js +0 -84
  65. package/dist/runtime-types.d.ts +0 -116
  66. package/dist/runtime-types.js +0 -40
  67. package/dist/runtime_types.js +0 -15
@@ -1,2603 +0,0 @@
1
- "use strict";
2
- /*
3
- * Copyright 2021 Google LLC
4
- *
5
- * This program is free software; you can redistribute it and/or
6
- * modify it under the terms of the GNU General Public License
7
- * version 2 as published by the Free Software Foundation.
8
- *
9
- * This program is distributed in the hope that it will be useful,
10
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
11
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12
- * GNU General Public License for more details.
13
- */
14
- var __importDefault = (this && this.__importDefault) || function (mod) {
15
- return (mod && mod.__esModule) ? mod : { "default": mod };
16
- };
17
- Object.defineProperty(exports, "__esModule", { value: true });
18
- exports.QueryModel = exports.Segment = void 0;
19
- const lodash_1 = require("lodash");
20
- const standardsql_1 = require("../dialect/standardsql");
21
- const dialect_1 = require("../dialect");
22
- const malloy_types_1 = require("./malloy-types");
23
- const utils_1 = require("./utils");
24
- const malloy_1 = require("../malloy");
25
- const md5_1 = __importDefault(require("md5"));
26
- function extendStructDef(structDef, extendSource) {
27
- return {
28
- ...structDef,
29
- fields: [...structDef.fields, ...extendSource],
30
- };
31
- }
32
- class StageWriter {
33
- constructor(parent) {
34
- this.withs = [];
35
- this.udfs = [];
36
- this.pdts = [];
37
- this.stagePrefix = "__stage";
38
- this.parent = parent;
39
- }
40
- getName(id) {
41
- return `${this.stagePrefix}${id}`;
42
- }
43
- root() {
44
- if (this.parent === undefined) {
45
- return this;
46
- }
47
- else {
48
- return this.parent.root();
49
- }
50
- }
51
- addStage(sql) {
52
- this.withs.push(sql);
53
- return this.getName(this.withs.length - 1);
54
- }
55
- addUDF(stageWriter, dialect) {
56
- // eslint-disable-next-line prefer-const
57
- let { sql, lastStageName } = stageWriter.combineStages(true);
58
- if (lastStageName === undefined) {
59
- throw new Error("Internal Error: no stage to combine");
60
- }
61
- sql += dialect.sqlCreateFunctionCombineLastStage(lastStageName);
62
- const id = `${dialect.udfPrefix}${this.root().udfs.length}`;
63
- sql = dialect.sqlCreateFunction(id, sql);
64
- this.root().udfs.push(sql);
65
- return id;
66
- }
67
- addPDT(baseName, dialect) {
68
- const sql = this.combineStages(false).sql + this.withs[this.withs.length - 1];
69
- const tableName = "scratch." + baseName + (0, md5_1.default)(sql);
70
- this.root().pdts.push(dialect.sqlCreateTableAsSelect(tableName, sql));
71
- return tableName;
72
- }
73
- // combine all the stages except the last one into a WITH statement
74
- // return SQL and the last stage name
75
- combineStages(includeLastStage) {
76
- let lastStageName = this.getName(0);
77
- let prefix = `WITH `;
78
- let w = "";
79
- for (let i = 0; i < this.withs.length - (includeLastStage ? 0 : 1); i++) {
80
- const sql = this.withs[i];
81
- lastStageName = this.getName(i);
82
- if (sql === undefined) {
83
- throw new Error(`Expected sql WITH to be present for stage ${lastStageName}.`);
84
- }
85
- w += `${prefix}${lastStageName} AS (\n${(0, utils_1.indent)(sql)})\n`;
86
- prefix = ", ";
87
- }
88
- return { sql: w, lastStageName };
89
- }
90
- /** emit the SQL for all the stages. */
91
- generateSQLStages() {
92
- const lastStageNum = this.withs.length - 1;
93
- if (lastStageNum < 0) {
94
- throw new Error("No SQL generated");
95
- }
96
- const udfs = this.udfs.join(`\n`);
97
- const pdts = this.pdts.join(`\n`);
98
- const sql = this.combineStages(false).sql;
99
- return udfs + pdts + sql + this.withs[lastStageNum];
100
- }
101
- }
102
- // type QueryNodeType =
103
- // | "abstract"
104
- // | "dimension"
105
- // | "measure"
106
- // | "query"
107
- // | "turtle"
108
- // | "struct";
109
- class GenerateState {
110
- withWhere(s) {
111
- const newState = new GenerateState();
112
- newState.whereSQL = s;
113
- newState.applyValue = this.applyValue;
114
- return newState;
115
- }
116
- withApply(s) {
117
- const newState = new GenerateState();
118
- newState.whereSQL = this.whereSQL;
119
- newState.applyValue = s;
120
- return newState;
121
- }
122
- }
123
- class QueryNode {
124
- constructor(fieldDef) {
125
- this.fieldDef = fieldDef;
126
- }
127
- getIdentifier() {
128
- return (0, malloy_types_1.getIdentifier)(this.fieldDef);
129
- }
130
- getChildByName(_name) {
131
- return undefined;
132
- }
133
- }
134
- class QueryField extends QueryNode {
135
- constructor(fieldDef, parent) {
136
- super(fieldDef);
137
- this.parent = parent;
138
- this.fieldDef = fieldDef;
139
- }
140
- mayNeedUniqueKey() {
141
- return false;
142
- }
143
- getJoinableParent() {
144
- // if it is inline it should always have a parent
145
- const parent = this.parent;
146
- if (parent.fieldDef.structRelationship.type === "inline") {
147
- if (parent) {
148
- return parent.getJoinableParent();
149
- }
150
- else {
151
- throw new Error(`Internal Error: inline struct cannot be root`);
152
- }
153
- }
154
- return parent;
155
- }
156
- caseGroup(groupSets, s) {
157
- if (groupSets.length === 0) {
158
- return s;
159
- }
160
- else {
161
- const exp = groupSets.length === 1
162
- ? `=${groupSets[0]}`
163
- : ` IN (${groupSets.join(",")})`;
164
- return `CASE WHEN group_set${exp} THEN\n ${s}\n END`;
165
- }
166
- }
167
- getFullOutputName() {
168
- return this.parent.getFullOutputName() + this.getIdentifier();
169
- }
170
- generateFieldFragment(resultSet, context, expr, state) {
171
- // find the structDef and return the path to the field...
172
- const field = context.getFieldByName(expr.path);
173
- if ((0, malloy_types_1.hasExpression)(field.fieldDef)) {
174
- return this.generateExpressionFromExpr(resultSet, field.parent, field.fieldDef.e, state);
175
- }
176
- else {
177
- // return field.parent.getIdentifier() + "." + field.fieldDef.name;
178
- return field.generateExpression(resultSet);
179
- }
180
- }
181
- generateParameterFragment(resultSet, context, expr, state) {
182
- // find the structDef and return the path to the field...
183
- const param = context.parameters()[expr.path];
184
- if ((0, malloy_types_1.isValueParameter)(param)) {
185
- if (param.value) {
186
- return this.generateExpressionFromExpr(resultSet, context, param.value, state);
187
- }
188
- }
189
- else if (param.condition) {
190
- return this.generateExpressionFromExpr(resultSet, context, param.condition, state);
191
- }
192
- throw new Error(`Can't generate SQL, no value for ${expr.path}`);
193
- }
194
- generateFilterFragment(resultSet, context, expr, state) {
195
- const allWhere = new utils_1.AndChain(state.whereSQL);
196
- for (const cond of expr.filterList) {
197
- allWhere.add(this.generateExpressionFromExpr(resultSet, context, cond.expression, state.withWhere()));
198
- }
199
- return this.generateExpressionFromExpr(resultSet, context, expr.e, state.withWhere(allWhere.sql()));
200
- }
201
- generateDimFragment(resultSet, context, expr, state) {
202
- let dim = this.generateExpressionFromExpr(resultSet, context, expr.e, state);
203
- if (state.whereSQL) {
204
- dim = `CASE WHEN ${state.whereSQL} THEN ${dim} END`;
205
- }
206
- return dim;
207
- }
208
- generateDistinctKeyIfNecessary(resultSet, context, structPath) {
209
- let struct = context;
210
- if (structPath) {
211
- struct = this.parent.root().getStructByName(structPath);
212
- }
213
- if (struct.needsSymetricCalculation(resultSet)) {
214
- return struct.getDistinctKey().generateExpression(resultSet);
215
- }
216
- else {
217
- return undefined;
218
- }
219
- }
220
- generateSumFragment(resultSet, context, expr, state) {
221
- const dimSQL = this.generateDimFragment(resultSet, context, expr, state);
222
- const distinctKeySQL = this.generateDistinctKeyIfNecessary(resultSet, context, expr.structPath);
223
- if (distinctKeySQL) {
224
- return sqlSumDistinct(this.parent.dialect, dimSQL, distinctKeySQL);
225
- }
226
- else {
227
- return `SUM(${dimSQL})`;
228
- }
229
- }
230
- generateSymmetricFragment(resultSet, context, expr, state) {
231
- const dimSQL = this.generateDimFragment(resultSet, context, expr, state);
232
- const f = expr.function === "count_distinct"
233
- ? "count(distinct "
234
- : expr.function + "(";
235
- return `${f}${dimSQL})`;
236
- }
237
- generateAvgFragment(resultSet, context, expr, state) {
238
- // find the structDef and return the path to the field...
239
- const dimSQL = this.generateDimFragment(resultSet, context, expr, state);
240
- const distinctKeySQL = this.generateDistinctKeyIfNecessary(resultSet, context, expr.structPath);
241
- if (distinctKeySQL) {
242
- let countDistinctKeySQL = distinctKeySQL;
243
- if (state.whereSQL) {
244
- countDistinctKeySQL = `CASE WHEN ${state.whereSQL} THEN ${distinctKeySQL} END`;
245
- }
246
- return `${sqlSumDistinct(this.parent.dialect, dimSQL, distinctKeySQL)}/NULLIF(COUNT(DISTINCT ${countDistinctKeySQL}),0)`;
247
- }
248
- else {
249
- return `AVG(${dimSQL})`;
250
- }
251
- }
252
- generateCountFragment(resultSet, context, expr, state) {
253
- let func = "COUNT(";
254
- let thing = "1";
255
- const distinctKeySQL = this.generateDistinctKeyIfNecessary(resultSet, context, expr.structPath);
256
- if (distinctKeySQL) {
257
- func = "COUNT(DISTINCT";
258
- thing = distinctKeySQL;
259
- }
260
- // find the structDef and return the path to the field...
261
- if (state.whereSQL) {
262
- return `${func} CASE WHEN ${state.whereSQL} THEN ${thing} END)`;
263
- }
264
- else {
265
- return `${func} ${thing})`;
266
- }
267
- }
268
- generateExpressionFromExpr(resultSet, context, e, state = new GenerateState()) {
269
- let s = "";
270
- for (const expr of e) {
271
- if (typeof expr === "string") {
272
- s += expr;
273
- }
274
- else if ((0, malloy_types_1.isFieldFragment)(expr)) {
275
- s += this.generateFieldFragment(resultSet, context, expr, state);
276
- }
277
- else if ((0, malloy_types_1.isParameterFragment)(expr)) {
278
- s += this.generateParameterFragment(resultSet, context, expr, state);
279
- }
280
- else if ((0, malloy_types_1.isFilterFragment)(expr)) {
281
- s += this.generateFilterFragment(resultSet, context, expr, state);
282
- }
283
- else if ((0, malloy_types_1.isAggregateFragment)(expr)) {
284
- if (expr.function === "sum") {
285
- s += this.generateSumFragment(resultSet, context, expr, state);
286
- }
287
- else if (expr.function === "avg") {
288
- s += this.generateAvgFragment(resultSet, context, expr, state);
289
- }
290
- else if (expr.function === "count") {
291
- s += this.generateCountFragment(resultSet, context, expr, state);
292
- }
293
- else if (["count_distinct", "min", "max"].includes(expr.function)) {
294
- s += this.generateSymmetricFragment(resultSet, context, expr, state);
295
- }
296
- else {
297
- throw new Error(`Internal Error: Unknown aggregate function ${expr.function}`);
298
- }
299
- }
300
- else if ((0, malloy_types_1.isApplyFragment)(expr)) {
301
- const applyVal = this.generateExpressionFromExpr(resultSet, context, expr.value, state);
302
- s += this.generateExpressionFromExpr(resultSet, context, expr.to, state.withApply(applyVal));
303
- }
304
- else if ((0, malloy_types_1.isApplyValue)(expr)) {
305
- if (state.applyValue) {
306
- s += state.applyValue;
307
- }
308
- else {
309
- throw new Error(`Internal Error: Partial application value referenced but not provided`);
310
- }
311
- }
312
- else {
313
- throw new Error(`Internal Error: Unknown expression fragment ${JSON.stringify(expr, undefined, 2)}`);
314
- }
315
- }
316
- return s;
317
- }
318
- generateExpression(resultSet) {
319
- if ((0, malloy_types_1.hasExpression)(this.fieldDef)) {
320
- return this.generateExpressionFromExpr(resultSet, this.parent, this.fieldDef.e);
321
- }
322
- return this.parent.dialect.sqlFieldReference(this.parent.getIdentifier(), this.fieldDef.name, this.fieldDef.type, this.parent.fieldDef.structSource.type === "nested" ||
323
- this.parent.fieldDef.structSource.type === "inline" ||
324
- (this.parent.fieldDef.structSource.type === "sql" &&
325
- this.parent.fieldDef.structSource.method === "nested"));
326
- }
327
- }
328
- function isAggregateField(f) {
329
- return f instanceof QueryAtomicField && f.isAggregate();
330
- }
331
- function isScalarField(f) {
332
- return f instanceof QueryAtomicField && !f.isAggregate();
333
- }
334
- class QueryAtomicField extends QueryField {
335
- includeInWildcard() {
336
- return true;
337
- }
338
- isAggregate() {
339
- return (this.fieldDef.aggregate !== undefined &&
340
- this.fieldDef.aggregate === true);
341
- }
342
- getFilterList() {
343
- return [];
344
- }
345
- }
346
- // class QueryMeasure extends QueryField {}
347
- class QueryFieldString extends QueryAtomicField {
348
- }
349
- class QueryFieldNumber extends QueryAtomicField {
350
- }
351
- class QueryFieldBoolean extends QueryAtomicField {
352
- }
353
- // in a query a struct can be referenced. The struct will
354
- // emit the primary key field in the actual result set and
355
- // will include the StructDef as a foreign key join in the output
356
- // StructDef.
357
- class QueryFieldStruct extends QueryAtomicField {
358
- constructor(fieldDef, parent, primaryKey) {
359
- super(fieldDef, parent);
360
- this.primaryKey = primaryKey;
361
- }
362
- getName() {
363
- return (0, malloy_types_1.getIdentifier)(this.fieldDef);
364
- }
365
- getAsJoinedStructDef(foreignKeyName) {
366
- return {
367
- ...this.parent.fieldDef,
368
- structRelationship: {
369
- type: "one",
370
- onExpression: [
371
- {
372
- type: "field",
373
- path: this.primaryKey,
374
- },
375
- "=",
376
- { type: "field", path: foreignKeyName },
377
- ],
378
- },
379
- };
380
- }
381
- }
382
- const timeframeBQMap = {
383
- hour_of_day: "HOUR",
384
- day_of_month: "DAY",
385
- day_of_year: "DAYOFYEAR",
386
- month_of_year: "MONTH",
387
- };
388
- class QueryFieldDate extends QueryAtomicField {
389
- generateExpression(resultSet) {
390
- const fd = this.fieldDef;
391
- if (!fd.timeframe) {
392
- return super.generateExpression(resultSet);
393
- }
394
- else {
395
- const exprString = super.generateExpression(resultSet);
396
- let exprArray = [exprString];
397
- switch (fd.timeframe) {
398
- case "date":
399
- break;
400
- case "year":
401
- case "month":
402
- case "week":
403
- exprArray = this.parent.dialect.sqlDateTrunc(exprString, fd.timeframe);
404
- break;
405
- case "day_of_month":
406
- case "day_of_year":
407
- exprArray = [
408
- `EXTRACT(${timeframeBQMap[fd.timeframe]} FROM ${exprString})`,
409
- ];
410
- break;
411
- default:
412
- exprArray = [`DATE_TRUNC(${exprString}, ${fd.timeframe})`];
413
- }
414
- return exprArray.join("");
415
- }
416
- }
417
- // clone ourselves on demand as a timeframe.
418
- getChildByName(name) {
419
- const fieldDef = {
420
- ...this.fieldDef,
421
- as: `${this.getIdentifier()}_${name}`,
422
- timeframe: name,
423
- };
424
- return new QueryFieldDate(fieldDef, this.parent);
425
- }
426
- }
427
- class QueryFieldTimestamp extends QueryAtomicField {
428
- generateExpression(resultSet) {
429
- const fd = this.fieldDef;
430
- if (fd.e || !fd.timeframe) {
431
- return super.generateExpression(resultSet);
432
- }
433
- else {
434
- const exprString = super.generateExpression(resultSet);
435
- let exprArray = [exprString];
436
- switch (fd.timeframe) {
437
- case "year":
438
- case "month":
439
- case "week":
440
- case "date":
441
- exprArray = this.parent.dialect.sqlTimestampTrunc(exprString, fd.timeframe, "UTC");
442
- break;
443
- case "day_of_month":
444
- case "day_of_year":
445
- case "hour_of_day":
446
- case "month_of_year":
447
- exprArray = [
448
- `EXTRACT(${timeframeBQMap[fd.timeframe]} FROM ${exprString} AT TIME ZONE 'UTC')`,
449
- ];
450
- break;
451
- // case "date":
452
- // e = `DATE(${e},'UTC')`;
453
- // break;
454
- case "hour":
455
- case "minute":
456
- case "second":
457
- exprArray = this.parent.dialect.sqlTimestampTrunc(exprString, fd.timeframe, "UTC");
458
- break;
459
- }
460
- return exprArray.join("");
461
- }
462
- }
463
- // clone ourselves on demand as a timeframe.
464
- getChildByName(name) {
465
- const fieldDef = {
466
- ...this.fieldDef,
467
- as: `${this.getIdentifier()}_${name}`,
468
- timeframe: name,
469
- };
470
- return new QueryFieldTimestamp(fieldDef, this.parent);
471
- }
472
- }
473
- class QueryFieldDistinctKey extends QueryAtomicField {
474
- generateExpression(resultSet) {
475
- if (this.parent.primaryKey()) {
476
- const pk = this.parent.getPrimaryKeyField(this.fieldDef);
477
- return pk.generateExpression(resultSet);
478
- }
479
- else {
480
- return this.parent.getIdentifier() + "." + "__distinct_key";
481
- }
482
- }
483
- includeInWildcard() {
484
- return false;
485
- }
486
- }
487
- const NUMERIC_DECIMAL_PRECISION = 9;
488
- function sqlSumDistinct(dialect, sqlExp, sqlDistintKey) {
489
- const precision = 9;
490
- const uniqueInt = dialect.sqlSumDistinctHashedKey(sqlDistintKey);
491
- const multiplier = 10 ** (precision - NUMERIC_DECIMAL_PRECISION);
492
- const sumSQL = `
493
- (
494
- SUM(DISTINCT
495
- (CAST(ROUND(COALESCE(${sqlExp},0)*(${multiplier}*1.0), ${NUMERIC_DECIMAL_PRECISION}) AS NUMERIC) +
496
- ${uniqueInt}
497
- ))
498
- -
499
- SUM(DISTINCT ${uniqueInt})
500
- )`;
501
- let ret = `(${sumSQL}/(${multiplier}*1.0))`;
502
- ret = `CAST(${ret} as ${dialect.defaultNumberType})`;
503
- return ret;
504
- }
505
- class FieldInstanceField {
506
- constructor(f, fieldUsage, parent) {
507
- this.type = "field";
508
- this.groupSet = 0;
509
- this.f = f;
510
- this.fieldUsage = fieldUsage;
511
- this.parent = parent;
512
- if (parent) {
513
- this.groupSet = parent.groupSet;
514
- }
515
- }
516
- root() {
517
- return this.parent.root();
518
- }
519
- }
520
- class FieldInstanceResult {
521
- // query: QueryQuery;
522
- constructor(turtleDef, parent) {
523
- this.type = "query";
524
- this.allFields = new Map();
525
- this.groupSet = 0;
526
- this.depth = 0;
527
- this.childGroups = [];
528
- this.hasHaving = false;
529
- this.parent = parent;
530
- this.turtleDef = turtleDef;
531
- this.firstSegment = turtleDef.pipeline[0];
532
- }
533
- addField(as, field, usage) {
534
- let fi;
535
- if ((fi = this.allFields.get(as))) {
536
- if (fi.type === "query") {
537
- throw new Error(`Redefinition of field ${field.fieldDef.name} as struct`);
538
- }
539
- const fif = fi;
540
- if (fif.fieldUsage.type === "result") {
541
- if (usage.type !== "result") {
542
- // its already in the result, we can just ignore it.
543
- return;
544
- }
545
- else {
546
- throw new Error(`Ambiguous output field name '${field.fieldDef.name}'.`);
547
- }
548
- }
549
- }
550
- this.add(as, new FieldInstanceField(field, usage, this));
551
- }
552
- add(name, f) {
553
- this.allFields.set(name, f);
554
- }
555
- hasField(name) {
556
- const fi = this.allFields.get(name);
557
- return fi !== undefined && fi instanceof FieldInstanceField;
558
- }
559
- getField(name) {
560
- const fi = this.allFields.get(name);
561
- if (fi === undefined) {
562
- throw new Error(`Internal Error, field Not defined ${name}`);
563
- }
564
- else if (fi instanceof FieldInstanceField) {
565
- return fi;
566
- }
567
- throw new Error(`can't use a query here ${name}`);
568
- }
569
- getFieldByNumber(index) {
570
- for (const [name, fi] of this.allFields) {
571
- if (fi instanceof FieldInstanceField) {
572
- if (fi.fieldUsage.type === "result" &&
573
- fi.fieldUsage.resultIndex === index) {
574
- return { name, fif: fi };
575
- }
576
- }
577
- }
578
- throw new Error(`Invalid Order By index '${index}`);
579
- }
580
- // loops through all the turtled queries and computes recomputes the group numbers
581
- computeGroups(nextGroupSetNumber, depth) {
582
- this.groupSet = nextGroupSetNumber++;
583
- this.depth = depth;
584
- let maxDepth = depth;
585
- let isComplex = false;
586
- let children = [this.groupSet];
587
- for (const [_name, fi] of this.allFields) {
588
- if (fi.type === "query") {
589
- const fir = fi;
590
- isComplex = true;
591
- if (fir.firstSegment.type === "reduce") {
592
- const r = fir.computeGroups(nextGroupSetNumber, depth + 1);
593
- children = children.concat(r.children);
594
- nextGroupSetNumber = r.nextGroupSetNumber;
595
- if (r.maxDepth > maxDepth) {
596
- maxDepth = r.maxDepth;
597
- }
598
- }
599
- }
600
- else {
601
- fi.groupSet = this.groupSet;
602
- }
603
- }
604
- this.childGroups = children;
605
- return { nextGroupSetNumber, maxDepth, children, isComplex };
606
- }
607
- fields(fn = undefined) {
608
- const ret = [];
609
- for (const e of this.allFields.values()) {
610
- if (e instanceof FieldInstanceField) {
611
- if (fn === undefined || fn(e)) {
612
- ret.push(e);
613
- }
614
- }
615
- }
616
- return ret;
617
- }
618
- fieldNames(fn) {
619
- const ret = [];
620
- for (const [name, fi] of this.allFields) {
621
- if (fi instanceof FieldInstanceField) {
622
- if (fn === undefined || fn(fi)) {
623
- ret.push(name);
624
- }
625
- }
626
- }
627
- return ret;
628
- }
629
- // if a turtled result is all measures, we emit use ANY_VALUE for the aggregation
630
- // and emit the resulting structure as a RECORD instead of REPEATED
631
- // if we have all numbers, we need to know because we'll have to conjur a record.
632
- getRepeatedResultType() {
633
- let ret = "inline_all_numbers";
634
- for (const f of this.fields()) {
635
- if (f.fieldUsage.type === "result") {
636
- if (isScalarField(f.f)) {
637
- return "nested";
638
- }
639
- if (f.f instanceof QueryStruct) {
640
- ret = "inline";
641
- }
642
- }
643
- }
644
- return ret;
645
- }
646
- structs() {
647
- const ret = [];
648
- for (const e of this.allFields.values()) {
649
- if (e instanceof FieldInstanceResult) {
650
- ret.push(e);
651
- }
652
- }
653
- return ret;
654
- }
655
- // return a list of structs that match the criteria
656
- // specified in the function.
657
- selectStructs(result, fn) {
658
- if (fn(this)) {
659
- result.push(this);
660
- }
661
- for (const e of this.structs()) {
662
- e.selectStructs(result, fn);
663
- }
664
- return result;
665
- }
666
- calculateDefaultOrderBy() {
667
- // LookML rules for default ordering.
668
- // Date or time or ordnal based fields, that field ascending
669
- // First Measure Descending.
670
- let firstField;
671
- for (const [_name, fi] of this.allFields) {
672
- if (fi instanceof FieldInstanceField) {
673
- if (fi.fieldUsage.type === "result") {
674
- firstField || (firstField = fi.fieldUsage.resultIndex);
675
- if (["date", "timestamp"].indexOf(fi.f.fieldDef.type) > -1) {
676
- return [{ dir: "desc", field: fi.fieldUsage.resultIndex }];
677
- }
678
- else if (isAggregateField(fi.f)) {
679
- return [{ dir: "desc", field: fi.fieldUsage.resultIndex }];
680
- }
681
- }
682
- }
683
- }
684
- if (firstField) {
685
- return [{ dir: "asc", field: firstField }];
686
- }
687
- return [];
688
- }
689
- addStructToJoin(qs, query, mayNeedUniqueKey, joinStack) {
690
- var _a;
691
- const name = qs.getIdentifier();
692
- let join;
693
- if ((join = this.root().joins.get(name))) {
694
- join.mayNeedUniqueKey || (join.mayNeedUniqueKey = mayNeedUniqueKey);
695
- return;
696
- }
697
- // if we have a parent, join it first.
698
- let parent;
699
- const parentStruct = (_a = qs.parent) === null || _a === void 0 ? void 0 : _a.getJoinableParent();
700
- if (parentStruct) {
701
- // add dependant expressions first...
702
- this.addStructToJoin(parentStruct, query, false, joinStack);
703
- parent = this.root().joins.get(parentStruct.getIdentifier());
704
- }
705
- // add any dependant joins based on the ON
706
- const sr = qs.fieldDef.structRelationship;
707
- if ((0, malloy_types_1.isJoinOn)(sr) &&
708
- qs.parent && // if the join has an ON, it must thave a parent
709
- sr.onExpression !== undefined &&
710
- joinStack.indexOf(name) === -1) {
711
- query.addDependantExpr(this, qs.parent, sr.onExpression, [
712
- ...joinStack,
713
- name,
714
- ]);
715
- }
716
- if (!(join = this.root().joins.get(name))) {
717
- join = new JoinInstance(qs, name, parent);
718
- this.root().joins.set(name, join);
719
- }
720
- join.mayNeedUniqueKey || (join.mayNeedUniqueKey = mayNeedUniqueKey);
721
- }
722
- findJoins(query) {
723
- for (const dim of this.fields()) {
724
- this.addStructToJoin(dim.f.getJoinableParent(), query, dim.f.mayNeedUniqueKey(), []);
725
- }
726
- for (const s of this.structs()) {
727
- s.findJoins(query);
728
- }
729
- }
730
- root() {
731
- if (this.parent) {
732
- return this.parent.root();
733
- }
734
- throw new Error(`Internal Error, Null parent FieldInstanceResult`);
735
- }
736
- }
737
- /* Root Result as opposed to a turtled result */
738
- class FieldInstanceResultRoot extends FieldInstanceResult {
739
- constructor(turtleDef) {
740
- super(turtleDef, undefined);
741
- this.joins = new Map();
742
- this.havings = new utils_1.AndChain();
743
- }
744
- root() {
745
- return this;
746
- }
747
- // look at all the fields again in the structs in the query
748
- calculateSymmetricAggregates() {
749
- let leafiest;
750
- for (const [name, join] of this.joins) {
751
- // first join is by default the
752
- const relationship = join.parentRelationship();
753
- if (relationship === "many_to_many") {
754
- // everything must be calculated with symmetric aggregates
755
- leafiest = "0never";
756
- }
757
- else if (leafiest === undefined) {
758
- leafiest = name;
759
- }
760
- else if (join.parentRelationship() === "one_to_many") {
761
- // check up the parent relationship until you find
762
- // the current leafiest node. If it isn't in the direct path
763
- // we need symmetric aggregate for everything.
764
- // if it is in the path, than this one becomes leafiest
765
- const s = join.queryStruct;
766
- if (s.parent && s.parent.getIdentifier() === leafiest) {
767
- leafiest = name;
768
- }
769
- else {
770
- // we have more than on one_to_many join chain, all bets are off.
771
- leafiest = "0never";
772
- }
773
- }
774
- }
775
- // console.log(`LEAFIEST: ${leafiest}`);
776
- for (const [name, join] of this.joins) {
777
- join.leafiest = name === leafiest;
778
- }
779
- // figure out which joins we need to manufacture distinct keys for.
780
- // Nested Unique keys are dependant on the primary key of the parent
781
- // and the table.
782
- for (const [_name, join] of this.joins) {
783
- // don't need keys on leafiest
784
- if (!join.leafiest && join.mayNeedUniqueKey) {
785
- let j = join;
786
- while (j) {
787
- if (!j.queryStruct.primaryKey()) {
788
- j.makeUniqueKey = true;
789
- }
790
- if (j.queryStruct.fieldDef.structRelationship.type === "nested") {
791
- j = j.parent;
792
- }
793
- else {
794
- j = undefined;
795
- }
796
- }
797
- }
798
- }
799
- }
800
- }
801
- class JoinInstance {
802
- constructor(queryStruct, alias, parent) {
803
- this.mayNeedUniqueKey = false;
804
- this.makeUniqueKey = false;
805
- this.leafiest = false;
806
- this.children = [];
807
- this.queryStruct = queryStruct;
808
- this.parent = parent;
809
- this.alias = alias;
810
- if (parent) {
811
- parent.children.push(this);
812
- }
813
- // convert the filter list into a list of boolean fields so we can
814
- // generate dependancies and code for them.
815
- if (this.queryStruct.fieldDef.filterList) {
816
- for (const filter of this.queryStruct.fieldDef.filterList) {
817
- this.joinFilterConditions = [];
818
- const qf = new QueryFieldBoolean({
819
- type: "boolean",
820
- name: "ignoreme",
821
- e: filter.expression,
822
- }, this.queryStruct);
823
- this.joinFilterConditions.push(qf);
824
- }
825
- }
826
- }
827
- parentRelationship() {
828
- if (this.queryStruct.parent === undefined) {
829
- return "root";
830
- }
831
- switch (this.queryStruct.fieldDef.structRelationship.type) {
832
- case "one":
833
- return "many_to_one";
834
- case "cross":
835
- return "many_to_many";
836
- case "many":
837
- return "one_to_many";
838
- case "nested":
839
- return "one_to_many";
840
- case "inline":
841
- return "one_to_one";
842
- default:
843
- throw new Error(`Internal error unknown relationship type to parent for ${this.queryStruct.fieldDef.name}`);
844
- }
845
- }
846
- // postgres unnest needs to know the names of the physical fields.
847
- getDialectFieldList() {
848
- const dialectFieldList = [];
849
- for (const f of this.queryStruct.fieldDef.fields.filter(malloy_types_1.isPhysical)) {
850
- dialectFieldList.push({
851
- type: f.type,
852
- sqlExpression: (0, malloy_types_1.getIdentifier)(f),
853
- sqlOutputName: (0, malloy_types_1.getIdentifier)(f),
854
- });
855
- }
856
- return dialectFieldList;
857
- }
858
- }
859
- /** nested query */
860
- class QueryTurtle extends QueryField {
861
- }
862
- /**
863
- * Used by the translator to get the output StructDef of a pipe segment
864
- *
865
- * half translated to the new world of types ..
866
- */
867
- class Segment {
868
- // static nextStructDef(s: StructDef, q: AnonymousQueryDef): StructDef
869
- static nextStructDef(structDef, segment) {
870
- const qs = new QueryStruct(structDef, {
871
- model: new QueryModel(undefined),
872
- });
873
- const turtleDef = {
874
- type: "turtle",
875
- name: "ignoreme",
876
- pipeline: [segment],
877
- };
878
- const queryQueryQuery = QueryQuery.makeQuery(turtleDef, qs, new StageWriter(undefined) // stage write indicates we want to get a result.
879
- );
880
- return queryQueryQuery.getResultStructDef();
881
- }
882
- }
883
- exports.Segment = Segment;
884
- /** Query builder object. */
885
- class QueryQuery extends QueryField {
886
- constructor(fieldDef, parent, stageWriter) {
887
- super(fieldDef, parent);
888
- this.prepared = false;
889
- this.maxDepth = 0;
890
- this.maxGroupSet = 0;
891
- this.fieldDef = fieldDef;
892
- this.rootResult = new FieldInstanceResultRoot(fieldDef);
893
- this.stageWriter = stageWriter;
894
- // do some magic here to get the first segment.
895
- this.firstSegment = fieldDef.pipeline[0];
896
- }
897
- static makeQuery(fieldDef, parentStruct, stageWriter = undefined) {
898
- let flatTurtleDef = parentStruct.flattenTurtleDef(fieldDef);
899
- let parent = parentStruct;
900
- const firstStage = flatTurtleDef.pipeline[0];
901
- // if we are generating code
902
- // and have extended declaration, we need to make a new QueryStruct
903
- // copy the definitions into a new structdef
904
- // edit the declations from the pipeline
905
- if (stageWriter !== undefined &&
906
- (0, malloy_types_1.isQuerySegment)(firstStage) &&
907
- firstStage.extendSource !== undefined) {
908
- parent = new QueryStruct({
909
- ...parentStruct.fieldDef,
910
- fields: [...parentStruct.fieldDef.fields, ...firstStage.extendSource],
911
- }, parent.parent ? { struct: parent } : { model: parent.model });
912
- flatTurtleDef = {
913
- ...flatTurtleDef,
914
- pipeline: [
915
- {
916
- ...firstStage,
917
- extendSource: undefined,
918
- },
919
- ...flatTurtleDef.pipeline.slice(1),
920
- ],
921
- };
922
- }
923
- switch (firstStage.type) {
924
- case "reduce":
925
- return new QueryQueryReduce(flatTurtleDef, parent, stageWriter);
926
- case "project":
927
- return new QueryQueryProject(flatTurtleDef, parent, stageWriter);
928
- case "index":
929
- return new QueryQueryIndex(flatTurtleDef, parent, stageWriter);
930
- }
931
- }
932
- getFieldList() {
933
- switch (this.firstSegment.type) {
934
- // case "index":
935
- // return this.firstSegment.fields || [];
936
- case "reduce":
937
- return this.firstSegment.fields;
938
- // probably need some way of checking type class of field here...
939
- // project should only contain scalars
940
- case "project":
941
- return this.firstSegment.fields;
942
- default:
943
- throw new Error(`Query contains no fields ${JSON.stringify(this.fieldDef)}`);
944
- }
945
- }
946
- // get a field ref and expand it.
947
- expandField(f) {
948
- let as;
949
- let field;
950
- // if it is a string
951
- if (typeof f === "string") {
952
- field = this.parent.getQueryFieldByName(f);
953
- }
954
- else if ("type" in f) {
955
- field = this.parent.makeQueryField(f);
956
- }
957
- // or FilteredAliasedName or a hacked timestamp field.
958
- else if ("name" in f && "as" in f) {
959
- field = this.parent.getQueryFieldByName(f.name);
960
- // QueryFieldStructs return new names...
961
- as = field.fieldDef.as || f.as;
962
- if (field instanceof QueryFieldStruct) {
963
- throw new Error("Syntax currently disallowed. Semantics up for discussion");
964
- }
965
- // Types of aliased fields.
966
- // turtles
967
- // Timestamps and Dates (are just fine to leave as is).
968
- // measures
969
- let e;
970
- if (field instanceof QueryQuery) {
971
- const newFieldDef = (0, lodash_1.cloneDeep)(field.fieldDef);
972
- newFieldDef.as = f.name;
973
- newFieldDef.filterList = f.filterList;
974
- field = QueryQuery.makeQuery(newFieldDef, this.parent);
975
- }
976
- else if (!(field instanceof QueryFieldTimestamp ||
977
- field instanceof QueryFieldDate)) {
978
- // its a measure
979
- e = [{ type: "field", path: field.getFullOutputName() }];
980
- if ("filterList" in f && f.filterList) {
981
- e = [{ type: "filterExpression", filterList: f.filterList, e: e }];
982
- }
983
- const newFieldDef = {
984
- type: field.fieldDef.type,
985
- name: f.as,
986
- e,
987
- aggregate: isAggregateField(field),
988
- };
989
- field = this.parent.makeQueryField(newFieldDef);
990
- }
991
- // or inline field FieldTypeDef
992
- }
993
- else {
994
- throw new Error(`Unrecognized field definition ${JSON.stringify(f, undefined, 2)}`);
995
- }
996
- if (!as) {
997
- as = field.getIdentifier();
998
- }
999
- return { as, field };
1000
- }
1001
- expandDependantField(resultStruct, fieldRef) {
1002
- this.expandField(fieldRef);
1003
- }
1004
- // find all the fieldNames in the struct (and children)
1005
- // that match the filter
1006
- expandWildCardStruct(struct, expandChildren, filter = undefined) {
1007
- let fieldNames = [];
1008
- const structs = [];
1009
- for (const [_name, f] of struct.nameMap) {
1010
- if (f instanceof QueryAtomicField &&
1011
- isScalarField(f) &&
1012
- f.includeInWildcard() &&
1013
- !this.parent.dialect.ignoreInProject(f.fieldDef.name) &&
1014
- (!filter || filter(f))) {
1015
- // fieldNames.push(`${struct.getFullOutputName()}${name}`);
1016
- fieldNames.push(f.getFullOutputName());
1017
- }
1018
- else if (f instanceof QueryStruct && expandChildren) {
1019
- structs.push(f);
1020
- }
1021
- }
1022
- for (const s of structs) {
1023
- fieldNames = fieldNames.concat(this.expandWildCardStruct(s, expandChildren, filter));
1024
- }
1025
- return fieldNames;
1026
- }
1027
- // Do any '*' expansion.
1028
- expandWildCards(fields, filter = undefined) {
1029
- let ret = [];
1030
- for (const f of fields) {
1031
- if (typeof f !== "string") {
1032
- ret.push(f);
1033
- }
1034
- else {
1035
- const fieldName = f;
1036
- const path = fieldName.split(".");
1037
- if (!path[path.length - 1].startsWith("*")) {
1038
- ret.push(f);
1039
- }
1040
- else {
1041
- const expandChildren = path.pop() === "**";
1042
- let struct = this.parent;
1043
- let pathElementName;
1044
- while (path.length > 0 && (pathElementName = path.shift())) {
1045
- const structNode = struct.getChildByName(pathElementName);
1046
- if (structNode === undefined) {
1047
- throw new Error(`Nested explore not found '${pathElementName}'`);
1048
- }
1049
- if (structNode instanceof QueryStruct) {
1050
- struct = structNode;
1051
- }
1052
- else {
1053
- throw new Error(`'${pathElementName}' is not an explore object`);
1054
- }
1055
- }
1056
- ret = ret.concat(this.expandWildCardStruct(struct, expandChildren, filter));
1057
- }
1058
- }
1059
- }
1060
- return ret;
1061
- }
1062
- addDependantPath(resultStruct, context, path, mayNeedUniqueKey, joinStack) {
1063
- const node = context.getFieldByName(path);
1064
- let struct;
1065
- if (node instanceof QueryField) {
1066
- struct = node.parent;
1067
- }
1068
- else if (node instanceof QueryStruct) {
1069
- struct = node;
1070
- }
1071
- else {
1072
- throw new Error(`Internal Error: Unknown object type`);
1073
- }
1074
- resultStruct
1075
- .root()
1076
- .addStructToJoin(struct.getJoinableParent(), this, mayNeedUniqueKey, joinStack);
1077
- }
1078
- addDependantExpr(resultStruct, context, e, joinStack) {
1079
- for (const expr of e) {
1080
- if ((0, malloy_types_1.isFieldFragment)(expr)) {
1081
- const field = context.getDimensionOrMeasureByName(expr.path);
1082
- if ((0, malloy_types_1.hasExpression)(field.fieldDef)) {
1083
- this.addDependantExpr(resultStruct, field.parent, field.fieldDef.e, joinStack);
1084
- }
1085
- else {
1086
- resultStruct
1087
- .root()
1088
- .addStructToJoin(field.parent.getJoinableParent(), this, false, joinStack);
1089
- // this.addDependantPath(resultStruct, field.parent, expr.path, false);
1090
- }
1091
- }
1092
- else if ((0, malloy_types_1.isFilterFragment)(expr)) {
1093
- for (const filterCond of expr.filterList) {
1094
- this.addDependantExpr(resultStruct, context, filterCond.expression, joinStack);
1095
- }
1096
- }
1097
- else if ((0, malloy_types_1.isAggregateFragment)(expr)) {
1098
- if ((0, malloy_types_1.isAsymmetricFragment)(expr)) {
1099
- if (expr.structPath) {
1100
- this.addDependantPath(resultStruct, context, expr.structPath, true, joinStack);
1101
- }
1102
- else {
1103
- // we are doing a sum in the root. It may need symetric aggregates
1104
- resultStruct.addStructToJoin(context, this, true, joinStack);
1105
- }
1106
- }
1107
- this.addDependantExpr(resultStruct, context, expr.e, joinStack);
1108
- }
1109
- }
1110
- }
1111
- addDependancies(resultStruct, field) {
1112
- if ((0, malloy_types_1.hasExpression)(field.fieldDef)) {
1113
- this.addDependantExpr(resultStruct, field.parent, field.fieldDef.e, []);
1114
- }
1115
- }
1116
- expandFields(resultStruct) {
1117
- let resultIndex = 1;
1118
- for (const f of this.expandWildCards(resultStruct.firstSegment.fields)) {
1119
- const { as, field } = this.expandField(f);
1120
- if (field instanceof QueryTurtle || field instanceof QueryQuery) {
1121
- if (this.firstSegment.type === "project") {
1122
- throw new Error(`Turtled Queries cannot be used in PROJECT - '${field.fieldDef.name}'`);
1123
- }
1124
- const fir = new FieldInstanceResult(field.fieldDef, resultStruct);
1125
- this.expandFields(fir);
1126
- resultStruct.add(as, fir);
1127
- }
1128
- else if (field instanceof QueryAtomicField) {
1129
- resultStruct.addField(as, field, {
1130
- resultIndex,
1131
- type: "result",
1132
- });
1133
- this.addDependancies(resultStruct, field);
1134
- if (isAggregateField(field)) {
1135
- if (this.firstSegment.type === "project") {
1136
- throw new Error(`Aggregate Fields cannot be used in PROJECT - '${field.fieldDef.name}'`);
1137
- }
1138
- }
1139
- // } else if (field instanceof QueryStruct) {
1140
- // // this could probably be optimized. We are adding the primary key of the joined structure
1141
- // // instead of the foreignKey. We have to do this in at least the INNER join case
1142
- // // so i'm just going to let the SQL database do the optimization (which is pretty rudimentary)
1143
- // const pkFieldDef = field.getAsQueryField();
1144
- // resultStruct.addField(as, pkFieldDef, {
1145
- // resultIndex,
1146
- // type: "result",
1147
- // });
1148
- // resultStruct.addStructToJoin(field, false);
1149
- }
1150
- // else if (
1151
- // this.firstSegment.type === "project" &&
1152
- // field instanceof QueryStruct
1153
- // ) {
1154
- // // TODO lloyd refactor or comment why we do nothing here
1155
- // } else {
1156
- // throw new Error(`'${as}' cannot be used as in this way.`);
1157
- // }
1158
- resultIndex++;
1159
- }
1160
- this.expandFilters(resultStruct);
1161
- }
1162
- expandFilters(resultStruct) {
1163
- if (resultStruct.firstSegment.filterList === undefined) {
1164
- return;
1165
- }
1166
- // Go through the filters and make or find dependant fields
1167
- // add them to the field index. Place the individual filters
1168
- // in the correct catgory.
1169
- for (const cond of resultStruct.firstSegment.filterList || []) {
1170
- const context = this.parent;
1171
- this.addDependantExpr(resultStruct, context, cond.expression, []);
1172
- }
1173
- for (const join of resultStruct.root().joins.values() || []) {
1174
- for (const qf of join.joinFilterConditions || []) {
1175
- if (qf.fieldDef.type === "boolean" && qf.fieldDef.e) {
1176
- this.addDependantExpr(resultStruct, qf.parent, qf.fieldDef.e, []);
1177
- }
1178
- }
1179
- }
1180
- }
1181
- generateSQLFilters(resultStruct, which, filterList = undefined) {
1182
- const resultFilters = new utils_1.AndChain();
1183
- const list = filterList || resultStruct.firstSegment.filterList;
1184
- if (list === undefined) {
1185
- return resultFilters;
1186
- }
1187
- // Go through the filters and make or find dependant fields
1188
- // add them to the field index. Place the individual filters
1189
- // in the correct catgory.
1190
- for (const cond of list || []) {
1191
- const context = this.parent;
1192
- if ((which === "having" && cond.aggregate) ||
1193
- (which === "where" && !cond.aggregate)) {
1194
- const sqlClause = this.generateExpressionFromExpr(resultStruct, context, cond.expression, undefined);
1195
- resultFilters.add(sqlClause);
1196
- }
1197
- }
1198
- return resultFilters;
1199
- }
1200
- prepare(_stageWriter) {
1201
- if (!this.prepared) {
1202
- this.expandFields(this.rootResult);
1203
- this.rootResult.findJoins(this);
1204
- this.rootResult.calculateSymmetricAggregates();
1205
- this.prepared = true;
1206
- }
1207
- }
1208
- // get the source fieldname and filters associated with the field (so we can drill later)
1209
- getResultMetadata(fi) {
1210
- if (fi instanceof FieldInstanceField) {
1211
- if (fi.fieldUsage.type === "result") {
1212
- const fieldDef = fi.f.fieldDef;
1213
- let filterList;
1214
- const sourceField = fi.f.parent.getFullOutputName() + (fieldDef.name || fieldDef.as);
1215
- const sourceExpression = fieldDef.source;
1216
- const sourceClasses = [sourceField];
1217
- if (isAggregateField(fi.f)) {
1218
- filterList = fi.f.getFilterList();
1219
- return {
1220
- sourceField,
1221
- sourceExpression,
1222
- filterList,
1223
- sourceClasses,
1224
- fieldKind: "measure",
1225
- };
1226
- }
1227
- if (isScalarField(fi.f)) {
1228
- return {
1229
- sourceField,
1230
- sourceExpression,
1231
- filterList,
1232
- sourceClasses,
1233
- fieldKind: "dimension",
1234
- };
1235
- }
1236
- else {
1237
- return undefined;
1238
- }
1239
- }
1240
- return undefined;
1241
- }
1242
- else if (fi instanceof FieldInstanceResult) {
1243
- const sourceField = fi.turtleDef.name || fi.turtleDef.as;
1244
- const sourceClasses = sourceField ? [sourceField] : [];
1245
- const filterList = fi.firstSegment.filterList;
1246
- const limit = fi.turtleDef.pipeline[fi.turtleDef.pipeline.length - 1].limit;
1247
- if (sourceField) {
1248
- return {
1249
- sourceField,
1250
- filterList,
1251
- sourceClasses,
1252
- fieldKind: "struct",
1253
- limit,
1254
- };
1255
- }
1256
- }
1257
- return undefined;
1258
- }
1259
- /** returns a fields and primary key of a struct for this query */
1260
- getResultStructDef(resultStruct = this.rootResult, isRoot = true) {
1261
- const fields = [];
1262
- let primaryKey;
1263
- this.prepare(undefined);
1264
- let dimCount = 0;
1265
- for (const [name, fi] of resultStruct.allFields) {
1266
- const resultMetadata = this.getResultMetadata(fi);
1267
- if (fi instanceof FieldInstanceResult) {
1268
- const { structDef } = this.generateTurtlePipelineSQL(fi, new StageWriter(undefined));
1269
- // LTNOTE: This is probably broken now. Need to look at the last stage
1270
- // to figure out the resulting nested/inline state...
1271
- const resultType = fi.getRepeatedResultType() === "nested" ? "nested" : "inline";
1272
- structDef.name = name;
1273
- structDef.structRelationship = { field: name, type: resultType };
1274
- structDef.structSource = { type: resultType };
1275
- structDef.resultMetadata = resultMetadata;
1276
- fields.push(structDef);
1277
- }
1278
- else if (fi instanceof FieldInstanceField) {
1279
- if (fi.fieldUsage.type === "result") {
1280
- if (fi.f instanceof QueryFieldStruct) {
1281
- fields.push(fi.f.getAsJoinedStructDef(name));
1282
- }
1283
- // if there is only one dimension, it is the primaryKey
1284
- // if there are more, primaryKey is undefined.
1285
- if (isScalarField(fi.f)) {
1286
- if (dimCount === 0 && isRoot) {
1287
- primaryKey = name;
1288
- }
1289
- else {
1290
- primaryKey = undefined;
1291
- }
1292
- dimCount++;
1293
- }
1294
- const location = fi.f.fieldDef.location;
1295
- // build out the result fields...
1296
- switch (fi.f.fieldDef.type) {
1297
- case "boolean":
1298
- case "string":
1299
- fields.push({
1300
- name,
1301
- type: fi.f.fieldDef.type,
1302
- resultMetadata,
1303
- location,
1304
- });
1305
- break;
1306
- case "timestamp": {
1307
- const timeframe = fi.f.fieldDef.timeframe || "second";
1308
- switch (timeframe) {
1309
- case "year":
1310
- case "month":
1311
- case "week":
1312
- case "quarter":
1313
- case "day":
1314
- fields.push({
1315
- name,
1316
- type: "date",
1317
- timeframe,
1318
- resultMetadata,
1319
- location,
1320
- });
1321
- break;
1322
- case "second":
1323
- case "minute":
1324
- case "date":
1325
- case "hour":
1326
- fields.push({
1327
- name,
1328
- type: "timestamp",
1329
- timeframe,
1330
- resultMetadata,
1331
- location,
1332
- });
1333
- break;
1334
- case "hour_of_day":
1335
- case "day_of_month":
1336
- case "day_of_year":
1337
- case "month_of_year":
1338
- fields.push({
1339
- name,
1340
- type: "number",
1341
- numberType: "integer",
1342
- timeframe,
1343
- resultMetadata,
1344
- location,
1345
- });
1346
- break;
1347
- }
1348
- break;
1349
- }
1350
- case "date": {
1351
- fields.push({
1352
- name,
1353
- type: fi.f.fieldDef.type,
1354
- timeframe: fi.f.fieldDef.timeframe,
1355
- resultMetadata,
1356
- location,
1357
- });
1358
- break;
1359
- }
1360
- case "number":
1361
- fields.push({
1362
- name,
1363
- numberType: fi.f.fieldDef.numberType,
1364
- type: "number",
1365
- resultMetadata,
1366
- location,
1367
- });
1368
- break;
1369
- default:
1370
- throw new Error(`unknown Field Type in query ${JSON.stringify(fi.f.fieldDef)}`);
1371
- }
1372
- }
1373
- }
1374
- }
1375
- return {
1376
- fields,
1377
- name: this.resultStage || "result",
1378
- dialect: this.parent.dialect.name,
1379
- primaryKey,
1380
- structRelationship: {
1381
- type: "basetable",
1382
- connectionName: this.parent.connectionName,
1383
- },
1384
- // structSource: {type: 'query', query: this.fieldDef}
1385
- structSource: { type: "table" },
1386
- resultMetadata: this.getResultMetadata(this.rootResult),
1387
- type: "struct",
1388
- };
1389
- }
1390
- generateSQLJoinBlock(stageWriter, ji) {
1391
- let s = "";
1392
- const qs = ji.queryStruct;
1393
- const structRelationship = qs.fieldDef.structRelationship;
1394
- let structSQL = qs.structSourceSQL(stageWriter);
1395
- if ((0, malloy_types_1.isJoinOn)(structRelationship)) {
1396
- if (ji.makeUniqueKey) {
1397
- structSQL = `(SELECT ${qs.dialect.sqlGenerateUUID()} as __distinct_key, * FROM ${structSQL})`;
1398
- }
1399
- let onCondition = "";
1400
- if (qs.parent === undefined) {
1401
- throw new Error("Expected joined struct to have a parent.");
1402
- }
1403
- if (structRelationship.onExpression) {
1404
- onCondition = new QueryFieldBoolean({
1405
- type: "boolean",
1406
- name: "ignoreme",
1407
- e: structRelationship.onExpression,
1408
- }, qs.parent).generateExpression(this.rootResult);
1409
- }
1410
- else {
1411
- onCondition = "1=1";
1412
- }
1413
- let filters = "";
1414
- let conditions = undefined;
1415
- if (ji.joinFilterConditions) {
1416
- conditions = ji.joinFilterConditions.map((qf) => qf.generateExpression(this.rootResult));
1417
- }
1418
- if (ji.children.length === 0 || conditions === undefined) {
1419
- if (conditions !== undefined && conditions.length >= 1) {
1420
- filters = ` AND ${conditions.join(" AND ")}`;
1421
- }
1422
- s += `LEFT JOIN ${structSQL} AS ${ji.alias}\n ON ${onCondition}${filters}\n`;
1423
- }
1424
- else {
1425
- let select = `SELECT ${ji.alias}.*`;
1426
- let joins = "";
1427
- for (const childJoin of ji.children) {
1428
- joins += this.generateSQLJoinBlock(stageWriter, childJoin);
1429
- select += `, ${this.parent.dialect.sqlSelectAliasAsStruct(childJoin.alias)} AS ${childJoin.alias}`;
1430
- }
1431
- select += `\nFROM ${structSQL} AS ${ji.alias}\n${joins}\nWHERE ${conditions === null || conditions === void 0 ? void 0 : conditions.join(" AND ")}\n`;
1432
- s += `LEFT JOIN (\n${(0, utils_1.indent)(select)}) AS ${ji.alias}\n ON ${onCondition}\n`;
1433
- return s;
1434
- }
1435
- }
1436
- else if (structRelationship.type === "nested") {
1437
- if (qs.parent === undefined || ji.parent === undefined) {
1438
- throw new Error("Internal Error, nested structure with no parent.");
1439
- }
1440
- const fieldExpression = this.parent.dialect.sqlFieldReference(qs.parent.getIdentifier(), structRelationship.field, "struct", qs.parent.fieldDef.structRelationship.type === "nested");
1441
- // we need to generate primary key. If parent has a primary key combine
1442
- s += `\n${this.parent.dialect.sqlUnnestAlias(fieldExpression, ji.alias, ji.getDialectFieldList(), ji.makeUniqueKey)}\n`;
1443
- }
1444
- else if (structRelationship.type === "inline") {
1445
- throw new Error("Internal Error: inline structs should never appear in join trees");
1446
- }
1447
- else {
1448
- throw new Error(`Join type not implemented ${JSON.stringify(qs.fieldDef.structRelationship)}`);
1449
- }
1450
- for (const childJoin of ji.children) {
1451
- s += this.generateSQLJoinBlock(stageWriter, childJoin);
1452
- }
1453
- return s;
1454
- }
1455
- generateSQLJoins(stageWriter) {
1456
- let s = "";
1457
- // get the first value from the map (weird, I know)
1458
- const [[, ji]] = this.rootResult.joins;
1459
- const qs = ji.queryStruct;
1460
- // Joins
1461
- let structSQL = qs.structSourceSQL(stageWriter);
1462
- const structRelationship = qs.fieldDef.structRelationship;
1463
- if (structRelationship.type === "basetable") {
1464
- if (ji.makeUniqueKey) {
1465
- structSQL = `(SELECT ${qs.dialect.sqlGenerateUUID()} as __distinct_key, * FROM ${structSQL})`;
1466
- }
1467
- s += `FROM ${structSQL} as ${this.parent.getIdentifier()}\n`;
1468
- }
1469
- else {
1470
- throw new Error("Internal Error, queries must start from a basetable");
1471
- }
1472
- for (const childJoin of ji.children) {
1473
- s += this.generateSQLJoinBlock(stageWriter, childJoin);
1474
- }
1475
- return s;
1476
- }
1477
- genereateSQLOrderBy(queryDef, resultStruct) {
1478
- let s = "";
1479
- if (this.firstSegment.type === "project" && !queryDef.orderBy) {
1480
- return ""; // No default ordering for project.
1481
- }
1482
- const orderBy = queryDef.orderBy || resultStruct.calculateDefaultOrderBy();
1483
- const o = [];
1484
- for (const f of orderBy) {
1485
- if (typeof f.field === "string") {
1486
- // convert name to an index
1487
- const fi = resultStruct.getField(f.field);
1488
- if (fi && fi.fieldUsage.type === "result") {
1489
- o.push(`${fi.fieldUsage.resultIndex} ${f.dir || "ASC"}`);
1490
- }
1491
- else {
1492
- throw new Error(`Unknown field in ORDER BY ${f.field}`);
1493
- }
1494
- }
1495
- else {
1496
- o.push(`${f.field} ${f.dir || "ASC"}`);
1497
- }
1498
- }
1499
- if (o.length > 0) {
1500
- s = `ORDER BY ${o.join(",")}\n`;
1501
- }
1502
- return s;
1503
- }
1504
- generateSimpleSQL(stageWriter) {
1505
- let s = "";
1506
- s += "SELECT \n";
1507
- const fields = [];
1508
- for (const [name, field] of this.rootResult.allFields) {
1509
- const fi = field;
1510
- const sqlName = this.parent.dialect.sqlMaybeQuoteIdentifier(name);
1511
- if (fi.fieldUsage.type === "result") {
1512
- fields.push(` ${fi.f.generateExpression(this.rootResult)} as ${sqlName}`);
1513
- }
1514
- }
1515
- s += (0, utils_1.indent)(fields.join(",\n")) + "\n";
1516
- s += this.generateSQLJoins(stageWriter);
1517
- s += this.generateSQLFilters(this.rootResult, "where").sql("where");
1518
- // group by
1519
- if (this.firstSegment.type === "reduce") {
1520
- const n = [];
1521
- for (const field of this.rootResult.fields()) {
1522
- const fi = field;
1523
- if (fi.fieldUsage.type === "result" && isScalarField(fi.f)) {
1524
- n.push(fi.fieldUsage.resultIndex.toString());
1525
- }
1526
- }
1527
- if (n.length > 0) {
1528
- s += `GROUP BY ${n.join(",")}\n`;
1529
- }
1530
- }
1531
- s += this.generateSQLFilters(this.rootResult, "having").sql("having");
1532
- // order by
1533
- s += this.genereateSQLOrderBy(this.firstSegment, this.rootResult);
1534
- // limit
1535
- if (this.firstSegment.limit) {
1536
- s += `LIMIT ${this.firstSegment.limit}\n`;
1537
- }
1538
- this.resultStage = stageWriter.addStage(s);
1539
- return this.resultStage;
1540
- }
1541
- generateStage0Fields(resultSet, output, stageWriter) {
1542
- for (const [name, fi] of resultSet.allFields) {
1543
- if (fi instanceof FieldInstanceField) {
1544
- if (fi.fieldUsage.type === "result") {
1545
- if (isScalarField(fi.f)) {
1546
- let exp = fi.f.generateExpression(resultSet);
1547
- exp = this.caseGroup(resultSet.groupSet > 0 ? resultSet.childGroups : [], exp);
1548
- output.sql.push(`${exp} as ${name}__${resultSet.groupSet}`);
1549
- output.dimensionIndexes.push(output.fieldIndex++);
1550
- }
1551
- else if (isAggregateField(fi.f)) {
1552
- let exp = fi.f.generateExpression(resultSet);
1553
- exp = this.caseGroup([resultSet.groupSet], exp);
1554
- output.sql.push(`${exp} as ${name}__${resultSet.groupSet}`);
1555
- output.fieldIndex++;
1556
- }
1557
- }
1558
- }
1559
- else if (fi instanceof FieldInstanceResult) {
1560
- if (fi.firstSegment.type === "reduce") {
1561
- this.generateStage0Fields(fi, output, stageWriter);
1562
- }
1563
- else if (fi.firstSegment.type === "project") {
1564
- const s = this.generateTurtleSQL(fi, stageWriter);
1565
- output.sql.push(`${s} as ${name}__${resultSet.groupSet}`);
1566
- output.fieldIndex++;
1567
- }
1568
- }
1569
- }
1570
- // LTNOTE: we could optimize here in the future.
1571
- // leaf turtles can have their having clauses in the main query
1572
- // turtles with leaves need to promote their state to their
1573
- // children.
1574
- const having = this.generateSQLFilters(resultSet, "having");
1575
- if (!having.empty()) {
1576
- // if we have no children, the having can run at the root level
1577
- if (resultSet.childGroups.length === 1) {
1578
- resultSet
1579
- .root()
1580
- .havings.add(`(group_set<>${resultSet.groupSet} OR (group_set=${resultSet.groupSet} AND ${having.sql()}))`);
1581
- }
1582
- else {
1583
- resultSet.hasHaving = true;
1584
- output.sql.push(`CASE WHEN group_set=${resultSet.groupSet} THEN CASE WHEN ${having.sql()} THEN 0 ELSE 1 END END as __delete__${resultSet.groupSet}`);
1585
- output.fieldIndex++;
1586
- }
1587
- }
1588
- }
1589
- generateSQLWhereChildren(resultStruct) {
1590
- const wheres = new utils_1.AndChain();
1591
- for (const [, field] of resultStruct.allFields) {
1592
- if (field.type === "query") {
1593
- const fir = field;
1594
- const turtleWhere = this.generateSQLFilters(fir, "where");
1595
- if (turtleWhere.present()) {
1596
- const groupSets = fir.childGroups.join(",");
1597
- wheres.add(`(group_set NOT IN (${groupSets})` +
1598
- ` OR (group_set IN (${groupSets}) AND ${turtleWhere.sql()}))`);
1599
- }
1600
- wheres.addChain(this.generateSQLWhereChildren(fir));
1601
- }
1602
- }
1603
- return wheres;
1604
- }
1605
- generateSQLWhereTurtled() {
1606
- const wheres = this.generateSQLFilters(this.rootResult, "where");
1607
- wheres.addChain(this.generateSQLWhereChildren(this.rootResult));
1608
- return wheres.sql("where");
1609
- }
1610
- // iterate over the nested queries looking for Havings (and someday limits).
1611
- // if you find any, generate a new stage(s) to perform these functions.
1612
- generateSQLHavingLimit(stageWriter, lastStageName) {
1613
- const fields = [];
1614
- const resultsWithHaving = this.rootResult.selectStructs([], (result) => result.hasHaving);
1615
- if (resultsWithHaving.length > 0) {
1616
- for (const result of resultsWithHaving) {
1617
- // find all the parent dimension names.
1618
- const dimensions = [];
1619
- let r = result;
1620
- while (r) {
1621
- for (const name of r.fieldNames((fi) => isScalarField(fi.f))) {
1622
- dimensions.push(`${name}__${r.groupSet}`);
1623
- }
1624
- r = r.parent;
1625
- }
1626
- fields.push(`MAX(CASE WHEN group_set IN (${result.childGroups.join(",")}) THEN __delete__${result.groupSet} END) OVER(partition by ${dimensions
1627
- .map((x) => `CAST(${x} AS ${this.parent.dialect.stringTypeName}) `)
1628
- .join(",")}) as __shaving__${result.groupSet}`);
1629
- }
1630
- }
1631
- if (resultsWithHaving.length > 0) {
1632
- lastStageName = stageWriter.addStage(`SELECT\n *,\n ${fields.join(",\n ")} \nFROM ${lastStageName}`);
1633
- const havings = new utils_1.AndChain();
1634
- for (const result of resultsWithHaving) {
1635
- havings.add(`group_set IN (${result.childGroups.join(",")}) AND __shaving__${result.groupSet}=1`);
1636
- }
1637
- lastStageName = stageWriter.addStage(`SELECT *\nFROM ${lastStageName}\nWHERE NOT (${havings.sqlOr()})`);
1638
- }
1639
- return lastStageName;
1640
- }
1641
- generateSQLStage0(stageWriter) {
1642
- let s = "SELECT\n";
1643
- let from = this.generateSQLJoins(stageWriter);
1644
- const wheres = this.generateSQLWhereTurtled();
1645
- const f = {
1646
- dimensionIndexes: [1],
1647
- fieldIndex: 2,
1648
- sql: ["group_set"],
1649
- groupsAggregated: [],
1650
- };
1651
- this.generateStage0Fields(this.rootResult, f, stageWriter);
1652
- if (this.firstSegment.type === "project") {
1653
- throw new Error("PROJECT cannot be used on queries with turtles");
1654
- }
1655
- const groupBy = "GROUP BY " + f.dimensionIndexes.join(",") + "\n";
1656
- from += this.parent.dialect.sqlGroupSetTable(this.maxGroupSet) + "\n";
1657
- s += (0, utils_1.indent)(f.sql.join(",\n")) + "\n";
1658
- s += from + wheres + groupBy + this.rootResult.havings.sql("having");
1659
- // generate the stage
1660
- const resultStage = stageWriter.addStage(s);
1661
- // generate stages for havings and limits
1662
- this.resultStage = this.generateSQLHavingLimit(stageWriter, resultStage);
1663
- return this.resultStage;
1664
- }
1665
- generateDepthNFields(depth, resultSet, output, stageWriter) {
1666
- const groupsToMap = [];
1667
- for (const [name, fi] of resultSet.allFields) {
1668
- const sqlFieldName = `${name}__${resultSet.groupSet}`;
1669
- if (fi instanceof FieldInstanceField) {
1670
- if (fi.fieldUsage.type === "result") {
1671
- if (isScalarField(fi.f)) {
1672
- const exp = this.caseGroup(resultSet.groupSet > 0 ? resultSet.childGroups : [], sqlFieldName);
1673
- output.sql.push(`${exp} as ${sqlFieldName}`);
1674
- output.dimensionIndexes.push(output.fieldIndex++);
1675
- }
1676
- else if (isAggregateField(fi.f)) {
1677
- const exp = this.parent.dialect.sqlAnyValue(resultSet.groupSet, sqlFieldName);
1678
- output.sql.push(`${exp} as ${sqlFieldName}`);
1679
- output.fieldIndex++;
1680
- }
1681
- }
1682
- }
1683
- else if (fi instanceof FieldInstanceResult) {
1684
- if (fi.depth > depth) {
1685
- // ignore it, we've already dealt with it.
1686
- }
1687
- else if (fi.depth === depth) {
1688
- const s = this.generateTurtleSQL(fi, stageWriter);
1689
- output.groupsAggregated.push({
1690
- fromGroup: fi.groupSet,
1691
- toGroup: resultSet.groupSet,
1692
- });
1693
- groupsToMap.push(fi.groupSet);
1694
- output.sql.push(`${s} as ${sqlFieldName}`);
1695
- output.fieldIndex++;
1696
- }
1697
- else {
1698
- this.generateDepthNFields(depth, fi, output, stageWriter);
1699
- }
1700
- }
1701
- }
1702
- if (output.groupsAggregated.length > 0) {
1703
- output.sql[0] = `CASE `;
1704
- for (const m of output.groupsAggregated) {
1705
- output.sql[0] += `WHEN group_set=${m.fromGroup} THEN ${m.toGroup} `;
1706
- }
1707
- output.sql[0] += ` ELSE group_set END as group_set`;
1708
- }
1709
- }
1710
- generateSQLDepthN(depth, stageWriter, stageName) {
1711
- let s = "SELECT \n";
1712
- const f = {
1713
- dimensionIndexes: [1],
1714
- fieldIndex: 2,
1715
- sql: ["group_set"],
1716
- groupsAggregated: [],
1717
- };
1718
- this.generateDepthNFields(depth, this.rootResult, f, stageWriter);
1719
- s += (0, utils_1.indent)(f.sql.join(",\n")) + "\n";
1720
- s += `FROM ${stageName}\n`;
1721
- if (f.dimensionIndexes.length > 0) {
1722
- s += `GROUP BY ${f.dimensionIndexes.join(",")}\n`;
1723
- }
1724
- this.resultStage = stageWriter.addStage(s);
1725
- return this.resultStage;
1726
- }
1727
- genereateSQLCombineTurtles(stageWriter, stage0Name) {
1728
- let s = "SELECT\n";
1729
- const fieldsSQL = [];
1730
- let fieldIndex = 1;
1731
- const dimensionIndexes = [];
1732
- for (const [name, fi] of this.rootResult.allFields) {
1733
- const sqlName = this.parent.dialect.sqlMaybeQuoteIdentifier(name);
1734
- if (fi instanceof FieldInstanceField) {
1735
- if (fi.fieldUsage.type === "result") {
1736
- if (isScalarField(fi.f)) {
1737
- fieldsSQL.push(`${name}__0 as ${sqlName}`);
1738
- dimensionIndexes.push(fieldIndex++);
1739
- }
1740
- else if (isAggregateField(fi.f)) {
1741
- fieldsSQL.push(this.parent.dialect.sqlAnyValueLastTurtle(name, sqlName));
1742
- fieldIndex++;
1743
- }
1744
- }
1745
- }
1746
- else if (fi instanceof FieldInstanceResult) {
1747
- if (fi.firstSegment.type === "reduce") {
1748
- fieldsSQL.push(`${this.generateTurtleSQL(fi, stageWriter)} as ${sqlName}`);
1749
- fieldIndex++;
1750
- }
1751
- else if (fi.firstSegment.type === "project") {
1752
- fieldsSQL.push(this.parent.dialect.sqlAnyValueLastTurtle(name, sqlName));
1753
- fieldIndex++;
1754
- }
1755
- }
1756
- }
1757
- s += (0, utils_1.indent)(fieldsSQL.join(",\n")) + `\nFROM ${stage0Name}\n`;
1758
- if (dimensionIndexes.length > 0) {
1759
- s += `GROUP BY ${dimensionIndexes.join(",")}\n`;
1760
- }
1761
- // order by
1762
- s += this.genereateSQLOrderBy(this.firstSegment, this.rootResult);
1763
- // limit
1764
- if (this.firstSegment.limit) {
1765
- s += `LIMIT ${this.firstSegment.limit}\n`;
1766
- }
1767
- this.resultStage = stageWriter.addStage(s);
1768
- return this.resultStage;
1769
- }
1770
- generateTurtleSQL(resultStruct, stageWriter) {
1771
- // let fieldsSQL: string[] = [];
1772
- const dialectFieldList = [];
1773
- let orderBy = "";
1774
- const limit = resultStruct.firstSegment.limit;
1775
- // If the turtle is a pipeline, generate a UDF to compute it.
1776
- const newStageWriter = new StageWriter(stageWriter);
1777
- const { hasPipeline } = this.generateTurtlePipelineSQL(resultStruct, newStageWriter);
1778
- let udfName;
1779
- if (hasPipeline) {
1780
- udfName = stageWriter.addUDF(newStageWriter, this.parent.dialect);
1781
- }
1782
- // calculate the ordering.
1783
- const obSQL = [];
1784
- let orderingField;
1785
- const orderByDef = resultStruct.firstSegment.orderBy ||
1786
- resultStruct.calculateDefaultOrderBy();
1787
- for (const ordering of orderByDef) {
1788
- if (typeof ordering.field === "string") {
1789
- orderingField = {
1790
- name: ordering.field,
1791
- fif: resultStruct.getField(ordering.field),
1792
- };
1793
- }
1794
- else {
1795
- orderingField = resultStruct.getFieldByNumber(ordering.field);
1796
- }
1797
- if (resultStruct.firstSegment.type === "reduce") {
1798
- obSQL.push(` ${orderingField.name}__${resultStruct.groupSet} ${ordering.dir || "ASC"}`);
1799
- }
1800
- else if (resultStruct.firstSegment.type === "project") {
1801
- obSQL.push(` ${orderingField.fif.f.generateExpression(resultStruct)} ${ordering.dir || "ASC"}`);
1802
- }
1803
- }
1804
- if (obSQL.length > 0) {
1805
- orderBy = ` ORDER BY ${obSQL.join(",")}`;
1806
- }
1807
- for (const [name, field] of resultStruct.allFields) {
1808
- const sqlName = this.parent.dialect.sqlMaybeQuoteIdentifier(name);
1809
- //
1810
- if (resultStruct.firstSegment.type === "reduce" &&
1811
- (field instanceof FieldInstanceResult ||
1812
- (field instanceof FieldInstanceField &&
1813
- field.fieldUsage.type === "result"))) {
1814
- // fieldsSQL.push(`${name}__${resultStruct.groupSet} as ${sqlName}`);
1815
- // outputFieldNames.push(name);
1816
- dialectFieldList.push({
1817
- type: field instanceof FieldInstanceField
1818
- ? field.f.fieldDef.type
1819
- : "struct",
1820
- sqlExpression: `${name}__${resultStruct.groupSet}`,
1821
- sqlOutputName: sqlName,
1822
- });
1823
- }
1824
- else if (resultStruct.firstSegment.type === "project" &&
1825
- field instanceof FieldInstanceField &&
1826
- field.fieldUsage.type === "result") {
1827
- // fieldsSQL.push(
1828
- // `${field.f.generateExpression(resultStruct)} as ${sqlName}`
1829
- // );
1830
- dialectFieldList.push({
1831
- type: field.type,
1832
- sqlExpression: field.f.generateExpression(resultStruct),
1833
- sqlOutputName: sqlName,
1834
- });
1835
- }
1836
- }
1837
- let resultType;
1838
- let ret;
1839
- if ((resultType = resultStruct.getRepeatedResultType()) !== "nested") {
1840
- if (resultType === "inline_all_numbers") {
1841
- ret = this.parent.dialect.sqlCoaleseMeasuresInline(resultStruct.groupSet, dialectFieldList);
1842
- }
1843
- else {
1844
- ret = this.parent.dialect.sqlAnyValueTurtle(resultStruct.groupSet, dialectFieldList);
1845
- }
1846
- }
1847
- else {
1848
- ret = this.parent.dialect.sqlAggregateTurtle(resultStruct.groupSet, dialectFieldList, orderBy, limit);
1849
- }
1850
- return udfName ? `${udfName}(${ret})` : ret;
1851
- // return `${aggregateFunction}(CASE WHEN group_set=${
1852
- // resultStruct.groupSet
1853
- // } THEN STRUCT(${fieldsSQL.join(",\n")}) END${tailSQL})`;
1854
- }
1855
- generateTurtlePipelineSQL(fi, stageWriter) {
1856
- let structDef = this.getResultStructDef(fi, false);
1857
- const hasPipeline = fi.turtleDef.pipeline.length > 1;
1858
- if (hasPipeline) {
1859
- const pipeline = [...fi.turtleDef.pipeline];
1860
- pipeline.shift();
1861
- const newTurtle = {
1862
- type: "turtle",
1863
- name: "starthere",
1864
- pipeline,
1865
- };
1866
- structDef.name = this.parent.dialect.sqlUnnestPipelineHead();
1867
- structDef.structSource = { type: "sql", method: "nested" };
1868
- const qs = new QueryStruct(structDef, {
1869
- model: this.parent.getModel(),
1870
- });
1871
- const q = QueryQuery.makeQuery(newTurtle, qs, stageWriter);
1872
- const { outputStruct } = q.generateSQLFromPipeline(stageWriter);
1873
- // console.log(stageWriter.generateSQLStages());
1874
- structDef = outputStruct;
1875
- }
1876
- return { structDef, hasPipeline };
1877
- }
1878
- generateComplexSQL(stageWriter) {
1879
- let stageName = this.generateSQLStage0(stageWriter);
1880
- if (this.maxDepth > 1) {
1881
- let i = this.maxDepth;
1882
- while (i > 1) {
1883
- stageName = this.generateSQLDepthN(i, stageWriter, stageName);
1884
- i--;
1885
- }
1886
- }
1887
- // nest the turtles.
1888
- return this.genereateSQLCombineTurtles(stageWriter, stageName);
1889
- }
1890
- generateSQL(stageWriter) {
1891
- const r = this.rootResult.computeGroups(0, 0);
1892
- this.maxDepth = r.maxDepth;
1893
- this.maxGroupSet = r.nextGroupSetNumber - 1;
1894
- if (this.maxDepth === 0 && !r.isComplex) {
1895
- return this.generateSimpleSQL(stageWriter);
1896
- }
1897
- else {
1898
- return this.generateComplexSQL(stageWriter);
1899
- }
1900
- }
1901
- toMalloy() {
1902
- let ret = `EXPLORE ${(0, malloy_types_1.getIdentifier)(this.parent.fieldDef)} | `;
1903
- ret += this.fieldDef.type.toUpperCase() + " ";
1904
- return ret;
1905
- }
1906
- generateSQLFromPipeline(stageWriter) {
1907
- this.prepare(stageWriter);
1908
- let lastStageName = this.generateSQL(stageWriter);
1909
- let outputStruct = this.getResultStructDef();
1910
- if (this.fieldDef.pipeline.length > 1) {
1911
- // console.log(pretty(outputStruct));
1912
- const pipeline = [...this.fieldDef.pipeline];
1913
- pipeline.shift();
1914
- for (const transform of pipeline) {
1915
- const s = new QueryStruct(outputStruct, {
1916
- model: this.parent.getModel(),
1917
- });
1918
- const q = QueryQuery.makeQuery({ type: "turtle", name: "ignoreme", pipeline: [transform] }, s, stageWriter);
1919
- q.prepare(stageWriter);
1920
- lastStageName = q.generateSQL(stageWriter);
1921
- outputStruct = q.getResultStructDef();
1922
- }
1923
- }
1924
- return { lastStageName, outputStruct };
1925
- }
1926
- }
1927
- class QueryQueryReduce extends QueryQuery {
1928
- }
1929
- class QueryQueryProject extends QueryQuery {
1930
- }
1931
- class QueryQueryIndex extends QueryQuery {
1932
- constructor(fieldDef, parent, stageWriter) {
1933
- super(fieldDef, parent, stageWriter);
1934
- this.fieldDef = fieldDef;
1935
- }
1936
- // get a field ref and expand it.
1937
- expandField(f) {
1938
- const field = this.parent.getFieldByName(f);
1939
- return { as: f, field };
1940
- }
1941
- expandFields(resultStruct) {
1942
- let resultIndex = 1;
1943
- const groupIndex = resultStruct.groupSet;
1944
- this.maxGroupSet = groupIndex;
1945
- // if no fields were specified, look in the parent struct for strings.
1946
- let fieldNames = this.firstSegment.fields || [];
1947
- if (fieldNames.length === 0) {
1948
- fieldNames.push("**");
1949
- }
1950
- fieldNames = this.expandWildCards(fieldNames, (qf) => ["string", "number", "timestamp", "date"].indexOf(qf.fieldDef.type) !==
1951
- -1);
1952
- for (const f of fieldNames) {
1953
- const { as, field } = this.expandField(f);
1954
- resultStruct.addField(as, field, {
1955
- resultIndex,
1956
- type: "result",
1957
- });
1958
- resultIndex++;
1959
- }
1960
- const measure = this.firstSegment.weightMeasure;
1961
- if (measure !== undefined) {
1962
- resultStruct.addField(measure, this.parent.getFieldByName(measure), {
1963
- resultIndex,
1964
- type: "result",
1965
- });
1966
- }
1967
- this.expandFilters(resultStruct);
1968
- }
1969
- generateSQL(stageWriter) {
1970
- let measureSQL = "COUNT(*)";
1971
- const dialect = this.parent.dialect;
1972
- const measureName = this.firstSegment.weightMeasure;
1973
- if (measureName) {
1974
- measureSQL = this.rootResult
1975
- .getField(measureName)
1976
- .f.generateExpression(this.rootResult);
1977
- }
1978
- const fields = [];
1979
- for (const [name, field] of this.rootResult.allFields) {
1980
- const fi = field;
1981
- if (fi.fieldUsage.type === "result" && isScalarField(fi.f)) {
1982
- const expression = fi.f.generateExpression(this.rootResult);
1983
- fields.push({ name, type: fi.f.fieldDef.type, expression });
1984
- }
1985
- }
1986
- let s = `SELECT\n group_set,\n`;
1987
- s += ` CASE group_set\n`;
1988
- for (let i = 0; i < fields.length; i++) {
1989
- s += ` WHEN ${i} THEN '${fields[i].name}'\n`;
1990
- }
1991
- s += ` END as field_name,`;
1992
- s += ` CASE group_set\n`;
1993
- for (let i = 0; i < fields.length; i++) {
1994
- s += ` WHEN ${i} THEN '${fields[i].type}'\n`;
1995
- }
1996
- s += ` END as field_type,`;
1997
- s += ` CASE group_set\n`;
1998
- for (let i = 0; i < fields.length; i++) {
1999
- if (fields[i].type === "string") {
2000
- s += ` WHEN ${i} THEN ${fields[i].expression}\n`;
2001
- }
2002
- }
2003
- s += ` END as field_value,\n`;
2004
- s += ` ${measureSQL} as weight,\n`;
2005
- // just in case we don't have any field types, force the case statement to have at least one value.
2006
- s += ` CASE group_set\n WHEN 99999 THEN ''`;
2007
- for (let i = 0; i < fields.length; i++) {
2008
- if (fields[i].type === "number") {
2009
- s += ` WHEN ${i} THEN CAST(MIN(${fields[i].expression}) AS ${dialect.stringTypeName}) || ' to ' || CAST(MAX(${fields[i].expression}) AS ${dialect.stringTypeName})\n`;
2010
- }
2011
- if (fields[i].type === "timestamp" || fields[i].type === "date") {
2012
- s += ` WHEN ${i} THEN MIN(${dialect.sqlDateToString(fields[i].expression)}) || ' to ' || MAX(${dialect.sqlDateToString(fields[i].expression)})\n`;
2013
- }
2014
- }
2015
- s += ` END as field_range\n`;
2016
- // CASE
2017
- // WHEN field_type = 'timestamp' or field_type = 'date'
2018
- // THEN MIN(field_value) || ' to ' || MAX(field_value)
2019
- // WHEN field_type = 'number'
2020
- // THEN
2021
- // ELSE NULL
2022
- // END as field_range\n`;
2023
- s += this.generateSQLJoins(stageWriter);
2024
- s += dialect.sqlGroupSetTable(fields.length) + "\n";
2025
- s += this.generateSQLFilters(this.rootResult, "where").sql("where");
2026
- s += "GROUP BY 1,2,3,4\nORDER BY 5 DESC\n";
2027
- // limit
2028
- if (this.firstSegment.limit) {
2029
- s += `LIMIT ${this.firstSegment.limit}\n`;
2030
- }
2031
- // console.log(s);
2032
- const resultStage = stageWriter.addStage(s);
2033
- this.resultStage = stageWriter.addStage(`SELECT
2034
- field_name,
2035
- field_type,
2036
- COALESCE(field_value, field_range) as field_value,
2037
- weight
2038
- FROM ${resultStage}\n`);
2039
- return this.resultStage;
2040
- }
2041
- /** All Indexes have the same output schema */
2042
- getResultStructDef() {
2043
- return {
2044
- type: "struct",
2045
- name: this.resultStage || "result",
2046
- dialect: this.parent.fieldDef.dialect,
2047
- fields: [
2048
- { type: "string", name: "field_name" },
2049
- { type: "string", name: "field_value" },
2050
- { type: "string", name: "field_type" },
2051
- { type: "number", name: "weight", numberType: "integer" },
2052
- ],
2053
- structRelationship: {
2054
- type: "basetable",
2055
- connectionName: this.parent.connectionName,
2056
- },
2057
- structSource: { type: "table" },
2058
- };
2059
- }
2060
- }
2061
- /** Structure object as it is used to build a query */
2062
- class QueryStruct extends QueryNode {
2063
- constructor(fieldDef, parent) {
2064
- super(fieldDef);
2065
- this.nameMap = new Map();
2066
- this.setParent(parent);
2067
- if ("model" in parent) {
2068
- this.model = parent.model;
2069
- this.pathAliasMap = new Map();
2070
- if (fieldDef.structRelationship.type === "basetable") {
2071
- this.connectionName = fieldDef.structRelationship.connectionName;
2072
- }
2073
- else {
2074
- throw new Error("All root StructDefs should be a baseTable");
2075
- }
2076
- }
2077
- else {
2078
- this.model = this.getModel();
2079
- this.pathAliasMap = this.root().pathAliasMap;
2080
- this.connectionName = this.root().connectionName;
2081
- }
2082
- this.fieldDef = fieldDef; // shouldn't have to do this, but
2083
- // type script is missing a beat here.
2084
- this.dialect = (0, dialect_1.getDialect)(this.fieldDef.dialect);
2085
- this.addFieldsFromFieldList(this.fieldDef.fields);
2086
- }
2087
- parameters() {
2088
- return this.fieldDef.parameters || {};
2089
- }
2090
- addFieldsFromFieldList(fields) {
2091
- for (const field of fields) {
2092
- const as = (0, malloy_types_1.getIdentifier)(field);
2093
- switch (field.type) {
2094
- case "struct": {
2095
- this.addFieldToNameMap(as, new QueryStruct(field, {
2096
- struct: this,
2097
- }));
2098
- break;
2099
- }
2100
- // case "reduce" || "project" || "index": {
2101
- case "turtle": {
2102
- // not sure why we need to cast here...
2103
- this.addFieldToNameMap(as, QueryQuery.makeQuery(field, this));
2104
- break;
2105
- }
2106
- default: {
2107
- this.addFieldToNameMap(as, this.makeQueryField(field));
2108
- }
2109
- }
2110
- }
2111
- // if we don't have distinct key yet for this struct, add it.
2112
- if (!this.nameMap.has("__distinct_key")) {
2113
- this.addFieldToNameMap("__distinct_key", new QueryFieldDistinctKey({ type: "string", name: "__distinct_key" }, this));
2114
- }
2115
- }
2116
- // generate unique string for the alias.
2117
- // return a string that can be used to represent the full
2118
- // join path to a struct.
2119
- getAliasIdentifier() {
2120
- const path = this.getFullOutputName();
2121
- const ret = this.pathAliasMap.get(path);
2122
- // make a unique alias name
2123
- if (ret === undefined) {
2124
- const aliases = Array.from(this.pathAliasMap.values());
2125
- const base = (0, malloy_types_1.getIdentifier)(this.fieldDef);
2126
- let name = `${base}_0`;
2127
- let n = 1;
2128
- while (aliases.includes(name) && n < 1000) {
2129
- n++;
2130
- name = `${base}_${n}`;
2131
- }
2132
- if (n < 1000) {
2133
- this.pathAliasMap.set(path, name);
2134
- return name;
2135
- }
2136
- else {
2137
- throw new Error("Internal Error: cannot create unique alias name");
2138
- }
2139
- // get the malloy name for this struct (will include a trailing dot)
2140
- // return this.getFullOutputName().replace(/\.$/, "").replace(/\./g, "_o_");
2141
- }
2142
- else {
2143
- return ret;
2144
- }
2145
- }
2146
- // when structs are referenced in queries, incorporate the
2147
- // primary key of struct and add the struct as a join to the result.
2148
- getAsQueryField() {
2149
- if (this.fieldDef.primaryKey === undefined) {
2150
- throw new Error(`Joined explores can only be included in queries if a primary key is defined: '${this.getFullOutputName()}' has no primary key`);
2151
- }
2152
- const pkField = this.getPrimaryKeyField(this.fieldDef);
2153
- const pkType = pkField.fieldDef.type;
2154
- if (pkType !== "string" && pkType !== "number") {
2155
- throw new Error(`Unknown Primary key data type for ${name}`);
2156
- }
2157
- const aliasName = (0, malloy_types_1.getIdentifier)(this.fieldDef);
2158
- const pkName = this.fieldDef.primaryKey;
2159
- const fieldDef = {
2160
- type: pkType,
2161
- name: `${aliasName}_id`,
2162
- e: [
2163
- {
2164
- type: "field",
2165
- // path: pkField.getFullOutputName(),
2166
- path: pkField.getIdentifier(),
2167
- },
2168
- ],
2169
- };
2170
- return new QueryFieldStruct(fieldDef, this, `${aliasName}.${pkName}`);
2171
- }
2172
- // return the name of the field in SQL
2173
- getIdentifier() {
2174
- // if it is the root table, use provided alias if we have one.
2175
- if (this.fieldDef.structRelationship.type === "basetable") {
2176
- if (this.fieldDef.as === undefined) {
2177
- return "base";
2178
- }
2179
- else {
2180
- return super.getIdentifier();
2181
- }
2182
- }
2183
- // if this is an inline object, include the parents alias.
2184
- if (this.fieldDef.structRelationship.type === "inline" && this.parent) {
2185
- return this.parent.getIdentifier() + "." + super.getIdentifier();
2186
- }
2187
- // we are somewhere in the join tree. Make sure the alias is unique.
2188
- return this.getAliasIdentifier();
2189
- }
2190
- // return the name of the field in Malloy
2191
- getFullOutputName() {
2192
- if (this.parent) {
2193
- return (this.parent.getFullOutputName() + (0, malloy_types_1.getIdentifier)(this.fieldDef) + ".");
2194
- }
2195
- else {
2196
- return "";
2197
- }
2198
- }
2199
- needsSymetricCalculation(resultSet) {
2200
- const joinName = this.getJoinableParent().getIdentifier();
2201
- const join = resultSet.root().joins.get(joinName);
2202
- if (join) {
2203
- return !join.leafiest;
2204
- }
2205
- throw new Error(`Join ${joinName} not found in result set`);
2206
- }
2207
- getJoinableParent() {
2208
- // if it is inline it should always have a parent
2209
- if (this.fieldDef.structRelationship.type === "inline") {
2210
- if (this.parent) {
2211
- return this.parent.getJoinableParent();
2212
- }
2213
- else {
2214
- throw new Error(`Internal Error: inline struct cannot be root`);
2215
- }
2216
- }
2217
- return this;
2218
- }
2219
- addFieldToNameMap(as, n) {
2220
- if (this.nameMap.has(as)) {
2221
- throw new Error(`Redefinition of ${as}`);
2222
- }
2223
- this.nameMap.set(as, n);
2224
- }
2225
- /** the the primary key or throw an error. */
2226
- getPrimaryKeyField(fieldDef) {
2227
- let pk;
2228
- if ((pk = this.primaryKey())) {
2229
- return pk;
2230
- }
2231
- else {
2232
- throw new Error(`Missing primary key for ${fieldDef}`);
2233
- }
2234
- }
2235
- /**
2236
- * called after all structure has been loaded. Examine this structure to see
2237
- * if if it is based on a query and if it is, add the output fields (unless
2238
- * they exist) to the structure.
2239
- */
2240
- resolveQueryFields() {
2241
- if (this.fieldDef.structSource.type === "query") {
2242
- const structDef = this.model
2243
- .loadQuery(this.fieldDef.structSource.query, undefined)
2244
- .structs.pop();
2245
- // should never happen.
2246
- if (!structDef) {
2247
- throw new Error("Internal Error, query didn't produce a struct");
2248
- }
2249
- const fieldDef = { ...this.fieldDef };
2250
- for (const f of structDef.fields) {
2251
- let as;
2252
- if (!this.nameMap.has((as = (0, malloy_types_1.getIdentifier)(f)))) {
2253
- fieldDef.fields.push(f);
2254
- this.nameMap.set(as, this.makeQueryField(f));
2255
- }
2256
- }
2257
- this.fieldDef = fieldDef;
2258
- if (!this.fieldDef.primaryKey && structDef.primaryKey) {
2259
- this.fieldDef.primaryKey = structDef.primaryKey;
2260
- }
2261
- }
2262
- for (const [, v] of this.nameMap) {
2263
- if (v instanceof QueryStruct) {
2264
- v.resolveQueryFields();
2265
- }
2266
- }
2267
- }
2268
- getModel() {
2269
- if (this.model) {
2270
- return this.model;
2271
- }
2272
- else {
2273
- if (this.parent === undefined) {
2274
- throw new Error("Expected this query struct to have a parent, as no model was present.");
2275
- }
2276
- return this.parent.getModel();
2277
- }
2278
- }
2279
- setParent(parent) {
2280
- if ("struct" in parent) {
2281
- this.parent = parent.struct;
2282
- }
2283
- if ("model" in parent) {
2284
- this.model = parent.model;
2285
- }
2286
- else {
2287
- this.model = this.getModel();
2288
- }
2289
- }
2290
- /** makes a new queryable field object from a fieldDef */
2291
- makeQueryField(field) {
2292
- switch (field.type) {
2293
- case "string":
2294
- return new QueryFieldString(field, this);
2295
- case "date":
2296
- return new QueryFieldDate(field, this);
2297
- case "timestamp":
2298
- return new QueryFieldTimestamp(field, this);
2299
- case "number":
2300
- return new QueryFieldNumber(field, this);
2301
- case "boolean":
2302
- return new QueryFieldBoolean(field, this);
2303
- // case "reduce":
2304
- // case "project":
2305
- // case "index":
2306
- case "turtle":
2307
- return new QueryTurtle(field, this);
2308
- default:
2309
- throw new Error(`unknown field definition ${JSON.stringify(field)}`);
2310
- }
2311
- }
2312
- /**
2313
- * return a field if it exists, make one if we are passed a field definition.
2314
- */
2315
- getOrMakeField(fieldRef) {
2316
- if (typeof fieldRef === "string") {
2317
- return this.getFieldByName(fieldRef);
2318
- }
2319
- else {
2320
- return this.makeQueryField(fieldRef);
2321
- }
2322
- }
2323
- /** returns a dimension for the given name or make one. */
2324
- getOrMakeDimension(fieldRef) {
2325
- const dim = this.getOrMakeField(fieldRef);
2326
- if (dim instanceof QueryAtomicField && isScalarField(dim)) {
2327
- return dim;
2328
- }
2329
- else {
2330
- throw new Error(`${fieldRef} is not of type a scalar'`);
2331
- }
2332
- }
2333
- structSourceSQL(stageWriter) {
2334
- switch (this.fieldDef.structSource.type) {
2335
- case "table": {
2336
- const { tablePath } = (0, malloy_1.parseTableURL)(this.fieldDef.structSource.tablePath || this.fieldDef.name);
2337
- return this.dialect.quoteTableName(tablePath);
2338
- }
2339
- case "sql":
2340
- if (this.fieldDef.structSource.method === "nested") {
2341
- return this.fieldDef.name;
2342
- }
2343
- else if (this.fieldDef.structSource.method === "subquery") {
2344
- return `(${this.fieldDef.structSource.sqlBlock.select})`;
2345
- }
2346
- throw new Error("Internal Error: Unknown structSource type 'sql' method");
2347
- case "nested":
2348
- // 'name' is always the source field even if has been renamed through
2349
- // 'as'
2350
- return `UNNEST(this.fieldDef.name)`;
2351
- case "inline":
2352
- return "";
2353
- case "query": {
2354
- // cache derived table.
2355
- const name = (0, malloy_types_1.getIdentifier)(this.fieldDef);
2356
- // this is a hack for now. Need some way to denote this table
2357
- // should be cached.
2358
- if (name.includes("cache")) {
2359
- const dtStageWriter = new StageWriter(stageWriter);
2360
- this.model.loadQuery(this.fieldDef.structSource.query, dtStageWriter);
2361
- return dtStageWriter.addPDT(name, this.dialect);
2362
- }
2363
- else {
2364
- // returns the stage name.
2365
- return this.model.loadQuery(this.fieldDef.structSource.query, stageWriter).lastStageName;
2366
- }
2367
- }
2368
- default:
2369
- throw new Error(`unknown structSource ${this.fieldDef}`);
2370
- }
2371
- }
2372
- root() {
2373
- if (this.parent === undefined) {
2374
- return this;
2375
- }
2376
- else {
2377
- return this.parent.root();
2378
- }
2379
- }
2380
- primaryKey() {
2381
- if (this.fieldDef.primaryKey) {
2382
- return this.getDimensionByName(this.fieldDef.primaryKey);
2383
- }
2384
- else {
2385
- return undefined;
2386
- }
2387
- }
2388
- /** get the componennts of a field path */
2389
- static resolvePath(name) {
2390
- return name.split(".");
2391
- }
2392
- getChildByName(name) {
2393
- return this.nameMap.get(name);
2394
- }
2395
- /** convert a name into a field reference */
2396
- getFieldByName(name) {
2397
- const path = QueryStruct.resolvePath(name);
2398
- let ret = this;
2399
- for (const n of path) {
2400
- const r = ret.getChildByName(n);
2401
- if (r === undefined) {
2402
- throw new Error(`Path not found ${name}`);
2403
- }
2404
- ret = r;
2405
- }
2406
- return ret;
2407
- }
2408
- // structs referenced in queries are converted to fields.
2409
- getQueryFieldByName(name) {
2410
- let field = this.getFieldByName(name);
2411
- if (field instanceof QueryStruct) {
2412
- field = field.getAsQueryField();
2413
- }
2414
- return field;
2415
- }
2416
- getDimensionOrMeasureByName(name) {
2417
- const query = this.getFieldByName(name);
2418
- if (query instanceof QueryAtomicField) {
2419
- return query;
2420
- }
2421
- else {
2422
- throw new Error(`${name} is not of type a scalar'`);
2423
- }
2424
- }
2425
- /** returns a query object for the given name */
2426
- getDimensionByName(name) {
2427
- const query = this.getFieldByName(name);
2428
- if (query instanceof QueryAtomicField && isScalarField(query)) {
2429
- return query;
2430
- }
2431
- else {
2432
- throw new Error(`${name} is not of type a scalar'`);
2433
- }
2434
- }
2435
- /** returns a query object for the given name */
2436
- getStructByName(name) {
2437
- const struct = this.getFieldByName(name);
2438
- if (struct instanceof QueryStruct) {
2439
- return struct;
2440
- }
2441
- else {
2442
- throw new Error(`Error: Path to structure not found '${name}'`);
2443
- }
2444
- }
2445
- getDistinctKey() {
2446
- if (this.fieldDef.structRelationship.type !== "inline") {
2447
- return this.getDimensionByName("__distinct_key");
2448
- }
2449
- else if (this.parent) {
2450
- return this.parent.getDistinctKey();
2451
- }
2452
- else {
2453
- throw new Error("Internal Error. inline struct can not be top level");
2454
- }
2455
- }
2456
- // take a TurtleDef that might have names and make it so it doesn't.
2457
- flattenTurtleDef(turtleDef) {
2458
- let pipeline = turtleDef.pipeline;
2459
- let pipeHead = turtleDef.pipeHead;
2460
- while (pipeHead) {
2461
- const field = this.getFieldByName(pipeHead.name);
2462
- if (field instanceof QueryQuery) {
2463
- pipeHead = field.fieldDef.pipeHead;
2464
- pipeline = field.fieldDef.pipeline.concat(pipeline);
2465
- }
2466
- else {
2467
- throw new Error(`Only Turtles can be used in a pipeline ${pipeHead.name}`);
2468
- }
2469
- }
2470
- const addedFilters = turtleDef.filterList || [];
2471
- pipeline = (0, lodash_1.cloneDeep)(pipeline);
2472
- pipeline[0].filterList = addedFilters.concat(pipeline[0].filterList || [], this.fieldDef.filterList || []);
2473
- const flatTurtleDef = {
2474
- type: "turtle",
2475
- name: turtleDef.name,
2476
- pipeline,
2477
- location: turtleDef.location,
2478
- };
2479
- return flatTurtleDef;
2480
- }
2481
- }
2482
- // const exploreSearchSQLMap = new Map<string, string>();
2483
- /** start here */
2484
- class QueryModel {
2485
- constructor(modelDef) {
2486
- this.dialect = new standardsql_1.StandardSQLDialect();
2487
- // dialect: Dialect = new PostgresDialect();
2488
- this.modelDef = undefined;
2489
- this.structs = new Map();
2490
- if (modelDef) {
2491
- this.loadModelFromDef(modelDef);
2492
- }
2493
- }
2494
- loadModelFromDef(modelDef) {
2495
- this.modelDef = modelDef;
2496
- for (const s of Object.values(this.modelDef.contents)) {
2497
- let qs;
2498
- if (s.type === "struct") {
2499
- qs = new QueryStruct(s, { model: this });
2500
- this.structs.set((0, malloy_types_1.getIdentifier)(s), qs);
2501
- qs.resolveQueryFields();
2502
- }
2503
- else if (s.type === "query") {
2504
- /* TODO */
2505
- }
2506
- else {
2507
- throw new Error("Internal Error: Unknown structure type");
2508
- }
2509
- }
2510
- }
2511
- parseQueryPath(name) {
2512
- const path = name.split(".");
2513
- let struct;
2514
- if ((struct = this.structs.get(path[0]))) {
2515
- if (path.length > 1) {
2516
- path.shift();
2517
- }
2518
- else {
2519
- throw new Error(`No query specified in Struct '${path[0]}'`);
2520
- }
2521
- return { queryName: path.join("."), struct };
2522
- }
2523
- else {
2524
- throw new Error(`Cannot find Struct '${path[0]}' Model`);
2525
- }
2526
- }
2527
- getStructByName(name) {
2528
- let s;
2529
- if ((s = this.structs.get(name))) {
2530
- return s;
2531
- }
2532
- else {
2533
- throw new Error(`Struct ${name} not found in model.`);
2534
- }
2535
- }
2536
- getStructFromRef(structRef) {
2537
- let structDef;
2538
- if (typeof structRef === "string") {
2539
- return this.getStructByName(structRef);
2540
- }
2541
- else if (structRef.type === "struct") {
2542
- structDef = structRef;
2543
- }
2544
- else {
2545
- throw new Error("Broken for now");
2546
- }
2547
- return new QueryStruct(structDef, { model: this });
2548
- }
2549
- loadQuery(query, stageWriter, emitFinalStage = false) {
2550
- const malloy = "";
2551
- if (!stageWriter) {
2552
- stageWriter = new StageWriter(undefined);
2553
- }
2554
- const turtleDef = {
2555
- type: "turtle",
2556
- name: "ignoreme",
2557
- pipeHead: query.pipeHead,
2558
- pipeline: query.pipeline,
2559
- filterList: query.filterList,
2560
- };
2561
- const q = QueryQuery.makeQuery(turtleDef, this.getStructFromRef(query.structRef), stageWriter);
2562
- const ret = q.generateSQLFromPipeline(stageWriter);
2563
- if (emitFinalStage && q.parent.dialect.hasFinalStage) {
2564
- ret.lastStageName = stageWriter.addStage(q.parent.dialect.sqlFinalStage(ret.lastStageName));
2565
- }
2566
- return {
2567
- lastStageName: ret.lastStageName,
2568
- malloy,
2569
- stageWriter,
2570
- structs: [ret.outputStruct],
2571
- connectionName: q.parent.connectionName,
2572
- };
2573
- }
2574
- compileQuery(query) {
2575
- let newModel;
2576
- const m = newModel || this;
2577
- const ret = m.loadQuery(query, undefined, true);
2578
- const sourceExplore = typeof query.structRef === "string"
2579
- ? query.structRef
2580
- : // LTNOTE: the parser needs to capture the query before the |. This will work
2581
- // in most cases but isn't actually complete.
2582
- query.structRef.type === "struct"
2583
- ? query.structRef.as || query.structRef.name
2584
- : "(need to figure this out)";
2585
- if (this.dialect.hasFinalStage) {
2586
- ret.lastStageName = ret.stageWriter.addStage(this.dialect.sqlFinalStage(ret.lastStageName));
2587
- }
2588
- return {
2589
- lastStageName: ret.lastStageName,
2590
- malloy: ret.malloy,
2591
- sql: ret.stageWriter.generateSQLStages(),
2592
- structs: ret.structs,
2593
- sourceExplore,
2594
- sourceFilters: query.filterList,
2595
- queryName: query.pipeHead && query.pipeline.length === 0
2596
- ? query.pipeHead.name
2597
- : undefined,
2598
- connectionName: ret.connectionName,
2599
- };
2600
- }
2601
- }
2602
- exports.QueryModel = QueryModel;
2603
- //# sourceMappingURL=malloy-query.js.map