@malloydata/malloy 0.0.88-dev231002231014 → 0.0.88-dev231002235732
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/dist/lang/ast/elements/define-query.d.ts +3 -3
- package/dist/lang/ast/elements/define-query.js +9 -4
- package/dist/lang/ast/elements/define-source.d.ts +3 -3
- package/dist/lang/ast/elements/define-source.js +12 -4
- package/dist/lang/ast/elements/pipeline-desc.d.ts +1 -0
- package/dist/lang/ast/elements/pipeline-desc.js +3 -0
- package/dist/lang/ast/elements/source-query-nodes.d.ts +72 -0
- package/dist/lang/ast/elements/source-query-nodes.js +368 -0
- package/dist/lang/ast/elements/source-query.d.ts +11 -0
- package/dist/lang/ast/{query-elements/run-query.js → elements/source-query.js} +32 -21
- package/dist/lang/ast/index.d.ts +2 -2
- package/dist/lang/ast/index.js +2 -2
- package/dist/lang/ast/query-elements/anonymous-query.d.ts +3 -3
- package/dist/lang/ast/query-elements/anonymous-query.js +15 -8
- package/dist/lang/ast/query-elements/existing-query.js +1 -1
- package/dist/lang/ast/query-items/field-references.d.ts +4 -0
- package/dist/lang/ast/query-items/field-references.js +11 -1
- package/dist/lang/ast/query-items/typecheck_utils.js +5 -5
- package/dist/lang/ast/query-properties/joins.d.ts +7 -5
- package/dist/lang/ast/query-properties/joins.js +22 -8
- package/dist/lang/ast/sources/sql-source.js +5 -0
- package/dist/lang/ast/sql-elements/sql-string.d.ts +2 -2
- package/dist/lang/ast/sql-elements/sql-string.js +19 -9
- package/dist/lang/ast/types/malloy-element.d.ts +3 -1
- package/dist/lang/ast/types/malloy-element.js +16 -5
- package/dist/lang/lib/Malloy/MalloyParser.d.ts +179 -184
- package/dist/lang/lib/Malloy/MalloyParser.js +3141 -2928
- package/dist/lang/lib/Malloy/MalloyParserListener.d.ts +147 -199
- package/dist/lang/lib/Malloy/MalloyParserVisitor.d.ts +88 -120
- package/dist/lang/malloy-to-ast.d.ts +16 -15
- package/dist/lang/malloy-to-ast.js +147 -141
- package/dist/lang/parse-tree-walkers/document-symbol-walker.js +1 -1
- package/dist/lang/parse-utils.js +2 -2
- package/dist/lang/test/annotation.spec.js +35 -46
- package/dist/lang/test/document-symbol-walker.spec.js +2 -2
- package/dist/lang/test/expressions.spec.js +16 -16
- package/dist/lang/test/imports.spec.js +10 -8
- package/dist/lang/test/literals.spec.js +4 -4
- package/dist/lang/test/locations.spec.js +82 -80
- package/dist/lang/test/parse-expects.js +5 -1
- package/dist/lang/test/parse.spec.js +173 -117
- package/dist/lang/test/query.spec.js +199 -203
- package/dist/lang/test/source.spec.js +54 -45
- package/dist/lang/test/sql-block.spec.js +19 -12
- package/dist/lang/test/test-translator.js +8 -5
- package/dist/malloy.js +2 -2
- package/package.json +1 -2
- package/dist/lang/ast/query-elements/run-query.d.ts +0 -13
- package/dist/lang/ast/sources/named-source-or-query.d.ts +0 -10
- package/dist/lang/ast/sources/named-source-or-query.js +0 -71
|
@@ -28,10 +28,10 @@ require("./parse-expects");
|
|
|
28
28
|
describe('model statements', () => {
|
|
29
29
|
describe('table method', () => {
|
|
30
30
|
test('table method works', () => {
|
|
31
|
-
expect("source: testA is
|
|
31
|
+
expect("source: testA is _db_.table('aTable')").toTranslate();
|
|
32
32
|
});
|
|
33
33
|
test('table method works with quoted connection name', () => {
|
|
34
|
-
expect("source: testA is `
|
|
34
|
+
expect("source: testA is `_db_`.table('aTable')").toTranslate();
|
|
35
35
|
});
|
|
36
36
|
test('table method fails when connection name is wrong', () => {
|
|
37
37
|
expect("source: testA is bad_conn.table('aTable')").not.toTranslate();
|
|
@@ -52,51 +52,51 @@ describe('model statements', () => {
|
|
|
52
52
|
});
|
|
53
53
|
test('table function is deprecated', () => {
|
|
54
54
|
expect(`##! m4warnings
|
|
55
|
-
source: testA is table('
|
|
55
|
+
source: testA is table('_db_:aTable')
|
|
56
56
|
`).toTranslateWithWarnings("`table('connection_name:table_path')` is deprecated; use `connection_name.table('table_path')`");
|
|
57
57
|
});
|
|
58
58
|
});
|
|
59
59
|
test('errors on redefinition of query', () => {
|
|
60
|
-
expect('query: q1 is a -> {
|
|
60
|
+
expect('query: q1 is a -> { select: * }, q1 is a -> { select: * }').translationToFailWith("'q1' is already defined, cannot redefine");
|
|
61
61
|
});
|
|
62
62
|
});
|
|
63
63
|
describe('error handling', () => {
|
|
64
64
|
test('field and query with same name does not overflow', () => {
|
|
65
65
|
expect(`
|
|
66
|
-
source: flights is table('malloytest.flights') {
|
|
67
|
-
|
|
66
|
+
source: flights is _db_.table('malloytest.flights') extend {
|
|
67
|
+
view: carrier is { group_by: carrier }
|
|
68
68
|
}
|
|
69
69
|
`).translationToFailWith("Cannot redefine 'carrier'");
|
|
70
70
|
});
|
|
71
71
|
test('redefine source', () => {
|
|
72
72
|
expect((0, test_translator_1.markSource) `
|
|
73
|
-
source: airports is table('malloytest.airports')
|
|
73
|
+
source: airports is _db_.table('malloytest.airports') extend {
|
|
74
74
|
primary_key: code
|
|
75
75
|
}
|
|
76
|
-
source: airports is table('malloytest.airports')
|
|
76
|
+
source: airports is _db_.table('malloytest.airports') extend {
|
|
77
77
|
primary_key: code
|
|
78
78
|
}
|
|
79
79
|
`).translationToFailWith("Cannot redefine 'airports'");
|
|
80
80
|
});
|
|
81
81
|
test('query from undefined source', () => {
|
|
82
|
-
expect((0, test_translator_1.markSource) `
|
|
82
|
+
expect((0, test_translator_1.markSource) `run: ${'x'}->{ select: y }`).translationToFailWith("Reference to undefined object 'x'");
|
|
83
83
|
});
|
|
84
84
|
test('query with expression from undefined source', () => {
|
|
85
85
|
// Regression check: Once upon a time this died with an exception even
|
|
86
86
|
// when "query: x->{ group_by: y}" (above) generated the correct error.
|
|
87
|
-
expect((0, test_translator_1.markSource) `
|
|
87
|
+
expect((0, test_translator_1.markSource) `run: ${'x'}->{ select: y is z / 2 }`).translationToFailWith("Reference to undefined object 'x'");
|
|
88
88
|
});
|
|
89
89
|
test('join reference before definition', () => {
|
|
90
90
|
expect((0, test_translator_1.markSource) `
|
|
91
|
-
source: newAB is a { join_one: newB is ${'bb'} on astring }
|
|
91
|
+
source: newAB is a extend { join_one: newB is ${'bb'} on astring }
|
|
92
92
|
source: newB is b
|
|
93
|
-
`).translationToFailWith("
|
|
93
|
+
`).translationToFailWith("Reference to undefined object 'bb'");
|
|
94
94
|
});
|
|
95
95
|
test('non-rename rename', () => {
|
|
96
|
-
expect('source: na is a { rename: astr is astr }').translationToFailWith("Can't rename field to itself");
|
|
96
|
+
expect('source: na is a extend { rename: astr is astr }').translationToFailWith("Can't rename field to itself");
|
|
97
97
|
});
|
|
98
98
|
test('reference to field in its definition', () => {
|
|
99
|
-
expect('source: na is a { dimension: ustr is upper(ustr) } ').translationToFailWith("Circular reference to 'ustr' in definition");
|
|
99
|
+
expect('source: na is a extend { dimension: ustr is upper(ustr) } ').translationToFailWith("Circular reference to 'ustr' in definition");
|
|
100
100
|
});
|
|
101
101
|
test('empty model', () => {
|
|
102
102
|
expect('').toTranslate();
|
|
@@ -105,51 +105,53 @@ describe('error handling', () => {
|
|
|
105
105
|
expect('\n').toTranslate();
|
|
106
106
|
});
|
|
107
107
|
test('query without fields', () => {
|
|
108
|
-
expect('
|
|
108
|
+
expect('run: a -> { top: 5 }').translationToFailWith("Can't determine query type (group_by/aggregate/nest,project,index)");
|
|
109
109
|
});
|
|
110
110
|
test('refine cannot change query type', () => {
|
|
111
|
-
expect('
|
|
111
|
+
expect('run: ab -> aturtle + { select: astr }').translationToFailWith(/Not legal in grouping query/);
|
|
112
112
|
});
|
|
113
113
|
test('undefined field ref in query', () => {
|
|
114
|
-
expect('
|
|
114
|
+
expect('run: ab -> { aggregate: xyzzy }').translationToFailWith("'xyzzy' is not defined");
|
|
115
115
|
});
|
|
116
116
|
test('query on source with errors', () => {
|
|
117
117
|
expect((0, test_translator_1.markSource) `
|
|
118
|
-
source: na is a { join_one: ${'n'} on astr }
|
|
119
|
-
`).translationToFailWith("
|
|
118
|
+
source: na is a extend { join_one: ${'n'} on astr }
|
|
119
|
+
`).translationToFailWith("Reference to undefined object 'n'");
|
|
120
120
|
});
|
|
121
121
|
test('detect duplicate output field names', () => {
|
|
122
|
-
expect((0, test_translator_1.markSource) `
|
|
122
|
+
expect((0, test_translator_1.markSource) `run: ab -> { group_by: astr, ${'astr'} }`).translationToFailWith("Output already has a field named 'astr'");
|
|
123
123
|
});
|
|
124
124
|
test('detect join tail overlap existing ref', () => {
|
|
125
|
-
expect((0, test_translator_1.markSource) `
|
|
125
|
+
expect((0, test_translator_1.markSource) `run: ab -> { group_by: astr, ${'b.astr'} }`).translationToFailWith("Output already has a field named 'astr'");
|
|
126
126
|
});
|
|
127
127
|
test('undefined in expression with regex compare', () => {
|
|
128
128
|
expect(`
|
|
129
|
-
source: c is a {
|
|
129
|
+
source: c is a extend {
|
|
130
130
|
dimension: d is meaning_of_life ~ r'(forty two|fifty four)'
|
|
131
131
|
}
|
|
132
132
|
`).translationToFailWith("'meaning_of_life' is not defined");
|
|
133
133
|
});
|
|
134
134
|
test('detect output collision on join references', () => {
|
|
135
135
|
expect(`
|
|
136
|
-
|
|
136
|
+
run: ab -> {
|
|
137
137
|
group_by: astr, b.astr
|
|
138
138
|
}
|
|
139
139
|
`).translationToFailWith("Output already has a field named 'astr'");
|
|
140
140
|
});
|
|
141
141
|
test('rejoin a query is renamed', () => {
|
|
142
|
+
// should not use from()
|
|
142
143
|
expect(`
|
|
144
|
+
##! -m4warnings
|
|
143
145
|
source: querySrc is from(
|
|
144
|
-
table('malloytest.flights')->{
|
|
146
|
+
_db_.table('malloytest.flights')->{
|
|
145
147
|
group_by: origin
|
|
146
148
|
nest: nested is { group_by: destination }
|
|
147
149
|
}
|
|
148
150
|
)
|
|
149
151
|
|
|
150
|
-
source: refineQuerySrc is querySrc {
|
|
152
|
+
source: refineQuerySrc is querySrc extend {
|
|
151
153
|
join_one: rejoin is querySrc on 7=8
|
|
152
|
-
|
|
154
|
+
view: broken is {
|
|
153
155
|
group_by: rejoin.nested.destination
|
|
154
156
|
}
|
|
155
157
|
}
|
|
@@ -159,7 +161,7 @@ describe('error handling', () => {
|
|
|
159
161
|
expect('}%').translationToFailWith(/extraneous input '}%' expecting/);
|
|
160
162
|
});
|
|
161
163
|
test('bad sql in sql block', () => {
|
|
162
|
-
const badSelect = (0, test_translator_1.model) `
|
|
164
|
+
const badSelect = (0, test_translator_1.model) `source: someName is _db_.sql(")")`;
|
|
163
165
|
const badTrans = badSelect.translator;
|
|
164
166
|
expect(badSelect).toParse();
|
|
165
167
|
const needSchema = badTrans.translate();
|
|
@@ -168,12 +170,12 @@ describe('error handling', () => {
|
|
|
168
170
|
badTrans.update({
|
|
169
171
|
errors: {
|
|
170
172
|
compileSQL: {
|
|
171
|
-
[needSchema.compileSQL.name]: 'ZZZZ',
|
|
173
|
+
[needSchema.compileSQL.name]: 'Nobody ZZZZ',
|
|
172
174
|
},
|
|
173
175
|
},
|
|
174
176
|
});
|
|
175
177
|
}
|
|
176
|
-
expect(badTrans).translationToFailWith('Invalid SQL, ZZZZ');
|
|
178
|
+
expect(badTrans).translationToFailWith('Invalid SQL, Nobody ZZZZ');
|
|
177
179
|
});
|
|
178
180
|
});
|
|
179
181
|
describe('translate imports', () => {
|
|
@@ -184,7 +186,7 @@ describe('translate imports', () => {
|
|
|
184
186
|
expect(resp1.final).toBeUndefined();
|
|
185
187
|
m.update({
|
|
186
188
|
urls: {
|
|
187
|
-
'internal://test/langtests/myfile.malloy': 'query: ab -> {
|
|
189
|
+
'internal://test/langtests/myfile.malloy': 'query: ab -> { select: * }',
|
|
188
190
|
},
|
|
189
191
|
});
|
|
190
192
|
const resp2 = m.translate();
|
|
@@ -207,8 +209,8 @@ describe('translation need error locations', () => {
|
|
|
207
209
|
});
|
|
208
210
|
test('sql struct error location', () => {
|
|
209
211
|
const source = (0, test_translator_1.markSource) `
|
|
210
|
-
|
|
211
|
-
|
|
212
|
+
source: bad_sql is _db_.sql(${'"""BAD_SQL"""'})
|
|
213
|
+
run: bad_sql -> { select: * }
|
|
212
214
|
`;
|
|
213
215
|
const m = new test_translator_1.TestTranslator(source.code);
|
|
214
216
|
expect(m).toParse();
|
|
@@ -223,7 +225,7 @@ describe('translation need error locations', () => {
|
|
|
223
225
|
});
|
|
224
226
|
test('table struct error location', () => {
|
|
225
227
|
const source = (0, test_translator_1.model) `
|
|
226
|
-
source: bad_explore is ${"table('malloy-data.bad.table')"}
|
|
228
|
+
source: bad_explore is ${"_db_.table('malloy-data.bad.table')"}
|
|
227
229
|
`;
|
|
228
230
|
const m = source.translator;
|
|
229
231
|
const result = m.translate();
|
|
@@ -238,8 +240,8 @@ describe('translation need error locations', () => {
|
|
|
238
240
|
describe('error cascading', () => {
|
|
239
241
|
test('errors can appear in multiple top level objects', () => {
|
|
240
242
|
expect((0, test_translator_1.markSource) `
|
|
241
|
-
source: a1 is a { dimension: ${'x is count()'} }
|
|
242
|
-
source: a2 is a { dimension: ${'x is count()'} }
|
|
243
|
+
source: a1 is a extend { dimension: ${'x is count()'} }
|
|
244
|
+
source: a2 is a extend { dimension: ${'x is count()'} }
|
|
243
245
|
`).translationToFailWith('Cannot use an aggregate field in a dimension declaration, did you mean to use a measure declaration instead?', 'Cannot use an aggregate field in a dimension declaration, did you mean to use a measure declaration instead?');
|
|
244
246
|
});
|
|
245
247
|
const typedScalars = {
|
|
@@ -305,13 +307,13 @@ describe('error cascading', () => {
|
|
|
305
307
|
const ungroupedAggregates = ['all(measure_err)'];
|
|
306
308
|
test('dependent errors do not cascade', () => {
|
|
307
309
|
expect(`
|
|
308
|
-
source: a1 is a {
|
|
310
|
+
source: a1 is a extend {
|
|
309
311
|
join_one: b with astr
|
|
310
312
|
dimension:
|
|
311
313
|
${'err is null'}
|
|
312
314
|
${scalars.map((d, i) => `e${i} is ${d}`).join('\n ')}
|
|
313
315
|
measure:
|
|
314
|
-
measure_err is count(
|
|
316
|
+
measure_err is count(foo),
|
|
315
317
|
${[...aggregates, ...ungroupedAggregates]
|
|
316
318
|
.map((m, i) => `e${i + scalars.length} is ${m}`)
|
|
317
319
|
.join('\n ')}
|
|
@@ -321,7 +323,7 @@ describe('error cascading', () => {
|
|
|
321
323
|
test('error type inference is good', () => {
|
|
322
324
|
for (const scalar of scalars) {
|
|
323
325
|
const source = `
|
|
324
|
-
source: a1 is a {
|
|
326
|
+
source: a1 is a extend {
|
|
325
327
|
dimension:
|
|
326
328
|
${'err is null'}
|
|
327
329
|
dim is length(${scalar}, 1)
|
|
@@ -332,14 +334,14 @@ describe('error cascading', () => {
|
|
|
332
334
|
});
|
|
333
335
|
test('eval space of errors is preserved', () => {
|
|
334
336
|
expect(`
|
|
335
|
-
source: a1 is a {
|
|
337
|
+
source: a1 is a extend {
|
|
336
338
|
join_one: b with astr
|
|
337
339
|
}
|
|
338
|
-
|
|
340
|
+
run: a1 -> {
|
|
339
341
|
group_by:
|
|
340
342
|
${'err is null'}
|
|
341
343
|
aggregate:
|
|
342
|
-
measure_err is count(
|
|
344
|
+
measure_err is count(foo)
|
|
343
345
|
calculate:
|
|
344
346
|
${scalars
|
|
345
347
|
.map((d, i) => `e${i} is lag(${d})`)
|
|
@@ -354,74 +356,74 @@ describe('error cascading', () => {
|
|
|
354
356
|
describe('pipeline comprehension', () => {
|
|
355
357
|
test('second query gets namespace from first', () => {
|
|
356
358
|
expect(`
|
|
357
|
-
source: aq is a {
|
|
358
|
-
|
|
359
|
+
source: aq is a extend {
|
|
360
|
+
view: t1 is {
|
|
359
361
|
group_by: t1int is ai, t1str is astr
|
|
360
362
|
} -> {
|
|
361
|
-
|
|
363
|
+
select: t1str, t1int
|
|
362
364
|
}
|
|
363
365
|
}
|
|
364
366
|
`).toTranslate();
|
|
365
367
|
});
|
|
366
368
|
test("second query doesn't have access to original fields", () => {
|
|
367
369
|
expect((0, test_translator_1.model) `
|
|
368
|
-
source: aq is a {
|
|
369
|
-
|
|
370
|
+
source: aq is a extend {
|
|
371
|
+
view: t1 is {
|
|
370
372
|
group_by: t1int is ai, t1str is astr
|
|
371
373
|
} -> {
|
|
372
|
-
|
|
374
|
+
select: ${'ai'}
|
|
373
375
|
}
|
|
374
376
|
}
|
|
375
377
|
`).translationToFailWith("'ai' is not defined");
|
|
376
378
|
});
|
|
377
379
|
test('new query can append ops to existing query', () => {
|
|
378
380
|
expect(`
|
|
379
|
-
source: aq is a {
|
|
380
|
-
|
|
381
|
+
source: aq is a extend {
|
|
382
|
+
view: t0 is {
|
|
381
383
|
group_by: t1int is ai, t1str is astr
|
|
382
384
|
}
|
|
383
|
-
|
|
384
|
-
|
|
385
|
+
view: t1 is t0 -> {
|
|
386
|
+
select: t1str, t1int
|
|
385
387
|
}
|
|
386
388
|
}
|
|
387
389
|
`).toTranslate();
|
|
388
390
|
});
|
|
389
391
|
test('new query can refine and append to exisiting query', () => {
|
|
390
392
|
expect(`
|
|
391
|
-
source: aq is table('aTable') {
|
|
392
|
-
|
|
393
|
-
|
|
393
|
+
source: aq is _db_.table('aTable') extend {
|
|
394
|
+
view: by_region is { group_by: astr }
|
|
395
|
+
view: by_region2 is by_region + {
|
|
394
396
|
nest: dateNest is { group_by: ad }
|
|
395
397
|
} -> {
|
|
396
|
-
|
|
398
|
+
select: astr, dateNest.ad
|
|
397
399
|
}
|
|
398
400
|
}
|
|
399
401
|
`).toTranslate();
|
|
400
402
|
});
|
|
401
403
|
test('reference to a query can include a refinement', () => {
|
|
402
404
|
expect(`
|
|
403
|
-
|
|
405
|
+
run: ab -> {
|
|
404
406
|
group_by: ai
|
|
405
|
-
nest: aturtle { limit: 1 }
|
|
407
|
+
nest: aturtle + { limit: 1 }
|
|
406
408
|
}
|
|
407
409
|
`).toTranslate();
|
|
408
410
|
});
|
|
409
411
|
test('Querying an sourcebased on a query', () => {
|
|
410
412
|
expect(`
|
|
411
413
|
query: q is a -> { group_by: astr; aggregate: strsum is ai.sum() }
|
|
412
|
-
source: aq is a {
|
|
413
|
-
join_one: aq is
|
|
414
|
+
source: aq is a extend {
|
|
415
|
+
join_one: aq is q on astr = aq.astr
|
|
414
416
|
}
|
|
415
|
-
|
|
417
|
+
run: aq -> { select: * }
|
|
416
418
|
`).toTranslate();
|
|
417
419
|
});
|
|
418
420
|
test('new query appends to existing query', () => {
|
|
419
421
|
const src = `
|
|
420
|
-
query: s1 is table('malloytest.flights') -> {
|
|
422
|
+
query: s1 is _db_.table('malloytest.flights') -> {
|
|
421
423
|
group_by: origin, destination
|
|
422
424
|
}
|
|
423
|
-
query: s2 is
|
|
424
|
-
|
|
425
|
+
query: s2 is s1 -> {
|
|
426
|
+
select: *, extra is 'extra'
|
|
425
427
|
}
|
|
426
428
|
`;
|
|
427
429
|
const m = new test_translator_1.TestTranslator(src);
|
|
@@ -480,7 +482,7 @@ describe('sql expressions', () => {
|
|
|
480
482
|
});
|
|
481
483
|
test('reference to sql expression in extended source', () => {
|
|
482
484
|
const m = (0, test_translator_1.model) `
|
|
483
|
-
source: na is bigquery.sql("""SELECT 1 as one""") {
|
|
485
|
+
source: na is bigquery.sql("""SELECT 1 as one""") extend {
|
|
484
486
|
dimension: two is one + one
|
|
485
487
|
}
|
|
486
488
|
`;
|
|
@@ -495,7 +497,7 @@ describe('sql expressions', () => {
|
|
|
495
497
|
});
|
|
496
498
|
test('reference to sql expression in named query', () => {
|
|
497
499
|
const m = new test_translator_1.TestTranslator(`
|
|
498
|
-
query: nq is bigquery.sql("""SELECT 1 as one""") -> {
|
|
500
|
+
query: nq is bigquery.sql("""SELECT 1 as one""") -> { select: * }
|
|
499
501
|
`);
|
|
500
502
|
expect(m).toParse();
|
|
501
503
|
const compileSql = m.translate().compileSQL;
|
|
@@ -509,7 +511,7 @@ describe('sql expressions', () => {
|
|
|
509
511
|
});
|
|
510
512
|
test('reference to sql expression in unnamed query', () => {
|
|
511
513
|
const m = new test_translator_1.TestTranslator(`
|
|
512
|
-
|
|
514
|
+
run: bigquery.sql("""SELECT 1 as one""") -> { select: * }
|
|
513
515
|
`);
|
|
514
516
|
expect(m).toParse();
|
|
515
517
|
const compileSql = m.translate().compileSQL;
|
|
@@ -523,7 +525,7 @@ describe('sql expressions', () => {
|
|
|
523
525
|
});
|
|
524
526
|
test('reference to sql expression in join', () => {
|
|
525
527
|
const m = (0, test_translator_1.model) `
|
|
526
|
-
source: quux is a {
|
|
528
|
+
source: quux is a extend {
|
|
527
529
|
join_one: xyzzy is
|
|
528
530
|
duckdb.sql("""SELECT 1 as one""")
|
|
529
531
|
on ai = xyzzy.one
|
|
@@ -591,7 +593,7 @@ describe('sql expressions', () => {
|
|
|
591
593
|
});
|
|
592
594
|
test('reference to sql expression in anonymous query', () => {
|
|
593
595
|
const m = new test_translator_1.TestTranslator(`
|
|
594
|
-
|
|
596
|
+
run: bigquery.sql("""select 1 as one""")
|
|
595
597
|
`);
|
|
596
598
|
expect(m).toParse();
|
|
597
599
|
const compileSql = m.translate().compileSQL;
|
|
@@ -605,11 +607,11 @@ describe('sql expressions', () => {
|
|
|
605
607
|
});
|
|
606
608
|
// TODO this is not possible to implement yet unless we
|
|
607
609
|
// can distinguish between the generated IR of `conn.sql(...)`
|
|
608
|
-
// and `conn.sql(...) -> {
|
|
610
|
+
// and `conn.sql(...) -> { select: * }`.
|
|
609
611
|
test.skip('cannot refine a SQL query', () => {
|
|
610
612
|
const m = new test_translator_1.TestTranslator(`
|
|
611
613
|
query: q1 is bigquery.sql("""select 1 as one""")
|
|
612
|
-
run: q1
|
|
614
|
+
run: q1 + { where: 1 = 1 }
|
|
613
615
|
`);
|
|
614
616
|
expect(m).toParse();
|
|
615
617
|
const compileSql = m.translate().compileSQL;
|
|
@@ -623,11 +625,12 @@ describe('sql expressions', () => {
|
|
|
623
625
|
});
|
|
624
626
|
});
|
|
625
627
|
describe('turtle with leading arrow', () => {
|
|
626
|
-
test('optional leading arrow for defining a turtle', () => {
|
|
628
|
+
test('pre m4 optional leading arrow for defining a turtle', () => {
|
|
627
629
|
expect(`
|
|
630
|
+
##! -m4warnings
|
|
628
631
|
source: c is a extend {
|
|
629
|
-
query: x is -> {
|
|
630
|
-
query: y is {
|
|
632
|
+
query: x is -> { select: * }
|
|
633
|
+
query: y is { select: * }
|
|
631
634
|
}
|
|
632
635
|
`).toTranslate();
|
|
633
636
|
});
|
|
@@ -637,7 +640,7 @@ describe('extend and refine', () => {
|
|
|
637
640
|
test('query name with query refinements', () => {
|
|
638
641
|
expect(`
|
|
639
642
|
query: q is a -> { group_by: ai }
|
|
640
|
-
run: q
|
|
643
|
+
run: q + { group_by: ai2 is ai }
|
|
641
644
|
`).toTranslate();
|
|
642
645
|
});
|
|
643
646
|
test('query name with source refinements', () => {
|
|
@@ -647,7 +650,7 @@ describe('extend and refine', () => {
|
|
|
647
650
|
`).toTranslate();
|
|
648
651
|
});
|
|
649
652
|
test('source name with query refinements', () => {
|
|
650
|
-
expect('run: a
|
|
653
|
+
expect('run: a + { group_by: ai }').translationToFailWith("Cannot add view refinements to 'a' because it is a source");
|
|
651
654
|
});
|
|
652
655
|
test('source name with source refinements', () => {
|
|
653
656
|
expect('source: s is a extend { dimension: ai_2 is ai + ai }').toTranslate();
|
|
@@ -656,96 +659,111 @@ describe('extend and refine', () => {
|
|
|
656
659
|
expect('source: s is a -> { group_by: ai } extend { dimension: ai_2 is ai + ai }').toTranslate();
|
|
657
660
|
});
|
|
658
661
|
test('query with extension then new stage', () => {
|
|
659
|
-
expect('run: a { dimension: x is 1 } -> { group_by: x }').toTranslate();
|
|
662
|
+
expect('run: a extend { dimension: x is 1 } -> { group_by: x }').toTranslate();
|
|
660
663
|
});
|
|
661
664
|
});
|
|
662
665
|
describe('extend and refine, old syntax', () => {
|
|
663
666
|
test('query name with query refinements', () => {
|
|
664
667
|
expect(`
|
|
665
668
|
query: q is a -> { group_by: ai }
|
|
666
|
-
run: q { group_by: ai2 is ai }
|
|
669
|
+
run: q + { group_by: ai2 is ai }
|
|
667
670
|
`).toTranslate();
|
|
668
671
|
});
|
|
669
672
|
test('query name with source refinements', () => {
|
|
670
673
|
expect(`
|
|
671
674
|
query: q is a -> { group_by: ai }
|
|
672
|
-
source: s is q { dimension: ai_2 is ai + ai }
|
|
675
|
+
source: s is q extend { dimension: ai_2 is ai + ai }
|
|
673
676
|
`).toTranslate();
|
|
674
677
|
});
|
|
675
678
|
test('source name with query refinements', () => {
|
|
676
|
-
expect('run: a { group_by: one }').translationToFailWith("
|
|
679
|
+
expect('run: a + { group_by: one }').translationToFailWith("Cannot add view refinements to 'a' because it is a source");
|
|
677
680
|
});
|
|
678
681
|
test('source name with source refinements', () => {
|
|
679
|
-
expect('source: s is a { dimension: ai_2 is ai + ai }').toTranslate();
|
|
682
|
+
expect('source: s is a extend { dimension: ai_2 is ai + ai }').toTranslate();
|
|
680
683
|
});
|
|
681
684
|
test('source name with ambiguous refinements', () => {
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
expect(
|
|
685
|
-
|
|
686
|
-
expect(
|
|
687
|
-
|
|
688
|
-
expect(
|
|
685
|
+
expect(`##! -m4warnings
|
|
686
|
+
run: a + { join_one: b on b.ai = ai } -> { select: b.* }`).toTranslate();
|
|
687
|
+
expect(`##! -m4warnings
|
|
688
|
+
run: a + { where: 1 = 1 } -> { select: * }`).toTranslate();
|
|
689
|
+
expect(`##! -m4warnings
|
|
690
|
+
run: a + { dimension: three is 3 } -> { select: * }`).toTranslate();
|
|
691
|
+
expect(`##! -m4warnings
|
|
692
|
+
source: s is a { join_one: b on b.ai = ai }`).toTranslate();
|
|
693
|
+
expect(`##! -m4warnings
|
|
694
|
+
source: s is a { where: 1 = 1 }`).toTranslate();
|
|
695
|
+
expect(`##! -m4warnings
|
|
696
|
+
source: s is a { dimension: three is 3 }`).toTranslate();
|
|
689
697
|
});
|
|
690
698
|
describe('query name with ambiguous refinements', () => {
|
|
691
|
-
test('
|
|
699
|
+
test('adding segment to ambuguously refined query', () => {
|
|
692
700
|
expect(`
|
|
701
|
+
##! -m4warnings
|
|
693
702
|
query: q is a -> { group_by: ai }
|
|
694
|
-
run: q { join_one: b on 1 = 1 } -> {
|
|
695
|
-
`).
|
|
703
|
+
run: q + { join_one: b on 1 = 1 } -> { select: b.* }
|
|
704
|
+
`).translationToFailWith("No such field as 'b'");
|
|
696
705
|
expect(`
|
|
706
|
+
##! -m4warnings
|
|
697
707
|
query: q is a -> { group_by: ai }
|
|
698
|
-
run: q { where: 1 = 1 } -> {
|
|
708
|
+
run: q + { where: 1 = 1 } -> { select: * }
|
|
699
709
|
`).toTranslate();
|
|
700
710
|
expect(`
|
|
711
|
+
##! -m4warnings
|
|
701
712
|
query: q is a -> { group_by: ai }
|
|
702
|
-
run: q { declare: three is 3 } -> {
|
|
713
|
+
run: q + { declare: three is 3 } -> { select: * }
|
|
703
714
|
`).toTranslate();
|
|
704
715
|
});
|
|
705
716
|
test('automatically recognize this as a query refinement without new stage', () => {
|
|
706
717
|
expect(`
|
|
718
|
+
##! -m4warnings
|
|
707
719
|
query: q is a -> { group_by: ai }
|
|
708
|
-
run: q { join_one: b on 1 = 1 }
|
|
720
|
+
run: q + { join_one: b on 1 = 1 }
|
|
709
721
|
`).toTranslate();
|
|
710
722
|
expect(`
|
|
723
|
+
##! -m4warnings
|
|
711
724
|
query: q is a -> { group_by: ai }
|
|
712
|
-
run: q { where: 1 = 1 }
|
|
725
|
+
run: q + { where: 1 = 1 }
|
|
713
726
|
`).toTranslate();
|
|
714
727
|
expect(`
|
|
728
|
+
##! -m4warnings
|
|
715
729
|
query: q is a -> { group_by: ai }
|
|
716
|
-
run: q { declare: three is 3 }
|
|
730
|
+
run: q + { declare: three is 3 }
|
|
717
731
|
`).toTranslate();
|
|
718
732
|
});
|
|
719
|
-
test('can
|
|
720
|
-
// TODO add a warning when you use from() -- "`from()` is deprecated; to apply source extensions to a query, use `extend`"
|
|
733
|
+
test('can extend a query into a source', () => {
|
|
721
734
|
expect(`
|
|
722
735
|
query: q is a -> { group_by: ai }
|
|
723
|
-
source: s is
|
|
736
|
+
source: s is q extend { join_one: b on b.ai = ai }
|
|
724
737
|
`).toTranslate();
|
|
725
738
|
});
|
|
726
739
|
test('can also add an arrow to clarify that it is a query', () => {
|
|
727
740
|
// Of course, number 1 and 3 are actually useless because you're defining
|
|
728
741
|
// something and then not using it...
|
|
729
742
|
expect(`
|
|
743
|
+
##! -m4warnings
|
|
730
744
|
query: q is a -> { group_by: ai }
|
|
731
|
-
run:
|
|
745
|
+
run: q + { join_one: b on b.ai = ai } -> { select: * }
|
|
732
746
|
`).toTranslate();
|
|
733
747
|
expect(`
|
|
748
|
+
##! -m4warnings
|
|
734
749
|
query: q is a -> { group_by: ai }
|
|
735
|
-
run:
|
|
750
|
+
run: q + { where: 1 = 1 } -> { select: * }
|
|
736
751
|
`).toTranslate();
|
|
737
752
|
expect(`
|
|
753
|
+
##! -m4warnings
|
|
738
754
|
query: q is a -> { group_by: ai }
|
|
739
|
-
run:
|
|
755
|
+
run: q + { declare: three is 3 } -> { select: * }
|
|
740
756
|
`).toTranslate();
|
|
741
757
|
// Alternatively, fix 1 and 3 by actually using the declared thing
|
|
742
758
|
expect(`
|
|
759
|
+
##! -m4warnings
|
|
743
760
|
query: q is a -> { group_by: ai }
|
|
744
|
-
run: q { join_one: b on b.ai = ai; group_by: ai2 is b.ai }
|
|
761
|
+
run: q + { join_one: b on b.ai = ai; group_by: ai2 is b.ai }
|
|
745
762
|
`).toTranslate();
|
|
746
763
|
expect(`
|
|
764
|
+
##! -m4warnings
|
|
747
765
|
query: q is a -> { group_by: ai }
|
|
748
|
-
run: q {
|
|
766
|
+
run: q + { extend: {dimension: three is 3} group_by: three }
|
|
749
767
|
`).toTranslate();
|
|
750
768
|
});
|
|
751
769
|
});
|
|
@@ -755,41 +773,40 @@ describe('extend and refine', () => {
|
|
|
755
773
|
expect('run: a').translationToFailWith("Illegal reference to 'a', query expected");
|
|
756
774
|
});
|
|
757
775
|
test('syntactically valid to refine a source, but illegal', () => {
|
|
758
|
-
expect('run: a { group_by: ai } ').translationToFailWith("
|
|
759
|
-
expect('run: a refine { group_by: ai } ').translationToFailWith("Illegal reference to 'a', query expected");
|
|
776
|
+
expect('run: a + { group_by: ai } ').translationToFailWith("Cannot add view refinements to 'a' because it is a source");
|
|
760
777
|
});
|
|
761
778
|
});
|
|
762
779
|
describe('turtles', () => {
|
|
763
780
|
test('explicit refine in turtle works', () => {
|
|
764
781
|
expect(`source: c is a extend {
|
|
765
|
-
|
|
766
|
-
|
|
782
|
+
view: x is { select: * }
|
|
783
|
+
view: y is x + { limit: 1 }
|
|
767
784
|
}`).toTranslate();
|
|
768
785
|
});
|
|
769
786
|
test('implicit refine in turtle works', () => {
|
|
770
787
|
expect(`source: c is a extend {
|
|
771
|
-
|
|
772
|
-
|
|
788
|
+
view: x is { select: * }
|
|
789
|
+
view: y is x + { limit: 1 }
|
|
773
790
|
}`).toTranslate();
|
|
774
791
|
});
|
|
775
792
|
});
|
|
776
793
|
describe('nests', () => {
|
|
777
794
|
test('explicit refine in nest', () => {
|
|
778
795
|
expect(`source: c is a extend {
|
|
779
|
-
|
|
796
|
+
view: x is { select: * }
|
|
780
797
|
}
|
|
781
798
|
|
|
782
799
|
run: c -> {
|
|
783
|
-
nest: x
|
|
800
|
+
nest: x + { limit: 1 }
|
|
784
801
|
}`).toTranslate();
|
|
785
802
|
});
|
|
786
|
-
test('
|
|
803
|
+
test('refine in nest', () => {
|
|
787
804
|
expect(`source: c is a extend {
|
|
788
|
-
|
|
805
|
+
view: x is { select: * }
|
|
789
806
|
}
|
|
790
807
|
|
|
791
808
|
run: c -> {
|
|
792
|
-
nest: x { limit: 1 }
|
|
809
|
+
nest: x + { limit: 1 }
|
|
793
810
|
}`).toTranslate();
|
|
794
811
|
});
|
|
795
812
|
});
|
|
@@ -802,20 +819,20 @@ describe('extend and refine', () => {
|
|
|
802
819
|
|
|
803
820
|
run: c -> {
|
|
804
821
|
nest: x { limit: 1 }
|
|
805
|
-
}`).toTranslateWithWarnings('Implicit query refinement is deprecated, use the
|
|
822
|
+
}`).toTranslateWithWarnings('Implicit query refinement is deprecated, use the `+` operator');
|
|
806
823
|
});
|
|
807
824
|
test('implicit refine in turtle works', () => {
|
|
808
825
|
expect(`##! m4warnings
|
|
809
826
|
source: c is a extend {
|
|
810
827
|
view: x is { select: * }
|
|
811
828
|
view: y is x { limit: 1 }
|
|
812
|
-
}`).toTranslateWithWarnings('Implicit query refinement is deprecated, use the
|
|
829
|
+
}`).toTranslateWithWarnings('Implicit query refinement is deprecated, use the `+` operator');
|
|
813
830
|
});
|
|
814
831
|
test('implicit query refinement', () => {
|
|
815
832
|
expect(`##! m4warnings
|
|
816
833
|
query: q is a -> { group_by: ai }
|
|
817
834
|
run: q { group_by: three is 3 }
|
|
818
|
-
`).toTranslateWithWarnings('Implicit query refinement is deprecated, use the
|
|
835
|
+
`).toTranslateWithWarnings('Implicit query refinement is deprecated, use the `+` operator');
|
|
819
836
|
});
|
|
820
837
|
test('implicit source extension', () => {
|
|
821
838
|
expect(`##! m4warnings
|
|
@@ -867,4 +884,43 @@ describe('miscellaneous m4 warnings', () => {
|
|
|
867
884
|
`).toTranslateWithWarnings('Leading arrow (`->`) when referencing a query is deprecated; remove the arrow');
|
|
868
885
|
});
|
|
869
886
|
});
|
|
887
|
+
describe('m3/m4 source query sentences', () => {
|
|
888
|
+
const srcExtend = '{accept:ai}';
|
|
889
|
+
const qryRefine = '{limit:1}';
|
|
890
|
+
const query = '{select:*}';
|
|
891
|
+
// todo MTOY write test to make sure arrow has correct precedence vs +
|
|
892
|
+
// also maybe arrow vs extend
|
|
893
|
+
test('M4 should error on these sq expressions', () => {
|
|
894
|
+
expect(`source: s is a + ${qryRefine}`).translationToFailWith("Cannot add view refinements to 'a' because it is a source");
|
|
895
|
+
expect(`query: q_m4_err is a + ${qryRefine}`).translationToFailWith("Cannot add view refinements to 'a' because it is a source");
|
|
896
|
+
});
|
|
897
|
+
test('legal sqexpressions', () => {
|
|
898
|
+
// some things that are m4 warnings are commented out
|
|
899
|
+
expect(`
|
|
900
|
+
source: s is a
|
|
901
|
+
query: q is s -> ${query}
|
|
902
|
+
|
|
903
|
+
source: s0 is a;
|
|
904
|
+
// source: s0_extbare is s ${srcExtend};
|
|
905
|
+
// source: s0_extplus is s + ${srcExtend};
|
|
906
|
+
source: s0_ext is s extend ${srcExtend};
|
|
907
|
+
source: qs is q;
|
|
908
|
+
source: qs0 is q extend ${srcExtend};
|
|
909
|
+
source: qs1 is q + ${qryRefine};
|
|
910
|
+
source: s1_m4 is q + ${qryRefine};
|
|
911
|
+
source: s2_m4 is q + ${qryRefine} -> ${query} extend ${srcExtend};
|
|
912
|
+
source: s3 is s extend ${srcExtend};
|
|
913
|
+
source: s4 is q extend ${srcExtend};
|
|
914
|
+
// source: s5 is from(s -> ${query})
|
|
915
|
+
|
|
916
|
+
query: q0 is q;
|
|
917
|
+
// query: q0_refbare is q ${qryRefine};
|
|
918
|
+
query: q0_refplus is q + ${qryRefine};
|
|
919
|
+
// query: q1_bare is ab -> aturtle ${qryRefine};
|
|
920
|
+
query: q1_plus is ab -> aturtle + ${qryRefine};
|
|
921
|
+
query: q2 is s -> ${query} extend ${srcExtend} -> ${query};
|
|
922
|
+
|
|
923
|
+
`).toTranslate();
|
|
924
|
+
});
|
|
925
|
+
});
|
|
870
926
|
//# sourceMappingURL=parse.spec.js.map
|