@malloydata/malloy 0.0.43-dev230621180201 → 0.0.43-dev230621212545
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/test/imports.spec.js +18 -15
- package/dist/lang/test/parse-expects.d.ts +32 -6
- package/dist/lang/test/parse-expects.js +39 -60
- package/dist/lang/test/parse.spec.js +731 -452
- package/dist/lang/test/sql-block.spec.js +15 -12
- package/dist/lang/test/test-translator.d.ts +6 -0
- package/dist/lang/test/test-translator.js +15 -1
- package/package.json +1 -1
|
@@ -22,76 +22,34 @@
|
|
|
22
22
|
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
|
23
23
|
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
24
24
|
*/
|
|
25
|
-
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
26
|
-
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
27
|
-
};
|
|
28
25
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
29
26
|
const model_1 = require("../../model");
|
|
30
27
|
const test_translator_1 = require("./test-translator");
|
|
31
|
-
const isEqual_1 = __importDefault(require("lodash/isEqual"));
|
|
32
28
|
const granular_result_1 = require("../ast/types/granular-result");
|
|
33
29
|
require("./parse-expects");
|
|
34
|
-
function exprOK(s) {
|
|
35
|
-
return () => {
|
|
36
|
-
expect(new test_translator_1.BetaExpression(s)).modelParsed();
|
|
37
|
-
return undefined;
|
|
38
|
-
};
|
|
39
|
-
}
|
|
40
|
-
function exprType(s, t) {
|
|
41
|
-
return () => {
|
|
42
|
-
const expr = new test_translator_1.BetaExpression(s);
|
|
43
|
-
expect(expr).modelParsed();
|
|
44
|
-
expect(expr.generated().dataType).toBe(t);
|
|
45
|
-
return undefined;
|
|
46
|
-
};
|
|
47
|
-
}
|
|
48
|
-
function modelOK(s) {
|
|
49
|
-
return () => {
|
|
50
|
-
const m = new test_translator_1.TestTranslator(s);
|
|
51
|
-
expect(m).modelCompiled();
|
|
52
|
-
return undefined;
|
|
53
|
-
};
|
|
54
|
-
}
|
|
55
|
-
function badModel(s, msg) {
|
|
56
|
-
return () => {
|
|
57
|
-
const src = typeof s === 'string' ? s : s.code;
|
|
58
|
-
const emsg = `Error expectation not met\nExpected error: '${msg}'\nSource:\n${src}`;
|
|
59
|
-
const m = new test_translator_1.TestTranslator(src);
|
|
60
|
-
const t = m.translate();
|
|
61
|
-
if (t.translated) {
|
|
62
|
-
fail(emsg);
|
|
63
|
-
}
|
|
64
|
-
else {
|
|
65
|
-
const errList = m.errors().errors;
|
|
66
|
-
const firstError = errList[0];
|
|
67
|
-
if (firstError.message !== msg) {
|
|
68
|
-
fail(`Received errror: ${firstError.message}\n${emsg}`);
|
|
69
|
-
}
|
|
70
|
-
if (typeof s !== 'string') {
|
|
71
|
-
if (!(0, isEqual_1.default)(errList[0].at, s.locations[0])) {
|
|
72
|
-
fail(`Expected location: ${s.locations[0]}\n` +
|
|
73
|
-
`Received location: ${errList[0].at}\n${emsg}`);
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
return undefined;
|
|
78
|
-
};
|
|
79
|
-
}
|
|
80
30
|
describe('model statements', () => {
|
|
81
31
|
describe('source:', () => {
|
|
82
|
-
test('table',
|
|
32
|
+
test('table', () => {
|
|
33
|
+
expect("source: testA is table('aTable')").toTranslate();
|
|
34
|
+
});
|
|
83
35
|
test('shorcut fitlered table', () => {
|
|
84
|
-
expect("source:
|
|
85
|
-
});
|
|
86
|
-
test('fitlered table',
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
test('ref source with no refinement',
|
|
90
|
-
|
|
91
|
-
|
|
36
|
+
expect("source: xA is table('aTable') {? astr ~ 'a%' }").toTranslate();
|
|
37
|
+
});
|
|
38
|
+
test('fitlered table', () => {
|
|
39
|
+
expect("source: testA is table('aTable') { where: astr ~ 'a%' }").toTranslate();
|
|
40
|
+
});
|
|
41
|
+
test('ref source with no refinement', () => {
|
|
42
|
+
expect('source: testA is a').toTranslate();
|
|
43
|
+
});
|
|
44
|
+
test('from(query)', () => {
|
|
45
|
+
expect('source: testA is from(a->{group_by: astr})').toTranslate();
|
|
46
|
+
});
|
|
47
|
+
test('refine source', () => {
|
|
48
|
+
expect('source: aa is a { dimension: a is astr }').toTranslate();
|
|
49
|
+
});
|
|
92
50
|
test('source refinement preserves original', () => {
|
|
93
51
|
const x = new test_translator_1.TestTranslator('source: na is a + { dimension: one is 1 }');
|
|
94
|
-
expect(x).
|
|
52
|
+
expect(x).toTranslate();
|
|
95
53
|
const a = x.getSourceDef('a');
|
|
96
54
|
if (a) {
|
|
97
55
|
const aFields = a.fields.map(f => f.as || f.name);
|
|
@@ -101,45 +59,67 @@ describe('model statements', () => {
|
|
|
101
59
|
});
|
|
102
60
|
});
|
|
103
61
|
describe('query:', () => {
|
|
104
|
-
test('anonymous query',
|
|
105
|
-
|
|
106
|
-
|
|
62
|
+
test('anonymous query', () => {
|
|
63
|
+
expect("query: table('aTable') -> { group_by: astr }").toTranslate();
|
|
64
|
+
});
|
|
65
|
+
test('query', () => {
|
|
66
|
+
expect("query: name is table('aTable') -> { group_by: astr }").toTranslate();
|
|
67
|
+
});
|
|
68
|
+
test('query from query', () => {
|
|
69
|
+
expect(`
|
|
107
70
|
query: q1 is ab->{ group_by: astr limit: 10 }
|
|
108
71
|
query: q2 is ->q1
|
|
109
|
-
`));
|
|
110
|
-
|
|
72
|
+
`).toTranslate();
|
|
73
|
+
});
|
|
74
|
+
test('query with refinements from query', () => {
|
|
75
|
+
expect(`
|
|
111
76
|
query: q1 is ab->{ group_by: astr limit: 10 }
|
|
112
77
|
query: q2 is ->q1 { aggregate: acount }
|
|
113
|
-
`));
|
|
114
|
-
|
|
78
|
+
`).toTranslate();
|
|
79
|
+
});
|
|
80
|
+
test('chained query operations', () => {
|
|
81
|
+
expect(`
|
|
115
82
|
query: ab
|
|
116
83
|
-> { group_by: astr; aggregate: acount }
|
|
117
84
|
-> { top: 5; where: astr ~ 'a%' group_by: astr }
|
|
118
|
-
`));
|
|
119
|
-
|
|
120
|
-
test('query
|
|
121
|
-
|
|
122
|
-
|
|
85
|
+
`).toTranslate();
|
|
86
|
+
});
|
|
87
|
+
test('from(query) refined into query', () => {
|
|
88
|
+
expect('query: from(ab -> {group_by: astr}) { dimension: bigstr is UPPER(astr) } -> { group_by: bigstr }').toTranslate();
|
|
89
|
+
});
|
|
90
|
+
test('query with shortcut filtered turtle', () => {
|
|
91
|
+
expect("query: allA is ab->aturtle {? astr ~ 'a%' }").toTranslate();
|
|
92
|
+
});
|
|
93
|
+
test('query with filtered turtle', () => {
|
|
94
|
+
expect("query: allA is ab->aturtle { where: astr ~ 'a%' }").toTranslate();
|
|
95
|
+
});
|
|
96
|
+
test('nest: in group_by:', () => {
|
|
97
|
+
expect(`
|
|
123
98
|
query: ab -> {
|
|
124
99
|
group_by: astr;
|
|
125
100
|
nest: nested_count is {
|
|
126
101
|
aggregate: acount
|
|
127
102
|
}
|
|
128
103
|
}
|
|
129
|
-
`));
|
|
130
|
-
|
|
104
|
+
`).toTranslate();
|
|
105
|
+
});
|
|
106
|
+
test('reduce pipe project', () => {
|
|
107
|
+
expect(`
|
|
131
108
|
query: a -> { aggregate: f is count() } -> { project: f2 is f + 1 }
|
|
132
|
-
`));
|
|
133
|
-
|
|
109
|
+
`).toTranslate();
|
|
110
|
+
});
|
|
111
|
+
test('refine and extend query', () => {
|
|
112
|
+
expect(`
|
|
134
113
|
query: a_by_str is a -> { group_by: astr }
|
|
135
114
|
query: -> a_by_str { aggregate: str_count is count() }
|
|
136
|
-
`));
|
|
115
|
+
`).toTranslate();
|
|
116
|
+
});
|
|
137
117
|
test('query refinement preserves original', () => {
|
|
138
118
|
const x = new test_translator_1.TestTranslator(`
|
|
139
119
|
query: q is a -> { aggregate: acount is count() }
|
|
140
120
|
query: nq is -> q + { group_by: astr }
|
|
141
121
|
`);
|
|
142
|
-
expect(x).
|
|
122
|
+
expect(x).toTranslate();
|
|
143
123
|
const q = x.getQuery('q');
|
|
144
124
|
expect(q).toBeDefined();
|
|
145
125
|
if (q) {
|
|
@@ -152,14 +132,15 @@ describe('model statements', () => {
|
|
|
152
132
|
query: q is ab -> { aggregate: acount }
|
|
153
133
|
query: nq is -> q -> { project: * }
|
|
154
134
|
`);
|
|
155
|
-
expect(x).
|
|
135
|
+
expect(x).toTranslate();
|
|
156
136
|
const q = x.getQuery('q');
|
|
157
137
|
expect(q).toBeDefined();
|
|
158
138
|
if (q) {
|
|
159
139
|
expect(q.pipeline.length).toBe(1);
|
|
160
140
|
}
|
|
161
141
|
});
|
|
162
|
-
test('all ungroup with args',
|
|
142
|
+
test('all ungroup with args', () => {
|
|
143
|
+
expect(`
|
|
163
144
|
query: a -> {
|
|
164
145
|
group_by: astr
|
|
165
146
|
nest: by_int is {
|
|
@@ -167,7 +148,8 @@ describe('model statements', () => {
|
|
|
167
148
|
aggregate: bi_count is all(count(), ai)
|
|
168
149
|
}
|
|
169
150
|
}
|
|
170
|
-
`));
|
|
151
|
+
`).toTranslate();
|
|
152
|
+
});
|
|
171
153
|
test('all ungroup checks args', () => {
|
|
172
154
|
expect(`
|
|
173
155
|
query: a -> {
|
|
@@ -177,9 +159,10 @@ describe('model statements', () => {
|
|
|
177
159
|
aggregate: bi_count is all(count(), afloat)
|
|
178
160
|
}
|
|
179
161
|
}
|
|
180
|
-
`).
|
|
162
|
+
`).translationToFailWith("all() 'afloat' is missing from query output");
|
|
181
163
|
});
|
|
182
|
-
test('exclude ungroup with args',
|
|
164
|
+
test('exclude ungroup with args', () => {
|
|
165
|
+
expect(`
|
|
183
166
|
query: a -> {
|
|
184
167
|
group_by: aa is 'a'
|
|
185
168
|
nest: by_b is {
|
|
@@ -190,7 +173,8 @@ describe('model statements', () => {
|
|
|
190
173
|
}
|
|
191
174
|
}
|
|
192
175
|
}
|
|
193
|
-
`));
|
|
176
|
+
`).toTranslate();
|
|
177
|
+
});
|
|
194
178
|
test('exclude ungroup checks args', () => {
|
|
195
179
|
expect(`
|
|
196
180
|
query: a -> {
|
|
@@ -203,9 +187,10 @@ describe('model statements', () => {
|
|
|
203
187
|
}
|
|
204
188
|
}
|
|
205
189
|
}
|
|
206
|
-
`).
|
|
190
|
+
`).translationToFailWith("exclude() 'aaa' is missing from query output");
|
|
207
191
|
});
|
|
208
|
-
test('exclude problem revealed by production models',
|
|
192
|
+
test('exclude problem revealed by production models', () => {
|
|
193
|
+
expect(`
|
|
209
194
|
source: carriers is table('malloytest.carriers') {
|
|
210
195
|
primary_key: code
|
|
211
196
|
}
|
|
@@ -222,89 +207,92 @@ describe('model statements', () => {
|
|
|
222
207
|
}
|
|
223
208
|
}
|
|
224
209
|
}
|
|
225
|
-
`));
|
|
210
|
+
`).toTranslate();
|
|
211
|
+
});
|
|
226
212
|
describe('query operation typechecking', () => {
|
|
227
213
|
describe('field declarations', () => {
|
|
228
214
|
test('cannot use aggregate in group_by', () => {
|
|
229
|
-
expect('query: a -> { group_by: s is count()}').
|
|
215
|
+
expect('query: a -> { group_by: s is count()}').translationToFailWith('Cannot use an aggregate field in a group_by operation, did you mean to use an aggregate operation instead?');
|
|
230
216
|
});
|
|
231
217
|
test('cannot use ungrouped_aggregate in group_by', () => {
|
|
232
|
-
expect('query: a -> { group_by: s is all(count())}').
|
|
218
|
+
expect('query: a -> { group_by: s is all(count())}').translationToFailWith('Cannot use an aggregate field in a group_by operation, did you mean to use an aggregate operation instead?');
|
|
233
219
|
});
|
|
234
220
|
test('cannot use analytic in group_by', () => {
|
|
235
|
-
expect('query: a -> { group_by: s is row_number()}').
|
|
221
|
+
expect('query: a -> { group_by: s is row_number()}').translationToFailWith('Cannot use an analytic field in a group_by operation, did you mean to use a calculate operation instead?');
|
|
236
222
|
});
|
|
237
223
|
test('cannot use aggregate in dimension', () => {
|
|
238
|
-
expect('source: a1 is a { dimension: s is count()}').
|
|
224
|
+
expect('source: a1 is a { dimension: s is count()}').translationToFailWith('Cannot use an aggregate field in a dimension declaration, did you mean to use a measure declaration instead?');
|
|
239
225
|
});
|
|
240
226
|
test('cannot use ungrouped_aggregate in dimension', () => {
|
|
241
|
-
expect('source: a1 is a { dimension: s is all(count())}').
|
|
227
|
+
expect('source: a1 is a { dimension: s is all(count())}').translationToFailWith('Cannot use an aggregate field in a dimension declaration, did you mean to use a measure declaration instead?');
|
|
242
228
|
});
|
|
243
229
|
test('cannot use analytic in dimension', () => {
|
|
244
|
-
expect('source: a1 is a { dimension: s is row_number()}').
|
|
230
|
+
expect('source: a1 is a { dimension: s is row_number()}').translationToFailWith('Cannot use an analytic field in a dimension declaration');
|
|
245
231
|
});
|
|
246
232
|
test('cannot use scalar in measure', () => {
|
|
247
|
-
expect('source: a1 is a { measure: s is 1}').
|
|
233
|
+
expect('source: a1 is a { measure: s is 1}').translationToFailWith('Cannot use a scalar field in a measure declaration, did you mean to use a dimension declaration instead?');
|
|
248
234
|
});
|
|
249
235
|
test('cannot use analytic in measure', () => {
|
|
250
|
-
expect('source: a1 is a { measure: s is lag(count())}').
|
|
236
|
+
expect('source: a1 is a { measure: s is lag(count())}').translationToFailWith('Cannot use an analytic field in a measure declaration');
|
|
251
237
|
});
|
|
252
238
|
test('cannot use scalar in aggregate', () => {
|
|
253
|
-
expect('query: a -> { aggregate: s is 1}').
|
|
239
|
+
expect('query: a -> { aggregate: s is 1}').translationToFailWith('Cannot use a scalar field in an aggregate operation, did you mean to use a group_by or project operation instead?');
|
|
254
240
|
});
|
|
255
241
|
test('cannot use analytic in aggregate', () => {
|
|
256
|
-
expect('query: a -> { aggregate: s is lag(count())}').
|
|
242
|
+
expect('query: a -> { aggregate: s is lag(count())}').translationToFailWith('Cannot use an analytic field in an aggregate operation, did you mean to use a calculate operation instead?');
|
|
257
243
|
});
|
|
258
244
|
test('cannot use scalar in calculate', () => {
|
|
259
|
-
expect('query: a -> { group_by: a is 1; calculate: s is 1 }').
|
|
245
|
+
expect('query: a -> { group_by: a is 1; calculate: s is 1 }').translationToFailWith('Cannot use a scalar field in a calculate operation, did you mean to use a group_by or project operation instead?');
|
|
260
246
|
});
|
|
261
247
|
test('cannot use aggregate in calculate', () => {
|
|
262
|
-
expect('query: a -> { group_by: a is 1; calculate: s is count() }').
|
|
248
|
+
expect('query: a -> { group_by: a is 1; calculate: s is count() }').translationToFailWith('Cannot use an aggregate field in a calculate operation, did you mean to use an aggregate operation instead?');
|
|
263
249
|
});
|
|
264
250
|
test('cannot use aggregate in project', () => {
|
|
265
|
-
expect('query: a -> { project: s is count() }').
|
|
251
|
+
expect('query: a -> { project: s is count() }').translationToFailWith('Cannot use an aggregate field in a project operation, did you mean to use an aggregate operation instead?');
|
|
266
252
|
});
|
|
267
253
|
test('cannot use analytic in project', () => {
|
|
268
|
-
expect('query: a -> { project: s is row_number() }').
|
|
254
|
+
expect('query: a -> { project: s is row_number() }').translationToFailWith('Cannot use an analytic field in a project operation, did you mean to use a calculate operation instead?');
|
|
269
255
|
});
|
|
270
256
|
test('cannot use analytic in declare', () => {
|
|
271
|
-
expect('query: a -> { group_by: a is 1; declare: s is row_number() }').
|
|
257
|
+
expect('query: a -> { group_by: a is 1; declare: s is row_number() }').translationToFailWith('Analytic expressions can not be used in a declare block');
|
|
272
258
|
});
|
|
273
259
|
test('cannot use aggregate in index', () => {
|
|
274
|
-
expect('query: a { measure: acount is count() } -> { index: acount }').
|
|
260
|
+
expect('query: a { measure: acount is count() } -> { index: acount }').translationToFailWith('Cannot use an aggregate field in an index operation');
|
|
275
261
|
});
|
|
276
|
-
test('can use aggregate in except',
|
|
262
|
+
test('can use aggregate in except', () => {
|
|
263
|
+
expect(`
|
|
277
264
|
source: b1 is a { measure: acount is count() }
|
|
278
265
|
source: c1 is b1 { except: acount }
|
|
279
|
-
`));
|
|
266
|
+
`).toTranslate();
|
|
267
|
+
});
|
|
280
268
|
});
|
|
281
269
|
describe('field references', () => {
|
|
282
270
|
test('cannot use aggregate in group_by', () => {
|
|
283
|
-
expect('query: a -> { declare: acount is count(); group_by: acount }').
|
|
271
|
+
expect('query: a -> { declare: acount is count(); group_by: acount }').translationToFailWith('Cannot use an aggregate field in a group_by operation, did you mean to use an aggregate operation instead?');
|
|
284
272
|
});
|
|
285
273
|
test('cannot use query in group_by', () => {
|
|
286
|
-
expect('query: a { query: q is { group_by: x is 1 } } -> { group_by: q }').
|
|
274
|
+
expect('query: a { query: q is { group_by: x is 1 } } -> { group_by: q }').translationToFailWith('Cannot use a query field in a group_by operation, did you mean to use a nest operation instead?');
|
|
287
275
|
});
|
|
288
276
|
test('cannot use scalar in aggregate', () => {
|
|
289
|
-
expect('query: a -> { declare: aconst is 1; aggregate: aconst }').
|
|
277
|
+
expect('query: a -> { declare: aconst is 1; aggregate: aconst }').translationToFailWith('Cannot use a scalar field in an aggregate operation, did you mean to use a group_by or project operation instead?');
|
|
290
278
|
});
|
|
291
279
|
test('cannot use scalar in calculate', () => {
|
|
292
|
-
expect('query: a -> { declare: aconst is 1; group_by: x is 1; calculate: aconst }').
|
|
280
|
+
expect('query: a -> { declare: aconst is 1; group_by: x is 1; calculate: aconst }').translationToFailWith('Cannot use a scalar field in a calculate operation, did you mean to use a group_by or project operation instead?');
|
|
293
281
|
});
|
|
294
282
|
test('cannot use aggregate in calculate', () => {
|
|
295
|
-
expect('query: a -> { declare: acount is count(); group_by: x is 1; calculate: acount }').
|
|
283
|
+
expect('query: a -> { declare: acount is count(); group_by: x is 1; calculate: acount }').translationToFailWith('Cannot use an aggregate field in a calculate operation, did you mean to use an aggregate operation instead?');
|
|
296
284
|
});
|
|
297
285
|
test('cannot use query in project', () => {
|
|
298
|
-
expect('query: a { query: q is { group_by: x is 1 } } -> { project: q }').
|
|
286
|
+
expect('query: a { query: q is { group_by: x is 1 } } -> { project: q }').translationToFailWith('Cannot use a query field in a project operation, did you mean to use a nest operation instead?');
|
|
299
287
|
});
|
|
300
288
|
test('cannot use query in index', () => {
|
|
301
|
-
expect('query: a { query: q is { group_by: x is 1 } } -> { index: q }').
|
|
289
|
+
expect('query: a { query: q is { group_by: x is 1 } } -> { index: q }').translationToFailWith('Cannot use a query field in an index operation');
|
|
302
290
|
});
|
|
303
291
|
test('cannot use query in calculate', () => {
|
|
304
|
-
expect('query: a { query: q is { group_by: x is 1 } } -> { group_by: x is 1; calculate: q }').
|
|
292
|
+
expect('query: a { query: q is { group_by: x is 1 } } -> { group_by: x is 1; calculate: q }').translationToFailWith('Cannot use a query field in a calculate operation, did you mean to use a nest operation instead?');
|
|
305
293
|
});
|
|
306
294
|
test('cannot use query in aggregate', () => {
|
|
307
|
-
expect('query: a { query: q is { group_by: x is 1 } } -> { aggregate: q }').
|
|
295
|
+
expect('query: a { query: q is { group_by: x is 1 } } -> { aggregate: q }').translationToFailWith('Cannot use a query field in an aggregate operation, did you mean to use a nest operation instead?');
|
|
308
296
|
});
|
|
309
297
|
test('cannot use aggregate in calculate, preserved over refinement', () => {
|
|
310
298
|
expect(`query: a1 is a -> {
|
|
@@ -312,7 +300,7 @@ describe('model statements', () => {
|
|
|
312
300
|
}
|
|
313
301
|
query: -> a1 {
|
|
314
302
|
calculate: b is c
|
|
315
|
-
}`).
|
|
303
|
+
}`).translationToFailWith('Cannot use an aggregate field in a calculate operation, did you mean to use an aggregate operation instead?');
|
|
316
304
|
});
|
|
317
305
|
test('cannot use scalar in calculate, preserved over refinement', () => {
|
|
318
306
|
expect(`query: a1 is a -> {
|
|
@@ -320,7 +308,7 @@ describe('model statements', () => {
|
|
|
320
308
|
}
|
|
321
309
|
query: -> a1 {
|
|
322
310
|
calculate: b is c
|
|
323
|
-
}`).
|
|
311
|
+
}`).translationToFailWith('Cannot use a scalar field in a calculate operation, did you mean to use a group_by or project operation instead?');
|
|
324
312
|
});
|
|
325
313
|
test('cannot use analytic in group_by, preserved over refinement', () => {
|
|
326
314
|
expect(`query: a1 is a -> {
|
|
@@ -329,7 +317,7 @@ describe('model statements', () => {
|
|
|
329
317
|
}
|
|
330
318
|
query: -> a1 {
|
|
331
319
|
group_by: b is c2
|
|
332
|
-
}`).
|
|
320
|
+
}`).translationToFailWith(
|
|
333
321
|
// c2 is not defined because group_by doesn't know to look in the output space
|
|
334
322
|
"'c2' is not defined", "Cannot define 'b', value has unknown type");
|
|
335
323
|
});
|
|
@@ -340,85 +328,99 @@ describe('model statements', () => {
|
|
|
340
328
|
}
|
|
341
329
|
query: -> a1 {
|
|
342
330
|
order_by: c2
|
|
343
|
-
}`).
|
|
331
|
+
}`).translationToFailWith('Illegal order by of analytic field c2');
|
|
344
332
|
});
|
|
345
333
|
test('cannot ungroup an ungrouped', () => {
|
|
346
334
|
expect(`query: a1 is a -> {
|
|
347
335
|
group_by: c is 1
|
|
348
336
|
aggregate: c2 is all(all(sum(ai)))
|
|
349
|
-
}`).
|
|
337
|
+
}`).translationToFailWith('all() expression must not already be ungrouped', "Cannot define 'c2', value has unknown type");
|
|
350
338
|
});
|
|
351
339
|
test('cannot aggregate an ungrouped', () => {
|
|
352
340
|
expect(`query: a1 is a -> {
|
|
353
341
|
group_by: c is 1
|
|
354
342
|
aggregate: c2 is sum(all(sum(ai)))
|
|
355
|
-
}`).
|
|
343
|
+
}`).translationToFailWith('Aggregate expression cannot be aggregate', "Cannot define 'c2', value has unknown type");
|
|
356
344
|
});
|
|
357
345
|
test('cannot aggregate an aggregate', () => {
|
|
358
346
|
expect(`query: a1 is a -> {
|
|
359
347
|
group_by: c is 1
|
|
360
348
|
aggregate: c2 is sum(sum(ai))
|
|
361
|
-
}`).
|
|
349
|
+
}`).translationToFailWith('Aggregate expression cannot be aggregate', "Cannot define 'c2', value has unknown type");
|
|
362
350
|
});
|
|
363
|
-
test('can use field def in group_by, preserved over refinement',
|
|
351
|
+
test('can use field def in group_by, preserved over refinement', () => {
|
|
352
|
+
expect(`query: a1 is a -> {
|
|
364
353
|
group_by: c is 1
|
|
365
354
|
}
|
|
366
355
|
query: -> a1 {
|
|
367
356
|
order_by: c
|
|
368
|
-
}`));
|
|
369
|
-
|
|
357
|
+
}`).toTranslate();
|
|
358
|
+
});
|
|
359
|
+
test('can use field ref in group_by, preserved over refinement', () => {
|
|
360
|
+
expect(`query: a1 is a -> {
|
|
370
361
|
group_by: c is astr
|
|
371
362
|
}
|
|
372
363
|
query: -> a1 {
|
|
373
364
|
order_by: c
|
|
374
|
-
}`));
|
|
365
|
+
}`).toTranslate();
|
|
366
|
+
});
|
|
375
367
|
});
|
|
376
368
|
});
|
|
377
369
|
describe('function typechecking', () => {
|
|
378
|
-
test('use function correctly',
|
|
370
|
+
test('use function correctly', () => {
|
|
371
|
+
expect(`query: a -> {
|
|
379
372
|
group_by: s is concat('a', 'b')
|
|
380
|
-
}`));
|
|
373
|
+
}`).toTranslate();
|
|
374
|
+
});
|
|
381
375
|
test('function no matching overload', () => {
|
|
382
376
|
expect(`query: a -> {
|
|
383
377
|
group_by: s is floor('a', 'b')
|
|
384
|
-
}`).
|
|
378
|
+
}`).translationToFailWith('No matching overload for function floor(string, string)', "Cannot define 's', value has unknown type");
|
|
385
379
|
});
|
|
386
380
|
test('unknown function', () => {
|
|
387
381
|
expect(`query: a -> {
|
|
388
382
|
group_by: s is asdfasdf()
|
|
389
|
-
}`).
|
|
383
|
+
}`).translationToFailWith("Unknown function 'asdfasdf'. Use 'asdfasdf!(...)' to call a SQL function directly.", "Cannot define 's', value has unknown type");
|
|
390
384
|
});
|
|
391
|
-
test('can select different overload',
|
|
392
|
-
|
|
385
|
+
test('can select different overload', () => {
|
|
386
|
+
expect('query: a -> { group_by: s is concat() }').toTranslate();
|
|
387
|
+
});
|
|
388
|
+
test('can pass different expression types', () => {
|
|
389
|
+
expect(`query: a -> {
|
|
393
390
|
group_by: f1 is sqrt(1)
|
|
394
391
|
aggregate: f2 is sqrt(count())
|
|
395
392
|
aggregate: f3 is sqrt(all(count()))
|
|
396
393
|
calculate: f4 is sqrt(lag(f1))
|
|
397
394
|
calculate: f5 is sqrt(lag(count()))
|
|
398
|
-
}`));
|
|
399
|
-
|
|
395
|
+
}`).toTranslate();
|
|
396
|
+
});
|
|
397
|
+
test('function return type correct', () => {
|
|
398
|
+
expect(`query: a -> {
|
|
400
399
|
group_by: s is floor(1.2) + 1
|
|
401
|
-
}`));
|
|
400
|
+
}`).toTranslate();
|
|
401
|
+
});
|
|
402
402
|
test('function return type incorrect', () => {
|
|
403
403
|
expect(`query: a -> {
|
|
404
404
|
group_by: s is floor(1.2) + 'a'
|
|
405
|
-
}`).
|
|
405
|
+
}`).translationToFailWith("Non numeric('number,string') value with '+'", "Cannot define 's', value has unknown type");
|
|
406
406
|
});
|
|
407
|
-
test('can use output value in calculate',
|
|
407
|
+
test('can use output value in calculate', () => {
|
|
408
|
+
expect(`query: a -> {
|
|
408
409
|
group_by: x is 1
|
|
409
410
|
calculate: s is lag(x)
|
|
410
|
-
}`));
|
|
411
|
+
}`).toTranslate();
|
|
412
|
+
});
|
|
411
413
|
test('cannot use output value in group_by', () => {
|
|
412
414
|
expect(`query: a -> {
|
|
413
415
|
group_by: x is 1
|
|
414
416
|
group_by: y is x
|
|
415
|
-
}`).
|
|
417
|
+
}`).translationToFailWith("'x' is not defined", "Cannot define 'y', value has unknown type");
|
|
416
418
|
});
|
|
417
419
|
test('lag can check that other args are constant', () => {
|
|
418
420
|
expect(`query: a -> {
|
|
419
421
|
group_by: x is 1
|
|
420
422
|
calculate: s is lag(x, 1, x)
|
|
421
|
-
}`).
|
|
423
|
+
}`).translationToFailWith(
|
|
422
424
|
// TODO improve this error message
|
|
423
425
|
"Parameter 3 ('default') of lag must be literal or constant, but received output");
|
|
424
426
|
});
|
|
@@ -426,7 +428,7 @@ describe('model statements', () => {
|
|
|
426
428
|
expect(`query: a -> {
|
|
427
429
|
group_by: x is 1
|
|
428
430
|
calculate: s is lag(x, 1 + 1)
|
|
429
|
-
}`).
|
|
431
|
+
}`).translationToFailWith(
|
|
430
432
|
// TODO improve this error message
|
|
431
433
|
"Parameter 2 ('offset') of lag must be literal, but received constant");
|
|
432
434
|
});
|
|
@@ -434,28 +436,32 @@ describe('model statements', () => {
|
|
|
434
436
|
expect(`query: a -> {
|
|
435
437
|
group_by: x is 1
|
|
436
438
|
calculate: s is lag(x, null)
|
|
437
|
-
}`).
|
|
439
|
+
}`).translationToFailWith("Parameter 2 ('offset') of lag must not be a literal null");
|
|
438
440
|
});
|
|
439
|
-
test('lag can use constant values for other args',
|
|
441
|
+
test('lag can use constant values for other args', () => {
|
|
442
|
+
expect(`query: a -> {
|
|
440
443
|
group_by: x is 1
|
|
441
444
|
calculate: s is lag(x, 2)
|
|
442
|
-
}`));
|
|
445
|
+
}`).toTranslate();
|
|
446
|
+
});
|
|
443
447
|
test('cannot name top level objects same as functions', () => {
|
|
444
|
-
expect((0, test_translator_1.markSource) `query: ${'concat is a -> { group_by: x is 1 }'}`).
|
|
448
|
+
expect((0, test_translator_1.markSource) `query: ${'concat is a -> { group_by: x is 1 }'}`).translationToFailWith(
|
|
445
449
|
// TODO improve this error message
|
|
446
450
|
"'concat' is already defined, cannot redefine");
|
|
447
451
|
});
|
|
448
|
-
test('`now` is considered constant`',
|
|
452
|
+
test('`now` is considered constant`', () => {
|
|
453
|
+
expect(`query: a -> {
|
|
449
454
|
group_by: n is now
|
|
450
455
|
calculate: l is lag(n, 1, now)
|
|
451
|
-
}`));
|
|
456
|
+
}`).toTranslate();
|
|
457
|
+
});
|
|
452
458
|
// TODO it might be nice to reference a field which is a constant, and be able to
|
|
453
459
|
// use that as a constant param. Same with a literal.
|
|
454
460
|
test('cannot use a field which is a constant in a constant param', () => {
|
|
455
461
|
expect(`query: a -> {
|
|
456
462
|
group_by: ai, pi is pi()
|
|
457
463
|
calculate: l is lag(ai, 1, pi)
|
|
458
|
-
}`).
|
|
464
|
+
}`).translationToFailWith("Parameter 3 ('default') of lag must be literal or constant, but received output");
|
|
459
465
|
});
|
|
460
466
|
// TODO we don't handle referencing a join as a field correctly in all cases today.
|
|
461
467
|
// For now, it at least is considered type `struct` and therefore fails to parse
|
|
@@ -465,58 +471,66 @@ describe('model statements', () => {
|
|
|
465
471
|
expect(`query: a {join_one: b with astr } -> {
|
|
466
472
|
group_by: b
|
|
467
473
|
calculate: foo is lag(b)
|
|
468
|
-
}`).
|
|
474
|
+
}`).translationToFailWith('No matching overload for function lag(struct)', "Cannot define 'foo', value has unknown type");
|
|
469
475
|
});
|
|
470
476
|
// TODO this doesn't work today, we're not rigorous enough with integer
|
|
471
477
|
// subtypes. But we should probably make this typecheck properly.
|
|
472
478
|
test.skip('cannot use float in round precision', () => {
|
|
473
479
|
expect(`query: a -> {
|
|
474
480
|
group_by: x is round(1.5, 1.6)
|
|
475
|
-
}`).
|
|
481
|
+
}`).translationToFailWith(
|
|
476
482
|
// TODO improve this error message
|
|
477
483
|
"Parameter 2 ('precision') for round must be integer, received float");
|
|
478
484
|
});
|
|
479
485
|
test('cannot use stddev with no arguments', () => {
|
|
480
486
|
expect(`query: a -> {
|
|
481
487
|
aggregate: x is stddev()
|
|
482
|
-
}`).
|
|
488
|
+
}`).translationToFailWith('No matching overload for function stddev()', "Cannot define 'x', value has unknown type");
|
|
483
489
|
});
|
|
484
|
-
test('can use stddev with postfix syntax',
|
|
490
|
+
test('can use stddev with postfix syntax', () => {
|
|
491
|
+
expect(`query: a -> {
|
|
485
492
|
declare: y is 1
|
|
486
493
|
aggregate: x is y.stddev()
|
|
487
|
-
}`));
|
|
488
|
-
|
|
494
|
+
}`).toTranslate();
|
|
495
|
+
});
|
|
496
|
+
test('can use stddev with postfix syntax on join', () => {
|
|
497
|
+
expect(`query: a -> {
|
|
489
498
|
join_one: b with astr
|
|
490
499
|
aggregate: x is b.ai.stddev()
|
|
491
|
-
}`));
|
|
492
|
-
|
|
500
|
+
}`).toTranslate();
|
|
501
|
+
});
|
|
502
|
+
test('can use calculate with a measure', () => {
|
|
503
|
+
expect(`query: a { measure: c is count() } -> {
|
|
493
504
|
group_by: y is 1
|
|
494
505
|
calculate: x is lag(c)
|
|
495
|
-
}`));
|
|
506
|
+
}`).toTranslate();
|
|
507
|
+
});
|
|
496
508
|
test('cannot use calculate with input fields', () => {
|
|
497
509
|
expect(`query: a -> {
|
|
498
510
|
group_by: y is 1
|
|
499
511
|
calculate: x is lag(ai)
|
|
500
|
-
}`).
|
|
512
|
+
}`).translationToFailWith(
|
|
501
513
|
// TODO improve this error message:
|
|
502
514
|
// Parameter 1 ('value') of 'lag' must be a constant, an aggregate, or an expression using
|
|
503
515
|
// only fields that appear in the query output. Received an expression which uses a field
|
|
504
516
|
// that is not in the query output.
|
|
505
517
|
"Parameter 1 ('value') of lag must be literal, constant or output, but received input");
|
|
506
518
|
});
|
|
507
|
-
test('can use calculate with aggregate field which is not in query',
|
|
519
|
+
test('can use calculate with aggregate field which is not in query', () => {
|
|
520
|
+
expect(`query: a { measure: acount is count() } -> {
|
|
508
521
|
group_by: astr
|
|
509
522
|
calculate: pc is lag(acount)
|
|
510
|
-
}`));
|
|
523
|
+
}`).toTranslate();
|
|
524
|
+
});
|
|
511
525
|
test('cannot use agregate as argument to agg function', () => {
|
|
512
526
|
expect(`query: a -> {
|
|
513
527
|
aggregate: x is stddev(count())
|
|
514
|
-
}`).
|
|
528
|
+
}`).translationToFailWith("Parameter 1 ('value') of stddev must be scalar, but received aggregate");
|
|
515
529
|
});
|
|
516
530
|
test('cannot use calculate with no other fields', () => {
|
|
517
531
|
expect(`query: a -> {
|
|
518
532
|
calculate: x is row_number()
|
|
519
|
-
}`).
|
|
533
|
+
}`).translationToFailWith("Can't determine query type (group_by/aggregate/nest,project,index)");
|
|
520
534
|
});
|
|
521
535
|
// TODO someday make it so we can order by an analytic function
|
|
522
536
|
test('today: cannot order by analytic function', () => {
|
|
@@ -524,7 +538,7 @@ describe('model statements', () => {
|
|
|
524
538
|
group_by: astr
|
|
525
539
|
calculate: row_num is row_number()
|
|
526
540
|
order_by: row_num desc
|
|
527
|
-
}`).
|
|
541
|
+
}`).translationToFailWith('Illegal order by of analytic field row_num');
|
|
528
542
|
});
|
|
529
543
|
test('cannot use analytic in calculate -- and preserved over refinement', () => {
|
|
530
544
|
expect(`query: a1 is a -> {
|
|
@@ -533,72 +547,97 @@ describe('model statements', () => {
|
|
|
533
547
|
}
|
|
534
548
|
query: -> a1 {
|
|
535
549
|
calculate: p1 is lag(p)
|
|
536
|
-
}`).
|
|
550
|
+
}`).translationToFailWith("Parameter 1 ('value') of lag must be scalar or aggregate, but received scalar_analytic");
|
|
537
551
|
});
|
|
538
552
|
test('cannot use aggregate analytic in project', () => {
|
|
539
553
|
expect(`query: a -> {
|
|
540
554
|
project: astr
|
|
541
555
|
calculate: p is lag(count())
|
|
542
|
-
}`).
|
|
556
|
+
}`).translationToFailWith('Cannot add aggregate analyics to project');
|
|
543
557
|
});
|
|
544
|
-
test('reference field in join',
|
|
558
|
+
test('reference field in join', () => {
|
|
559
|
+
expect(`query: a -> {
|
|
545
560
|
join_one: b with astr
|
|
546
561
|
group_by: b.ai
|
|
547
|
-
}`));
|
|
562
|
+
}`).toTranslate();
|
|
563
|
+
});
|
|
548
564
|
// TODO decide whether this syntax should be legal, really...
|
|
549
|
-
test('reference join name in group_by',
|
|
565
|
+
test('reference join name in group_by', () => {
|
|
566
|
+
expect(`query: a -> {
|
|
550
567
|
join_one: b with astr
|
|
551
568
|
group_by: b
|
|
552
|
-
}`));
|
|
553
|
-
|
|
569
|
+
}`).toTranslate();
|
|
570
|
+
});
|
|
571
|
+
test('can reference project: inline join.* field in calculate', () => {
|
|
572
|
+
expect(`query: a -> {
|
|
554
573
|
join_one: b with astr
|
|
555
574
|
project: b.*
|
|
556
575
|
calculate: s is lag(ai)
|
|
557
|
-
}`));
|
|
558
|
-
|
|
576
|
+
}`).toTranslate();
|
|
577
|
+
});
|
|
578
|
+
test('can reference project: join.* field in calculate', () => {
|
|
579
|
+
expect(`query: a { join_one: b with astr } -> {
|
|
559
580
|
project: b.*
|
|
560
581
|
calculate: s is lag(ai)
|
|
561
|
-
}`));
|
|
582
|
+
}`).toTranslate();
|
|
583
|
+
});
|
|
562
584
|
});
|
|
563
585
|
});
|
|
564
586
|
test('errors on redefinition of query', () => {
|
|
565
|
-
expect('query: q1 is a -> { project: * }, q1 is a -> { project: * }').
|
|
587
|
+
expect('query: q1 is a -> { project: * }, q1 is a -> { project: * }').translationToFailWith("'q1' is already defined, cannot redefine");
|
|
566
588
|
});
|
|
567
589
|
});
|
|
568
590
|
describe('source properties', () => {
|
|
569
|
-
test('single dimension',
|
|
570
|
-
|
|
591
|
+
test('single dimension', () => {
|
|
592
|
+
expect('source: aa is a { dimension: x is 1 }').toTranslate();
|
|
593
|
+
});
|
|
594
|
+
test('multiple dimensions', () => {
|
|
595
|
+
expect(`
|
|
571
596
|
source: aa is a {
|
|
572
597
|
dimension:
|
|
573
598
|
x is 1
|
|
574
599
|
y is 2
|
|
575
600
|
}
|
|
576
|
-
`));
|
|
577
|
-
|
|
578
|
-
test('
|
|
601
|
+
`).toTranslate();
|
|
602
|
+
});
|
|
603
|
+
test('single declare', () => {
|
|
604
|
+
expect('source: aa is a { declare: x is 1 }').toTranslate();
|
|
605
|
+
});
|
|
606
|
+
test('multiple declare', () => {
|
|
607
|
+
expect(`
|
|
579
608
|
source: aa is a {
|
|
580
609
|
declare:
|
|
581
610
|
x is 1
|
|
582
611
|
y is 2
|
|
583
612
|
}
|
|
584
|
-
`));
|
|
585
|
-
|
|
586
|
-
test('
|
|
613
|
+
`).toTranslate();
|
|
614
|
+
});
|
|
615
|
+
test('single measure', () => {
|
|
616
|
+
expect('source: aa is a { measure: x is count() }').toTranslate();
|
|
617
|
+
});
|
|
618
|
+
test('multiple measures', () => {
|
|
619
|
+
expect(`
|
|
587
620
|
source: aa is a {
|
|
588
621
|
measure:
|
|
589
622
|
x is count()
|
|
590
623
|
y is x * x
|
|
591
624
|
}
|
|
592
|
-
`));
|
|
593
|
-
|
|
594
|
-
test('
|
|
625
|
+
`).toTranslate();
|
|
626
|
+
});
|
|
627
|
+
test('single where', () => {
|
|
628
|
+
expect('source: aa is a { where: ai > 10 }').toTranslate();
|
|
629
|
+
});
|
|
630
|
+
test('multiple where', () => {
|
|
631
|
+
expect(`
|
|
595
632
|
source: aa is a {
|
|
596
633
|
where:
|
|
597
634
|
ai > 10,
|
|
598
635
|
af < 1000
|
|
599
636
|
}
|
|
600
|
-
`));
|
|
601
|
-
|
|
637
|
+
`).toTranslate();
|
|
638
|
+
});
|
|
639
|
+
test('where clause can use the join namespace in source refined query', () => {
|
|
640
|
+
expect(`
|
|
602
641
|
source: flights is table('malloytest.flights') + {
|
|
603
642
|
query: boo is {
|
|
604
643
|
join_one: carriers is table('malloytest.carriers') on carrier = carriers.code
|
|
@@ -606,61 +645,97 @@ describe('source properties', () => {
|
|
|
606
645
|
group_by: carriers.nickname
|
|
607
646
|
aggregate: flight_count is count()
|
|
608
647
|
}
|
|
609
|
-
}`));
|
|
648
|
+
}`).toTranslate();
|
|
649
|
+
});
|
|
610
650
|
describe('joins', () => {
|
|
611
|
-
test('with',
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
test('
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
test('
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
test('
|
|
621
|
-
|
|
651
|
+
test('with', () => {
|
|
652
|
+
expect('source: x is a { join_one: b with astr }').toTranslate();
|
|
653
|
+
});
|
|
654
|
+
test('with', () => {
|
|
655
|
+
expect((0, test_translator_1.model) `source: x is a { join_one: y is b with astr }`).toTranslate();
|
|
656
|
+
});
|
|
657
|
+
test('with dotted ref', () => {
|
|
658
|
+
expect((0, test_translator_1.model) `source: x is ab { join_one: xz is a with b.astr }`).toTranslate();
|
|
659
|
+
});
|
|
660
|
+
test('one on', () => {
|
|
661
|
+
expect((0, test_translator_1.model) `source: x is a { join_one: b on astr = b.astr }`).toTranslate();
|
|
662
|
+
});
|
|
663
|
+
test('one is on', () => {
|
|
664
|
+
expect('source: x is a { join_one: y is b on astr = y.astr }').toTranslate();
|
|
665
|
+
});
|
|
666
|
+
test('many on', () => {
|
|
667
|
+
expect('source: nab is a { join_many: b on astr = b.astr }').toTranslate();
|
|
668
|
+
});
|
|
669
|
+
test('many is on', () => {
|
|
670
|
+
expect('source: y is a { join_many: x is b on astr = x.astr }').toTranslate();
|
|
671
|
+
});
|
|
672
|
+
test('cross', () => {
|
|
673
|
+
expect('source: nab is a { join_cross: b }').toTranslate();
|
|
674
|
+
});
|
|
675
|
+
test('cross is', () => {
|
|
676
|
+
expect('source: nab is a { join_cross: xb is b }').toTranslate();
|
|
677
|
+
});
|
|
678
|
+
test('cross on', () => {
|
|
679
|
+
expect('source: nab is a { join_cross: b on true}').toTranslate();
|
|
680
|
+
});
|
|
681
|
+
test('multiple joins', () => {
|
|
682
|
+
expect(`
|
|
622
683
|
source: nab is a {
|
|
623
684
|
join_one:
|
|
624
685
|
b with astr,
|
|
625
686
|
br is b with astr
|
|
626
687
|
}
|
|
627
|
-
`));
|
|
688
|
+
`).toTranslate();
|
|
689
|
+
});
|
|
628
690
|
test('with requires primary key', () => {
|
|
629
691
|
expect((0, test_translator_1.markSource) `
|
|
630
692
|
source: nb is b {
|
|
631
693
|
join_one: ${"bb is table('aTable') with astr"}
|
|
632
694
|
}
|
|
633
|
-
`).
|
|
695
|
+
`).translationToFailWith('join_one: Cannot use with unless source has a primary key');
|
|
634
696
|
});
|
|
635
697
|
});
|
|
636
|
-
test('primary_key',
|
|
637
|
-
|
|
698
|
+
test('primary_key', () => {
|
|
699
|
+
expect('source: c is a { primary_key: ai }').toTranslate();
|
|
700
|
+
});
|
|
701
|
+
test('rename', () => {
|
|
702
|
+
expect('source: c is a { rename: nn is ai }').toTranslate();
|
|
703
|
+
});
|
|
638
704
|
test('accept single', () => {
|
|
639
705
|
const onlyAstr = new test_translator_1.TestTranslator('source: c is a { accept: astr }');
|
|
640
|
-
expect(onlyAstr).
|
|
706
|
+
expect(onlyAstr).toTranslate();
|
|
641
707
|
const c = onlyAstr.getSourceDef('c');
|
|
642
708
|
if (c) {
|
|
643
709
|
expect(c.fields.length).toBe(1);
|
|
644
710
|
}
|
|
645
711
|
});
|
|
646
|
-
test('accept multi',
|
|
712
|
+
test('accept multi', () => {
|
|
713
|
+
expect('source: c is a { accept: astr, af }').toTranslate();
|
|
714
|
+
});
|
|
647
715
|
test('except single', () => {
|
|
648
716
|
const noAstr = new test_translator_1.TestTranslator('source: c is a { except: astr }');
|
|
649
|
-
expect(noAstr).
|
|
717
|
+
expect(noAstr).toTranslate();
|
|
650
718
|
const c = noAstr.getSourceDef('c');
|
|
651
719
|
if (c) {
|
|
652
720
|
const foundAstr = c.fields.find(f => f.name === 'astr');
|
|
653
721
|
expect(foundAstr).toBeUndefined();
|
|
654
722
|
}
|
|
655
723
|
});
|
|
656
|
-
test('except multi',
|
|
657
|
-
|
|
658
|
-
|
|
724
|
+
test('except multi', () => {
|
|
725
|
+
expect('source: c is a { except: astr, af }').toTranslate();
|
|
726
|
+
});
|
|
727
|
+
test('explore-query', () => {
|
|
728
|
+
expect('source: c is a {query: q is { group_by: astr } }').toTranslate();
|
|
729
|
+
});
|
|
730
|
+
test('refined explore-query', () => {
|
|
731
|
+
expect(`
|
|
659
732
|
source: abNew is ab {
|
|
660
733
|
query: for1 is aturtle {? ai = 1 }
|
|
661
734
|
}
|
|
662
|
-
`));
|
|
663
|
-
|
|
735
|
+
`).toTranslate();
|
|
736
|
+
});
|
|
737
|
+
test('chained explore-query', () => {
|
|
738
|
+
expect(`
|
|
664
739
|
source: c is a {
|
|
665
740
|
query: chain is {
|
|
666
741
|
group_by: astr
|
|
@@ -669,40 +744,69 @@ describe('source properties', () => {
|
|
|
669
744
|
project: *
|
|
670
745
|
}
|
|
671
746
|
}
|
|
672
|
-
`));
|
|
673
|
-
|
|
747
|
+
`).toTranslate();
|
|
748
|
+
});
|
|
749
|
+
test('multiple explore-query', () => {
|
|
750
|
+
expect(`
|
|
674
751
|
source: abNew is ab {
|
|
675
752
|
query:
|
|
676
753
|
q1 is { group_by: astr },
|
|
677
754
|
q2 is { group_by: ai }
|
|
678
755
|
}
|
|
679
|
-
`));
|
|
756
|
+
`).toTranslate();
|
|
757
|
+
});
|
|
680
758
|
});
|
|
681
759
|
describe('qops', () => {
|
|
682
|
-
test('group by single',
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
test(
|
|
686
|
-
|
|
760
|
+
test('group by single', () => {
|
|
761
|
+
expect('query: a->{ group_by: astr }').toTranslate();
|
|
762
|
+
});
|
|
763
|
+
test("group_by x is x'", () => {
|
|
764
|
+
expect('query: a->{ group_by: ai is ai/2 }').toTranslate();
|
|
765
|
+
});
|
|
766
|
+
test('group by multiple', () => {
|
|
767
|
+
expect('query: a->{ group_by: astr,ai }').toTranslate();
|
|
768
|
+
});
|
|
769
|
+
test('aggregate single', () => {
|
|
770
|
+
expect('query: a->{ aggregate: num is count() }').toTranslate();
|
|
771
|
+
});
|
|
772
|
+
test('aggregate multiple', () => {
|
|
773
|
+
expect(`
|
|
687
774
|
query: a->{
|
|
688
775
|
aggregate: num is count(), total is sum(ai)
|
|
689
776
|
}
|
|
690
|
-
`));
|
|
691
|
-
|
|
692
|
-
test('project
|
|
693
|
-
|
|
694
|
-
|
|
777
|
+
`).toTranslate();
|
|
778
|
+
});
|
|
779
|
+
test('project ref', () => {
|
|
780
|
+
expect('query:ab->{ project: b.astr }').toTranslate();
|
|
781
|
+
});
|
|
782
|
+
test('project *', () => {
|
|
783
|
+
expect('query:ab->{ project: * }').toTranslate();
|
|
784
|
+
});
|
|
785
|
+
test('project def', () => {
|
|
786
|
+
expect('query:ab->{ project: one is 1 }').toTranslate();
|
|
787
|
+
});
|
|
788
|
+
test('project multiple', () => {
|
|
789
|
+
expect(`
|
|
695
790
|
query: a->{
|
|
696
791
|
project: one is 1, astr
|
|
697
792
|
}
|
|
698
|
-
`));
|
|
699
|
-
|
|
700
|
-
test('index
|
|
701
|
-
|
|
702
|
-
|
|
793
|
+
`).toTranslate();
|
|
794
|
+
});
|
|
795
|
+
test('index single', () => {
|
|
796
|
+
expect('query:a->{index: astr}').toTranslate();
|
|
797
|
+
});
|
|
798
|
+
test('index path', () => {
|
|
799
|
+
expect('query:ab->{index: ab.astr}').toTranslate();
|
|
800
|
+
});
|
|
801
|
+
test('index unique on path', () => {
|
|
802
|
+
expect('query:ab->{index: b.astr, ab.astr}').toTranslate();
|
|
803
|
+
});
|
|
804
|
+
test('index join.*', () => {
|
|
805
|
+
expect('query:ab->{index: ab.*}').toTranslate();
|
|
806
|
+
});
|
|
703
807
|
test('index multiple', () => {
|
|
704
808
|
const model = new test_translator_1.TestTranslator('query:a->{index: af, astr}');
|
|
705
|
-
expect(model).
|
|
809
|
+
expect(model).toTranslate();
|
|
706
810
|
const q = model.getQuery(0);
|
|
707
811
|
expect(q).toBeDefined();
|
|
708
812
|
if (q) {
|
|
@@ -713,7 +817,7 @@ describe('qops', () => {
|
|
|
713
817
|
});
|
|
714
818
|
test('index star', () => {
|
|
715
819
|
const model = new test_translator_1.TestTranslator('query:a->{index: *, astr}');
|
|
716
|
-
expect(model).
|
|
820
|
+
expect(model).toTranslate();
|
|
717
821
|
const q = model.getQuery(0);
|
|
718
822
|
expect(q).toBeDefined();
|
|
719
823
|
if (q) {
|
|
@@ -722,12 +826,18 @@ describe('qops', () => {
|
|
|
722
826
|
expect(index.fields).toEqual(['*', 'astr']);
|
|
723
827
|
}
|
|
724
828
|
});
|
|
725
|
-
test('index by',
|
|
726
|
-
|
|
727
|
-
|
|
829
|
+
test('index by', () => {
|
|
830
|
+
expect('query:a->{index: * by ai}').toTranslate();
|
|
831
|
+
});
|
|
832
|
+
test('index sampled', () => {
|
|
833
|
+
expect('query:a->{index: *; sample: true}').toTranslate();
|
|
834
|
+
});
|
|
835
|
+
test('index unsampled', () => {
|
|
836
|
+
expect('query:a->{index: *; sample: false}').toTranslate();
|
|
837
|
+
});
|
|
728
838
|
test('index sample-percent', () => {
|
|
729
839
|
const model = new test_translator_1.TestTranslator('query:a->{index: *; sample: 42%}');
|
|
730
|
-
expect(model).
|
|
840
|
+
expect(model).toTranslate();
|
|
731
841
|
const q = model.getQuery(0);
|
|
732
842
|
expect(q).toBeDefined();
|
|
733
843
|
if (q) {
|
|
@@ -738,35 +848,67 @@ describe('qops', () => {
|
|
|
738
848
|
}
|
|
739
849
|
}
|
|
740
850
|
});
|
|
741
|
-
test('index sample-rows',
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
test('top N
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
test('
|
|
748
|
-
|
|
851
|
+
test('index sample-rows', () => {
|
|
852
|
+
expect('query:a->{index: *; sample: 100000}').toTranslate();
|
|
853
|
+
});
|
|
854
|
+
test('top N', () => {
|
|
855
|
+
expect('query: a->{ top: 5; group_by: astr }').toTranslate();
|
|
856
|
+
});
|
|
857
|
+
test('top N by field', () => {
|
|
858
|
+
expect('query: a->{top: 5 by astr; group_by: astr}').toTranslate();
|
|
859
|
+
});
|
|
860
|
+
test('top N by expression', () => {
|
|
861
|
+
expect('query: ab->{top: 5 by ai + 1; group_by: ai}').toTranslate();
|
|
862
|
+
});
|
|
863
|
+
test('top N by field must be in the output space', () => expect('query: a->{top: 5 by af; group_by: astr}').translationToFailWith('Unknown field af in output space'));
|
|
864
|
+
test('limit N', () => {
|
|
865
|
+
expect('query: a->{ limit: 5; group_by: astr }').toTranslate();
|
|
866
|
+
});
|
|
867
|
+
test('order by', () => {
|
|
868
|
+
expect('query: a->{ order_by: astr; group_by: astr }').toTranslate();
|
|
869
|
+
});
|
|
870
|
+
test('order by preserved over refinement', () => {
|
|
871
|
+
expect(`
|
|
749
872
|
query: a1 is a -> { group_by: astr }
|
|
750
873
|
query: -> a1 { order_by: astr }
|
|
751
|
-
`));
|
|
752
|
-
|
|
753
|
-
test('order by
|
|
754
|
-
test('order by
|
|
755
|
-
|
|
756
|
-
|
|
874
|
+
`).toTranslate();
|
|
875
|
+
});
|
|
876
|
+
test('order by must be in the output space', () => expect('query: a -> { order_by: af; group_by: astr }').translationToFailWith('Unknown field af in output space'));
|
|
877
|
+
test('order by asc', () => {
|
|
878
|
+
expect('query: a->{ order_by: astr asc; group_by: astr }').toTranslate();
|
|
879
|
+
});
|
|
880
|
+
test('order by desc', () => {
|
|
881
|
+
expect('query: a->{ order_by: astr desc; group_by: astr }').toTranslate();
|
|
882
|
+
});
|
|
883
|
+
test('order by N', () => {
|
|
884
|
+
expect('query: a->{ order_by: 1 asc; group_by: astr }').toTranslate();
|
|
885
|
+
});
|
|
886
|
+
test('order by multiple', () => {
|
|
887
|
+
expect(`
|
|
757
888
|
query: a->{
|
|
758
889
|
order_by: 1 asc, af desc
|
|
759
890
|
group_by: astr, af
|
|
760
891
|
}
|
|
761
|
-
`));
|
|
762
|
-
|
|
763
|
-
test('
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
test('
|
|
892
|
+
`).toTranslate();
|
|
893
|
+
});
|
|
894
|
+
test('where single', () => {
|
|
895
|
+
expect('query:a->{ group_by: astr; where: af > 10 }').toTranslate();
|
|
896
|
+
});
|
|
897
|
+
test('having single', () => {
|
|
898
|
+
expect('query:ab->{ aggregate: acount; group_by: astr; having: acount > 10 }').toTranslate();
|
|
899
|
+
});
|
|
900
|
+
test('compound having still works', () => {
|
|
901
|
+
expect('query:ab->{ aggregate: acount; having: acount > 10 and acount < 100 }').toTranslate();
|
|
902
|
+
});
|
|
903
|
+
test('compound aggregate still works', () => {
|
|
904
|
+
expect('query:ab->{ aggregate: thing is acount > 10 and acount < 100 }').toTranslate();
|
|
905
|
+
});
|
|
906
|
+
test('where multiple', () => {
|
|
907
|
+
expect("query:a->{ group_by: astr; where: af > 10,astr~'a%' }").toTranslate();
|
|
908
|
+
});
|
|
767
909
|
test('filters preserve source formatting in code:', () => {
|
|
768
910
|
const model = new test_translator_1.TestTranslator("source: notb is a + { where: astr != 'b' }");
|
|
769
|
-
expect(model).
|
|
911
|
+
expect(model).toTranslate();
|
|
770
912
|
const notb = model.getSourceDef('notb');
|
|
771
913
|
expect(notb).toBeDefined();
|
|
772
914
|
if (notb) {
|
|
@@ -779,7 +921,7 @@ describe('qops', () => {
|
|
|
779
921
|
});
|
|
780
922
|
test('field expressions preserve source formatting in code:', () => {
|
|
781
923
|
const model = new test_translator_1.TestTranslator('source: notb is a + { dimension: d is 1 + 2 }');
|
|
782
|
-
expect(model).
|
|
924
|
+
expect(model).toTranslate();
|
|
783
925
|
const notb = model.getSourceDef('notb');
|
|
784
926
|
expect(notb).toBeDefined();
|
|
785
927
|
if (notb) {
|
|
@@ -791,21 +933,27 @@ describe('qops', () => {
|
|
|
791
933
|
}
|
|
792
934
|
}
|
|
793
935
|
});
|
|
794
|
-
test('nest single',
|
|
936
|
+
test('nest single', () => {
|
|
937
|
+
expect(`
|
|
795
938
|
query: a->{
|
|
796
939
|
group_by: ai
|
|
797
940
|
nest: nestbystr is { group_by: astr; aggregate: N is count() }
|
|
798
941
|
}
|
|
799
|
-
`));
|
|
800
|
-
|
|
942
|
+
`).toTranslate();
|
|
943
|
+
});
|
|
944
|
+
test('nest multiple', () => {
|
|
945
|
+
expect(`
|
|
801
946
|
query: a->{
|
|
802
947
|
group_by: ai
|
|
803
948
|
nest:
|
|
804
949
|
nestbystr is { group_by: astr; aggregate: N is count() },
|
|
805
950
|
renest is { group_by: astr; aggregate: N is count() }
|
|
806
951
|
}
|
|
807
|
-
`));
|
|
808
|
-
|
|
952
|
+
`).toTranslate();
|
|
953
|
+
});
|
|
954
|
+
test('nest ref', () => {
|
|
955
|
+
expect('query: ab->{group_by: ai; nest: aturtle}').toTranslate();
|
|
956
|
+
});
|
|
809
957
|
test('refine query with extended source', () => {
|
|
810
958
|
const m = new test_translator_1.TestTranslator(`
|
|
811
959
|
source: nab is ab {
|
|
@@ -815,7 +963,7 @@ describe('qops', () => {
|
|
|
815
963
|
}
|
|
816
964
|
query: nab -> xturtle + { aggregate: aratio }
|
|
817
965
|
`);
|
|
818
|
-
expect(m).
|
|
966
|
+
expect(m).toTranslate();
|
|
819
967
|
const t = m.translate();
|
|
820
968
|
if (t.translated) {
|
|
821
969
|
const q = t.translated.queryList[0].pipeline[0];
|
|
@@ -839,7 +987,7 @@ describe('qops', () => {
|
|
|
839
987
|
aggregate: aratio
|
|
840
988
|
}
|
|
841
989
|
`);
|
|
842
|
-
expect(m).
|
|
990
|
+
expect(m).toTranslate();
|
|
843
991
|
const t = m.translate();
|
|
844
992
|
if (t.translated) {
|
|
845
993
|
const q = t.translated.queryList[0].pipeline[0];
|
|
@@ -863,7 +1011,7 @@ describe('qops', () => {
|
|
|
863
1011
|
group_by: foo is bb.astr
|
|
864
1012
|
}
|
|
865
1013
|
`);
|
|
866
|
-
expect(m).
|
|
1014
|
+
expect(m).toTranslate();
|
|
867
1015
|
const t = m.translate();
|
|
868
1016
|
if (t.translated) {
|
|
869
1017
|
const q = t.translated.queryList[0].pipeline[0];
|
|
@@ -878,10 +1026,20 @@ describe('qops', () => {
|
|
|
878
1026
|
});
|
|
879
1027
|
});
|
|
880
1028
|
describe('literals', () => {
|
|
881
|
-
test('integer',
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
test('string
|
|
1029
|
+
test('integer', () => {
|
|
1030
|
+
expect((0, test_translator_1.expr) `42`).toTranslate();
|
|
1031
|
+
});
|
|
1032
|
+
test('string', () => {
|
|
1033
|
+
expect((0, test_translator_1.expr) `'fortywo-two'`).toTranslate();
|
|
1034
|
+
});
|
|
1035
|
+
test('string with quoted quote', () => {
|
|
1036
|
+
const str = "'Isn" + '\\' + "'t this nice'";
|
|
1037
|
+
expect(new test_translator_1.BetaExpression(str)).toTranslate();
|
|
1038
|
+
});
|
|
1039
|
+
test('string with quoted backslash', () => {
|
|
1040
|
+
const str = "'Is " + '\\' + '\\' + " nice'";
|
|
1041
|
+
expect(new test_translator_1.BetaExpression(str)).toTranslate();
|
|
1042
|
+
});
|
|
885
1043
|
const literalTimes = [
|
|
886
1044
|
['@1960', 'date', 'year', { literal: '1960-01-01' }],
|
|
887
1045
|
['@1960-Q2', 'date', 'quarter', { literal: '1960-04-01' }],
|
|
@@ -925,7 +1083,7 @@ describe('literals', () => {
|
|
|
925
1083
|
];
|
|
926
1084
|
test.each(literalTimes)('%s', (expr, timeType, timeframe, result) => {
|
|
927
1085
|
const exprModel = new test_translator_1.BetaExpression(expr);
|
|
928
|
-
expect(exprModel).
|
|
1086
|
+
expect(exprModel).toTranslate();
|
|
929
1087
|
const ir = exprModel.generated();
|
|
930
1088
|
expect(ir.dataType).toEqual(timeType);
|
|
931
1089
|
if (timeframe) {
|
|
@@ -949,7 +1107,7 @@ describe('literals', () => {
|
|
|
949
1107
|
];
|
|
950
1108
|
test.each(morphicLiterals)('morphic value for %s is %s', (expr, morphic) => {
|
|
951
1109
|
const exprModel = new test_translator_1.BetaExpression(expr);
|
|
952
|
-
expect(exprModel).
|
|
1110
|
+
expect(exprModel).toTranslate();
|
|
953
1111
|
const ir = exprModel.generated();
|
|
954
1112
|
const morphTo = ir.morphic && ir.morphic['timestamp'];
|
|
955
1113
|
if (morphic) {
|
|
@@ -962,13 +1120,27 @@ describe('literals', () => {
|
|
|
962
1120
|
expect(morphTo).toBeUndefined();
|
|
963
1121
|
}
|
|
964
1122
|
});
|
|
965
|
-
test('minute+locale',
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
test('
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
test('
|
|
1123
|
+
test('minute+locale', () => {
|
|
1124
|
+
expect((0, test_translator_1.expr) `@1960-06-30 10:30[America/Los_Angeles]`).toTranslate();
|
|
1125
|
+
});
|
|
1126
|
+
test('second 8601', () => {
|
|
1127
|
+
expect((0, test_translator_1.expr) `@1960-06-30T10:30:31`).toTranslate();
|
|
1128
|
+
});
|
|
1129
|
+
test('null', () => {
|
|
1130
|
+
expect((0, test_translator_1.expr) `null`).toTranslate();
|
|
1131
|
+
});
|
|
1132
|
+
test('now', () => {
|
|
1133
|
+
expect((0, test_translator_1.expr) `now`).toTranslate();
|
|
1134
|
+
});
|
|
1135
|
+
test('true', () => {
|
|
1136
|
+
expect((0, test_translator_1.expr) `true`).toTranslate();
|
|
1137
|
+
});
|
|
1138
|
+
test('false', () => {
|
|
1139
|
+
expect((0, test_translator_1.expr) `false`).toTranslate();
|
|
1140
|
+
});
|
|
1141
|
+
test('regex', () => {
|
|
1142
|
+
expect((0, test_translator_1.expr) `r'RegularExpression'`).toTranslate();
|
|
1143
|
+
});
|
|
972
1144
|
});
|
|
973
1145
|
describe('expressions', () => {
|
|
974
1146
|
describe('timeframes', () => {
|
|
@@ -983,69 +1155,115 @@ describe('expressions', () => {
|
|
|
983
1155
|
'year',
|
|
984
1156
|
];
|
|
985
1157
|
test.each(timeframes.map(x => [x]))('truncate %s', unit => {
|
|
986
|
-
expect(new test_translator_1.BetaExpression(`ats.${unit}`)).
|
|
1158
|
+
expect(new test_translator_1.BetaExpression(`ats.${unit}`)).toParse();
|
|
987
1159
|
});
|
|
988
1160
|
// mtoy todo units missing: implement, or document
|
|
989
1161
|
const diffable = ['second', 'minute', 'hour', 'day'];
|
|
990
1162
|
test.each(diffable.map(x => [x]))('timestamp difference - %s', unit => {
|
|
991
|
-
expect(new test_translator_1.BetaExpression(`${unit}(@2021 to ats)`)).
|
|
1163
|
+
expect(new test_translator_1.BetaExpression(`${unit}(@2021 to ats)`)).toParse();
|
|
992
1164
|
});
|
|
993
1165
|
test.each(diffable.map(x => [x]))('timestamp difference - %s', unit => {
|
|
994
|
-
expect(new test_translator_1.BetaExpression(`${unit}(ats to @2030)`)).
|
|
1166
|
+
expect(new test_translator_1.BetaExpression(`${unit}(ats to @2030)`)).toParse();
|
|
995
1167
|
});
|
|
996
1168
|
});
|
|
997
|
-
test('field name',
|
|
998
|
-
|
|
1169
|
+
test('field name', () => {
|
|
1170
|
+
expect((0, test_translator_1.expr) `astr`).toTranslate();
|
|
1171
|
+
});
|
|
1172
|
+
test('function call', () => {
|
|
1173
|
+
expect((0, test_translator_1.expr) `concat('foo')`).toTranslate();
|
|
1174
|
+
});
|
|
999
1175
|
describe('operators', () => {
|
|
1000
|
-
test('addition',
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
test('
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
test('
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
test('
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
test('
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
test('
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
test('
|
|
1176
|
+
test('addition', () => {
|
|
1177
|
+
expect((0, test_translator_1.expr) `42 + 7`).toTranslate();
|
|
1178
|
+
});
|
|
1179
|
+
test('subtraction', () => {
|
|
1180
|
+
expect((0, test_translator_1.expr) `42 - 7`).toTranslate();
|
|
1181
|
+
});
|
|
1182
|
+
test('multiplication', () => {
|
|
1183
|
+
expect((0, test_translator_1.expr) `42 * 7`).toTranslate();
|
|
1184
|
+
});
|
|
1185
|
+
test('mod', () => {
|
|
1186
|
+
expect((0, test_translator_1.expr) `42 % 7`).toTranslate();
|
|
1187
|
+
});
|
|
1188
|
+
test('division', () => {
|
|
1189
|
+
expect((0, test_translator_1.expr) `42 / 7`).toTranslate();
|
|
1190
|
+
});
|
|
1191
|
+
test('unary negation', () => {
|
|
1192
|
+
expect((0, test_translator_1.expr) `- ai`).toTranslate();
|
|
1193
|
+
});
|
|
1194
|
+
test('equal', () => {
|
|
1195
|
+
expect((0, test_translator_1.expr) `42 = 7`).toTranslate();
|
|
1196
|
+
});
|
|
1197
|
+
test('not equal', () => {
|
|
1198
|
+
expect((0, test_translator_1.expr) `42 != 7`).toTranslate();
|
|
1199
|
+
});
|
|
1200
|
+
test('greater than', () => {
|
|
1201
|
+
expect((0, test_translator_1.expr) `42 > 7`).toTranslate();
|
|
1202
|
+
});
|
|
1203
|
+
test('greater than or equal', () => {
|
|
1204
|
+
expect((0, test_translator_1.expr) `42 >= 7`).toTranslate();
|
|
1205
|
+
});
|
|
1206
|
+
test('less than or equal', () => {
|
|
1207
|
+
expect((0, test_translator_1.expr) `42 <= 7`).toTranslate();
|
|
1208
|
+
});
|
|
1209
|
+
test('less than', () => {
|
|
1210
|
+
expect((0, test_translator_1.expr) `42 < 7`).toTranslate();
|
|
1211
|
+
});
|
|
1212
|
+
test('match', () => {
|
|
1213
|
+
expect((0, test_translator_1.expr) `'forty-two' ~ 'fifty-four'`).toTranslate();
|
|
1214
|
+
});
|
|
1215
|
+
test('not match', () => {
|
|
1216
|
+
expect((0, test_translator_1.expr) `'forty-two' !~ 'fifty-four'`).toTranslate();
|
|
1217
|
+
});
|
|
1218
|
+
test('apply', () => {
|
|
1219
|
+
expect((0, test_translator_1.expr) `'forty-two' ? 'fifty-four'`).toTranslate();
|
|
1220
|
+
});
|
|
1221
|
+
test('not', () => {
|
|
1222
|
+
expect((0, test_translator_1.expr) `not true`).toTranslate();
|
|
1223
|
+
});
|
|
1224
|
+
test('and', () => {
|
|
1225
|
+
expect((0, test_translator_1.expr) `true and false`).toTranslate();
|
|
1226
|
+
});
|
|
1227
|
+
test('or', () => {
|
|
1228
|
+
expect((0, test_translator_1.expr) `true or false`).toTranslate();
|
|
1229
|
+
});
|
|
1230
|
+
test('null-check (??)', () => {
|
|
1231
|
+
expect((0, test_translator_1.expr) `ai ?? 7`).toTranslate();
|
|
1232
|
+
});
|
|
1019
1233
|
test('disallow date OP number', () => {
|
|
1020
|
-
expect(new test_translator_1.BetaExpression('@2001 = 7')).
|
|
1234
|
+
expect(new test_translator_1.BetaExpression('@2001 = 7')).translationToFailWith('Cannot compare a date to a number');
|
|
1021
1235
|
});
|
|
1022
1236
|
test('disallow date OP timestamp', () => {
|
|
1023
|
-
expect(new test_translator_1.BetaExpression('ad = ats')).
|
|
1237
|
+
expect(new test_translator_1.BetaExpression('ad = ats')).translationToFailWith('Cannot compare a date to a timestamp');
|
|
1024
1238
|
});
|
|
1025
1239
|
test('disallow interval from date to timestamp', () => {
|
|
1026
|
-
expect(new test_translator_1.BetaExpression('days(ad to ats)')).
|
|
1240
|
+
expect(new test_translator_1.BetaExpression('days(ad to ats)')).translationToFailWith('Cannot measure from date to timestamp');
|
|
1027
1241
|
});
|
|
1028
1242
|
test('comparison promotes date literal to timestamp', () => {
|
|
1029
|
-
expect(
|
|
1243
|
+
expect((0, test_translator_1.expr) `@2001 = ats`).toTranslate();
|
|
1030
1244
|
});
|
|
1031
1245
|
test('can apply range to date', () => {
|
|
1032
|
-
expect(
|
|
1246
|
+
expect((0, test_translator_1.expr) `ad ? @2001 for 1 day`).toTranslate();
|
|
1033
1247
|
});
|
|
1034
1248
|
const noOffset = ['second', 'minute', 'hour'];
|
|
1035
1249
|
test.each(noOffset.map(x => [x]))('disallow date delta %s', unit => {
|
|
1036
|
-
expect(new test_translator_1.BetaExpression(`ad + 10 ${unit}s`)).
|
|
1250
|
+
expect(new test_translator_1.BetaExpression(`ad + 10 ${unit}s`)).translationToFailWith(`Cannot offset date by ${unit}`);
|
|
1037
1251
|
});
|
|
1038
1252
|
});
|
|
1039
|
-
test('filtered measure',
|
|
1040
|
-
|
|
1253
|
+
test('filtered measure', () => {
|
|
1254
|
+
expect((0, test_translator_1.expr) `acount {? astr = 'why?' }`).toTranslate();
|
|
1255
|
+
});
|
|
1256
|
+
test('filtered ungrouped aggregate', () => {
|
|
1257
|
+
expect(`
|
|
1041
1258
|
query: a -> {
|
|
1042
1259
|
group_by: ai
|
|
1043
1260
|
aggregate: x is all(avg(ai)) { where: true }
|
|
1044
1261
|
}
|
|
1045
|
-
`));
|
|
1262
|
+
`).toTranslate();
|
|
1263
|
+
});
|
|
1046
1264
|
test('correctly flags filtered scalar', () => {
|
|
1047
1265
|
const e = new test_translator_1.BetaExpression('ai { where: true }');
|
|
1048
|
-
expect(e).
|
|
1266
|
+
expect(e).translationToFailWith('Filtered expression requires an aggregate computation');
|
|
1049
1267
|
});
|
|
1050
1268
|
test('correctly flags filtered analytic', () => {
|
|
1051
1269
|
expect((0, test_translator_1.markSource) `
|
|
@@ -1053,65 +1271,93 @@ describe('expressions', () => {
|
|
|
1053
1271
|
group_by: ai
|
|
1054
1272
|
calculate: l is lag(ai) { where: true }
|
|
1055
1273
|
}
|
|
1056
|
-
`).
|
|
1274
|
+
`).translationToFailWith('Filtered expression requires an aggregate computation');
|
|
1057
1275
|
});
|
|
1058
1276
|
describe('aggregate forms', () => {
|
|
1059
|
-
test('count',
|
|
1060
|
-
|
|
1061
|
-
|
|
1277
|
+
test('count', () => {
|
|
1278
|
+
expect((0, test_translator_1.expr) `count()`).toTranslate();
|
|
1279
|
+
});
|
|
1280
|
+
test('count distinct', () => {
|
|
1281
|
+
expect((0, test_translator_1.expr) `count(distinct astr)`).toTranslate();
|
|
1282
|
+
});
|
|
1283
|
+
test('join.count()', () => {
|
|
1284
|
+
expect((0, test_translator_1.expr) `b.count()`).toTranslate();
|
|
1285
|
+
});
|
|
1062
1286
|
for (const f of ['sum', 'min', 'max', 'avg']) {
|
|
1063
1287
|
const fOfT = `${f}(af)`;
|
|
1064
|
-
test(fOfT,
|
|
1288
|
+
test(fOfT, () => {
|
|
1289
|
+
expect(new test_translator_1.BetaExpression(fOfT)).toTranslate();
|
|
1290
|
+
});
|
|
1065
1291
|
if (f !== 'min' && f !== 'max') {
|
|
1066
1292
|
const joinDot = `b.af.${f}()`;
|
|
1067
|
-
test(joinDot,
|
|
1293
|
+
test(joinDot, () => {
|
|
1294
|
+
expect(new test_translator_1.BetaExpression(joinDot)).toTranslate();
|
|
1295
|
+
});
|
|
1068
1296
|
const joinAgg = `b.${f}(af)`;
|
|
1069
|
-
test(joinAgg,
|
|
1297
|
+
test(joinAgg, () => {
|
|
1298
|
+
expect(new test_translator_1.BetaExpression(joinAgg)).toTranslate();
|
|
1299
|
+
});
|
|
1070
1300
|
}
|
|
1071
1301
|
}
|
|
1072
1302
|
});
|
|
1073
1303
|
describe('pick statements', () => {
|
|
1074
|
-
test('full',
|
|
1304
|
+
test('full', () => {
|
|
1305
|
+
expect((0, test_translator_1.expr) `
|
|
1075
1306
|
pick 'the answer' when ai = 42
|
|
1076
1307
|
pick 'the questionable answer' when ai = 54
|
|
1077
1308
|
else 'random'
|
|
1078
|
-
`));
|
|
1079
|
-
|
|
1309
|
+
`).toTranslate();
|
|
1310
|
+
});
|
|
1311
|
+
test('applied', () => {
|
|
1312
|
+
expect((0, test_translator_1.expr) `
|
|
1080
1313
|
astr ?
|
|
1081
1314
|
pick 'the answer' when = '42'
|
|
1082
1315
|
pick 'the questionable answer' when = '54'
|
|
1083
1316
|
else 'random'
|
|
1084
|
-
`));
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
test('null branch
|
|
1090
|
-
|
|
1091
|
-
|
|
1317
|
+
`).toTranslate();
|
|
1318
|
+
});
|
|
1319
|
+
test('filtering', () => {
|
|
1320
|
+
expect((0, test_translator_1.expr) `astr ? pick 'missing value' when NULL`).toTranslate();
|
|
1321
|
+
});
|
|
1322
|
+
test('null branch with else', () => {
|
|
1323
|
+
expect("astr ? pick null when = '42' else 3").toReturnType('number');
|
|
1324
|
+
});
|
|
1325
|
+
test('null branch no else', () => {
|
|
1326
|
+
expect("astr ? pick null when = '42'").toReturnType('string');
|
|
1327
|
+
});
|
|
1328
|
+
test('null branch no apply', () => {
|
|
1329
|
+
expect('pick null when 1 = 1 else 3').toReturnType('number');
|
|
1330
|
+
});
|
|
1331
|
+
test('tiering', () => {
|
|
1332
|
+
expect((0, test_translator_1.expr) `
|
|
1092
1333
|
ai ?
|
|
1093
1334
|
pick 1 when < 10
|
|
1094
1335
|
pick 10 when < 100
|
|
1095
1336
|
pick 100 when < 1000
|
|
1096
1337
|
else 10000
|
|
1097
|
-
`));
|
|
1098
|
-
|
|
1338
|
+
`).toTranslate();
|
|
1339
|
+
});
|
|
1340
|
+
test('transforming', () => {
|
|
1341
|
+
expect((0, test_translator_1.expr) `
|
|
1099
1342
|
ai ?
|
|
1100
1343
|
pick 'small' when < 10
|
|
1101
1344
|
pick 'medium' when < 100
|
|
1102
1345
|
else 'large'
|
|
1103
|
-
`));
|
|
1104
|
-
|
|
1346
|
+
`).toTranslate();
|
|
1347
|
+
});
|
|
1348
|
+
test('when single values', () => {
|
|
1349
|
+
expect((0, test_translator_1.expr) `
|
|
1105
1350
|
ai ?
|
|
1106
1351
|
pick 'one' when 1
|
|
1107
1352
|
else 'a lot'
|
|
1108
|
-
`));
|
|
1353
|
+
`).toTranslate();
|
|
1354
|
+
});
|
|
1109
1355
|
test('n-ary without else', () => {
|
|
1110
|
-
expect(
|
|
1356
|
+
expect(`
|
|
1111
1357
|
source: na is a + { dimension: d is
|
|
1112
1358
|
pick 7 when true and true
|
|
1113
1359
|
}
|
|
1114
|
-
`).
|
|
1360
|
+
`).translationToFailWith("pick incomplete, missing 'else'", "Cannot define 'd', value has unknown type");
|
|
1115
1361
|
});
|
|
1116
1362
|
test('n-ary with mismatch when clauses', () => {
|
|
1117
1363
|
expect((0, test_translator_1.markSource) `
|
|
@@ -1120,7 +1366,7 @@ describe('expressions', () => {
|
|
|
1120
1366
|
pick '7' when true or true
|
|
1121
1367
|
else 7
|
|
1122
1368
|
}
|
|
1123
|
-
`).
|
|
1369
|
+
`).translationToFailWith("pick type 'string', expected 'number'", "Cannot define 'd', value has unknown type");
|
|
1124
1370
|
});
|
|
1125
1371
|
test('n-ary with mismatched else clause', () => {
|
|
1126
1372
|
expect((0, test_translator_1.markSource) `
|
|
@@ -1128,35 +1374,35 @@ describe('expressions', () => {
|
|
|
1128
1374
|
pick 7 when true and true
|
|
1129
1375
|
else '7'
|
|
1130
1376
|
}
|
|
1131
|
-
`).
|
|
1377
|
+
`).translationToFailWith("else type 'string', expected 'number'", "Cannot define 'd', value has unknown type");
|
|
1132
1378
|
});
|
|
1133
1379
|
test('applied else mismatch', () => {
|
|
1134
1380
|
expect((0, test_translator_1.markSource) `
|
|
1135
1381
|
source: na is a + { dimension: d is
|
|
1136
1382
|
7 ? pick 7 when 7 else 'not seven'
|
|
1137
1383
|
}
|
|
1138
|
-
`).
|
|
1384
|
+
`).translationToFailWith("else type 'string', expected 'number'", "Cannot define 'd', value has unknown type");
|
|
1139
1385
|
});
|
|
1140
1386
|
test('applied default mismatch', () => {
|
|
1141
1387
|
expect((0, test_translator_1.markSource) `
|
|
1142
1388
|
source: na is a + { dimension: d is
|
|
1143
1389
|
7 ? pick 'seven' when 7
|
|
1144
1390
|
}
|
|
1145
|
-
`).
|
|
1391
|
+
`).translationToFailWith("pick default type 'number', expected 'string'", "Cannot define 'd', value has unknown type");
|
|
1146
1392
|
});
|
|
1147
1393
|
test('applied when mismatch', () => {
|
|
1148
1394
|
expect((0, test_translator_1.markSource) `
|
|
1149
1395
|
source: na is a + { dimension: d is
|
|
1150
1396
|
7 ? pick 'seven' when 7 pick 6 when 6
|
|
1151
1397
|
}
|
|
1152
|
-
`).
|
|
1398
|
+
`).translationToFailWith("pick type 'number', expected 'string'", "Cannot define 'd', value has unknown type");
|
|
1153
1399
|
});
|
|
1154
1400
|
});
|
|
1155
1401
|
test('paren and applied div', () => {
|
|
1156
1402
|
var _a, _b;
|
|
1157
1403
|
const modelSrc = 'query: z is a -> { group_by: x is 1+(3/4) }';
|
|
1158
1404
|
const m = new test_translator_1.TestTranslator(modelSrc);
|
|
1159
|
-
expect(m).
|
|
1405
|
+
expect(m).toTranslate();
|
|
1160
1406
|
const queryDef = (_b = (_a = m.translate()) === null || _a === void 0 ? void 0 : _a.translated) === null || _b === void 0 ? void 0 : _b.modelDef.contents['z'];
|
|
1161
1407
|
expect(queryDef).toBeDefined();
|
|
1162
1408
|
expect(queryDef === null || queryDef === void 0 ? void 0 : queryDef.type).toBe('query');
|
|
@@ -1211,39 +1457,39 @@ describe('expressions', () => {
|
|
|
1211
1457
|
['astr', 'string'],
|
|
1212
1458
|
['abool', 'boolean'],
|
|
1213
1459
|
])('Can compare field %s (type %s) to NULL', (name, _datatype) => {
|
|
1214
|
-
expect(`${name} = NULL`).
|
|
1460
|
+
expect((0, test_translator_1.expr) `${name} = NULL`).toTranslate();
|
|
1215
1461
|
});
|
|
1216
1462
|
});
|
|
1217
1463
|
describe('unspported fields in schema', () => {
|
|
1218
1464
|
test('unsupported reference in result allowed', () => {
|
|
1219
1465
|
const uModel = new test_translator_1.TestTranslator('query: a->{ group_by: aun }');
|
|
1220
|
-
expect(uModel).
|
|
1466
|
+
expect(uModel).toTranslate();
|
|
1221
1467
|
});
|
|
1222
1468
|
test('unsupported reference can be compared to NULL', () => {
|
|
1223
1469
|
const uModel = new test_translator_1.TestTranslator('query: a->{ where: aun != NULL; project: * }');
|
|
1224
|
-
expect(uModel).
|
|
1470
|
+
expect(uModel).toTranslate();
|
|
1225
1471
|
});
|
|
1226
1472
|
test('flag unsupported equality', () => {
|
|
1227
1473
|
// because we don't know if the two unsupported types are comparable
|
|
1228
1474
|
const uModel = new test_translator_1.TestTranslator('query: ab->{ where: aun = b.aun project: * }');
|
|
1229
|
-
expect(uModel).
|
|
1475
|
+
expect(uModel).translationToFailWith('Unsupported type not allowed in expression');
|
|
1230
1476
|
});
|
|
1231
1477
|
test('flag unsupported compare', () => {
|
|
1232
1478
|
// because we don't know if the two unsupported types are comparable
|
|
1233
1479
|
const uModel = new test_translator_1.TestTranslator('query: ab->{ where: aun > b.aun project: * }');
|
|
1234
|
-
expect(uModel).
|
|
1480
|
+
expect(uModel).translationToFailWith('Unsupported type not allowed in expression');
|
|
1235
1481
|
});
|
|
1236
1482
|
test('allow unsupported equality when raw types match', () => {
|
|
1237
1483
|
const uModel = new test_translator_1.TestTranslator('query: ab->{ where: aweird = b.aweird project: * }');
|
|
1238
|
-
expect(uModel).
|
|
1484
|
+
expect(uModel).toTranslate();
|
|
1239
1485
|
});
|
|
1240
1486
|
test('flag not applied to unsupported', () => {
|
|
1241
1487
|
const uModel = new test_translator_1.TestTranslator('source: x is a { dimension: notUn is not aun }');
|
|
1242
|
-
expect(uModel).
|
|
1488
|
+
expect(uModel).translationToFailWith("'not' Can't use type unsupported");
|
|
1243
1489
|
});
|
|
1244
1490
|
test('allow unsupported to be cast', () => {
|
|
1245
1491
|
const uModel = new test_translator_1.TestTranslator('source: x is a { dimension: notUn is aun::string }');
|
|
1246
|
-
expect(uModel).
|
|
1492
|
+
expect(uModel).toTranslate();
|
|
1247
1493
|
});
|
|
1248
1494
|
});
|
|
1249
1495
|
describe('error handling', () => {
|
|
@@ -1252,7 +1498,7 @@ describe('error handling', () => {
|
|
|
1252
1498
|
source: flights is table('malloytest.flights') {
|
|
1253
1499
|
query: carrier is { group_by: carrier }
|
|
1254
1500
|
}
|
|
1255
|
-
`).
|
|
1501
|
+
`).translationToFailWith("Cannot redefine 'carrier'");
|
|
1256
1502
|
});
|
|
1257
1503
|
test('redefine source', () => {
|
|
1258
1504
|
expect((0, test_translator_1.markSource) `
|
|
@@ -1262,55 +1508,70 @@ describe('error handling', () => {
|
|
|
1262
1508
|
source: airports is table('malloytest.airports') + {
|
|
1263
1509
|
primary_key: code
|
|
1264
1510
|
}
|
|
1265
|
-
`).
|
|
1511
|
+
`).translationToFailWith("Cannot redefine 'airports'");
|
|
1266
1512
|
});
|
|
1267
1513
|
test('query from undefined source', () => {
|
|
1268
|
-
expect((0, test_translator_1.markSource) `query: ${'x'}->{ project: y }`).
|
|
1514
|
+
expect((0, test_translator_1.markSource) `query: ${'x'}->{ project: y }`).translationToFailWith("Undefined source 'x'");
|
|
1269
1515
|
});
|
|
1270
1516
|
test('query with expression from undefined source', () => {
|
|
1271
1517
|
// Regression check: Once upon a time this died with an exception even
|
|
1272
1518
|
// when "query: x->{ group_by: y}" (above) generated the correct error.
|
|
1273
|
-
expect((0, test_translator_1.markSource) `query: ${'x'}->{ project: y is z / 2 }`).
|
|
1519
|
+
expect((0, test_translator_1.markSource) `query: ${'x'}->{ project: y is z / 2 }`).translationToFailWith("Undefined source 'x'");
|
|
1274
1520
|
});
|
|
1275
1521
|
test('join reference before definition', () => {
|
|
1276
1522
|
expect((0, test_translator_1.markSource) `
|
|
1277
1523
|
source: newAB is a { join_one: newB is ${'bb'} on astring }
|
|
1278
1524
|
source: newB is b
|
|
1279
|
-
`).
|
|
1280
|
-
});
|
|
1281
|
-
test('non-rename rename',
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
test('
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
test('
|
|
1525
|
+
`).translationToFailWith("Undefined source 'bb'");
|
|
1526
|
+
});
|
|
1527
|
+
test('non-rename rename', () => {
|
|
1528
|
+
expect('source: na is a { rename: astr is astr }').translationToFailWith("Can't rename field to itself");
|
|
1529
|
+
});
|
|
1530
|
+
test('reference to field in its definition', () => {
|
|
1531
|
+
expect('source: na is a { dimension: ustr is UPPER(ustr) } ').translationToFailWith("Circular reference to 'ustr' in definition");
|
|
1532
|
+
});
|
|
1533
|
+
test('empty model', () => {
|
|
1534
|
+
expect('').toTranslate();
|
|
1535
|
+
});
|
|
1536
|
+
test('one line model ', () => {
|
|
1537
|
+
expect('\n').toTranslate();
|
|
1538
|
+
});
|
|
1539
|
+
test('query without fields', () => {
|
|
1540
|
+
expect('query: a -> { top: 5 }').translationToFailWith("Can't determine query type (group_by/aggregate/nest,project,index)");
|
|
1541
|
+
});
|
|
1542
|
+
test("refine can't change query type", () => {
|
|
1543
|
+
expect('query: ab -> aturtle { project: astr }').translationToFailWith('project: not legal in grouping query');
|
|
1544
|
+
});
|
|
1545
|
+
test('undefined field ref in query', () => {
|
|
1546
|
+
expect('query: ab -> { aggregate: xyzzy }').translationToFailWith("'xyzzy' is not defined");
|
|
1547
|
+
});
|
|
1288
1548
|
test('query on source with errors', () => {
|
|
1289
1549
|
expect((0, test_translator_1.markSource) `
|
|
1290
1550
|
source: na is a { join_one: ${'n'} on astr }
|
|
1291
|
-
`).
|
|
1551
|
+
`).translationToFailWith("Undefined source 'n'");
|
|
1292
1552
|
});
|
|
1293
1553
|
test('detect duplicate output field names', () => {
|
|
1294
|
-
expect((0, test_translator_1.markSource) `query: ab -> { group_by: astr, ${'astr'} }`).
|
|
1554
|
+
expect((0, test_translator_1.markSource) `query: ab -> { group_by: astr, ${'astr'} }`).translationToFailWith("Output already has a field named 'astr'");
|
|
1295
1555
|
});
|
|
1296
1556
|
test('detect join tail overlap existing ref', () => {
|
|
1297
|
-
expect((0, test_translator_1.markSource) `query: ab -> { group_by: astr, ${'b.astr'} }`).
|
|
1557
|
+
expect((0, test_translator_1.markSource) `query: ab -> { group_by: astr, ${'b.astr'} }`).translationToFailWith("Output already has a field named 'astr'");
|
|
1298
1558
|
});
|
|
1299
1559
|
test('undefined in expression with regex compare', () => {
|
|
1300
1560
|
expect(`
|
|
1301
1561
|
source: c is a {
|
|
1302
1562
|
dimension: d is meaning_of_life ~ r'(forty two|fifty four)'
|
|
1303
1563
|
}
|
|
1304
|
-
`).
|
|
1564
|
+
`).translationToFailWith("'meaning_of_life' is not defined");
|
|
1305
1565
|
});
|
|
1306
1566
|
test('detect output collision on join references', () => {
|
|
1307
1567
|
expect(`
|
|
1308
1568
|
query: ab -> {
|
|
1309
1569
|
group_by: astr, b.astr
|
|
1310
1570
|
}
|
|
1311
|
-
`).
|
|
1571
|
+
`).translationToFailWith("Output already has a field named 'astr'");
|
|
1312
1572
|
});
|
|
1313
|
-
test('rejoin a query is renamed',
|
|
1573
|
+
test('rejoin a query is renamed', () => {
|
|
1574
|
+
expect(`
|
|
1314
1575
|
source: querySrc is from(
|
|
1315
1576
|
table('malloytest.flights')->{
|
|
1316
1577
|
group_by: origin
|
|
@@ -1324,13 +1585,14 @@ describe('error handling', () => {
|
|
|
1324
1585
|
group_by: rejoin.nested.destination
|
|
1325
1586
|
}
|
|
1326
1587
|
}
|
|
1327
|
-
`));
|
|
1588
|
+
`).toTranslate();
|
|
1589
|
+
});
|
|
1328
1590
|
test('popping out of embedding when not embedded', () => {
|
|
1329
|
-
expect('}%').
|
|
1591
|
+
expect('}%').translationToFailWith(/extraneous input '}%' expecting/);
|
|
1330
1592
|
});
|
|
1331
1593
|
test('bad sql in sql block', () => {
|
|
1332
1594
|
const badModel = new test_translator_1.TestTranslator('sql: { select: """)""" }');
|
|
1333
|
-
expect(badModel).
|
|
1595
|
+
expect(badModel).toParse();
|
|
1334
1596
|
const needSchema = badModel.translate();
|
|
1335
1597
|
expect(needSchema.compileSQL).toBeDefined();
|
|
1336
1598
|
if (needSchema.compileSQL) {
|
|
@@ -1342,7 +1604,7 @@ describe('error handling', () => {
|
|
|
1342
1604
|
},
|
|
1343
1605
|
});
|
|
1344
1606
|
}
|
|
1345
|
-
expect(badModel).
|
|
1607
|
+
expect(badModel).translationToFailWith('Invalid SQL, ZZZZ');
|
|
1346
1608
|
});
|
|
1347
1609
|
});
|
|
1348
1610
|
function getSelectOneStruct(sqlBlock) {
|
|
@@ -1371,19 +1633,19 @@ describe('source locations', () => {
|
|
|
1371
1633
|
test('renamed source location', () => {
|
|
1372
1634
|
const source = (0, test_translator_1.markSource) `source: ${'na is a'}`;
|
|
1373
1635
|
const m = new test_translator_1.TestTranslator(source.code);
|
|
1374
|
-
expect(m).
|
|
1636
|
+
expect(m).toParse();
|
|
1375
1637
|
expect((0, test_translator_1.getExplore)(m.modelDef, 'na').location).toMatchObject(source.locations[0]);
|
|
1376
1638
|
});
|
|
1377
1639
|
test('refined source location', () => {
|
|
1378
1640
|
const source = (0, test_translator_1.markSource) `source: ${'na is a {}'}`;
|
|
1379
1641
|
const m = new test_translator_1.TestTranslator(source.code);
|
|
1380
|
-
expect(m).
|
|
1642
|
+
expect(m).toParse();
|
|
1381
1643
|
expect((0, test_translator_1.getExplore)(m.modelDef, 'na').location).toMatchObject(source.locations[0]);
|
|
1382
1644
|
});
|
|
1383
1645
|
test('location of defined dimension', () => {
|
|
1384
1646
|
const source = (0, test_translator_1.markSource) `source: na is a { dimension: ${'x is 1'} }`;
|
|
1385
1647
|
const m = new test_translator_1.TestTranslator(source.code);
|
|
1386
|
-
expect(m).
|
|
1648
|
+
expect(m).toTranslate();
|
|
1387
1649
|
const na = (0, test_translator_1.getExplore)(m.modelDef, 'na');
|
|
1388
1650
|
const x = (0, test_translator_1.getField)(na, 'x');
|
|
1389
1651
|
expect(x.location).toMatchObject(source.locations[0]);
|
|
@@ -1391,7 +1653,7 @@ describe('source locations', () => {
|
|
|
1391
1653
|
test('location of defined measure', () => {
|
|
1392
1654
|
const source = (0, test_translator_1.markSource) `source: na is a { measure: ${'x is count()'} }`;
|
|
1393
1655
|
const m = new test_translator_1.TestTranslator(source.code);
|
|
1394
|
-
expect(m).
|
|
1656
|
+
expect(m).toTranslate();
|
|
1395
1657
|
const na = (0, test_translator_1.getExplore)(m.modelDef, 'na');
|
|
1396
1658
|
const x = (0, test_translator_1.getField)(na, 'x');
|
|
1397
1659
|
expect(x.location).toMatchObject(source.locations[0]);
|
|
@@ -1399,7 +1661,7 @@ describe('source locations', () => {
|
|
|
1399
1661
|
test('location of defined query', () => {
|
|
1400
1662
|
const source = (0, test_translator_1.markSource) `source: na is a { query: ${'x is { group_by: y is 1 }'} }`;
|
|
1401
1663
|
const m = new test_translator_1.TestTranslator(source.code);
|
|
1402
|
-
expect(m).
|
|
1664
|
+
expect(m).toTranslate();
|
|
1403
1665
|
const na = (0, test_translator_1.getExplore)(m.modelDef, 'na');
|
|
1404
1666
|
const x = (0, test_translator_1.getField)(na, 'x');
|
|
1405
1667
|
expect(x.location).toMatchObject(source.locations[0]);
|
|
@@ -1412,7 +1674,7 @@ describe('source locations', () => {
|
|
|
1412
1674
|
}
|
|
1413
1675
|
}`;
|
|
1414
1676
|
const m = new test_translator_1.TestTranslator(source.code);
|
|
1415
|
-
expect(m).
|
|
1677
|
+
expect(m).toTranslate();
|
|
1416
1678
|
const na = (0, test_translator_1.getExplore)(m.modelDef, 'na');
|
|
1417
1679
|
const x = (0, test_translator_1.getQueryField)(na, 'x');
|
|
1418
1680
|
const y = (0, test_translator_1.getField)(x.pipeline[0], 'y');
|
|
@@ -1427,7 +1689,7 @@ describe('source locations', () => {
|
|
|
1427
1689
|
}
|
|
1428
1690
|
}`;
|
|
1429
1691
|
const m = new test_translator_1.TestTranslator(source.code);
|
|
1430
|
-
expect(m).
|
|
1692
|
+
expect(m).toTranslate();
|
|
1431
1693
|
const na = (0, test_translator_1.getExplore)(m.modelDef, 'na');
|
|
1432
1694
|
const x = (0, test_translator_1.getQueryField)(na, 'x');
|
|
1433
1695
|
const z = (0, test_translator_1.getField)(x.pipeline[0], 'z');
|
|
@@ -1436,7 +1698,7 @@ describe('source locations', () => {
|
|
|
1436
1698
|
test('location of field inherited from table', () => {
|
|
1437
1699
|
const source = (0, test_translator_1.markSource) `source: na is ${"table('aTable')"}`;
|
|
1438
1700
|
const m = new test_translator_1.TestTranslator(source.code);
|
|
1439
|
-
expect(m).
|
|
1701
|
+
expect(m).toTranslate();
|
|
1440
1702
|
const na = (0, test_translator_1.getExplore)(m.modelDef, 'na');
|
|
1441
1703
|
const abool = (0, test_translator_1.getField)(na, 'abool');
|
|
1442
1704
|
expect(abool.location).toMatchObject(source.locations[0]);
|
|
@@ -1447,14 +1709,14 @@ describe('source locations', () => {
|
|
|
1447
1709
|
source: na is from_sql(s)
|
|
1448
1710
|
`;
|
|
1449
1711
|
const m = new test_translator_1.TestTranslator(source.code);
|
|
1450
|
-
expect(m).
|
|
1712
|
+
expect(m).toParse();
|
|
1451
1713
|
const compileSql = m.translate().compileSQL;
|
|
1452
1714
|
expect(compileSql).toBeDefined();
|
|
1453
1715
|
if (compileSql) {
|
|
1454
1716
|
m.update({
|
|
1455
1717
|
compileSQL: { [compileSql.name]: getSelectOneStruct(compileSql) },
|
|
1456
1718
|
});
|
|
1457
|
-
expect(m).
|
|
1719
|
+
expect(m).toTranslate();
|
|
1458
1720
|
const na = (0, test_translator_1.getExplore)(m.modelDef, 'na');
|
|
1459
1721
|
const one = (0, test_translator_1.getField)(na, 'one');
|
|
1460
1722
|
expect(one.location).isLocationIn(source.locations[0], source.code);
|
|
@@ -1471,7 +1733,7 @@ describe('source locations', () => {
|
|
|
1471
1733
|
)
|
|
1472
1734
|
`;
|
|
1473
1735
|
const m = new test_translator_1.TestTranslator(source.code);
|
|
1474
|
-
expect(m).
|
|
1736
|
+
expect(m).toTranslate();
|
|
1475
1737
|
const na = (0, test_translator_1.getExplore)(m.modelDef, 'na');
|
|
1476
1738
|
const abool = (0, test_translator_1.getField)(na, 'abool');
|
|
1477
1739
|
expect(abool.location).toMatchObject(source.locations[0]);
|
|
@@ -1481,14 +1743,14 @@ describe('source locations', () => {
|
|
|
1481
1743
|
test('location of named query', () => {
|
|
1482
1744
|
const source = (0, test_translator_1.markSource) `query: ${'q is a -> { project: * }'}`;
|
|
1483
1745
|
const m = new test_translator_1.TestTranslator(source.code);
|
|
1484
|
-
expect(m).
|
|
1746
|
+
expect(m).toTranslate();
|
|
1485
1747
|
const q = (0, test_translator_1.getExplore)(m.modelDef, 'q');
|
|
1486
1748
|
expect(q.location).toMatchObject(source.locations[0]);
|
|
1487
1749
|
});
|
|
1488
1750
|
test('location of field in named query', () => {
|
|
1489
1751
|
const source = (0, test_translator_1.markSource) `query: q is a -> { group_by: ${'b is 1'} }`;
|
|
1490
1752
|
const m = new test_translator_1.TestTranslator(source.code);
|
|
1491
|
-
expect(m).
|
|
1753
|
+
expect(m).toTranslate();
|
|
1492
1754
|
const q = (0, test_translator_1.getModelQuery)(m.modelDef, 'q');
|
|
1493
1755
|
const a = (0, test_translator_1.getField)(q.pipeline[0], 'b');
|
|
1494
1756
|
expect(a.location).toMatchObject(source.locations[0]);
|
|
@@ -1496,14 +1758,14 @@ describe('source locations', () => {
|
|
|
1496
1758
|
test('location of named SQL block', () => {
|
|
1497
1759
|
const source = (0, test_translator_1.markSource) `${'sql: s is { select: """SELECT 1 as one""" }'}`;
|
|
1498
1760
|
const m = new test_translator_1.TestTranslator(source.code);
|
|
1499
|
-
expect(m).
|
|
1761
|
+
expect(m).toParse();
|
|
1500
1762
|
const compileSql = m.translate().compileSQL;
|
|
1501
1763
|
expect(compileSql).toBeDefined();
|
|
1502
1764
|
if (compileSql) {
|
|
1503
1765
|
m.update({
|
|
1504
1766
|
compileSQL: { [compileSql.name]: getSelectOneStruct(compileSql) },
|
|
1505
1767
|
});
|
|
1506
|
-
expect(m).
|
|
1768
|
+
expect(m).toTranslate();
|
|
1507
1769
|
const s = m.sqlBlocks[0];
|
|
1508
1770
|
expect(s.location).isLocationIn(source.locations[0], source.code);
|
|
1509
1771
|
}
|
|
@@ -1515,7 +1777,7 @@ describe('source locations', () => {
|
|
|
1515
1777
|
}
|
|
1516
1778
|
`;
|
|
1517
1779
|
const m = new test_translator_1.TestTranslator(source.code);
|
|
1518
|
-
expect(m).
|
|
1780
|
+
expect(m).toTranslate();
|
|
1519
1781
|
const na = (0, test_translator_1.getExplore)(m.modelDef, 'na');
|
|
1520
1782
|
const bbool = (0, test_translator_1.getField)(na, 'bbool');
|
|
1521
1783
|
expect(bbool.location).toMatchObject(source.locations[0]);
|
|
@@ -1527,7 +1789,7 @@ describe('source locations', () => {
|
|
|
1527
1789
|
}
|
|
1528
1790
|
`;
|
|
1529
1791
|
const m = new test_translator_1.TestTranslator(source.code);
|
|
1530
|
-
expect(m).
|
|
1792
|
+
expect(m).toTranslate();
|
|
1531
1793
|
const na = (0, test_translator_1.getExplore)(m.modelDef, 'na');
|
|
1532
1794
|
const x = (0, test_translator_1.getField)(na, 'x');
|
|
1533
1795
|
expect(x.location).toMatchObject(source.locations[0]);
|
|
@@ -1539,7 +1801,7 @@ describe('source locations', () => {
|
|
|
1539
1801
|
}
|
|
1540
1802
|
`;
|
|
1541
1803
|
const m = new test_translator_1.TestTranslator(source.code);
|
|
1542
|
-
expect(m).
|
|
1804
|
+
expect(m).toTranslate();
|
|
1543
1805
|
const na = (0, test_translator_1.getExplore)(m.modelDef, 'na');
|
|
1544
1806
|
const x = (0, test_translator_1.getField)(na, 'x');
|
|
1545
1807
|
expect(x.location).toMatchObject(source.locations[0]);
|
|
@@ -1554,7 +1816,7 @@ describe('source locations', () => {
|
|
|
1554
1816
|
}
|
|
1555
1817
|
`;
|
|
1556
1818
|
const m = new test_translator_1.TestTranslator(source.code);
|
|
1557
|
-
expect(m).
|
|
1819
|
+
expect(m).toTranslate();
|
|
1558
1820
|
const na = (0, test_translator_1.getExplore)(m.modelDef, 'na');
|
|
1559
1821
|
const x = (0, test_translator_1.getJoinField)(na, 'x');
|
|
1560
1822
|
const y = (0, test_translator_1.getField)(x, 'y');
|
|
@@ -1564,15 +1826,25 @@ describe('source locations', () => {
|
|
|
1564
1826
|
// test("multi line sql block token span is correct", () => {
|
|
1565
1827
|
// const sqlSource = `sql: { select: """// line 0\n//line 1\n// line 2""" }`;
|
|
1566
1828
|
// const m = new TestTranslator(sqlSource);
|
|
1567
|
-
// expect(m).not.
|
|
1829
|
+
// expect(m).not.toParse();
|
|
1568
1830
|
// const errList = m.errors().errors;
|
|
1569
1831
|
// expect(errList[0].at?.range.end).toEqual({ line: 2, character: 11 });
|
|
1570
1832
|
// });
|
|
1571
|
-
test('undefined query location',
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
test
|
|
1575
|
-
|
|
1833
|
+
test('undefined query location', () => {
|
|
1834
|
+
expect((0, test_translator_1.model) `query: ${'-> xyz'}`).translationToFailWith("Reference to undefined query 'xyz'");
|
|
1835
|
+
});
|
|
1836
|
+
test('undefined field reference', () => {
|
|
1837
|
+
expect((0, test_translator_1.model) `query: a -> { group_by: ${'xyz'} }`).translationToFailWith("'xyz' is not defined");
|
|
1838
|
+
});
|
|
1839
|
+
test('bad query', () => {
|
|
1840
|
+
expect((0, test_translator_1.model) `query: a -> { group_by: astr; ${'project: *'} }`).translationToFailWith('project: not legal in grouping query');
|
|
1841
|
+
});
|
|
1842
|
+
test.skip('undefined field reference in top', () => {
|
|
1843
|
+
expect((0, test_translator_1.model) `query: a -> { group_by: one is 1; top: 1 by ${'xyz'} }`).translationToFailWith("'xyz' is not defined");
|
|
1844
|
+
});
|
|
1845
|
+
test.skip('undefined field reference in order_by', () => {
|
|
1846
|
+
expect((0, test_translator_1.model) `query: a -> { group_by: one is 1; order_by: ${'xyz'} }`).translationToFailWith("'xyz' is not defined");
|
|
1847
|
+
});
|
|
1576
1848
|
});
|
|
1577
1849
|
describe('source references', () => {
|
|
1578
1850
|
test('reference to explore', () => {
|
|
@@ -1581,7 +1853,7 @@ describe('source references', () => {
|
|
|
1581
1853
|
query: ${'na'} -> { project: * }
|
|
1582
1854
|
`;
|
|
1583
1855
|
const m = new test_translator_1.TestTranslator(source.code);
|
|
1584
|
-
expect(m).
|
|
1856
|
+
expect(m).toTranslate();
|
|
1585
1857
|
expect(m.referenceAt(pos(source.locations[1]))).toMatchObject({
|
|
1586
1858
|
location: source.locations[1],
|
|
1587
1859
|
type: 'exploreReference',
|
|
@@ -1599,7 +1871,7 @@ describe('source references', () => {
|
|
|
1599
1871
|
query: t -> ${'q'}
|
|
1600
1872
|
`;
|
|
1601
1873
|
const m = new test_translator_1.TestTranslator(source.code);
|
|
1602
|
-
expect(m).
|
|
1874
|
+
expect(m).toTranslate();
|
|
1603
1875
|
expect(m.referenceAt(pos(source.locations[1]))).toMatchObject({
|
|
1604
1876
|
location: source.locations[1],
|
|
1605
1877
|
type: 'fieldReference',
|
|
@@ -1615,7 +1887,7 @@ describe('source references', () => {
|
|
|
1615
1887
|
query: na -> ${'x'}
|
|
1616
1888
|
`;
|
|
1617
1889
|
const m = new test_translator_1.TestTranslator(source.code);
|
|
1618
|
-
expect(m).
|
|
1890
|
+
expect(m).toTranslate();
|
|
1619
1891
|
expect(m.referenceAt(pos(source.locations[1]))).toMatchObject({
|
|
1620
1892
|
location: source.locations[1],
|
|
1621
1893
|
type: 'fieldReference',
|
|
@@ -1631,14 +1903,14 @@ describe('source references', () => {
|
|
|
1631
1903
|
source: na is from_sql(${'s'})
|
|
1632
1904
|
`;
|
|
1633
1905
|
const m = new test_translator_1.TestTranslator(source.code);
|
|
1634
|
-
expect(m).
|
|
1906
|
+
expect(m).toParse();
|
|
1635
1907
|
const compileSql = m.translate().compileSQL;
|
|
1636
1908
|
expect(compileSql).toBeDefined();
|
|
1637
1909
|
if (compileSql) {
|
|
1638
1910
|
m.update({
|
|
1639
1911
|
compileSQL: { [compileSql.name]: getSelectOneStruct(compileSql) },
|
|
1640
1912
|
});
|
|
1641
|
-
expect(m).
|
|
1913
|
+
expect(m).toTranslate();
|
|
1642
1914
|
const ref = m.referenceAt(pos(source.locations[1]));
|
|
1643
1915
|
expect(ref).toMatchObject({
|
|
1644
1916
|
location: source.locations[1],
|
|
@@ -1657,7 +1929,7 @@ describe('source references', () => {
|
|
|
1657
1929
|
source: na is from(-> ${'q'})
|
|
1658
1930
|
`;
|
|
1659
1931
|
const m = new test_translator_1.TestTranslator(source.code);
|
|
1660
|
-
expect(m).
|
|
1932
|
+
expect(m).toTranslate();
|
|
1661
1933
|
expect(m.referenceAt(pos(source.locations[1]))).toMatchObject({
|
|
1662
1934
|
location: source.locations[1],
|
|
1663
1935
|
type: 'queryReference',
|
|
@@ -1673,7 +1945,7 @@ describe('source references', () => {
|
|
|
1673
1945
|
query: q2 is -> ${'q'} -> { project: * }
|
|
1674
1946
|
`;
|
|
1675
1947
|
const m = new test_translator_1.TestTranslator(source.code);
|
|
1676
|
-
expect(m).
|
|
1948
|
+
expect(m).toTranslate();
|
|
1677
1949
|
expect(m.referenceAt(pos(source.locations[1]))).toMatchObject({
|
|
1678
1950
|
location: source.locations[1],
|
|
1679
1951
|
type: 'queryReference',
|
|
@@ -1689,7 +1961,7 @@ describe('source references', () => {
|
|
|
1689
1961
|
query: q2 is -> ${'q'} { limit: 10 }
|
|
1690
1962
|
`;
|
|
1691
1963
|
const m = new test_translator_1.TestTranslator(source.code);
|
|
1692
|
-
expect(m).
|
|
1964
|
+
expect(m).toTranslate();
|
|
1693
1965
|
expect(m.referenceAt(pos(source.locations[1]))).toMatchObject({
|
|
1694
1966
|
location: source.locations[1],
|
|
1695
1967
|
type: 'queryReference',
|
|
@@ -1705,7 +1977,7 @@ describe('source references', () => {
|
|
|
1705
1977
|
query: na -> { project: bbool is not ${'abool'} }
|
|
1706
1978
|
`;
|
|
1707
1979
|
const m = new test_translator_1.TestTranslator(source.code);
|
|
1708
|
-
expect(m).
|
|
1980
|
+
expect(m).toTranslate();
|
|
1709
1981
|
expect(m.referenceAt(pos(source.locations[1]))).toMatchObject({
|
|
1710
1982
|
location: source.locations[1],
|
|
1711
1983
|
type: 'fieldReference',
|
|
@@ -1723,7 +1995,7 @@ describe('source references', () => {
|
|
|
1723
1995
|
query: na -> { project: ${'`name`'} }
|
|
1724
1996
|
`;
|
|
1725
1997
|
const m = new test_translator_1.TestTranslator(source.code);
|
|
1726
|
-
expect(m).
|
|
1998
|
+
expect(m).toTranslate();
|
|
1727
1999
|
expect(m.referenceAt(pos(source.locations[1]))).toMatchObject({
|
|
1728
2000
|
location: source.locations[1],
|
|
1729
2001
|
type: 'fieldReference',
|
|
@@ -1742,7 +2014,7 @@ describe('source references', () => {
|
|
|
1742
2014
|
query: na -> { project: bstr is self.${'astr'} }
|
|
1743
2015
|
`;
|
|
1744
2016
|
const m = new test_translator_1.TestTranslator(source.code);
|
|
1745
|
-
expect(m).
|
|
2017
|
+
expect(m).toTranslate();
|
|
1746
2018
|
expect(m.referenceAt(pos(source.locations[1]))).toMatchObject({
|
|
1747
2019
|
location: source.locations[1],
|
|
1748
2020
|
type: 'fieldReference',
|
|
@@ -1760,7 +2032,7 @@ describe('source references', () => {
|
|
|
1760
2032
|
query: na -> { project: bstr is ${'self'}.astr }
|
|
1761
2033
|
`;
|
|
1762
2034
|
const m = new test_translator_1.TestTranslator(source.code);
|
|
1763
|
-
expect(m).
|
|
2035
|
+
expect(m).toTranslate();
|
|
1764
2036
|
expect(m.referenceAt(pos(source.locations[1]))).toMatchObject({
|
|
1765
2037
|
location: source.locations[1],
|
|
1766
2038
|
type: 'joinReference',
|
|
@@ -1775,7 +2047,7 @@ describe('source references', () => {
|
|
|
1775
2047
|
query: ${"table('aTable')"} -> { group_by: ${'abool'} }
|
|
1776
2048
|
`;
|
|
1777
2049
|
const m = new test_translator_1.TestTranslator(source.code);
|
|
1778
|
-
expect(m).
|
|
2050
|
+
expect(m).toTranslate();
|
|
1779
2051
|
expect(m.referenceAt(pos(source.locations[1]))).toMatchObject({
|
|
1780
2052
|
location: source.locations[1],
|
|
1781
2053
|
type: 'fieldReference',
|
|
@@ -1791,7 +2063,7 @@ describe('source references', () => {
|
|
|
1791
2063
|
query: na -> { project: ${'abool'} }
|
|
1792
2064
|
`;
|
|
1793
2065
|
const m = new test_translator_1.TestTranslator(source.code);
|
|
1794
|
-
expect(m).
|
|
2066
|
+
expect(m).toTranslate();
|
|
1795
2067
|
expect(m.referenceAt(pos(source.locations[1]))).toMatchObject({
|
|
1796
2068
|
location: source.locations[1],
|
|
1797
2069
|
type: 'fieldReference',
|
|
@@ -1809,7 +2081,7 @@ describe('source references', () => {
|
|
|
1809
2081
|
}
|
|
1810
2082
|
`;
|
|
1811
2083
|
const m = new test_translator_1.TestTranslator(source.code);
|
|
1812
|
-
expect(m).
|
|
2084
|
+
expect(m).toTranslate();
|
|
1813
2085
|
expect(m.referenceAt(pos(source.locations[1]))).toMatchObject({
|
|
1814
2086
|
location: source.locations[1],
|
|
1815
2087
|
type: 'fieldReference',
|
|
@@ -1827,7 +2099,7 @@ describe('source references', () => {
|
|
|
1827
2099
|
}
|
|
1828
2100
|
`;
|
|
1829
2101
|
const m = new test_translator_1.TestTranslator(source.code);
|
|
1830
|
-
expect(m).
|
|
2102
|
+
expect(m).toTranslate();
|
|
1831
2103
|
expect(m.referenceAt(pos(source.locations[1]))).toMatchObject({
|
|
1832
2104
|
location: source.locations[1],
|
|
1833
2105
|
type: 'fieldReference',
|
|
@@ -1845,7 +2117,7 @@ describe('source references', () => {
|
|
|
1845
2117
|
}
|
|
1846
2118
|
`;
|
|
1847
2119
|
const m = new test_translator_1.TestTranslator(source.code);
|
|
1848
|
-
expect(m).
|
|
2120
|
+
expect(m).toTranslate();
|
|
1849
2121
|
expect(m.referenceAt(pos(source.locations[1]))).toMatchObject({
|
|
1850
2122
|
location: source.locations[1],
|
|
1851
2123
|
type: 'fieldReference',
|
|
@@ -1863,7 +2135,7 @@ describe('source references', () => {
|
|
|
1863
2135
|
}
|
|
1864
2136
|
`;
|
|
1865
2137
|
const m = new test_translator_1.TestTranslator(source.code);
|
|
1866
|
-
expect(m).
|
|
2138
|
+
expect(m).toTranslate();
|
|
1867
2139
|
expect(m.referenceAt(pos(source.locations[1]))).toMatchObject({
|
|
1868
2140
|
location: source.locations[1],
|
|
1869
2141
|
type: 'fieldReference',
|
|
@@ -1881,7 +2153,7 @@ describe('source references', () => {
|
|
|
1881
2153
|
}
|
|
1882
2154
|
`;
|
|
1883
2155
|
const m = new test_translator_1.TestTranslator(source.code);
|
|
1884
|
-
expect(m).
|
|
2156
|
+
expect(m).toTranslate();
|
|
1885
2157
|
expect(m.referenceAt(pos(source.locations[1]))).toMatchObject({
|
|
1886
2158
|
location: source.locations[1],
|
|
1887
2159
|
type: 'fieldReference',
|
|
@@ -1899,7 +2171,7 @@ describe('source references', () => {
|
|
|
1899
2171
|
}
|
|
1900
2172
|
`;
|
|
1901
2173
|
const m = new test_translator_1.TestTranslator(source.code);
|
|
1902
|
-
expect(m).
|
|
2174
|
+
expect(m).toTranslate();
|
|
1903
2175
|
expect(m.referenceAt(pos(source.locations[1]))).toMatchObject({
|
|
1904
2176
|
location: source.locations[1],
|
|
1905
2177
|
type: 'fieldReference',
|
|
@@ -1917,7 +2189,7 @@ describe('source references', () => {
|
|
|
1917
2189
|
}
|
|
1918
2190
|
`;
|
|
1919
2191
|
const m = new test_translator_1.TestTranslator(source.code);
|
|
1920
|
-
expect(m).
|
|
2192
|
+
expect(m).toTranslate();
|
|
1921
2193
|
expect(m.referenceAt(pos(source.locations[1]))).toMatchObject({
|
|
1922
2194
|
location: source.locations[1],
|
|
1923
2195
|
type: 'fieldReference',
|
|
@@ -1933,7 +2205,7 @@ describe('source references', () => {
|
|
|
1933
2205
|
query: na -> { aggregate: ai_sum is ${'ai'}.sum() }
|
|
1934
2206
|
`;
|
|
1935
2207
|
const m = new test_translator_1.TestTranslator(source.code);
|
|
1936
|
-
expect(m).
|
|
2208
|
+
expect(m).toTranslate();
|
|
1937
2209
|
expect(m.referenceAt(pos(source.locations[1]))).toMatchObject({
|
|
1938
2210
|
location: source.locations[1],
|
|
1939
2211
|
type: 'fieldReference',
|
|
@@ -1954,7 +2226,7 @@ describe('source references', () => {
|
|
|
1954
2226
|
query: na -> { aggregate: ai_sum is ${'self'}.sum(self.ai) }
|
|
1955
2227
|
`;
|
|
1956
2228
|
const m = new test_translator_1.TestTranslator(source.code);
|
|
1957
|
-
expect(m).
|
|
2229
|
+
expect(m).toTranslate();
|
|
1958
2230
|
expect(m.referenceAt(pos(source.locations[1]))).toMatchObject({
|
|
1959
2231
|
location: source.locations[1],
|
|
1960
2232
|
type: 'joinReference',
|
|
@@ -1972,7 +2244,7 @@ describe('source references', () => {
|
|
|
1972
2244
|
query: na -> { aggregate: ai_sum is self.sum(${'self'}.ai) }
|
|
1973
2245
|
`;
|
|
1974
2246
|
const m = new test_translator_1.TestTranslator(source.code);
|
|
1975
|
-
expect(m).
|
|
2247
|
+
expect(m).toTranslate();
|
|
1976
2248
|
expect(m.referenceAt(pos(source.locations[1]))).toMatchObject({
|
|
1977
2249
|
location: source.locations[1],
|
|
1978
2250
|
type: 'joinReference',
|
|
@@ -1990,7 +2262,7 @@ describe('source references', () => {
|
|
|
1990
2262
|
}
|
|
1991
2263
|
`;
|
|
1992
2264
|
const m = new test_translator_1.TestTranslator(source.code);
|
|
1993
|
-
expect(m).
|
|
2265
|
+
expect(m).toTranslate();
|
|
1994
2266
|
expect(m.referenceAt(pos(source.locations[1]))).toMatchObject({
|
|
1995
2267
|
location: source.locations[1],
|
|
1996
2268
|
type: 'exploreReference',
|
|
@@ -2006,7 +2278,7 @@ describe('source references', () => {
|
|
|
2006
2278
|
query: na -> { aggregate: ai_sum is sum(${'ai'}) }
|
|
2007
2279
|
`;
|
|
2008
2280
|
const m = new test_translator_1.TestTranslator(source.code);
|
|
2009
|
-
expect(m).
|
|
2281
|
+
expect(m).toTranslate();
|
|
2010
2282
|
expect(m.referenceAt(pos(source.locations[1]))).toMatchObject({
|
|
2011
2283
|
location: source.locations[1],
|
|
2012
2284
|
type: 'fieldReference',
|
|
@@ -2023,7 +2295,7 @@ describe('source references', () => {
|
|
|
2023
2295
|
}
|
|
2024
2296
|
`;
|
|
2025
2297
|
const m = new test_translator_1.TestTranslator(source.code);
|
|
2026
|
-
expect(m).
|
|
2298
|
+
expect(m).toTranslate();
|
|
2027
2299
|
expect(m.referenceAt(pos(source.locations[1]))).toMatchObject({
|
|
2028
2300
|
location: source.locations[1],
|
|
2029
2301
|
type: 'fieldReference',
|
|
@@ -2041,7 +2313,7 @@ describe('source references', () => {
|
|
|
2041
2313
|
}
|
|
2042
2314
|
`;
|
|
2043
2315
|
const m = new test_translator_1.TestTranslator(source.code);
|
|
2044
|
-
expect(m).
|
|
2316
|
+
expect(m).toTranslate();
|
|
2045
2317
|
expect(m.referenceAt(pos(source.locations[1]))).toMatchObject({
|
|
2046
2318
|
location: source.locations[1],
|
|
2047
2319
|
type: 'fieldReference',
|
|
@@ -2054,15 +2326,13 @@ describe('source references', () => {
|
|
|
2054
2326
|
});
|
|
2055
2327
|
describe('translation need error locations', () => {
|
|
2056
2328
|
test('import error location', () => {
|
|
2057
|
-
const source = (0, test_translator_1.
|
|
2058
|
-
const m =
|
|
2329
|
+
const source = (0, test_translator_1.model) `import ${'"badfile"'}`;
|
|
2330
|
+
const m = source.translator;
|
|
2059
2331
|
const result = m.translate();
|
|
2060
2332
|
m.update({
|
|
2061
2333
|
errors: { urls: { [(result.urls || [])[0]]: 'Bad file!' } },
|
|
2062
2334
|
});
|
|
2063
|
-
expect(
|
|
2064
|
-
const errList = m.errors().errors;
|
|
2065
|
-
expect(errList[0].at).isLocationIn(source.locations[0], source.code);
|
|
2335
|
+
expect(source).translationToFailWith(/Bad file!/);
|
|
2066
2336
|
return undefined;
|
|
2067
2337
|
});
|
|
2068
2338
|
test('sql struct error location', () => {
|
|
@@ -2071,35 +2341,33 @@ describe('translation need error locations', () => {
|
|
|
2071
2341
|
query: from_sql(bad_sql) -> { project: * }
|
|
2072
2342
|
`;
|
|
2073
2343
|
const m = new test_translator_1.TestTranslator(source.code);
|
|
2074
|
-
expect(m).
|
|
2344
|
+
expect(m).toParse();
|
|
2075
2345
|
const req = m.translate().compileSQL;
|
|
2076
2346
|
expect(req).toBeDefined();
|
|
2077
2347
|
if (req) {
|
|
2078
2348
|
m.update({ errors: { compileSQL: { [req.name]: 'Bad SQL!' } } });
|
|
2079
2349
|
}
|
|
2080
|
-
expect(m).not.
|
|
2350
|
+
expect(m).not.toTranslate();
|
|
2081
2351
|
const errList = m.errors().errors;
|
|
2082
2352
|
expect(errList[0].at).isLocationIn(source.locations[0], source.code);
|
|
2083
2353
|
});
|
|
2084
2354
|
test('table struct error location', () => {
|
|
2085
|
-
const source = (0, test_translator_1.
|
|
2355
|
+
const source = (0, test_translator_1.model) `
|
|
2086
2356
|
source: bad_explore is ${"table('malloy-data.bad.table')"}
|
|
2087
2357
|
`;
|
|
2088
|
-
const m =
|
|
2358
|
+
const m = source.translator;
|
|
2089
2359
|
const result = m.translate();
|
|
2090
2360
|
m.update({
|
|
2091
2361
|
errors: {
|
|
2092
2362
|
tables: { [(result.tables || [])[0]]: 'Bad table!' },
|
|
2093
2363
|
},
|
|
2094
2364
|
});
|
|
2095
|
-
expect(m).
|
|
2096
|
-
const errList = m.errors().errors;
|
|
2097
|
-
expect(errList[0].at).isLocationIn(source.locations[0], source.code);
|
|
2098
|
-
return undefined;
|
|
2365
|
+
expect(m).translationToFailWith(/Bad table!/);
|
|
2099
2366
|
});
|
|
2100
2367
|
});
|
|
2101
2368
|
describe('pipeline comprehension', () => {
|
|
2102
|
-
test('second query gets namespace from first',
|
|
2369
|
+
test('second query gets namespace from first', () => {
|
|
2370
|
+
expect(`
|
|
2103
2371
|
source: aq is a {
|
|
2104
2372
|
query: t1 is {
|
|
2105
2373
|
group_by: t1int is ai, t1str is astr
|
|
@@ -2107,8 +2375,10 @@ describe('pipeline comprehension', () => {
|
|
|
2107
2375
|
project: t1str, t1int
|
|
2108
2376
|
}
|
|
2109
2377
|
}
|
|
2110
|
-
`));
|
|
2111
|
-
|
|
2378
|
+
`).toTranslate();
|
|
2379
|
+
});
|
|
2380
|
+
test("second query doesn't have access to original fields", () => {
|
|
2381
|
+
expect((0, test_translator_1.model) `
|
|
2112
2382
|
source: aq is a {
|
|
2113
2383
|
query: t1 is {
|
|
2114
2384
|
group_by: t1int is ai, t1str is astr
|
|
@@ -2116,8 +2386,10 @@ describe('pipeline comprehension', () => {
|
|
|
2116
2386
|
project: ${'ai'}
|
|
2117
2387
|
}
|
|
2118
2388
|
}
|
|
2119
|
-
|
|
2120
|
-
|
|
2389
|
+
`).translationToFailWith("'ai' is not defined");
|
|
2390
|
+
});
|
|
2391
|
+
test('new query can append ops to existing query', () => {
|
|
2392
|
+
expect(`
|
|
2121
2393
|
source: aq is a {
|
|
2122
2394
|
query: t0 is {
|
|
2123
2395
|
group_by: t1int is ai, t1str is astr
|
|
@@ -2126,8 +2398,10 @@ describe('pipeline comprehension', () => {
|
|
|
2126
2398
|
project: t1str, t1int
|
|
2127
2399
|
}
|
|
2128
2400
|
}
|
|
2129
|
-
`));
|
|
2130
|
-
|
|
2401
|
+
`).toTranslate();
|
|
2402
|
+
});
|
|
2403
|
+
test('new query can refine and append to exisiting query', () => {
|
|
2404
|
+
expect(`
|
|
2131
2405
|
source: aq is table('aTable') {
|
|
2132
2406
|
query: by_region is { group_by: astr }
|
|
2133
2407
|
query: by_region2 is by_region {
|
|
@@ -2136,20 +2410,25 @@ describe('pipeline comprehension', () => {
|
|
|
2136
2410
|
project: astr, dateNest.ad
|
|
2137
2411
|
}
|
|
2138
2412
|
}
|
|
2139
|
-
`));
|
|
2140
|
-
|
|
2413
|
+
`).toTranslate();
|
|
2414
|
+
});
|
|
2415
|
+
test('reference to a query can include a refinement', () => {
|
|
2416
|
+
expect(`
|
|
2141
2417
|
query: ab -> {
|
|
2142
2418
|
group_by: ai
|
|
2143
2419
|
nest: aturtle { limit: 1 }
|
|
2144
2420
|
}
|
|
2145
|
-
`));
|
|
2146
|
-
|
|
2421
|
+
`).toTranslate();
|
|
2422
|
+
});
|
|
2423
|
+
test('Querying an sourcebased on a query', () => {
|
|
2424
|
+
expect(`
|
|
2147
2425
|
query: q is a -> { group_by: astr; aggregate: strsum is ai.sum() }
|
|
2148
2426
|
source: aq is a {
|
|
2149
2427
|
join_one: aq is from(->q) on astr = aq.astr
|
|
2150
2428
|
}
|
|
2151
2429
|
query: aqf is aq -> { project: * }
|
|
2152
|
-
`));
|
|
2430
|
+
`).toTranslate();
|
|
2431
|
+
});
|
|
2153
2432
|
test('new query appends to existing query', () => {
|
|
2154
2433
|
const src = `
|
|
2155
2434
|
query: s1 is table('malloytest.flights') -> {
|
|
@@ -2160,7 +2439,7 @@ describe('pipeline comprehension', () => {
|
|
|
2160
2439
|
}
|
|
2161
2440
|
`;
|
|
2162
2441
|
const m = new test_translator_1.TestTranslator(src);
|
|
2163
|
-
expect(m).
|
|
2442
|
+
expect(m).toTranslate();
|
|
2164
2443
|
const s2 = m.getQuery('s2');
|
|
2165
2444
|
expect(s2 === null || s2 === void 0 ? void 0 : s2.pipeline.length).toBe(2);
|
|
2166
2445
|
});
|