@malloydata/malloy 0.0.3 → 0.0.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/lang/ast/ast-expr.d.ts +0 -1
- package/dist/lang/ast/ast-main.d.ts +2 -2
- package/package.json +1 -1
- package/dist/connection_utils.js +0 -56
- package/dist/dialect/dialect.js +0 -77
- package/dist/dialect/dialect_map.js +0 -35
- package/dist/dialect/duckdb.js +0 -347
- package/dist/dialect/index.js +0 -27
- package/dist/dialect/postgres.js +0 -315
- package/dist/dialect/standardsql.js +0 -362
- package/dist/index.js +0 -47
- package/dist/lang/ast/apply-expr.js +0 -231
- package/dist/lang/ast/ast-expr.js +0 -1041
- package/dist/lang/ast/ast-main.js +0 -2087
- package/dist/lang/ast/ast-time-expr.js +0 -549
- package/dist/lang/ast/ast-types.js +0 -215
- package/dist/lang/ast/index.js +0 -34
- package/dist/lang/ast/time-utils.js +0 -94
- package/dist/lang/field-space.js +0 -631
- package/dist/lang/field-utils.js +0 -33
- package/dist/lang/index.js +0 -22
- package/dist/lang/parse-log.js +0 -41
- package/dist/lang/reference-list.js +0 -80
- package/dist/lang/space-field.js +0 -346
- package/dist/lang/test/document-help-context-walker.spec.js +0 -45
- package/dist/lang/test/document-symbol-walker.spec.js +0 -100
- package/dist/lang/test/field-symbols.spec.js +0 -172
- package/dist/lang/test/parse.spec.js +0 -1951
- package/dist/lang/test/test-translator.js +0 -334
- package/dist/lang/zone.js +0 -97
- package/dist/malloy.js +0 -2360
- package/dist/model/index.js +0 -34
- package/dist/model/malloy_query.js +0 -2994
- package/dist/model/malloy_types.js +0 -283
- package/dist/model/sql_block.js +0 -41
- package/dist/model/utils.js +0 -84
- package/dist/runtime_types.js +0 -15
|
@@ -1,1951 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
/* eslint-disable no-console */
|
|
3
|
-
/*
|
|
4
|
-
* Copyright 2021 Google LLC
|
|
5
|
-
*
|
|
6
|
-
* This program is free software; you can redistribute it and/or
|
|
7
|
-
* modify it under the terms of the GNU General Public License
|
|
8
|
-
* version 2 as published by the Free Software Foundation.
|
|
9
|
-
*
|
|
10
|
-
* This program is distributed in the hope that it will be useful,
|
|
11
|
-
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
12
|
-
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
13
|
-
* GNU General Public License for more details.
|
|
14
|
-
*/
|
|
15
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
16
|
-
const sql_block_1 = require("../../model/sql_block");
|
|
17
|
-
const ast_1 = require("../ast");
|
|
18
|
-
const field_space_1 = require("../field-space");
|
|
19
|
-
const test_translator_1 = require("./test-translator");
|
|
20
|
-
const lodash_1 = require("lodash");
|
|
21
|
-
const util_1 = require("util");
|
|
22
|
-
const inspectCompile = false;
|
|
23
|
-
/*
|
|
24
|
-
* Thinking of these tests as just "do things parse", there should maybe
|
|
25
|
-
* be additional tests for, "does the correct code get generated", but
|
|
26
|
-
* the first step should be to write all the phrases in the grammar
|
|
27
|
-
* and make sure they parse to ast and the ast generates something
|
|
28
|
-
*/
|
|
29
|
-
class Testable extends test_translator_1.TestTranslator {
|
|
30
|
-
}
|
|
31
|
-
class BetaModel extends Testable {
|
|
32
|
-
constructor(testSrc) {
|
|
33
|
-
super(testSrc);
|
|
34
|
-
this.testSrc = testSrc;
|
|
35
|
-
}
|
|
36
|
-
compile() {
|
|
37
|
-
const compileTo = this.translate();
|
|
38
|
-
if (compileTo.translated && inspectCompile) {
|
|
39
|
-
console.log("MODEL: ", (0, test_translator_1.pretty)(compileTo.translated.modelDef));
|
|
40
|
-
console.log("QUERIES: ", (0, test_translator_1.pretty)(compileTo.translated.queryList));
|
|
41
|
-
}
|
|
42
|
-
// All the stuff to ask the ast for a translation is already in TestTranslator
|
|
43
|
-
}
|
|
44
|
-
unresolved() {
|
|
45
|
-
return this.importsAndTablesStep.step(this);
|
|
46
|
-
}
|
|
47
|
-
getSourceDef(srcName) {
|
|
48
|
-
const t = this.translate().translated;
|
|
49
|
-
if (t) {
|
|
50
|
-
const s = t.modelDef.contents[srcName];
|
|
51
|
-
if (s.type == "struct") {
|
|
52
|
-
return s;
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
return undefined;
|
|
56
|
-
}
|
|
57
|
-
getQuery(queryName) {
|
|
58
|
-
const t = this.translate().translated;
|
|
59
|
-
if (t) {
|
|
60
|
-
const s = typeof queryName == "string"
|
|
61
|
-
? t.modelDef.contents[queryName]
|
|
62
|
-
: t.queryList[queryName];
|
|
63
|
-
if (s.type == "query") {
|
|
64
|
-
return s;
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
return undefined;
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
function checkForErrors(trans) {
|
|
71
|
-
if (trans.logger === undefined) {
|
|
72
|
-
throw new Error("JESTERY BROKEN, CANT FIND ERORR LOG");
|
|
73
|
-
}
|
|
74
|
-
if (trans.logger.hasErrors()) {
|
|
75
|
-
return {
|
|
76
|
-
message: () => `Translation Errors:\n${trans.prettyErrors()}`,
|
|
77
|
-
pass: false,
|
|
78
|
-
};
|
|
79
|
-
}
|
|
80
|
-
return {
|
|
81
|
-
message: () => "Unexpected error free translation",
|
|
82
|
-
pass: true,
|
|
83
|
-
};
|
|
84
|
-
}
|
|
85
|
-
function prettyNeeds(response) {
|
|
86
|
-
let needString = "";
|
|
87
|
-
if (response.tables) {
|
|
88
|
-
needString += "Tables:\n";
|
|
89
|
-
response.tables.forEach((table) => (needString += ` - ${table}`));
|
|
90
|
-
}
|
|
91
|
-
if (response.sqlStructs) {
|
|
92
|
-
needString += "SQL Structs:\n";
|
|
93
|
-
response.sqlStructs.forEach((sql) => (needString += ` - ${sql.as || "(unnamed sql struct)"}`));
|
|
94
|
-
}
|
|
95
|
-
if (response.urls) {
|
|
96
|
-
needString += "URLs:\n";
|
|
97
|
-
response.urls.forEach((url) => (needString += ` - ${url}`));
|
|
98
|
-
}
|
|
99
|
-
return needString;
|
|
100
|
-
}
|
|
101
|
-
function checkForNeededs(trans) {
|
|
102
|
-
const response = trans.translateStep.step(trans);
|
|
103
|
-
if (!response.final) {
|
|
104
|
-
return {
|
|
105
|
-
message: () => `Translation is not complete, needs:\n${prettyNeeds(response)}`,
|
|
106
|
-
pass: false,
|
|
107
|
-
};
|
|
108
|
-
}
|
|
109
|
-
return {
|
|
110
|
-
message: () => "Unexpected complete translation",
|
|
111
|
-
pass: true,
|
|
112
|
-
};
|
|
113
|
-
}
|
|
114
|
-
expect.extend({
|
|
115
|
-
toCompile: function (x) {
|
|
116
|
-
x.compile();
|
|
117
|
-
return checkForErrors(x);
|
|
118
|
-
},
|
|
119
|
-
toTranslate: function (x) {
|
|
120
|
-
x.compile();
|
|
121
|
-
const errorCheck = checkForErrors(x);
|
|
122
|
-
if (!errorCheck.pass) {
|
|
123
|
-
return errorCheck;
|
|
124
|
-
}
|
|
125
|
-
x.translate();
|
|
126
|
-
return checkForNeededs(x);
|
|
127
|
-
},
|
|
128
|
-
toBeErrorless: function (trans) {
|
|
129
|
-
return checkForErrors(trans);
|
|
130
|
-
},
|
|
131
|
-
toReturnType: function (functionCall, returnType) {
|
|
132
|
-
const exprModel = new BetaModel(`explore: x is a { dimension: d is ${functionCall} }`);
|
|
133
|
-
expect(exprModel).toTranslate();
|
|
134
|
-
const x = exprModel.getSourceDef("x");
|
|
135
|
-
expect(x).toBeDefined();
|
|
136
|
-
if (x) {
|
|
137
|
-
const d = x.fields.find((f) => f.name === "d");
|
|
138
|
-
expect(d === null || d === void 0 ? void 0 : d.type).toBe(returnType);
|
|
139
|
-
}
|
|
140
|
-
return {
|
|
141
|
-
pass: true,
|
|
142
|
-
message: () => "",
|
|
143
|
-
};
|
|
144
|
-
},
|
|
145
|
-
compileToFailWith: function (s, ...msgs) {
|
|
146
|
-
var _a;
|
|
147
|
-
let emsg = "Compile Error expectation not met\nExpected error";
|
|
148
|
-
let mSrc;
|
|
149
|
-
const qmsgs = msgs.map((s) => `error '${s}'`);
|
|
150
|
-
if (msgs.length == 1) {
|
|
151
|
-
emsg += ` ${qmsgs[0]}`;
|
|
152
|
-
}
|
|
153
|
-
else {
|
|
154
|
-
emsg += `s [\n${qmsgs.join("\n")}\n]`;
|
|
155
|
-
}
|
|
156
|
-
let m;
|
|
157
|
-
let src;
|
|
158
|
-
if (s instanceof BetaModel) {
|
|
159
|
-
m = s;
|
|
160
|
-
src = m.testSrc;
|
|
161
|
-
}
|
|
162
|
-
else {
|
|
163
|
-
if (typeof s == "string") {
|
|
164
|
-
src = s;
|
|
165
|
-
}
|
|
166
|
-
else {
|
|
167
|
-
src = s.code;
|
|
168
|
-
mSrc = s;
|
|
169
|
-
}
|
|
170
|
-
m = new BetaModel(src);
|
|
171
|
-
}
|
|
172
|
-
emsg += `\nSource:\n${src}`;
|
|
173
|
-
const t = m.translate();
|
|
174
|
-
if (t.translated) {
|
|
175
|
-
return { pass: false, message: () => emsg };
|
|
176
|
-
}
|
|
177
|
-
else if (t.errors == undefined) {
|
|
178
|
-
return {
|
|
179
|
-
pass: false,
|
|
180
|
-
message: () => `TEST ERROR, not all objects resolved in source\n` +
|
|
181
|
-
(0, test_translator_1.pretty)(t) +
|
|
182
|
-
"\n" +
|
|
183
|
-
emsg,
|
|
184
|
-
};
|
|
185
|
-
}
|
|
186
|
-
else {
|
|
187
|
-
const explain = [];
|
|
188
|
-
const errList = m.errors().errors;
|
|
189
|
-
let i;
|
|
190
|
-
for (i = 0; i < msgs.length && errList[i]; i += 1) {
|
|
191
|
-
const msg = msgs[i];
|
|
192
|
-
const err = errList[i];
|
|
193
|
-
if (msg != err.message) {
|
|
194
|
-
explain.push(`Expected: ${msg}\nGot: ${err.message}`);
|
|
195
|
-
}
|
|
196
|
-
else {
|
|
197
|
-
if (mSrc === null || mSrc === void 0 ? void 0 : mSrc.locations[i]) {
|
|
198
|
-
const have = (_a = err.at) === null || _a === void 0 ? void 0 : _a.range;
|
|
199
|
-
const want = mSrc.locations[i].range;
|
|
200
|
-
if (!this.equals(have, want)) {
|
|
201
|
-
explain.push(`Expected '${msg}' at location: ${(0, util_1.inspect)(want)}\n` +
|
|
202
|
-
`Actual location: ${(0, util_1.inspect)(have)}`);
|
|
203
|
-
}
|
|
204
|
-
}
|
|
205
|
-
}
|
|
206
|
-
}
|
|
207
|
-
if (i != msgs.length) {
|
|
208
|
-
explain.push(...msgs.slice(i).map((m) => `Missing: ${m}`));
|
|
209
|
-
}
|
|
210
|
-
if (i != errList.length) {
|
|
211
|
-
explain.push(...errList.slice(i).map((m) => `Unexpected Error: ${m.message}`));
|
|
212
|
-
}
|
|
213
|
-
if (explain.length == 0) {
|
|
214
|
-
return {
|
|
215
|
-
pass: true,
|
|
216
|
-
message: () => `All expected errors found: ${(0, test_translator_1.pretty)(msgs)}`,
|
|
217
|
-
};
|
|
218
|
-
}
|
|
219
|
-
return {
|
|
220
|
-
pass: false,
|
|
221
|
-
message: () => `Compiler did not generated expected errors\n${explain.join("\n")}`,
|
|
222
|
-
};
|
|
223
|
-
}
|
|
224
|
-
},
|
|
225
|
-
});
|
|
226
|
-
class BetaExpression extends Testable {
|
|
227
|
-
constructor(src) {
|
|
228
|
-
super(src, "justExpr");
|
|
229
|
-
}
|
|
230
|
-
compile() {
|
|
231
|
-
const exprAst = this.ast();
|
|
232
|
-
if (exprAst instanceof ast_1.ExpressionDef) {
|
|
233
|
-
const aStruct = this.internalModel.contents.ab;
|
|
234
|
-
if (aStruct.type === "struct") {
|
|
235
|
-
const tstFS = new field_space_1.StaticSpace(aStruct);
|
|
236
|
-
const exprDef = exprAst.getExpression(tstFS);
|
|
237
|
-
if (inspectCompile) {
|
|
238
|
-
console.log("EXPRESSION: ", (0, test_translator_1.pretty)(exprDef));
|
|
239
|
-
}
|
|
240
|
-
}
|
|
241
|
-
else {
|
|
242
|
-
throw new Error("Can't get simple namespace for expression tests");
|
|
243
|
-
}
|
|
244
|
-
}
|
|
245
|
-
else if (this.logger.hasErrors()) {
|
|
246
|
-
return;
|
|
247
|
-
}
|
|
248
|
-
else {
|
|
249
|
-
const whatIsIt = (exprAst === null || exprAst === void 0 ? void 0 : exprAst.toString()) || "NO AST GENERATED";
|
|
250
|
-
throw new Error(`Not an expression: ${whatIsIt}`);
|
|
251
|
-
}
|
|
252
|
-
}
|
|
253
|
-
}
|
|
254
|
-
function exprOK(s) {
|
|
255
|
-
return () => {
|
|
256
|
-
expect(new BetaExpression(s)).toCompile();
|
|
257
|
-
return undefined;
|
|
258
|
-
};
|
|
259
|
-
}
|
|
260
|
-
function modelOK(s) {
|
|
261
|
-
return () => {
|
|
262
|
-
const m = new BetaModel(s);
|
|
263
|
-
expect(m).toTranslate();
|
|
264
|
-
return undefined;
|
|
265
|
-
};
|
|
266
|
-
}
|
|
267
|
-
function badModel(s, msg) {
|
|
268
|
-
return () => {
|
|
269
|
-
const src = typeof s == "string" ? s : s.code;
|
|
270
|
-
const emsg = `Error expectation not met\nExpected error: '${msg}'\nSource:\n${src}`;
|
|
271
|
-
const m = new BetaModel(src);
|
|
272
|
-
const t = m.translate();
|
|
273
|
-
if (t.translated) {
|
|
274
|
-
fail(emsg);
|
|
275
|
-
}
|
|
276
|
-
else {
|
|
277
|
-
const errList = m.errors().errors;
|
|
278
|
-
const firstError = errList[0];
|
|
279
|
-
if (firstError.message != msg) {
|
|
280
|
-
fail(`Received errror: ${firstError.message}\n${emsg}`);
|
|
281
|
-
}
|
|
282
|
-
if (typeof s != "string") {
|
|
283
|
-
if (!(0, lodash_1.isEqual)(errList[0].at, s.locations[0])) {
|
|
284
|
-
fail(`Expected location: ${s.locations[0]}\n` +
|
|
285
|
-
`Received location: ${errList[0].at}\n${emsg}`);
|
|
286
|
-
}
|
|
287
|
-
}
|
|
288
|
-
}
|
|
289
|
-
return undefined;
|
|
290
|
-
};
|
|
291
|
-
}
|
|
292
|
-
describe("model statements", () => {
|
|
293
|
-
describe("explore:", () => {
|
|
294
|
-
test("explore table", modelOK(`explore: testA is table('aTable')`));
|
|
295
|
-
test("explore shorcut fitlered table", modelOK(`
|
|
296
|
-
explore: testA is table('aTable') {? astr ~ 'a%' }
|
|
297
|
-
`));
|
|
298
|
-
test("explore fitlered table", modelOK(`
|
|
299
|
-
explore: testA is table('aTable') { where: astr ~ 'a%' }
|
|
300
|
-
`));
|
|
301
|
-
test("explore explore", modelOK(`explore: testA is a`));
|
|
302
|
-
test("explore query", modelOK(`explore: testA is from(a->{group_by: astr})`));
|
|
303
|
-
test("refine explore", modelOK(`explore: aa is a { dimension: a is astr }`));
|
|
304
|
-
test("source refinement preserves original", () => {
|
|
305
|
-
const x = new BetaModel("source: na is a + { dimension: one is 1 }");
|
|
306
|
-
expect(x).toTranslate();
|
|
307
|
-
const a = x.getSourceDef("a");
|
|
308
|
-
if (a) {
|
|
309
|
-
const aFields = a.fields.map((f) => f.as || f.name);
|
|
310
|
-
expect(aFields).toContain("astr");
|
|
311
|
-
expect(aFields).not.toContain("one");
|
|
312
|
-
}
|
|
313
|
-
});
|
|
314
|
-
});
|
|
315
|
-
describe("query:", () => {
|
|
316
|
-
test("anonymous query", modelOK("query: table('aTable') -> { group_by: astr }"));
|
|
317
|
-
test("query", modelOK("query: name is table('aTable') -> { group_by: astr }"));
|
|
318
|
-
test("query from query", modelOK(`
|
|
319
|
-
query: q1 is ab->{ group_by: astr limit: 10 }
|
|
320
|
-
query: q2 is ->q1
|
|
321
|
-
`));
|
|
322
|
-
test("query with refinements from query", modelOK(`
|
|
323
|
-
query: q1 is ab->{ group_by: astr limit: 10 }
|
|
324
|
-
query: q2 is ->q1 { aggregate: acount }
|
|
325
|
-
`));
|
|
326
|
-
test("chained query operations", modelOK(`
|
|
327
|
-
query: ab
|
|
328
|
-
-> { group_by: astr; aggregate: acount }
|
|
329
|
-
-> { top: 5; where: astr ~ 'a%' group_by: astr }
|
|
330
|
-
`));
|
|
331
|
-
test("query from explore from query", modelOK(`query: from(ab -> {group_by: astr}) { dimension: bigstr is UPPER(astr) } -> { group_by: bigstr }`));
|
|
332
|
-
test("query with shortcut filtered turtle", modelOK("query: allA is ab->aturtle {? astr ~ 'a%' }"));
|
|
333
|
-
test("query with filtered turtle", modelOK("query: allA is ab->aturtle { where: astr ~ 'a%' }"));
|
|
334
|
-
test("nest: in group_by:", modelOK(`
|
|
335
|
-
query: ab -> {
|
|
336
|
-
group_by: astr;
|
|
337
|
-
nest: nested_count is {
|
|
338
|
-
aggregate: acount
|
|
339
|
-
}
|
|
340
|
-
}
|
|
341
|
-
`));
|
|
342
|
-
test("reduce pipe project", modelOK(`
|
|
343
|
-
query: a -> { aggregate: f is count() } -> { project: f2 is f + 1 }
|
|
344
|
-
`));
|
|
345
|
-
test("refine and extend query", modelOK(`
|
|
346
|
-
query: a_by_str is a -> { group_by: astr }
|
|
347
|
-
query: -> a_by_str { aggregate: str_count is count() }
|
|
348
|
-
`));
|
|
349
|
-
test("query refinement preserves original", () => {
|
|
350
|
-
const x = new BetaModel(`
|
|
351
|
-
query: q is a -> { aggregate: acount is count() }
|
|
352
|
-
query: nq is -> q + { group_by: astr }
|
|
353
|
-
`);
|
|
354
|
-
expect(x).toTranslate();
|
|
355
|
-
const q = x.getQuery("q");
|
|
356
|
-
expect(q).toBeDefined();
|
|
357
|
-
if (q) {
|
|
358
|
-
const qFields = q.pipeline[0].fields;
|
|
359
|
-
expect(qFields.length).toBe(1);
|
|
360
|
-
}
|
|
361
|
-
});
|
|
362
|
-
test("query composition preserves original", () => {
|
|
363
|
-
const x = new BetaModel(`
|
|
364
|
-
query: q is ab -> { aggregate: acount }
|
|
365
|
-
query: nq is -> q -> { project: * }
|
|
366
|
-
`);
|
|
367
|
-
expect(x).toTranslate();
|
|
368
|
-
const q = x.getQuery("q");
|
|
369
|
-
expect(q).toBeDefined();
|
|
370
|
-
if (q) {
|
|
371
|
-
expect(q.pipeline.length).toBe(1);
|
|
372
|
-
}
|
|
373
|
-
});
|
|
374
|
-
test("all ungroup with args", modelOK(`
|
|
375
|
-
query: a -> {
|
|
376
|
-
group_by: astr
|
|
377
|
-
nest: by_int is {
|
|
378
|
-
group_by: ai
|
|
379
|
-
aggregate: bi_count is all(count(), ai)
|
|
380
|
-
}
|
|
381
|
-
}
|
|
382
|
-
`));
|
|
383
|
-
test("all ungroup checks args", () => {
|
|
384
|
-
expect(`
|
|
385
|
-
query: a -> {
|
|
386
|
-
group_by: astr
|
|
387
|
-
nest: by_int is {
|
|
388
|
-
group_by: ai
|
|
389
|
-
aggregate: bi_count is all(count(), afloat)
|
|
390
|
-
}
|
|
391
|
-
}
|
|
392
|
-
`).compileToFailWith(`all() 'afloat' is missing from query output`);
|
|
393
|
-
});
|
|
394
|
-
test("exclude ungroup with args", modelOK(`
|
|
395
|
-
query: a -> {
|
|
396
|
-
group_by: aa is 'a'
|
|
397
|
-
nest: by_b is {
|
|
398
|
-
group_by: bb is 'b'
|
|
399
|
-
nest: by_c is {
|
|
400
|
-
group_by: cc is 'c'
|
|
401
|
-
aggregate: bb_count is exclude(count(), aa, cc)
|
|
402
|
-
}
|
|
403
|
-
}
|
|
404
|
-
}
|
|
405
|
-
`));
|
|
406
|
-
test("exclude ungroup checks args", () => {
|
|
407
|
-
expect(`
|
|
408
|
-
query: a -> {
|
|
409
|
-
group_by: aa is 'a'
|
|
410
|
-
nest: by_b is {
|
|
411
|
-
group_by: bb is 'b'
|
|
412
|
-
nest: by_c is {
|
|
413
|
-
group_by: cc is 'c'
|
|
414
|
-
aggregate: bb_count is exclude(count(), aaa, cc)
|
|
415
|
-
}
|
|
416
|
-
}
|
|
417
|
-
}
|
|
418
|
-
`).compileToFailWith("exclude() 'aaa' is missing from query output");
|
|
419
|
-
});
|
|
420
|
-
test("exclude problem revealed by production models", modelOK(`
|
|
421
|
-
source: carriers is table('malloytest.carriers') {
|
|
422
|
-
primary_key: code
|
|
423
|
-
}
|
|
424
|
-
source: flights is table('malloytest.flights') {
|
|
425
|
-
primary_key: id2
|
|
426
|
-
join_one: carriers with carrier
|
|
427
|
-
|
|
428
|
-
query: carrier_overview is {
|
|
429
|
-
group_by: carrier_name is carriers.nickname
|
|
430
|
-
nest: top_destinations is {
|
|
431
|
-
group_by: destination
|
|
432
|
-
aggregate:
|
|
433
|
-
flights_to_dest is exclude(count(), carrier_name)*100
|
|
434
|
-
}
|
|
435
|
-
}
|
|
436
|
-
}
|
|
437
|
-
`));
|
|
438
|
-
});
|
|
439
|
-
describe("import:", () => {
|
|
440
|
-
test("simple import", () => {
|
|
441
|
-
const docParse = new BetaModel(`import "child"`);
|
|
442
|
-
const xr = docParse.unresolved();
|
|
443
|
-
expect(docParse).toBeErrorless();
|
|
444
|
-
expect(xr).toEqual({ urls: ["internal://test/langtests/child"] });
|
|
445
|
-
docParse.update({
|
|
446
|
-
urls: { "internal://test/langtests/child": "explore: aa is a" },
|
|
447
|
-
});
|
|
448
|
-
const yr = docParse.unresolved();
|
|
449
|
-
expect(yr).toBeNull();
|
|
450
|
-
});
|
|
451
|
-
test("missing import", () => {
|
|
452
|
-
const docParse = new BetaModel(`import "child"`);
|
|
453
|
-
const xr = docParse.unresolved();
|
|
454
|
-
expect(docParse).toBeErrorless();
|
|
455
|
-
expect(xr).toEqual({ urls: ["internal://test/langtests/child"] });
|
|
456
|
-
const reportedError = "ENOWAY: No way to find your child";
|
|
457
|
-
docParse.update({
|
|
458
|
-
errors: { urls: { "internal://test/langtests/child": reportedError } },
|
|
459
|
-
});
|
|
460
|
-
docParse.translate();
|
|
461
|
-
expect(docParse).not.toBeErrorless();
|
|
462
|
-
expect(docParse.prettyErrors()).toContain(reportedError);
|
|
463
|
-
});
|
|
464
|
-
test("chained imports", () => {
|
|
465
|
-
const docParse = new BetaModel(`import "child"`);
|
|
466
|
-
docParse.update({
|
|
467
|
-
urls: { "internal://test/langtests/child": `import "grandChild"` },
|
|
468
|
-
});
|
|
469
|
-
const xr = docParse.unresolved();
|
|
470
|
-
expect(docParse).toBeErrorless();
|
|
471
|
-
expect(xr).toEqual({ urls: ["internal://test/langtests/grandChild"] });
|
|
472
|
-
});
|
|
473
|
-
test("relative imports", () => {
|
|
474
|
-
const docParse = new BetaModel(`import "../parent.malloy"`);
|
|
475
|
-
expect(docParse).toCompile();
|
|
476
|
-
const xr = docParse.unresolved();
|
|
477
|
-
expect(xr).toEqual({ urls: ["internal://test/parent.malloy"] });
|
|
478
|
-
docParse.update({
|
|
479
|
-
urls: {
|
|
480
|
-
"internal://test/parent.malloy": `source: aa is table('aTable')`,
|
|
481
|
-
},
|
|
482
|
-
});
|
|
483
|
-
expect(docParse).toTranslate();
|
|
484
|
-
});
|
|
485
|
-
test("relative imports with errors", () => {
|
|
486
|
-
const docParse = new BetaModel(`import "../parent.malloy"`);
|
|
487
|
-
expect(docParse).toCompile();
|
|
488
|
-
const xr = docParse.unresolved();
|
|
489
|
-
expect(xr).toEqual({ urls: ["internal://test/parent.malloy"] });
|
|
490
|
-
docParse.update({
|
|
491
|
-
urls: {
|
|
492
|
-
"internal://test/parent.malloy": `
|
|
493
|
-
source: aa is table('aTable') {
|
|
494
|
-
dimension: astr is 'not legal beause astr exists'
|
|
495
|
-
}`,
|
|
496
|
-
},
|
|
497
|
-
});
|
|
498
|
-
expect(docParse).compileToFailWith("Cannot redefine 'astr'");
|
|
499
|
-
});
|
|
500
|
-
});
|
|
501
|
-
});
|
|
502
|
-
describe("explore properties", () => {
|
|
503
|
-
test("single dimension", modelOK("explore: aa is a { dimension: x is 1 }"));
|
|
504
|
-
test("multiple dimensions", modelOK(`
|
|
505
|
-
explore: aa is a {
|
|
506
|
-
dimension:
|
|
507
|
-
x is 1
|
|
508
|
-
y is 2
|
|
509
|
-
}
|
|
510
|
-
`));
|
|
511
|
-
test("single declare", modelOK("explore: aa is a { declare: x is 1 }"));
|
|
512
|
-
test("multiple declare", modelOK(`
|
|
513
|
-
explore: aa is a {
|
|
514
|
-
declare:
|
|
515
|
-
x is 1
|
|
516
|
-
y is 2
|
|
517
|
-
}
|
|
518
|
-
`));
|
|
519
|
-
test("single measure", modelOK("explore: aa is a { measure: x is count() }"));
|
|
520
|
-
test("multiple measures", modelOK(`
|
|
521
|
-
explore: aa is a {
|
|
522
|
-
dimension:
|
|
523
|
-
x is count()
|
|
524
|
-
y is x * x
|
|
525
|
-
}
|
|
526
|
-
`));
|
|
527
|
-
test("single where", modelOK("explore: aa is a { where: ai > 10 }"));
|
|
528
|
-
test("multiple where", modelOK(`
|
|
529
|
-
explore: aa is a {
|
|
530
|
-
where:
|
|
531
|
-
ai > 10,
|
|
532
|
-
af < 1000
|
|
533
|
-
}
|
|
534
|
-
`));
|
|
535
|
-
test("where clause can use the join namespace in source refined query", modelOK(`
|
|
536
|
-
source: flights is table('malloytest.flights') + {
|
|
537
|
-
query: boo is {
|
|
538
|
-
join_one: carriers is table('malloytest.carriers') on carrier = carriers.code
|
|
539
|
-
where: carriers.code = 'WN' | 'AA'
|
|
540
|
-
group_by: carriers.nickname
|
|
541
|
-
aggregate: flight_count is count()
|
|
542
|
-
}
|
|
543
|
-
}`));
|
|
544
|
-
describe("joins", () => {
|
|
545
|
-
test("with", modelOK("explore: x is a { join_one: b with astr }"));
|
|
546
|
-
test("with", modelOK("explore: x is a { join_one: y is b with astr }"));
|
|
547
|
-
test("with dotted ref", modelOK("explore: x is ab { join_one: xz is a with b.astr }"));
|
|
548
|
-
test("one on", modelOK("explore: x is a { join_one: b on astr = b.astr }"));
|
|
549
|
-
test("one is on", modelOK("explore: x is a { join_one: y is b on astr = y.astr }"));
|
|
550
|
-
test("many on", modelOK("explore: nab is a { join_many: b on astr = b.astr }"));
|
|
551
|
-
test("many is on", modelOK("explore: y is a { join_many: x is b on astr = x.astr }"));
|
|
552
|
-
test("cross", modelOK("explore: nab is a { join_cross: b }"));
|
|
553
|
-
test("cross is", modelOK("explore: nab is a { join_cross: xb is b }"));
|
|
554
|
-
test("cross on", modelOK("explore: nab is a { join_cross: b on true}"));
|
|
555
|
-
test("multiple joins", modelOK(`
|
|
556
|
-
explore: nab is a {
|
|
557
|
-
join_one:
|
|
558
|
-
b with astr,
|
|
559
|
-
br is b with astr
|
|
560
|
-
}
|
|
561
|
-
`));
|
|
562
|
-
test("with requires primary key", () => {
|
|
563
|
-
expect((0, test_translator_1.markSource) `
|
|
564
|
-
source: nb is b {
|
|
565
|
-
join_one: ${"bb is table('aTable') with astr"}
|
|
566
|
-
}
|
|
567
|
-
`).compileToFailWith("join_one: Cannot use with unless source has a primary key");
|
|
568
|
-
});
|
|
569
|
-
});
|
|
570
|
-
test("primary_key", modelOK("explore: c is a { primary_key: ai }"));
|
|
571
|
-
test("rename", modelOK("explore: c is a { rename: nn is ai }"));
|
|
572
|
-
test("accept single", () => {
|
|
573
|
-
const onlyAstr = new BetaModel("explore: c is a { accept: astr }");
|
|
574
|
-
expect(onlyAstr).toTranslate();
|
|
575
|
-
const c = onlyAstr.getSourceDef("c");
|
|
576
|
-
if (c) {
|
|
577
|
-
expect(c.fields.length).toBe(1);
|
|
578
|
-
}
|
|
579
|
-
});
|
|
580
|
-
test("accept multi", modelOK("explore: c is a { accept: astr, af }"));
|
|
581
|
-
test("except single", () => {
|
|
582
|
-
const noAstr = new BetaModel("explore: c is a { except: astr }");
|
|
583
|
-
expect(noAstr).toTranslate();
|
|
584
|
-
const c = noAstr.getSourceDef("c");
|
|
585
|
-
if (c) {
|
|
586
|
-
const foundAstr = c.fields.find((f) => f.name == "astr");
|
|
587
|
-
expect(foundAstr).toBeUndefined();
|
|
588
|
-
}
|
|
589
|
-
});
|
|
590
|
-
test("except multi", modelOK("explore: c is a { except: astr, af }"));
|
|
591
|
-
test("explore-query", modelOK("explore: c is a {query: q is { group_by: astr }}"));
|
|
592
|
-
test("refined explore-query", modelOK(`
|
|
593
|
-
explore: abNew is ab {
|
|
594
|
-
query: for1 is aturtle {? ai = 1 }
|
|
595
|
-
}
|
|
596
|
-
`));
|
|
597
|
-
test("chained explore-query", modelOK(`
|
|
598
|
-
explore: c is a {
|
|
599
|
-
query: chain is {
|
|
600
|
-
group_by: astr
|
|
601
|
-
} -> {
|
|
602
|
-
top: 10; order_by: astr
|
|
603
|
-
project: *
|
|
604
|
-
}
|
|
605
|
-
}
|
|
606
|
-
`));
|
|
607
|
-
test("multiple explore-query", modelOK(`
|
|
608
|
-
explore: abNew is ab {
|
|
609
|
-
query:
|
|
610
|
-
q1 is { group_by: astr },
|
|
611
|
-
q2 is { group_by: ai }
|
|
612
|
-
}
|
|
613
|
-
`));
|
|
614
|
-
});
|
|
615
|
-
describe("qops", () => {
|
|
616
|
-
test("group by single", modelOK("query: a->{ group_by: astr }"));
|
|
617
|
-
test("group_by x is x'", modelOK("query: a->{ group_by: ai is ai/2 }"));
|
|
618
|
-
test("group by multiple", modelOK("query: a->{ group_by: astr,ai }"));
|
|
619
|
-
test("aggregate single", modelOK("query: a->{ aggregate: num is count() }"));
|
|
620
|
-
test("aggregate multiple", modelOK(`
|
|
621
|
-
query: a->{
|
|
622
|
-
aggregate: num is count(), total is sum(ai)
|
|
623
|
-
}
|
|
624
|
-
`));
|
|
625
|
-
test("project ref", modelOK("query:ab->{ project: b.astr }"));
|
|
626
|
-
test("project *", modelOK("query:ab->{ project: * }"));
|
|
627
|
-
test("project def", modelOK("query:ab->{ project: one is 1 }"));
|
|
628
|
-
test("project multiple", modelOK(`
|
|
629
|
-
query: a->{
|
|
630
|
-
project: one is 1, astr
|
|
631
|
-
}
|
|
632
|
-
`));
|
|
633
|
-
test("index single", modelOK("query:a->{index: astr}"));
|
|
634
|
-
test("index path", modelOK("query:ab->{index: ab.astr}"));
|
|
635
|
-
test("index unique on path", modelOK("query:ab->{index: b.astr, ab.astr}"));
|
|
636
|
-
test("index join.*", modelOK("query:ab->{index: ab.*}"));
|
|
637
|
-
test("index multiple", () => {
|
|
638
|
-
const model = new BetaModel("query:a->{index: af, astr}");
|
|
639
|
-
expect(model).toTranslate();
|
|
640
|
-
const q = model.getQuery(0);
|
|
641
|
-
expect(q).toBeDefined();
|
|
642
|
-
if (q) {
|
|
643
|
-
const index = q.pipeline[0];
|
|
644
|
-
expect(index.type).toBe("index");
|
|
645
|
-
expect(index.fields).toEqual(["af", "astr"]);
|
|
646
|
-
}
|
|
647
|
-
});
|
|
648
|
-
test("index star", () => {
|
|
649
|
-
const model = new BetaModel("query:a->{index: *, astr}");
|
|
650
|
-
expect(model).toTranslate();
|
|
651
|
-
const q = model.getQuery(0);
|
|
652
|
-
expect(q).toBeDefined();
|
|
653
|
-
if (q) {
|
|
654
|
-
const index = q.pipeline[0];
|
|
655
|
-
expect(index.type).toBe("index");
|
|
656
|
-
expect(index.fields).toEqual(["*", "astr"]);
|
|
657
|
-
}
|
|
658
|
-
});
|
|
659
|
-
test("index by", modelOK("query:a->{index: * by ai}"));
|
|
660
|
-
test("index sampled", modelOK("query:a->{index: *; sample: true}"));
|
|
661
|
-
test("index unsampled", modelOK("query:a->{index: *; sample: false}"));
|
|
662
|
-
test("index sample-percent", () => {
|
|
663
|
-
const model = new BetaModel("query:a->{index: *; sample: 42%}");
|
|
664
|
-
expect(model).toTranslate();
|
|
665
|
-
const q = model.getQuery(0);
|
|
666
|
-
expect(q).toBeDefined();
|
|
667
|
-
if (q) {
|
|
668
|
-
const index = q.pipeline[0];
|
|
669
|
-
expect(index.type).toBe("index");
|
|
670
|
-
if (index.type == "index") {
|
|
671
|
-
expect(index.sample).toEqual({ percent: 42 });
|
|
672
|
-
}
|
|
673
|
-
}
|
|
674
|
-
});
|
|
675
|
-
test("index sample-rows", modelOK("query:a->{index: *; sample: 100000}"));
|
|
676
|
-
test("top N", modelOK("query: a->{ top: 5; group_by: astr }"));
|
|
677
|
-
test("top N by field", modelOK("query: a->{top: 5 by af; group_by: astr}"));
|
|
678
|
-
test("top N by expression", modelOK("query: ab->{top: 5 by acount; group_by: astr}"));
|
|
679
|
-
test("limit N", modelOK("query: a->{ limit: 5; group_by: astr }"));
|
|
680
|
-
test("order by", modelOK("query: a->{ order_by: af; group_by: astr }"));
|
|
681
|
-
test("order by asc", modelOK("query: a->{ order_by: af asc; group_by: astr }"));
|
|
682
|
-
test("order by desc", modelOK("query: a->{ order_by: af desc; group_by: astr }"));
|
|
683
|
-
test("order by N", modelOK("query: a->{ order_by: 1 asc; group_by: astr }"));
|
|
684
|
-
test("order by multiple", modelOK(`
|
|
685
|
-
query: a->{
|
|
686
|
-
order_by: 1 asc, af desc
|
|
687
|
-
group_by: astr, af
|
|
688
|
-
}
|
|
689
|
-
`));
|
|
690
|
-
test("where single", modelOK("query:a->{ group_by: astr; where: af > 10 }"));
|
|
691
|
-
test("having single", modelOK("query:ab->{ aggregate: acount; group_by: astr; having: acount > 10 }"));
|
|
692
|
-
test("where multiple", modelOK("query:a->{ group_by: astr; where: af > 10,astr~'a%' }"));
|
|
693
|
-
test(`filters preserve source formatting in code:`, () => {
|
|
694
|
-
const model = new BetaModel(`source: notb is a + { where: astr != 'b' }`);
|
|
695
|
-
expect(model).toTranslate();
|
|
696
|
-
const notb = model.getSourceDef("notb");
|
|
697
|
-
expect(notb).toBeDefined();
|
|
698
|
-
if (notb) {
|
|
699
|
-
const f = notb.filterList;
|
|
700
|
-
expect(f).toBeDefined();
|
|
701
|
-
if (f) {
|
|
702
|
-
expect(f[0].code).toBe("astr != 'b'");
|
|
703
|
-
}
|
|
704
|
-
}
|
|
705
|
-
});
|
|
706
|
-
test(`field expressions preserve source formatting in code:`, () => {
|
|
707
|
-
const model = new BetaModel(`source: notb is a + { dimension: d is 1 + 2 }`);
|
|
708
|
-
expect(model).toTranslate();
|
|
709
|
-
const notb = model.getSourceDef("notb");
|
|
710
|
-
expect(notb).toBeDefined();
|
|
711
|
-
if (notb) {
|
|
712
|
-
const d = notb.fields.find((f) => f.as || f.name === "d");
|
|
713
|
-
expect(d).toBeDefined();
|
|
714
|
-
expect(d === null || d === void 0 ? void 0 : d.type).toBe("number");
|
|
715
|
-
if ((d === null || d === void 0 ? void 0 : d.type) === "number") {
|
|
716
|
-
expect(d.code).toBe("1 + 2");
|
|
717
|
-
}
|
|
718
|
-
}
|
|
719
|
-
});
|
|
720
|
-
test("nest single", modelOK(`
|
|
721
|
-
query: a->{
|
|
722
|
-
group_by: ai
|
|
723
|
-
nest: nestbystr is { group_by: astr; aggregate: N is count() }
|
|
724
|
-
}
|
|
725
|
-
`));
|
|
726
|
-
test("nest multiple", modelOK(`
|
|
727
|
-
query: a->{
|
|
728
|
-
group_by: ai
|
|
729
|
-
nest:
|
|
730
|
-
nestbystr is { group_by: astr; aggregate: N is count() },
|
|
731
|
-
renest is { group_by: astr; aggregate: N is count() }
|
|
732
|
-
}
|
|
733
|
-
`));
|
|
734
|
-
test("nest ref", modelOK("query: ab->{group_by: ai; nest: aturtle}"));
|
|
735
|
-
test("refine query with extended source", () => {
|
|
736
|
-
const m = new BetaModel(`
|
|
737
|
-
source: nab is ab {
|
|
738
|
-
query: xturtle is aturtle + {
|
|
739
|
-
declare: aratio is ai / acount
|
|
740
|
-
}
|
|
741
|
-
}
|
|
742
|
-
query: nab -> xturtle + { aggregate: aratio }
|
|
743
|
-
`);
|
|
744
|
-
expect(m).toTranslate();
|
|
745
|
-
const t = m.translate();
|
|
746
|
-
if (t.translated) {
|
|
747
|
-
const q = t.translated.queryList[0].pipeline[0];
|
|
748
|
-
if (q.type == "reduce" && q.extendSource) {
|
|
749
|
-
expect(q.extendSource.length).toBe(1);
|
|
750
|
-
const f = q.extendSource[0];
|
|
751
|
-
expect(f.type).toBe("number");
|
|
752
|
-
if (f.type == "number") {
|
|
753
|
-
expect(f.aggregate).toBeTruthy();
|
|
754
|
-
}
|
|
755
|
-
}
|
|
756
|
-
else {
|
|
757
|
-
fail("Did not generate extendSource");
|
|
758
|
-
}
|
|
759
|
-
}
|
|
760
|
-
});
|
|
761
|
-
test("refine query source with field", () => {
|
|
762
|
-
const m = new BetaModel(`
|
|
763
|
-
query: ab -> aturtle + {
|
|
764
|
-
declare: aratio is ai / acount
|
|
765
|
-
aggregate: aratio
|
|
766
|
-
}
|
|
767
|
-
`);
|
|
768
|
-
expect(m).toTranslate();
|
|
769
|
-
const t = m.translate();
|
|
770
|
-
if (t.translated) {
|
|
771
|
-
const q = t.translated.queryList[0].pipeline[0];
|
|
772
|
-
if (q.type == "reduce" && q.extendSource) {
|
|
773
|
-
expect(q.extendSource.length).toBe(1);
|
|
774
|
-
const f = q.extendSource[0];
|
|
775
|
-
expect(f.type).toBe("number");
|
|
776
|
-
if (f.type == "number") {
|
|
777
|
-
expect(f.aggregate).toBeTruthy();
|
|
778
|
-
}
|
|
779
|
-
}
|
|
780
|
-
else {
|
|
781
|
-
fail("Did not generate extendSource");
|
|
782
|
-
}
|
|
783
|
-
}
|
|
784
|
-
});
|
|
785
|
-
test("refine query source with join", () => {
|
|
786
|
-
const m = new BetaModel(`
|
|
787
|
-
query: ab -> aturtle + {
|
|
788
|
-
join_one: bb is b on bb.astr = astr
|
|
789
|
-
group_by: foo is bb.astr
|
|
790
|
-
}
|
|
791
|
-
`);
|
|
792
|
-
expect(m).toTranslate();
|
|
793
|
-
const t = m.translate();
|
|
794
|
-
if (t.translated) {
|
|
795
|
-
const q = t.translated.queryList[0].pipeline[0];
|
|
796
|
-
if (q.type == "reduce" && q.extendSource) {
|
|
797
|
-
expect(q.extendSource.length).toBe(1);
|
|
798
|
-
expect(q.extendSource[0].type).toBe("struct");
|
|
799
|
-
}
|
|
800
|
-
else {
|
|
801
|
-
fail("Did not generate extendSource");
|
|
802
|
-
}
|
|
803
|
-
}
|
|
804
|
-
});
|
|
805
|
-
});
|
|
806
|
-
describe("expressions", () => {
|
|
807
|
-
describe("literals", () => {
|
|
808
|
-
test("integer", exprOK("42"));
|
|
809
|
-
test("string", exprOK(`'fortywo-two'`));
|
|
810
|
-
test("string with quoted quote", exprOK(`'Isn` + `\\` + `'t this nice'`));
|
|
811
|
-
test("string with quoted backslash", exprOK(`'Is ` + `\\` + `\\` + ` nice'`));
|
|
812
|
-
test("year", exprOK("@1960"));
|
|
813
|
-
test("quarter", exprOK("@1960-Q1"));
|
|
814
|
-
test("week", exprOK("@WK1960-06-26"));
|
|
815
|
-
test("month", exprOK("@1960-06"));
|
|
816
|
-
test("day", exprOK("@1960-06-30"));
|
|
817
|
-
test("minute", exprOK("@1960-06-30 10:30"));
|
|
818
|
-
test("second", exprOK("@1960-06-30 10:30:31"));
|
|
819
|
-
test("null", exprOK("null"));
|
|
820
|
-
test("now", exprOK("now"));
|
|
821
|
-
test("true", exprOK("true"));
|
|
822
|
-
test("false", exprOK("false"));
|
|
823
|
-
test("regex", exprOK("r'RegularExpression'"));
|
|
824
|
-
});
|
|
825
|
-
describe("timeframes", () => {
|
|
826
|
-
const timeframes = [
|
|
827
|
-
"second",
|
|
828
|
-
"minute",
|
|
829
|
-
"hour",
|
|
830
|
-
"day",
|
|
831
|
-
"week",
|
|
832
|
-
"month",
|
|
833
|
-
"quarter",
|
|
834
|
-
"year",
|
|
835
|
-
];
|
|
836
|
-
describe("timestamp truncation", () => {
|
|
837
|
-
for (const unit of timeframes) {
|
|
838
|
-
test(`timestamp truncate ${unit}`, exprOK(`ats.${unit}`));
|
|
839
|
-
}
|
|
840
|
-
});
|
|
841
|
-
describe("timestamp difference", () => {
|
|
842
|
-
for (const unit of timeframes) {
|
|
843
|
-
// TODO expect these to error ...
|
|
844
|
-
test(`timestamp extract ${unit}`, exprOK(`${unit}(@2021 to ats)`));
|
|
845
|
-
}
|
|
846
|
-
});
|
|
847
|
-
});
|
|
848
|
-
test("field name", exprOK("astr"));
|
|
849
|
-
test("function call", exprOK("CURRENT_TIMESTAMP()"));
|
|
850
|
-
describe("operators", () => {
|
|
851
|
-
test("addition", exprOK("42 + 7"));
|
|
852
|
-
test("subtraction", exprOK("42 - 7"));
|
|
853
|
-
test("multiplication", exprOK("42 * 7"));
|
|
854
|
-
test("division", exprOK("42 / 7"));
|
|
855
|
-
test("unary negation", exprOK("- ai"));
|
|
856
|
-
test("equal", exprOK("42 = 7"));
|
|
857
|
-
test("not equal", exprOK("42 != 7"));
|
|
858
|
-
test("greater than", exprOK("42 > 7"));
|
|
859
|
-
test("greater than or equal", exprOK("42 >= 7"));
|
|
860
|
-
test("less than or equal", exprOK("42 <= 7"));
|
|
861
|
-
test("less than", exprOK("42 < 7"));
|
|
862
|
-
test("match", exprOK("'forty-two' ~ 'fifty-four'"));
|
|
863
|
-
test("not match", exprOK("'forty-two' !~ 'fifty-four'"));
|
|
864
|
-
test("apply", exprOK("'forty-two' ? 'fifty-four'"));
|
|
865
|
-
test("not", exprOK("not true"));
|
|
866
|
-
test("and", exprOK("true and false"));
|
|
867
|
-
test("or", exprOK("true or false"));
|
|
868
|
-
});
|
|
869
|
-
test("filtered measure", exprOK("acount {? astr = 'why?' }"));
|
|
870
|
-
describe("aggregate forms", () => {
|
|
871
|
-
test("count", exprOK("count()"));
|
|
872
|
-
test("count distinct", exprOK("count(distinct astr)"));
|
|
873
|
-
test("join.count()", exprOK("b.count()"));
|
|
874
|
-
for (const f of ["sum", "min", "max", "avg"]) {
|
|
875
|
-
const fOfT = `${f}(af)`;
|
|
876
|
-
test(fOfT, exprOK(fOfT));
|
|
877
|
-
if (f !== "min" && f !== "max") {
|
|
878
|
-
const joinDot = `b.af.${f}()`;
|
|
879
|
-
test(joinDot, exprOK(joinDot));
|
|
880
|
-
const joinAgg = `b.${f}(af)`;
|
|
881
|
-
test(joinAgg, exprOK(joinAgg));
|
|
882
|
-
}
|
|
883
|
-
}
|
|
884
|
-
});
|
|
885
|
-
describe("pick statements", () => {
|
|
886
|
-
test("full", exprOK(`
|
|
887
|
-
pick 'the answer' when ai = 42
|
|
888
|
-
pick 'the questionable answer' when ai = 54
|
|
889
|
-
else 'random'
|
|
890
|
-
`));
|
|
891
|
-
test("applied", exprOK(`
|
|
892
|
-
astr ?
|
|
893
|
-
pick 'the answer' when = '42'
|
|
894
|
-
pick 'the questionable answer' when = '54'
|
|
895
|
-
else 'random'
|
|
896
|
-
`));
|
|
897
|
-
test("filtering", exprOK(`
|
|
898
|
-
astr ? pick 'missing value' when NULL
|
|
899
|
-
`));
|
|
900
|
-
test("tiering", exprOK(`
|
|
901
|
-
ai ?
|
|
902
|
-
pick 1 when < 10
|
|
903
|
-
pick 10 when < 100
|
|
904
|
-
pick 100 when < 1000
|
|
905
|
-
else 10000
|
|
906
|
-
`));
|
|
907
|
-
test("transforming", exprOK(`
|
|
908
|
-
ai ?
|
|
909
|
-
pick 'small' when < 10
|
|
910
|
-
pick 'medium' when < 100
|
|
911
|
-
else 'large'
|
|
912
|
-
`));
|
|
913
|
-
test("when single values", exprOK(`
|
|
914
|
-
ai ?
|
|
915
|
-
pick 'one' when 1
|
|
916
|
-
else 'a lot'
|
|
917
|
-
`));
|
|
918
|
-
test("n-ary without else", () => {
|
|
919
|
-
expect((0, test_translator_1.markSource) `
|
|
920
|
-
explore: na is a + { dimension: d is
|
|
921
|
-
pick 7 when true and true
|
|
922
|
-
}
|
|
923
|
-
`).compileToFailWith("pick incomplete, missing 'else'", "Cannot define 'd', value has unknown type");
|
|
924
|
-
});
|
|
925
|
-
test("n-ary with mismatch when clauses", () => {
|
|
926
|
-
expect((0, test_translator_1.markSource) `
|
|
927
|
-
explore: na is a + { dimension: d is
|
|
928
|
-
pick 7 when true and true
|
|
929
|
-
pick '7' when true or true
|
|
930
|
-
else 7
|
|
931
|
-
}
|
|
932
|
-
`).compileToFailWith("pick type 'string', expected 'number'", "Cannot define 'd', value has unknown type");
|
|
933
|
-
});
|
|
934
|
-
test("n-ary with mismatched else clause", () => {
|
|
935
|
-
expect((0, test_translator_1.markSource) `
|
|
936
|
-
explore: na is a + { dimension: d is
|
|
937
|
-
pick 7 when true and true
|
|
938
|
-
else '7'
|
|
939
|
-
}
|
|
940
|
-
`).compileToFailWith("else type 'string', expected 'number'", "Cannot define 'd', value has unknown type");
|
|
941
|
-
});
|
|
942
|
-
test("applied else mismatch", () => {
|
|
943
|
-
expect((0, test_translator_1.markSource) `
|
|
944
|
-
explore: na is a + { dimension: d is
|
|
945
|
-
7 ? pick 7 when 7 else 'not seven'
|
|
946
|
-
}
|
|
947
|
-
`).compileToFailWith("else type 'string', expected 'number'", "Cannot define 'd', value has unknown type");
|
|
948
|
-
});
|
|
949
|
-
test("applied default mismatch", () => {
|
|
950
|
-
expect((0, test_translator_1.markSource) `
|
|
951
|
-
explore: na is a + { dimension: d is
|
|
952
|
-
7 ? pick 'seven' when 7
|
|
953
|
-
}
|
|
954
|
-
`).compileToFailWith("pick default type 'number', expected 'string'", "Cannot define 'd', value has unknown type");
|
|
955
|
-
});
|
|
956
|
-
test("applied when mismatch", () => {
|
|
957
|
-
expect((0, test_translator_1.markSource) `
|
|
958
|
-
explore: na is a + { dimension: d is
|
|
959
|
-
7 ? pick 'seven' when 7 pick 6 when 6
|
|
960
|
-
}
|
|
961
|
-
`).compileToFailWith("pick type 'number', expected 'string'", "Cannot define 'd', value has unknown type");
|
|
962
|
-
});
|
|
963
|
-
});
|
|
964
|
-
});
|
|
965
|
-
describe("sql backdoor", () => {
|
|
966
|
-
function makeSchemaResponse(sql) {
|
|
967
|
-
return {
|
|
968
|
-
type: "struct",
|
|
969
|
-
name: sql.name,
|
|
970
|
-
dialect: "standardsql'",
|
|
971
|
-
structSource: {
|
|
972
|
-
type: "sql",
|
|
973
|
-
method: "subquery",
|
|
974
|
-
sqlBlock: { ...sql },
|
|
975
|
-
},
|
|
976
|
-
structRelationship: { type: "basetable", connectionName: "bigquery" },
|
|
977
|
-
fields: test_translator_1.aTableDef.fields,
|
|
978
|
-
};
|
|
979
|
-
}
|
|
980
|
-
test("single sql statement", modelOK("sql: users is || SELECT * FROM USERS;;"));
|
|
981
|
-
test("connection shows up in model", () => {
|
|
982
|
-
const model = new BetaModel(`
|
|
983
|
-
sql: users IS || SELECT * FROM aTable ;; on "someConnection";
|
|
984
|
-
source: malloyUsers is from_sql(users) { primary_key: ai }
|
|
985
|
-
`);
|
|
986
|
-
const needReq = model.translate();
|
|
987
|
-
expect(model).toBeErrorless();
|
|
988
|
-
const needs = needReq === null || needReq === void 0 ? void 0 : needReq.sqlStructs;
|
|
989
|
-
expect(needs).toBeDefined();
|
|
990
|
-
if (needs) {
|
|
991
|
-
expect(needs.length).toBe(1);
|
|
992
|
-
const sql = (0, sql_block_1.makeSQLBlock)({
|
|
993
|
-
select: " SELECT * FROM aTable ",
|
|
994
|
-
connection: "someConnection",
|
|
995
|
-
});
|
|
996
|
-
expect(needs[0]).toMatchObject(sql);
|
|
997
|
-
const refKey = needs[0].name;
|
|
998
|
-
expect(refKey).toBeDefined();
|
|
999
|
-
if (refKey) {
|
|
1000
|
-
model.update({ sqlStructs: { [refKey]: makeSchemaResponse(sql) } });
|
|
1001
|
-
expect(model).toTranslate();
|
|
1002
|
-
}
|
|
1003
|
-
const users = model.getSourceDef("malloyUsers");
|
|
1004
|
-
expect(users).toBeDefined();
|
|
1005
|
-
expect(users).toHaveProperty("structSource.sqlBlock.connection", "someConnection");
|
|
1006
|
-
}
|
|
1007
|
-
});
|
|
1008
|
-
test("explore from sql", () => {
|
|
1009
|
-
const model = new BetaModel(`
|
|
1010
|
-
sql: users IS || SELECT * FROM aTable ;;
|
|
1011
|
-
source: malloyUsers is from_sql(users) { primary_key: ai }
|
|
1012
|
-
`);
|
|
1013
|
-
const needReq = model.translate();
|
|
1014
|
-
expect(model).toBeErrorless();
|
|
1015
|
-
const needs = needReq === null || needReq === void 0 ? void 0 : needReq.sqlStructs;
|
|
1016
|
-
expect(needs).toBeDefined();
|
|
1017
|
-
if (needs) {
|
|
1018
|
-
expect(needs.length).toBe(1);
|
|
1019
|
-
const sql = (0, sql_block_1.makeSQLBlock)({ select: " SELECT * FROM aTable " });
|
|
1020
|
-
expect(needs[0]).toMatchObject(sql);
|
|
1021
|
-
const refKey = needs[0].name;
|
|
1022
|
-
expect(refKey).toBeDefined();
|
|
1023
|
-
if (refKey) {
|
|
1024
|
-
model.update({ sqlStructs: { [refKey]: makeSchemaResponse(sql) } });
|
|
1025
|
-
expect(model).toTranslate();
|
|
1026
|
-
}
|
|
1027
|
-
}
|
|
1028
|
-
});
|
|
1029
|
-
test("explore from imported sql-based-source", () => {
|
|
1030
|
-
const createModel = `
|
|
1031
|
-
sql: users IS || SELECT * FROM aTable ;;
|
|
1032
|
-
source: malloyUsers is from_sql(users) { primary_key: ai }
|
|
1033
|
-
`;
|
|
1034
|
-
const model = new BetaModel(`
|
|
1035
|
-
import "createModel.malloy"
|
|
1036
|
-
source: foo is malloyUsers
|
|
1037
|
-
`);
|
|
1038
|
-
model.importZone.define("internal://test/langtests/createModel.malloy", createModel);
|
|
1039
|
-
const needReq = model.translate();
|
|
1040
|
-
expect(model).toBeErrorless();
|
|
1041
|
-
const needs = needReq === null || needReq === void 0 ? void 0 : needReq.sqlStructs;
|
|
1042
|
-
expect(needs).toBeDefined();
|
|
1043
|
-
if (needs) {
|
|
1044
|
-
expect(needs.length).toBe(1);
|
|
1045
|
-
const sql = (0, sql_block_1.makeSQLBlock)({ select: " SELECT * FROM aTable " });
|
|
1046
|
-
expect(needs[0]).toMatchObject(sql);
|
|
1047
|
-
const refKey = needs[0].name;
|
|
1048
|
-
expect(refKey).toBeDefined();
|
|
1049
|
-
if (refKey) {
|
|
1050
|
-
model.update({ sqlStructs: { [refKey]: makeSchemaResponse(sql) } });
|
|
1051
|
-
expect(model).toTranslate();
|
|
1052
|
-
}
|
|
1053
|
-
}
|
|
1054
|
-
});
|
|
1055
|
-
});
|
|
1056
|
-
describe("error handling", () => {
|
|
1057
|
-
test("field and query with same name does not overflow", () => {
|
|
1058
|
-
expect(`
|
|
1059
|
-
source: flights is table('malloytest.flights') {
|
|
1060
|
-
query: carrier is { group_by: carrier }
|
|
1061
|
-
}
|
|
1062
|
-
`).compileToFailWith("Cannot redefine 'carrier'");
|
|
1063
|
-
});
|
|
1064
|
-
test("redefine source", () => {
|
|
1065
|
-
expect((0, test_translator_1.markSource) `
|
|
1066
|
-
source: airports is table('malloytest.airports') + {
|
|
1067
|
-
primary_key: code
|
|
1068
|
-
}
|
|
1069
|
-
source: airports is table('malloytest.airports') + {
|
|
1070
|
-
primary_key: code
|
|
1071
|
-
}
|
|
1072
|
-
`).compileToFailWith("Cannot redefine 'airports'");
|
|
1073
|
-
});
|
|
1074
|
-
test("query from undefined source", () => {
|
|
1075
|
-
expect((0, test_translator_1.markSource) `query: ${"x"}->{ project: y }`).compileToFailWith("Undefined source 'x'");
|
|
1076
|
-
});
|
|
1077
|
-
test("query with expression from undefined source", () => {
|
|
1078
|
-
// Regression check: Once upon a time this died with an exception even
|
|
1079
|
-
// when "query: x->{ group_by: y}" (above) generated the correct error.
|
|
1080
|
-
expect((0, test_translator_1.markSource) `query: ${"x"}->{ project: y is z / 2 }`).compileToFailWith("Undefined source 'x'");
|
|
1081
|
-
});
|
|
1082
|
-
test("join reference before definition", () => {
|
|
1083
|
-
expect((0, test_translator_1.markSource) `
|
|
1084
|
-
explore: newAB is a { join_one: newB is ${"bb"} on astring }
|
|
1085
|
-
explore: newB is b
|
|
1086
|
-
`).compileToFailWith("Undefined source 'bb'");
|
|
1087
|
-
});
|
|
1088
|
-
test("non-rename rename", badModel("explore: na is a { rename: astr is astr }", "Can't rename field to itself"));
|
|
1089
|
-
test("reference to field in its definition", badModel(`explore: na is a { dimension: ustr is UPPER(ustr) } `, "Circular reference to 'ustr' in definition"));
|
|
1090
|
-
test("empty model", modelOK(""));
|
|
1091
|
-
test("one line model ", modelOK("\n"));
|
|
1092
|
-
test("query without fields", badModel(`query: a -> { top: 5 }`, "Can't determine query type (group_by/aggregate/nest,project,index)"));
|
|
1093
|
-
test("refine can't change query type", badModel(`query: ab -> aturtle { project: astr }`, "project: not legal in grouping query"));
|
|
1094
|
-
test("undefined field ref in query", badModel(`query: ab -> { aggregate: xyzzy }`, "'xyzzy' is not defined"));
|
|
1095
|
-
// test("queries with anonymous expressions", () => {
|
|
1096
|
-
// const m = new BetaModel("query: a->{\n group_by: a+1\n}");
|
|
1097
|
-
// expect(m).not.toCompile();
|
|
1098
|
-
// const errList = m.errors().errors;
|
|
1099
|
-
// const firstError = errList[0];
|
|
1100
|
-
// expect(firstError.message).toBe("Expressions in queries must have names");
|
|
1101
|
-
// });
|
|
1102
|
-
test("query on source with errors", () => {
|
|
1103
|
-
expect((0, test_translator_1.markSource) `
|
|
1104
|
-
explore: na is a { join_one: ${"n"} on astr }
|
|
1105
|
-
`).compileToFailWith("Undefined source 'n'");
|
|
1106
|
-
});
|
|
1107
|
-
test("detect duplicate output field names", () => {
|
|
1108
|
-
expect((0, test_translator_1.markSource) `query: ab -> { group_by: astr, ${"astr"} }`).compileToFailWith("Output already has a field named 'astr'");
|
|
1109
|
-
});
|
|
1110
|
-
test("detect join tail overlap existing ref", () => {
|
|
1111
|
-
expect((0, test_translator_1.markSource) `query: ab -> { group_by: astr, ${"b.astr"} }`).compileToFailWith("Output already has a field named 'astr'");
|
|
1112
|
-
});
|
|
1113
|
-
test("undefined in expression with regex compare", () => {
|
|
1114
|
-
expect(`
|
|
1115
|
-
source: c is a {
|
|
1116
|
-
dimension: d is meaning_of_life ~ r'(forty two|fifty four)'
|
|
1117
|
-
}
|
|
1118
|
-
`).compileToFailWith("'meaning_of_life' is not defined");
|
|
1119
|
-
});
|
|
1120
|
-
test("detect output collision on join references", () => {
|
|
1121
|
-
expect(`
|
|
1122
|
-
query: ab -> {
|
|
1123
|
-
group_by: astr, b.astr
|
|
1124
|
-
}
|
|
1125
|
-
`).compileToFailWith("Output already has a field named 'astr'");
|
|
1126
|
-
});
|
|
1127
|
-
test("rejoin a query is renamed", modelOK(`
|
|
1128
|
-
source: querySrc is from(
|
|
1129
|
-
table('malloytest.flights')->{
|
|
1130
|
-
group_by: origin
|
|
1131
|
-
nest: nested is { group_by: destination }
|
|
1132
|
-
}
|
|
1133
|
-
)
|
|
1134
|
-
|
|
1135
|
-
source: refineQuerySrc is querySrc {
|
|
1136
|
-
join_one: rejoin is querySrc on 7=8
|
|
1137
|
-
query: broken is {
|
|
1138
|
-
group_by: rejoin.nested.destination
|
|
1139
|
-
}
|
|
1140
|
-
}
|
|
1141
|
-
`));
|
|
1142
|
-
});
|
|
1143
|
-
function getSelectOneStruct(sqlBlock) {
|
|
1144
|
-
return {
|
|
1145
|
-
type: "struct",
|
|
1146
|
-
name: sqlBlock.name,
|
|
1147
|
-
dialect: "bigquery",
|
|
1148
|
-
structSource: {
|
|
1149
|
-
type: "sql",
|
|
1150
|
-
method: "subquery",
|
|
1151
|
-
sqlBlock,
|
|
1152
|
-
},
|
|
1153
|
-
structRelationship: { type: "basetable", connectionName: "bigquery" },
|
|
1154
|
-
fields: [{ type: "number", name: "one" }],
|
|
1155
|
-
};
|
|
1156
|
-
}
|
|
1157
|
-
describe("source locations", () => {
|
|
1158
|
-
test("renamed explore location", () => {
|
|
1159
|
-
const source = (0, test_translator_1.markSource) `explore: ${"na is a"}`;
|
|
1160
|
-
const m = new BetaModel(source.code);
|
|
1161
|
-
expect(m).toCompile();
|
|
1162
|
-
expect((0, test_translator_1.getExplore)(m.modelDef, "na").location).toMatchObject(source.locations[0]);
|
|
1163
|
-
});
|
|
1164
|
-
test("refined explore location", () => {
|
|
1165
|
-
const source = (0, test_translator_1.markSource) `explore: ${"na is a {}"}`;
|
|
1166
|
-
const m = new BetaModel(source.code);
|
|
1167
|
-
expect(m).toCompile();
|
|
1168
|
-
expect((0, test_translator_1.getExplore)(m.modelDef, "na").location).toMatchObject(source.locations[0]);
|
|
1169
|
-
});
|
|
1170
|
-
test("location of defined dimension", () => {
|
|
1171
|
-
const source = (0, test_translator_1.markSource) `explore: na is a { dimension: ${"x is 1"} }`;
|
|
1172
|
-
const m = new BetaModel(source.code);
|
|
1173
|
-
expect(m).toTranslate();
|
|
1174
|
-
const na = (0, test_translator_1.getExplore)(m.modelDef, "na");
|
|
1175
|
-
const x = (0, test_translator_1.getField)(na, "x");
|
|
1176
|
-
expect(x.location).toMatchObject(source.locations[0]);
|
|
1177
|
-
});
|
|
1178
|
-
test("location of defined measure", () => {
|
|
1179
|
-
const source = (0, test_translator_1.markSource) `explore: na is a { measure: ${"x is count()"} }`;
|
|
1180
|
-
const m = new BetaModel(source.code);
|
|
1181
|
-
expect(m).toTranslate();
|
|
1182
|
-
const na = (0, test_translator_1.getExplore)(m.modelDef, "na");
|
|
1183
|
-
const x = (0, test_translator_1.getField)(na, "x");
|
|
1184
|
-
expect(x.location).toMatchObject(source.locations[0]);
|
|
1185
|
-
});
|
|
1186
|
-
test("location of defined query", () => {
|
|
1187
|
-
const source = (0, test_translator_1.markSource) `explore: na is a { query: ${"x is { group_by: y is 1 }"} }`;
|
|
1188
|
-
const m = new BetaModel(source.code);
|
|
1189
|
-
expect(m).toTranslate();
|
|
1190
|
-
const na = (0, test_translator_1.getExplore)(m.modelDef, "na");
|
|
1191
|
-
const x = (0, test_translator_1.getField)(na, "x");
|
|
1192
|
-
expect(x.location).toMatchObject(source.locations[0]);
|
|
1193
|
-
});
|
|
1194
|
-
test("location of defined field inside a query", () => {
|
|
1195
|
-
const source = (0, test_translator_1.markSource) `
|
|
1196
|
-
explore: na is a {
|
|
1197
|
-
query: x is {
|
|
1198
|
-
group_by: ${"y is 1"}
|
|
1199
|
-
}
|
|
1200
|
-
}`;
|
|
1201
|
-
const m = new BetaModel(source.code);
|
|
1202
|
-
expect(m).toTranslate();
|
|
1203
|
-
const na = (0, test_translator_1.getExplore)(m.modelDef, "na");
|
|
1204
|
-
const x = (0, test_translator_1.getQueryField)(na, "x");
|
|
1205
|
-
const y = (0, test_translator_1.getField)(x.pipeline[0], "y");
|
|
1206
|
-
expect(y.location).toMatchObject(source.locations[0]);
|
|
1207
|
-
});
|
|
1208
|
-
test("location of filtered field inside a query", () => {
|
|
1209
|
-
const source = (0, test_translator_1.markSource) `
|
|
1210
|
-
explore: na is a {
|
|
1211
|
-
measure: y is count()
|
|
1212
|
-
query: x is {
|
|
1213
|
-
group_by: ${"z is y { where: true }"}
|
|
1214
|
-
}
|
|
1215
|
-
}`;
|
|
1216
|
-
const m = new BetaModel(source.code);
|
|
1217
|
-
expect(m).toTranslate();
|
|
1218
|
-
const na = (0, test_translator_1.getExplore)(m.modelDef, "na");
|
|
1219
|
-
const x = (0, test_translator_1.getQueryField)(na, "x");
|
|
1220
|
-
const z = (0, test_translator_1.getField)(x.pipeline[0], "z");
|
|
1221
|
-
expect(z.location).toMatchObject(source.locations[0]);
|
|
1222
|
-
});
|
|
1223
|
-
test("location of field inherited from table", () => {
|
|
1224
|
-
const source = (0, test_translator_1.markSource) `explore: na is ${"table('aTable')"}`;
|
|
1225
|
-
const m = new BetaModel(source.code);
|
|
1226
|
-
expect(m).toTranslate();
|
|
1227
|
-
const na = (0, test_translator_1.getExplore)(m.modelDef, "na");
|
|
1228
|
-
const abool = (0, test_translator_1.getField)(na, "abool");
|
|
1229
|
-
expect(abool.location).toMatchObject(source.locations[0]);
|
|
1230
|
-
});
|
|
1231
|
-
test("location of field inherited from sql block", () => {
|
|
1232
|
-
const source = (0, test_translator_1.markSource) `
|
|
1233
|
-
sql: ${"s is || SELECT 1 as one ;;"}
|
|
1234
|
-
|
|
1235
|
-
explore: na is from_sql(s)
|
|
1236
|
-
`;
|
|
1237
|
-
const m = new BetaModel(source.code);
|
|
1238
|
-
const result = m.translate();
|
|
1239
|
-
const sqlBlock = (result.sqlStructs || [])[0];
|
|
1240
|
-
m.update({
|
|
1241
|
-
sqlStructs: {
|
|
1242
|
-
[sqlBlock.name]: getSelectOneStruct(sqlBlock),
|
|
1243
|
-
},
|
|
1244
|
-
});
|
|
1245
|
-
expect(m).toTranslate();
|
|
1246
|
-
const na = (0, test_translator_1.getExplore)(m.modelDef, "na");
|
|
1247
|
-
const one = (0, test_translator_1.getField)(na, "one");
|
|
1248
|
-
expect(one.location).toMatchObject(source.locations[0]);
|
|
1249
|
-
});
|
|
1250
|
-
test("location of fields inherited from a query", () => {
|
|
1251
|
-
const source = (0, test_translator_1.markSource) `
|
|
1252
|
-
explore: na is from(
|
|
1253
|
-
${"table('aTable')"} -> {
|
|
1254
|
-
group_by:
|
|
1255
|
-
abool
|
|
1256
|
-
${"y is 1"}
|
|
1257
|
-
}
|
|
1258
|
-
)
|
|
1259
|
-
`;
|
|
1260
|
-
const m = new BetaModel(source.code);
|
|
1261
|
-
expect(m).toTranslate();
|
|
1262
|
-
const na = (0, test_translator_1.getExplore)(m.modelDef, "na");
|
|
1263
|
-
const abool = (0, test_translator_1.getField)(na, "abool");
|
|
1264
|
-
expect(abool.location).toMatchObject(source.locations[0]);
|
|
1265
|
-
const y = (0, test_translator_1.getField)(na, "y");
|
|
1266
|
-
expect(y.location).toMatchObject(source.locations[1]);
|
|
1267
|
-
});
|
|
1268
|
-
test("location of named query", () => {
|
|
1269
|
-
const source = (0, test_translator_1.markSource) `query: ${"q is a -> { project: * }"}`;
|
|
1270
|
-
const m = new BetaModel(source.code);
|
|
1271
|
-
expect(m).toTranslate();
|
|
1272
|
-
const q = (0, test_translator_1.getExplore)(m.modelDef, "q");
|
|
1273
|
-
expect(q.location).toMatchObject(source.locations[0]);
|
|
1274
|
-
});
|
|
1275
|
-
test("location of field in named query", () => {
|
|
1276
|
-
const source = (0, test_translator_1.markSource) `query: q is a -> { group_by: ${"b is 1"} }`;
|
|
1277
|
-
const m = new BetaModel(source.code);
|
|
1278
|
-
expect(m).toTranslate();
|
|
1279
|
-
const q = (0, test_translator_1.getModelQuery)(m.modelDef, "q");
|
|
1280
|
-
const a = (0, test_translator_1.getField)(q.pipeline[0], "b");
|
|
1281
|
-
expect(a.location).toMatchObject(source.locations[0]);
|
|
1282
|
-
});
|
|
1283
|
-
test("location of named SQL block", () => {
|
|
1284
|
-
const source = (0, test_translator_1.markSource) `sql: ${"s is || SELECT 1 as one ;;"}`;
|
|
1285
|
-
const m = new BetaModel(source.code);
|
|
1286
|
-
expect(m).toTranslate();
|
|
1287
|
-
const s = m.sqlBlocks[0];
|
|
1288
|
-
expect(s.location).toMatchObject(source.locations[0]);
|
|
1289
|
-
});
|
|
1290
|
-
test("location of renamed field", () => {
|
|
1291
|
-
const source = (0, test_translator_1.markSource) `
|
|
1292
|
-
explore: na is a {
|
|
1293
|
-
rename: ${"bbool is abool"}
|
|
1294
|
-
}
|
|
1295
|
-
`;
|
|
1296
|
-
const m = new BetaModel(source.code);
|
|
1297
|
-
expect(m).toTranslate();
|
|
1298
|
-
const na = (0, test_translator_1.getExplore)(m.modelDef, "na");
|
|
1299
|
-
const bbool = (0, test_translator_1.getField)(na, "bbool");
|
|
1300
|
-
expect(bbool.location).toMatchObject(source.locations[0]);
|
|
1301
|
-
});
|
|
1302
|
-
test("location of join on", () => {
|
|
1303
|
-
const source = (0, test_translator_1.markSource) `
|
|
1304
|
-
explore: na is a {
|
|
1305
|
-
join_one: ${"x is a { primary_key: abool } on abool"}
|
|
1306
|
-
}
|
|
1307
|
-
`;
|
|
1308
|
-
const m = new BetaModel(source.code);
|
|
1309
|
-
expect(m).toTranslate();
|
|
1310
|
-
const na = (0, test_translator_1.getExplore)(m.modelDef, "na");
|
|
1311
|
-
const x = (0, test_translator_1.getField)(na, "x");
|
|
1312
|
-
expect(x.location).toMatchObject(source.locations[0]);
|
|
1313
|
-
});
|
|
1314
|
-
test("location of join with", () => {
|
|
1315
|
-
const source = (0, test_translator_1.markSource) `
|
|
1316
|
-
explore: na is a {
|
|
1317
|
-
join_one: ${"x is a { primary_key: astr } with astr"}
|
|
1318
|
-
}
|
|
1319
|
-
`;
|
|
1320
|
-
const m = new BetaModel(source.code);
|
|
1321
|
-
expect(m).toTranslate();
|
|
1322
|
-
const na = (0, test_translator_1.getExplore)(m.modelDef, "na");
|
|
1323
|
-
const x = (0, test_translator_1.getField)(na, "x");
|
|
1324
|
-
expect(x.location).toMatchObject(source.locations[0]);
|
|
1325
|
-
});
|
|
1326
|
-
test("location of field in join", () => {
|
|
1327
|
-
const source = (0, test_translator_1.markSource) `
|
|
1328
|
-
explore: na is a {
|
|
1329
|
-
join_one: x is a {
|
|
1330
|
-
primary_key: abool
|
|
1331
|
-
dimension: ${"y is 1"}
|
|
1332
|
-
} on abool
|
|
1333
|
-
}
|
|
1334
|
-
`;
|
|
1335
|
-
const m = new BetaModel(source.code);
|
|
1336
|
-
expect(m).toTranslate();
|
|
1337
|
-
const na = (0, test_translator_1.getExplore)(m.modelDef, "na");
|
|
1338
|
-
const x = (0, test_translator_1.getJoinField)(na, "x");
|
|
1339
|
-
const y = (0, test_translator_1.getField)(x, "y");
|
|
1340
|
-
expect(y.location).toMatchObject(source.locations[0]);
|
|
1341
|
-
});
|
|
1342
|
-
test("multi line sql block token span is correct", () => {
|
|
1343
|
-
var _a;
|
|
1344
|
-
// There is exactly one token in this file ..
|
|
1345
|
-
const sqlSource = "|| // line 0\n//line 1\n// line 2;;";
|
|
1346
|
-
const m = new BetaModel(sqlSource);
|
|
1347
|
-
expect(m).not.toCompile();
|
|
1348
|
-
const errList = m.errors().errors;
|
|
1349
|
-
expect((_a = errList[0].at) === null || _a === void 0 ? void 0 : _a.range.end).toEqual({ line: 2, character: 11 });
|
|
1350
|
-
});
|
|
1351
|
-
test("undefined query location", badModel((0, test_translator_1.markSource) `query: ${"-> xyz"}`, "Reference to undefined query 'xyz'"));
|
|
1352
|
-
test("undefined field reference", badModel((0, test_translator_1.markSource) `query: a -> { group_by: ${"xyz"} }`, "'xyz' is not defined"));
|
|
1353
|
-
test("bad query", badModel((0, test_translator_1.markSource) `query: a -> { group_by: astr; ${"project: *"} }`, "project: not legal in grouping query"));
|
|
1354
|
-
test.skip("undefined field reference in top", badModel((0, test_translator_1.markSource) `query: a -> { group_by: one is 1; top: 1 by ${"xyz"} }`, "'xyz' is not defined"));
|
|
1355
|
-
test.skip("undefined field reference in order_by", badModel((0, test_translator_1.markSource) `query: a -> { group_by: one is 1; order_by: ${"xyz"} }`, "'xyz' is not defined"));
|
|
1356
|
-
});
|
|
1357
|
-
describe("source references", () => {
|
|
1358
|
-
test("reference to explore", () => {
|
|
1359
|
-
const source = (0, test_translator_1.markSource) `
|
|
1360
|
-
explore: ${"na is a"}
|
|
1361
|
-
query: ${"na"} -> { project: * }
|
|
1362
|
-
`;
|
|
1363
|
-
const m = new BetaModel(source.code);
|
|
1364
|
-
expect(m).toTranslate();
|
|
1365
|
-
expect(m.referenceAt(pos(source.locations[1]))).toMatchObject({
|
|
1366
|
-
location: source.locations[1],
|
|
1367
|
-
type: "exploreReference",
|
|
1368
|
-
text: "na",
|
|
1369
|
-
definition: {
|
|
1370
|
-
location: source.locations[0],
|
|
1371
|
-
},
|
|
1372
|
-
});
|
|
1373
|
-
});
|
|
1374
|
-
test("reference to query in query", () => {
|
|
1375
|
-
const source = (0, test_translator_1.markSource) `
|
|
1376
|
-
explore: t is a {
|
|
1377
|
-
query: ${"q is { project: * }"}
|
|
1378
|
-
}
|
|
1379
|
-
query: t -> ${"q"}
|
|
1380
|
-
`;
|
|
1381
|
-
const m = new BetaModel(source.code);
|
|
1382
|
-
expect(m).toTranslate();
|
|
1383
|
-
expect(m.referenceAt(pos(source.locations[1]))).toMatchObject({
|
|
1384
|
-
location: source.locations[1],
|
|
1385
|
-
type: "fieldReference",
|
|
1386
|
-
text: "q",
|
|
1387
|
-
definition: {
|
|
1388
|
-
location: source.locations[0],
|
|
1389
|
-
},
|
|
1390
|
-
});
|
|
1391
|
-
});
|
|
1392
|
-
test("reference to query in query (version 2)", () => {
|
|
1393
|
-
const source = (0, test_translator_1.markSource) `
|
|
1394
|
-
explore: na is a { query: ${"x is { group_by: y is 1 }"} }
|
|
1395
|
-
query: na -> ${"x"}
|
|
1396
|
-
`;
|
|
1397
|
-
const m = new BetaModel(source.code);
|
|
1398
|
-
expect(m).toTranslate();
|
|
1399
|
-
expect(m.referenceAt(pos(source.locations[1]))).toMatchObject({
|
|
1400
|
-
location: source.locations[1],
|
|
1401
|
-
type: "fieldReference",
|
|
1402
|
-
text: "x",
|
|
1403
|
-
definition: {
|
|
1404
|
-
location: source.locations[0],
|
|
1405
|
-
},
|
|
1406
|
-
});
|
|
1407
|
-
});
|
|
1408
|
-
test("reference to sql block", () => {
|
|
1409
|
-
const source = (0, test_translator_1.markSource) `
|
|
1410
|
-
sql: ${"s is || SELECT 1 as one ;;"}
|
|
1411
|
-
explore: na is from_sql(${"s"})
|
|
1412
|
-
`;
|
|
1413
|
-
const m = new BetaModel(source.code);
|
|
1414
|
-
const result = m.translate();
|
|
1415
|
-
const sqlBlock = (result.sqlStructs || [])[0];
|
|
1416
|
-
m.update({
|
|
1417
|
-
sqlStructs: {
|
|
1418
|
-
[sqlBlock.name]: getSelectOneStruct(sqlBlock),
|
|
1419
|
-
},
|
|
1420
|
-
});
|
|
1421
|
-
expect(m).toTranslate();
|
|
1422
|
-
expect(m.referenceAt(pos(source.locations[1]))).toMatchObject({
|
|
1423
|
-
location: source.locations[1],
|
|
1424
|
-
type: "sqlBlockReference",
|
|
1425
|
-
text: "s",
|
|
1426
|
-
definition: {
|
|
1427
|
-
location: source.locations[0],
|
|
1428
|
-
},
|
|
1429
|
-
});
|
|
1430
|
-
});
|
|
1431
|
-
test("reference to query in from", () => {
|
|
1432
|
-
const source = (0, test_translator_1.markSource) `
|
|
1433
|
-
query: ${"q is a -> { project: * }"}
|
|
1434
|
-
explore: na is from(-> ${"q"})
|
|
1435
|
-
`;
|
|
1436
|
-
const m = new BetaModel(source.code);
|
|
1437
|
-
expect(m).toTranslate();
|
|
1438
|
-
expect(m.referenceAt(pos(source.locations[1]))).toMatchObject({
|
|
1439
|
-
location: source.locations[1],
|
|
1440
|
-
type: "queryReference",
|
|
1441
|
-
text: "q",
|
|
1442
|
-
definition: {
|
|
1443
|
-
location: source.locations[0],
|
|
1444
|
-
},
|
|
1445
|
-
});
|
|
1446
|
-
});
|
|
1447
|
-
test("reference to query in query head", () => {
|
|
1448
|
-
const source = (0, test_translator_1.markSource) `
|
|
1449
|
-
query: ${"q is a -> { project: * }"}
|
|
1450
|
-
query: q2 is -> ${"q"} -> { project: * }
|
|
1451
|
-
`;
|
|
1452
|
-
const m = new BetaModel(source.code);
|
|
1453
|
-
expect(m).toTranslate();
|
|
1454
|
-
expect(m.referenceAt(pos(source.locations[1]))).toMatchObject({
|
|
1455
|
-
location: source.locations[1],
|
|
1456
|
-
type: "queryReference",
|
|
1457
|
-
text: "q",
|
|
1458
|
-
definition: {
|
|
1459
|
-
location: source.locations[0],
|
|
1460
|
-
},
|
|
1461
|
-
});
|
|
1462
|
-
});
|
|
1463
|
-
test("reference to query in refined query", () => {
|
|
1464
|
-
const source = (0, test_translator_1.markSource) `
|
|
1465
|
-
query: ${"q is a -> { project: * }"}
|
|
1466
|
-
query: q2 is -> ${"q"} { limit: 10 }
|
|
1467
|
-
`;
|
|
1468
|
-
const m = new BetaModel(source.code);
|
|
1469
|
-
expect(m).toTranslate();
|
|
1470
|
-
expect(m.referenceAt(pos(source.locations[1]))).toMatchObject({
|
|
1471
|
-
location: source.locations[1],
|
|
1472
|
-
type: "queryReference",
|
|
1473
|
-
text: "q",
|
|
1474
|
-
definition: {
|
|
1475
|
-
location: source.locations[0],
|
|
1476
|
-
},
|
|
1477
|
-
});
|
|
1478
|
-
});
|
|
1479
|
-
test("reference to field in expression", () => {
|
|
1480
|
-
const source = (0, test_translator_1.markSource) `
|
|
1481
|
-
explore: na is ${"table('aTable')"}
|
|
1482
|
-
query: na -> { project: bbool is not ${"abool"} }
|
|
1483
|
-
`;
|
|
1484
|
-
const m = new BetaModel(source.code);
|
|
1485
|
-
expect(m).toTranslate();
|
|
1486
|
-
expect(m.referenceAt(pos(source.locations[1]))).toMatchObject({
|
|
1487
|
-
location: source.locations[1],
|
|
1488
|
-
type: "fieldReference",
|
|
1489
|
-
text: "abool",
|
|
1490
|
-
definition: {
|
|
1491
|
-
location: source.locations[0],
|
|
1492
|
-
},
|
|
1493
|
-
});
|
|
1494
|
-
});
|
|
1495
|
-
test("reference to quoted field in expression", () => {
|
|
1496
|
-
const source = (0, test_translator_1.markSource) `
|
|
1497
|
-
explore: na is a {
|
|
1498
|
-
dimension: ${"`name` is 'name'"}
|
|
1499
|
-
}
|
|
1500
|
-
query: na -> { project: ${"`name`"} }
|
|
1501
|
-
`;
|
|
1502
|
-
const m = new BetaModel(source.code);
|
|
1503
|
-
expect(m).toTranslate();
|
|
1504
|
-
expect(m.referenceAt(pos(source.locations[1]))).toMatchObject({
|
|
1505
|
-
location: source.locations[1],
|
|
1506
|
-
type: "fieldReference",
|
|
1507
|
-
text: "name",
|
|
1508
|
-
definition: {
|
|
1509
|
-
location: source.locations[0],
|
|
1510
|
-
},
|
|
1511
|
-
});
|
|
1512
|
-
});
|
|
1513
|
-
test("reference to joined field in expression", () => {
|
|
1514
|
-
const source = (0, test_translator_1.markSource) `
|
|
1515
|
-
explore: na is a {
|
|
1516
|
-
join_one: self is ${"table('aTable')"}
|
|
1517
|
-
on astr = self.astr
|
|
1518
|
-
}
|
|
1519
|
-
query: na -> { project: bstr is self.${"astr"} }
|
|
1520
|
-
`;
|
|
1521
|
-
const m = new BetaModel(source.code);
|
|
1522
|
-
expect(m).toTranslate();
|
|
1523
|
-
expect(m.referenceAt(pos(source.locations[1]))).toMatchObject({
|
|
1524
|
-
location: source.locations[1],
|
|
1525
|
-
type: "fieldReference",
|
|
1526
|
-
text: "astr",
|
|
1527
|
-
definition: {
|
|
1528
|
-
location: source.locations[0],
|
|
1529
|
-
},
|
|
1530
|
-
});
|
|
1531
|
-
});
|
|
1532
|
-
test("reference to joined join in expression", () => {
|
|
1533
|
-
const source = (0, test_translator_1.markSource) `
|
|
1534
|
-
explore: na is a {
|
|
1535
|
-
join_one: ${"self is a on astr = self.astr"}
|
|
1536
|
-
}
|
|
1537
|
-
query: na -> { project: bstr is ${"self"}.astr }
|
|
1538
|
-
`;
|
|
1539
|
-
const m = new BetaModel(source.code);
|
|
1540
|
-
expect(m).toTranslate();
|
|
1541
|
-
expect(m.referenceAt(pos(source.locations[1]))).toMatchObject({
|
|
1542
|
-
location: source.locations[1],
|
|
1543
|
-
type: "joinReference",
|
|
1544
|
-
text: "self",
|
|
1545
|
-
definition: {
|
|
1546
|
-
location: source.locations[0],
|
|
1547
|
-
},
|
|
1548
|
-
});
|
|
1549
|
-
});
|
|
1550
|
-
test("reference to field not in expression (group by)", () => {
|
|
1551
|
-
const source = (0, test_translator_1.markSource) `
|
|
1552
|
-
query: ${"table('aTable')"} -> { group_by: ${"abool"} }
|
|
1553
|
-
`;
|
|
1554
|
-
const m = new BetaModel(source.code);
|
|
1555
|
-
expect(m).toTranslate();
|
|
1556
|
-
expect(m.referenceAt(pos(source.locations[1]))).toMatchObject({
|
|
1557
|
-
location: source.locations[1],
|
|
1558
|
-
type: "fieldReference",
|
|
1559
|
-
text: "abool",
|
|
1560
|
-
definition: {
|
|
1561
|
-
location: source.locations[0],
|
|
1562
|
-
},
|
|
1563
|
-
});
|
|
1564
|
-
});
|
|
1565
|
-
test("reference to field not in expression (project)", () => {
|
|
1566
|
-
const source = (0, test_translator_1.markSource) `
|
|
1567
|
-
explore: na is ${"table('aTable')"}
|
|
1568
|
-
query: na -> { project: ${"abool"} }
|
|
1569
|
-
`;
|
|
1570
|
-
const m = new BetaModel(source.code);
|
|
1571
|
-
expect(m).toTranslate();
|
|
1572
|
-
expect(m.referenceAt(pos(source.locations[1]))).toMatchObject({
|
|
1573
|
-
location: source.locations[1],
|
|
1574
|
-
type: "fieldReference",
|
|
1575
|
-
text: "abool",
|
|
1576
|
-
definition: {
|
|
1577
|
-
location: source.locations[0],
|
|
1578
|
-
},
|
|
1579
|
-
});
|
|
1580
|
-
});
|
|
1581
|
-
test.skip("reference to field in order by", () => {
|
|
1582
|
-
const source = (0, test_translator_1.markSource) `
|
|
1583
|
-
query: ${"table('aTable')"} -> {
|
|
1584
|
-
group_by: abool
|
|
1585
|
-
order_by: ${"abool"}
|
|
1586
|
-
}
|
|
1587
|
-
`;
|
|
1588
|
-
const m = new BetaModel(source.code);
|
|
1589
|
-
expect(m).toTranslate();
|
|
1590
|
-
expect(m.referenceAt(pos(source.locations[1]))).toMatchObject({
|
|
1591
|
-
location: source.locations[1],
|
|
1592
|
-
type: "fieldReference",
|
|
1593
|
-
text: "abool",
|
|
1594
|
-
definition: {
|
|
1595
|
-
location: source.locations[0],
|
|
1596
|
-
},
|
|
1597
|
-
});
|
|
1598
|
-
});
|
|
1599
|
-
test.skip("reference to field in order by (output space)", () => {
|
|
1600
|
-
const source = (0, test_translator_1.markSource) `
|
|
1601
|
-
query: a -> {
|
|
1602
|
-
group_by: ${"one is 1"}
|
|
1603
|
-
order_by: ${"one"}
|
|
1604
|
-
}
|
|
1605
|
-
`;
|
|
1606
|
-
const m = new BetaModel(source.code);
|
|
1607
|
-
expect(m).toTranslate();
|
|
1608
|
-
expect(m.referenceAt(pos(source.locations[1]))).toMatchObject({
|
|
1609
|
-
location: source.locations[1],
|
|
1610
|
-
type: "fieldReference",
|
|
1611
|
-
text: "abool",
|
|
1612
|
-
definition: {
|
|
1613
|
-
location: source.locations[0],
|
|
1614
|
-
},
|
|
1615
|
-
});
|
|
1616
|
-
});
|
|
1617
|
-
test("reference to field in aggregate", () => {
|
|
1618
|
-
const source = (0, test_translator_1.markSource) `
|
|
1619
|
-
query: a { measure: ${"c is count()"} } -> {
|
|
1620
|
-
group_by: abool
|
|
1621
|
-
aggregate: ${"c"}
|
|
1622
|
-
}
|
|
1623
|
-
`;
|
|
1624
|
-
const m = new BetaModel(source.code);
|
|
1625
|
-
expect(m).toTranslate();
|
|
1626
|
-
expect(m.referenceAt(pos(source.locations[1]))).toMatchObject({
|
|
1627
|
-
location: source.locations[1],
|
|
1628
|
-
type: "fieldReference",
|
|
1629
|
-
text: "c",
|
|
1630
|
-
definition: {
|
|
1631
|
-
location: source.locations[0],
|
|
1632
|
-
},
|
|
1633
|
-
});
|
|
1634
|
-
});
|
|
1635
|
-
test("reference to field in measure", () => {
|
|
1636
|
-
const source = (0, test_translator_1.markSource) `
|
|
1637
|
-
explore: e is a {
|
|
1638
|
-
measure: ${"c is count()"}
|
|
1639
|
-
measure: c2 is ${"c"}
|
|
1640
|
-
}
|
|
1641
|
-
`;
|
|
1642
|
-
const m = new BetaModel(source.code);
|
|
1643
|
-
expect(m).toTranslate();
|
|
1644
|
-
expect(m.referenceAt(pos(source.locations[1]))).toMatchObject({
|
|
1645
|
-
location: source.locations[1],
|
|
1646
|
-
type: "fieldReference",
|
|
1647
|
-
text: "c",
|
|
1648
|
-
definition: {
|
|
1649
|
-
location: source.locations[0],
|
|
1650
|
-
},
|
|
1651
|
-
});
|
|
1652
|
-
});
|
|
1653
|
-
test.skip("reference to field in top", () => {
|
|
1654
|
-
const source = (0, test_translator_1.markSource) `
|
|
1655
|
-
query: ${"table('aTable')"} -> {
|
|
1656
|
-
group_by: abool
|
|
1657
|
-
top: 10 by ${"abool"}
|
|
1658
|
-
}
|
|
1659
|
-
`;
|
|
1660
|
-
const m = new BetaModel(source.code);
|
|
1661
|
-
expect(m).toTranslate();
|
|
1662
|
-
expect(m.referenceAt(pos(source.locations[1]))).toMatchObject({
|
|
1663
|
-
location: source.locations[1],
|
|
1664
|
-
type: "fieldReference",
|
|
1665
|
-
text: "abool",
|
|
1666
|
-
definition: {
|
|
1667
|
-
location: source.locations[0],
|
|
1668
|
-
},
|
|
1669
|
-
});
|
|
1670
|
-
});
|
|
1671
|
-
test.skip("reference to field in top (output space)", () => {
|
|
1672
|
-
const source = (0, test_translator_1.markSource) `
|
|
1673
|
-
query: a -> {
|
|
1674
|
-
group_by: ${"one is 1"}
|
|
1675
|
-
top: 10 by ${"one"}
|
|
1676
|
-
}
|
|
1677
|
-
`;
|
|
1678
|
-
const m = new BetaModel(source.code);
|
|
1679
|
-
expect(m).toTranslate();
|
|
1680
|
-
expect(m.referenceAt(pos(source.locations[1]))).toMatchObject({
|
|
1681
|
-
location: source.locations[1],
|
|
1682
|
-
type: "fieldReference",
|
|
1683
|
-
text: "abool",
|
|
1684
|
-
definition: {
|
|
1685
|
-
location: source.locations[0],
|
|
1686
|
-
},
|
|
1687
|
-
});
|
|
1688
|
-
});
|
|
1689
|
-
test("reference to field in filter", () => {
|
|
1690
|
-
const source = (0, test_translator_1.markSource) `
|
|
1691
|
-
query: ${"table('aTable')"} -> {
|
|
1692
|
-
group_by: abool
|
|
1693
|
-
where: ${"abool"}
|
|
1694
|
-
}
|
|
1695
|
-
`;
|
|
1696
|
-
const m = new BetaModel(source.code);
|
|
1697
|
-
expect(m).toTranslate();
|
|
1698
|
-
expect(m.referenceAt(pos(source.locations[1]))).toMatchObject({
|
|
1699
|
-
location: source.locations[1],
|
|
1700
|
-
type: "fieldReference",
|
|
1701
|
-
text: "abool",
|
|
1702
|
-
definition: {
|
|
1703
|
-
location: source.locations[0],
|
|
1704
|
-
},
|
|
1705
|
-
});
|
|
1706
|
-
});
|
|
1707
|
-
test("reference to field in aggregate source", () => {
|
|
1708
|
-
const source = (0, test_translator_1.markSource) `
|
|
1709
|
-
explore: na is ${"table('aTable')"}
|
|
1710
|
-
query: na -> { aggregate: ai_sum is ${"ai"}.sum() }
|
|
1711
|
-
`;
|
|
1712
|
-
const m = new BetaModel(source.code);
|
|
1713
|
-
expect(m).toTranslate();
|
|
1714
|
-
expect(m.referenceAt(pos(source.locations[1]))).toMatchObject({
|
|
1715
|
-
location: source.locations[1],
|
|
1716
|
-
type: "fieldReference",
|
|
1717
|
-
text: "ai",
|
|
1718
|
-
definition: {
|
|
1719
|
-
location: source.locations[0],
|
|
1720
|
-
},
|
|
1721
|
-
});
|
|
1722
|
-
});
|
|
1723
|
-
function pos(location) {
|
|
1724
|
-
return location.range.start;
|
|
1725
|
-
}
|
|
1726
|
-
test("reference to join in aggregate source", () => {
|
|
1727
|
-
const source = (0, test_translator_1.markSource) `
|
|
1728
|
-
explore: na is a {
|
|
1729
|
-
join_one: ${"self is a on astr = self.astr"}
|
|
1730
|
-
}
|
|
1731
|
-
query: na -> { aggregate: ai_sum is ${"self"}.sum(self.ai) }
|
|
1732
|
-
`;
|
|
1733
|
-
const m = new BetaModel(source.code);
|
|
1734
|
-
expect(m).toTranslate();
|
|
1735
|
-
expect(m.referenceAt(pos(source.locations[1]))).toMatchObject({
|
|
1736
|
-
location: source.locations[1],
|
|
1737
|
-
type: "joinReference",
|
|
1738
|
-
text: "self",
|
|
1739
|
-
definition: {
|
|
1740
|
-
location: source.locations[0],
|
|
1741
|
-
},
|
|
1742
|
-
});
|
|
1743
|
-
});
|
|
1744
|
-
test("reference to join in aggregate in expr", () => {
|
|
1745
|
-
const source = (0, test_translator_1.markSource) `
|
|
1746
|
-
explore: na is a {
|
|
1747
|
-
join_one: ${"self is a on astr = self.astr"}
|
|
1748
|
-
}
|
|
1749
|
-
query: na -> { aggregate: ai_sum is self.sum(${"self"}.ai) }
|
|
1750
|
-
`;
|
|
1751
|
-
const m = new BetaModel(source.code);
|
|
1752
|
-
expect(m).toTranslate();
|
|
1753
|
-
expect(m.referenceAt(pos(source.locations[1]))).toMatchObject({
|
|
1754
|
-
location: source.locations[1],
|
|
1755
|
-
type: "joinReference",
|
|
1756
|
-
text: "self",
|
|
1757
|
-
definition: {
|
|
1758
|
-
location: source.locations[0],
|
|
1759
|
-
},
|
|
1760
|
-
});
|
|
1761
|
-
});
|
|
1762
|
-
test("reference to explore in join", () => {
|
|
1763
|
-
const source = (0, test_translator_1.markSource) `
|
|
1764
|
-
explore: ${"exp1 is a"}
|
|
1765
|
-
explore: exp2 is a {
|
|
1766
|
-
join_one: ${"exp1"} on astr = exp1.astr
|
|
1767
|
-
}
|
|
1768
|
-
`;
|
|
1769
|
-
const m = new BetaModel(source.code);
|
|
1770
|
-
expect(m).toTranslate();
|
|
1771
|
-
expect(m.referenceAt(pos(source.locations[1]))).toMatchObject({
|
|
1772
|
-
location: source.locations[1],
|
|
1773
|
-
type: "exploreReference",
|
|
1774
|
-
text: "exp1",
|
|
1775
|
-
definition: {
|
|
1776
|
-
location: source.locations[0],
|
|
1777
|
-
},
|
|
1778
|
-
});
|
|
1779
|
-
});
|
|
1780
|
-
test("reference to field in aggregate (in expr)", () => {
|
|
1781
|
-
const source = (0, test_translator_1.markSource) `
|
|
1782
|
-
explore: na is ${"table('aTable')"}
|
|
1783
|
-
query: na -> { aggregate: ai_sum is sum(${"ai"}) }
|
|
1784
|
-
`;
|
|
1785
|
-
const m = new BetaModel(source.code);
|
|
1786
|
-
expect(m).toTranslate();
|
|
1787
|
-
expect(m.referenceAt(pos(source.locations[1]))).toMatchObject({
|
|
1788
|
-
location: source.locations[1],
|
|
1789
|
-
type: "fieldReference",
|
|
1790
|
-
text: "ai",
|
|
1791
|
-
definition: {
|
|
1792
|
-
location: source.locations[0],
|
|
1793
|
-
},
|
|
1794
|
-
});
|
|
1795
|
-
});
|
|
1796
|
-
test("reference to field in rename", () => {
|
|
1797
|
-
const source = (0, test_translator_1.markSource) `
|
|
1798
|
-
explore: na is ${"table('aTable')"} {
|
|
1799
|
-
rename: bbool is ${"abool"}
|
|
1800
|
-
}
|
|
1801
|
-
`;
|
|
1802
|
-
const m = new BetaModel(source.code);
|
|
1803
|
-
expect(m).toTranslate();
|
|
1804
|
-
expect(m.referenceAt(pos(source.locations[1]))).toMatchObject({
|
|
1805
|
-
location: source.locations[1],
|
|
1806
|
-
type: "fieldReference",
|
|
1807
|
-
text: "abool",
|
|
1808
|
-
definition: {
|
|
1809
|
-
location: source.locations[0],
|
|
1810
|
-
},
|
|
1811
|
-
});
|
|
1812
|
-
});
|
|
1813
|
-
test("reference to field in join with", () => {
|
|
1814
|
-
const source = (0, test_translator_1.markSource) `
|
|
1815
|
-
explore: exp1 is a { primary_key: astr }
|
|
1816
|
-
explore: exp2 is ${"table('aTable')"} {
|
|
1817
|
-
join_one: exp1 with ${"astr"}
|
|
1818
|
-
}
|
|
1819
|
-
`;
|
|
1820
|
-
const m = new BetaModel(source.code);
|
|
1821
|
-
expect(m).toTranslate();
|
|
1822
|
-
expect(m.referenceAt(pos(source.locations[1]))).toMatchObject({
|
|
1823
|
-
location: source.locations[1],
|
|
1824
|
-
type: "fieldReference",
|
|
1825
|
-
text: "astr",
|
|
1826
|
-
definition: {
|
|
1827
|
-
location: source.locations[0],
|
|
1828
|
-
},
|
|
1829
|
-
});
|
|
1830
|
-
});
|
|
1831
|
-
});
|
|
1832
|
-
describe("translation need error locations", () => {
|
|
1833
|
-
test("import error location", () => {
|
|
1834
|
-
const source = (0, test_translator_1.markSource) `import ${'"badfile"'}`;
|
|
1835
|
-
const m = new BetaModel(source.code);
|
|
1836
|
-
const result = m.translate();
|
|
1837
|
-
m.update({
|
|
1838
|
-
errors: { urls: { [(result.urls || [])[0]]: "Bad file!" } },
|
|
1839
|
-
});
|
|
1840
|
-
expect(m).not.toCompile();
|
|
1841
|
-
const errList = m.errors().errors;
|
|
1842
|
-
expect(errList[0].at).toEqual(source.locations[0]);
|
|
1843
|
-
return undefined;
|
|
1844
|
-
});
|
|
1845
|
-
test("sql struct error location", () => {
|
|
1846
|
-
const source = (0, test_translator_1.markSource) `
|
|
1847
|
-
sql: bad_sql is || BAD SQL ;;
|
|
1848
|
-
query: ${"from_sql(bad_sql)"} -> { project: * }
|
|
1849
|
-
`;
|
|
1850
|
-
const m = new BetaModel(source.code);
|
|
1851
|
-
const result = m.translate();
|
|
1852
|
-
m.update({
|
|
1853
|
-
errors: {
|
|
1854
|
-
sqlStructs: { [(result.sqlStructs || [])[0].name]: "Bad SQL!" },
|
|
1855
|
-
},
|
|
1856
|
-
});
|
|
1857
|
-
expect(m).not.toCompile();
|
|
1858
|
-
const errList = m.errors().errors;
|
|
1859
|
-
expect(errList[0].at).toEqual(source.locations[0]);
|
|
1860
|
-
return undefined;
|
|
1861
|
-
});
|
|
1862
|
-
test("table struct error location", () => {
|
|
1863
|
-
const source = (0, test_translator_1.markSource) `
|
|
1864
|
-
explore: bad_explore is ${"table('malloy-data.bad.table')"}
|
|
1865
|
-
`;
|
|
1866
|
-
const m = new BetaModel(source.code);
|
|
1867
|
-
const result = m.translate();
|
|
1868
|
-
m.update({
|
|
1869
|
-
errors: {
|
|
1870
|
-
tables: { [(result.tables || [])[0]]: "Bad table!" },
|
|
1871
|
-
},
|
|
1872
|
-
});
|
|
1873
|
-
expect(m).not.toCompile();
|
|
1874
|
-
const errList = m.errors().errors;
|
|
1875
|
-
expect(errList[0].at).toEqual(source.locations[0]);
|
|
1876
|
-
return undefined;
|
|
1877
|
-
});
|
|
1878
|
-
});
|
|
1879
|
-
describe("pipeline comprehension", () => {
|
|
1880
|
-
test("second query gets namespace from first", modelOK(`
|
|
1881
|
-
explore: aq is a {
|
|
1882
|
-
query: t1 is {
|
|
1883
|
-
group_by: t1int is ai, t1str is astr
|
|
1884
|
-
} -> {
|
|
1885
|
-
project: t1str, t1int
|
|
1886
|
-
}
|
|
1887
|
-
}
|
|
1888
|
-
`));
|
|
1889
|
-
test("second query doesn't have access to original fields", badModel((0, test_translator_1.markSource) `
|
|
1890
|
-
explore: aq is a {
|
|
1891
|
-
query: t1 is {
|
|
1892
|
-
group_by: t1int is ai, t1str is astr
|
|
1893
|
-
} -> {
|
|
1894
|
-
project: ${"ai"}
|
|
1895
|
-
}
|
|
1896
|
-
}
|
|
1897
|
-
`, "'ai' is not defined"));
|
|
1898
|
-
test("new query can append ops to existing query", modelOK(`
|
|
1899
|
-
explore: aq is a {
|
|
1900
|
-
query: t0 is {
|
|
1901
|
-
group_by: t1int is ai, t1str is astr
|
|
1902
|
-
}
|
|
1903
|
-
query: t1 is t0 -> {
|
|
1904
|
-
project: t1str, t1int
|
|
1905
|
-
}
|
|
1906
|
-
}
|
|
1907
|
-
`));
|
|
1908
|
-
test("new query can refine and append to exisiting query", modelOK(`
|
|
1909
|
-
explore: aq is table('aTable') {
|
|
1910
|
-
query: by_region is { group_by: astr }
|
|
1911
|
-
query: by_region2 is by_region {
|
|
1912
|
-
nest: dateNest is { group_by: ad }
|
|
1913
|
-
} -> {
|
|
1914
|
-
project: astr, dateNest.ad
|
|
1915
|
-
}
|
|
1916
|
-
}
|
|
1917
|
-
`));
|
|
1918
|
-
test("reference to a query can include a refinement", modelOK(`
|
|
1919
|
-
query: ab -> {
|
|
1920
|
-
group_by: ai
|
|
1921
|
-
nest: aturtle { limit: 1 }
|
|
1922
|
-
}
|
|
1923
|
-
`));
|
|
1924
|
-
test("Querying an explore based on a query", modelOK(`
|
|
1925
|
-
query: q is a -> { group_by: astr; aggregate: strsum is ai.sum() }
|
|
1926
|
-
explore: aq is a {
|
|
1927
|
-
join_one: aq is from(->q) on astr = aq.astr
|
|
1928
|
-
}
|
|
1929
|
-
query: aqf is aq -> { project: * }
|
|
1930
|
-
`));
|
|
1931
|
-
test("new query appends to existing query", () => {
|
|
1932
|
-
const src = `
|
|
1933
|
-
query: s1 is table('malloytest.flights') -> {
|
|
1934
|
-
group_by: origin, destination
|
|
1935
|
-
}
|
|
1936
|
-
query: s2 is ->s1 ->{
|
|
1937
|
-
group_by: destination
|
|
1938
|
-
}
|
|
1939
|
-
`;
|
|
1940
|
-
const m = new BetaModel(src);
|
|
1941
|
-
expect(m).toTranslate();
|
|
1942
|
-
const s2 = m.getQuery("s2");
|
|
1943
|
-
expect(s2 === null || s2 === void 0 ? void 0 : s2.pipeline.length).toBe(2);
|
|
1944
|
-
});
|
|
1945
|
-
});
|
|
1946
|
-
describe("standard sql function return types", () => {
|
|
1947
|
-
test("timestamp_seconds", () => {
|
|
1948
|
-
expect("timestamp_seconds(0)").toReturnType("timestamp");
|
|
1949
|
-
});
|
|
1950
|
-
});
|
|
1951
|
-
//# sourceMappingURL=parse.spec.js.map
|