@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.
- package/README.md +12 -11
- package/dist/dialect/duckdb.d.ts +1 -1
- package/dist/dialect/postgres.d.ts +1 -1
- package/dist/index.d.ts +4 -4
- package/dist/lang/ast/ast-expr.d.ts +0 -1
- package/dist/lang/field-space.d.ts +9 -7
- package/dist/lang/lib/Malloy/MalloyLexer.d.ts +108 -106
- package/dist/lang/lib/Malloy/MalloyLexer.js +1019 -1006
- package/dist/lang/lib/Malloy/MalloyParser.d.ts +581 -590
- package/dist/lang/lib/Malloy/MalloyParser.js +1141 -1202
- package/dist/lang/lib/Malloy/MalloyParserListener.d.ts +2015 -0
- package/dist/lang/lib/Malloy/MalloyParserListener.js +4 -0
- package/dist/lang/lib/Malloy/MalloyParserVisitor.d.ts +1269 -0
- package/dist/lang/lib/Malloy/MalloyParserVisitor.js +4 -0
- package/dist/lang/parse-to-ast.d.ts +3 -4
- package/dist/lang/parse-to-ast.js +1 -1
- package/dist/lang/parse-tree-walkers/document-completion-walker.js +2 -0
- package/dist/lang/parse-tree-walkers/explore-query-walker.d.ts +2 -2
- package/dist/malloy.d.ts +1 -1
- package/dist/md5.d.ts +1 -0
- package/dist/md5.js +30 -0
- package/dist/model/malloy_types.d.ts +3 -1
- package/package.json +9 -14
- package/dist/connection_utils.js +0 -56
- package/dist/dialect/dialect-map.d.ts +0 -3
- package/dist/dialect/dialect-map.js +0 -33
- package/dist/dialect/dialect.js +0 -77
- package/dist/dialect/dialect_map.js +0 -35
- package/dist/dialect/duckdb.js +0 -349
- package/dist/dialect/index.js +0 -27
- package/dist/dialect/postgres.js +0 -308
- package/dist/dialect/standardsql.js +0 -361
- package/dist/index.js +0 -47
- package/dist/lang/ast/apply-expr.js +0 -231
- package/dist/lang/ast/ast-expr.js +0 -1051
- package/dist/lang/ast/ast-main.js +0 -2086
- package/dist/lang/ast/ast-time-expr.js +0 -549
- package/dist/lang/ast/ast-types.js +0 -215
- package/dist/lang/ast/index.js +0 -34
- package/dist/lang/ast/time-utils.js +0 -94
- package/dist/lang/field-space.js +0 -629
- package/dist/lang/field-utils.js +0 -33
- package/dist/lang/index.js +0 -22
- package/dist/lang/parse-log.js +0 -41
- package/dist/lang/reference-list.js +0 -80
- package/dist/lang/space-field.js +0 -346
- package/dist/lang/test/document-help-context-walker.spec.js +0 -45
- package/dist/lang/test/document-symbol-walker.spec.js +0 -100
- package/dist/lang/test/field-symbols.spec.js +0 -172
- package/dist/lang/test/parse.spec.js +0 -1936
- package/dist/lang/test/test-translator.js +0 -334
- package/dist/lang/zone.js +0 -97
- package/dist/malloy.js +0 -2354
- package/dist/model/index.js +0 -34
- package/dist/model/malloy-query.d.ts +0 -313
- package/dist/model/malloy-query.js +0 -2603
- package/dist/model/malloy-types.d.ts +0 -404
- package/dist/model/malloy-types.js +0 -242
- package/dist/model/malloy_query.js +0 -2996
- package/dist/model/malloy_types.js +0 -283
- package/dist/model/sql-block.d.ts +0 -11
- package/dist/model/sql-block.js +0 -38
- package/dist/model/sql_block.js +0 -41
- package/dist/model/utils.js +0 -84
- package/dist/runtime-types.d.ts +0 -116
- package/dist/runtime-types.js +0 -40
- 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
|