@malloydata/malloy-query-builder 0.0.237-dev250225144145
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/DEVELOPING.md +27 -0
- package/README.md +437 -0
- package/dist/expects.d.ts +26 -0
- package/dist/expects.js +149 -0
- package/dist/expects.js.map +1 -0
- package/dist/flights_model.d.ts +2 -0
- package/dist/flights_model.js +2146 -0
- package/dist/flights_model.js.map +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +24 -0
- package/dist/index.js.map +1 -0
- package/dist/query-ast.d.ts +1614 -0
- package/dist/query-ast.js +3633 -0
- package/dist/query-ast.js.map +1 -0
- package/dist/query-ast.spec.d.ts +1 -0
- package/dist/query-ast.spec.js +1776 -0
- package/dist/query-ast.spec.js.map +1 -0
- package/flow/flights_model.d.ts +2 -0
- package/flow/index.d.ts +1 -0
- package/flow/query-ast.d.ts +1100 -0
- package/package.json +34 -0
- package/scripts/flow-api-translator.d.ts +3 -0
- package/scripts/gen_flow.ts +23 -0
- package/src/expects.ts +194 -0
- package/src/flights_model.ts +2150 -0
- package/src/index.ts +8 -0
- package/src/query-ast.spec.ts +1777 -0
- package/src/query-ast.ts +4654 -0
- package/tsconfig.json +11 -0
package/src/query-ast.ts
ADDED
|
@@ -0,0 +1,4654 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
3
|
+
*
|
|
4
|
+
* This source code is licensed under the MIT license found in the
|
|
5
|
+
* LICENSE file in the root directory of this source tree.
|
|
6
|
+
*/
|
|
7
|
+
import * as Malloy from '@malloydata/malloy-interfaces';
|
|
8
|
+
import {Tag, TagSetValue} from '@malloydata/malloy-tag';
|
|
9
|
+
import * as Filter from '@malloydata/malloy-filter';
|
|
10
|
+
|
|
11
|
+
export type ParsedFilter =
|
|
12
|
+
| {kind: 'string'; clauses: Filter.StringClause[]}
|
|
13
|
+
| {kind: 'number'; clauses: Filter.NumberClause[]}
|
|
14
|
+
| {kind: 'boolean'; clauses: Filter.BooleanClause[]}
|
|
15
|
+
| {kind: 'date'; clauses: Filter.DateClause[]};
|
|
16
|
+
|
|
17
|
+
export type PathSegment = number | string;
|
|
18
|
+
export type Path = PathSegment[];
|
|
19
|
+
|
|
20
|
+
type ASTAny = ASTNode<unknown>;
|
|
21
|
+
|
|
22
|
+
type ASTChildren<T> = {
|
|
23
|
+
[Key in keyof T]: LiteralOrNode<T[Key]>;
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
type NonOptionalASTNode<T> = T extends undefined ? never : ASTNode<T>;
|
|
27
|
+
|
|
28
|
+
type LiteralOrNode<T> = T extends string
|
|
29
|
+
? T
|
|
30
|
+
: T extends number
|
|
31
|
+
? T
|
|
32
|
+
: T extends string[]
|
|
33
|
+
? T
|
|
34
|
+
: T extends boolean
|
|
35
|
+
? T
|
|
36
|
+
: undefined extends T
|
|
37
|
+
? NonOptionalASTNode<T> | undefined
|
|
38
|
+
: ASTNode<T>;
|
|
39
|
+
|
|
40
|
+
abstract class ASTNode<T> {
|
|
41
|
+
/**
|
|
42
|
+
* @internal
|
|
43
|
+
*/
|
|
44
|
+
edited = false;
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* @internal
|
|
48
|
+
*/
|
|
49
|
+
public _parent: ASTAny | undefined;
|
|
50
|
+
|
|
51
|
+
abstract build(): T;
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* @internal
|
|
55
|
+
*/
|
|
56
|
+
edit() {
|
|
57
|
+
this.edited = true;
|
|
58
|
+
if (this._parent) this._parent.edit();
|
|
59
|
+
return this;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* @internal
|
|
64
|
+
*/
|
|
65
|
+
abstract find(path: Path): ASTAny;
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Returns this node as an `ASTQuery`. Throws if it is not an `ASTQuery`.
|
|
69
|
+
*
|
|
70
|
+
* There are variants of this method for _all_ ASTXYZ nodes `asXYZ`, but they
|
|
71
|
+
* are not shown here so the docs aren't crazy big.
|
|
72
|
+
*
|
|
73
|
+
* @returns Returns this node as an `ASTQuery`.
|
|
74
|
+
*/
|
|
75
|
+
asQuery(): ASTQuery {
|
|
76
|
+
if (this instanceof ASTQuery) return this;
|
|
77
|
+
throw new Error('Not an ASTQuery');
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Finds the AST node at the given `path`. Throws if it is not an `ASTQuery`.
|
|
82
|
+
*
|
|
83
|
+
* There are variants of this method for _all_ ASTXYZ nodes `findXYZ`, but they
|
|
84
|
+
* are not shown here so the docs aren't crazy big.
|
|
85
|
+
*
|
|
86
|
+
* @param path Path to the desired ASTNode, e.g. `['source', 'parameters', 0]`
|
|
87
|
+
* @returns Returns this node as an `ASTQuery`.
|
|
88
|
+
*/
|
|
89
|
+
findQuery(path: Path): ASTQuery {
|
|
90
|
+
return this.find(path).asQuery();
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* @hidden
|
|
95
|
+
*/
|
|
96
|
+
asReference(): ASTReference {
|
|
97
|
+
if (this instanceof ASTReference) return this;
|
|
98
|
+
throw new Error('Not an ASTReference');
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* @hidden
|
|
103
|
+
*/
|
|
104
|
+
findReference(path: Path): ASTReference {
|
|
105
|
+
return this.find(path).asReference();
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* @hidden
|
|
110
|
+
*/
|
|
111
|
+
asSourceReference(): ASTSourceReference {
|
|
112
|
+
if (this instanceof ASTSourceReference) return this;
|
|
113
|
+
throw new Error('Not an ASTSourceReference');
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* @hidden
|
|
118
|
+
*/
|
|
119
|
+
findSourceReference(path: Path): ASTSourceReference {
|
|
120
|
+
return this.find(path).asSourceReference();
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* @hidden
|
|
125
|
+
*/
|
|
126
|
+
asParameterValueList(): ASTParameterValueList {
|
|
127
|
+
if (this instanceof ASTParameterValueList) return this;
|
|
128
|
+
throw new Error('Not an ASTParameterValueList');
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* @hidden
|
|
133
|
+
*/
|
|
134
|
+
findParameterValueList(path: Path): ASTParameterValueList {
|
|
135
|
+
return this.find(path).asParameterValueList();
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* @hidden
|
|
140
|
+
*/
|
|
141
|
+
asWhere(): ASTWhere {
|
|
142
|
+
if (this instanceof ASTWhere) return this;
|
|
143
|
+
throw new Error('Not an ASTWhere');
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* @hidden
|
|
148
|
+
*/
|
|
149
|
+
findWhere(path: Path): ASTWhere {
|
|
150
|
+
return this.find(path).asWhere();
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* @hidden
|
|
155
|
+
*/
|
|
156
|
+
asWhereList(): ASTWhereList {
|
|
157
|
+
if (this instanceof ASTWhereList) return this;
|
|
158
|
+
throw new Error('Not an ASTWhereList');
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* @hidden
|
|
163
|
+
*/
|
|
164
|
+
findWhereList(path: Path): ASTWhereList {
|
|
165
|
+
return this.find(path).asWhereList();
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* @hidden
|
|
170
|
+
*/
|
|
171
|
+
asParameterValue(): ASTParameterValue {
|
|
172
|
+
if (this instanceof ASTParameterValue) return this;
|
|
173
|
+
throw new Error('Not an ASTParameterValue');
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* @hidden
|
|
178
|
+
*/
|
|
179
|
+
findParameterValue(path: Path): ASTParameterValue {
|
|
180
|
+
return this.find(path).asParameterValue();
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* @hidden
|
|
185
|
+
*/
|
|
186
|
+
asStringLiteralValue(): ASTStringLiteralValue {
|
|
187
|
+
if (this instanceof ASTStringLiteralValue) return this;
|
|
188
|
+
throw new Error('Not an ASTStringLiteralValue');
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* @hidden
|
|
193
|
+
*/
|
|
194
|
+
findStringLiteralValue(path: Path): ASTStringLiteralValue {
|
|
195
|
+
return this.find(path).asStringLiteralValue();
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* @hidden
|
|
200
|
+
*/
|
|
201
|
+
asNumberLiteralValue(): ASTNumberLiteralValue {
|
|
202
|
+
if (this instanceof ASTNumberLiteralValue) return this;
|
|
203
|
+
throw new Error('Not an ASTNumberLiteralValue');
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/**
|
|
207
|
+
* @hidden
|
|
208
|
+
*/
|
|
209
|
+
findNumberLiteralValue(path: Path): ASTNumberLiteralValue {
|
|
210
|
+
return this.find(path).asNumberLiteralValue();
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* @hidden
|
|
215
|
+
*/
|
|
216
|
+
asViewOperationList(): ASTViewOperationList {
|
|
217
|
+
if (this instanceof ASTViewOperationList) return this;
|
|
218
|
+
throw new Error('Not an ASTViewOperationList');
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
/**
|
|
222
|
+
* @hidden
|
|
223
|
+
*/
|
|
224
|
+
findViewOperationList(path: Path): ASTViewOperationList {
|
|
225
|
+
return this.find(path).asViewOperationList();
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
/**
|
|
229
|
+
* @hidden
|
|
230
|
+
*/
|
|
231
|
+
asGroupByViewOperation(): ASTGroupByViewOperation {
|
|
232
|
+
if (this instanceof ASTGroupByViewOperation) return this;
|
|
233
|
+
throw new Error('Not an ASTGroupByViewOperation');
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
/**
|
|
237
|
+
* @hidden
|
|
238
|
+
*/
|
|
239
|
+
findGroupByViewOperation(path: Path): ASTGroupByViewOperation {
|
|
240
|
+
return this.find(path).asGroupByViewOperation();
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
/**
|
|
244
|
+
* @hidden
|
|
245
|
+
*/
|
|
246
|
+
asAggregateViewOperation(): ASTAggregateViewOperation {
|
|
247
|
+
if (this instanceof ASTAggregateViewOperation) return this;
|
|
248
|
+
throw new Error('Not an ASTAggregateViewOperation');
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
/**
|
|
252
|
+
* @hidden
|
|
253
|
+
*/
|
|
254
|
+
findAggregateViewOperation(path: Path): ASTAggregateViewOperation {
|
|
255
|
+
return this.find(path).asAggregateViewOperation();
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
/**
|
|
259
|
+
* @hidden
|
|
260
|
+
*/
|
|
261
|
+
asOrderByViewOperation(): ASTOrderByViewOperation {
|
|
262
|
+
if (this instanceof ASTOrderByViewOperation) return this;
|
|
263
|
+
throw new Error('Not an ASTOrderByViewOperation');
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
/**
|
|
267
|
+
* @hidden
|
|
268
|
+
*/
|
|
269
|
+
findOrderByViewOperation(path: Path): ASTOrderByViewOperation {
|
|
270
|
+
return this.find(path).asOrderByViewOperation();
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
/**
|
|
274
|
+
* @hidden
|
|
275
|
+
*/
|
|
276
|
+
asField(): ASTField {
|
|
277
|
+
if (this instanceof ASTField) return this;
|
|
278
|
+
throw new Error('Not an ASTField');
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
/**
|
|
282
|
+
* @hidden
|
|
283
|
+
*/
|
|
284
|
+
findField(path: Path): ASTField {
|
|
285
|
+
return this.find(path).asField();
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
/**
|
|
289
|
+
* @hidden
|
|
290
|
+
*/
|
|
291
|
+
asReferenceExpression(): ASTReferenceExpression {
|
|
292
|
+
if (this instanceof ASTReferenceExpression) return this;
|
|
293
|
+
throw new Error('Not an ASTReferenceExpression');
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
/**
|
|
297
|
+
* @hidden
|
|
298
|
+
*/
|
|
299
|
+
findReferenceExpression(path: Path): ASTReferenceExpression {
|
|
300
|
+
return this.find(path).asReferenceExpression();
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
/**
|
|
304
|
+
* @hidden
|
|
305
|
+
*/
|
|
306
|
+
asReferenceViewDefinition(): ASTReferenceViewDefinition {
|
|
307
|
+
if (this instanceof ASTReferenceViewDefinition) return this;
|
|
308
|
+
throw new Error('Not an ASTReferenceViewDefinition');
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
/**
|
|
312
|
+
* @hidden
|
|
313
|
+
*/
|
|
314
|
+
findReferenceViewDefinition(path: Path): ASTReferenceViewDefinition {
|
|
315
|
+
return this.find(path).asReferenceViewDefinition();
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
/**
|
|
319
|
+
* @hidden
|
|
320
|
+
*/
|
|
321
|
+
asArrowQueryDefinition(): ASTArrowQueryDefinition {
|
|
322
|
+
if (this instanceof ASTArrowQueryDefinition) return this;
|
|
323
|
+
throw new Error('Not an ASTArrowQueryDefinition');
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
/**
|
|
327
|
+
* @hidden
|
|
328
|
+
*/
|
|
329
|
+
findArrowQueryDefinition(path: Path): ASTArrowQueryDefinition {
|
|
330
|
+
return this.find(path).asArrowQueryDefinition();
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
/**
|
|
334
|
+
* @hidden
|
|
335
|
+
*/
|
|
336
|
+
asArrowViewDefinition(): ASTArrowViewDefinition {
|
|
337
|
+
if (this instanceof ASTArrowViewDefinition) return this;
|
|
338
|
+
throw new Error('Not an ASTArrowViewDefinition');
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
/**
|
|
342
|
+
* @hidden
|
|
343
|
+
*/
|
|
344
|
+
findArrowViewDefinition(path: Path): ASTArrowViewDefinition {
|
|
345
|
+
return this.find(path).asArrowViewDefinition();
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
/**
|
|
349
|
+
* @hidden
|
|
350
|
+
*/
|
|
351
|
+
asRefinementViewDefinition(): ASTRefinementViewDefinition {
|
|
352
|
+
if (this instanceof ASTRefinementViewDefinition) return this;
|
|
353
|
+
throw new Error('Not an ASTRefinementViewDefinition');
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
/**
|
|
357
|
+
* @hidden
|
|
358
|
+
*/
|
|
359
|
+
findRefinementViewDefinition(path: Path): ASTRefinementViewDefinition {
|
|
360
|
+
return this.find(path).asRefinementViewDefinition();
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
/**
|
|
364
|
+
* @hidden
|
|
365
|
+
*/
|
|
366
|
+
asTimeTruncationExpression(): ASTTimeTruncationExpression {
|
|
367
|
+
if (this instanceof ASTTimeTruncationExpression) return this;
|
|
368
|
+
throw new Error('Not an ASTTimeTruncationExpression');
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
/**
|
|
372
|
+
* @hidden
|
|
373
|
+
*/
|
|
374
|
+
findTimeTruncationExpression(path: Path): ASTTimeTruncationExpression {
|
|
375
|
+
return this.find(path).asTimeTruncationExpression();
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
/**
|
|
379
|
+
* @hidden
|
|
380
|
+
*/
|
|
381
|
+
asFilteredFieldExpression(): ASTFilteredFieldExpression {
|
|
382
|
+
if (this instanceof ASTFilteredFieldExpression) return this;
|
|
383
|
+
throw new Error('Not an ASTFilteredFieldExpression');
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
/**
|
|
387
|
+
* @hidden
|
|
388
|
+
*/
|
|
389
|
+
findFilteredFieldExpression(path: Path): ASTFilteredFieldExpression {
|
|
390
|
+
return this.find(path).asFilteredFieldExpression();
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
/**
|
|
394
|
+
* @hidden
|
|
395
|
+
*/
|
|
396
|
+
asNestViewOperation(): ASTNestViewOperation {
|
|
397
|
+
if (this instanceof ASTNestViewOperation) return this;
|
|
398
|
+
throw new Error('Not an ASTNestViewOperation');
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
/**
|
|
402
|
+
* @hidden
|
|
403
|
+
*/
|
|
404
|
+
findNestViewOperation(path: Path): ASTNestViewOperation {
|
|
405
|
+
return this.find(path).asNestViewOperation();
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
/**
|
|
409
|
+
* @hidden
|
|
410
|
+
*/
|
|
411
|
+
asView(): ASTView {
|
|
412
|
+
if (this instanceof ASTView) return this;
|
|
413
|
+
throw new Error('Not an ASTView');
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
/**
|
|
417
|
+
* @hidden
|
|
418
|
+
*/
|
|
419
|
+
findView(path: Path): ASTView {
|
|
420
|
+
return this.find(path).asView();
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
/**
|
|
424
|
+
* @hidden
|
|
425
|
+
*/
|
|
426
|
+
asSegmentViewDefinition(): ASTSegmentViewDefinition {
|
|
427
|
+
if (this instanceof ASTSegmentViewDefinition) return this;
|
|
428
|
+
throw new Error('Not an ASTSegmentViewDefinition');
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
/**
|
|
432
|
+
* @hidden
|
|
433
|
+
*/
|
|
434
|
+
findSegmentViewDefinition(path: Path): ASTSegmentViewDefinition {
|
|
435
|
+
return this.find(path).asSegmentViewDefinition();
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
/**
|
|
439
|
+
* @hidden
|
|
440
|
+
*/
|
|
441
|
+
asLimitViewOperation(): ASTLimitViewOperation {
|
|
442
|
+
if (this instanceof ASTLimitViewOperation) return this;
|
|
443
|
+
throw new Error('Not an ASTLimitViewOperation');
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
/**
|
|
447
|
+
* @hidden
|
|
448
|
+
*/
|
|
449
|
+
findLimitViewOperation(path: Path): ASTLimitViewOperation {
|
|
450
|
+
return this.find(path).asLimitViewOperation();
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
/**
|
|
454
|
+
* @hidden
|
|
455
|
+
*/
|
|
456
|
+
asAnnotationList(): ASTAnnotationList {
|
|
457
|
+
if (this instanceof ASTAnnotationList) return this;
|
|
458
|
+
throw new Error('Not an ASTAnnotationList');
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
/**
|
|
462
|
+
* @hidden
|
|
463
|
+
*/
|
|
464
|
+
findAnnotationList(path: Path): ASTAnnotationList {
|
|
465
|
+
return this.find(path).asAnnotationList();
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
/**
|
|
469
|
+
* @hidden
|
|
470
|
+
*/
|
|
471
|
+
asAnnotation(): ASTAnnotation {
|
|
472
|
+
if (this instanceof ASTAnnotation) return this;
|
|
473
|
+
throw new Error('Not an ASTAnnotation');
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
/**
|
|
477
|
+
* @hidden
|
|
478
|
+
*/
|
|
479
|
+
findAnnotation(path: Path): ASTAnnotation {
|
|
480
|
+
return this.find(path).asAnnotation();
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
/**
|
|
484
|
+
* @internal
|
|
485
|
+
*/
|
|
486
|
+
get parent() {
|
|
487
|
+
if (this._parent === undefined) {
|
|
488
|
+
throw new Error('This node does not have a parent');
|
|
489
|
+
}
|
|
490
|
+
return this._parent;
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
/**
|
|
494
|
+
* @internal
|
|
495
|
+
*/
|
|
496
|
+
set parent(parent: ASTAny) {
|
|
497
|
+
this._parent = parent;
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
/**
|
|
501
|
+
* @internal
|
|
502
|
+
*/
|
|
503
|
+
static schemaTryGet(
|
|
504
|
+
schema: Malloy.Schema,
|
|
505
|
+
name: string,
|
|
506
|
+
path: string[] | undefined
|
|
507
|
+
) {
|
|
508
|
+
let current = schema;
|
|
509
|
+
for (const part of path ?? []) {
|
|
510
|
+
const field = current.fields.find(f => f.name === part);
|
|
511
|
+
if (field === undefined) {
|
|
512
|
+
throw new Error(`${part} not found`);
|
|
513
|
+
}
|
|
514
|
+
if (field.kind !== 'join') {
|
|
515
|
+
throw new Error(`${part} is not a join`);
|
|
516
|
+
}
|
|
517
|
+
current = field.schema;
|
|
518
|
+
}
|
|
519
|
+
const field = current.fields.find(f => f.name === name);
|
|
520
|
+
return field;
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
/**
|
|
524
|
+
* @internal
|
|
525
|
+
*/
|
|
526
|
+
static schemaGet(
|
|
527
|
+
schema: Malloy.Schema,
|
|
528
|
+
name: string,
|
|
529
|
+
path: string[] | undefined
|
|
530
|
+
) {
|
|
531
|
+
const field = ASTNode.schemaTryGet(schema, name, path);
|
|
532
|
+
if (field === undefined) {
|
|
533
|
+
throw new Error(`${name} not found`);
|
|
534
|
+
}
|
|
535
|
+
return field;
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
/**
|
|
539
|
+
* @internal
|
|
540
|
+
*/
|
|
541
|
+
static schemaMerge(a: Malloy.Schema, b: Malloy.Schema): Malloy.Schema {
|
|
542
|
+
return {
|
|
543
|
+
fields: [...a.fields, ...b.fields],
|
|
544
|
+
};
|
|
545
|
+
}
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
function isBasic(
|
|
549
|
+
t: ASTAny | string | number | string[] | boolean
|
|
550
|
+
): t is string | number | string[] | boolean {
|
|
551
|
+
return (
|
|
552
|
+
Array.isArray(t) ||
|
|
553
|
+
typeof t === 'string' ||
|
|
554
|
+
typeof t === 'number' ||
|
|
555
|
+
typeof t === 'boolean'
|
|
556
|
+
);
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
abstract class ASTListNode<
|
|
560
|
+
T,
|
|
561
|
+
N extends ASTNode<T> = ASTNode<T>,
|
|
562
|
+
> extends ASTNode<T[]> {
|
|
563
|
+
originalChildren: N[];
|
|
564
|
+
constructor(
|
|
565
|
+
protected node: T[],
|
|
566
|
+
protected children: N[]
|
|
567
|
+
) {
|
|
568
|
+
super();
|
|
569
|
+
this.originalChildren = [...children];
|
|
570
|
+
children.forEach(c => (c.parent = this));
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
*iter(): Generator<N, void, unknown> {
|
|
574
|
+
for (let i = 0; i < this.length; i++) {
|
|
575
|
+
yield this.index(i);
|
|
576
|
+
}
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
get length() {
|
|
580
|
+
return this.children.length;
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
get last() {
|
|
584
|
+
return this.children[this.children.length - 1];
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
/**
|
|
588
|
+
* Get the `i`th element of this list
|
|
589
|
+
*
|
|
590
|
+
* @param i The index of the list to get
|
|
591
|
+
* @returns The item at index `i` in this list
|
|
592
|
+
*/
|
|
593
|
+
index(i: number) {
|
|
594
|
+
return this.children[i];
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
/**
|
|
598
|
+
* @internal
|
|
599
|
+
*/
|
|
600
|
+
insert(n: N, index: number) {
|
|
601
|
+
this.edit();
|
|
602
|
+
this.children.splice(index, 0, n);
|
|
603
|
+
n.parent = this;
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
/**
|
|
607
|
+
* @internal
|
|
608
|
+
*/
|
|
609
|
+
add(n: N) {
|
|
610
|
+
this.edit();
|
|
611
|
+
this.children.push(n);
|
|
612
|
+
n.parent = this;
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
/**
|
|
616
|
+
* @internal
|
|
617
|
+
*/
|
|
618
|
+
remove(n: N) {
|
|
619
|
+
const idx = this.children.findIndex(o => o === n);
|
|
620
|
+
if (idx === -1) return this;
|
|
621
|
+
this.edit();
|
|
622
|
+
this.children.splice(idx, 1);
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
/**
|
|
626
|
+
* @internal
|
|
627
|
+
*/
|
|
628
|
+
build(): T[] {
|
|
629
|
+
if (!this.edited) return this.node;
|
|
630
|
+
const ret = this.children.map(c => c.build());
|
|
631
|
+
this.edited = false;
|
|
632
|
+
this.originalChildren = [...this.children];
|
|
633
|
+
this.node = ret;
|
|
634
|
+
return ret;
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
/**
|
|
638
|
+
* @internal
|
|
639
|
+
*/
|
|
640
|
+
find(path: Path): ASTAny {
|
|
641
|
+
if (path.length === 0) {
|
|
642
|
+
return this;
|
|
643
|
+
}
|
|
644
|
+
const [head, ...rest] = path;
|
|
645
|
+
if (typeof head !== 'number') {
|
|
646
|
+
throw new Error(
|
|
647
|
+
`${this.constructor.name} is a ASTListNode and thus cannot contain a ${head}`
|
|
648
|
+
);
|
|
649
|
+
}
|
|
650
|
+
const child = this.children[head];
|
|
651
|
+
return child.find(rest);
|
|
652
|
+
}
|
|
653
|
+
|
|
654
|
+
/**
|
|
655
|
+
* @internal
|
|
656
|
+
*/
|
|
657
|
+
findIndex(predicate: (n: N) => boolean): number {
|
|
658
|
+
return this.children.findIndex(predicate);
|
|
659
|
+
}
|
|
660
|
+
|
|
661
|
+
/**
|
|
662
|
+
* @internal
|
|
663
|
+
*/
|
|
664
|
+
indexOf(n: N): number {
|
|
665
|
+
return this.children.indexOf(n);
|
|
666
|
+
}
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
abstract class ASTObjectNode<
|
|
670
|
+
T,
|
|
671
|
+
Children extends ASTChildren<T>,
|
|
672
|
+
> extends ASTNode<T> {
|
|
673
|
+
constructor(
|
|
674
|
+
protected node: T,
|
|
675
|
+
protected children: Children
|
|
676
|
+
) {
|
|
677
|
+
super();
|
|
678
|
+
for (const key in children) {
|
|
679
|
+
const child = children[key];
|
|
680
|
+
if (child && !isBasic(child)) {
|
|
681
|
+
child.parent = this;
|
|
682
|
+
}
|
|
683
|
+
}
|
|
684
|
+
}
|
|
685
|
+
|
|
686
|
+
/**
|
|
687
|
+
* @internal
|
|
688
|
+
*/
|
|
689
|
+
build(): T {
|
|
690
|
+
if (!this.edited) return this.node;
|
|
691
|
+
let ret = this.node;
|
|
692
|
+
for (const key in this.children) {
|
|
693
|
+
const child = this.children[key];
|
|
694
|
+
if (child === undefined) {
|
|
695
|
+
ret = {...ret, [key]: undefined};
|
|
696
|
+
} else if (isBasic(child)) {
|
|
697
|
+
if (this.edited) {
|
|
698
|
+
ret = {...ret, [key]: child};
|
|
699
|
+
}
|
|
700
|
+
} else {
|
|
701
|
+
ret = {...ret, [key]: child.build()};
|
|
702
|
+
}
|
|
703
|
+
}
|
|
704
|
+
this.node = ret;
|
|
705
|
+
this.edited = false;
|
|
706
|
+
return ret;
|
|
707
|
+
}
|
|
708
|
+
|
|
709
|
+
/**
|
|
710
|
+
* @internal
|
|
711
|
+
*/
|
|
712
|
+
find(path: Path): ASTAny {
|
|
713
|
+
if (path.length === 0) {
|
|
714
|
+
return this;
|
|
715
|
+
}
|
|
716
|
+
const [head, ...rest] = path;
|
|
717
|
+
if (typeof head !== 'string') {
|
|
718
|
+
throw new Error(
|
|
719
|
+
`${this.constructor.name} is a ASTListNode and thus cannot contain a ${head}`
|
|
720
|
+
);
|
|
721
|
+
}
|
|
722
|
+
const child = this.children[head];
|
|
723
|
+
if (isBasic(child)) {
|
|
724
|
+
throw new Error(
|
|
725
|
+
`${this.constructor.name}.${head} refers to a basic type, not an ASTNode`
|
|
726
|
+
);
|
|
727
|
+
}
|
|
728
|
+
return child.find(rest);
|
|
729
|
+
}
|
|
730
|
+
}
|
|
731
|
+
|
|
732
|
+
/**
|
|
733
|
+
* AST Object to represent the whole query AST.
|
|
734
|
+
*
|
|
735
|
+
* ```ts
|
|
736
|
+
* const q = new ASTQuery({
|
|
737
|
+
* source: flightsSourceInfo,
|
|
738
|
+
* });
|
|
739
|
+
* const segment = q.getOrAddDefaultSegment();
|
|
740
|
+
* segment.addGroupBy("carrier");
|
|
741
|
+
* segment.addOrderBy("carrier", Malloy.OrderByDirection.DESC);
|
|
742
|
+
* const malloy = q.toMalloy();
|
|
743
|
+
* const query = q.build();
|
|
744
|
+
* ```
|
|
745
|
+
*/
|
|
746
|
+
export class ASTQuery
|
|
747
|
+
extends ASTObjectNode<
|
|
748
|
+
Malloy.Query,
|
|
749
|
+
{
|
|
750
|
+
definition: ASTQueryDefinition;
|
|
751
|
+
annotations?: ASTAnnotationList;
|
|
752
|
+
}
|
|
753
|
+
>
|
|
754
|
+
implements IASTAnnotatable
|
|
755
|
+
{
|
|
756
|
+
model: Malloy.ModelInfo;
|
|
757
|
+
|
|
758
|
+
/**
|
|
759
|
+
* Create a new Query AST object against a given source.
|
|
760
|
+
*
|
|
761
|
+
* @param options.query Optional query to begin with; if none is provided, will
|
|
762
|
+
* use an empty query (`{pipeline: {stages: []}}`)
|
|
763
|
+
* @param options.source A source to base the query on. Will set the source name
|
|
764
|
+
* in the provided (or default) query to the name of this source.
|
|
765
|
+
*/
|
|
766
|
+
constructor(options: {
|
|
767
|
+
query?: Malloy.Query;
|
|
768
|
+
source:
|
|
769
|
+
| Malloy.ModelEntryValueWithSource
|
|
770
|
+
| Malloy.ModelEntryValueWithQuery
|
|
771
|
+
| Malloy.SourceInfo
|
|
772
|
+
| Malloy.QueryInfo;
|
|
773
|
+
});
|
|
774
|
+
/**
|
|
775
|
+
* Create a new Query AST object against a given model.
|
|
776
|
+
*
|
|
777
|
+
* @param options.query Optional query to begin with; if none is provided, will
|
|
778
|
+
* use an empty query (`{pipeline: {stages: []}}`)
|
|
779
|
+
* @param options.model A model to use for building this query. Use {@link setSource}
|
|
780
|
+
* to configure the source, or set {@link setQueryHead} to run
|
|
781
|
+
* a top level query in the model.
|
|
782
|
+
*/
|
|
783
|
+
constructor(options: {query?: Malloy.Query; model: Malloy.ModelInfo});
|
|
784
|
+
constructor(options: {
|
|
785
|
+
query?: Malloy.Query;
|
|
786
|
+
model?: Malloy.ModelInfo;
|
|
787
|
+
source?:
|
|
788
|
+
| Malloy.ModelEntryValueWithSource
|
|
789
|
+
| Malloy.ModelEntryValueWithQuery
|
|
790
|
+
| Malloy.SourceInfo
|
|
791
|
+
| Malloy.QueryInfo;
|
|
792
|
+
}) {
|
|
793
|
+
let chosenSource = options.source;
|
|
794
|
+
if (chosenSource === undefined) {
|
|
795
|
+
if (options.model === undefined) {
|
|
796
|
+
throw new Error('Need a model or source');
|
|
797
|
+
}
|
|
798
|
+
if (options.query) {
|
|
799
|
+
const definition = options.query.definition;
|
|
800
|
+
if (definition.kind === 'arrow') {
|
|
801
|
+
const name = definition.source_reference.name;
|
|
802
|
+
chosenSource = options.model.entries.find(e => e.name === name);
|
|
803
|
+
if (chosenSource === undefined) {
|
|
804
|
+
throw new Error(
|
|
805
|
+
`Model does not contain source or query named ${name}`
|
|
806
|
+
);
|
|
807
|
+
}
|
|
808
|
+
}
|
|
809
|
+
}
|
|
810
|
+
if (chosenSource === undefined) {
|
|
811
|
+
chosenSource = options.model.entries[0];
|
|
812
|
+
}
|
|
813
|
+
if (chosenSource === undefined) {
|
|
814
|
+
throw new Error('Model does not contain any sources or queries');
|
|
815
|
+
}
|
|
816
|
+
}
|
|
817
|
+
const source = sourceOrQueryToModelEntry(chosenSource);
|
|
818
|
+
const query = options.query ?? {
|
|
819
|
+
definition: {
|
|
820
|
+
kind: 'arrow',
|
|
821
|
+
source_reference: {
|
|
822
|
+
name: source.name,
|
|
823
|
+
},
|
|
824
|
+
view: {
|
|
825
|
+
kind: 'segment',
|
|
826
|
+
operations: [],
|
|
827
|
+
},
|
|
828
|
+
},
|
|
829
|
+
};
|
|
830
|
+
const model: Malloy.ModelInfo | undefined = options.model ?? {
|
|
831
|
+
entries: [source],
|
|
832
|
+
anonymous_queries: [],
|
|
833
|
+
};
|
|
834
|
+
super(query, {
|
|
835
|
+
definition: ASTQueryDefinition.from(query.definition),
|
|
836
|
+
annotations:
|
|
837
|
+
query.annotations && new ASTAnnotationList(query.annotations),
|
|
838
|
+
});
|
|
839
|
+
this.model = model;
|
|
840
|
+
if (options.source) {
|
|
841
|
+
this.setSource(options.source.name);
|
|
842
|
+
}
|
|
843
|
+
}
|
|
844
|
+
|
|
845
|
+
get definition() {
|
|
846
|
+
return this.children.definition;
|
|
847
|
+
}
|
|
848
|
+
|
|
849
|
+
set definition(definition: ASTQueryDefinition) {
|
|
850
|
+
this.edit();
|
|
851
|
+
this.children.definition = definition;
|
|
852
|
+
definition.parent = this;
|
|
853
|
+
}
|
|
854
|
+
|
|
855
|
+
get annotations() {
|
|
856
|
+
return this.children.annotations;
|
|
857
|
+
}
|
|
858
|
+
|
|
859
|
+
reorderFields(names: string[]) {
|
|
860
|
+
this.definition.reorderFields(names);
|
|
861
|
+
}
|
|
862
|
+
|
|
863
|
+
getOrAddAnnotations() {
|
|
864
|
+
if (this.annotations) return this.annotations;
|
|
865
|
+
this.edit();
|
|
866
|
+
const annotations = new ASTAnnotationList([]);
|
|
867
|
+
this.children.annotations = annotations;
|
|
868
|
+
return annotations;
|
|
869
|
+
}
|
|
870
|
+
|
|
871
|
+
getInheritedTag(prefix: RegExp | string = '# ') {
|
|
872
|
+
return tagFromAnnotations(prefix, this.getInheritedAnnotations());
|
|
873
|
+
}
|
|
874
|
+
|
|
875
|
+
getIntrinsicTag(prefix: RegExp | string = '# ') {
|
|
876
|
+
return this.annotations?.getIntrinsicTag(prefix) ?? new Tag();
|
|
877
|
+
}
|
|
878
|
+
|
|
879
|
+
getTag(prefix: RegExp | string = '# ') {
|
|
880
|
+
return this.annotations?.getTag(prefix) ?? this.getInheritedTag(prefix);
|
|
881
|
+
}
|
|
882
|
+
|
|
883
|
+
setTagProperty(path: Path, value: TagSetValue = null, prefix = '# ') {
|
|
884
|
+
this.getOrAddAnnotations().setTagProperty(path, value, prefix);
|
|
885
|
+
}
|
|
886
|
+
|
|
887
|
+
removeTagProperty(path: Path, prefix = '# ') {
|
|
888
|
+
if (!this.getTag().has(...path)) return;
|
|
889
|
+
this.getOrAddAnnotations().removeTagProperty(path, prefix);
|
|
890
|
+
}
|
|
891
|
+
|
|
892
|
+
setViewToEmptySegment() {
|
|
893
|
+
if (!(this.definition instanceof ASTArrowQueryDefinition)) {
|
|
894
|
+
throw new Error('Must set source before setting view');
|
|
895
|
+
}
|
|
896
|
+
this.definition = new ASTArrowQueryDefinition({
|
|
897
|
+
kind: 'arrow',
|
|
898
|
+
source_reference: this.definition.sourceReference.build(),
|
|
899
|
+
view: {
|
|
900
|
+
kind: 'segment',
|
|
901
|
+
operations: [],
|
|
902
|
+
},
|
|
903
|
+
});
|
|
904
|
+
return this.definition.view.asSegmentViewDefinition();
|
|
905
|
+
}
|
|
906
|
+
|
|
907
|
+
isRunnable() {
|
|
908
|
+
return this.definition.isRunnable();
|
|
909
|
+
}
|
|
910
|
+
|
|
911
|
+
/**
|
|
912
|
+
* Gets an {@link ASTSegmentViewDefinition} for the "default" place to add query
|
|
913
|
+
* operations, or creates one if it doesn't exist.
|
|
914
|
+
*
|
|
915
|
+
* ```
|
|
916
|
+
* run: flights ->
|
|
917
|
+
* ```
|
|
918
|
+
* ```ts
|
|
919
|
+
* q.getOrAddDefaultSegment();
|
|
920
|
+
* ```
|
|
921
|
+
* ```
|
|
922
|
+
* run: flights -> { }
|
|
923
|
+
* ```
|
|
924
|
+
*
|
|
925
|
+
* If there is a view at the head, it will refine it:
|
|
926
|
+
* ```
|
|
927
|
+
* run: flights -> by_carrier
|
|
928
|
+
* ```
|
|
929
|
+
* ```ts
|
|
930
|
+
* q.getOrAddDefaultSegment();
|
|
931
|
+
* ```
|
|
932
|
+
* ```
|
|
933
|
+
* run: flights -> by_carrier + { }
|
|
934
|
+
* ```
|
|
935
|
+
*/
|
|
936
|
+
public getOrAddDefaultSegment(): ASTSegmentViewDefinition {
|
|
937
|
+
return this.definition.getOrAddDefaultSegment();
|
|
938
|
+
}
|
|
939
|
+
|
|
940
|
+
/**
|
|
941
|
+
* Sets the source of this query to be a reference to a source in the model.
|
|
942
|
+
*
|
|
943
|
+
* ```ts
|
|
944
|
+
* q.setSource('flights')
|
|
945
|
+
* ```
|
|
946
|
+
* ```
|
|
947
|
+
* run: flights -> { }
|
|
948
|
+
* ```
|
|
949
|
+
*
|
|
950
|
+
* @param name The name of the source in the model to reference.
|
|
951
|
+
*/
|
|
952
|
+
public setSource(name: string) {
|
|
953
|
+
if (this.definition instanceof ASTArrowQueryDefinition) {
|
|
954
|
+
if (this.definition.sourceReference.name === name) {
|
|
955
|
+
return;
|
|
956
|
+
}
|
|
957
|
+
}
|
|
958
|
+
const source = this.model.entries.find(e => e.name === name);
|
|
959
|
+
if (source === undefined) {
|
|
960
|
+
throw new Error(`Source ${name} is not defined in model`);
|
|
961
|
+
}
|
|
962
|
+
this.definition = new ASTArrowQueryDefinition({
|
|
963
|
+
kind: 'arrow',
|
|
964
|
+
source_reference: {name},
|
|
965
|
+
view:
|
|
966
|
+
this.definition instanceof ASTArrowQueryDefinition
|
|
967
|
+
? this.definition.view.build()
|
|
968
|
+
: {
|
|
969
|
+
kind: 'segment',
|
|
970
|
+
operations: [],
|
|
971
|
+
},
|
|
972
|
+
});
|
|
973
|
+
}
|
|
974
|
+
|
|
975
|
+
/**
|
|
976
|
+
* Sets the head of this query to be a reference to a top level query.
|
|
977
|
+
*
|
|
978
|
+
* ```ts
|
|
979
|
+
* q.setQueryHead('flights_by_carrier')
|
|
980
|
+
* ```
|
|
981
|
+
* ```
|
|
982
|
+
* run: flights_by_carrier
|
|
983
|
+
* ```
|
|
984
|
+
*
|
|
985
|
+
* @param name The name of the query in the model to reference.
|
|
986
|
+
*/
|
|
987
|
+
public setQueryHead(name: string) {
|
|
988
|
+
const query = this.model.entries.find(e => e.name === name);
|
|
989
|
+
if (query === undefined || query.kind !== 'query') {
|
|
990
|
+
throw new Error(`No query named ${name} in the model`);
|
|
991
|
+
}
|
|
992
|
+
this.definition = new ASTReferenceQueryDefinition({
|
|
993
|
+
kind: 'query_reference',
|
|
994
|
+
name,
|
|
995
|
+
});
|
|
996
|
+
}
|
|
997
|
+
|
|
998
|
+
/**
|
|
999
|
+
* @internal
|
|
1000
|
+
*/
|
|
1001
|
+
getQueryInfo(name: string): Malloy.QueryInfo {
|
|
1002
|
+
const query = this.model.entries.find(e => e.name === name);
|
|
1003
|
+
if (query === undefined) {
|
|
1004
|
+
throw new Error(`Query ${name} is not defined in model`);
|
|
1005
|
+
}
|
|
1006
|
+
if (query.kind !== 'query') {
|
|
1007
|
+
throw new Error(`Model entry ${name} is not a query`);
|
|
1008
|
+
}
|
|
1009
|
+
return query;
|
|
1010
|
+
}
|
|
1011
|
+
|
|
1012
|
+
/**
|
|
1013
|
+
* Emits the current query object as Malloy code
|
|
1014
|
+
*
|
|
1015
|
+
* ```ts
|
|
1016
|
+
* q.setSource('flights')
|
|
1017
|
+
* q.setView('by_carrier')
|
|
1018
|
+
* q.toMalloy();
|
|
1019
|
+
* ```
|
|
1020
|
+
* ```
|
|
1021
|
+
* run: flights -> by_carrier
|
|
1022
|
+
* ```
|
|
1023
|
+
*
|
|
1024
|
+
* @returns Malloy code for the query
|
|
1025
|
+
*/
|
|
1026
|
+
toMalloy(): string {
|
|
1027
|
+
return Malloy.queryToMalloy(this.build());
|
|
1028
|
+
}
|
|
1029
|
+
|
|
1030
|
+
/**
|
|
1031
|
+
* Build the query into its `Malloy.Query` object form. New JS objects will be
|
|
1032
|
+
* created for any subtree which has edits.
|
|
1033
|
+
*
|
|
1034
|
+
* @returns A `Malloy.Query` representing the current query
|
|
1035
|
+
*/
|
|
1036
|
+
build(): Malloy.Query {
|
|
1037
|
+
return super.build();
|
|
1038
|
+
}
|
|
1039
|
+
|
|
1040
|
+
/**
|
|
1041
|
+
* Set the view of this query; overwrites any other query operations.
|
|
1042
|
+
*
|
|
1043
|
+
* ```
|
|
1044
|
+
* run: flights ->
|
|
1045
|
+
* ```
|
|
1046
|
+
* ```ts
|
|
1047
|
+
* q.setView('by_carrier')
|
|
1048
|
+
* ```
|
|
1049
|
+
* ```
|
|
1050
|
+
* run: flights -> by_carrier
|
|
1051
|
+
* ```
|
|
1052
|
+
*
|
|
1053
|
+
* @param name The name of the view to set as the head of the query pipeline
|
|
1054
|
+
*/
|
|
1055
|
+
setView(name: string): ASTReferenceViewDefinition {
|
|
1056
|
+
if (!(this.definition instanceof ASTArrowQueryDefinition)) {
|
|
1057
|
+
throw new Error('Must set source before setting view');
|
|
1058
|
+
}
|
|
1059
|
+
this.definition = new ASTArrowQueryDefinition({
|
|
1060
|
+
kind: 'arrow',
|
|
1061
|
+
source_reference: this.definition.sourceReference.build(),
|
|
1062
|
+
view: {
|
|
1063
|
+
kind: 'view_reference',
|
|
1064
|
+
name,
|
|
1065
|
+
},
|
|
1066
|
+
});
|
|
1067
|
+
return this.definition.view.asReferenceViewDefinition();
|
|
1068
|
+
}
|
|
1069
|
+
|
|
1070
|
+
getInheritedAnnotations(): Malloy.Annotation[] {
|
|
1071
|
+
if (this.definition instanceof ASTReferenceQueryDefinition) {
|
|
1072
|
+
const query = this.getQueryInfo(this.definition.name);
|
|
1073
|
+
return query.annotations ?? [];
|
|
1074
|
+
} else if (this.definition instanceof ASTRefinementQueryDefinition) {
|
|
1075
|
+
const query = this.getQueryInfo(this.definition.queryReference.name);
|
|
1076
|
+
return query.annotations ?? [];
|
|
1077
|
+
} else if (this.definition instanceof ASTArrowQueryDefinition) {
|
|
1078
|
+
return this.definition.view.getInheritedAnnotations();
|
|
1079
|
+
}
|
|
1080
|
+
return [];
|
|
1081
|
+
}
|
|
1082
|
+
}
|
|
1083
|
+
|
|
1084
|
+
export type RawLiteralValue =
|
|
1085
|
+
| number
|
|
1086
|
+
| string
|
|
1087
|
+
| {date: Date; granularity: Malloy.TimestampTimeframe}
|
|
1088
|
+
| boolean
|
|
1089
|
+
| null;
|
|
1090
|
+
|
|
1091
|
+
export interface IASTReference extends ASTAny {
|
|
1092
|
+
/**
|
|
1093
|
+
* Gets the parameter list for this reference, or creates it if it does not exist.
|
|
1094
|
+
*
|
|
1095
|
+
* @returns The parameter list `ASTParameterValueList`
|
|
1096
|
+
*/
|
|
1097
|
+
getOrAddParameters(): ASTParameterValueList;
|
|
1098
|
+
|
|
1099
|
+
/**
|
|
1100
|
+
* Adds a parameter to this source with with the given name and value
|
|
1101
|
+
*
|
|
1102
|
+
* This will override an existing parameter with the same name.
|
|
1103
|
+
*
|
|
1104
|
+
* @param name The name of the parameter to set
|
|
1105
|
+
* @param value The value of the parameter to set
|
|
1106
|
+
*/
|
|
1107
|
+
setParameter(name: string, value: RawLiteralValue): void;
|
|
1108
|
+
|
|
1109
|
+
tryGetParameter(name: string): ASTParameterValue | undefined;
|
|
1110
|
+
|
|
1111
|
+
parameters: ASTParameterValueList | undefined;
|
|
1112
|
+
name: string;
|
|
1113
|
+
path: string[] | undefined;
|
|
1114
|
+
}
|
|
1115
|
+
|
|
1116
|
+
export class ASTReference
|
|
1117
|
+
extends ASTObjectNode<
|
|
1118
|
+
Malloy.Reference,
|
|
1119
|
+
{
|
|
1120
|
+
name: string;
|
|
1121
|
+
path?: string[];
|
|
1122
|
+
parameters?: ASTParameterValueList;
|
|
1123
|
+
}
|
|
1124
|
+
>
|
|
1125
|
+
implements IASTReference
|
|
1126
|
+
{
|
|
1127
|
+
constructor(public reference: Malloy.Reference) {
|
|
1128
|
+
super(reference, {
|
|
1129
|
+
name: reference.name,
|
|
1130
|
+
path: reference.path,
|
|
1131
|
+
parameters:
|
|
1132
|
+
reference.parameters && new ASTParameterValueList(reference.parameters),
|
|
1133
|
+
});
|
|
1134
|
+
}
|
|
1135
|
+
|
|
1136
|
+
get name() {
|
|
1137
|
+
return this.children.name;
|
|
1138
|
+
}
|
|
1139
|
+
|
|
1140
|
+
set name(value: string) {
|
|
1141
|
+
this.edit();
|
|
1142
|
+
this.children.name = value;
|
|
1143
|
+
}
|
|
1144
|
+
|
|
1145
|
+
get parameters() {
|
|
1146
|
+
return this.children.parameters;
|
|
1147
|
+
}
|
|
1148
|
+
|
|
1149
|
+
set parameters(parameters: ASTParameterValueList | undefined) {
|
|
1150
|
+
this.edit();
|
|
1151
|
+
this.children.parameters = parameters;
|
|
1152
|
+
}
|
|
1153
|
+
|
|
1154
|
+
get path() {
|
|
1155
|
+
return this.children.path;
|
|
1156
|
+
}
|
|
1157
|
+
|
|
1158
|
+
/**
|
|
1159
|
+
* @internal
|
|
1160
|
+
*/
|
|
1161
|
+
static getOrAddParameters(reference: IASTReference) {
|
|
1162
|
+
if (reference.parameters) {
|
|
1163
|
+
return reference.parameters;
|
|
1164
|
+
}
|
|
1165
|
+
reference.edit();
|
|
1166
|
+
const parameters = new ASTParameterValueList([]);
|
|
1167
|
+
reference.parameters = parameters;
|
|
1168
|
+
return parameters;
|
|
1169
|
+
}
|
|
1170
|
+
|
|
1171
|
+
/**
|
|
1172
|
+
* @internal
|
|
1173
|
+
*/
|
|
1174
|
+
static setParameter(
|
|
1175
|
+
reference: IASTReference,
|
|
1176
|
+
name: string,
|
|
1177
|
+
value: RawLiteralValue
|
|
1178
|
+
) {
|
|
1179
|
+
return reference.getOrAddParameters().addParameter(name, value);
|
|
1180
|
+
}
|
|
1181
|
+
|
|
1182
|
+
/**
|
|
1183
|
+
* @internal
|
|
1184
|
+
*/
|
|
1185
|
+
static tryGetParameter(reference: IASTReference, name: string) {
|
|
1186
|
+
if (reference.parameters === undefined) return undefined;
|
|
1187
|
+
for (const parameter of reference.parameters.iter()) {
|
|
1188
|
+
if (parameter.name === name) {
|
|
1189
|
+
return parameter;
|
|
1190
|
+
}
|
|
1191
|
+
}
|
|
1192
|
+
}
|
|
1193
|
+
|
|
1194
|
+
public getOrAddParameters() {
|
|
1195
|
+
return ASTReference.getOrAddParameters(this);
|
|
1196
|
+
}
|
|
1197
|
+
|
|
1198
|
+
public setParameter(name: string, value: RawLiteralValue) {
|
|
1199
|
+
return ASTReference.setParameter(this, name, value);
|
|
1200
|
+
}
|
|
1201
|
+
|
|
1202
|
+
public tryGetParameter(name: string): ASTParameterValue | undefined {
|
|
1203
|
+
return ASTReference.tryGetParameter(this, name);
|
|
1204
|
+
}
|
|
1205
|
+
}
|
|
1206
|
+
|
|
1207
|
+
type ASTFieldReferenceParent =
|
|
1208
|
+
| ASTFilterWithFilterString
|
|
1209
|
+
| ASTOrderByViewOperation
|
|
1210
|
+
| ASTTimeTruncationExpression
|
|
1211
|
+
| ASTFilteredFieldExpression;
|
|
1212
|
+
|
|
1213
|
+
export class ASTFieldReference extends ASTReference {
|
|
1214
|
+
/**
|
|
1215
|
+
* @internal
|
|
1216
|
+
*/
|
|
1217
|
+
get segment(): ASTSegmentViewDefinition {
|
|
1218
|
+
const parent = this.parent as ASTFieldReferenceParent;
|
|
1219
|
+
if (
|
|
1220
|
+
parent instanceof ASTFilteredFieldExpression ||
|
|
1221
|
+
parent instanceof ASTTimeTruncationExpression
|
|
1222
|
+
) {
|
|
1223
|
+
return parent.field.segment;
|
|
1224
|
+
} else if (parent instanceof ASTFilterWithFilterString) {
|
|
1225
|
+
const grand = parent.parent as ASTWhere | ASTWhereViewOperation;
|
|
1226
|
+
if (grand instanceof ASTWhere) {
|
|
1227
|
+
return grand.list.expression.field.segment;
|
|
1228
|
+
} else {
|
|
1229
|
+
return grand.list.segment;
|
|
1230
|
+
}
|
|
1231
|
+
} else {
|
|
1232
|
+
return parent.list.segment;
|
|
1233
|
+
}
|
|
1234
|
+
}
|
|
1235
|
+
|
|
1236
|
+
getFieldInfo() {
|
|
1237
|
+
const schema = this.segment.getInputSchema();
|
|
1238
|
+
return ASTNode.schemaGet(schema, this.name, this.path);
|
|
1239
|
+
}
|
|
1240
|
+
}
|
|
1241
|
+
|
|
1242
|
+
export class ASTSourceReference extends ASTReference {
|
|
1243
|
+
/**
|
|
1244
|
+
* @internal
|
|
1245
|
+
*/
|
|
1246
|
+
get query(): ASTQuery {
|
|
1247
|
+
return this.parent.parent.asQuery();
|
|
1248
|
+
}
|
|
1249
|
+
|
|
1250
|
+
/**
|
|
1251
|
+
* Gets the `Malloy.SourceInfo` for the referenced source
|
|
1252
|
+
*
|
|
1253
|
+
* @returns The source information for the referenced source
|
|
1254
|
+
*/
|
|
1255
|
+
public getSourceInfo(): Malloy.SourceInfo {
|
|
1256
|
+
const info = this.query.model.entries.find(e => e.name === this.name);
|
|
1257
|
+
if (info === undefined) {
|
|
1258
|
+
throw new Error('No source info found');
|
|
1259
|
+
}
|
|
1260
|
+
return info;
|
|
1261
|
+
}
|
|
1262
|
+
|
|
1263
|
+
public getSourceParameters(): Malloy.ParameterInfo[] {
|
|
1264
|
+
return this.getSourceInfo().parameters ?? [];
|
|
1265
|
+
}
|
|
1266
|
+
|
|
1267
|
+
areRequiredParametersSet() {
|
|
1268
|
+
const sourceParameters = this.getSourceParameters();
|
|
1269
|
+
for (const parameterInfo of sourceParameters) {
|
|
1270
|
+
if (parameterInfo.default_value !== undefined) continue;
|
|
1271
|
+
const parameter = this.tryGetParameter(parameterInfo.name);
|
|
1272
|
+
if (parameter === undefined) {
|
|
1273
|
+
return false;
|
|
1274
|
+
}
|
|
1275
|
+
}
|
|
1276
|
+
return true;
|
|
1277
|
+
}
|
|
1278
|
+
}
|
|
1279
|
+
|
|
1280
|
+
export class ASTParameterValueList extends ASTListNode<
|
|
1281
|
+
Malloy.ParameterValue,
|
|
1282
|
+
ASTParameterValue
|
|
1283
|
+
> {
|
|
1284
|
+
constructor(parameters: Malloy.ParameterValue[]) {
|
|
1285
|
+
super(
|
|
1286
|
+
parameters,
|
|
1287
|
+
parameters.map(p => new ASTParameterValue(p))
|
|
1288
|
+
);
|
|
1289
|
+
}
|
|
1290
|
+
|
|
1291
|
+
get parameters() {
|
|
1292
|
+
return this.children;
|
|
1293
|
+
}
|
|
1294
|
+
|
|
1295
|
+
addParameter(name: string, value: RawLiteralValue) {
|
|
1296
|
+
// TODO validate that the parameter is valid (name and type)
|
|
1297
|
+
this.add(
|
|
1298
|
+
new ASTParameterValue({
|
|
1299
|
+
name,
|
|
1300
|
+
value: LiteralValueAST.makeLiteral(value),
|
|
1301
|
+
})
|
|
1302
|
+
);
|
|
1303
|
+
}
|
|
1304
|
+
}
|
|
1305
|
+
|
|
1306
|
+
export class ASTParameterValue extends ASTObjectNode<
|
|
1307
|
+
Malloy.ParameterValue,
|
|
1308
|
+
{
|
|
1309
|
+
name: string;
|
|
1310
|
+
value: ASTLiteralValue;
|
|
1311
|
+
}
|
|
1312
|
+
> {
|
|
1313
|
+
constructor(public parameter: Malloy.ParameterValue) {
|
|
1314
|
+
super(parameter, {
|
|
1315
|
+
name: parameter.name,
|
|
1316
|
+
value: LiteralValueAST.from(parameter.value),
|
|
1317
|
+
});
|
|
1318
|
+
}
|
|
1319
|
+
|
|
1320
|
+
get name() {
|
|
1321
|
+
return this.children.name;
|
|
1322
|
+
}
|
|
1323
|
+
}
|
|
1324
|
+
|
|
1325
|
+
export type ASTLiteralValue =
|
|
1326
|
+
| ASTStringLiteralValue
|
|
1327
|
+
| ASTNumberLiteralValue
|
|
1328
|
+
| ASTBooleanLiteralValue
|
|
1329
|
+
| ASTDateLiteralValue
|
|
1330
|
+
| ASTTimestampLiteralValue
|
|
1331
|
+
| ASTNullLiteralValue;
|
|
1332
|
+
export const LiteralValueAST = {
|
|
1333
|
+
from(value: Malloy.LiteralValue) {
|
|
1334
|
+
switch (value.kind) {
|
|
1335
|
+
case 'string_literal':
|
|
1336
|
+
return new ASTStringLiteralValue(value);
|
|
1337
|
+
case 'number_literal':
|
|
1338
|
+
return new ASTNumberLiteralValue(value);
|
|
1339
|
+
case 'boolean_literal':
|
|
1340
|
+
return new ASTBooleanLiteralValue(value);
|
|
1341
|
+
case 'date_literal':
|
|
1342
|
+
return new ASTDateLiteralValue(value);
|
|
1343
|
+
case 'timestamp_literal':
|
|
1344
|
+
return new ASTTimestampLiteralValue(value);
|
|
1345
|
+
case 'null_literal':
|
|
1346
|
+
return new ASTNullLiteralValue(value);
|
|
1347
|
+
}
|
|
1348
|
+
},
|
|
1349
|
+
makeLiteral(value: RawLiteralValue): Malloy.LiteralValue {
|
|
1350
|
+
if (typeof value === 'string') {
|
|
1351
|
+
return {
|
|
1352
|
+
kind: 'string_literal',
|
|
1353
|
+
string_value: value,
|
|
1354
|
+
};
|
|
1355
|
+
} else if (typeof value === 'boolean') {
|
|
1356
|
+
return {
|
|
1357
|
+
kind: 'boolean_literal',
|
|
1358
|
+
boolean_value: value,
|
|
1359
|
+
};
|
|
1360
|
+
} else if (typeof value === 'number') {
|
|
1361
|
+
return {
|
|
1362
|
+
kind: 'number_literal',
|
|
1363
|
+
number_value: value,
|
|
1364
|
+
};
|
|
1365
|
+
} else if (value === null) {
|
|
1366
|
+
return {
|
|
1367
|
+
kind: 'null_literal',
|
|
1368
|
+
};
|
|
1369
|
+
} else if ('date' in value) {
|
|
1370
|
+
const granularity = value.granularity;
|
|
1371
|
+
const serialized = serializeDateAsLiteral(value.date);
|
|
1372
|
+
if (isDateTimeframe(granularity)) {
|
|
1373
|
+
return {
|
|
1374
|
+
kind: 'date_literal',
|
|
1375
|
+
date_value: serialized,
|
|
1376
|
+
granularity: granularity,
|
|
1377
|
+
};
|
|
1378
|
+
}
|
|
1379
|
+
return {
|
|
1380
|
+
kind: 'timestamp_literal',
|
|
1381
|
+
timestamp_value: serialized,
|
|
1382
|
+
granularity: granularity,
|
|
1383
|
+
};
|
|
1384
|
+
}
|
|
1385
|
+
throw new Error('Unknown literal type');
|
|
1386
|
+
},
|
|
1387
|
+
};
|
|
1388
|
+
|
|
1389
|
+
export class ASTStringLiteralValue extends ASTObjectNode<
|
|
1390
|
+
Malloy.LiteralValueWithStringLiteral,
|
|
1391
|
+
{
|
|
1392
|
+
kind: 'string_literal';
|
|
1393
|
+
string_value: string;
|
|
1394
|
+
}
|
|
1395
|
+
> {
|
|
1396
|
+
readonly kind: Malloy.LiteralValueType = 'string_literal';
|
|
1397
|
+
|
|
1398
|
+
constructor(public node: Malloy.LiteralValueWithStringLiteral) {
|
|
1399
|
+
super(node, {
|
|
1400
|
+
kind: node.kind,
|
|
1401
|
+
string_value: node.string_value,
|
|
1402
|
+
});
|
|
1403
|
+
}
|
|
1404
|
+
}
|
|
1405
|
+
|
|
1406
|
+
export class ASTNullLiteralValue extends ASTObjectNode<
|
|
1407
|
+
Malloy.LiteralValueWithNullLiteral,
|
|
1408
|
+
{
|
|
1409
|
+
kind: 'null_literal';
|
|
1410
|
+
}
|
|
1411
|
+
> {
|
|
1412
|
+
readonly kind: Malloy.LiteralValueType = 'null_literal';
|
|
1413
|
+
|
|
1414
|
+
constructor(public node: Malloy.LiteralValueWithNullLiteral) {
|
|
1415
|
+
super(node, {
|
|
1416
|
+
kind: node.kind,
|
|
1417
|
+
});
|
|
1418
|
+
}
|
|
1419
|
+
}
|
|
1420
|
+
|
|
1421
|
+
export class ASTNumberLiteralValue extends ASTObjectNode<
|
|
1422
|
+
Malloy.LiteralValueWithNumberLiteral,
|
|
1423
|
+
{
|
|
1424
|
+
kind: 'number_literal';
|
|
1425
|
+
number_value: number;
|
|
1426
|
+
}
|
|
1427
|
+
> {
|
|
1428
|
+
readonly kind: Malloy.LiteralValueType = 'number_literal';
|
|
1429
|
+
|
|
1430
|
+
constructor(public node: Malloy.LiteralValueWithNumberLiteral) {
|
|
1431
|
+
super(node, {
|
|
1432
|
+
kind: node.kind,
|
|
1433
|
+
number_value: node.number_value,
|
|
1434
|
+
});
|
|
1435
|
+
}
|
|
1436
|
+
}
|
|
1437
|
+
|
|
1438
|
+
export class ASTBooleanLiteralValue extends ASTObjectNode<
|
|
1439
|
+
Malloy.LiteralValueWithBooleanLiteral,
|
|
1440
|
+
{
|
|
1441
|
+
kind: 'boolean_literal';
|
|
1442
|
+
boolean_value: boolean;
|
|
1443
|
+
}
|
|
1444
|
+
> {
|
|
1445
|
+
readonly kind: Malloy.LiteralValueType = 'boolean_literal';
|
|
1446
|
+
|
|
1447
|
+
constructor(public node: Malloy.LiteralValueWithBooleanLiteral) {
|
|
1448
|
+
super(node, {
|
|
1449
|
+
kind: node.kind,
|
|
1450
|
+
boolean_value: node.boolean_value,
|
|
1451
|
+
});
|
|
1452
|
+
}
|
|
1453
|
+
}
|
|
1454
|
+
|
|
1455
|
+
export class ASTDateLiteralValue extends ASTObjectNode<
|
|
1456
|
+
Malloy.LiteralValueWithDateLiteral,
|
|
1457
|
+
{
|
|
1458
|
+
kind: 'date_literal';
|
|
1459
|
+
date_value: string;
|
|
1460
|
+
granularity?: Malloy.DateTimeframe;
|
|
1461
|
+
}
|
|
1462
|
+
> {
|
|
1463
|
+
readonly kind: Malloy.LiteralValueType = 'date_literal';
|
|
1464
|
+
|
|
1465
|
+
constructor(public node: Malloy.LiteralValueWithDateLiteral) {
|
|
1466
|
+
super(node, {
|
|
1467
|
+
kind: node.kind,
|
|
1468
|
+
date_value: node.date_value,
|
|
1469
|
+
granularity: node.granularity,
|
|
1470
|
+
});
|
|
1471
|
+
}
|
|
1472
|
+
}
|
|
1473
|
+
|
|
1474
|
+
export class ASTTimestampLiteralValue extends ASTObjectNode<
|
|
1475
|
+
Malloy.LiteralValueWithTimestampLiteral,
|
|
1476
|
+
{
|
|
1477
|
+
kind: 'timestamp_literal';
|
|
1478
|
+
timestamp_value: string;
|
|
1479
|
+
granularity?: Malloy.TimestampTimeframe;
|
|
1480
|
+
}
|
|
1481
|
+
> {
|
|
1482
|
+
readonly kind: Malloy.LiteralValueType = 'timestamp_literal';
|
|
1483
|
+
|
|
1484
|
+
constructor(public node: Malloy.LiteralValueWithTimestampLiteral) {
|
|
1485
|
+
super(node, {
|
|
1486
|
+
kind: node.kind,
|
|
1487
|
+
timestamp_value: node.timestamp_value,
|
|
1488
|
+
granularity: node.granularity,
|
|
1489
|
+
});
|
|
1490
|
+
}
|
|
1491
|
+
}
|
|
1492
|
+
|
|
1493
|
+
export class ASTUnimplemented<T> extends ASTNode<T> {
|
|
1494
|
+
constructor(private readonly node: T) {
|
|
1495
|
+
super();
|
|
1496
|
+
}
|
|
1497
|
+
get treeEdited(): boolean {
|
|
1498
|
+
return false;
|
|
1499
|
+
}
|
|
1500
|
+
|
|
1501
|
+
build(): T {
|
|
1502
|
+
return this.node;
|
|
1503
|
+
}
|
|
1504
|
+
|
|
1505
|
+
find(): never {
|
|
1506
|
+
throw new Error('Tried to find a node from an unimplemented node type');
|
|
1507
|
+
}
|
|
1508
|
+
}
|
|
1509
|
+
|
|
1510
|
+
export interface IASTQueryOrViewDefinition {
|
|
1511
|
+
/**
|
|
1512
|
+
* Upward propagation of field deletion/rename etc
|
|
1513
|
+
* @internal
|
|
1514
|
+
*/
|
|
1515
|
+
propagateUp(f: PropagationFunction): void;
|
|
1516
|
+
/**
|
|
1517
|
+
* Downward propagation of field deletion/rename etc
|
|
1518
|
+
* @internal
|
|
1519
|
+
*/
|
|
1520
|
+
propagateDown(f: PropagationFunction): void;
|
|
1521
|
+
}
|
|
1522
|
+
|
|
1523
|
+
type PropagationFunction = (propagatable: IASTQueryOrViewDefinition) => void;
|
|
1524
|
+
|
|
1525
|
+
export interface IASTQueryDefinition extends IASTQueryOrViewDefinition {
|
|
1526
|
+
getOrAddDefaultSegment(): ASTSegmentViewDefinition;
|
|
1527
|
+
reorderFields(names: string[]): void;
|
|
1528
|
+
isRunnable(): boolean;
|
|
1529
|
+
}
|
|
1530
|
+
|
|
1531
|
+
export type ASTQueryDefinition =
|
|
1532
|
+
| ASTReferenceQueryDefinition
|
|
1533
|
+
| ASTArrowQueryDefinition
|
|
1534
|
+
| ASTRefinementQueryDefinition;
|
|
1535
|
+
export const ASTQueryDefinition = {
|
|
1536
|
+
from: (definition: Malloy.QueryDefinition) => {
|
|
1537
|
+
switch (definition.kind) {
|
|
1538
|
+
case 'arrow':
|
|
1539
|
+
return new ASTArrowQueryDefinition(definition);
|
|
1540
|
+
case 'query_reference':
|
|
1541
|
+
return new ASTReferenceQueryDefinition(definition);
|
|
1542
|
+
case 'refinement':
|
|
1543
|
+
return new ASTRefinementQueryDefinition(definition);
|
|
1544
|
+
}
|
|
1545
|
+
},
|
|
1546
|
+
};
|
|
1547
|
+
|
|
1548
|
+
export class ASTArrowQueryDefinition
|
|
1549
|
+
extends ASTObjectNode<
|
|
1550
|
+
Malloy.QueryDefinitionWithArrow,
|
|
1551
|
+
{
|
|
1552
|
+
kind: 'arrow';
|
|
1553
|
+
source_reference: ASTSourceReference;
|
|
1554
|
+
view: ASTViewDefinition;
|
|
1555
|
+
}
|
|
1556
|
+
>
|
|
1557
|
+
implements IASTQueryDefinition
|
|
1558
|
+
{
|
|
1559
|
+
constructor(public node: Malloy.QueryDefinitionWithArrow) {
|
|
1560
|
+
super(node, {
|
|
1561
|
+
kind: 'arrow',
|
|
1562
|
+
source_reference: new ASTSourceReference(node.source_reference),
|
|
1563
|
+
view: ASTViewDefinition.from(node.view),
|
|
1564
|
+
});
|
|
1565
|
+
}
|
|
1566
|
+
|
|
1567
|
+
get view() {
|
|
1568
|
+
return this.children.view;
|
|
1569
|
+
}
|
|
1570
|
+
|
|
1571
|
+
set view(view: ASTViewDefinition) {
|
|
1572
|
+
this.edit();
|
|
1573
|
+
this.children.view = view;
|
|
1574
|
+
view.parent = this;
|
|
1575
|
+
}
|
|
1576
|
+
|
|
1577
|
+
get sourceReference() {
|
|
1578
|
+
return this.children.source_reference;
|
|
1579
|
+
}
|
|
1580
|
+
|
|
1581
|
+
getOrAddDefaultSegment(): ASTSegmentViewDefinition {
|
|
1582
|
+
return this.view.getOrAddDefaultSegment();
|
|
1583
|
+
}
|
|
1584
|
+
|
|
1585
|
+
getSourceInfo() {
|
|
1586
|
+
return this.sourceReference.getSourceInfo();
|
|
1587
|
+
}
|
|
1588
|
+
|
|
1589
|
+
getOutputSchema() {
|
|
1590
|
+
return this.view.getRefinementSchema();
|
|
1591
|
+
}
|
|
1592
|
+
|
|
1593
|
+
isRunnable(): boolean {
|
|
1594
|
+
return (
|
|
1595
|
+
this.view.isRunnable() && this.sourceReference.areRequiredParametersSet()
|
|
1596
|
+
);
|
|
1597
|
+
}
|
|
1598
|
+
|
|
1599
|
+
/**
|
|
1600
|
+
* @internal
|
|
1601
|
+
*/
|
|
1602
|
+
get query() {
|
|
1603
|
+
return this.parent.asQuery();
|
|
1604
|
+
}
|
|
1605
|
+
|
|
1606
|
+
/**
|
|
1607
|
+
* @internal
|
|
1608
|
+
*/
|
|
1609
|
+
propagateUp(f: PropagationFunction): void {
|
|
1610
|
+
this.propagateDown(f);
|
|
1611
|
+
}
|
|
1612
|
+
|
|
1613
|
+
/**
|
|
1614
|
+
* @internal
|
|
1615
|
+
*/
|
|
1616
|
+
propagateDown(f: PropagationFunction): void {
|
|
1617
|
+
f(this.view);
|
|
1618
|
+
this.view.propagateDown(f);
|
|
1619
|
+
}
|
|
1620
|
+
|
|
1621
|
+
reorderFields(names: string[]): void {
|
|
1622
|
+
if (this.view instanceof ASTSegmentViewDefinition) {
|
|
1623
|
+
this.view.reorderFields(names);
|
|
1624
|
+
} else {
|
|
1625
|
+
this.query.getOrAddAnnotations().setTagProperty(['field_order'], names);
|
|
1626
|
+
}
|
|
1627
|
+
}
|
|
1628
|
+
}
|
|
1629
|
+
|
|
1630
|
+
export class ASTRefinementQueryDefinition
|
|
1631
|
+
extends ASTObjectNode<
|
|
1632
|
+
Malloy.QueryDefinitionWithRefinement,
|
|
1633
|
+
{
|
|
1634
|
+
kind: 'refinement';
|
|
1635
|
+
query_reference: ASTReference;
|
|
1636
|
+
refinement: ASTViewDefinition;
|
|
1637
|
+
}
|
|
1638
|
+
>
|
|
1639
|
+
implements IASTQueryDefinition
|
|
1640
|
+
{
|
|
1641
|
+
constructor(public node: Malloy.QueryDefinitionWithRefinement) {
|
|
1642
|
+
super(node, {
|
|
1643
|
+
kind: 'refinement',
|
|
1644
|
+
query_reference: new ASTReference(node.query_reference),
|
|
1645
|
+
refinement: ASTViewDefinition.from(node.refinement),
|
|
1646
|
+
});
|
|
1647
|
+
}
|
|
1648
|
+
|
|
1649
|
+
get queryReference() {
|
|
1650
|
+
return this.children.query_reference;
|
|
1651
|
+
}
|
|
1652
|
+
|
|
1653
|
+
get refinement() {
|
|
1654
|
+
return this.children.refinement;
|
|
1655
|
+
}
|
|
1656
|
+
|
|
1657
|
+
set refinement(refinement: ASTViewDefinition) {
|
|
1658
|
+
this.edit();
|
|
1659
|
+
this.children.refinement = refinement;
|
|
1660
|
+
refinement.parent = this;
|
|
1661
|
+
}
|
|
1662
|
+
|
|
1663
|
+
isRunnable(): boolean {
|
|
1664
|
+
return this.refinement.isRunnable();
|
|
1665
|
+
}
|
|
1666
|
+
|
|
1667
|
+
/**
|
|
1668
|
+
* @internal
|
|
1669
|
+
*/
|
|
1670
|
+
get query() {
|
|
1671
|
+
return this.parent.asQuery();
|
|
1672
|
+
}
|
|
1673
|
+
|
|
1674
|
+
getOrAddDefaultSegment(): ASTSegmentViewDefinition {
|
|
1675
|
+
return this.refinement.getOrAddDefaultSegment();
|
|
1676
|
+
}
|
|
1677
|
+
|
|
1678
|
+
getOutputSchema() {
|
|
1679
|
+
const model = this.query.model;
|
|
1680
|
+
const query = model.entries.find(e => e.name === this.queryReference.name);
|
|
1681
|
+
if (query === undefined) {
|
|
1682
|
+
throw new Error(`Query not found with name ${this.queryReference.name}`);
|
|
1683
|
+
}
|
|
1684
|
+
const base = query.schema;
|
|
1685
|
+
const refinement = this.refinement.getRefinementSchema();
|
|
1686
|
+
return ASTQuery.schemaMerge(base, refinement);
|
|
1687
|
+
}
|
|
1688
|
+
|
|
1689
|
+
/**
|
|
1690
|
+
* @internal
|
|
1691
|
+
*/
|
|
1692
|
+
propagateUp(f: PropagationFunction): void {
|
|
1693
|
+
this.propagateDown(f);
|
|
1694
|
+
}
|
|
1695
|
+
|
|
1696
|
+
/**
|
|
1697
|
+
* @internal
|
|
1698
|
+
*/
|
|
1699
|
+
propagateDown(f: PropagationFunction): void {
|
|
1700
|
+
f(this.refinement);
|
|
1701
|
+
this.refinement.propagateDown(f);
|
|
1702
|
+
}
|
|
1703
|
+
|
|
1704
|
+
reorderFields(names: string[]): void {
|
|
1705
|
+
this.query.getOrAddAnnotations().setTagProperty(['field_order'], names);
|
|
1706
|
+
}
|
|
1707
|
+
}
|
|
1708
|
+
|
|
1709
|
+
export class ASTReferenceQueryDefinition
|
|
1710
|
+
extends ASTObjectNode<
|
|
1711
|
+
Malloy.QueryDefinitionWithQueryReference,
|
|
1712
|
+
{
|
|
1713
|
+
kind: 'query_reference';
|
|
1714
|
+
name: string;
|
|
1715
|
+
path?: string[];
|
|
1716
|
+
parameters?: ASTParameterValueList;
|
|
1717
|
+
}
|
|
1718
|
+
>
|
|
1719
|
+
implements IASTQueryDefinition, IASTReference
|
|
1720
|
+
{
|
|
1721
|
+
constructor(public node: Malloy.QueryDefinitionWithQueryReference) {
|
|
1722
|
+
super(node, {
|
|
1723
|
+
kind: 'query_reference',
|
|
1724
|
+
name: node.name,
|
|
1725
|
+
path: node.path,
|
|
1726
|
+
parameters: node.parameters && new ASTParameterValueList(node.parameters),
|
|
1727
|
+
});
|
|
1728
|
+
}
|
|
1729
|
+
|
|
1730
|
+
isRunnable(): boolean {
|
|
1731
|
+
return true;
|
|
1732
|
+
}
|
|
1733
|
+
|
|
1734
|
+
get name() {
|
|
1735
|
+
return this.children.name;
|
|
1736
|
+
}
|
|
1737
|
+
|
|
1738
|
+
get query() {
|
|
1739
|
+
return this.parent.asQuery();
|
|
1740
|
+
}
|
|
1741
|
+
|
|
1742
|
+
get parameters() {
|
|
1743
|
+
return this.children.parameters;
|
|
1744
|
+
}
|
|
1745
|
+
|
|
1746
|
+
set parameters(parameters: ASTParameterValueList | undefined) {
|
|
1747
|
+
this.edit();
|
|
1748
|
+
this.children.parameters = parameters;
|
|
1749
|
+
}
|
|
1750
|
+
|
|
1751
|
+
get path() {
|
|
1752
|
+
return this.children.path;
|
|
1753
|
+
}
|
|
1754
|
+
|
|
1755
|
+
getOrAddDefaultSegment(): ASTSegmentViewDefinition {
|
|
1756
|
+
const newQuery = new ASTRefinementQueryDefinition({
|
|
1757
|
+
kind: 'refinement',
|
|
1758
|
+
query_reference: {
|
|
1759
|
+
name: this.name,
|
|
1760
|
+
path: this.path,
|
|
1761
|
+
parameters: this.parameters?.build(),
|
|
1762
|
+
},
|
|
1763
|
+
refinement: {
|
|
1764
|
+
kind: 'segment',
|
|
1765
|
+
operations: [],
|
|
1766
|
+
},
|
|
1767
|
+
});
|
|
1768
|
+
this.query.definition = newQuery;
|
|
1769
|
+
return newQuery.refinement.asSegmentViewDefinition();
|
|
1770
|
+
}
|
|
1771
|
+
|
|
1772
|
+
/**
|
|
1773
|
+
* @internal
|
|
1774
|
+
*/
|
|
1775
|
+
propagateUp(_f: PropagationFunction): void {
|
|
1776
|
+
return;
|
|
1777
|
+
}
|
|
1778
|
+
|
|
1779
|
+
/**
|
|
1780
|
+
* @internal
|
|
1781
|
+
*/
|
|
1782
|
+
propagateDown(_f: PropagationFunction): void {
|
|
1783
|
+
return;
|
|
1784
|
+
}
|
|
1785
|
+
|
|
1786
|
+
reorderFields(names: string[]): void {
|
|
1787
|
+
this.query.getOrAddAnnotations().setTagProperty(['field_order'], names);
|
|
1788
|
+
}
|
|
1789
|
+
|
|
1790
|
+
public getOrAddParameters() {
|
|
1791
|
+
return ASTReference.getOrAddParameters(this);
|
|
1792
|
+
}
|
|
1793
|
+
|
|
1794
|
+
public setParameter(name: string, value: RawLiteralValue) {
|
|
1795
|
+
return ASTReference.setParameter(this, name, value);
|
|
1796
|
+
}
|
|
1797
|
+
|
|
1798
|
+
public tryGetParameter(name: string): ASTParameterValue | undefined {
|
|
1799
|
+
return ASTReference.tryGetParameter(this, name);
|
|
1800
|
+
}
|
|
1801
|
+
}
|
|
1802
|
+
|
|
1803
|
+
export interface IASTViewDefinition extends IASTQueryOrViewDefinition {
|
|
1804
|
+
isRunnable(): boolean;
|
|
1805
|
+
getOrAddDefaultSegment(): ASTSegmentViewDefinition;
|
|
1806
|
+
getInputSchema(): Malloy.Schema;
|
|
1807
|
+
getOutputSchema(): Malloy.Schema;
|
|
1808
|
+
getImplicitName(): string | undefined;
|
|
1809
|
+
getRefinementSchema(): Malloy.Schema;
|
|
1810
|
+
addEmptyRefinement(): ASTSegmentViewDefinition;
|
|
1811
|
+
addViewRefinement(name: string, path?: string[]): ASTReferenceViewDefinition;
|
|
1812
|
+
isValidViewRefinement(
|
|
1813
|
+
name: string,
|
|
1814
|
+
path?: string[]
|
|
1815
|
+
): {
|
|
1816
|
+
isValidViewRefinement: boolean;
|
|
1817
|
+
error?: string;
|
|
1818
|
+
};
|
|
1819
|
+
getInheritedAnnotations(): Malloy.Annotation[];
|
|
1820
|
+
}
|
|
1821
|
+
|
|
1822
|
+
export type ASTViewDefinition =
|
|
1823
|
+
| ASTArrowViewDefinition
|
|
1824
|
+
| ASTRefinementViewDefinition
|
|
1825
|
+
| ASTSegmentViewDefinition
|
|
1826
|
+
| ASTReferenceViewDefinition;
|
|
1827
|
+
const ASTViewDefinition = {
|
|
1828
|
+
from(definition: Malloy.ViewDefinition) {
|
|
1829
|
+
switch (definition.kind) {
|
|
1830
|
+
case 'arrow':
|
|
1831
|
+
return new ASTArrowViewDefinition(definition);
|
|
1832
|
+
case 'view_reference':
|
|
1833
|
+
return new ASTReferenceViewDefinition(definition);
|
|
1834
|
+
case 'segment':
|
|
1835
|
+
return new ASTSegmentViewDefinition(definition);
|
|
1836
|
+
case 'refinement':
|
|
1837
|
+
return new ASTRefinementViewDefinition(definition);
|
|
1838
|
+
}
|
|
1839
|
+
},
|
|
1840
|
+
};
|
|
1841
|
+
|
|
1842
|
+
function swapViewInParent(node: ASTViewDefinition, view: ASTViewDefinition) {
|
|
1843
|
+
const parent = node.parent as
|
|
1844
|
+
| ASTArrowQueryDefinition
|
|
1845
|
+
| ASTRefinementQueryDefinition
|
|
1846
|
+
| ASTView
|
|
1847
|
+
| ASTArrowViewDefinition
|
|
1848
|
+
| ASTRefinementViewDefinition;
|
|
1849
|
+
if (parent instanceof ASTArrowQueryDefinition) {
|
|
1850
|
+
parent.view = view;
|
|
1851
|
+
} else if (parent instanceof ASTRefinementQueryDefinition) {
|
|
1852
|
+
parent.refinement = view;
|
|
1853
|
+
} else if (parent instanceof ASTView) {
|
|
1854
|
+
parent.definition = view;
|
|
1855
|
+
} else if (parent instanceof ASTArrowViewDefinition) {
|
|
1856
|
+
if (parent.source === node) {
|
|
1857
|
+
parent.source = view;
|
|
1858
|
+
} else {
|
|
1859
|
+
parent.view = view;
|
|
1860
|
+
}
|
|
1861
|
+
} else {
|
|
1862
|
+
if (parent.base === node) {
|
|
1863
|
+
parent.base = view;
|
|
1864
|
+
} else {
|
|
1865
|
+
parent.refinement = view;
|
|
1866
|
+
}
|
|
1867
|
+
}
|
|
1868
|
+
}
|
|
1869
|
+
|
|
1870
|
+
function isValidViewRefinement(
|
|
1871
|
+
view: ASTViewDefinition,
|
|
1872
|
+
name: string,
|
|
1873
|
+
path: string[] = []
|
|
1874
|
+
): {
|
|
1875
|
+
isValidViewRefinement: boolean;
|
|
1876
|
+
error?: string;
|
|
1877
|
+
} {
|
|
1878
|
+
const schema = view.getInputSchema();
|
|
1879
|
+
const field = ASTQuery.schemaGet(schema, name, path);
|
|
1880
|
+
if (field === undefined) {
|
|
1881
|
+
return {isValidViewRefinement: false, error: `${name} is not defined`};
|
|
1882
|
+
} else if (field.kind !== 'view') {
|
|
1883
|
+
// TODO scalar refinements
|
|
1884
|
+
return {isValidViewRefinement: false, error: `${name} is not a view`};
|
|
1885
|
+
}
|
|
1886
|
+
const prevOutput = view.getOutputSchema();
|
|
1887
|
+
for (const refinementField of field.schema.fields) {
|
|
1888
|
+
if (ASTQuery.schemaTryGet(prevOutput, refinementField.name, [])) {
|
|
1889
|
+
return {
|
|
1890
|
+
isValidViewRefinement: false,
|
|
1891
|
+
error: `Cannot refine with ${name} because stage already has an output field named ${refinementField.name}`,
|
|
1892
|
+
};
|
|
1893
|
+
}
|
|
1894
|
+
}
|
|
1895
|
+
return {isValidViewRefinement: true};
|
|
1896
|
+
}
|
|
1897
|
+
|
|
1898
|
+
export class ASTReferenceViewDefinition
|
|
1899
|
+
extends ASTObjectNode<
|
|
1900
|
+
Malloy.ViewDefinitionWithViewReference,
|
|
1901
|
+
{
|
|
1902
|
+
kind: 'view_reference';
|
|
1903
|
+
name: string;
|
|
1904
|
+
path?: string[];
|
|
1905
|
+
parameters?: ASTParameterValueList;
|
|
1906
|
+
}
|
|
1907
|
+
>
|
|
1908
|
+
implements IASTViewDefinition, IASTReference
|
|
1909
|
+
{
|
|
1910
|
+
constructor(public node: Malloy.ViewDefinitionWithViewReference) {
|
|
1911
|
+
super(node, {
|
|
1912
|
+
kind: 'view_reference',
|
|
1913
|
+
name: node.name,
|
|
1914
|
+
path: node.path,
|
|
1915
|
+
parameters: node.parameters && new ASTParameterValueList(node.parameters),
|
|
1916
|
+
});
|
|
1917
|
+
}
|
|
1918
|
+
|
|
1919
|
+
isRunnable(): boolean {
|
|
1920
|
+
return true;
|
|
1921
|
+
}
|
|
1922
|
+
|
|
1923
|
+
get name() {
|
|
1924
|
+
return this.children.name;
|
|
1925
|
+
}
|
|
1926
|
+
|
|
1927
|
+
get path() {
|
|
1928
|
+
return this.children.path;
|
|
1929
|
+
}
|
|
1930
|
+
|
|
1931
|
+
get parameters() {
|
|
1932
|
+
return this.children.parameters;
|
|
1933
|
+
}
|
|
1934
|
+
|
|
1935
|
+
set parameters(parameters: ASTParameterValueList | undefined) {
|
|
1936
|
+
this.edit();
|
|
1937
|
+
this.children.parameters = parameters;
|
|
1938
|
+
}
|
|
1939
|
+
|
|
1940
|
+
getOrAddDefaultSegment(): ASTSegmentViewDefinition {
|
|
1941
|
+
return this.addEmptyRefinement();
|
|
1942
|
+
}
|
|
1943
|
+
|
|
1944
|
+
addEmptyRefinement(): ASTSegmentViewDefinition {
|
|
1945
|
+
const newView = ASTRefinementViewDefinition.segmentRefinementOf(
|
|
1946
|
+
this.build()
|
|
1947
|
+
);
|
|
1948
|
+
swapViewInParent(this, newView);
|
|
1949
|
+
return newView.refinement.asSegmentViewDefinition();
|
|
1950
|
+
}
|
|
1951
|
+
|
|
1952
|
+
addViewRefinement(name: string, path?: string[]): ASTReferenceViewDefinition {
|
|
1953
|
+
const {error} = this.isValidViewRefinement(name, path);
|
|
1954
|
+
if (error) {
|
|
1955
|
+
throw new Error(error);
|
|
1956
|
+
}
|
|
1957
|
+
const newView = ASTRefinementViewDefinition.viewRefinementOf(
|
|
1958
|
+
this.build(),
|
|
1959
|
+
name,
|
|
1960
|
+
path
|
|
1961
|
+
);
|
|
1962
|
+
swapViewInParent(this, newView);
|
|
1963
|
+
return newView.refinement.asReferenceViewDefinition();
|
|
1964
|
+
}
|
|
1965
|
+
|
|
1966
|
+
isValidViewRefinement(
|
|
1967
|
+
name: string,
|
|
1968
|
+
path?: string[]
|
|
1969
|
+
): {
|
|
1970
|
+
isValidViewRefinement: boolean;
|
|
1971
|
+
error?: string;
|
|
1972
|
+
} {
|
|
1973
|
+
return isValidViewRefinement(this, name, path);
|
|
1974
|
+
}
|
|
1975
|
+
|
|
1976
|
+
getInputSchema(): Malloy.Schema {
|
|
1977
|
+
return getInputSchemaFromViewParent(this.parent as ViewParent);
|
|
1978
|
+
}
|
|
1979
|
+
|
|
1980
|
+
getOutputSchema(): Malloy.Schema {
|
|
1981
|
+
const parent = this.parent as ViewParent;
|
|
1982
|
+
return parent.getOutputSchema();
|
|
1983
|
+
}
|
|
1984
|
+
|
|
1985
|
+
getImplicitName(): string | undefined {
|
|
1986
|
+
return this.name;
|
|
1987
|
+
}
|
|
1988
|
+
|
|
1989
|
+
getViewInfo(): Malloy.FieldInfoWithView {
|
|
1990
|
+
const schema = this.getInputSchema();
|
|
1991
|
+
const view = ASTNode.schemaGet(schema, this.name, this.path);
|
|
1992
|
+
if (view.kind !== 'view') {
|
|
1993
|
+
throw new Error('Not a view');
|
|
1994
|
+
}
|
|
1995
|
+
return view;
|
|
1996
|
+
}
|
|
1997
|
+
|
|
1998
|
+
getRefinementSchema(): Malloy.Schema {
|
|
1999
|
+
const view = this.getViewInfo();
|
|
2000
|
+
return view.schema;
|
|
2001
|
+
}
|
|
2002
|
+
|
|
2003
|
+
/**
|
|
2004
|
+
* @internal
|
|
2005
|
+
*/
|
|
2006
|
+
propagateUp(f: PropagationFunction): void {
|
|
2007
|
+
(this.parent as ViewParent).propagateUp(f);
|
|
2008
|
+
}
|
|
2009
|
+
|
|
2010
|
+
/**
|
|
2011
|
+
* @internal
|
|
2012
|
+
*/
|
|
2013
|
+
propagateDown(_f: PropagationFunction): void {
|
|
2014
|
+
return;
|
|
2015
|
+
}
|
|
2016
|
+
|
|
2017
|
+
getInheritedAnnotations(): Malloy.Annotation[] {
|
|
2018
|
+
const view = this.getViewInfo();
|
|
2019
|
+
return view.annotations ?? [];
|
|
2020
|
+
}
|
|
2021
|
+
|
|
2022
|
+
public getOrAddParameters() {
|
|
2023
|
+
return ASTReference.getOrAddParameters(this);
|
|
2024
|
+
}
|
|
2025
|
+
|
|
2026
|
+
public setParameter(name: string, value: RawLiteralValue) {
|
|
2027
|
+
return ASTReference.setParameter(this, name, value);
|
|
2028
|
+
}
|
|
2029
|
+
|
|
2030
|
+
public tryGetParameter(name: string): ASTParameterValue | undefined {
|
|
2031
|
+
return ASTReference.tryGetParameter(this, name);
|
|
2032
|
+
}
|
|
2033
|
+
}
|
|
2034
|
+
|
|
2035
|
+
export class ASTArrowViewDefinition
|
|
2036
|
+
extends ASTObjectNode<
|
|
2037
|
+
Malloy.ViewDefinitionWithArrow,
|
|
2038
|
+
{
|
|
2039
|
+
kind: 'arrow';
|
|
2040
|
+
source: ASTViewDefinition;
|
|
2041
|
+
view: ASTViewDefinition;
|
|
2042
|
+
}
|
|
2043
|
+
>
|
|
2044
|
+
implements IASTViewDefinition
|
|
2045
|
+
{
|
|
2046
|
+
constructor(public node: Malloy.ViewDefinitionWithArrow) {
|
|
2047
|
+
super(node, {
|
|
2048
|
+
kind: 'arrow',
|
|
2049
|
+
source: ASTViewDefinition.from(node.source),
|
|
2050
|
+
view: ASTViewDefinition.from(node.view),
|
|
2051
|
+
});
|
|
2052
|
+
}
|
|
2053
|
+
|
|
2054
|
+
isRunnable(): boolean {
|
|
2055
|
+
return this.source.isRunnable() && this.view.isRunnable();
|
|
2056
|
+
}
|
|
2057
|
+
|
|
2058
|
+
get source() {
|
|
2059
|
+
return this.children.source;
|
|
2060
|
+
}
|
|
2061
|
+
|
|
2062
|
+
set source(source: ASTViewDefinition) {
|
|
2063
|
+
this.edit();
|
|
2064
|
+
this.children.source = source;
|
|
2065
|
+
}
|
|
2066
|
+
|
|
2067
|
+
get view() {
|
|
2068
|
+
return this.children.view;
|
|
2069
|
+
}
|
|
2070
|
+
|
|
2071
|
+
set view(view: ASTViewDefinition) {
|
|
2072
|
+
this.edit();
|
|
2073
|
+
this.children.view = view;
|
|
2074
|
+
view.parent = this;
|
|
2075
|
+
}
|
|
2076
|
+
|
|
2077
|
+
getOrAddDefaultSegment(): ASTSegmentViewDefinition {
|
|
2078
|
+
return this.view.getOrAddDefaultSegment();
|
|
2079
|
+
}
|
|
2080
|
+
|
|
2081
|
+
addEmptyRefinement(): ASTSegmentViewDefinition {
|
|
2082
|
+
return this.view.addEmptyRefinement();
|
|
2083
|
+
}
|
|
2084
|
+
|
|
2085
|
+
addViewRefinement(name: string, path?: string[]): ASTReferenceViewDefinition {
|
|
2086
|
+
return this.view.addViewRefinement(name, path);
|
|
2087
|
+
}
|
|
2088
|
+
|
|
2089
|
+
getInputSchema(): Malloy.Schema {
|
|
2090
|
+
return this.source.getOutputSchema();
|
|
2091
|
+
}
|
|
2092
|
+
|
|
2093
|
+
getOutputSchema(): Malloy.Schema {
|
|
2094
|
+
return this.view.getRefinementSchema();
|
|
2095
|
+
}
|
|
2096
|
+
|
|
2097
|
+
getImplicitName(): string | undefined {
|
|
2098
|
+
return this.view.getImplicitName();
|
|
2099
|
+
}
|
|
2100
|
+
|
|
2101
|
+
getRefinementSchema(): Malloy.Schema {
|
|
2102
|
+
throw new Error('An arrow is not a valid refinement');
|
|
2103
|
+
}
|
|
2104
|
+
|
|
2105
|
+
isValidViewRefinement(
|
|
2106
|
+
name: string,
|
|
2107
|
+
path?: string[]
|
|
2108
|
+
): {
|
|
2109
|
+
isValidViewRefinement: boolean;
|
|
2110
|
+
error?: string;
|
|
2111
|
+
} {
|
|
2112
|
+
return isValidViewRefinement(this, name, path);
|
|
2113
|
+
}
|
|
2114
|
+
|
|
2115
|
+
/**
|
|
2116
|
+
* @internal
|
|
2117
|
+
*/
|
|
2118
|
+
propagateUp(f: PropagationFunction): void {
|
|
2119
|
+
this.propagateDown(f);
|
|
2120
|
+
}
|
|
2121
|
+
|
|
2122
|
+
/**
|
|
2123
|
+
* @internal
|
|
2124
|
+
*/
|
|
2125
|
+
propagateDown(f: PropagationFunction): void {
|
|
2126
|
+
f(this.view);
|
|
2127
|
+
this.view.propagateDown(f);
|
|
2128
|
+
}
|
|
2129
|
+
|
|
2130
|
+
getInheritedAnnotations(): Malloy.Annotation[] {
|
|
2131
|
+
return [];
|
|
2132
|
+
}
|
|
2133
|
+
}
|
|
2134
|
+
|
|
2135
|
+
type ViewParent =
|
|
2136
|
+
| ASTArrowQueryDefinition
|
|
2137
|
+
| ASTRefinementQueryDefinition
|
|
2138
|
+
| ASTView
|
|
2139
|
+
| ASTArrowViewDefinition
|
|
2140
|
+
| ASTRefinementViewDefinition;
|
|
2141
|
+
|
|
2142
|
+
export class ASTRefinementViewDefinition
|
|
2143
|
+
extends ASTObjectNode<
|
|
2144
|
+
Malloy.ViewDefinitionWithRefinement,
|
|
2145
|
+
{
|
|
2146
|
+
kind: 'refinement';
|
|
2147
|
+
base: ASTViewDefinition;
|
|
2148
|
+
refinement: ASTViewDefinition;
|
|
2149
|
+
}
|
|
2150
|
+
>
|
|
2151
|
+
implements IASTViewDefinition
|
|
2152
|
+
{
|
|
2153
|
+
constructor(public node: Malloy.ViewDefinitionWithRefinement) {
|
|
2154
|
+
super(node, {
|
|
2155
|
+
kind: 'refinement',
|
|
2156
|
+
base: ASTViewDefinition.from(node.base),
|
|
2157
|
+
refinement: ASTViewDefinition.from(node.refinement),
|
|
2158
|
+
});
|
|
2159
|
+
}
|
|
2160
|
+
|
|
2161
|
+
isRunnable(): boolean {
|
|
2162
|
+
return this.base.isRunnable() && this.refinement.isRunnable();
|
|
2163
|
+
}
|
|
2164
|
+
|
|
2165
|
+
get refinement() {
|
|
2166
|
+
return this.children.refinement;
|
|
2167
|
+
}
|
|
2168
|
+
|
|
2169
|
+
set refinement(refinement: ASTViewDefinition) {
|
|
2170
|
+
this.edit();
|
|
2171
|
+
this.children.refinement = refinement;
|
|
2172
|
+
refinement.parent = this;
|
|
2173
|
+
}
|
|
2174
|
+
|
|
2175
|
+
get base() {
|
|
2176
|
+
return this.children.base;
|
|
2177
|
+
}
|
|
2178
|
+
|
|
2179
|
+
set base(base: ASTViewDefinition) {
|
|
2180
|
+
this.edit();
|
|
2181
|
+
this.children.base = base;
|
|
2182
|
+
}
|
|
2183
|
+
|
|
2184
|
+
getOrAddDefaultSegment(): ASTSegmentViewDefinition {
|
|
2185
|
+
return this.refinement.getOrAddDefaultSegment();
|
|
2186
|
+
}
|
|
2187
|
+
|
|
2188
|
+
addEmptyRefinement(): ASTSegmentViewDefinition {
|
|
2189
|
+
return this.refinement.addEmptyRefinement();
|
|
2190
|
+
}
|
|
2191
|
+
|
|
2192
|
+
addViewRefinement(name: string, path?: string[]): ASTReferenceViewDefinition {
|
|
2193
|
+
return this.refinement.addViewRefinement(name, path);
|
|
2194
|
+
}
|
|
2195
|
+
|
|
2196
|
+
getInputSchema(): Malloy.Schema {
|
|
2197
|
+
return getInputSchemaFromViewParent(this.parent as ViewParent);
|
|
2198
|
+
}
|
|
2199
|
+
|
|
2200
|
+
getOutputSchema(): Malloy.Schema {
|
|
2201
|
+
const parent = this.parent as ViewParent;
|
|
2202
|
+
return parent.getOutputSchema();
|
|
2203
|
+
}
|
|
2204
|
+
|
|
2205
|
+
getRefinementSchema(): Malloy.Schema {
|
|
2206
|
+
return ASTNode.schemaMerge(
|
|
2207
|
+
this.base.getRefinementSchema(),
|
|
2208
|
+
this.refinement.getRefinementSchema()
|
|
2209
|
+
);
|
|
2210
|
+
}
|
|
2211
|
+
|
|
2212
|
+
getImplicitName(): string | undefined {
|
|
2213
|
+
return this.base.getImplicitName();
|
|
2214
|
+
}
|
|
2215
|
+
|
|
2216
|
+
isValidViewRefinement(
|
|
2217
|
+
name: string,
|
|
2218
|
+
path?: string[]
|
|
2219
|
+
): {
|
|
2220
|
+
isValidViewRefinement: boolean;
|
|
2221
|
+
error?: string;
|
|
2222
|
+
} {
|
|
2223
|
+
return isValidViewRefinement(this, name, path);
|
|
2224
|
+
}
|
|
2225
|
+
|
|
2226
|
+
/**
|
|
2227
|
+
* @internal
|
|
2228
|
+
*/
|
|
2229
|
+
propagateUp(f: PropagationFunction): void {
|
|
2230
|
+
(this.parent as ViewParent).propagateUp(f);
|
|
2231
|
+
}
|
|
2232
|
+
|
|
2233
|
+
/**
|
|
2234
|
+
* @internal
|
|
2235
|
+
*/
|
|
2236
|
+
propagateDown(f: PropagationFunction): void {
|
|
2237
|
+
f(this.base);
|
|
2238
|
+
f(this.refinement);
|
|
2239
|
+
this.base.propagateDown(f);
|
|
2240
|
+
this.refinement.propagateDown(f);
|
|
2241
|
+
}
|
|
2242
|
+
|
|
2243
|
+
getInheritedAnnotations(): Malloy.Annotation[] {
|
|
2244
|
+
return this.base.getInheritedAnnotations();
|
|
2245
|
+
}
|
|
2246
|
+
|
|
2247
|
+
/**
|
|
2248
|
+
* @internal
|
|
2249
|
+
*/
|
|
2250
|
+
static viewRefinementOf(
|
|
2251
|
+
view: Malloy.ViewDefinition,
|
|
2252
|
+
name: string,
|
|
2253
|
+
path?: string[]
|
|
2254
|
+
) {
|
|
2255
|
+
return new ASTRefinementViewDefinition({
|
|
2256
|
+
kind: 'refinement',
|
|
2257
|
+
base: view,
|
|
2258
|
+
refinement: {
|
|
2259
|
+
kind: 'view_reference',
|
|
2260
|
+
name,
|
|
2261
|
+
path,
|
|
2262
|
+
},
|
|
2263
|
+
});
|
|
2264
|
+
}
|
|
2265
|
+
|
|
2266
|
+
/**
|
|
2267
|
+
* @internal
|
|
2268
|
+
*/
|
|
2269
|
+
static segmentRefinementOf(view: Malloy.ViewDefinition) {
|
|
2270
|
+
return new ASTRefinementViewDefinition({
|
|
2271
|
+
kind: 'refinement',
|
|
2272
|
+
base: view,
|
|
2273
|
+
refinement: {
|
|
2274
|
+
kind: 'segment',
|
|
2275
|
+
operations: [],
|
|
2276
|
+
},
|
|
2277
|
+
});
|
|
2278
|
+
}
|
|
2279
|
+
}
|
|
2280
|
+
|
|
2281
|
+
export class ASTSegmentViewDefinition
|
|
2282
|
+
extends ASTObjectNode<
|
|
2283
|
+
Malloy.ViewDefinitionWithSegment,
|
|
2284
|
+
{
|
|
2285
|
+
kind: 'segment';
|
|
2286
|
+
operations: ASTViewOperationList;
|
|
2287
|
+
}
|
|
2288
|
+
>
|
|
2289
|
+
implements IASTViewDefinition
|
|
2290
|
+
{
|
|
2291
|
+
constructor(public node: Malloy.ViewDefinitionWithSegment) {
|
|
2292
|
+
super(node, {
|
|
2293
|
+
kind: 'segment',
|
|
2294
|
+
operations: new ASTViewOperationList(node.operations),
|
|
2295
|
+
});
|
|
2296
|
+
}
|
|
2297
|
+
|
|
2298
|
+
isRunnable(): boolean {
|
|
2299
|
+
for (const operation of this.operations.iter()) {
|
|
2300
|
+
if (
|
|
2301
|
+
operation instanceof ASTAggregateViewOperation ||
|
|
2302
|
+
operation instanceof ASTGroupByViewOperation ||
|
|
2303
|
+
operation instanceof ASTNestViewOperation
|
|
2304
|
+
) {
|
|
2305
|
+
return true;
|
|
2306
|
+
}
|
|
2307
|
+
}
|
|
2308
|
+
return false;
|
|
2309
|
+
}
|
|
2310
|
+
|
|
2311
|
+
get operations() {
|
|
2312
|
+
return this.children.operations;
|
|
2313
|
+
}
|
|
2314
|
+
|
|
2315
|
+
/**
|
|
2316
|
+
* @internal
|
|
2317
|
+
*/
|
|
2318
|
+
renameOrderBys(oldName: string, newName: string) {
|
|
2319
|
+
for (const operation of this.operations.iter()) {
|
|
2320
|
+
if (operation instanceof ASTOrderByViewOperation) {
|
|
2321
|
+
if (operation.name === oldName) {
|
|
2322
|
+
operation.setField(newName);
|
|
2323
|
+
}
|
|
2324
|
+
}
|
|
2325
|
+
}
|
|
2326
|
+
}
|
|
2327
|
+
|
|
2328
|
+
/**
|
|
2329
|
+
* @internal
|
|
2330
|
+
*/
|
|
2331
|
+
propagateUp(f: PropagationFunction): void {
|
|
2332
|
+
(this.parent as ViewParent).propagateUp(f);
|
|
2333
|
+
}
|
|
2334
|
+
|
|
2335
|
+
/**
|
|
2336
|
+
* @internal
|
|
2337
|
+
*/
|
|
2338
|
+
propagateDown(_f: PropagationFunction): void {
|
|
2339
|
+
return;
|
|
2340
|
+
}
|
|
2341
|
+
|
|
2342
|
+
/**
|
|
2343
|
+
* @internal
|
|
2344
|
+
*/
|
|
2345
|
+
removeOrderBys(name: string): void {
|
|
2346
|
+
for (const operation of this.operations.iter()) {
|
|
2347
|
+
if (operation instanceof ASTOrderByViewOperation) {
|
|
2348
|
+
if (operation.name === name) {
|
|
2349
|
+
operation.delete();
|
|
2350
|
+
}
|
|
2351
|
+
}
|
|
2352
|
+
}
|
|
2353
|
+
}
|
|
2354
|
+
|
|
2355
|
+
reorderFields(names: string[]): void {
|
|
2356
|
+
const leadingOperations: ASTViewOperation[] = [];
|
|
2357
|
+
const trailingOperations: ASTViewOperation[] = [];
|
|
2358
|
+
const opsByName: {
|
|
2359
|
+
[name: string]:
|
|
2360
|
+
| ASTAggregateViewOperation
|
|
2361
|
+
| ASTGroupByViewOperation
|
|
2362
|
+
| ASTNestViewOperation;
|
|
2363
|
+
} = {};
|
|
2364
|
+
let seenAnyNamed = false;
|
|
2365
|
+
for (const operation of this.operations.iter()) {
|
|
2366
|
+
if (
|
|
2367
|
+
operation instanceof ASTAggregateViewOperation ||
|
|
2368
|
+
operation instanceof ASTGroupByViewOperation ||
|
|
2369
|
+
operation instanceof ASTNestViewOperation
|
|
2370
|
+
) {
|
|
2371
|
+
if (names.includes(operation.name)) {
|
|
2372
|
+
opsByName[operation.name] = operation;
|
|
2373
|
+
seenAnyNamed = true;
|
|
2374
|
+
continue;
|
|
2375
|
+
}
|
|
2376
|
+
}
|
|
2377
|
+
if (seenAnyNamed) {
|
|
2378
|
+
trailingOperations.push(operation);
|
|
2379
|
+
} else {
|
|
2380
|
+
leadingOperations.push(operation);
|
|
2381
|
+
}
|
|
2382
|
+
}
|
|
2383
|
+
const middleOperations: ASTViewOperation[] = [];
|
|
2384
|
+
for (const name of names) {
|
|
2385
|
+
const operation = opsByName[name];
|
|
2386
|
+
if (operation === undefined) {
|
|
2387
|
+
throw new Error(`No field named ${name}`);
|
|
2388
|
+
}
|
|
2389
|
+
middleOperations.push(operation);
|
|
2390
|
+
}
|
|
2391
|
+
const operations = [
|
|
2392
|
+
...leadingOperations,
|
|
2393
|
+
...middleOperations,
|
|
2394
|
+
...trailingOperations,
|
|
2395
|
+
];
|
|
2396
|
+
this.operations.items = operations;
|
|
2397
|
+
}
|
|
2398
|
+
|
|
2399
|
+
public renameField(
|
|
2400
|
+
field:
|
|
2401
|
+
| ASTAggregateViewOperation
|
|
2402
|
+
| ASTGroupByViewOperation
|
|
2403
|
+
| ASTNestViewOperation,
|
|
2404
|
+
name: string
|
|
2405
|
+
) {
|
|
2406
|
+
if (field.name === name) return;
|
|
2407
|
+
const output = this.getOutputSchema();
|
|
2408
|
+
if (ASTNode.schemaTryGet(output, name, [])) {
|
|
2409
|
+
throw new Error(`Output already has a field named ${name}`);
|
|
2410
|
+
}
|
|
2411
|
+
const oldName = field.name;
|
|
2412
|
+
field.name = name;
|
|
2413
|
+
this.propagateUp(v => {
|
|
2414
|
+
if (v instanceof ASTSegmentViewDefinition) {
|
|
2415
|
+
v.renameOrderBys(oldName, name);
|
|
2416
|
+
}
|
|
2417
|
+
});
|
|
2418
|
+
}
|
|
2419
|
+
|
|
2420
|
+
/**
|
|
2421
|
+
* Adds an order by to the segment. Will override the direction of an existing order by
|
|
2422
|
+
* if one is present for the same field.
|
|
2423
|
+
*
|
|
2424
|
+
* ```
|
|
2425
|
+
* run: flights -> { group_by: carrier }
|
|
2426
|
+
* ```
|
|
2427
|
+
* ```ts
|
|
2428
|
+
* q.getOrAddDefaultSegment().addOrderBy("carrier", Malloy.OrderByDirection.DESC);
|
|
2429
|
+
* ```
|
|
2430
|
+
* ```
|
|
2431
|
+
* run: flights -> {
|
|
2432
|
+
* group_by: carrier
|
|
2433
|
+
* order_by: carrier desc
|
|
2434
|
+
* }
|
|
2435
|
+
* ```
|
|
2436
|
+
*
|
|
2437
|
+
* The order by item is added after an existing order by operation if one is present,
|
|
2438
|
+
* or to a new order by operation at the end of the query.
|
|
2439
|
+
*
|
|
2440
|
+
* @param name The name of the field to order by.
|
|
2441
|
+
* @param direction The order by direction (ascending or descending).
|
|
2442
|
+
*/
|
|
2443
|
+
public addOrderBy(name: string, direction?: Malloy.OrderByDirection) {
|
|
2444
|
+
// Ensure output schema has a field with this name
|
|
2445
|
+
const outputSchema = this.getOutputSchema();
|
|
2446
|
+
try {
|
|
2447
|
+
ASTNode.schemaGet(outputSchema, name, []);
|
|
2448
|
+
} catch {
|
|
2449
|
+
throw new Error(`No such field ${name} in stage output`);
|
|
2450
|
+
}
|
|
2451
|
+
// first see if there is already an order by for this field
|
|
2452
|
+
for (const operation of this.operations.iter()) {
|
|
2453
|
+
if (operation instanceof ASTOrderByViewOperation) {
|
|
2454
|
+
if (operation.name === name) {
|
|
2455
|
+
operation.direction = direction;
|
|
2456
|
+
return;
|
|
2457
|
+
}
|
|
2458
|
+
}
|
|
2459
|
+
}
|
|
2460
|
+
// add a new order by operation
|
|
2461
|
+
this.addOperation(
|
|
2462
|
+
new ASTOrderByViewOperation({
|
|
2463
|
+
kind: 'order_by',
|
|
2464
|
+
field_reference: {name},
|
|
2465
|
+
direction,
|
|
2466
|
+
})
|
|
2467
|
+
);
|
|
2468
|
+
}
|
|
2469
|
+
|
|
2470
|
+
/**
|
|
2471
|
+
* Adds an empty nest to this segment, with the given name.
|
|
2472
|
+
* @param name The name of the new nest.
|
|
2473
|
+
*
|
|
2474
|
+
* The new nest is always added to the end of the query in a new nest block.
|
|
2475
|
+
*
|
|
2476
|
+
* ```
|
|
2477
|
+
* run: flights -> { group_by: carrier }
|
|
2478
|
+
* ```
|
|
2479
|
+
* ```ts
|
|
2480
|
+
* q.getOrAddDefaultSegment().addEmptyNest("by_origin");
|
|
2481
|
+
* ```
|
|
2482
|
+
* ```
|
|
2483
|
+
* run: flights -> {
|
|
2484
|
+
* group_by: carrier
|
|
2485
|
+
* nest: by_origin is { }
|
|
2486
|
+
* }
|
|
2487
|
+
* ```
|
|
2488
|
+
*
|
|
2489
|
+
* @returns The {@link ASTNestViewOperation} that was created.
|
|
2490
|
+
*
|
|
2491
|
+
*/
|
|
2492
|
+
public addEmptyNest(name: string): ASTNestViewOperation {
|
|
2493
|
+
const nest = new ASTNestViewOperation({
|
|
2494
|
+
kind: 'nest',
|
|
2495
|
+
name,
|
|
2496
|
+
view: {
|
|
2497
|
+
definition: {
|
|
2498
|
+
kind: 'segment',
|
|
2499
|
+
operations: [],
|
|
2500
|
+
},
|
|
2501
|
+
},
|
|
2502
|
+
});
|
|
2503
|
+
this.addOperation(nest);
|
|
2504
|
+
return nest;
|
|
2505
|
+
}
|
|
2506
|
+
|
|
2507
|
+
private firstIndexOfOperationType(type: Malloy.ViewOperationType) {
|
|
2508
|
+
return this.operations.findIndex(o => o.kind === type);
|
|
2509
|
+
}
|
|
2510
|
+
|
|
2511
|
+
private DEFAULT_INSERTION_ORDER: Malloy.ViewOperationType[] = [
|
|
2512
|
+
'where',
|
|
2513
|
+
'group_by',
|
|
2514
|
+
'aggregate',
|
|
2515
|
+
'nest',
|
|
2516
|
+
'order_by',
|
|
2517
|
+
];
|
|
2518
|
+
|
|
2519
|
+
private findInsertionPoint(kind: Malloy.ViewOperationType): number {
|
|
2520
|
+
const firstOfType = this.firstIndexOfOperationType(kind);
|
|
2521
|
+
if (firstOfType > -1) {
|
|
2522
|
+
let i = firstOfType;
|
|
2523
|
+
while (this.operations.index(i) && this.operations.index(i).kind === kind)
|
|
2524
|
+
i++;
|
|
2525
|
+
return i;
|
|
2526
|
+
}
|
|
2527
|
+
const indexInOrder = this.DEFAULT_INSERTION_ORDER.indexOf(kind);
|
|
2528
|
+
if (indexInOrder === -1) {
|
|
2529
|
+
throw new Error(
|
|
2530
|
+
`Operation ${kind} is not supported for \`findInsertionPoint\``
|
|
2531
|
+
);
|
|
2532
|
+
}
|
|
2533
|
+
const laterOperations = this.DEFAULT_INSERTION_ORDER.slice(
|
|
2534
|
+
indexInOrder + 1
|
|
2535
|
+
);
|
|
2536
|
+
for (const laterType of laterOperations) {
|
|
2537
|
+
const firstOfType = this.firstIndexOfOperationType(laterType);
|
|
2538
|
+
return firstOfType;
|
|
2539
|
+
}
|
|
2540
|
+
return this.operations.length;
|
|
2541
|
+
}
|
|
2542
|
+
|
|
2543
|
+
public getFieldNamed(
|
|
2544
|
+
name: string
|
|
2545
|
+
):
|
|
2546
|
+
| ASTGroupByViewOperation
|
|
2547
|
+
| ASTAggregateViewOperation
|
|
2548
|
+
| ASTNestViewOperation
|
|
2549
|
+
| undefined {
|
|
2550
|
+
const field = this.tryGetFieldNamed(name);
|
|
2551
|
+
if (field === undefined) throw new Error('No such field');
|
|
2552
|
+
return field;
|
|
2553
|
+
}
|
|
2554
|
+
|
|
2555
|
+
public hasFieldNamed(name: string): boolean {
|
|
2556
|
+
return this.tryGetFieldNamed(name) !== undefined;
|
|
2557
|
+
}
|
|
2558
|
+
|
|
2559
|
+
public hasField(name: string, path?: string[]): boolean {
|
|
2560
|
+
return this.tryGetField(name, path) !== undefined;
|
|
2561
|
+
}
|
|
2562
|
+
|
|
2563
|
+
public getField(
|
|
2564
|
+
name: string,
|
|
2565
|
+
path?: string[]
|
|
2566
|
+
):
|
|
2567
|
+
| ASTGroupByViewOperation
|
|
2568
|
+
| ASTAggregateViewOperation
|
|
2569
|
+
| ASTNestViewOperation {
|
|
2570
|
+
const field = this.tryGetField(name, path);
|
|
2571
|
+
if (field === undefined) {
|
|
2572
|
+
throw new Error('No such field');
|
|
2573
|
+
}
|
|
2574
|
+
return field;
|
|
2575
|
+
}
|
|
2576
|
+
|
|
2577
|
+
// TODO what constitutes "having a field" -- does "dep_time.month" count as dep_time?
|
|
2578
|
+
// does flight_count {where: carrier = 'CA' } count as flight_count?
|
|
2579
|
+
public tryGetField(
|
|
2580
|
+
name: string,
|
|
2581
|
+
path?: string[]
|
|
2582
|
+
):
|
|
2583
|
+
| ASTGroupByViewOperation
|
|
2584
|
+
| ASTAggregateViewOperation
|
|
2585
|
+
| ASTNestViewOperation
|
|
2586
|
+
| undefined {
|
|
2587
|
+
for (const operation of this.operations.iter()) {
|
|
2588
|
+
if (
|
|
2589
|
+
operation instanceof ASTGroupByViewOperation ||
|
|
2590
|
+
operation instanceof ASTAggregateViewOperation
|
|
2591
|
+
) {
|
|
2592
|
+
const reference = operation.field.getReference();
|
|
2593
|
+
if (reference.name === name && pathsMatch(reference.path, path)) {
|
|
2594
|
+
return operation;
|
|
2595
|
+
}
|
|
2596
|
+
} else if (operation instanceof ASTNestViewOperation) {
|
|
2597
|
+
if (operation.view instanceof ASTReferenceViewDefinition) {
|
|
2598
|
+
return operation;
|
|
2599
|
+
}
|
|
2600
|
+
}
|
|
2601
|
+
}
|
|
2602
|
+
return undefined;
|
|
2603
|
+
}
|
|
2604
|
+
|
|
2605
|
+
public tryGetFieldNamed(
|
|
2606
|
+
name: string
|
|
2607
|
+
):
|
|
2608
|
+
| ASTGroupByViewOperation
|
|
2609
|
+
| ASTAggregateViewOperation
|
|
2610
|
+
| ASTNestViewOperation
|
|
2611
|
+
| undefined {
|
|
2612
|
+
for (const operation of this.operations.iter()) {
|
|
2613
|
+
if (
|
|
2614
|
+
operation instanceof ASTGroupByViewOperation ||
|
|
2615
|
+
operation instanceof ASTAggregateViewOperation ||
|
|
2616
|
+
operation instanceof ASTNestViewOperation
|
|
2617
|
+
) {
|
|
2618
|
+
if (operation.name === name) {
|
|
2619
|
+
return operation;
|
|
2620
|
+
}
|
|
2621
|
+
}
|
|
2622
|
+
}
|
|
2623
|
+
return undefined;
|
|
2624
|
+
}
|
|
2625
|
+
|
|
2626
|
+
public getGroupBy(name: string) {
|
|
2627
|
+
for (const operation of this.operations.iter()) {
|
|
2628
|
+
if (operation instanceof ASTGroupByViewOperation) {
|
|
2629
|
+
if (operation.name === name) {
|
|
2630
|
+
return operation;
|
|
2631
|
+
}
|
|
2632
|
+
}
|
|
2633
|
+
}
|
|
2634
|
+
}
|
|
2635
|
+
|
|
2636
|
+
public removeGroupBy(name: string) {
|
|
2637
|
+
this.getGroupBy(name)?.delete();
|
|
2638
|
+
return this;
|
|
2639
|
+
}
|
|
2640
|
+
|
|
2641
|
+
/**
|
|
2642
|
+
* Adds a group by field with the given name to this segment.
|
|
2643
|
+
*
|
|
2644
|
+
* ```
|
|
2645
|
+
* run: flights -> { }
|
|
2646
|
+
* ```
|
|
2647
|
+
* ```ts
|
|
2648
|
+
* q.getOrAddDefaultSegment().addGroupBy("carrier");
|
|
2649
|
+
* ```
|
|
2650
|
+
* ```
|
|
2651
|
+
* run: flights -> { group_by: carrier }
|
|
2652
|
+
* ```
|
|
2653
|
+
*
|
|
2654
|
+
* If there is already a group by clause, the new field will be added
|
|
2655
|
+
* to that clause (or the first one if there are multiple).
|
|
2656
|
+
*
|
|
2657
|
+
* ```
|
|
2658
|
+
* run: flights -> { group_by: carrier }
|
|
2659
|
+
* ```
|
|
2660
|
+
* ```ts
|
|
2661
|
+
* q.getOrAddDefaultSegment().addGroupBy("origin_code");
|
|
2662
|
+
* ```
|
|
2663
|
+
* ```
|
|
2664
|
+
* run: flights -> {
|
|
2665
|
+
* group_by:
|
|
2666
|
+
* carrier
|
|
2667
|
+
* origin_code
|
|
2668
|
+
* }
|
|
2669
|
+
* ```
|
|
2670
|
+
*
|
|
2671
|
+
* If there is no group by clause, it will be added
|
|
2672
|
+
* 1) before the first aggregate clause if there is one, or
|
|
2673
|
+
* 2) before the first nest clause if there is one, or
|
|
2674
|
+
* 3) before the first order by clause if ther is one, or
|
|
2675
|
+
* 4) at the end of the query
|
|
2676
|
+
*
|
|
2677
|
+
* ```
|
|
2678
|
+
* run: flights -> {
|
|
2679
|
+
* order_by: flight_count
|
|
2680
|
+
* aggregate: flight_count
|
|
2681
|
+
* }
|
|
2682
|
+
* ```
|
|
2683
|
+
* ```ts
|
|
2684
|
+
* q.getOrAddDefaultSegment().addGroupBy("carrier");
|
|
2685
|
+
* ```
|
|
2686
|
+
* ```
|
|
2687
|
+
* run: flights -> {
|
|
2688
|
+
* order_by: flight_count
|
|
2689
|
+
* group_by: carrier
|
|
2690
|
+
* aggregate: flight_count
|
|
2691
|
+
* }
|
|
2692
|
+
* ```
|
|
2693
|
+
*
|
|
2694
|
+
* @param name The name of the dimension to group by.
|
|
2695
|
+
* @param path Join path for this dimension.
|
|
2696
|
+
*/
|
|
2697
|
+
public addGroupBy(name: string, path: string[] = [], rename?: string) {
|
|
2698
|
+
const item = this.makeField(name, path, rename, 'dimension');
|
|
2699
|
+
this.addOperation(item);
|
|
2700
|
+
return item;
|
|
2701
|
+
}
|
|
2702
|
+
|
|
2703
|
+
public addWhere(name: string, filter: ParsedFilter): ASTWhereViewOperation;
|
|
2704
|
+
public addWhere(name: string, filterString: string): ASTWhereViewOperation;
|
|
2705
|
+
public addWhere(
|
|
2706
|
+
name: string,
|
|
2707
|
+
path: string[],
|
|
2708
|
+
filter: ParsedFilter
|
|
2709
|
+
): ASTWhereViewOperation;
|
|
2710
|
+
public addWhere(
|
|
2711
|
+
name: string,
|
|
2712
|
+
path: string[],
|
|
2713
|
+
filterString: string
|
|
2714
|
+
): ASTWhereViewOperation;
|
|
2715
|
+
public addWhere(
|
|
2716
|
+
name: string,
|
|
2717
|
+
arg2: string[] | string | ParsedFilter,
|
|
2718
|
+
arg3?: string | ParsedFilter
|
|
2719
|
+
): ASTWhereViewOperation {
|
|
2720
|
+
const path = Array.isArray(arg2) ? arg2 : [];
|
|
2721
|
+
const filter = arg3 === undefined ? (arg2 as string | ParsedFilter) : arg3;
|
|
2722
|
+
const filterString =
|
|
2723
|
+
typeof filter === 'string' ? filter : serializeFilter(filter);
|
|
2724
|
+
const schema = this.getInputSchema();
|
|
2725
|
+
// Validate name
|
|
2726
|
+
const field = ASTQuery.schemaGet(schema, name, path);
|
|
2727
|
+
// Validate filter
|
|
2728
|
+
validateFilter(field, filter);
|
|
2729
|
+
const item = new ASTWhereViewOperation({
|
|
2730
|
+
kind: 'where',
|
|
2731
|
+
filter: {
|
|
2732
|
+
kind: 'filter_string',
|
|
2733
|
+
field_reference: {name},
|
|
2734
|
+
filter: filterString,
|
|
2735
|
+
},
|
|
2736
|
+
});
|
|
2737
|
+
this.addOperation(item);
|
|
2738
|
+
return item;
|
|
2739
|
+
}
|
|
2740
|
+
|
|
2741
|
+
private addTimeGroupBy(
|
|
2742
|
+
name: string,
|
|
2743
|
+
path: string[],
|
|
2744
|
+
timeframe: Malloy.TimestampTimeframe,
|
|
2745
|
+
type: 'date_type' | 'timestamp_type'
|
|
2746
|
+
): ASTGroupByViewOperation {
|
|
2747
|
+
const schema = this.getInputSchema();
|
|
2748
|
+
const fieldInfo = ASTNode.schemaGet(schema, name, path);
|
|
2749
|
+
if (fieldInfo === undefined) {
|
|
2750
|
+
throw new Error(`No such field ${name}`);
|
|
2751
|
+
}
|
|
2752
|
+
if (fieldInfo.kind !== 'dimension') {
|
|
2753
|
+
throw new Error(`Cannot group by non-dimension ${name}`);
|
|
2754
|
+
}
|
|
2755
|
+
if (fieldInfo.type.kind !== type) {
|
|
2756
|
+
throw new Error(`${name} is not a ${type}`);
|
|
2757
|
+
}
|
|
2758
|
+
const item = new ASTGroupByViewOperation({
|
|
2759
|
+
kind: 'group_by',
|
|
2760
|
+
field: {
|
|
2761
|
+
expression: {
|
|
2762
|
+
kind: 'time_truncation',
|
|
2763
|
+
field_reference: {name, path},
|
|
2764
|
+
truncation: timeframe,
|
|
2765
|
+
},
|
|
2766
|
+
},
|
|
2767
|
+
});
|
|
2768
|
+
this.addOperation(item);
|
|
2769
|
+
return item;
|
|
2770
|
+
}
|
|
2771
|
+
|
|
2772
|
+
public addDateGroupBy(
|
|
2773
|
+
name: string,
|
|
2774
|
+
path: string[],
|
|
2775
|
+
timeframe: Malloy.DateTimeframe
|
|
2776
|
+
): ASTGroupByViewOperation;
|
|
2777
|
+
public addDateGroupBy(
|
|
2778
|
+
name: string,
|
|
2779
|
+
timeframe: Malloy.DateTimeframe
|
|
2780
|
+
): ASTGroupByViewOperation;
|
|
2781
|
+
public addDateGroupBy(
|
|
2782
|
+
name: string,
|
|
2783
|
+
arg2: string[] | Malloy.DateTimeframe,
|
|
2784
|
+
arg3?: Malloy.DateTimeframe
|
|
2785
|
+
): ASTGroupByViewOperation {
|
|
2786
|
+
const timeframe =
|
|
2787
|
+
arg3 === undefined ? (arg2 as Malloy.DateTimeframe) : arg3;
|
|
2788
|
+
const path = arg3 === undefined ? [] : (arg2 as string[]);
|
|
2789
|
+
return this.addTimeGroupBy(name, path, timeframe, 'date_type');
|
|
2790
|
+
}
|
|
2791
|
+
|
|
2792
|
+
public addTimestampGroupBy(
|
|
2793
|
+
name: string,
|
|
2794
|
+
path: string[],
|
|
2795
|
+
timeframe: Malloy.TimestampTimeframe
|
|
2796
|
+
): ASTGroupByViewOperation;
|
|
2797
|
+
public addTimestampGroupBy(
|
|
2798
|
+
name: string,
|
|
2799
|
+
timeframe: Malloy.TimestampTimeframe
|
|
2800
|
+
): ASTGroupByViewOperation;
|
|
2801
|
+
public addTimestampGroupBy(
|
|
2802
|
+
name: string,
|
|
2803
|
+
arg2: string[] | Malloy.TimestampTimeframe,
|
|
2804
|
+
arg3?: Malloy.TimestampTimeframe
|
|
2805
|
+
): ASTGroupByViewOperation {
|
|
2806
|
+
const timeframe =
|
|
2807
|
+
arg3 === undefined ? (arg2 as Malloy.TimestampTimeframe) : arg3;
|
|
2808
|
+
const path = arg3 === undefined ? [] : (arg2 as string[]);
|
|
2809
|
+
return this.addTimeGroupBy(name, path, timeframe, 'timestamp_type');
|
|
2810
|
+
}
|
|
2811
|
+
|
|
2812
|
+
/**
|
|
2813
|
+
* Adds an aggregate item with the given name to this segment.
|
|
2814
|
+
*
|
|
2815
|
+
* ```
|
|
2816
|
+
* run: flights -> { }
|
|
2817
|
+
* ```
|
|
2818
|
+
* ```ts
|
|
2819
|
+
* q.getOrAddDefaultSegment().addAggregate("flight_count");
|
|
2820
|
+
* ```
|
|
2821
|
+
* ```
|
|
2822
|
+
* run: flights -> { aggregate: flight_count }
|
|
2823
|
+
* ```
|
|
2824
|
+
*
|
|
2825
|
+
* Added
|
|
2826
|
+
* 1) at the end of an existing aggregate clause if ther is one, or
|
|
2827
|
+
* 2) before the first nest clause if there is one, or
|
|
2828
|
+
* 3) before the first order by clause if ther is one, or
|
|
2829
|
+
* 4) at the end of the query
|
|
2830
|
+
*
|
|
2831
|
+
* @param name The name of the measure to aggregate.
|
|
2832
|
+
* @param path The join path of the measure to aggregate.
|
|
2833
|
+
* @param rename A new name for this measure
|
|
2834
|
+
*/
|
|
2835
|
+
public addAggregate(name: string, path: string[] = [], rename?: string) {
|
|
2836
|
+
const item = this.makeField(name, path, rename, 'measure');
|
|
2837
|
+
this.addOperation(item);
|
|
2838
|
+
return item;
|
|
2839
|
+
}
|
|
2840
|
+
|
|
2841
|
+
/**
|
|
2842
|
+
* Adds a nest item with the given name to this segment.
|
|
2843
|
+
*
|
|
2844
|
+
* ```
|
|
2845
|
+
* run: flights -> { }
|
|
2846
|
+
* ```
|
|
2847
|
+
* ```ts
|
|
2848
|
+
* q.getOrAddDefaultSegment().addNest("by_carrier");
|
|
2849
|
+
* ```
|
|
2850
|
+
* ```
|
|
2851
|
+
* run: flights -> { nest: by_carrier }
|
|
2852
|
+
* ```
|
|
2853
|
+
*
|
|
2854
|
+
* Added
|
|
2855
|
+
* 1) at the end of an existing nest clause if there is one, or
|
|
2856
|
+
* 2) before the first order by clause if ther is one, or
|
|
2857
|
+
* 3) at the end of the query
|
|
2858
|
+
*
|
|
2859
|
+
* @param name The name of the view to nest.
|
|
2860
|
+
* @param rename A new name for this view in the query
|
|
2861
|
+
*/
|
|
2862
|
+
public addNest(name: string, rename?: string) {
|
|
2863
|
+
const item = this.makeField(name, [], rename, 'view');
|
|
2864
|
+
this.addOperation(item);
|
|
2865
|
+
return item;
|
|
2866
|
+
}
|
|
2867
|
+
|
|
2868
|
+
private makeField(
|
|
2869
|
+
name: string,
|
|
2870
|
+
path: string[],
|
|
2871
|
+
rename: string | undefined,
|
|
2872
|
+
type: 'dimension'
|
|
2873
|
+
): ASTGroupByViewOperation;
|
|
2874
|
+
private makeField(
|
|
2875
|
+
name: string,
|
|
2876
|
+
path: string[],
|
|
2877
|
+
rename: string | undefined,
|
|
2878
|
+
type: 'measure'
|
|
2879
|
+
): ASTAggregateViewOperation;
|
|
2880
|
+
private makeField(
|
|
2881
|
+
name: string,
|
|
2882
|
+
path: string[],
|
|
2883
|
+
rename: string | undefined,
|
|
2884
|
+
type: 'view'
|
|
2885
|
+
): ASTNestViewOperation;
|
|
2886
|
+
private makeField(
|
|
2887
|
+
name: string,
|
|
2888
|
+
path: string[],
|
|
2889
|
+
rename: string | undefined,
|
|
2890
|
+
type: 'dimension' | 'measure' | 'view'
|
|
2891
|
+
) {
|
|
2892
|
+
const schema = this.getInputSchema();
|
|
2893
|
+
const fieldInfo = ASTNode.schemaGet(schema, name, path);
|
|
2894
|
+
if (fieldInfo === undefined) {
|
|
2895
|
+
throw new Error(`No such field ${name}`);
|
|
2896
|
+
}
|
|
2897
|
+
if (fieldInfo.kind !== type) {
|
|
2898
|
+
const action = fieldTypeToAction(type);
|
|
2899
|
+
const typeName = fieldTypeName(type);
|
|
2900
|
+
throw new Error(`Cannot ${action} non-${typeName} ${name}`);
|
|
2901
|
+
}
|
|
2902
|
+
if (type === 'dimension') {
|
|
2903
|
+
return ASTGroupByViewOperation.fromReference(name, path, rename);
|
|
2904
|
+
} else if (type === 'measure') {
|
|
2905
|
+
return ASTAggregateViewOperation.fromReference(name, path, rename);
|
|
2906
|
+
} else {
|
|
2907
|
+
return ASTNestViewOperation.fromReference(name, path, rename);
|
|
2908
|
+
}
|
|
2909
|
+
}
|
|
2910
|
+
|
|
2911
|
+
private addOperation(
|
|
2912
|
+
item:
|
|
2913
|
+
| ASTGroupByViewOperation
|
|
2914
|
+
| ASTAggregateViewOperation
|
|
2915
|
+
| ASTNestViewOperation
|
|
2916
|
+
| ASTWhereViewOperation
|
|
2917
|
+
| ASTOrderByViewOperation
|
|
2918
|
+
) {
|
|
2919
|
+
if (
|
|
2920
|
+
item instanceof ASTGroupByViewOperation ||
|
|
2921
|
+
item instanceof ASTAggregateViewOperation ||
|
|
2922
|
+
item instanceof ASTNestViewOperation
|
|
2923
|
+
) {
|
|
2924
|
+
if (this.hasFieldNamed(item.name)) {
|
|
2925
|
+
throw new Error(`Query already has a field named ${item.name}`);
|
|
2926
|
+
}
|
|
2927
|
+
}
|
|
2928
|
+
const whereToInsert = this.findInsertionPoint(item.kind);
|
|
2929
|
+
this.operations.insert(item, whereToInsert);
|
|
2930
|
+
return item;
|
|
2931
|
+
}
|
|
2932
|
+
|
|
2933
|
+
/**
|
|
2934
|
+
* @internal
|
|
2935
|
+
*/
|
|
2936
|
+
getRefinementSchema(): Malloy.Schema {
|
|
2937
|
+
const fields: Malloy.FieldInfo[] = [];
|
|
2938
|
+
for (const operation of this.operations.iter()) {
|
|
2939
|
+
if (
|
|
2940
|
+
operation instanceof ASTGroupByViewOperation ||
|
|
2941
|
+
operation instanceof ASTAggregateViewOperation ||
|
|
2942
|
+
operation instanceof ASTNestViewOperation
|
|
2943
|
+
) {
|
|
2944
|
+
// TODO convert measures into dimensions for output
|
|
2945
|
+
fields.push(operation.getFieldInfo());
|
|
2946
|
+
}
|
|
2947
|
+
}
|
|
2948
|
+
return {fields};
|
|
2949
|
+
}
|
|
2950
|
+
|
|
2951
|
+
/**
|
|
2952
|
+
* Sets the limit for this segment. Overrides an existing limit.
|
|
2953
|
+
*
|
|
2954
|
+
* ```
|
|
2955
|
+
* run: flights -> { group_by: carrier }
|
|
2956
|
+
* ```
|
|
2957
|
+
* ```ts
|
|
2958
|
+
* q.getOrAddDefaultSegment().setLimit(10);
|
|
2959
|
+
* ```
|
|
2960
|
+
* ```
|
|
2961
|
+
* run: flights -> {
|
|
2962
|
+
* group_by: carrier
|
|
2963
|
+
* limit: 10
|
|
2964
|
+
* }
|
|
2965
|
+
* ```
|
|
2966
|
+
*
|
|
2967
|
+
* @param limit The limit to set. Must be an integer.
|
|
2968
|
+
*/
|
|
2969
|
+
public setLimit(limit: number) {
|
|
2970
|
+
ASTLimitViewOperation.validateLimit(limit);
|
|
2971
|
+
const limitOp: ASTLimitViewOperation | undefined = [
|
|
2972
|
+
...this.operations.iter(),
|
|
2973
|
+
].find(ASTViewOperation.isLimit);
|
|
2974
|
+
if (limitOp) {
|
|
2975
|
+
limitOp.limit = limit;
|
|
2976
|
+
} else {
|
|
2977
|
+
this.operations.add(
|
|
2978
|
+
new ASTLimitViewOperation({
|
|
2979
|
+
kind: 'limit',
|
|
2980
|
+
limit,
|
|
2981
|
+
})
|
|
2982
|
+
);
|
|
2983
|
+
}
|
|
2984
|
+
}
|
|
2985
|
+
|
|
2986
|
+
getOrAddDefaultSegment(): ASTSegmentViewDefinition {
|
|
2987
|
+
return this;
|
|
2988
|
+
}
|
|
2989
|
+
|
|
2990
|
+
addEmptyRefinement(): ASTSegmentViewDefinition {
|
|
2991
|
+
const view = ASTRefinementViewDefinition.segmentRefinementOf(this.build());
|
|
2992
|
+
swapViewInParent(this, view);
|
|
2993
|
+
return view.refinement.asSegmentViewDefinition();
|
|
2994
|
+
}
|
|
2995
|
+
|
|
2996
|
+
addViewRefinement(name: string, path?: string[]): ASTReferenceViewDefinition {
|
|
2997
|
+
const {error} = this.isValidViewRefinement(name, path);
|
|
2998
|
+
if (error) {
|
|
2999
|
+
throw new Error(error);
|
|
3000
|
+
}
|
|
3001
|
+
const view = ASTRefinementViewDefinition.viewRefinementOf(
|
|
3002
|
+
this.build(),
|
|
3003
|
+
name,
|
|
3004
|
+
path
|
|
3005
|
+
);
|
|
3006
|
+
swapViewInParent(this, view);
|
|
3007
|
+
return view.refinement.asReferenceViewDefinition();
|
|
3008
|
+
}
|
|
3009
|
+
|
|
3010
|
+
getInputSchema(): Malloy.Schema {
|
|
3011
|
+
return getInputSchemaFromViewParent(this.parent as ViewParent);
|
|
3012
|
+
}
|
|
3013
|
+
|
|
3014
|
+
getOutputSchema(): Malloy.Schema {
|
|
3015
|
+
const parent = this.parent as ViewParent;
|
|
3016
|
+
return parent.getOutputSchema();
|
|
3017
|
+
}
|
|
3018
|
+
|
|
3019
|
+
getImplicitName(): string | undefined {
|
|
3020
|
+
return undefined;
|
|
3021
|
+
}
|
|
3022
|
+
|
|
3023
|
+
isValidViewRefinement(
|
|
3024
|
+
name: string,
|
|
3025
|
+
path?: string[]
|
|
3026
|
+
): {
|
|
3027
|
+
isValidViewRefinement: boolean;
|
|
3028
|
+
error?: string;
|
|
3029
|
+
} {
|
|
3030
|
+
return isValidViewRefinement(this, name, path);
|
|
3031
|
+
}
|
|
3032
|
+
|
|
3033
|
+
getInheritedAnnotations(): Malloy.Annotation[] {
|
|
3034
|
+
return [];
|
|
3035
|
+
}
|
|
3036
|
+
}
|
|
3037
|
+
|
|
3038
|
+
export class ASTViewOperationList extends ASTListNode<
|
|
3039
|
+
Malloy.ViewOperation,
|
|
3040
|
+
ASTViewOperation
|
|
3041
|
+
> {
|
|
3042
|
+
constructor(operations: Malloy.ViewOperation[]) {
|
|
3043
|
+
super(
|
|
3044
|
+
operations,
|
|
3045
|
+
operations.map(p => ASTViewOperation.from(p))
|
|
3046
|
+
);
|
|
3047
|
+
}
|
|
3048
|
+
|
|
3049
|
+
get items() {
|
|
3050
|
+
return this.children;
|
|
3051
|
+
}
|
|
3052
|
+
|
|
3053
|
+
/**
|
|
3054
|
+
* @internal
|
|
3055
|
+
*/
|
|
3056
|
+
set items(operations: ASTViewOperation[]) {
|
|
3057
|
+
this.edit();
|
|
3058
|
+
this.children = operations;
|
|
3059
|
+
}
|
|
3060
|
+
|
|
3061
|
+
/**
|
|
3062
|
+
* @internal
|
|
3063
|
+
*/
|
|
3064
|
+
get segment() {
|
|
3065
|
+
return this.parent.asSegmentViewDefinition();
|
|
3066
|
+
}
|
|
3067
|
+
}
|
|
3068
|
+
|
|
3069
|
+
export type ASTViewOperation =
|
|
3070
|
+
| ASTGroupByViewOperation
|
|
3071
|
+
| ASTAggregateViewOperation
|
|
3072
|
+
| ASTOrderByViewOperation
|
|
3073
|
+
| ASTNestViewOperation
|
|
3074
|
+
| ASTLimitViewOperation
|
|
3075
|
+
| ASTWhereViewOperation;
|
|
3076
|
+
export const ASTViewOperation = {
|
|
3077
|
+
from(value: Malloy.ViewOperation): ASTViewOperation {
|
|
3078
|
+
switch (value.kind) {
|
|
3079
|
+
case 'group_by':
|
|
3080
|
+
return new ASTGroupByViewOperation(value);
|
|
3081
|
+
case 'aggregate':
|
|
3082
|
+
return new ASTAggregateViewOperation(value);
|
|
3083
|
+
case 'order_by':
|
|
3084
|
+
return new ASTOrderByViewOperation(value);
|
|
3085
|
+
case 'nest':
|
|
3086
|
+
return new ASTNestViewOperation(value);
|
|
3087
|
+
case 'limit':
|
|
3088
|
+
return new ASTLimitViewOperation(value);
|
|
3089
|
+
case 'where':
|
|
3090
|
+
return new ASTWhereViewOperation(value);
|
|
3091
|
+
}
|
|
3092
|
+
},
|
|
3093
|
+
isLimit(x: ASTViewOperation): x is ASTLimitViewOperation {
|
|
3094
|
+
return x instanceof ASTLimitViewOperation;
|
|
3095
|
+
},
|
|
3096
|
+
};
|
|
3097
|
+
|
|
3098
|
+
export interface IASTAnnotatable {
|
|
3099
|
+
getOrAddAnnotations(): ASTAnnotationList;
|
|
3100
|
+
getInheritedTag(prefix: RegExp | string): Tag;
|
|
3101
|
+
getIntrinsicTag(prefix: RegExp | string): Tag;
|
|
3102
|
+
getTag(prefix: RegExp | string): Tag;
|
|
3103
|
+
setTagProperty(path: Path, value: TagSetValue, prefix: string): void;
|
|
3104
|
+
removeTagProperty(path: Path, prefix: string): void;
|
|
3105
|
+
}
|
|
3106
|
+
|
|
3107
|
+
export class ASTOrderByViewOperation extends ASTObjectNode<
|
|
3108
|
+
Malloy.ViewOperationWithOrderBy,
|
|
3109
|
+
{
|
|
3110
|
+
kind: 'order_by';
|
|
3111
|
+
field_reference: ASTFieldReference;
|
|
3112
|
+
direction?: Malloy.OrderByDirection;
|
|
3113
|
+
}
|
|
3114
|
+
> {
|
|
3115
|
+
readonly kind: Malloy.ViewOperationType = 'order_by';
|
|
3116
|
+
constructor(public node: Malloy.ViewOperationWithOrderBy) {
|
|
3117
|
+
super(node, {
|
|
3118
|
+
kind: 'order_by',
|
|
3119
|
+
field_reference: new ASTFieldReference(node.field_reference),
|
|
3120
|
+
direction: node.direction,
|
|
3121
|
+
});
|
|
3122
|
+
}
|
|
3123
|
+
|
|
3124
|
+
get fieldReference() {
|
|
3125
|
+
return this.children.field_reference;
|
|
3126
|
+
}
|
|
3127
|
+
|
|
3128
|
+
get name() {
|
|
3129
|
+
return this.fieldReference.name;
|
|
3130
|
+
}
|
|
3131
|
+
|
|
3132
|
+
get direction() {
|
|
3133
|
+
return this.children.direction;
|
|
3134
|
+
}
|
|
3135
|
+
|
|
3136
|
+
set direction(direction: Malloy.OrderByDirection | undefined) {
|
|
3137
|
+
if (this.direction === direction) return;
|
|
3138
|
+
this.edit();
|
|
3139
|
+
this.children.direction = direction;
|
|
3140
|
+
}
|
|
3141
|
+
|
|
3142
|
+
get list() {
|
|
3143
|
+
return this.parent.asViewOperationList();
|
|
3144
|
+
}
|
|
3145
|
+
|
|
3146
|
+
delete() {
|
|
3147
|
+
const list = this.list;
|
|
3148
|
+
list.remove(this);
|
|
3149
|
+
}
|
|
3150
|
+
|
|
3151
|
+
setField(name: string) {
|
|
3152
|
+
const schema = this.list.segment.getOutputSchema();
|
|
3153
|
+
ASTNode.schemaGet(schema, name, []);
|
|
3154
|
+
this.edit();
|
|
3155
|
+
this.children.field_reference = new ASTFieldReference({name});
|
|
3156
|
+
}
|
|
3157
|
+
|
|
3158
|
+
setDirection(direction: Malloy.OrderByDirection | undefined) {
|
|
3159
|
+
this.direction = direction;
|
|
3160
|
+
}
|
|
3161
|
+
}
|
|
3162
|
+
|
|
3163
|
+
export class ASTGroupByViewOperation
|
|
3164
|
+
extends ASTObjectNode<
|
|
3165
|
+
Malloy.ViewOperationWithGroupBy,
|
|
3166
|
+
{
|
|
3167
|
+
kind: 'group_by';
|
|
3168
|
+
name?: string;
|
|
3169
|
+
field: ASTField;
|
|
3170
|
+
}
|
|
3171
|
+
>
|
|
3172
|
+
implements IASTAnnotatable
|
|
3173
|
+
{
|
|
3174
|
+
readonly kind: Malloy.ViewOperationType = 'group_by';
|
|
3175
|
+
constructor(public node: Malloy.ViewOperationWithGroupBy) {
|
|
3176
|
+
super(node, {
|
|
3177
|
+
kind: 'group_by',
|
|
3178
|
+
name: node.name,
|
|
3179
|
+
field: new ASTField(node.field),
|
|
3180
|
+
});
|
|
3181
|
+
}
|
|
3182
|
+
|
|
3183
|
+
get field() {
|
|
3184
|
+
return this.children.field;
|
|
3185
|
+
}
|
|
3186
|
+
|
|
3187
|
+
get name() {
|
|
3188
|
+
return this.children.name ?? this.field.name;
|
|
3189
|
+
}
|
|
3190
|
+
|
|
3191
|
+
set name(name: string) {
|
|
3192
|
+
if (this.name === name) return;
|
|
3193
|
+
this.edit();
|
|
3194
|
+
if (this.field.name === name) {
|
|
3195
|
+
this.children.name = undefined;
|
|
3196
|
+
} else {
|
|
3197
|
+
this.children.name = name;
|
|
3198
|
+
}
|
|
3199
|
+
}
|
|
3200
|
+
|
|
3201
|
+
/**
|
|
3202
|
+
* @internal
|
|
3203
|
+
*/
|
|
3204
|
+
get list() {
|
|
3205
|
+
return this.parent.asViewOperationList();
|
|
3206
|
+
}
|
|
3207
|
+
|
|
3208
|
+
/**
|
|
3209
|
+
* Renames the group by item. If the field's name matches the given name,
|
|
3210
|
+
* removes the `name is` part.
|
|
3211
|
+
*
|
|
3212
|
+
* ```
|
|
3213
|
+
* run: flights -> { group_by: carrier }
|
|
3214
|
+
* ```
|
|
3215
|
+
* ```ts
|
|
3216
|
+
* groupBy.rename("carrier_2");
|
|
3217
|
+
* ```
|
|
3218
|
+
* ```
|
|
3219
|
+
* run: flights -> { group_by: carrier2 is carrier }
|
|
3220
|
+
* ```
|
|
3221
|
+
*
|
|
3222
|
+
* ```
|
|
3223
|
+
* run: flights -> { group_by: renamed is carrier }
|
|
3224
|
+
* ```
|
|
3225
|
+
* ```ts
|
|
3226
|
+
* groupBy.rename("carrier");
|
|
3227
|
+
* ```
|
|
3228
|
+
* ```
|
|
3229
|
+
* run: flights -> { group_by: carrier }
|
|
3230
|
+
* ```
|
|
3231
|
+
*
|
|
3232
|
+
*
|
|
3233
|
+
* @param name The new name
|
|
3234
|
+
*/
|
|
3235
|
+
rename(name: string) {
|
|
3236
|
+
this.list.segment.renameField(this, name);
|
|
3237
|
+
}
|
|
3238
|
+
|
|
3239
|
+
/**
|
|
3240
|
+
* Delete this group by item.
|
|
3241
|
+
*
|
|
3242
|
+
* Possible side effects:
|
|
3243
|
+
* - If this was the last item in the group by operation, the whole
|
|
3244
|
+
* operation is removed.
|
|
3245
|
+
* - Any order by that references this group by item will be removed.
|
|
3246
|
+
*
|
|
3247
|
+
* ```
|
|
3248
|
+
* run: flights -> {
|
|
3249
|
+
* group_by: carrier
|
|
3250
|
+
* aggregate: flight_count
|
|
3251
|
+
* order by:
|
|
3252
|
+
* flight_count desc
|
|
3253
|
+
* carrier asc
|
|
3254
|
+
* }
|
|
3255
|
+
* ```
|
|
3256
|
+
* ```ts
|
|
3257
|
+
* groupBy.delete();
|
|
3258
|
+
* ```
|
|
3259
|
+
* ```
|
|
3260
|
+
* run: flights -> {
|
|
3261
|
+
* aggregate: flight_count
|
|
3262
|
+
* order by: flight_count desc
|
|
3263
|
+
* }
|
|
3264
|
+
* ```
|
|
3265
|
+
*
|
|
3266
|
+
*/
|
|
3267
|
+
delete() {
|
|
3268
|
+
this.list.remove(this);
|
|
3269
|
+
this.list.segment.propagateUp(v => {
|
|
3270
|
+
if (v instanceof ASTSegmentViewDefinition) {
|
|
3271
|
+
v.removeOrderBys(this.name);
|
|
3272
|
+
}
|
|
3273
|
+
});
|
|
3274
|
+
}
|
|
3275
|
+
|
|
3276
|
+
getFieldInfo(): Malloy.FieldInfo {
|
|
3277
|
+
return {
|
|
3278
|
+
kind: 'dimension',
|
|
3279
|
+
name: this.name,
|
|
3280
|
+
type: this.field.type,
|
|
3281
|
+
};
|
|
3282
|
+
}
|
|
3283
|
+
|
|
3284
|
+
private get annotations() {
|
|
3285
|
+
return this.field.annotations;
|
|
3286
|
+
}
|
|
3287
|
+
|
|
3288
|
+
private set annotations(annotations: ASTAnnotationList | undefined) {
|
|
3289
|
+
this.edit();
|
|
3290
|
+
this.field.annotations = annotations;
|
|
3291
|
+
}
|
|
3292
|
+
|
|
3293
|
+
getOrAddAnnotations() {
|
|
3294
|
+
return this.field.getOrAddAnnotations();
|
|
3295
|
+
}
|
|
3296
|
+
|
|
3297
|
+
getInheritedTag(prefix: RegExp | string = '# ') {
|
|
3298
|
+
return tagFromAnnotations(prefix, this.field.getInheritedAnnotations());
|
|
3299
|
+
}
|
|
3300
|
+
|
|
3301
|
+
getIntrinsicTag(prefix: RegExp | string = '# ') {
|
|
3302
|
+
return this.annotations?.getIntrinsicTag(prefix) ?? new Tag();
|
|
3303
|
+
}
|
|
3304
|
+
|
|
3305
|
+
getTag(prefix: RegExp | string = '# ') {
|
|
3306
|
+
return this.annotations?.getTag(prefix) ?? this.getInheritedTag(prefix);
|
|
3307
|
+
}
|
|
3308
|
+
|
|
3309
|
+
setTagProperty(path: Path, value: TagSetValue = null, prefix = '# ') {
|
|
3310
|
+
this.getOrAddAnnotations().setTagProperty(path, value, prefix);
|
|
3311
|
+
}
|
|
3312
|
+
|
|
3313
|
+
removeTagProperty(path: Path, prefix = '# ') {
|
|
3314
|
+
if (!this.getTag().has(...path)) return;
|
|
3315
|
+
this.getOrAddAnnotations().removeTagProperty(path, prefix);
|
|
3316
|
+
}
|
|
3317
|
+
|
|
3318
|
+
/**
|
|
3319
|
+
* @internal
|
|
3320
|
+
*/
|
|
3321
|
+
static fromReference(
|
|
3322
|
+
name: string,
|
|
3323
|
+
path: string[] | undefined,
|
|
3324
|
+
rename: string | undefined
|
|
3325
|
+
) {
|
|
3326
|
+
return new ASTGroupByViewOperation({
|
|
3327
|
+
kind: 'group_by',
|
|
3328
|
+
name: rename,
|
|
3329
|
+
field: {
|
|
3330
|
+
expression: {
|
|
3331
|
+
kind: 'field_reference',
|
|
3332
|
+
name,
|
|
3333
|
+
path,
|
|
3334
|
+
},
|
|
3335
|
+
},
|
|
3336
|
+
});
|
|
3337
|
+
}
|
|
3338
|
+
}
|
|
3339
|
+
|
|
3340
|
+
export class ASTAggregateViewOperation
|
|
3341
|
+
extends ASTObjectNode<
|
|
3342
|
+
Malloy.ViewOperationWithAggregate,
|
|
3343
|
+
{
|
|
3344
|
+
kind: 'aggregate';
|
|
3345
|
+
name?: string;
|
|
3346
|
+
field: ASTField;
|
|
3347
|
+
}
|
|
3348
|
+
>
|
|
3349
|
+
implements IASTAnnotatable
|
|
3350
|
+
{
|
|
3351
|
+
readonly kind: Malloy.ViewOperationType = 'aggregate';
|
|
3352
|
+
constructor(public node: Malloy.ViewOperationWithAggregate) {
|
|
3353
|
+
super(node, {
|
|
3354
|
+
kind: 'aggregate',
|
|
3355
|
+
name: node.name,
|
|
3356
|
+
field: new ASTField(node.field),
|
|
3357
|
+
});
|
|
3358
|
+
}
|
|
3359
|
+
|
|
3360
|
+
get field() {
|
|
3361
|
+
return this.children.field;
|
|
3362
|
+
}
|
|
3363
|
+
|
|
3364
|
+
get name() {
|
|
3365
|
+
return this.children.name ?? this.field.name;
|
|
3366
|
+
}
|
|
3367
|
+
|
|
3368
|
+
set name(name: string) {
|
|
3369
|
+
if (this.name === name) return;
|
|
3370
|
+
this.edit();
|
|
3371
|
+
if (this.field.name === name) {
|
|
3372
|
+
this.children.name = undefined;
|
|
3373
|
+
} else {
|
|
3374
|
+
this.children.name = name;
|
|
3375
|
+
}
|
|
3376
|
+
}
|
|
3377
|
+
|
|
3378
|
+
get annotations() {
|
|
3379
|
+
return this.field.annotations;
|
|
3380
|
+
}
|
|
3381
|
+
|
|
3382
|
+
/**
|
|
3383
|
+
* Renames the aggregate item. If the field's name matches the given name,
|
|
3384
|
+
* removes the `name is` part.
|
|
3385
|
+
*
|
|
3386
|
+
* ```
|
|
3387
|
+
* run: flights -> { aggregate: flight_count }
|
|
3388
|
+
* ```
|
|
3389
|
+
* ```ts
|
|
3390
|
+
* aggregate.rename("flight_count_2");
|
|
3391
|
+
* ```
|
|
3392
|
+
* ```
|
|
3393
|
+
* run: flights -> { aggregate: flight_count2 is flight_count }
|
|
3394
|
+
* ```
|
|
3395
|
+
*
|
|
3396
|
+
* ```
|
|
3397
|
+
* run: flights -> { aggregate: renamed is flight_count }
|
|
3398
|
+
* ```
|
|
3399
|
+
* ```ts
|
|
3400
|
+
* aggregate.rename("flight_count");
|
|
3401
|
+
* ```
|
|
3402
|
+
* ```
|
|
3403
|
+
* run: flights -> { aggregate: flight_count }
|
|
3404
|
+
* ```
|
|
3405
|
+
*
|
|
3406
|
+
*
|
|
3407
|
+
* @param name The new name
|
|
3408
|
+
*/
|
|
3409
|
+
rename(name: string) {
|
|
3410
|
+
this.list.segment.renameField(this, name);
|
|
3411
|
+
}
|
|
3412
|
+
|
|
3413
|
+
/**
|
|
3414
|
+
* @internal
|
|
3415
|
+
*/
|
|
3416
|
+
get list() {
|
|
3417
|
+
return this.parent.asViewOperationList();
|
|
3418
|
+
}
|
|
3419
|
+
|
|
3420
|
+
delete() {
|
|
3421
|
+
this.list.remove(this);
|
|
3422
|
+
this.list.segment.propagateUp(v => {
|
|
3423
|
+
if (v instanceof ASTSegmentViewDefinition) {
|
|
3424
|
+
v.removeOrderBys(this.name);
|
|
3425
|
+
}
|
|
3426
|
+
});
|
|
3427
|
+
}
|
|
3428
|
+
|
|
3429
|
+
getFieldInfo(): Malloy.FieldInfo {
|
|
3430
|
+
return {
|
|
3431
|
+
kind: 'dimension',
|
|
3432
|
+
name: this.name,
|
|
3433
|
+
type: this.field.type,
|
|
3434
|
+
};
|
|
3435
|
+
}
|
|
3436
|
+
|
|
3437
|
+
getOrAddAnnotations() {
|
|
3438
|
+
return this.field.getOrAddAnnotations();
|
|
3439
|
+
}
|
|
3440
|
+
|
|
3441
|
+
getInheritedTag(prefix: RegExp | string = '# ') {
|
|
3442
|
+
return tagFromAnnotations(prefix, this.field.getInheritedAnnotations());
|
|
3443
|
+
}
|
|
3444
|
+
|
|
3445
|
+
getIntrinsicTag(prefix: RegExp | string = '# ') {
|
|
3446
|
+
return this.annotations?.getIntrinsicTag(prefix) ?? new Tag();
|
|
3447
|
+
}
|
|
3448
|
+
|
|
3449
|
+
getTag(prefix: RegExp | string = '# ') {
|
|
3450
|
+
return this.annotations?.getTag(prefix) ?? this.getInheritedTag(prefix);
|
|
3451
|
+
}
|
|
3452
|
+
|
|
3453
|
+
setTagProperty(path: Path, value: TagSetValue = null, prefix = '# ') {
|
|
3454
|
+
this.getOrAddAnnotations().setTagProperty(path, value, prefix);
|
|
3455
|
+
}
|
|
3456
|
+
|
|
3457
|
+
removeTagProperty(path: Path, prefix = '# ') {
|
|
3458
|
+
if (!this.getTag().has(...path)) return;
|
|
3459
|
+
this.getOrAddAnnotations().removeTagProperty(path, prefix);
|
|
3460
|
+
}
|
|
3461
|
+
|
|
3462
|
+
addWhere(
|
|
3463
|
+
name: string,
|
|
3464
|
+
path: string[],
|
|
3465
|
+
filterString: string
|
|
3466
|
+
): ASTFilteredFieldExpression;
|
|
3467
|
+
addWhere(
|
|
3468
|
+
name: string,
|
|
3469
|
+
path: string[],
|
|
3470
|
+
filter: ParsedFilter
|
|
3471
|
+
): ASTFilteredFieldExpression;
|
|
3472
|
+
addWhere(name: string, filterString: string): ASTFilteredFieldExpression;
|
|
3473
|
+
addWhere(name: string, filter: ParsedFilter): ASTFilteredFieldExpression;
|
|
3474
|
+
addWhere(
|
|
3475
|
+
name: string,
|
|
3476
|
+
arg2: string[] | string | ParsedFilter,
|
|
3477
|
+
arg3?: string | ParsedFilter
|
|
3478
|
+
): ASTFilteredFieldExpression {
|
|
3479
|
+
const path = Array.isArray(arg2) ? arg2 : [];
|
|
3480
|
+
const filter = arg3 === undefined ? (arg2 as string | ParsedFilter) : arg3;
|
|
3481
|
+
const filterString =
|
|
3482
|
+
typeof filter === 'string' ? filter : serializeFilter(filter);
|
|
3483
|
+
const schema = this.list.segment.getInputSchema();
|
|
3484
|
+
const field = ASTQuery.schemaGet(schema, name, path);
|
|
3485
|
+
// Validate filter
|
|
3486
|
+
validateFilter(field, filter);
|
|
3487
|
+
const where: Malloy.Where = {
|
|
3488
|
+
filter: {
|
|
3489
|
+
kind: 'filter_string',
|
|
3490
|
+
field_reference: {name},
|
|
3491
|
+
filter: filterString,
|
|
3492
|
+
},
|
|
3493
|
+
};
|
|
3494
|
+
if (this.field.expression instanceof ASTFilteredFieldExpression) {
|
|
3495
|
+
this.field.expression.where.add(new ASTWhere(where));
|
|
3496
|
+
return this.field.expression;
|
|
3497
|
+
} else if (this.field.expression instanceof ASTReferenceExpression) {
|
|
3498
|
+
const existing = this.field.expression.build();
|
|
3499
|
+
this.field.expression = new ASTFilteredFieldExpression({
|
|
3500
|
+
kind: 'filtered_field',
|
|
3501
|
+
field_reference: {
|
|
3502
|
+
name: existing.name,
|
|
3503
|
+
path: existing.path,
|
|
3504
|
+
parameters: existing.parameters,
|
|
3505
|
+
},
|
|
3506
|
+
where: [where],
|
|
3507
|
+
});
|
|
3508
|
+
return this.field.expression;
|
|
3509
|
+
} else {
|
|
3510
|
+
throw new Error('This kind of expression does not support addWhere');
|
|
3511
|
+
}
|
|
3512
|
+
}
|
|
3513
|
+
|
|
3514
|
+
/**
|
|
3515
|
+
* @internal
|
|
3516
|
+
*/
|
|
3517
|
+
static fromReference(
|
|
3518
|
+
name: string,
|
|
3519
|
+
path: string[] | undefined,
|
|
3520
|
+
rename: string | undefined
|
|
3521
|
+
) {
|
|
3522
|
+
return new ASTAggregateViewOperation({
|
|
3523
|
+
kind: 'aggregate',
|
|
3524
|
+
name: rename,
|
|
3525
|
+
field: {
|
|
3526
|
+
expression: {
|
|
3527
|
+
kind: 'field_reference',
|
|
3528
|
+
name,
|
|
3529
|
+
path,
|
|
3530
|
+
},
|
|
3531
|
+
},
|
|
3532
|
+
});
|
|
3533
|
+
}
|
|
3534
|
+
}
|
|
3535
|
+
|
|
3536
|
+
export class ASTField
|
|
3537
|
+
extends ASTObjectNode<
|
|
3538
|
+
Malloy.Field,
|
|
3539
|
+
{
|
|
3540
|
+
expression: ASTExpression;
|
|
3541
|
+
annotations?: ASTAnnotationList;
|
|
3542
|
+
}
|
|
3543
|
+
>
|
|
3544
|
+
implements IASTAnnotatable
|
|
3545
|
+
{
|
|
3546
|
+
constructor(public node: Malloy.Field) {
|
|
3547
|
+
super(node, {
|
|
3548
|
+
expression: ASTExpression.from(node.expression),
|
|
3549
|
+
annotations: node.annotations && new ASTAnnotationList(node.annotations),
|
|
3550
|
+
});
|
|
3551
|
+
}
|
|
3552
|
+
|
|
3553
|
+
get expression() {
|
|
3554
|
+
return this.children.expression;
|
|
3555
|
+
}
|
|
3556
|
+
|
|
3557
|
+
set expression(expression: ASTExpression) {
|
|
3558
|
+
this.edit();
|
|
3559
|
+
this.children.expression = expression;
|
|
3560
|
+
expression.parent = this;
|
|
3561
|
+
}
|
|
3562
|
+
|
|
3563
|
+
get name() {
|
|
3564
|
+
return this.expression.name;
|
|
3565
|
+
}
|
|
3566
|
+
|
|
3567
|
+
get type() {
|
|
3568
|
+
return this.expression.fieldType;
|
|
3569
|
+
}
|
|
3570
|
+
|
|
3571
|
+
get annotations() {
|
|
3572
|
+
return this.children.annotations;
|
|
3573
|
+
}
|
|
3574
|
+
|
|
3575
|
+
set annotations(annotations: ASTAnnotationList | undefined) {
|
|
3576
|
+
this.edit();
|
|
3577
|
+
this.children.annotations = annotations;
|
|
3578
|
+
}
|
|
3579
|
+
|
|
3580
|
+
// Returns a Malloy reference that this field points to
|
|
3581
|
+
getReference() {
|
|
3582
|
+
return this.expression.getReference();
|
|
3583
|
+
}
|
|
3584
|
+
|
|
3585
|
+
getOrAddAnnotations() {
|
|
3586
|
+
if (this.annotations) return this.annotations;
|
|
3587
|
+
this.edit();
|
|
3588
|
+
const annotations = new ASTAnnotationList([]);
|
|
3589
|
+
this.children.annotations = annotations;
|
|
3590
|
+
return annotations;
|
|
3591
|
+
}
|
|
3592
|
+
|
|
3593
|
+
getInheritedTag(prefix: RegExp | string = '# ') {
|
|
3594
|
+
return tagFromAnnotations(prefix, this.getInheritedAnnotations());
|
|
3595
|
+
}
|
|
3596
|
+
|
|
3597
|
+
getIntrinsicTag(prefix: RegExp | string = '# ') {
|
|
3598
|
+
return this.annotations?.getIntrinsicTag(prefix) ?? new Tag();
|
|
3599
|
+
}
|
|
3600
|
+
|
|
3601
|
+
getTag(prefix: RegExp | string = '# ') {
|
|
3602
|
+
return this.annotations?.getTag(prefix) ?? this.getInheritedTag(prefix);
|
|
3603
|
+
}
|
|
3604
|
+
|
|
3605
|
+
setTagProperty(path: Path, value: TagSetValue = null, prefix = '# ') {
|
|
3606
|
+
this.getOrAddAnnotations().setTagProperty(path, value, prefix);
|
|
3607
|
+
}
|
|
3608
|
+
|
|
3609
|
+
removeTagProperty(path: Path, prefix = '# ') {
|
|
3610
|
+
if (!this.getTag().has(...path)) return;
|
|
3611
|
+
this.getOrAddAnnotations().removeTagProperty(path, prefix);
|
|
3612
|
+
}
|
|
3613
|
+
|
|
3614
|
+
/**
|
|
3615
|
+
* @internal
|
|
3616
|
+
*/
|
|
3617
|
+
get segment() {
|
|
3618
|
+
const groupByOrAggregate = this.parent as
|
|
3619
|
+
| ASTGroupByViewOperation
|
|
3620
|
+
| ASTAggregateViewOperation;
|
|
3621
|
+
const operationList = groupByOrAggregate.list;
|
|
3622
|
+
return operationList.segment;
|
|
3623
|
+
}
|
|
3624
|
+
|
|
3625
|
+
getInheritedAnnotations(): Malloy.Annotation[] {
|
|
3626
|
+
return this.expression.getInheritedAnnotations();
|
|
3627
|
+
}
|
|
3628
|
+
}
|
|
3629
|
+
|
|
3630
|
+
export type ASTExpression =
|
|
3631
|
+
| ASTReferenceExpression
|
|
3632
|
+
| ASTFilteredFieldExpression
|
|
3633
|
+
| ASTTimeTruncationExpression;
|
|
3634
|
+
export const ASTExpression = {
|
|
3635
|
+
from(value: Malloy.Expression): ASTExpression {
|
|
3636
|
+
switch (value.kind) {
|
|
3637
|
+
case 'field_reference':
|
|
3638
|
+
return new ASTReferenceExpression(value);
|
|
3639
|
+
case 'filtered_field':
|
|
3640
|
+
return new ASTFilteredFieldExpression(value);
|
|
3641
|
+
case 'time_truncation':
|
|
3642
|
+
return new ASTTimeTruncationExpression(value);
|
|
3643
|
+
}
|
|
3644
|
+
},
|
|
3645
|
+
};
|
|
3646
|
+
|
|
3647
|
+
// TODO would be nice for this to extend ASTFieldReference?
|
|
3648
|
+
export class ASTReferenceExpression
|
|
3649
|
+
extends ASTObjectNode<
|
|
3650
|
+
Malloy.ExpressionWithFieldReference,
|
|
3651
|
+
{
|
|
3652
|
+
kind: 'field_reference';
|
|
3653
|
+
name: string;
|
|
3654
|
+
path?: string[];
|
|
3655
|
+
parameters?: ASTParameterValueList;
|
|
3656
|
+
}
|
|
3657
|
+
>
|
|
3658
|
+
implements IASTReference
|
|
3659
|
+
{
|
|
3660
|
+
readonly kind: Malloy.ExpressionType = 'field_reference';
|
|
3661
|
+
|
|
3662
|
+
constructor(public node: Malloy.ExpressionWithFieldReference) {
|
|
3663
|
+
super(node, {
|
|
3664
|
+
kind: node.kind,
|
|
3665
|
+
name: node.name,
|
|
3666
|
+
path: node.path,
|
|
3667
|
+
parameters: node.parameters && new ASTParameterValueList(node.parameters),
|
|
3668
|
+
});
|
|
3669
|
+
}
|
|
3670
|
+
|
|
3671
|
+
get name() {
|
|
3672
|
+
return this.children.name;
|
|
3673
|
+
}
|
|
3674
|
+
|
|
3675
|
+
get parameters() {
|
|
3676
|
+
return this.children.parameters;
|
|
3677
|
+
}
|
|
3678
|
+
|
|
3679
|
+
set parameters(parameters: ASTParameterValueList | undefined) {
|
|
3680
|
+
this.edit();
|
|
3681
|
+
this.children.parameters = parameters;
|
|
3682
|
+
}
|
|
3683
|
+
|
|
3684
|
+
/**
|
|
3685
|
+
* @internal
|
|
3686
|
+
*/
|
|
3687
|
+
get field() {
|
|
3688
|
+
return this.parent.asField();
|
|
3689
|
+
}
|
|
3690
|
+
|
|
3691
|
+
get path() {
|
|
3692
|
+
return this.children.path;
|
|
3693
|
+
}
|
|
3694
|
+
|
|
3695
|
+
getReference() {
|
|
3696
|
+
return this.build();
|
|
3697
|
+
}
|
|
3698
|
+
|
|
3699
|
+
getFieldInfo(): Malloy.FieldInfoWithDimension | Malloy.FieldInfoWithMeasure {
|
|
3700
|
+
const schema = this.field.segment.getInputSchema();
|
|
3701
|
+
const def = ASTNode.schemaGet(schema, this.name, this.path);
|
|
3702
|
+
if (def.kind !== 'dimension' && def.kind !== 'measure') {
|
|
3703
|
+
throw new Error('Invalid field for ASTReferenceExpression');
|
|
3704
|
+
}
|
|
3705
|
+
return def;
|
|
3706
|
+
}
|
|
3707
|
+
|
|
3708
|
+
get fieldType() {
|
|
3709
|
+
return this.getFieldInfo().type;
|
|
3710
|
+
}
|
|
3711
|
+
|
|
3712
|
+
getInheritedAnnotations(): Malloy.Annotation[] {
|
|
3713
|
+
const field = this.getFieldInfo();
|
|
3714
|
+
return field.annotations ?? [];
|
|
3715
|
+
}
|
|
3716
|
+
|
|
3717
|
+
public getOrAddParameters() {
|
|
3718
|
+
return ASTReference.getOrAddParameters(this);
|
|
3719
|
+
}
|
|
3720
|
+
|
|
3721
|
+
public setParameter(name: string, value: RawLiteralValue) {
|
|
3722
|
+
return ASTReference.setParameter(this, name, value);
|
|
3723
|
+
}
|
|
3724
|
+
|
|
3725
|
+
public tryGetParameter(name: string): ASTParameterValue | undefined {
|
|
3726
|
+
return ASTReference.tryGetParameter(this, name);
|
|
3727
|
+
}
|
|
3728
|
+
}
|
|
3729
|
+
|
|
3730
|
+
export class ASTTimeTruncationExpression extends ASTObjectNode<
|
|
3731
|
+
Malloy.ExpressionWithTimeTruncation,
|
|
3732
|
+
{
|
|
3733
|
+
kind: 'time_truncation';
|
|
3734
|
+
field_reference: ASTFieldReference;
|
|
3735
|
+
truncation: Malloy.TimestampTimeframe;
|
|
3736
|
+
}
|
|
3737
|
+
> {
|
|
3738
|
+
readonly kind: Malloy.ExpressionType = 'time_truncation';
|
|
3739
|
+
|
|
3740
|
+
constructor(public node: Malloy.ExpressionWithTimeTruncation) {
|
|
3741
|
+
super(node, {
|
|
3742
|
+
kind: node.kind,
|
|
3743
|
+
field_reference: new ASTFieldReference(node.field_reference),
|
|
3744
|
+
truncation: node.truncation,
|
|
3745
|
+
});
|
|
3746
|
+
}
|
|
3747
|
+
|
|
3748
|
+
getReference() {
|
|
3749
|
+
return this.fieldReference.build();
|
|
3750
|
+
}
|
|
3751
|
+
|
|
3752
|
+
get fieldReference() {
|
|
3753
|
+
return this.children.field_reference;
|
|
3754
|
+
}
|
|
3755
|
+
|
|
3756
|
+
get truncation() {
|
|
3757
|
+
return this.children.truncation;
|
|
3758
|
+
}
|
|
3759
|
+
|
|
3760
|
+
get name() {
|
|
3761
|
+
return this.fieldReference.name;
|
|
3762
|
+
}
|
|
3763
|
+
|
|
3764
|
+
/**
|
|
3765
|
+
* @internal
|
|
3766
|
+
*/
|
|
3767
|
+
get field() {
|
|
3768
|
+
return this.parent.asField();
|
|
3769
|
+
}
|
|
3770
|
+
|
|
3771
|
+
getFieldInfo(): Malloy.FieldInfoWithDimension | Malloy.FieldInfoWithMeasure {
|
|
3772
|
+
const schema = this.field.segment.getInputSchema();
|
|
3773
|
+
const def = ASTNode.schemaGet(schema, this.name, this.fieldReference.path);
|
|
3774
|
+
if (def.kind !== 'dimension' && def.kind !== 'measure') {
|
|
3775
|
+
throw new Error('Invalid field for ASTReferenceExpression');
|
|
3776
|
+
}
|
|
3777
|
+
return def;
|
|
3778
|
+
}
|
|
3779
|
+
|
|
3780
|
+
get fieldType() {
|
|
3781
|
+
const def = this.getFieldInfo();
|
|
3782
|
+
if (def.type.kind === 'date_type') {
|
|
3783
|
+
return {
|
|
3784
|
+
...def.type,
|
|
3785
|
+
timeframe: timestampTimeframeToDateTimeframe(this.truncation),
|
|
3786
|
+
};
|
|
3787
|
+
} else if (def.type.kind === 'timestamp_type') {
|
|
3788
|
+
return {...def.type, timeframe: this.truncation};
|
|
3789
|
+
}
|
|
3790
|
+
throw new Error('This type of field cannot have a time truncation');
|
|
3791
|
+
}
|
|
3792
|
+
|
|
3793
|
+
getInheritedAnnotations(): Malloy.Annotation[] {
|
|
3794
|
+
const field = this.getFieldInfo();
|
|
3795
|
+
return field.annotations ?? [];
|
|
3796
|
+
}
|
|
3797
|
+
}
|
|
3798
|
+
|
|
3799
|
+
export class ASTWhere extends ASTObjectNode<Malloy.Where, {filter: ASTFilter}> {
|
|
3800
|
+
constructor(node: Malloy.Where) {
|
|
3801
|
+
super(node, {
|
|
3802
|
+
filter: ASTFilter.from(node.filter),
|
|
3803
|
+
});
|
|
3804
|
+
}
|
|
3805
|
+
|
|
3806
|
+
get filter() {
|
|
3807
|
+
return this.children.filter;
|
|
3808
|
+
}
|
|
3809
|
+
|
|
3810
|
+
get list() {
|
|
3811
|
+
return this.parent.asWhereList();
|
|
3812
|
+
}
|
|
3813
|
+
|
|
3814
|
+
delete() {
|
|
3815
|
+
this.list.remove(this);
|
|
3816
|
+
}
|
|
3817
|
+
}
|
|
3818
|
+
|
|
3819
|
+
export class ASTWhereList extends ASTListNode<Malloy.Where, ASTWhere> {
|
|
3820
|
+
constructor(wheres: Malloy.Where[]) {
|
|
3821
|
+
super(
|
|
3822
|
+
wheres,
|
|
3823
|
+
wheres.map(p => new ASTWhere(p))
|
|
3824
|
+
);
|
|
3825
|
+
}
|
|
3826
|
+
|
|
3827
|
+
get expression() {
|
|
3828
|
+
return this.parent.asFilteredFieldExpression();
|
|
3829
|
+
}
|
|
3830
|
+
}
|
|
3831
|
+
|
|
3832
|
+
export class ASTFilteredFieldExpression extends ASTObjectNode<
|
|
3833
|
+
Malloy.ExpressionWithFilteredField,
|
|
3834
|
+
{
|
|
3835
|
+
kind: 'filtered_field';
|
|
3836
|
+
field_reference: ASTFieldReference;
|
|
3837
|
+
where: ASTWhereList;
|
|
3838
|
+
}
|
|
3839
|
+
> {
|
|
3840
|
+
readonly kind: Malloy.ExpressionType = 'filtered_field';
|
|
3841
|
+
|
|
3842
|
+
constructor(public node: Malloy.ExpressionWithFilteredField) {
|
|
3843
|
+
super(node, {
|
|
3844
|
+
kind: node.kind,
|
|
3845
|
+
field_reference: new ASTFieldReference(node.field_reference),
|
|
3846
|
+
where: new ASTWhereList(node.where),
|
|
3847
|
+
});
|
|
3848
|
+
}
|
|
3849
|
+
|
|
3850
|
+
getReference() {
|
|
3851
|
+
return this.fieldReference.build();
|
|
3852
|
+
}
|
|
3853
|
+
|
|
3854
|
+
get fieldReference() {
|
|
3855
|
+
return this.children.field_reference;
|
|
3856
|
+
}
|
|
3857
|
+
|
|
3858
|
+
get name() {
|
|
3859
|
+
return this.fieldReference.name;
|
|
3860
|
+
}
|
|
3861
|
+
|
|
3862
|
+
get where() {
|
|
3863
|
+
return this.children.where;
|
|
3864
|
+
}
|
|
3865
|
+
|
|
3866
|
+
/**
|
|
3867
|
+
* @internal
|
|
3868
|
+
*/
|
|
3869
|
+
get field() {
|
|
3870
|
+
return this.parent.asField();
|
|
3871
|
+
}
|
|
3872
|
+
|
|
3873
|
+
getFieldInfo(): Malloy.FieldInfoWithMeasure {
|
|
3874
|
+
const schema = this.field.segment.getInputSchema();
|
|
3875
|
+
const def = ASTNode.schemaGet(schema, this.name, this.fieldReference.path);
|
|
3876
|
+
if (def.kind !== 'measure') {
|
|
3877
|
+
throw new Error('Invalid field for ASTFilteredFieldExpression');
|
|
3878
|
+
}
|
|
3879
|
+
return def;
|
|
3880
|
+
}
|
|
3881
|
+
|
|
3882
|
+
get fieldType() {
|
|
3883
|
+
return this.getFieldInfo().type;
|
|
3884
|
+
}
|
|
3885
|
+
|
|
3886
|
+
getInheritedAnnotations(): Malloy.Annotation[] {
|
|
3887
|
+
const field = this.getFieldInfo();
|
|
3888
|
+
return field.annotations ?? [];
|
|
3889
|
+
}
|
|
3890
|
+
}
|
|
3891
|
+
|
|
3892
|
+
function timestampTimeframeToDateTimeframe(
|
|
3893
|
+
timeframe: Malloy.TimestampTimeframe
|
|
3894
|
+
): Malloy.DateTimeframe {
|
|
3895
|
+
switch (timeframe) {
|
|
3896
|
+
case 'day':
|
|
3897
|
+
case 'week':
|
|
3898
|
+
case 'month':
|
|
3899
|
+
case 'year':
|
|
3900
|
+
case 'quarter':
|
|
3901
|
+
return timeframe;
|
|
3902
|
+
default:
|
|
3903
|
+
throw new Error('Invalid date timeframe');
|
|
3904
|
+
}
|
|
3905
|
+
}
|
|
3906
|
+
|
|
3907
|
+
export class ASTNestViewOperation
|
|
3908
|
+
extends ASTObjectNode<
|
|
3909
|
+
Malloy.ViewOperationWithNest,
|
|
3910
|
+
{
|
|
3911
|
+
kind: 'nest';
|
|
3912
|
+
name?: string;
|
|
3913
|
+
view: ASTView;
|
|
3914
|
+
}
|
|
3915
|
+
>
|
|
3916
|
+
implements IASTAnnotatable
|
|
3917
|
+
{
|
|
3918
|
+
readonly kind: Malloy.ViewOperationType = 'nest';
|
|
3919
|
+
constructor(public node: Malloy.ViewOperationWithNest) {
|
|
3920
|
+
super(node, {
|
|
3921
|
+
kind: 'nest',
|
|
3922
|
+
name: node.name,
|
|
3923
|
+
view: new ASTView(node.view),
|
|
3924
|
+
});
|
|
3925
|
+
}
|
|
3926
|
+
|
|
3927
|
+
get view() {
|
|
3928
|
+
return this.children.view;
|
|
3929
|
+
}
|
|
3930
|
+
|
|
3931
|
+
get annotations() {
|
|
3932
|
+
return this.view.annotations;
|
|
3933
|
+
}
|
|
3934
|
+
|
|
3935
|
+
get name() {
|
|
3936
|
+
const name = this.children.name ?? this.view.name;
|
|
3937
|
+
if (name === undefined) {
|
|
3938
|
+
throw new Error('Nest does not have a name');
|
|
3939
|
+
}
|
|
3940
|
+
return name;
|
|
3941
|
+
}
|
|
3942
|
+
|
|
3943
|
+
set name(name: string) {
|
|
3944
|
+
if (this.name === name) return;
|
|
3945
|
+
this.edit();
|
|
3946
|
+
if (this.view.name === name) {
|
|
3947
|
+
this.children.name = undefined;
|
|
3948
|
+
} else {
|
|
3949
|
+
this.children.name = name;
|
|
3950
|
+
}
|
|
3951
|
+
}
|
|
3952
|
+
|
|
3953
|
+
/**
|
|
3954
|
+
* @internal
|
|
3955
|
+
*/
|
|
3956
|
+
get list() {
|
|
3957
|
+
return this.parent.asViewOperationList();
|
|
3958
|
+
}
|
|
3959
|
+
|
|
3960
|
+
getOrAddAnnotations() {
|
|
3961
|
+
return this.view.getOrAddAnnotations();
|
|
3962
|
+
}
|
|
3963
|
+
|
|
3964
|
+
getInheritedTag(prefix: RegExp | string = '# ') {
|
|
3965
|
+
return tagFromAnnotations(prefix, this.view.getInheritedAnnotations());
|
|
3966
|
+
}
|
|
3967
|
+
|
|
3968
|
+
getIntrinsicTag(prefix: RegExp | string = '# ') {
|
|
3969
|
+
return this.annotations?.getIntrinsicTag(prefix) ?? new Tag();
|
|
3970
|
+
}
|
|
3971
|
+
|
|
3972
|
+
getTag(prefix: RegExp | string = '# ') {
|
|
3973
|
+
return this.annotations?.getTag(prefix) ?? this.getInheritedTag(prefix);
|
|
3974
|
+
}
|
|
3975
|
+
|
|
3976
|
+
setTagProperty(path: Path, value: TagSetValue = null, prefix = '# ') {
|
|
3977
|
+
this.getOrAddAnnotations().setTagProperty(path, value, prefix);
|
|
3978
|
+
}
|
|
3979
|
+
|
|
3980
|
+
removeTagProperty(path: Path, prefix = '# ') {
|
|
3981
|
+
if (!this.getTag().has(...path)) return;
|
|
3982
|
+
this.getOrAddAnnotations().removeTagProperty(path, prefix);
|
|
3983
|
+
}
|
|
3984
|
+
|
|
3985
|
+
delete() {
|
|
3986
|
+
this.list.remove(this);
|
|
3987
|
+
}
|
|
3988
|
+
|
|
3989
|
+
/**
|
|
3990
|
+
* Renames the nest item. If the view's name matches the given name,
|
|
3991
|
+
* removes the `name is` part.
|
|
3992
|
+
*
|
|
3993
|
+
* ```
|
|
3994
|
+
* run: flights -> { nest: by_carrier }
|
|
3995
|
+
* ```
|
|
3996
|
+
* ```ts
|
|
3997
|
+
* nest.rename("by_carrier_2");
|
|
3998
|
+
* ```
|
|
3999
|
+
* ```
|
|
4000
|
+
* run: flights -> { nest: by_carrier_2 is by_carrier }
|
|
4001
|
+
* ```
|
|
4002
|
+
*
|
|
4003
|
+
* ```
|
|
4004
|
+
* run: flights -> { nest: by_carrier_2 is by_carrier }
|
|
4005
|
+
* ```
|
|
4006
|
+
* ```ts
|
|
4007
|
+
* nest.rename("by_carrier");
|
|
4008
|
+
* ```
|
|
4009
|
+
* ```
|
|
4010
|
+
* run: flights -> { nest: by_carrier }
|
|
4011
|
+
* ```
|
|
4012
|
+
*
|
|
4013
|
+
* ```
|
|
4014
|
+
* run: flights -> {
|
|
4015
|
+
* nest: by_carrier is {
|
|
4016
|
+
* group_by: carrier
|
|
4017
|
+
* }
|
|
4018
|
+
* }
|
|
4019
|
+
* ```
|
|
4020
|
+
* ```ts
|
|
4021
|
+
* nest.rename("by_carrier_2");
|
|
4022
|
+
* ```
|
|
4023
|
+
* ```
|
|
4024
|
+
* run: flights -> {
|
|
4025
|
+
* nest: by_carrier_2 is {
|
|
4026
|
+
* group_by: carrier
|
|
4027
|
+
* }
|
|
4028
|
+
* }
|
|
4029
|
+
* ```
|
|
4030
|
+
*
|
|
4031
|
+
* @param name The new name
|
|
4032
|
+
*/
|
|
4033
|
+
rename(name: string) {
|
|
4034
|
+
this.list.segment.renameField(this, name);
|
|
4035
|
+
}
|
|
4036
|
+
|
|
4037
|
+
getFieldInfo(): Malloy.FieldInfo {
|
|
4038
|
+
return {
|
|
4039
|
+
kind: 'view',
|
|
4040
|
+
name: this.name,
|
|
4041
|
+
definition: this.view.build(),
|
|
4042
|
+
schema: this.view.getOutputSchema(),
|
|
4043
|
+
};
|
|
4044
|
+
}
|
|
4045
|
+
|
|
4046
|
+
/**
|
|
4047
|
+
* @internal
|
|
4048
|
+
*/
|
|
4049
|
+
static fromReference(
|
|
4050
|
+
name: string,
|
|
4051
|
+
path: string[] | undefined,
|
|
4052
|
+
rename: string | undefined
|
|
4053
|
+
) {
|
|
4054
|
+
return new ASTNestViewOperation({
|
|
4055
|
+
kind: 'nest',
|
|
4056
|
+
name: rename,
|
|
4057
|
+
view: {
|
|
4058
|
+
definition: {
|
|
4059
|
+
kind: 'view_reference',
|
|
4060
|
+
name,
|
|
4061
|
+
path,
|
|
4062
|
+
},
|
|
4063
|
+
},
|
|
4064
|
+
});
|
|
4065
|
+
}
|
|
4066
|
+
}
|
|
4067
|
+
|
|
4068
|
+
export class ASTWhereViewOperation extends ASTObjectNode<
|
|
4069
|
+
Malloy.ViewOperationWithWhere,
|
|
4070
|
+
{
|
|
4071
|
+
kind: 'where';
|
|
4072
|
+
filter: ASTFilter;
|
|
4073
|
+
}
|
|
4074
|
+
> {
|
|
4075
|
+
readonly kind: Malloy.ViewOperationType = 'nest';
|
|
4076
|
+
constructor(public node: Malloy.ViewOperationWithWhere) {
|
|
4077
|
+
super(node, {
|
|
4078
|
+
kind: 'where',
|
|
4079
|
+
filter: ASTFilter.from(node.filter),
|
|
4080
|
+
});
|
|
4081
|
+
}
|
|
4082
|
+
|
|
4083
|
+
get filter() {
|
|
4084
|
+
return this.children.filter;
|
|
4085
|
+
}
|
|
4086
|
+
|
|
4087
|
+
/**
|
|
4088
|
+
* @internal
|
|
4089
|
+
*/
|
|
4090
|
+
get list() {
|
|
4091
|
+
return this.parent.asViewOperationList();
|
|
4092
|
+
}
|
|
4093
|
+
|
|
4094
|
+
delete() {
|
|
4095
|
+
this.list.remove(this);
|
|
4096
|
+
}
|
|
4097
|
+
}
|
|
4098
|
+
|
|
4099
|
+
export type ASTFilter = ASTFilterWithFilterString;
|
|
4100
|
+
export const ASTFilter = {
|
|
4101
|
+
from(filter: Malloy.Filter) {
|
|
4102
|
+
return new ASTFilterWithFilterString(filter);
|
|
4103
|
+
},
|
|
4104
|
+
};
|
|
4105
|
+
|
|
4106
|
+
export class ASTFilterWithFilterString extends ASTObjectNode<
|
|
4107
|
+
Malloy.FilterWithFilterString,
|
|
4108
|
+
{
|
|
4109
|
+
kind: 'filter_string';
|
|
4110
|
+
field_reference: ASTFieldReference;
|
|
4111
|
+
filter: string;
|
|
4112
|
+
}
|
|
4113
|
+
> {
|
|
4114
|
+
readonly kind: Malloy.FilterType = 'filter_string';
|
|
4115
|
+
constructor(public node: Malloy.FilterWithFilterString) {
|
|
4116
|
+
super(node, {
|
|
4117
|
+
kind: 'filter_string',
|
|
4118
|
+
field_reference: new ASTFieldReference(node.field_reference),
|
|
4119
|
+
filter: node.filter,
|
|
4120
|
+
});
|
|
4121
|
+
}
|
|
4122
|
+
|
|
4123
|
+
get fieldReference() {
|
|
4124
|
+
return this.children.field_reference;
|
|
4125
|
+
}
|
|
4126
|
+
|
|
4127
|
+
get filterString() {
|
|
4128
|
+
return this.children.filter;
|
|
4129
|
+
}
|
|
4130
|
+
|
|
4131
|
+
set filterString(filter: string) {
|
|
4132
|
+
this.edit();
|
|
4133
|
+
this.children.filter = filter;
|
|
4134
|
+
}
|
|
4135
|
+
|
|
4136
|
+
setFilterString(filterString: string) {
|
|
4137
|
+
const kind = this.getFilterType();
|
|
4138
|
+
parseFilter(this.filterString, kind);
|
|
4139
|
+
this.filterString = filterString;
|
|
4140
|
+
}
|
|
4141
|
+
|
|
4142
|
+
getFieldInfo() {
|
|
4143
|
+
const field = this.fieldReference.getFieldInfo();
|
|
4144
|
+
if (field.kind !== 'dimension' && field.kind !== 'measure') {
|
|
4145
|
+
throw new Error('Invalid field type for filter with filter string');
|
|
4146
|
+
}
|
|
4147
|
+
return field;
|
|
4148
|
+
}
|
|
4149
|
+
|
|
4150
|
+
getFilterType(): 'string' | 'boolean' | 'number' | 'date' | 'other' {
|
|
4151
|
+
const fieldInfo = this.getFieldInfo();
|
|
4152
|
+
return getFilterType(fieldInfo);
|
|
4153
|
+
}
|
|
4154
|
+
|
|
4155
|
+
setFilter(filter: ParsedFilter) {
|
|
4156
|
+
const kind = this.getFilterType();
|
|
4157
|
+
if (kind !== filter.kind) {
|
|
4158
|
+
throw new Error(
|
|
4159
|
+
`Invalid filter: expected type ${kind}, got ${filter.kind}`
|
|
4160
|
+
);
|
|
4161
|
+
}
|
|
4162
|
+
this.filterString = serializeFilter(filter);
|
|
4163
|
+
}
|
|
4164
|
+
|
|
4165
|
+
getFilter(): ParsedFilter {
|
|
4166
|
+
const kind = this.getFilterType();
|
|
4167
|
+
return parseFilter(this.filterString, kind);
|
|
4168
|
+
}
|
|
4169
|
+
}
|
|
4170
|
+
|
|
4171
|
+
export class ASTView
|
|
4172
|
+
extends ASTObjectNode<
|
|
4173
|
+
Malloy.View,
|
|
4174
|
+
{
|
|
4175
|
+
definition: ASTViewDefinition;
|
|
4176
|
+
annotations?: ASTAnnotationList;
|
|
4177
|
+
}
|
|
4178
|
+
>
|
|
4179
|
+
implements IASTAnnotatable
|
|
4180
|
+
{
|
|
4181
|
+
constructor(public node: Malloy.View) {
|
|
4182
|
+
super(node, {
|
|
4183
|
+
definition: ASTViewDefinition.from(node.definition),
|
|
4184
|
+
annotations: node.annotations && new ASTAnnotationList(node.annotations),
|
|
4185
|
+
});
|
|
4186
|
+
}
|
|
4187
|
+
|
|
4188
|
+
get definition() {
|
|
4189
|
+
return this.children.definition;
|
|
4190
|
+
}
|
|
4191
|
+
|
|
4192
|
+
set definition(definition: ASTViewDefinition) {
|
|
4193
|
+
this.edit();
|
|
4194
|
+
this.children.definition = definition;
|
|
4195
|
+
definition.parent = this;
|
|
4196
|
+
}
|
|
4197
|
+
|
|
4198
|
+
get name() {
|
|
4199
|
+
return this.definition.getImplicitName();
|
|
4200
|
+
}
|
|
4201
|
+
|
|
4202
|
+
public getOrAddDefaultSegment(): ASTSegmentViewDefinition {
|
|
4203
|
+
return this.definition.getOrAddDefaultSegment();
|
|
4204
|
+
}
|
|
4205
|
+
|
|
4206
|
+
/**
|
|
4207
|
+
* @internal
|
|
4208
|
+
*/
|
|
4209
|
+
get nest() {
|
|
4210
|
+
return this.parent.asNestViewOperation();
|
|
4211
|
+
}
|
|
4212
|
+
|
|
4213
|
+
getInputSchema() {
|
|
4214
|
+
return this.nest.list.segment.getInputSchema();
|
|
4215
|
+
}
|
|
4216
|
+
|
|
4217
|
+
getOutputSchema() {
|
|
4218
|
+
return this.definition.getOutputSchema();
|
|
4219
|
+
}
|
|
4220
|
+
|
|
4221
|
+
/**
|
|
4222
|
+
* @internal
|
|
4223
|
+
*/
|
|
4224
|
+
propagateUp(f: PropagationFunction): void {
|
|
4225
|
+
this.propagateDown(f);
|
|
4226
|
+
}
|
|
4227
|
+
|
|
4228
|
+
/**
|
|
4229
|
+
* @internal
|
|
4230
|
+
*/
|
|
4231
|
+
propagateDown(f: PropagationFunction): void {
|
|
4232
|
+
f(this.definition);
|
|
4233
|
+
this.definition.propagateDown(f);
|
|
4234
|
+
}
|
|
4235
|
+
|
|
4236
|
+
reorderFields(names: string[]): void {
|
|
4237
|
+
if (this.definition instanceof ASTSegmentViewDefinition) {
|
|
4238
|
+
this.definition.reorderFields(names);
|
|
4239
|
+
} else {
|
|
4240
|
+
this.getOrAddAnnotations().setTagProperty(['field_order'], names);
|
|
4241
|
+
}
|
|
4242
|
+
}
|
|
4243
|
+
|
|
4244
|
+
get annotations() {
|
|
4245
|
+
return this.children.annotations;
|
|
4246
|
+
}
|
|
4247
|
+
|
|
4248
|
+
getOrAddAnnotations() {
|
|
4249
|
+
if (this.annotations) return this.annotations;
|
|
4250
|
+
this.edit();
|
|
4251
|
+
const annotations = new ASTAnnotationList([]);
|
|
4252
|
+
this.children.annotations = annotations;
|
|
4253
|
+
return annotations;
|
|
4254
|
+
}
|
|
4255
|
+
|
|
4256
|
+
getInheritedAnnotations(): Malloy.Annotation[] {
|
|
4257
|
+
return this.definition.getInheritedAnnotations();
|
|
4258
|
+
}
|
|
4259
|
+
|
|
4260
|
+
getInheritedTag(prefix: RegExp | string = '# ') {
|
|
4261
|
+
return tagFromAnnotations(prefix, this.getInheritedAnnotations());
|
|
4262
|
+
}
|
|
4263
|
+
|
|
4264
|
+
getIntrinsicTag(prefix: RegExp | string = '# ') {
|
|
4265
|
+
return this.annotations?.getIntrinsicTag(prefix) ?? new Tag();
|
|
4266
|
+
}
|
|
4267
|
+
|
|
4268
|
+
getTag(prefix: RegExp | string = '# ') {
|
|
4269
|
+
return this.annotations?.getTag(prefix) ?? this.getInheritedTag(prefix);
|
|
4270
|
+
}
|
|
4271
|
+
|
|
4272
|
+
setTagProperty(path: Path, value: TagSetValue = null, prefix = '# ') {
|
|
4273
|
+
this.getOrAddAnnotations().setTagProperty(path, value, prefix);
|
|
4274
|
+
}
|
|
4275
|
+
|
|
4276
|
+
removeTagProperty(path: Path, prefix = '# ') {
|
|
4277
|
+
if (!this.getTag().has(...path)) return;
|
|
4278
|
+
this.getOrAddAnnotations().removeTagProperty(path, prefix);
|
|
4279
|
+
}
|
|
4280
|
+
}
|
|
4281
|
+
|
|
4282
|
+
export class ASTLimitViewOperation extends ASTObjectNode<
|
|
4283
|
+
Malloy.ViewOperationWithLimit,
|
|
4284
|
+
{
|
|
4285
|
+
kind: 'limit';
|
|
4286
|
+
limit: number;
|
|
4287
|
+
}
|
|
4288
|
+
> {
|
|
4289
|
+
readonly kind: Malloy.ViewOperationType = 'limit';
|
|
4290
|
+
|
|
4291
|
+
get limit() {
|
|
4292
|
+
return this.children.limit;
|
|
4293
|
+
}
|
|
4294
|
+
|
|
4295
|
+
set limit(limit: number) {
|
|
4296
|
+
ASTLimitViewOperation.validateLimit(limit);
|
|
4297
|
+
this.edit();
|
|
4298
|
+
this.children.limit = limit;
|
|
4299
|
+
}
|
|
4300
|
+
|
|
4301
|
+
constructor(public node: Malloy.ViewOperationWithLimit) {
|
|
4302
|
+
super(node, {
|
|
4303
|
+
kind: node.kind,
|
|
4304
|
+
limit: node.limit,
|
|
4305
|
+
});
|
|
4306
|
+
}
|
|
4307
|
+
|
|
4308
|
+
/**
|
|
4309
|
+
* @internal
|
|
4310
|
+
*/
|
|
4311
|
+
get list() {
|
|
4312
|
+
return this.parent.asViewOperationList();
|
|
4313
|
+
}
|
|
4314
|
+
|
|
4315
|
+
delete() {
|
|
4316
|
+
this.list.remove(this);
|
|
4317
|
+
}
|
|
4318
|
+
|
|
4319
|
+
/**
|
|
4320
|
+
* @internal
|
|
4321
|
+
*/
|
|
4322
|
+
static validateLimit(limit: number) {
|
|
4323
|
+
if (!Number.isInteger(limit)) {
|
|
4324
|
+
throw new Error('Limit must be an integer');
|
|
4325
|
+
}
|
|
4326
|
+
}
|
|
4327
|
+
}
|
|
4328
|
+
|
|
4329
|
+
function fieldTypeToAction(type: Malloy.FieldInfoType): string {
|
|
4330
|
+
switch (type) {
|
|
4331
|
+
case 'dimension':
|
|
4332
|
+
return 'group by';
|
|
4333
|
+
case 'measure':
|
|
4334
|
+
return 'aggregate';
|
|
4335
|
+
case 'view':
|
|
4336
|
+
return 'nest';
|
|
4337
|
+
case 'join':
|
|
4338
|
+
return 'join';
|
|
4339
|
+
}
|
|
4340
|
+
}
|
|
4341
|
+
|
|
4342
|
+
function fieldTypeName(type: Malloy.FieldInfoType): string {
|
|
4343
|
+
return type;
|
|
4344
|
+
}
|
|
4345
|
+
|
|
4346
|
+
type ASTAnnotationListParent = ASTQuery | ASTField | ASTView;
|
|
4347
|
+
|
|
4348
|
+
export class ASTAnnotationList extends ASTListNode<
|
|
4349
|
+
Malloy.Annotation,
|
|
4350
|
+
ASTAnnotation
|
|
4351
|
+
> {
|
|
4352
|
+
constructor(annotations: Malloy.Annotation[]) {
|
|
4353
|
+
super(
|
|
4354
|
+
annotations,
|
|
4355
|
+
annotations.map(p => new ASTAnnotation(p))
|
|
4356
|
+
);
|
|
4357
|
+
}
|
|
4358
|
+
|
|
4359
|
+
get items() {
|
|
4360
|
+
return this.children;
|
|
4361
|
+
}
|
|
4362
|
+
|
|
4363
|
+
getInheritedAnnotations(): Malloy.Annotation[] {
|
|
4364
|
+
const parent = this.parent as ASTAnnotationListParent;
|
|
4365
|
+
return parent.getInheritedAnnotations();
|
|
4366
|
+
}
|
|
4367
|
+
|
|
4368
|
+
getIntrinsicTag(prefix: RegExp | string = '# '): Tag {
|
|
4369
|
+
return tagFromAnnotations(prefix, this.items);
|
|
4370
|
+
}
|
|
4371
|
+
|
|
4372
|
+
getTag(prefix: RegExp | string = '# '): Tag {
|
|
4373
|
+
const extending = Tag.fromTagLines(
|
|
4374
|
+
this.getInheritedAnnotations().map(a => a.value)
|
|
4375
|
+
).tag;
|
|
4376
|
+
return tagFromAnnotations(prefix, this.items, extending);
|
|
4377
|
+
}
|
|
4378
|
+
|
|
4379
|
+
get annotations() {
|
|
4380
|
+
return this.children.map(astAnnotation => astAnnotation.node);
|
|
4381
|
+
}
|
|
4382
|
+
|
|
4383
|
+
setTagProperty(path: Path, value: TagSetValue = null, prefix = '# ') {
|
|
4384
|
+
let firstMatch: ASTAnnotation | undefined = undefined;
|
|
4385
|
+
for (let i = this.items.length - 1; i >= 0; i--) {
|
|
4386
|
+
const annotation = this.index(i);
|
|
4387
|
+
if (!annotation.hasPrefix(prefix)) continue;
|
|
4388
|
+
firstMatch = annotation;
|
|
4389
|
+
if (annotation.hasIntrinsicTagProperty(path)) {
|
|
4390
|
+
annotation.setTagProperty(path, value);
|
|
4391
|
+
return;
|
|
4392
|
+
}
|
|
4393
|
+
}
|
|
4394
|
+
if (firstMatch) {
|
|
4395
|
+
firstMatch.setTagProperty(path, value);
|
|
4396
|
+
} else {
|
|
4397
|
+
this.add(
|
|
4398
|
+
new ASTAnnotation({
|
|
4399
|
+
value: new Tag({prefix}).set(path, value).toString(),
|
|
4400
|
+
})
|
|
4401
|
+
);
|
|
4402
|
+
}
|
|
4403
|
+
}
|
|
4404
|
+
|
|
4405
|
+
removeTagProperty(path: Path, prefix = '# ') {
|
|
4406
|
+
let firstMatch: ASTAnnotation | undefined = undefined;
|
|
4407
|
+
for (let i = this.items.length - 1; i >= 0; i--) {
|
|
4408
|
+
const annotation = this.index(i);
|
|
4409
|
+
if (!annotation.hasPrefix(prefix)) continue;
|
|
4410
|
+
firstMatch = annotation;
|
|
4411
|
+
if (annotation.hasIntrinsicTagProperty(path)) {
|
|
4412
|
+
annotation.removeTagProperty(path);
|
|
4413
|
+
return;
|
|
4414
|
+
}
|
|
4415
|
+
}
|
|
4416
|
+
if (firstMatch) {
|
|
4417
|
+
firstMatch.removeTagProperty(path);
|
|
4418
|
+
} else {
|
|
4419
|
+
this.add(
|
|
4420
|
+
new ASTAnnotation({value: new Tag({prefix}).unset(...path).toString()})
|
|
4421
|
+
);
|
|
4422
|
+
}
|
|
4423
|
+
}
|
|
4424
|
+
}
|
|
4425
|
+
|
|
4426
|
+
export class ASTAnnotation extends ASTObjectNode<
|
|
4427
|
+
Malloy.Annotation,
|
|
4428
|
+
{
|
|
4429
|
+
value: string;
|
|
4430
|
+
}
|
|
4431
|
+
> {
|
|
4432
|
+
readonly kind: Malloy.ViewOperationType = 'limit';
|
|
4433
|
+
|
|
4434
|
+
get value() {
|
|
4435
|
+
return this.children.value;
|
|
4436
|
+
}
|
|
4437
|
+
|
|
4438
|
+
set value(value: string) {
|
|
4439
|
+
this.edit();
|
|
4440
|
+
this.children.value = value;
|
|
4441
|
+
}
|
|
4442
|
+
|
|
4443
|
+
constructor(public node: Malloy.Annotation) {
|
|
4444
|
+
super(node, {
|
|
4445
|
+
value: node.value,
|
|
4446
|
+
});
|
|
4447
|
+
}
|
|
4448
|
+
|
|
4449
|
+
/**
|
|
4450
|
+
* @internal
|
|
4451
|
+
*/
|
|
4452
|
+
get list() {
|
|
4453
|
+
return this.parent.asAnnotationList();
|
|
4454
|
+
}
|
|
4455
|
+
|
|
4456
|
+
get index() {
|
|
4457
|
+
return this.list.indexOf(this);
|
|
4458
|
+
}
|
|
4459
|
+
|
|
4460
|
+
delete() {
|
|
4461
|
+
this.list.remove(this);
|
|
4462
|
+
}
|
|
4463
|
+
|
|
4464
|
+
getIntrinsicTag(): Tag {
|
|
4465
|
+
return Tag.fromTagLines([this.value]).tag ?? new Tag();
|
|
4466
|
+
}
|
|
4467
|
+
|
|
4468
|
+
getTag(): Tag {
|
|
4469
|
+
const extending =
|
|
4470
|
+
this.index === 0
|
|
4471
|
+
? Tag.fromTagLines(
|
|
4472
|
+
this.list.getInheritedAnnotations().map(a => a.value)
|
|
4473
|
+
).tag ?? new Tag()
|
|
4474
|
+
: this.list.index(this.index - 1).getTag();
|
|
4475
|
+
return Tag.fromTagLines([this.value], extending).tag ?? new Tag();
|
|
4476
|
+
}
|
|
4477
|
+
|
|
4478
|
+
hasPrefix(prefix: string) {
|
|
4479
|
+
return this.value.startsWith(prefix);
|
|
4480
|
+
}
|
|
4481
|
+
|
|
4482
|
+
hasIntrinsicTagProperty(path: Path) {
|
|
4483
|
+
return this.getIntrinsicTag().has(...path);
|
|
4484
|
+
}
|
|
4485
|
+
|
|
4486
|
+
setTagProperty(path: Path, value: TagSetValue) {
|
|
4487
|
+
this.value = this.getTag().set(path, value).toString();
|
|
4488
|
+
}
|
|
4489
|
+
|
|
4490
|
+
removeTagProperty(path: Path) {
|
|
4491
|
+
this.value = this.getTag()
|
|
4492
|
+
.unset(...path)
|
|
4493
|
+
.toString();
|
|
4494
|
+
}
|
|
4495
|
+
}
|
|
4496
|
+
|
|
4497
|
+
function tagFromAnnotations(
|
|
4498
|
+
prefix: string | RegExp,
|
|
4499
|
+
annotations: Malloy.Annotation[] = [],
|
|
4500
|
+
inherited?: Tag
|
|
4501
|
+
) {
|
|
4502
|
+
const lines = annotations.map(a => a.value);
|
|
4503
|
+
const filteredLines = lines.filter(l =>
|
|
4504
|
+
typeof prefix === 'string' ? l.startsWith(prefix) : l.match(prefix)
|
|
4505
|
+
);
|
|
4506
|
+
return Tag.fromTagLines(filteredLines, inherited).tag ?? new Tag();
|
|
4507
|
+
}
|
|
4508
|
+
|
|
4509
|
+
function serializeFilter(filter: ParsedFilter) {
|
|
4510
|
+
switch (filter.kind) {
|
|
4511
|
+
case 'string':
|
|
4512
|
+
return new Filter.StringSerializer(filter.clauses).serialize();
|
|
4513
|
+
case 'number':
|
|
4514
|
+
return new Filter.NumberSerializer(filter.clauses).serialize();
|
|
4515
|
+
case 'boolean':
|
|
4516
|
+
return new Filter.BooleanSerializer(filter.clauses).serialize();
|
|
4517
|
+
case 'date':
|
|
4518
|
+
return new Filter.DateSerializer(filter.clauses).serialize();
|
|
4519
|
+
}
|
|
4520
|
+
}
|
|
4521
|
+
|
|
4522
|
+
function parseFilter(
|
|
4523
|
+
filterString: string,
|
|
4524
|
+
kind: 'string' | 'number' | 'boolean' | 'date' | 'other'
|
|
4525
|
+
) {
|
|
4526
|
+
function handleError(logs: Filter.FilterLog[]) {
|
|
4527
|
+
const errors = logs.filter(l => l.severity === 'error');
|
|
4528
|
+
if (errors.length === 0) return;
|
|
4529
|
+
throw new Error(`Invalid Malloy filter string: ${errors[0].message}`);
|
|
4530
|
+
}
|
|
4531
|
+
switch (kind) {
|
|
4532
|
+
case 'string': {
|
|
4533
|
+
const result = new Filter.StringParser(filterString).parse();
|
|
4534
|
+
handleError(result.logs);
|
|
4535
|
+
return {kind, clauses: result.clauses};
|
|
4536
|
+
}
|
|
4537
|
+
case 'number': {
|
|
4538
|
+
const result = new Filter.NumberParser(filterString).parse();
|
|
4539
|
+
handleError(result.logs);
|
|
4540
|
+
return {kind, clauses: result.clauses};
|
|
4541
|
+
}
|
|
4542
|
+
case 'boolean': {
|
|
4543
|
+
const result = new Filter.BooleanParser(filterString).parse();
|
|
4544
|
+
handleError(result.logs);
|
|
4545
|
+
return {kind, clauses: result.clauses};
|
|
4546
|
+
}
|
|
4547
|
+
case 'date': {
|
|
4548
|
+
const result = new Filter.DateParser(filterString).parse();
|
|
4549
|
+
handleError(result.logs);
|
|
4550
|
+
return {kind, clauses: result.clauses};
|
|
4551
|
+
}
|
|
4552
|
+
case 'other':
|
|
4553
|
+
throw new Error('Not implemented');
|
|
4554
|
+
}
|
|
4555
|
+
}
|
|
4556
|
+
|
|
4557
|
+
function sourceOrQueryToModelEntry(
|
|
4558
|
+
entry:
|
|
4559
|
+
| Malloy.ModelEntryValueWithSource
|
|
4560
|
+
| Malloy.ModelEntryValueWithQuery
|
|
4561
|
+
| Malloy.SourceInfo
|
|
4562
|
+
| Malloy.QueryInfo
|
|
4563
|
+
): Malloy.ModelEntryValueWithSource | Malloy.ModelEntryValueWithQuery {
|
|
4564
|
+
if ('kind' in entry) {
|
|
4565
|
+
return entry;
|
|
4566
|
+
} else {
|
|
4567
|
+
return {...entry, kind: 'source'};
|
|
4568
|
+
}
|
|
4569
|
+
}
|
|
4570
|
+
|
|
4571
|
+
function isDateTimeframe(
|
|
4572
|
+
timeframe: Malloy.TimestampTimeframe
|
|
4573
|
+
): timeframe is Malloy.DateTimeframe {
|
|
4574
|
+
switch (timeframe) {
|
|
4575
|
+
case 'year':
|
|
4576
|
+
case 'quarter':
|
|
4577
|
+
case 'month':
|
|
4578
|
+
case 'week':
|
|
4579
|
+
case 'day':
|
|
4580
|
+
return true;
|
|
4581
|
+
default:
|
|
4582
|
+
return false;
|
|
4583
|
+
}
|
|
4584
|
+
}
|
|
4585
|
+
|
|
4586
|
+
function digits(value: number, digits: number) {
|
|
4587
|
+
return value.toString().padStart(digits, '0');
|
|
4588
|
+
}
|
|
4589
|
+
|
|
4590
|
+
function serializeDateAsLiteral(date: Date): string {
|
|
4591
|
+
const year = digits(date.getUTCFullYear(), 2);
|
|
4592
|
+
const month = digits(date.getUTCMonth() + 1, 2);
|
|
4593
|
+
const day = digits(date.getUTCDate(), 2);
|
|
4594
|
+
const hour = digits(date.getUTCHours(), 2);
|
|
4595
|
+
const minute = digits(date.getUTCMinutes(), 2);
|
|
4596
|
+
const second = digits(date.getUTCSeconds(), 2);
|
|
4597
|
+
return `${year}-${month}-${day} ${hour}:${minute}:${second}`;
|
|
4598
|
+
}
|
|
4599
|
+
|
|
4600
|
+
function pathsMatch(a: string[] | undefined, b: string[] | undefined): boolean {
|
|
4601
|
+
const aOrEmpty = a ?? [];
|
|
4602
|
+
const bOrEmpty = b ?? [];
|
|
4603
|
+
return (
|
|
4604
|
+
aOrEmpty.length === bOrEmpty.length &&
|
|
4605
|
+
aOrEmpty.every((s, i) => s === bOrEmpty[i])
|
|
4606
|
+
);
|
|
4607
|
+
}
|
|
4608
|
+
|
|
4609
|
+
function getFilterType(
|
|
4610
|
+
fieldInfo: Malloy.FieldInfoWithDimension | Malloy.FieldInfoWithMeasure
|
|
4611
|
+
): 'string' | 'boolean' | 'number' | 'date' | 'other' {
|
|
4612
|
+
switch (fieldInfo.type.kind) {
|
|
4613
|
+
case 'string_type':
|
|
4614
|
+
return 'string';
|
|
4615
|
+
case 'boolean_type':
|
|
4616
|
+
return 'boolean';
|
|
4617
|
+
case 'number_type':
|
|
4618
|
+
return 'number';
|
|
4619
|
+
case 'date_type':
|
|
4620
|
+
case 'timestamp_type':
|
|
4621
|
+
return 'date';
|
|
4622
|
+
default:
|
|
4623
|
+
return 'other';
|
|
4624
|
+
}
|
|
4625
|
+
}
|
|
4626
|
+
|
|
4627
|
+
function validateFilter(
|
|
4628
|
+
field: Malloy.FieldInfo,
|
|
4629
|
+
filter: string | ParsedFilter
|
|
4630
|
+
) {
|
|
4631
|
+
if (field.kind !== 'dimension' && field.kind !== 'measure') {
|
|
4632
|
+
throw new Error(`Cannot filter by field of type ${field.kind}`);
|
|
4633
|
+
}
|
|
4634
|
+
const type = getFilterType(field);
|
|
4635
|
+
if (typeof filter === 'string') {
|
|
4636
|
+
parseFilter(filter, type);
|
|
4637
|
+
} else {
|
|
4638
|
+
if (filter.kind !== type) {
|
|
4639
|
+
throw new Error(
|
|
4640
|
+
`Invalid filter for field ${field.name}; expected type ${type}, but got ${filter.kind}`
|
|
4641
|
+
);
|
|
4642
|
+
}
|
|
4643
|
+
}
|
|
4644
|
+
}
|
|
4645
|
+
|
|
4646
|
+
function getInputSchemaFromViewParent(parent: ViewParent) {
|
|
4647
|
+
if (parent instanceof ASTArrowQueryDefinition) {
|
|
4648
|
+
return parent.getSourceInfo().schema;
|
|
4649
|
+
} else if (parent instanceof ASTRefinementQueryDefinition) {
|
|
4650
|
+
throw new Error('unimplemented');
|
|
4651
|
+
} else {
|
|
4652
|
+
return parent.getInputSchema();
|
|
4653
|
+
}
|
|
4654
|
+
}
|