@malloydata/malloy 0.0.240-dev250311202829 → 0.0.240-dev250311214430
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/api/core.d.ts +1 -0
- package/dist/api/core.js +2 -1
- package/dist/lang/malloy-parse-info.d.ts +2 -10
- package/dist/lang/malloy-to-ast.d.ts +1 -0
- package/dist/lang/malloy-to-ast.js +9 -6
- package/dist/lang/malloy-to-stable-query.d.ts +76 -0
- package/dist/lang/malloy-to-stable-query.js +660 -0
- package/dist/lang/parse-log.d.ts +1 -0
- package/dist/lang/parse-malloy.d.ts +2 -9
- package/dist/lang/parse-malloy.js +7 -112
- package/dist/lang/parse-tree-walkers/model-annotation-walker.js +1 -1
- package/dist/lang/run-malloy-parser.d.ts +3 -0
- package/dist/lang/run-malloy-parser.js +53 -0
- package/dist/lang/syntax-errors/malloy-parser-error-listener.d.ts +4 -3
- package/dist/lang/syntax-errors/malloy-parser-error-listener.js +8 -4
- package/dist/lang/utils.d.ts +27 -1
- package/dist/lang/utils.js +78 -1
- package/package.json +3 -3
- package/dist/api/asynchronous.spec.d.ts +0 -1
- package/dist/api/asynchronous.spec.js +0 -240
- package/dist/api/sessioned.spec.d.ts +0 -1
- package/dist/api/sessioned.spec.js +0 -476
- package/dist/api/stateless.spec.d.ts +0 -1
- package/dist/api/stateless.spec.js +0 -400
- package/dist/lang/test/annotation.spec.d.ts +0 -14
- package/dist/lang/test/annotation.spec.js +0 -809
- package/dist/lang/test/composite-field-usage.spec.d.ts +0 -1
- package/dist/lang/test/composite-field-usage.spec.js +0 -261
- package/dist/lang/test/document-help-context-walker.spec.d.ts +0 -1
- package/dist/lang/test/document-help-context-walker.spec.js +0 -55
- package/dist/lang/test/document-symbol-walker.spec.d.ts +0 -1
- package/dist/lang/test/document-symbol-walker.spec.js +0 -209
- package/dist/lang/test/expressions.spec.d.ts +0 -1
- package/dist/lang/test/expressions.spec.js +0 -1181
- package/dist/lang/test/field-symbols.spec.d.ts +0 -1
- package/dist/lang/test/field-symbols.spec.js +0 -194
- package/dist/lang/test/find-table-path-walker.spec.d.ts +0 -1
- package/dist/lang/test/find-table-path-walker.spec.js +0 -50
- package/dist/lang/test/imports.spec.d.ts +0 -1
- package/dist/lang/test/imports.spec.js +0 -283
- package/dist/lang/test/lenses.spec.d.ts +0 -1
- package/dist/lang/test/lenses.spec.js +0 -384
- package/dist/lang/test/literals.spec.d.ts +0 -1
- package/dist/lang/test/literals.spec.js +0 -267
- package/dist/lang/test/locations.spec.d.ts +0 -1
- package/dist/lang/test/locations.spec.js +0 -679
- package/dist/lang/test/model-annotation-walker.spec.d.ts +0 -1
- package/dist/lang/test/model-annotation-walker.spec.js +0 -46
- package/dist/lang/test/model_serialization.spec.d.ts +0 -5
- package/dist/lang/test/model_serialization.spec.js +0 -116
- package/dist/lang/test/parameters.spec.d.ts +0 -1
- package/dist/lang/test/parameters.spec.js +0 -619
- package/dist/lang/test/parse.spec.d.ts +0 -1
- package/dist/lang/test/parse.spec.js +0 -893
- package/dist/lang/test/pretranslate.spec.d.ts +0 -1
- package/dist/lang/test/pretranslate.spec.js +0 -80
- package/dist/lang/test/query.spec.d.ts +0 -1
- package/dist/lang/test/query.spec.js +0 -1149
- package/dist/lang/test/source.spec.d.ts +0 -1
- package/dist/lang/test/source.spec.js +0 -570
- package/dist/lang/test/sql-block.spec.d.ts +0 -1
- package/dist/lang/test/sql-block.spec.js +0 -145
- package/dist/lang/test/syntax-errors.spec.d.ts +0 -1
- package/dist/lang/test/syntax-errors.spec.js +0 -142
- package/dist/model/utils.spec.d.ts +0 -1
- package/dist/model/utils.spec.js +0 -38
|
@@ -1,1181 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
/*
|
|
3
|
-
* Copyright 2023 Google LLC
|
|
4
|
-
*
|
|
5
|
-
* Permission is hereby granted, free of charge, to any person obtaining
|
|
6
|
-
* a copy of this software and associated documentation files
|
|
7
|
-
* (the "Software"), to deal in the Software without restriction,
|
|
8
|
-
* including without limitation the rights to use, copy, modify, merge,
|
|
9
|
-
* publish, distribute, sublicense, and/or sell copies of the Software,
|
|
10
|
-
* and to permit persons to whom the Software is furnished to do so,
|
|
11
|
-
* subject to the following conditions:
|
|
12
|
-
*
|
|
13
|
-
* The above copyright notice and this permission notice shall be
|
|
14
|
-
* included in all copies or substantial portions of the Software.
|
|
15
|
-
*
|
|
16
|
-
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
17
|
-
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
18
|
-
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
|
19
|
-
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
|
20
|
-
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
|
21
|
-
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
|
22
|
-
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
23
|
-
*/
|
|
24
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
25
|
-
const test_translator_1 = require("./test-translator");
|
|
26
|
-
require("./parse-expects");
|
|
27
|
-
describe('expressions', () => {
|
|
28
|
-
describe('timeframes', () => {
|
|
29
|
-
const timeframes = [
|
|
30
|
-
['second', 'minute', 'hour', 'day', 'week', 'month', 'quarter', 'year'],
|
|
31
|
-
];
|
|
32
|
-
test.each(timeframes)('timestamp truncate %s', unit => {
|
|
33
|
-
const truncSrc = (0, test_translator_1.model) `run: a->{select: tts is ats.${unit}}`;
|
|
34
|
-
expect(truncSrc).toTranslate();
|
|
35
|
-
const tQuery = truncSrc.translator.getQuery(0);
|
|
36
|
-
expect(tQuery).toBeDefined();
|
|
37
|
-
const tField = (0, test_translator_1.getQueryFieldDef)(tQuery.pipeline[0], 'tts');
|
|
38
|
-
expect(tField['timeframe']).toEqual(unit);
|
|
39
|
-
});
|
|
40
|
-
const dateTF = [['week', 'month', 'quarter', 'year']];
|
|
41
|
-
test.each(dateTF)('date truncate %s', unit => {
|
|
42
|
-
const truncSrc = (0, test_translator_1.model) `run: a->{select: td is ad.${unit}}`;
|
|
43
|
-
expect(truncSrc).toTranslate();
|
|
44
|
-
const tQuery = truncSrc.translator.getQuery(0);
|
|
45
|
-
expect(tQuery).toBeDefined();
|
|
46
|
-
const tField = (0, test_translator_1.getQueryFieldDef)(tQuery.pipeline[0], 'td');
|
|
47
|
-
expect(tField['timeframe']).toEqual(unit);
|
|
48
|
-
});
|
|
49
|
-
// mtoy todo units missing: implement, or document
|
|
50
|
-
const diffable = [['second', 'minute', 'hour', 'day']];
|
|
51
|
-
test.each(diffable)('timestamp difference - %s', unit => {
|
|
52
|
-
expect(new test_translator_1.BetaExpression(`${unit}(@2021 to ats)`)).toParse();
|
|
53
|
-
});
|
|
54
|
-
test.each(diffable)('timestamp difference - %s', unit => {
|
|
55
|
-
expect(new test_translator_1.BetaExpression(`${unit}(ats to @2030)`)).toParse();
|
|
56
|
-
});
|
|
57
|
-
});
|
|
58
|
-
test('field name', () => {
|
|
59
|
-
expect((0, test_translator_1.expr) `astr`).compilesTo('astr');
|
|
60
|
-
});
|
|
61
|
-
test('function call', () => {
|
|
62
|
-
expect((0, test_translator_1.expr) `concat('foo')`).toTranslate();
|
|
63
|
-
});
|
|
64
|
-
test('raw function call codegen', () => {
|
|
65
|
-
expect((0, test_translator_1.expr) `special_function!(aweird, 'foo')`).compilesTo('special_function({aweird},{{"foo"}})');
|
|
66
|
-
});
|
|
67
|
-
describe('operators', () => {
|
|
68
|
-
test('addition', () => {
|
|
69
|
-
expect('42 + 7').compilesTo('{42 + 7}');
|
|
70
|
-
});
|
|
71
|
-
test('typecheck addition lhs', () => {
|
|
72
|
-
const wrong = (0, test_translator_1.expr) `${'"string"'} + 1`;
|
|
73
|
-
expect(wrong).toLog((0, test_translator_1.errorMessage)("The '+' operator requires a number, not a 'string'"));
|
|
74
|
-
});
|
|
75
|
-
test('typecheck addition rhs', () => {
|
|
76
|
-
const wrong = (0, test_translator_1.expr) `1 + ${'"string"'}`;
|
|
77
|
-
expect(wrong).toLog((0, test_translator_1.errorMessage)("The '+' operator requires a number, not a 'string'"));
|
|
78
|
-
});
|
|
79
|
-
test('subtraction', () => {
|
|
80
|
-
expect('42 - 7').compilesTo('{42 - 7}');
|
|
81
|
-
});
|
|
82
|
-
test('multiplication', () => {
|
|
83
|
-
expect('42 * 7').compilesTo('{42 * 7}');
|
|
84
|
-
});
|
|
85
|
-
test('mod', () => {
|
|
86
|
-
expect('42 % 7').compilesTo('{42 % 7}');
|
|
87
|
-
});
|
|
88
|
-
test('division', () => {
|
|
89
|
-
expect('42 / 7').compilesTo('{42 / 7}');
|
|
90
|
-
});
|
|
91
|
-
test('unary negation', () => {
|
|
92
|
-
expect('- ai').compilesTo('{unary- ai}');
|
|
93
|
-
});
|
|
94
|
-
test('equal', () => {
|
|
95
|
-
expect('42 = 7').compilesTo('{42 = 7}');
|
|
96
|
-
});
|
|
97
|
-
test('not equal', () => {
|
|
98
|
-
expect('42 != 7').compilesTo('{42 != 7}');
|
|
99
|
-
});
|
|
100
|
-
test('greater than', () => {
|
|
101
|
-
expect('42 > 7').compilesTo('{42 > 7}');
|
|
102
|
-
});
|
|
103
|
-
test('greater than or equal', () => {
|
|
104
|
-
expect('42 >= 7').compilesTo('{42 >= 7}');
|
|
105
|
-
});
|
|
106
|
-
test('less than or equal', () => {
|
|
107
|
-
expect('42 <= 7').compilesTo('{42 <= 7}');
|
|
108
|
-
});
|
|
109
|
-
test('less than', () => {
|
|
110
|
-
expect('42 < 7').compilesTo('{42 < 7}');
|
|
111
|
-
});
|
|
112
|
-
test('match', () => {
|
|
113
|
-
expect("'forty-two' ~ 'fifty-four'").compilesTo('{{"forty-two"} like {"fifty-four"}}');
|
|
114
|
-
});
|
|
115
|
-
test('not match', () => {
|
|
116
|
-
expect("'forty-two' !~ 'fifty-four'").compilesTo('{{"forty-two"} !like {"fifty-four"}}');
|
|
117
|
-
});
|
|
118
|
-
test('regexp-match', () => {
|
|
119
|
-
expect("'forty-two' ~ r'fifty-four'").compilesTo('{{"forty-two"} regex-match /fifty-four/}');
|
|
120
|
-
});
|
|
121
|
-
test('not regexp-match', () => {
|
|
122
|
-
expect("'forty-two' !~ r'fifty-four'").compilesTo('{not {{"forty-two"} regex-match /fifty-four/}}');
|
|
123
|
-
});
|
|
124
|
-
test('apply as equality', () => {
|
|
125
|
-
expect("'forty-two' ? 'fifty-four'").compilesTo('{{"forty-two"} = {"fifty-four"}}');
|
|
126
|
-
});
|
|
127
|
-
test('not', () => {
|
|
128
|
-
expect('not true').compilesTo('{not true}');
|
|
129
|
-
});
|
|
130
|
-
test('and', () => {
|
|
131
|
-
expect('true and false').compilesTo('{true and false}');
|
|
132
|
-
});
|
|
133
|
-
test('or', () => {
|
|
134
|
-
expect('true or false').compilesTo('{true or false}');
|
|
135
|
-
});
|
|
136
|
-
test('null-check (??)', () => {
|
|
137
|
-
expect('ai ?? 7').compilesTo('{ai coalesce 7}');
|
|
138
|
-
});
|
|
139
|
-
test('normal is-null', () => {
|
|
140
|
-
expect('ai is null').compilesTo('{is-null ai}');
|
|
141
|
-
});
|
|
142
|
-
test('normal is-not-null', () => {
|
|
143
|
-
expect('ai is not null').compilesTo('{is-not-null ai}');
|
|
144
|
-
});
|
|
145
|
-
test('apply is-null', () => {
|
|
146
|
-
expect('ai ? is null').compilesTo('{is-null ai}');
|
|
147
|
-
});
|
|
148
|
-
test('apply is-not-null', () => {
|
|
149
|
-
expect('ai ? is not null').compilesTo('{is-not-null ai}');
|
|
150
|
-
});
|
|
151
|
-
test('coalesce type mismatch', () => {
|
|
152
|
-
expect(new test_translator_1.BetaExpression('ai ?? @2003')).toLog((0, test_translator_1.errorMessage)('Mismatched types for coalesce (number, date)'));
|
|
153
|
-
});
|
|
154
|
-
test('disallow date OP number', () => {
|
|
155
|
-
expect(new test_translator_1.BetaExpression('@2001 = 7')).toLog((0, test_translator_1.errorMessage)('Cannot compare a date to a number'));
|
|
156
|
-
});
|
|
157
|
-
test('disallow date OP timestamp', () => {
|
|
158
|
-
expect(new test_translator_1.BetaExpression('ad = ats')).toLog((0, test_translator_1.errorMessage)('Cannot compare a date to a timestamp'));
|
|
159
|
-
});
|
|
160
|
-
test('disallow interval from date to timestamp', () => {
|
|
161
|
-
expect(new test_translator_1.BetaExpression('days(ad to ats)')).toLog((0, test_translator_1.errorMessage)('Cannot measure from date to timestamp'));
|
|
162
|
-
});
|
|
163
|
-
test('compare to truncation uses straight comparison', () => {
|
|
164
|
-
expect('ad = ad.quarter').compilesTo('{ad = {timeTrunc-quarter ad}}');
|
|
165
|
-
});
|
|
166
|
-
test('compare to granular result expression uses straight comparison', () => {
|
|
167
|
-
expect('ad = ad.quarter + 1').compilesTo('{ad = {+quarter {timeTrunc-quarter ad} 1}}');
|
|
168
|
-
});
|
|
169
|
-
test('apply granular-truncation uses range', () => {
|
|
170
|
-
expect('ad ? ad.quarter').compilesTo('{{ad >= {timeTrunc-quarter ad}} and {ad < {+quarter {timeTrunc-quarter ad} 1}}}');
|
|
171
|
-
});
|
|
172
|
-
test('apply granular-literal alternation uses all literals for range', () => {
|
|
173
|
-
expect('ad ? @2020').compilesTo('{{ad >= @2020-01-01} and {ad < @2021-01-01}}');
|
|
174
|
-
});
|
|
175
|
-
test('apply followed by another condition', () => {
|
|
176
|
-
expect('ai ? (10 | 20) and ai is not null').toLog((0, test_translator_1.errorMessage)("no viable alternative at input 'ai'"));
|
|
177
|
-
});
|
|
178
|
-
test('apply followed by another condition, with parenthesis', () => {
|
|
179
|
-
expect('(ai ? (10 | 20)) and ai is not null').compilesTo('{({ai in {10,20}}) and {is-not-null ai}}');
|
|
180
|
-
});
|
|
181
|
-
test('apply or-tree granular-literal doesnt turn into IN', () => {
|
|
182
|
-
expect('ad ? @2020 | @2022').compilesTo('{{{ad >= @2020-01-01} and {ad < @2021-01-01}} or {{ad >= @2022-01-01} and {ad < @2023-01-01}}}');
|
|
183
|
-
});
|
|
184
|
-
test('comparison promotes date literal to timestamp', () => {
|
|
185
|
-
expect((0, test_translator_1.expr) `@2001 = ats`).toTranslate();
|
|
186
|
-
});
|
|
187
|
-
test('can apply range to date', () => {
|
|
188
|
-
expect((0, test_translator_1.expr) `ad ? @2001 for 1 day`).toTranslate();
|
|
189
|
-
});
|
|
190
|
-
const noOffset = ['second', 'minute', 'hour'];
|
|
191
|
-
test.each(noOffset.map(x => [x]))('disallow date delta %s', unit => {
|
|
192
|
-
expect(new test_translator_1.BetaExpression(`ad + 10 ${unit}s`)).toLog((0, test_translator_1.errorMessage)(`Cannot offset date by ${unit}`));
|
|
193
|
-
});
|
|
194
|
-
test('apply with parens', () => {
|
|
195
|
-
expect((0, test_translator_1.expr) `ai ? (> 1 & < 100)`).toTranslate();
|
|
196
|
-
});
|
|
197
|
-
describe('sql friendly warnings', () => {
|
|
198
|
-
test('= null with warning', () => {
|
|
199
|
-
const warnSrc = (0, test_translator_1.expr) `${'ai = null'}`;
|
|
200
|
-
expect(warnSrc).toLog((0, test_translator_1.warningMessage)("Use 'is null' to check for NULL instead of '= null'"));
|
|
201
|
-
expect(warnSrc).compilesTo('{is-null ai}');
|
|
202
|
-
const warning = warnSrc.translator.problems()[0];
|
|
203
|
-
expect(warning.replacement).toEqual('ai is null');
|
|
204
|
-
});
|
|
205
|
-
test('is not null with warning', () => {
|
|
206
|
-
const warnSrc = (0, test_translator_1.expr) `${'ai != null'}`;
|
|
207
|
-
expect(warnSrc).toLog((0, test_translator_1.warningMessage)("Use 'is not null' to check for NULL instead of '!= null'"));
|
|
208
|
-
expect(warnSrc).compilesTo('{is-not-null ai}');
|
|
209
|
-
const warning = warnSrc.translator.problems()[0];
|
|
210
|
-
expect(warning.replacement).toEqual('ai is not null');
|
|
211
|
-
});
|
|
212
|
-
test('like with warning', () => {
|
|
213
|
-
const warnSrc = (0, test_translator_1.expr) `astr like 'a'`;
|
|
214
|
-
expect(warnSrc).toLog((0, test_translator_1.warningMessage)("Use Malloy operator '~' instead of 'LIKE'"));
|
|
215
|
-
expect(warnSrc).compilesTo('{astr like {"a"}}');
|
|
216
|
-
const warning = warnSrc.translator.problems()[0];
|
|
217
|
-
expect(warning.replacement).toEqual("astr ~ 'a'");
|
|
218
|
-
});
|
|
219
|
-
test('NOT LIKE with warning', () => {
|
|
220
|
-
const warnSrc = (0, test_translator_1.expr) `astr not like 'a'`;
|
|
221
|
-
expect(warnSrc).toLog((0, test_translator_1.warningMessage)("Use Malloy operator '!~' instead of 'NOT LIKE'"));
|
|
222
|
-
expect(warnSrc).compilesTo('{astr !like {"a"}}');
|
|
223
|
-
const warning = warnSrc.translator.problems()[0];
|
|
224
|
-
expect(warning.replacement).toEqual("astr !~ 'a'");
|
|
225
|
-
});
|
|
226
|
-
test('x is expr y is not null', () => {
|
|
227
|
-
const isNullSrc = (0, test_translator_1.model) `source: xa is a extend { dimension: x is 1 y is not null }`;
|
|
228
|
-
expect(isNullSrc).toTranslate();
|
|
229
|
-
const xaModel = isNullSrc.translator.translate().modelDef;
|
|
230
|
-
const xa = (0, test_translator_1.getExplore)(xaModel, 'xa');
|
|
231
|
-
const x = (0, test_translator_1.getFieldDef)(xa, 'x');
|
|
232
|
-
expect(x).toMatchObject({ e: { node: 'numberLiteral' } });
|
|
233
|
-
const y = (0, test_translator_1.getFieldDef)(xa, 'y');
|
|
234
|
-
expect(y).toMatchObject({ e: { node: 'not' } });
|
|
235
|
-
});
|
|
236
|
-
test('not null::number', () => {
|
|
237
|
-
const notNull = (0, test_translator_1.expr) `not null::number`;
|
|
238
|
-
expect(notNull).toLog((0, test_translator_1.errorMessage)("'not' Can't use type number"));
|
|
239
|
-
});
|
|
240
|
-
test('(not null)::number', () => {
|
|
241
|
-
const notNull = (0, test_translator_1.expr) `(not null)::number`;
|
|
242
|
-
expect(notNull).toTranslate();
|
|
243
|
-
});
|
|
244
|
-
});
|
|
245
|
-
});
|
|
246
|
-
test('filtered measure', () => {
|
|
247
|
-
expect((0, test_translator_1.expr) `acount {where: astr = 'why?' }`).toTranslate();
|
|
248
|
-
});
|
|
249
|
-
test('filtered ungrouped aggregate', () => {
|
|
250
|
-
expect(`
|
|
251
|
-
run: a -> {
|
|
252
|
-
group_by: ai
|
|
253
|
-
aggregate: x is all(avg(ai)) { where: true }
|
|
254
|
-
}
|
|
255
|
-
`).toTranslate();
|
|
256
|
-
});
|
|
257
|
-
test('correctly flags filtered scalar', () => {
|
|
258
|
-
const e = new test_translator_1.BetaExpression('ai { where: true }');
|
|
259
|
-
expect(e).toLog((0, test_translator_1.errorMessage)('Filtered expression requires an aggregate computation'));
|
|
260
|
-
});
|
|
261
|
-
test('correctly flags filtered analytic', () => {
|
|
262
|
-
expect((0, test_translator_1.markSource) `
|
|
263
|
-
run: a -> {
|
|
264
|
-
group_by: ai
|
|
265
|
-
calculate: l is lag(ai) { where: true }
|
|
266
|
-
}
|
|
267
|
-
`).toLog((0, test_translator_1.errorMessage)('Filtered expression requires an aggregate computation'));
|
|
268
|
-
});
|
|
269
|
-
test('can use calculate with partition by in select', () => {
|
|
270
|
-
expect((0, test_translator_1.markSource) `
|
|
271
|
-
##! experimental { partition_by function_order_by }
|
|
272
|
-
run: a -> {
|
|
273
|
-
select: ai, astr
|
|
274
|
-
calculate: prev is lag(ai) {
|
|
275
|
-
partition_by: astr
|
|
276
|
-
order_by: ai asc
|
|
277
|
-
}
|
|
278
|
-
order_by: ai asc, astr asc
|
|
279
|
-
}`).toTranslate();
|
|
280
|
-
});
|
|
281
|
-
describe('expr props', () => {
|
|
282
|
-
test('aggregate order by not allowed without experiments enabled', () => {
|
|
283
|
-
expect((0, test_translator_1.markSource) `
|
|
284
|
-
run: a -> {
|
|
285
|
-
group_by: ai
|
|
286
|
-
aggregate: x1 is string_agg(astr) { order_by: ai }
|
|
287
|
-
}
|
|
288
|
-
`).toLog((0, test_translator_1.errorMessage)('Enable experiment `aggregate_order_by` to use `order_by` with an aggregate function'));
|
|
289
|
-
});
|
|
290
|
-
test('aggregate limit not allowed without experiments enabled', () => {
|
|
291
|
-
expect((0, test_translator_1.markSource) `
|
|
292
|
-
run: a -> {
|
|
293
|
-
group_by: ai
|
|
294
|
-
aggregate: x3 is string_agg(astr) { limit: 10 }
|
|
295
|
-
}
|
|
296
|
-
`).toLog((0, test_translator_1.error)('experiment-not-enabled', { experimentId: 'aggregate_limit' }));
|
|
297
|
-
});
|
|
298
|
-
test('aggregate order_by not allowed with different experiment enabled', () => {
|
|
299
|
-
expect((0, test_translator_1.markSource) `
|
|
300
|
-
##! experimental.something_else
|
|
301
|
-
run: a -> {
|
|
302
|
-
group_by: ai
|
|
303
|
-
aggregate: x1 is string_agg(astr) { order_by: ai }
|
|
304
|
-
}
|
|
305
|
-
`).toLog((0, test_translator_1.errorMessage)('Enable experiment `aggregate_order_by` to use `order_by` with an aggregate function'));
|
|
306
|
-
});
|
|
307
|
-
test('aggregate limit not allowed with different experiment enabled', () => {
|
|
308
|
-
expect((0, test_translator_1.markSource) `
|
|
309
|
-
##! experimental.something_else
|
|
310
|
-
run: a -> {
|
|
311
|
-
group_by: ai
|
|
312
|
-
group_by: x3 is string_agg(astr) { limit: 10 }
|
|
313
|
-
}
|
|
314
|
-
`).toLog((0, test_translator_1.error)('experiment-not-enabled', { experimentId: 'aggregate_limit' }));
|
|
315
|
-
});
|
|
316
|
-
test('props not allowed on most expressions', () => {
|
|
317
|
-
expect((0, test_translator_1.markSource) `
|
|
318
|
-
##! experimental { aggregate_order_by aggregate_limit }
|
|
319
|
-
run: a -> {
|
|
320
|
-
group_by: x1 is 1 { order_by: ai }
|
|
321
|
-
group_by: x2 is 1 { partition_by: ai }
|
|
322
|
-
group_by: x3 is 1 { limit: 10 }
|
|
323
|
-
group_by: x4 is 1 { where: ai }
|
|
324
|
-
}
|
|
325
|
-
`).toLog((0, test_translator_1.errorMessage)('`order_by` is not supported for this kind of expression'), (0, test_translator_1.errorMessage)('`partition_by` is not supported for this kind of expression'), (0, test_translator_1.errorMessage)('`limit` is not supported for this kind of expression'), (0, test_translator_1.errorMessage)('Filtered expression requires an aggregate computation'));
|
|
326
|
-
});
|
|
327
|
-
test('analytics can take parititon_by and order_by', () => {
|
|
328
|
-
expect((0, test_translator_1.markSource) `
|
|
329
|
-
run: a -> {
|
|
330
|
-
group_by: ai
|
|
331
|
-
calculate: x is lag(ai) { partition_by: ai; order_by: ai }
|
|
332
|
-
}
|
|
333
|
-
`).toTranslate();
|
|
334
|
-
});
|
|
335
|
-
test('partition by works with scalar and aggregate', () => {
|
|
336
|
-
expect((0, test_translator_1.markSource) `
|
|
337
|
-
run: a -> {
|
|
338
|
-
group_by: ai
|
|
339
|
-
aggregate: c is count()
|
|
340
|
-
calculate: x is lag(ai) { partition_by: ai, c }
|
|
341
|
-
}
|
|
342
|
-
`).toTranslate();
|
|
343
|
-
});
|
|
344
|
-
test('partition by fails with analytic and ungrouped aggregate', () => {
|
|
345
|
-
expect((0, test_translator_1.markSource) `
|
|
346
|
-
run: a -> {
|
|
347
|
-
group_by: ai
|
|
348
|
-
aggregate: ac is all(count())
|
|
349
|
-
calculate: x is lag(ai) { partition_by: ac }
|
|
350
|
-
calculate: y is lag(ai) { partition_by: x }
|
|
351
|
-
}
|
|
352
|
-
`).toLog((0, test_translator_1.errorMessage)('Partition expression must be scalar or aggregate'), (0, test_translator_1.errorMessage)('Partition expression must be scalar or aggregate'));
|
|
353
|
-
});
|
|
354
|
-
test('analytics order_by requires expression', () => {
|
|
355
|
-
expect((0, test_translator_1.markSource) `
|
|
356
|
-
##! experimental { aggregate_order_by }
|
|
357
|
-
run: a -> {
|
|
358
|
-
group_by: ai
|
|
359
|
-
calculate: x is lag(ai) { order_by: asc }
|
|
360
|
-
}
|
|
361
|
-
`).toLog((0, test_translator_1.errorMessage)('analytic `order_by` must specify an aggregate expression or output field reference'));
|
|
362
|
-
});
|
|
363
|
-
test('string_agg_distinct order by cannot specify expression', () => {
|
|
364
|
-
expect((0, test_translator_1.markSource) `
|
|
365
|
-
##! experimental { aggregate_order_by }
|
|
366
|
-
run: a -> {
|
|
367
|
-
group_by: ai
|
|
368
|
-
aggregate: x is string_agg_distinct(astr) { order_by: ai }
|
|
369
|
-
}
|
|
370
|
-
`).toLog((0, test_translator_1.errorMessage)('`order_by` must be only `asc` or `desc` with no expression'));
|
|
371
|
-
});
|
|
372
|
-
test('string_agg_distinct order by can be just direction', () => {
|
|
373
|
-
expect((0, test_translator_1.markSource) `
|
|
374
|
-
##! experimental { aggregate_order_by }
|
|
375
|
-
run: a -> {
|
|
376
|
-
group_by: ai
|
|
377
|
-
aggregate: x is string_agg_distinct(astr) { order_by: asc }
|
|
378
|
-
}
|
|
379
|
-
`).toTranslate();
|
|
380
|
-
});
|
|
381
|
-
test('string_agg order by can be just direction', () => {
|
|
382
|
-
expect((0, test_translator_1.markSource) `
|
|
383
|
-
##! experimental { aggregate_order_by }
|
|
384
|
-
run: a -> {
|
|
385
|
-
group_by: ai
|
|
386
|
-
aggregate: x is string_agg(astr) { order_by: asc }
|
|
387
|
-
}
|
|
388
|
-
`).toTranslate();
|
|
389
|
-
});
|
|
390
|
-
test('can specify multiple partition_bys', () => {
|
|
391
|
-
expect((0, test_translator_1.markSource) `
|
|
392
|
-
run: a -> {
|
|
393
|
-
group_by: ai, astr, abool
|
|
394
|
-
calculate: x is lag(ai) {
|
|
395
|
-
partition_by: ai
|
|
396
|
-
partition_by: astr, abool
|
|
397
|
-
}
|
|
398
|
-
}
|
|
399
|
-
`).toTranslate();
|
|
400
|
-
});
|
|
401
|
-
test('can specify multiple order_bys', () => {
|
|
402
|
-
expect((0, test_translator_1.markSource) `
|
|
403
|
-
##! experimental { aggregate_order_by }
|
|
404
|
-
run: a -> {
|
|
405
|
-
group_by: ai, astr, abool
|
|
406
|
-
calculate: x is lag(ai) {
|
|
407
|
-
order_by: ai
|
|
408
|
-
order_by: astr, abool
|
|
409
|
-
}
|
|
410
|
-
}
|
|
411
|
-
`).toTranslate();
|
|
412
|
-
});
|
|
413
|
-
test('aggregate order by cannot be aggregate', () => {
|
|
414
|
-
expect((0, test_translator_1.markSource) `
|
|
415
|
-
##! experimental { aggregate_order_by }
|
|
416
|
-
run: a -> {
|
|
417
|
-
aggregate: x is string_agg(astr) {
|
|
418
|
-
order_by: sum(ai)
|
|
419
|
-
}
|
|
420
|
-
}
|
|
421
|
-
`).toLog((0, test_translator_1.errorMessage)('aggregate `order_by` must be scalar'));
|
|
422
|
-
});
|
|
423
|
-
test('aggregate order by cannot be analytic', () => {
|
|
424
|
-
expect((0, test_translator_1.markSource) `
|
|
425
|
-
##! experimental { aggregate_order_by }
|
|
426
|
-
run: a -> {
|
|
427
|
-
aggregate: x is string_agg(astr) {
|
|
428
|
-
order_by: rank()
|
|
429
|
-
}
|
|
430
|
-
}
|
|
431
|
-
`).toLog((0, test_translator_1.errorMessage)('aggregate `order_by` must be scalar'));
|
|
432
|
-
});
|
|
433
|
-
test('analytic order by can be an aggregate', () => {
|
|
434
|
-
expect((0, test_translator_1.markSource) `
|
|
435
|
-
##! experimental { aggregate_order_by }
|
|
436
|
-
run: a -> {
|
|
437
|
-
group_by: abool
|
|
438
|
-
calculate: x is lag(abool) {
|
|
439
|
-
order_by: sum(ai)
|
|
440
|
-
}
|
|
441
|
-
}
|
|
442
|
-
`).toTranslate();
|
|
443
|
-
});
|
|
444
|
-
test('analytic order by can be an output field', () => {
|
|
445
|
-
expect((0, test_translator_1.markSource) `
|
|
446
|
-
##! experimental { aggregate_order_by }
|
|
447
|
-
run: a -> {
|
|
448
|
-
group_by: ai
|
|
449
|
-
calculate: x is lag(ai) {
|
|
450
|
-
order_by: ai
|
|
451
|
-
}
|
|
452
|
-
}
|
|
453
|
-
`).toTranslate();
|
|
454
|
-
});
|
|
455
|
-
test('analytic order by must be an output field', () => {
|
|
456
|
-
expect((0, test_translator_1.markSource) `
|
|
457
|
-
##! experimental { aggregate_order_by }
|
|
458
|
-
run: a -> {
|
|
459
|
-
group_by: abool
|
|
460
|
-
calculate: x is lag(abool) {
|
|
461
|
-
order_by: ai
|
|
462
|
-
}
|
|
463
|
-
}
|
|
464
|
-
`).toLog((0, test_translator_1.errorMessage)('analytic `order_by` must be an aggregate or an output field reference'));
|
|
465
|
-
});
|
|
466
|
-
test('can specify multiple wheres', () => {
|
|
467
|
-
expect((0, test_translator_1.markSource) `
|
|
468
|
-
##! experimental { aggregate_order_by }
|
|
469
|
-
run: a -> {
|
|
470
|
-
aggregate: x is count() {
|
|
471
|
-
where: ai > 10
|
|
472
|
-
where: astr ~ '%foo%'
|
|
473
|
-
}
|
|
474
|
-
}
|
|
475
|
-
`).toTranslate();
|
|
476
|
-
});
|
|
477
|
-
test('string_agg can take order_by', () => {
|
|
478
|
-
expect((0, test_translator_1.markSource) `
|
|
479
|
-
##! experimental { aggregate_order_by }
|
|
480
|
-
run: a -> {
|
|
481
|
-
aggregate: x1 is string_agg(astr) { order_by: ai }
|
|
482
|
-
aggregate: x2 is string_agg(astr) { order_by: ai * 2 }
|
|
483
|
-
aggregate: x3 is string_agg(astr) { order_by: ai desc }
|
|
484
|
-
aggregate: x4 is string_agg(astr) { order_by: ai asc }
|
|
485
|
-
aggregate: x5 is string_agg(astr) { order_by: ai asc, ai }
|
|
486
|
-
}
|
|
487
|
-
`).toTranslate();
|
|
488
|
-
});
|
|
489
|
-
});
|
|
490
|
-
describe('aggregate forms', () => {
|
|
491
|
-
const m = (0, test_translator_1.model) `
|
|
492
|
-
source: root is a extend {
|
|
493
|
-
rename: column is ai
|
|
494
|
-
rename: nested is astruct
|
|
495
|
-
rename: inline is aninline
|
|
496
|
-
dimension: field is column * 2
|
|
497
|
-
dimension: field_and_one_field is column + one.column
|
|
498
|
-
dimension: many_field is many.column * 2
|
|
499
|
-
dimension: many_one_field is many.column + one.column
|
|
500
|
-
join_one: one is a extend {
|
|
501
|
-
rename: column is ai
|
|
502
|
-
dimension: field is column * 2
|
|
503
|
-
dimension: many_field is many.column * 2
|
|
504
|
-
join_many: many is a extend {
|
|
505
|
-
rename: column is ai
|
|
506
|
-
dimension: field is column * 2
|
|
507
|
-
dimension: constant is 1
|
|
508
|
-
} on true
|
|
509
|
-
} on true
|
|
510
|
-
join_many: many is a extend {
|
|
511
|
-
rename: column is ai
|
|
512
|
-
dimension: field is column * 2
|
|
513
|
-
dimension: constant is 1
|
|
514
|
-
join_one: one is a extend {
|
|
515
|
-
rename: column is ai
|
|
516
|
-
dimension: field is column * 2
|
|
517
|
-
join_one: one is a extend {
|
|
518
|
-
rename: column is ai
|
|
519
|
-
dimension: field is column * 2
|
|
520
|
-
} on true
|
|
521
|
-
} on true
|
|
522
|
-
} on true
|
|
523
|
-
join_cross: cross is a extend {
|
|
524
|
-
rename: column is ai
|
|
525
|
-
dimension: field is column * 2
|
|
526
|
-
} on true
|
|
527
|
-
}
|
|
528
|
-
`;
|
|
529
|
-
m.translator.translate();
|
|
530
|
-
expect(m).toTranslate();
|
|
531
|
-
const modelX = (0, test_translator_1.makeModelFunc)({
|
|
532
|
-
model: m.translator.modelDef,
|
|
533
|
-
wrap: x => `run: root -> { aggregate: x is ${x} }`,
|
|
534
|
-
});
|
|
535
|
-
test('one.column.min()', () => {
|
|
536
|
-
expect(modelX `one.column.min()`).toTranslate();
|
|
537
|
-
});
|
|
538
|
-
test('one.min(one.column)', () => {
|
|
539
|
-
expect(modelX `one.min(one.column)`).toLog((0, test_translator_1.errorMessage)('Symmetric aggregate function `min` must be written as `min(expression)` or `path.to.field.min()`'));
|
|
540
|
-
});
|
|
541
|
-
test('min(one.column)', () => {
|
|
542
|
-
expect(modelX `min(one.column)`).toTranslate();
|
|
543
|
-
});
|
|
544
|
-
test('min(many.column)', () => {
|
|
545
|
-
expect(modelX `min(many.column)`).toTranslate();
|
|
546
|
-
});
|
|
547
|
-
test('min()', () => {
|
|
548
|
-
expect(modelX `min()`).toLog((0, test_translator_1.errorMessage)('Symmetric aggregate function `min` must be written as `min(expression)` or `path.to.field.min()`'));
|
|
549
|
-
});
|
|
550
|
-
test('source.min(column)', () => {
|
|
551
|
-
expect(modelX `source.min(column)`).toTranslate();
|
|
552
|
-
});
|
|
553
|
-
test('many.column.max()', () => {
|
|
554
|
-
expect(modelX `many.column.max()`).toTranslate();
|
|
555
|
-
});
|
|
556
|
-
test('max(many.column)', () => {
|
|
557
|
-
expect(modelX `max(many.column)`).toTranslate();
|
|
558
|
-
});
|
|
559
|
-
test('max()', () => {
|
|
560
|
-
expect(modelX `max()`).toLog((0, test_translator_1.errorMessage)('Symmetric aggregate function `max` must be written as `max(expression)` or `path.to.field.max()`'));
|
|
561
|
-
});
|
|
562
|
-
test('source.max(many.column)', () => {
|
|
563
|
-
expect(modelX `source.max(many.column)`).toTranslate();
|
|
564
|
-
});
|
|
565
|
-
test('many.column.count()', () => {
|
|
566
|
-
expect((0, test_translator_1.expr) `many.column.count()`).toTranslate();
|
|
567
|
-
});
|
|
568
|
-
test('count()', () => {
|
|
569
|
-
expect(modelX `count()`).toTranslate();
|
|
570
|
-
});
|
|
571
|
-
test('count(many.column)', () => {
|
|
572
|
-
expect(modelX `count(many.column)`).toTranslate();
|
|
573
|
-
});
|
|
574
|
-
test('source.count()', () => {
|
|
575
|
-
expect(modelX `source.count()`).toTranslate();
|
|
576
|
-
});
|
|
577
|
-
test('many.count()', () => {
|
|
578
|
-
expect(modelX `many.count()`).toTranslate();
|
|
579
|
-
});
|
|
580
|
-
test('sum()', () => {
|
|
581
|
-
expect(modelX `sum()`).toLog((0, test_translator_1.errorMessage)('Asymmetric aggregate function `sum` must be written as `path.to.field.sum()`, `path.to.join.sum(expression)`, or `sum(expression)`'));
|
|
582
|
-
});
|
|
583
|
-
test('sum(column)', () => {
|
|
584
|
-
expect(modelX `sum(column)`).toTranslate();
|
|
585
|
-
});
|
|
586
|
-
test('sum(column * 2)', () => {
|
|
587
|
-
expect(modelX `sum(column * 2)`).toTranslate();
|
|
588
|
-
});
|
|
589
|
-
test('column.sum()', () => {
|
|
590
|
-
expect(modelX `column.sum()`).toTranslate();
|
|
591
|
-
});
|
|
592
|
-
test('source.sum(column)', () => {
|
|
593
|
-
expect(modelX `source.sum(column)`).toTranslate();
|
|
594
|
-
});
|
|
595
|
-
test('sum(many.column)', () => {
|
|
596
|
-
expect(modelX `sum(many.column)`).toLog((0, test_translator_1.errorMessage)('Join path is required for this calculation; use `many.column.sum()`'));
|
|
597
|
-
});
|
|
598
|
-
test('source.sum(many.column)', () => {
|
|
599
|
-
expect(modelX `source.sum(many.column)`).toLog((0, test_translator_1.errorMessage)('Cannot compute `sum` across `join_many` relationship `many`; use `many.column.sum()`'));
|
|
600
|
-
});
|
|
601
|
-
test('many.column.sum()', () => {
|
|
602
|
-
expect(modelX `many.column.sum()`).toTranslate();
|
|
603
|
-
});
|
|
604
|
-
test('many.sum(many.column)', () => {
|
|
605
|
-
expect(modelX `many.sum(many.column)`).toTranslate();
|
|
606
|
-
});
|
|
607
|
-
test('sum(one.column)', () => {
|
|
608
|
-
expect(modelX `sum(one.column)`).toLog((0, test_translator_1.warningMessage)('Join path is required for this calculation; use `one.column.sum()` or `source.sum(one.column)` to get a result weighted with respect to `source`'));
|
|
609
|
-
});
|
|
610
|
-
test('sum(many.constant)', () => {
|
|
611
|
-
expect(modelX `sum(many.constant)`).toTranslate();
|
|
612
|
-
});
|
|
613
|
-
test('source.sum(many.constant)', () => {
|
|
614
|
-
expect(modelX `source.sum(many.constant)`).toTranslate();
|
|
615
|
-
});
|
|
616
|
-
test('sum(nested.column)', () => {
|
|
617
|
-
expect(modelX `sum(nested.column)`).toLog((0, test_translator_1.errorMessage)('Join path is required for this calculation; use `nested.column.sum()`'));
|
|
618
|
-
});
|
|
619
|
-
test('nested.column.sum()', () => {
|
|
620
|
-
expect(modelX `nested.column.sum()`).toTranslate();
|
|
621
|
-
});
|
|
622
|
-
test('source.sum(nested.column)', () => {
|
|
623
|
-
expect(modelX `source.sum(nested.column)`).toLog((0, test_translator_1.errorMessage)('Cannot compute `sum` across repeated relationship `nested`; use `nested.column.sum()`'));
|
|
624
|
-
});
|
|
625
|
-
test('can aggregate field defined with no join usage', () => {
|
|
626
|
-
expect((0, test_translator_1.markSource) `
|
|
627
|
-
##! experimental { sql_functions }
|
|
628
|
-
source: s is a extend {
|
|
629
|
-
measure: c is count()
|
|
630
|
-
dimension: f is 1
|
|
631
|
-
}
|
|
632
|
-
run: s -> {
|
|
633
|
-
aggregate: v is f.sum()
|
|
634
|
-
}
|
|
635
|
-
`).toTranslate();
|
|
636
|
-
});
|
|
637
|
-
test('sum(inline.column)', () => {
|
|
638
|
-
expect(modelX `sum(inline.column)`).toLog((0, test_translator_1.warningMessage)('Join path is required for this calculation; use `inline.column.sum()` or `source.sum(inline.column)` to get a result weighted with respect to `source`'));
|
|
639
|
-
});
|
|
640
|
-
test('inline.column.sum()', () => {
|
|
641
|
-
expect(modelX `inline.column.sum()`).toTranslate();
|
|
642
|
-
});
|
|
643
|
-
test('source.sum(inline.column)', () => {
|
|
644
|
-
expect(modelX `source.sum(inline.column)`).toTranslate();
|
|
645
|
-
});
|
|
646
|
-
test('sum(many.field)', () => {
|
|
647
|
-
expect(modelX `sum(many.field)`).toLog((0, test_translator_1.errorMessage)('Join path is required for this calculation; use `many.field.sum()`'));
|
|
648
|
-
});
|
|
649
|
-
test('source.sum(many.field)', () => {
|
|
650
|
-
expect(modelX `source.sum(many.field)`).toLog((0, test_translator_1.errorMessage)('Cannot compute `sum` across `join_many` relationship `many`; use `many.field.sum()`'));
|
|
651
|
-
});
|
|
652
|
-
test('many.field.sum()', () => {
|
|
653
|
-
expect(modelX `many.field.sum()`).toTranslate();
|
|
654
|
-
});
|
|
655
|
-
test('many.sum(many.field)', () => {
|
|
656
|
-
expect(modelX `many.sum(many.field)`).toTranslate();
|
|
657
|
-
});
|
|
658
|
-
test('sum(many.field + many.field)', () => {
|
|
659
|
-
expect(modelX `sum(many.field + many.field)`).toLog((0, test_translator_1.errorMessage)('Join path is required for this calculation; use `many.sum(many.field + many.field)`'));
|
|
660
|
-
});
|
|
661
|
-
test('source.sum(many.field + many.field)', () => {
|
|
662
|
-
expect(modelX `source.sum(many.field + many.field)`).toLog((0, test_translator_1.errorMessage)('Cannot compute `sum` across `join_many` relationship `many`; use `many.sum(many.field + many.field)`'));
|
|
663
|
-
});
|
|
664
|
-
test('many.field + many.field.sum()', () => {
|
|
665
|
-
expect(modelX `many.field + many.field.sum()`).toTranslate();
|
|
666
|
-
});
|
|
667
|
-
test('many.sum(many.field + many.field)', () => {
|
|
668
|
-
expect(modelX `many.sum(many.field + many.field)`).toTranslate();
|
|
669
|
-
});
|
|
670
|
-
test('sum(many_field)', () => {
|
|
671
|
-
expect(modelX `sum(many_field)`).toLog((0, test_translator_1.errorMessage)('Join path is required for this calculation; use `many_field.sum()`'));
|
|
672
|
-
});
|
|
673
|
-
test('source.sum(many_field)', () => {
|
|
674
|
-
expect(modelX `source.sum(many_field)`).toLog((0, test_translator_1.errorMessage)('Cannot compute `sum` across `join_many` relationship `many`; use `many_field.sum()`'));
|
|
675
|
-
});
|
|
676
|
-
test('many_field.sum()', () => {
|
|
677
|
-
expect(modelX `many_field.sum()`).toTranslate();
|
|
678
|
-
});
|
|
679
|
-
test('many.sum(many_field)', () => {
|
|
680
|
-
expect(modelX `many.sum(many_field)`).toTranslate();
|
|
681
|
-
});
|
|
682
|
-
test('sum(one.many_field)', () => {
|
|
683
|
-
expect(modelX `sum(one.many_field)`).toLog((0, test_translator_1.errorMessage)('Join path is required for this calculation; use `one.many_field.sum()`'));
|
|
684
|
-
});
|
|
685
|
-
test('source.sum(one.many_field)', () => {
|
|
686
|
-
expect(modelX `source.sum(one.many_field)`).toLog((0, test_translator_1.errorMessage)('Cannot compute `sum` across `join_many` relationship `many`; use `one.many_field.sum()`'));
|
|
687
|
-
});
|
|
688
|
-
test('one.many_field.sum()', () => {
|
|
689
|
-
expect(modelX `one.many_field.sum()`).toTranslate();
|
|
690
|
-
});
|
|
691
|
-
test('many.sum(one.many_field)', () => {
|
|
692
|
-
expect(modelX `one.many.sum(one.many_field)`).toTranslate();
|
|
693
|
-
});
|
|
694
|
-
test('sum(many.field + one.field)', () => {
|
|
695
|
-
expect(modelX `sum(many.field + one.field)`).toLog((0, test_translator_1.errorMessage)('Aggregated dimensional expression contains multiple join paths; rewrite, for example `sum(first_join.field + second_join.field)` as `first_join.field.sum() + second_join.field.sum()`'));
|
|
696
|
-
});
|
|
697
|
-
test('source.sum(many.field + one.field)', () => {
|
|
698
|
-
expect(modelX `source.sum(many.field + one.field)`).toLog((0, test_translator_1.errorMessage)('Aggregated dimensional expression contains multiple join paths; rewrite, for example `sum(first_join.field + second_join.field)` as `first_join.field.sum() + second_join.field.sum()`'));
|
|
699
|
-
});
|
|
700
|
-
test('many.sum(many.field + one.field)', () => {
|
|
701
|
-
expect(modelX `many.sum(many.field + one.field)`).toTranslate();
|
|
702
|
-
});
|
|
703
|
-
test('many_one_field.sum()', () => {
|
|
704
|
-
expect(modelX `many_one_field.sum()`).toLog((0, test_translator_1.errorMessage)('Aggregated dimensional expression contains multiple join paths; rewrite, for example `sum(first_join.field + second_join.field)` as `first_join.field.sum() + second_join.field.sum()`'));
|
|
705
|
-
});
|
|
706
|
-
test('sum(many_one_field)', () => {
|
|
707
|
-
expect(modelX `sum(many_one_field)`).toLog((0, test_translator_1.errorMessage)('Aggregated dimensional expression contains multiple join paths; rewrite, for example `sum(first_join.field + second_join.field)` as `first_join.field.sum() + second_join.field.sum()`'));
|
|
708
|
-
});
|
|
709
|
-
test('source.sum(many_one_field)', () => {
|
|
710
|
-
expect(modelX `source.sum(many_one_field)`).toLog((0, test_translator_1.errorMessage)('Aggregated dimensional expression contains multiple join paths; rewrite, for example `sum(first_join.field + second_join.field)` as `first_join.field.sum() + second_join.field.sum()`'));
|
|
711
|
-
});
|
|
712
|
-
test('many.sum(many_one_field)', () => {
|
|
713
|
-
expect(modelX `many.sum(many_one_field)`).toTranslate();
|
|
714
|
-
});
|
|
715
|
-
test('sum(many.one.field)', () => {
|
|
716
|
-
expect(modelX `sum(many.one.field)`).toLog((0, test_translator_1.errorMessage)('Join path is required for this calculation; use `many.one.field.sum()` or `many.sum(many.one.field)` to get a result weighted with respect to `many`'));
|
|
717
|
-
});
|
|
718
|
-
test('sum(many.one.one.field)', () => {
|
|
719
|
-
expect(modelX `sum(many.one.one.field)`).toLog((0, test_translator_1.errorMessage)('Join path is required for this calculation; use `many.one.one.field.sum()` or `many.sum(many.one.one.field)` to get a result weighted with respect to `many`'));
|
|
720
|
-
});
|
|
721
|
-
test('many.avg(field)', () => {
|
|
722
|
-
expect(modelX `many.avg(field)`).toTranslate();
|
|
723
|
-
});
|
|
724
|
-
test('one.avg(field)', () => {
|
|
725
|
-
expect(modelX `one.avg(field)`).toTranslate();
|
|
726
|
-
});
|
|
727
|
-
test('cross.avg(field)', () => {
|
|
728
|
-
expect(modelX `cross.avg(field)`).toLog((0, test_translator_1.errorMessage)('Cannot compute `avg` across `join_cross` relationship `cross`; use `field.avg()`'));
|
|
729
|
-
});
|
|
730
|
-
test('cross.avg(cross.field)', () => {
|
|
731
|
-
expect(modelX `cross.avg(cross.field)`).toTranslate();
|
|
732
|
-
});
|
|
733
|
-
test('one.column.sum()', () => {
|
|
734
|
-
expect(modelX `one.column.sum()`).toTranslate();
|
|
735
|
-
});
|
|
736
|
-
test('one.sum(one.column)', () => {
|
|
737
|
-
expect(modelX `one.sum(one.column)`).toTranslate();
|
|
738
|
-
});
|
|
739
|
-
test('source.sum(one.column)', () => {
|
|
740
|
-
expect(modelX `source.sum(one.column)`).toTranslate();
|
|
741
|
-
});
|
|
742
|
-
test('sum(one.column + one.column)', () => {
|
|
743
|
-
expect(modelX `sum(one.column + one.column)`).toLog((0, test_translator_1.warningMessage)('Join path is required for this calculation; use `one.sum(one.column + one.column)` or `source.sum(one.column + one.column)` to get a result weighted with respect to `source`'));
|
|
744
|
-
});
|
|
745
|
-
test('one.sum(one.column + one.column)', () => {
|
|
746
|
-
expect(modelX `one.sum(one.column + one.column)`).toTranslate();
|
|
747
|
-
});
|
|
748
|
-
test('source.sum(one.column + one.column)', () => {
|
|
749
|
-
expect(modelX `source.sum(one.column + one.column)`).toTranslate();
|
|
750
|
-
});
|
|
751
|
-
test('lag(sum(output))', () => {
|
|
752
|
-
expect((0, test_translator_1.model) `
|
|
753
|
-
##! m4warnings=warn
|
|
754
|
-
run: a -> {
|
|
755
|
-
group_by: output is 1
|
|
756
|
-
calculate: bar is lag(sum(output))
|
|
757
|
-
}`).toLog((0, test_translator_1.errorMessage)("'output' is not defined"));
|
|
758
|
-
});
|
|
759
|
-
});
|
|
760
|
-
describe('case statements', () => {
|
|
761
|
-
test('full', () => {
|
|
762
|
-
const e = (0, test_translator_1.expr) `
|
|
763
|
-
case
|
|
764
|
-
when ai = 42 then 'the answer'
|
|
765
|
-
when ai = 54 then 'the questionable answer'
|
|
766
|
-
else 'random'
|
|
767
|
-
end
|
|
768
|
-
`;
|
|
769
|
-
expect(e).toLog((0, test_translator_1.warning)('sql-case'));
|
|
770
|
-
expect(e).compilesTo('{case when {ai = 42} then {"the answer"} when {ai = 54} then {"the questionable answer"} else {"random"}}');
|
|
771
|
-
});
|
|
772
|
-
test('with value', () => {
|
|
773
|
-
const e = (0, test_translator_1.expr) `
|
|
774
|
-
case ai
|
|
775
|
-
when 42 then 'the answer'
|
|
776
|
-
when 54 then 'the questionable answer'
|
|
777
|
-
else 'random'
|
|
778
|
-
end
|
|
779
|
-
`;
|
|
780
|
-
expect(e).toLog((0, test_translator_1.warning)('sql-case'));
|
|
781
|
-
expect(e).compilesTo('{case ai when 42 then {"the answer"} when 54 then {"the questionable answer"} else {"random"}}');
|
|
782
|
-
});
|
|
783
|
-
test('no else', () => {
|
|
784
|
-
const e = (0, test_translator_1.expr) `
|
|
785
|
-
case
|
|
786
|
-
when ai = 42 then 'the answer'
|
|
787
|
-
when ai = 54 then 'the questionable answer'
|
|
788
|
-
end
|
|
789
|
-
`;
|
|
790
|
-
expect(e).toLog((0, test_translator_1.warning)('sql-case'));
|
|
791
|
-
expect(e).compilesTo('{case when {ai = 42} then {"the answer"} when {ai = 54} then {"the questionable answer"}}');
|
|
792
|
-
});
|
|
793
|
-
test('wrong then type', () => {
|
|
794
|
-
expect((0, test_translator_1.expr) `
|
|
795
|
-
case
|
|
796
|
-
when ai = 42 then 'the answer'
|
|
797
|
-
when ai = 54 then 7
|
|
798
|
-
end
|
|
799
|
-
`).toLog((0, test_translator_1.warning)('sql-case'), (0, test_translator_1.error)('case-then-type-does-not-match', {
|
|
800
|
-
thenType: 'number',
|
|
801
|
-
returnType: 'string',
|
|
802
|
-
}));
|
|
803
|
-
});
|
|
804
|
-
test('wrong when type', () => {
|
|
805
|
-
expect((0, test_translator_1.expr) `
|
|
806
|
-
case ai
|
|
807
|
-
when 42 then 'the answer'
|
|
808
|
-
when 'forty-two' then 'the answer but string'
|
|
809
|
-
end
|
|
810
|
-
`).toLog((0, test_translator_1.warning)('sql-case'), (0, test_translator_1.error)('case-when-type-does-not-match', {
|
|
811
|
-
whenType: 'string',
|
|
812
|
-
valueType: 'number',
|
|
813
|
-
}));
|
|
814
|
-
});
|
|
815
|
-
test('wrong else type', () => {
|
|
816
|
-
expect((0, test_translator_1.expr) `
|
|
817
|
-
case
|
|
818
|
-
when ai = 42 then 'the answer'
|
|
819
|
-
else @2020
|
|
820
|
-
end
|
|
821
|
-
`).toLog((0, test_translator_1.warning)('sql-case'), (0, test_translator_1.error)('case-else-type-does-not-match', {
|
|
822
|
-
elseType: 'date',
|
|
823
|
-
returnType: 'string',
|
|
824
|
-
}));
|
|
825
|
-
});
|
|
826
|
-
test('null then type okay second', () => {
|
|
827
|
-
expect((0, test_translator_1.expr) `
|
|
828
|
-
case
|
|
829
|
-
when ai = 42 then 'the answer'
|
|
830
|
-
when ai = 54 then null
|
|
831
|
-
end
|
|
832
|
-
`).toLog((0, test_translator_1.warning)('sql-case'));
|
|
833
|
-
});
|
|
834
|
-
test('null then type okay first', () => {
|
|
835
|
-
expect((0, test_translator_1.expr) `
|
|
836
|
-
case
|
|
837
|
-
when ai = 54 then null
|
|
838
|
-
when ai = 42 then 'the answer'
|
|
839
|
-
end
|
|
840
|
-
`).toLog((0, test_translator_1.warning)('sql-case'));
|
|
841
|
-
});
|
|
842
|
-
test('null else type okay', () => {
|
|
843
|
-
expect((0, test_translator_1.expr) `
|
|
844
|
-
case
|
|
845
|
-
when ai = 42 then 'the answer'
|
|
846
|
-
else null
|
|
847
|
-
end
|
|
848
|
-
`).toLog((0, test_translator_1.warning)('sql-case'));
|
|
849
|
-
});
|
|
850
|
-
test('null then type before else okay', () => {
|
|
851
|
-
expect((0, test_translator_1.expr) `
|
|
852
|
-
case
|
|
853
|
-
when ai = 42 then null
|
|
854
|
-
else 'not the answer'
|
|
855
|
-
end
|
|
856
|
-
`).toLog((0, test_translator_1.warning)('sql-case'));
|
|
857
|
-
});
|
|
858
|
-
test('non boolean when', () => {
|
|
859
|
-
expect((0, test_translator_1.expr) `
|
|
860
|
-
case when ai then null end
|
|
861
|
-
`).toLog((0, test_translator_1.warning)('sql-case'), (0, test_translator_1.error)('case-when-must-be-boolean'));
|
|
862
|
-
});
|
|
863
|
-
test('type of null then second', () => {
|
|
864
|
-
expect(`
|
|
865
|
-
case
|
|
866
|
-
when ai = 42 then 'the answer'
|
|
867
|
-
when ai = 54 then null
|
|
868
|
-
end
|
|
869
|
-
`).toReturnType('string');
|
|
870
|
-
});
|
|
871
|
-
test('type of null then first', () => {
|
|
872
|
-
expect(`
|
|
873
|
-
case
|
|
874
|
-
when ai = 54 then null
|
|
875
|
-
when ai = 42 then 'the answer'
|
|
876
|
-
end
|
|
877
|
-
`).toReturnType('string');
|
|
878
|
-
});
|
|
879
|
-
test('type of null else', () => {
|
|
880
|
-
expect(`
|
|
881
|
-
case
|
|
882
|
-
when ai = 42 then 'the answer'
|
|
883
|
-
else null
|
|
884
|
-
end
|
|
885
|
-
`).toReturnType('string');
|
|
886
|
-
});
|
|
887
|
-
test('type of null then type before else', () => {
|
|
888
|
-
expect(`
|
|
889
|
-
case
|
|
890
|
-
when ai = 42 then null
|
|
891
|
-
else 'not the answer'
|
|
892
|
-
end
|
|
893
|
-
`).toReturnType('string');
|
|
894
|
-
});
|
|
895
|
-
test('replacement for full case', () => {
|
|
896
|
-
const e = (0, test_translator_1.expr) `case
|
|
897
|
-
when ai = 42 then 'the answer'
|
|
898
|
-
when ai = 54 then 'the questionable answer'
|
|
899
|
-
else 'random'
|
|
900
|
-
end`;
|
|
901
|
-
e.translator.translate();
|
|
902
|
-
expect(e.translator.logger.getLog()[0].replacement).toBe("pick 'the answer' when ai = 42 pick 'the questionable answer' when ai = 54 else 'random'");
|
|
903
|
-
});
|
|
904
|
-
test('replacement for case with no else', () => {
|
|
905
|
-
const e = (0, test_translator_1.expr) `case
|
|
906
|
-
when ai = 42 then 'the answer'
|
|
907
|
-
when ai = 54 then 'the questionable answer'
|
|
908
|
-
end`;
|
|
909
|
-
e.translator.translate();
|
|
910
|
-
expect(e.translator.logger.getLog()[0].replacement).toBe("pick 'the answer' when ai = 42 pick 'the questionable answer' when ai = 54 else null");
|
|
911
|
-
});
|
|
912
|
-
test('replacement for case with value', () => {
|
|
913
|
-
const e = (0, test_translator_1.expr) `case ai
|
|
914
|
-
when 42 then 'a'
|
|
915
|
-
when 54 then 'b'
|
|
916
|
-
end`;
|
|
917
|
-
e.translator.translate();
|
|
918
|
-
expect(e.translator.logger.getLog()[0].replacement).toBe("ai ? pick 'a' when 42 pick 'b' when 54 else null");
|
|
919
|
-
});
|
|
920
|
-
test('interaction with pick', () => {
|
|
921
|
-
expect((0, test_translator_1.expr) `
|
|
922
|
-
pick case when true then 'hooray' end when true else null
|
|
923
|
-
`).toLog((0, test_translator_1.warning)('sql-case'));
|
|
924
|
-
});
|
|
925
|
-
});
|
|
926
|
-
describe('pick statements', () => {
|
|
927
|
-
test('full', () => {
|
|
928
|
-
expect((0, test_translator_1.expr) `
|
|
929
|
-
pick 'the answer' when ai = 42
|
|
930
|
-
pick 'the questionable answer' when ai = 54
|
|
931
|
-
else 'random'
|
|
932
|
-
`).toTranslate();
|
|
933
|
-
});
|
|
934
|
-
test('applied', () => {
|
|
935
|
-
expect((0, test_translator_1.expr) `
|
|
936
|
-
astr ?
|
|
937
|
-
pick 'the answer' when = '42'
|
|
938
|
-
pick 'the questionable answer' when = '54'
|
|
939
|
-
else 'random'
|
|
940
|
-
`).toTranslate();
|
|
941
|
-
});
|
|
942
|
-
test('filtering', () => {
|
|
943
|
-
expect((0, test_translator_1.expr) `astr ? pick 'missing value' when NULL`).toTranslate();
|
|
944
|
-
});
|
|
945
|
-
test('null branch with else', () => {
|
|
946
|
-
expect("astr ? pick null when = '42' else 3").toReturnType('number');
|
|
947
|
-
});
|
|
948
|
-
test('null branch no else', () => {
|
|
949
|
-
expect("astr ? pick null when = '42'").toReturnType('string');
|
|
950
|
-
});
|
|
951
|
-
test('null branch no apply', () => {
|
|
952
|
-
expect('pick null when 1 = 1 else 3').toReturnType('number');
|
|
953
|
-
});
|
|
954
|
-
test('tiering', () => {
|
|
955
|
-
expect((0, test_translator_1.expr) `
|
|
956
|
-
ai ?
|
|
957
|
-
pick 1 when < 10
|
|
958
|
-
pick 10 when < 100
|
|
959
|
-
pick 100 when < 1000
|
|
960
|
-
else 10000
|
|
961
|
-
`).toTranslate();
|
|
962
|
-
});
|
|
963
|
-
test('transforming', () => {
|
|
964
|
-
expect((0, test_translator_1.expr) `
|
|
965
|
-
ai ?
|
|
966
|
-
pick 'small' when < 10
|
|
967
|
-
pick 'medium' when < 100
|
|
968
|
-
else 'large'
|
|
969
|
-
`).toTranslate();
|
|
970
|
-
});
|
|
971
|
-
test('when single values', () => {
|
|
972
|
-
expect((0, test_translator_1.expr) `
|
|
973
|
-
ai ?
|
|
974
|
-
pick 'one' when 1
|
|
975
|
-
else 'a lot'
|
|
976
|
-
`).toTranslate();
|
|
977
|
-
});
|
|
978
|
-
test('n-ary without else', () => {
|
|
979
|
-
return expect(`
|
|
980
|
-
source: na is a extend { dimension: d is
|
|
981
|
-
pick 7 when true and true
|
|
982
|
-
}
|
|
983
|
-
`).toLog((0, test_translator_1.error)('pick-missing-else'));
|
|
984
|
-
});
|
|
985
|
-
test('n-ary with mismatch when clauses', () => {
|
|
986
|
-
expect((0, test_translator_1.markSource) `
|
|
987
|
-
source: na is a extend { dimension: d is
|
|
988
|
-
pick 7 when true and true
|
|
989
|
-
pick '7' when true or true
|
|
990
|
-
else 7
|
|
991
|
-
}
|
|
992
|
-
`).toLog((0, test_translator_1.error)('pick-type-does-not-match', {
|
|
993
|
-
pickType: 'string',
|
|
994
|
-
returnType: 'number',
|
|
995
|
-
}));
|
|
996
|
-
});
|
|
997
|
-
test('n-ary with mismatched else clause', () => {
|
|
998
|
-
expect((0, test_translator_1.markSource) `
|
|
999
|
-
source: na is a extend { dimension: d is
|
|
1000
|
-
pick 7 when true and true
|
|
1001
|
-
else '7'
|
|
1002
|
-
}
|
|
1003
|
-
`).toLog((0, test_translator_1.error)('pick-else-type-does-not-match', {
|
|
1004
|
-
elseType: 'string',
|
|
1005
|
-
returnType: 'number',
|
|
1006
|
-
}));
|
|
1007
|
-
});
|
|
1008
|
-
test('applied else mismatch', () => {
|
|
1009
|
-
expect((0, test_translator_1.markSource) `
|
|
1010
|
-
source: na is a extend { dimension: d is
|
|
1011
|
-
7 ? pick 7 when 7 else 'not seven'
|
|
1012
|
-
}
|
|
1013
|
-
`).toLog((0, test_translator_1.error)('pick-else-type-does-not-match', {
|
|
1014
|
-
elseType: 'string',
|
|
1015
|
-
returnType: 'number',
|
|
1016
|
-
}));
|
|
1017
|
-
});
|
|
1018
|
-
test('applied default mismatch', () => {
|
|
1019
|
-
expect((0, test_translator_1.markSource) `
|
|
1020
|
-
source: na is a extend { dimension: d is
|
|
1021
|
-
7 ? pick 'seven' when 7
|
|
1022
|
-
}
|
|
1023
|
-
`).toLog((0, test_translator_1.error)('pick-default-type-does-not-match', {
|
|
1024
|
-
defaultType: 'number',
|
|
1025
|
-
returnType: 'string',
|
|
1026
|
-
}));
|
|
1027
|
-
});
|
|
1028
|
-
test('applied when mismatch', () => {
|
|
1029
|
-
expect((0, test_translator_1.markSource) `
|
|
1030
|
-
source: na is a extend { dimension: d is
|
|
1031
|
-
7 ? pick 'seven' when 7 pick 6 when 6
|
|
1032
|
-
}
|
|
1033
|
-
`).toLog((0, test_translator_1.error)('pick-type-does-not-match', {
|
|
1034
|
-
pickType: 'number',
|
|
1035
|
-
returnType: 'string',
|
|
1036
|
-
}));
|
|
1037
|
-
});
|
|
1038
|
-
});
|
|
1039
|
-
test('paren and applied div', () => {
|
|
1040
|
-
expect('1+(3/4)').compilesTo('{1 + ({3 / 4})}');
|
|
1041
|
-
});
|
|
1042
|
-
test.each([
|
|
1043
|
-
['ats', 'timestamp'],
|
|
1044
|
-
['ad', 'date'],
|
|
1045
|
-
['ai', 'number'],
|
|
1046
|
-
['astr', 'string'],
|
|
1047
|
-
['abool', 'boolean'],
|
|
1048
|
-
])('Can compare field %s (type %s) to NULL', (name, _datatype) => {
|
|
1049
|
-
expect((0, test_translator_1.expr) `${name} IS NULL`).toTranslate();
|
|
1050
|
-
});
|
|
1051
|
-
});
|
|
1052
|
-
describe('alternations as in', () => {
|
|
1053
|
-
test('a=b|c', () => {
|
|
1054
|
-
expect('ai=1|2').compilesTo('{ai in {1,2}}');
|
|
1055
|
-
});
|
|
1056
|
-
test('a!=b|c', () => {
|
|
1057
|
-
expect('ai!=1|2').compilesTo('{ai not in {1,2}}');
|
|
1058
|
-
});
|
|
1059
|
-
test('a=(b|c)', () => {
|
|
1060
|
-
expect('ai=(1|2)').compilesTo('{ai in {1,2}}');
|
|
1061
|
-
});
|
|
1062
|
-
test('a?b|c', () => {
|
|
1063
|
-
expect('ai?1|2').compilesTo('{ai in {1,2}}');
|
|
1064
|
-
});
|
|
1065
|
-
test('a=(b)|c', () => {
|
|
1066
|
-
expect('ai=(1)|2').compilesTo('{ai in {1,2}}');
|
|
1067
|
-
});
|
|
1068
|
-
test('a=b|c|d', () => {
|
|
1069
|
-
expect('ai=1|2|3').compilesTo('{ai in {1,2,3}}');
|
|
1070
|
-
});
|
|
1071
|
-
test('a=(b|c)|d', () => {
|
|
1072
|
-
expect('ai=(1|2)|3').compilesTo('{ai in {1,2,3}}');
|
|
1073
|
-
});
|
|
1074
|
-
test('a=b|(c|d)', () => {
|
|
1075
|
-
expect('ai=1|(2|3)').compilesTo('{ai in {1,2,3}}');
|
|
1076
|
-
});
|
|
1077
|
-
test('a=b|c&d', () => {
|
|
1078
|
-
expect('ai=1|2&3').compilesTo('{{ai = 1} or {{ai = 2} and {ai = 3}}}');
|
|
1079
|
-
});
|
|
1080
|
-
test('a=b|>d', () => {
|
|
1081
|
-
expect('ai=1|>2').compilesTo('{{ai = 1} or {ai > 2}}');
|
|
1082
|
-
expect((0, test_translator_1.expr) `ai=1|>2`).toLog((0, test_translator_1.warningMessage)('Only | seperated values are legal when used with = operator'));
|
|
1083
|
-
});
|
|
1084
|
-
test('a ? (= (b | c))', () => {
|
|
1085
|
-
expect('ai ? (= (1 | 2))').compilesTo('{ai in {1,2}}');
|
|
1086
|
-
});
|
|
1087
|
-
test.skip('a ? (( =1) | 2)', () => {
|
|
1088
|
-
// Current grammar doesn't allow a partial on the LHS of an orbar
|
|
1089
|
-
// mtoy todo turn this test on or delete it when we fix the grammar
|
|
1090
|
-
expect('ai ? (( =1) | 2)').compilesTo('{{a1 = 1} or {ai = 2}}');
|
|
1091
|
-
});
|
|
1092
|
-
test('legacy in', () => {
|
|
1093
|
-
const inExpr = (0, test_translator_1.expr) `ai in (1,2,3)`;
|
|
1094
|
-
expect(inExpr).compilesTo('{ai in {1,2,3}}');
|
|
1095
|
-
expect(inExpr).toLog((0, test_translator_1.warningMessage)('Use = (a|b|c) instead of IN (a,b,c)'));
|
|
1096
|
-
});
|
|
1097
|
-
});
|
|
1098
|
-
describe('rigor around ? and =', () => { });
|
|
1099
|
-
describe('sql native fields in schema', () => {
|
|
1100
|
-
test('sql native reference in result allowed', () => {
|
|
1101
|
-
const uModel = new test_translator_1.TestTranslator('run: a->{ group_by: aun }');
|
|
1102
|
-
expect(uModel).toTranslate();
|
|
1103
|
-
});
|
|
1104
|
-
test('sql native reference can be compared to NULL', () => {
|
|
1105
|
-
const uModel = new test_translator_1.TestTranslator('run: a->{ where: aun is not null; select: * }');
|
|
1106
|
-
expect(uModel).toTranslate();
|
|
1107
|
-
});
|
|
1108
|
-
test('flag unsupported equality', () => {
|
|
1109
|
-
// because we don't know if the two unsupported types are comparable
|
|
1110
|
-
const uModel = new test_translator_1.TestTranslator('run: ab->{ where: aun = b.aun select: * }');
|
|
1111
|
-
expect(uModel).toLog((0, test_translator_1.error)('sql-native-not-allowed-in-expression', { rawType: undefined }));
|
|
1112
|
-
});
|
|
1113
|
-
test('flag unsupported compare', () => {
|
|
1114
|
-
// because we don't know if the two unsupported types are comparable
|
|
1115
|
-
const uModel = new test_translator_1.TestTranslator('run: ab->{ where: aun > b.aun select: * }');
|
|
1116
|
-
expect(uModel).toLog((0, test_translator_1.error)('sql-native-not-allowed-in-expression', { rawType: undefined }));
|
|
1117
|
-
});
|
|
1118
|
-
test('allow unsupported equality when raw types match', () => {
|
|
1119
|
-
const uModel = new test_translator_1.TestTranslator('run: ab->{ where: aweird = b.aweird select: * }');
|
|
1120
|
-
expect(uModel).toTranslate();
|
|
1121
|
-
});
|
|
1122
|
-
test('flag not applied to unsupported', () => {
|
|
1123
|
-
const uModel = new test_translator_1.TestTranslator('source: x is a extend { dimension: notUn is not aun }');
|
|
1124
|
-
expect(uModel).toLog((0, test_translator_1.error)('sql-native-not-allowed-in-expression', { rawType: undefined }));
|
|
1125
|
-
});
|
|
1126
|
-
test('allow unsupported to be cast', () => {
|
|
1127
|
-
const uModel = new test_translator_1.TestTranslator('source: x is a extend { dimension: notUn is aun::string }');
|
|
1128
|
-
expect(uModel).toTranslate();
|
|
1129
|
-
});
|
|
1130
|
-
test('negative numbers are not tokens', () => {
|
|
1131
|
-
expect((0, test_translator_1.expr) `ai-1`).toTranslate();
|
|
1132
|
-
});
|
|
1133
|
-
describe('sql functions', () => {
|
|
1134
|
-
test('can aggregate a sql_ function', () => {
|
|
1135
|
-
expect(`
|
|
1136
|
-
##! experimental.sql_functions
|
|
1137
|
-
run: a -> {
|
|
1138
|
-
aggregate: x is sum(sql_number("\${ai} * 2"))
|
|
1139
|
-
}
|
|
1140
|
-
`).toTranslate();
|
|
1141
|
-
});
|
|
1142
|
-
test('error when interpolating field that does not exist', () => {
|
|
1143
|
-
expect(`
|
|
1144
|
-
##! experimental.sql_functions
|
|
1145
|
-
run: a -> {
|
|
1146
|
-
group_by: x is sql_number("\${asdfasdf} * 2")
|
|
1147
|
-
}
|
|
1148
|
-
`).toLog((0, test_translator_1.errorMessage)("Invalid interpolation: 'asdfasdf' is not defined"));
|
|
1149
|
-
});
|
|
1150
|
-
test('error when using sql_ function without experiment', () => {
|
|
1151
|
-
expect(`
|
|
1152
|
-
run: a -> {
|
|
1153
|
-
group_by: x is sql_number("\${asdfasdf} * 2")
|
|
1154
|
-
}
|
|
1155
|
-
`).toLog((0, test_translator_1.errorMessage)('Cannot use sql_function `sql_number`; use `sql_functions` experiment to enable this behavior'));
|
|
1156
|
-
});
|
|
1157
|
-
});
|
|
1158
|
-
describe('cast', () => {
|
|
1159
|
-
// The "+ 1"s are there to make sure the result is of type 'number'
|
|
1160
|
-
test('sql cast', () => {
|
|
1161
|
-
expect((0, test_translator_1.expr) `ai::'integer' + 1`).toTranslate();
|
|
1162
|
-
expect((0, test_translator_1.expr) `ai::"integer" + 1`).toTranslate();
|
|
1163
|
-
expect((0, test_translator_1.expr) `ai::"""integer""" + 1`).toTranslate();
|
|
1164
|
-
});
|
|
1165
|
-
test('sql safe cast', () => {
|
|
1166
|
-
expect((0, test_translator_1.expr) `astr:::'integer' + 1`).toTranslate();
|
|
1167
|
-
expect((0, test_translator_1.expr) `astr:::"integer" + 1`).toTranslate();
|
|
1168
|
-
expect((0, test_translator_1.expr) `astr:::"""integer""" + 1`).toTranslate();
|
|
1169
|
-
});
|
|
1170
|
-
test('malloy cast', () => {
|
|
1171
|
-
expect((0, test_translator_1.expr) `astr::number + 1`).toTranslate();
|
|
1172
|
-
});
|
|
1173
|
-
test('malloy safe cast', () => {
|
|
1174
|
-
expect((0, test_translator_1.expr) `astr:::number + 1`).toTranslate();
|
|
1175
|
-
});
|
|
1176
|
-
test('sql cast illegal type name', () => {
|
|
1177
|
-
expect((0, test_translator_1.expr) `astr::"stuff 'n' things"`).toLog((0, test_translator_1.errorMessage)("Cast type `stuff 'n' things` is invalid for standardsql dialect"));
|
|
1178
|
-
});
|
|
1179
|
-
});
|
|
1180
|
-
});
|
|
1181
|
-
//# sourceMappingURL=expressions.spec.js.map
|