@malloydata/malloy 0.0.80-dev230907183434 → 0.0.80
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/lang/ast/expressions/expr-aggregate-function.d.ts +8 -2
- package/dist/lang/ast/expressions/expr-aggregate-function.js +211 -12
- package/dist/lang/ast/expressions/expr-asymmetric.d.ts +2 -1
- package/dist/lang/ast/expressions/expr-asymmetric.js +5 -2
- package/dist/lang/ast/expressions/expr-avg.d.ts +1 -1
- package/dist/lang/ast/expressions/expr-avg.js +2 -2
- package/dist/lang/ast/expressions/expr-sum.d.ts +1 -1
- package/dist/lang/ast/expressions/expr-sum.js +2 -2
- package/dist/lang/ast/field-space/static-space.js +19 -2
- package/dist/lang/ast/field-space/struct-space-field-base.d.ts +2 -1
- package/dist/lang/ast/field-space/struct-space-field-base.js +3 -0
- package/dist/lang/ast/types/lookup-result.d.ts +5 -0
- package/dist/lang/ast/types/malloy-element.d.ts +1 -0
- package/dist/lang/ast/types/malloy-element.js +4 -0
- package/dist/lang/lib/Malloy/MalloyLexer.d.ts +75 -74
- package/dist/lang/lib/Malloy/MalloyLexer.js +1101 -1094
- package/dist/lang/lib/Malloy/MalloyParser.d.ts +91 -78
- package/dist/lang/lib/Malloy/MalloyParser.js +866 -801
- package/dist/lang/lib/Malloy/MalloyParserListener.d.ts +13 -0
- package/dist/lang/lib/Malloy/MalloyParserVisitor.d.ts +8 -0
- package/dist/lang/malloy-to-ast.d.ts +3 -0
- package/dist/lang/malloy-to-ast.js +76 -25
- package/dist/lang/parse-malloy.d.ts +1 -0
- package/dist/lang/parse-malloy.js +13 -0
- package/dist/lang/test/expressions.spec.js +241 -25
- package/dist/lang/test/parse.spec.js +1 -127
- package/dist/lang/test/query.spec.js +1 -1
- package/dist/lang/test/test-translator.d.ts +7 -1
- package/dist/lang/test/test-translator.js +40 -13
- package/dist/model/utils.d.ts +1 -0
- package/dist/model/utils.js +101 -1
- package/package.json +1 -1
|
@@ -42,6 +42,7 @@ import { ExprLogicalAndContext } from "./MalloyParser";
|
|
|
42
42
|
import { ExprLogicalOrContext } from "./MalloyParser";
|
|
43
43
|
import { ExprCoalesceContext } from "./MalloyParser";
|
|
44
44
|
import { ExprCountDisinctContext } from "./MalloyParser";
|
|
45
|
+
import { ExprPathlessAggregateContext } from "./MalloyParser";
|
|
45
46
|
import { ExprAggregateContext } from "./MalloyParser";
|
|
46
47
|
import { ExprExprContext } from "./MalloyParser";
|
|
47
48
|
import { ExprAggFuncContext } from "./MalloyParser";
|
|
@@ -727,6 +728,18 @@ export interface MalloyParserListener extends ParseTreeListener {
|
|
|
727
728
|
* @param ctx the parse tree
|
|
728
729
|
*/
|
|
729
730
|
exitExprCountDisinct?: (ctx: ExprCountDisinctContext) => void;
|
|
731
|
+
/**
|
|
732
|
+
* Enter a parse tree produced by the `exprPathlessAggregate`
|
|
733
|
+
* labeled alternative in `MalloyParser.fieldExpr`.
|
|
734
|
+
* @param ctx the parse tree
|
|
735
|
+
*/
|
|
736
|
+
enterExprPathlessAggregate?: (ctx: ExprPathlessAggregateContext) => void;
|
|
737
|
+
/**
|
|
738
|
+
* Exit a parse tree produced by the `exprPathlessAggregate`
|
|
739
|
+
* labeled alternative in `MalloyParser.fieldExpr`.
|
|
740
|
+
* @param ctx the parse tree
|
|
741
|
+
*/
|
|
742
|
+
exitExprPathlessAggregate?: (ctx: ExprPathlessAggregateContext) => void;
|
|
730
743
|
/**
|
|
731
744
|
* Enter a parse tree produced by the `exprAggregate`
|
|
732
745
|
* labeled alternative in `MalloyParser.fieldExpr`.
|
|
@@ -42,6 +42,7 @@ import { ExprLogicalAndContext } from "./MalloyParser";
|
|
|
42
42
|
import { ExprLogicalOrContext } from "./MalloyParser";
|
|
43
43
|
import { ExprCoalesceContext } from "./MalloyParser";
|
|
44
44
|
import { ExprCountDisinctContext } from "./MalloyParser";
|
|
45
|
+
import { ExprPathlessAggregateContext } from "./MalloyParser";
|
|
45
46
|
import { ExprAggregateContext } from "./MalloyParser";
|
|
46
47
|
import { ExprExprContext } from "./MalloyParser";
|
|
47
48
|
import { ExprAggFuncContext } from "./MalloyParser";
|
|
@@ -515,6 +516,13 @@ export interface MalloyParserVisitor<Result> extends ParseTreeVisitor<Result> {
|
|
|
515
516
|
* @return the visitor result
|
|
516
517
|
*/
|
|
517
518
|
visitExprCountDisinct?: (ctx: ExprCountDisinctContext) => Result;
|
|
519
|
+
/**
|
|
520
|
+
* Visit a parse tree produced by the `exprPathlessAggregate`
|
|
521
|
+
* labeled alternative in `MalloyParser.fieldExpr`.
|
|
522
|
+
* @param ctx the parse tree
|
|
523
|
+
* @return the visitor result
|
|
524
|
+
*/
|
|
525
|
+
visitExprPathlessAggregate?: (ctx: ExprPathlessAggregateContext) => Result;
|
|
518
526
|
/**
|
|
519
527
|
* Visit a parse tree produced by the `exprAggregate`
|
|
520
528
|
* labeled alternative in `MalloyParser.fieldExpr`.
|
|
@@ -175,7 +175,10 @@ export declare class MalloyToAST extends AbstractParseTreeVisitor<ast.MalloyElem
|
|
|
175
175
|
visitExprCompare(pcx: parse.ExprCompareContext): ast.ExprCompare;
|
|
176
176
|
visitExprCountDisinct(pcx: parse.ExprCountDisinctContext): ast.ExprCountDistinct;
|
|
177
177
|
visitExprUngroup(pcx: parse.ExprUngroupContext): ast.ExprUngroup;
|
|
178
|
+
symmetricAggregateUsageError(aggFunc: string): string;
|
|
179
|
+
asymmetricAggregateUsageError(aggFunc: string): string;
|
|
178
180
|
visitExprAggregate(pcx: parse.ExprAggregateContext): ast.ExpressionDef;
|
|
181
|
+
visitExprPathlessAggregate(pcx: parse.ExprPathlessAggregateContext): ast.ExpressionDef;
|
|
179
182
|
visitExprApply(pcx: parse.ExprApplyContext): ast.Apply;
|
|
180
183
|
visitExprRange(pcx: parse.ExprRangeContext): ast.Range;
|
|
181
184
|
visitExprCast(pcx: parse.ExprCastContext): ast.ExpressionDef;
|
|
@@ -1006,6 +1006,9 @@ class MalloyToAST extends AbstractParseTreeVisitor_1.AbstractParseTreeVisitor {
|
|
|
1006
1006
|
throw this.internalError(pcx, `untranslatable comparison operator '${op}'`);
|
|
1007
1007
|
}
|
|
1008
1008
|
visitExprCountDisinct(pcx) {
|
|
1009
|
+
if (this.m4WarningsEnabled()) {
|
|
1010
|
+
this.contextError(pcx, "'count(DISTINCT expression)' deprecated, use 'count(expression)' instead");
|
|
1011
|
+
}
|
|
1009
1012
|
return this.astAt(new ast.ExprCountDistinct(this.getFieldExpr(pcx.fieldExpr())), pcx);
|
|
1010
1013
|
}
|
|
1011
1014
|
visitExprUngroup(pcx) {
|
|
@@ -1013,53 +1016,101 @@ class MalloyToAST extends AbstractParseTreeVisitor_1.AbstractParseTreeVisitor {
|
|
|
1013
1016
|
const kw = pcx.ungroup().text.toLowerCase();
|
|
1014
1017
|
return this.astAt(new ast.ExprUngroup(kw === 'all' ? kw : 'exclude', this.getFieldExpr(pcx.fieldExpr()), flist), pcx);
|
|
1015
1018
|
}
|
|
1019
|
+
symmetricAggregateUsageError(aggFunc) {
|
|
1020
|
+
return `Symmetric aggregate function \`${aggFunc}\` must be written as \`${aggFunc}(expression)\` or \`path.to.field.${aggFunc}()\``;
|
|
1021
|
+
}
|
|
1022
|
+
asymmetricAggregateUsageError(aggFunc) {
|
|
1023
|
+
return `Asymmetric aggregate function \`${aggFunc}\` must be written as \`path.to.field.${aggFunc}()\`, \`path.to.join.${aggFunc}(expression)\`, or \`${aggFunc}(expression)\``;
|
|
1024
|
+
}
|
|
1016
1025
|
visitExprAggregate(pcx) {
|
|
1017
1026
|
const pathCx = pcx.fieldPath();
|
|
1018
|
-
const path = pathCx
|
|
1019
|
-
? this.getFieldPath(pathCx, ast.ExpressionFieldReference)
|
|
1020
|
-
: undefined;
|
|
1027
|
+
const path = this.getFieldPath(pathCx, ast.ExpressionFieldReference);
|
|
1021
1028
|
const source = pathCx && path ? this.astAt(path, pathCx) : undefined;
|
|
1029
|
+
const aggFunc = pcx.aggregate().text.toLowerCase();
|
|
1022
1030
|
const exprDef = pcx.fieldExpr();
|
|
1023
1031
|
if (pcx.aggregate().COUNT()) {
|
|
1024
1032
|
if (exprDef) {
|
|
1025
|
-
this.contextError(exprDef, '
|
|
1033
|
+
this.contextError(exprDef, 'Expression illegal inside path.count()');
|
|
1026
1034
|
}
|
|
1027
1035
|
return new ast.ExprCount(source);
|
|
1028
1036
|
}
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1037
|
+
const expr = exprDef ? this.getFieldExpr(exprDef) : undefined;
|
|
1038
|
+
if (aggFunc === 'min' || aggFunc === 'max') {
|
|
1039
|
+
if (expr) {
|
|
1040
|
+
this.contextError(pcx, this.symmetricAggregateUsageError(aggFunc));
|
|
1041
|
+
}
|
|
1042
|
+
else {
|
|
1043
|
+
const idRef = this.astAt(new ast.ExprIdReference(path), pathCx);
|
|
1044
|
+
return aggFunc === 'min'
|
|
1045
|
+
? new ast.ExprMin(idRef)
|
|
1046
|
+
: new ast.ExprMax(idRef);
|
|
1047
|
+
}
|
|
1048
|
+
}
|
|
1049
|
+
else if (aggFunc === 'avg') {
|
|
1050
|
+
return new ast.ExprAvg(expr, source);
|
|
1051
|
+
}
|
|
1052
|
+
else if (aggFunc === 'sum') {
|
|
1053
|
+
return new ast.ExprSum(expr, source);
|
|
1054
|
+
}
|
|
1055
|
+
else {
|
|
1056
|
+
this.contextError(pcx, `Cannot parse aggregate function ${aggFunc}`);
|
|
1034
1057
|
}
|
|
1058
|
+
return new ast.ExprNULL();
|
|
1059
|
+
}
|
|
1060
|
+
/*
|
|
1061
|
+
* error toodos
|
|
1062
|
+
* for an aggregate function being called with no path predeeding ...
|
|
1063
|
+
* [X] sum/avg() -- always an error
|
|
1064
|
+
* [X] sum/avg(fieldName) -- suggest fieldName.sum
|
|
1065
|
+
* [X] sum/avg(expr) -- suggest source.sum(expr)
|
|
1066
|
+
* [X] count() -- OK
|
|
1067
|
+
* [X] count(anything) OK
|
|
1068
|
+
* ?? source.count()
|
|
1069
|
+
* [X] min/max() -- always an error
|
|
1070
|
+
* [X] min/max(expr) -- OK
|
|
1071
|
+
* ?? source.min/max(expr)
|
|
1072
|
+
*/
|
|
1073
|
+
visitExprPathlessAggregate(pcx) {
|
|
1074
|
+
const exprDef = pcx.fieldExpr();
|
|
1035
1075
|
const expr = exprDef ? this.getFieldExpr(exprDef) : undefined;
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1076
|
+
const source = undefined;
|
|
1077
|
+
const aggFunc = pcx.aggregate().text.toLowerCase();
|
|
1078
|
+
if (this.m4WarningsEnabled()) {
|
|
1079
|
+
if (pcx.STAR()) {
|
|
1080
|
+
this.contextError(pcx, `* illegal inside ${aggFunc}()`, 'warn');
|
|
1039
1081
|
}
|
|
1040
|
-
|
|
1082
|
+
}
|
|
1083
|
+
if (aggFunc === 'count') {
|
|
1084
|
+
return this.astAt(expr ? new ast.ExprCountDistinct(expr) : new ast.ExprCount(), pcx);
|
|
1085
|
+
}
|
|
1086
|
+
else if (aggFunc === 'min') {
|
|
1087
|
+
if (expr) {
|
|
1041
1088
|
return new ast.ExprMin(expr);
|
|
1042
1089
|
}
|
|
1043
1090
|
else {
|
|
1044
|
-
this.contextError(pcx,
|
|
1091
|
+
this.contextError(pcx, this.symmetricAggregateUsageError(aggFunc));
|
|
1045
1092
|
}
|
|
1046
1093
|
}
|
|
1047
|
-
else if (
|
|
1048
|
-
if (
|
|
1049
|
-
this.contextError(pcx, 'Path not legal for max()');
|
|
1050
|
-
}
|
|
1051
|
-
else if (expr) {
|
|
1094
|
+
else if (aggFunc === 'max') {
|
|
1095
|
+
if (expr) {
|
|
1052
1096
|
return new ast.ExprMax(expr);
|
|
1053
1097
|
}
|
|
1054
1098
|
else {
|
|
1055
|
-
this.contextError(pcx,
|
|
1099
|
+
this.contextError(pcx, this.symmetricAggregateUsageError(aggFunc));
|
|
1056
1100
|
}
|
|
1057
1101
|
}
|
|
1058
|
-
else
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1102
|
+
else {
|
|
1103
|
+
if (expr === undefined) {
|
|
1104
|
+
this.contextError(pcx, this.asymmetricAggregateUsageError(aggFunc));
|
|
1105
|
+
return new ast.ExprNULL();
|
|
1106
|
+
}
|
|
1107
|
+
const explicitSource = pcx.SOURCE_KW() !== undefined;
|
|
1108
|
+
if (aggFunc === 'avg') {
|
|
1109
|
+
return new ast.ExprAvg(expr, source, explicitSource);
|
|
1110
|
+
}
|
|
1111
|
+
else if (aggFunc === 'sum') {
|
|
1112
|
+
return new ast.ExprSum(expr, source, explicitSource);
|
|
1113
|
+
}
|
|
1063
1114
|
}
|
|
1064
1115
|
return new ast.ExprNULL();
|
|
1065
1116
|
}
|
|
@@ -124,6 +124,7 @@ export declare abstract class MalloyTranslation {
|
|
|
124
124
|
problemResponse(): ProblemResponse;
|
|
125
125
|
problems(): LogMessage[];
|
|
126
126
|
getLineMap(url: string): string[] | undefined;
|
|
127
|
+
codeAtLocation(location: DocumentLocation): string;
|
|
127
128
|
prettyErrors(): string;
|
|
128
129
|
childRequest(importURL: string): ModelDataRequest;
|
|
129
130
|
getChildExports(importURL: string): Record<string, NamedModelObject>;
|
|
@@ -575,6 +575,19 @@ class MalloyTranslation {
|
|
|
575
575
|
return (_b = theChild.parseStep.sourceInfo) === null || _b === void 0 ? void 0 : _b.lines;
|
|
576
576
|
}
|
|
577
577
|
}
|
|
578
|
+
codeAtLocation(location) {
|
|
579
|
+
const lines = this.getLineMap(location.url) || [];
|
|
580
|
+
if (location.range.start.line === location.range.end.line) {
|
|
581
|
+
return lines[location.range.start.line].slice(location.range.start.character, location.range.end.character);
|
|
582
|
+
}
|
|
583
|
+
let result = '';
|
|
584
|
+
result += lines[location.range.start.line].slice(location.range.start.character);
|
|
585
|
+
for (let lineNumber = location.range.start.line + 1; lineNumber < location.range.end.line; lineNumber++) {
|
|
586
|
+
result += lines[lineNumber];
|
|
587
|
+
}
|
|
588
|
+
result += lines[location.range.end.line].slice(0, location.range.end.character);
|
|
589
|
+
return result;
|
|
590
|
+
}
|
|
578
591
|
prettyErrors() {
|
|
579
592
|
let lovely = '';
|
|
580
593
|
let inFile = '';
|
|
@@ -172,31 +172,247 @@ describe('expressions', () => {
|
|
|
172
172
|
`).translationToFailWith('Filtered expression requires an aggregate computation');
|
|
173
173
|
});
|
|
174
174
|
describe('aggregate forms', () => {
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
175
|
+
const m = (0, test_translator_1.model) `
|
|
176
|
+
source: root is a {
|
|
177
|
+
rename: column is ai
|
|
178
|
+
rename: nested is astruct
|
|
179
|
+
dimension: field is column * 2
|
|
180
|
+
dimension: many_field is many.column * 2
|
|
181
|
+
dimension: many_one_field is many.column + one.column
|
|
182
|
+
join_one: one is a {
|
|
183
|
+
rename: column is ai
|
|
184
|
+
dimension: field is column * 2
|
|
185
|
+
dimension: many_field is many.column * 2
|
|
186
|
+
join_many: many is a {
|
|
187
|
+
rename: column is ai
|
|
188
|
+
dimension: field is column * 2
|
|
189
|
+
dimension: constant is 1
|
|
190
|
+
} on true
|
|
191
|
+
} on true
|
|
192
|
+
join_many: many is a {
|
|
193
|
+
rename: column is ai
|
|
194
|
+
dimension: field is column * 2
|
|
195
|
+
dimension: constant is 1
|
|
196
|
+
join_one: one is a {
|
|
197
|
+
rename: column is ai
|
|
198
|
+
dimension: field is column * 2
|
|
199
|
+
join_one: one is a {
|
|
200
|
+
rename: column is ai
|
|
201
|
+
dimension: field is column * 2
|
|
202
|
+
} on true
|
|
203
|
+
} on true
|
|
204
|
+
} on true
|
|
205
|
+
join_cross: cross is a {
|
|
206
|
+
rename: column is ai
|
|
207
|
+
dimension: field is column * 2
|
|
208
|
+
} on true
|
|
209
|
+
}
|
|
210
|
+
`;
|
|
211
|
+
m.translator.translate();
|
|
212
|
+
const modelX = (0, test_translator_1.makeModelFunc)({
|
|
213
|
+
model: m.translator.modelDef,
|
|
214
|
+
wrap: x => `run: root -> { aggregate: x is ${x} }`,
|
|
215
|
+
});
|
|
216
|
+
test('one.column.min()', () => {
|
|
217
|
+
expect(modelX `one.column.min()`).toTranslate();
|
|
218
|
+
});
|
|
219
|
+
test('one.min(one.column)', () => {
|
|
220
|
+
expect(modelX `one.min(one.column)`).translationToFailWith('Symmetric aggregate function `min` must be written as `min(expression)` or `path.to.field.min()`');
|
|
221
|
+
});
|
|
222
|
+
test('min(one.column)', () => {
|
|
223
|
+
expect(modelX `min(one.column)`).toTranslate();
|
|
224
|
+
});
|
|
225
|
+
test('min(many.column)', () => {
|
|
226
|
+
expect(modelX `min(many.column)`).toTranslate();
|
|
227
|
+
});
|
|
228
|
+
test('min()', () => {
|
|
229
|
+
expect(modelX `min()`).translationToFailWith('Symmetric aggregate function `min` must be written as `min(expression)` or `path.to.field.min()`');
|
|
230
|
+
});
|
|
231
|
+
test('source.min(column)', () => {
|
|
232
|
+
expect(modelX `source.min(column)`).toTranslate();
|
|
233
|
+
});
|
|
234
|
+
test('many.column.max()', () => {
|
|
235
|
+
expect(modelX `many.column.max()`).toTranslate();
|
|
236
|
+
});
|
|
237
|
+
test('max(many.column)', () => {
|
|
238
|
+
expect(modelX `max(many.column)`).toTranslate();
|
|
239
|
+
});
|
|
240
|
+
test('max()', () => {
|
|
241
|
+
expect(modelX `max()`).translationToFailWith('Symmetric aggregate function `max` must be written as `max(expression)` or `path.to.field.max()`');
|
|
242
|
+
});
|
|
243
|
+
test('source.max(many.column)', () => {
|
|
244
|
+
expect(modelX `source.max(many.column)`).toTranslate();
|
|
245
|
+
});
|
|
246
|
+
test('many.column.count()', () => {
|
|
247
|
+
expect((0, test_translator_1.expr) `many.column.count()`).toTranslate();
|
|
248
|
+
});
|
|
249
|
+
test('count()', () => {
|
|
250
|
+
expect(modelX `count()`).toTranslate();
|
|
251
|
+
});
|
|
252
|
+
test('count(many.column)', () => {
|
|
253
|
+
expect(modelX `count(many.column)`).toTranslate();
|
|
254
|
+
});
|
|
255
|
+
test('source.count()', () => {
|
|
256
|
+
expect(modelX `source.count()`).toTranslate();
|
|
257
|
+
});
|
|
258
|
+
test('many.count()', () => {
|
|
259
|
+
expect(modelX `many.count()`).toTranslate();
|
|
260
|
+
});
|
|
261
|
+
test('sum()', () => {
|
|
262
|
+
expect(modelX `sum()`).translationToFailWith('Asymmetric aggregate function `sum` must be written as `path.to.field.sum()`, `path.to.join.sum(expression)`, or `sum(expression)`');
|
|
263
|
+
});
|
|
264
|
+
test('sum(column)', () => {
|
|
265
|
+
expect(modelX `sum(column)`).toTranslate();
|
|
266
|
+
});
|
|
267
|
+
test('sum(column * 2)', () => {
|
|
268
|
+
expect(modelX `sum(column * 2)`).toTranslate();
|
|
269
|
+
});
|
|
270
|
+
test('column.sum()', () => {
|
|
271
|
+
expect(modelX `column.sum()`).toTranslate();
|
|
272
|
+
});
|
|
273
|
+
test('source.sum(column)', () => {
|
|
274
|
+
expect(modelX `source.sum(column)`).toTranslate();
|
|
275
|
+
});
|
|
276
|
+
test('sum(many.column)', () => {
|
|
277
|
+
expect(modelX `sum(many.column)`).translationToFailWith('Join path is required for this calculation; use `many.column.sum()`');
|
|
278
|
+
});
|
|
279
|
+
test('source.sum(many.column)', () => {
|
|
280
|
+
expect(modelX `source.sum(many.column)`).translationToFailWith('Cannot compute asymmetric aggregate across forward `join_many` relationship `many`; use `many.column.sum()`');
|
|
281
|
+
});
|
|
282
|
+
test('many.column.sum()', () => {
|
|
283
|
+
expect(modelX `many.column.sum()`).toTranslate();
|
|
284
|
+
});
|
|
285
|
+
test('many.sum(many.column)', () => {
|
|
286
|
+
expect(modelX `many.sum(many.column)`).toTranslate();
|
|
287
|
+
});
|
|
288
|
+
test('sum(one.column)', () => {
|
|
289
|
+
expect(modelX `sum(one.column)`).toTranslateWithWarnings('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`');
|
|
290
|
+
});
|
|
291
|
+
test('sum(many.constant)', () => {
|
|
292
|
+
expect(modelX `sum(many.constant)`).toTranslate();
|
|
293
|
+
});
|
|
294
|
+
test('source.sum(many.constant)', () => {
|
|
295
|
+
expect(modelX `source.sum(many.constant)`).toTranslate();
|
|
296
|
+
});
|
|
297
|
+
test('sum(nested.column)', () => {
|
|
298
|
+
expect(modelX `sum(nested.column)`).toTranslateWithWarnings('Join path is required for this calculation; use `nested.column.sum()` or `source.sum(nested.column)` to get a result weighted with respect to `source`');
|
|
299
|
+
});
|
|
300
|
+
test('nested.column.sum()', () => {
|
|
301
|
+
expect(modelX `nested.column.sum()`).toTranslate();
|
|
302
|
+
});
|
|
303
|
+
test('source.sum(nested.column)', () => {
|
|
304
|
+
expect(modelX `source.sum(nested.column)`).toTranslate();
|
|
305
|
+
});
|
|
306
|
+
test('sum(many.field)', () => {
|
|
307
|
+
expect(modelX `sum(many.field)`).translationToFailWith('Join path is required for this calculation; use `many.field.sum()`');
|
|
308
|
+
});
|
|
309
|
+
test('source.sum(many.field)', () => {
|
|
310
|
+
expect(modelX `source.sum(many.field)`).translationToFailWith('Cannot compute asymmetric aggregate across forward `join_many` relationship `many`; use `many.field.sum()`');
|
|
311
|
+
});
|
|
312
|
+
test('many.field.sum()', () => {
|
|
313
|
+
expect(modelX `many.field.sum()`).toTranslate();
|
|
314
|
+
});
|
|
315
|
+
test('many.sum(many.field)', () => {
|
|
316
|
+
expect(modelX `many.sum(many.field)`).toTranslate();
|
|
317
|
+
});
|
|
318
|
+
test('sum(many.field + many.field)', () => {
|
|
319
|
+
expect(modelX `sum(many.field + many.field)`).translationToFailWith('Join path is required for this calculation; use `many.sum(many.field + many.field)`');
|
|
320
|
+
});
|
|
321
|
+
test('source.sum(many.field + many.field)', () => {
|
|
322
|
+
expect(modelX `source.sum(many.field + many.field)`).translationToFailWith('Cannot compute asymmetric aggregate across forward `join_many` relationship `many`; use `many.sum(many.field + many.field)`');
|
|
323
|
+
});
|
|
324
|
+
test('many.field + many.field.sum()', () => {
|
|
325
|
+
expect(modelX `many.field + many.field.sum()`).toTranslate();
|
|
326
|
+
});
|
|
327
|
+
test('many.sum(many.field + many.field)', () => {
|
|
328
|
+
expect(modelX `many.sum(many.field + many.field)`).toTranslate();
|
|
329
|
+
});
|
|
330
|
+
test('sum(many_field)', () => {
|
|
331
|
+
expect(modelX `sum(many_field)`).translationToFailWith('Join path is required for this calculation; use `many_field.sum()`');
|
|
332
|
+
});
|
|
333
|
+
test('source.sum(many_field)', () => {
|
|
334
|
+
expect(modelX `source.sum(many_field)`).translationToFailWith('Cannot compute asymmetric aggregate across forward `join_many` relationship `many`; use `many_field.sum()`');
|
|
335
|
+
});
|
|
336
|
+
test('many_field.sum()', () => {
|
|
337
|
+
expect(modelX `many_field.sum()`).toTranslate();
|
|
338
|
+
});
|
|
339
|
+
test('many.sum(many_field)', () => {
|
|
340
|
+
expect(modelX `many.sum(many_field)`).toTranslate();
|
|
341
|
+
});
|
|
342
|
+
test('sum(one.many_field)', () => {
|
|
343
|
+
expect(modelX `sum(one.many_field)`).translationToFailWith('Join path is required for this calculation; use `one.many_field.sum()`');
|
|
344
|
+
});
|
|
345
|
+
test('source.sum(one.many_field)', () => {
|
|
346
|
+
expect(modelX `source.sum(one.many_field)`).translationToFailWith('Cannot compute asymmetric aggregate across forward `join_many` relationship `many`; use `one.many_field.sum()`');
|
|
347
|
+
});
|
|
348
|
+
test('one.many_field.sum()', () => {
|
|
349
|
+
expect(modelX `one.many_field.sum()`).toTranslate();
|
|
350
|
+
});
|
|
351
|
+
test('many.sum(one.many_field)', () => {
|
|
352
|
+
expect(modelX `one.many.sum(one.many_field)`).toTranslate();
|
|
353
|
+
});
|
|
354
|
+
test('sum(many.field + one.field)', () => {
|
|
355
|
+
expect(modelX `sum(many.field + one.field)`).translationToFailWith('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()`');
|
|
356
|
+
});
|
|
357
|
+
test('source.sum(many.field + one.field)', () => {
|
|
358
|
+
expect(modelX `source.sum(many.field + one.field)`).translationToFailWith('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()`');
|
|
359
|
+
});
|
|
360
|
+
test('many.sum(many.field + one.field)', () => {
|
|
361
|
+
expect(modelX `many.sum(many.field + one.field)`).toTranslate();
|
|
362
|
+
});
|
|
363
|
+
test('sum(many_one_field)', () => {
|
|
364
|
+
expect(modelX `sum(many_one_field)`).translationToFailWith('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()`');
|
|
365
|
+
});
|
|
366
|
+
test('source.sum(many_one_field)', () => {
|
|
367
|
+
expect(modelX `source.sum(many_one_field)`).translationToFailWith('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()`');
|
|
368
|
+
});
|
|
369
|
+
test('many.sum(many_one_field)', () => {
|
|
370
|
+
expect(modelX `many.sum(many_one_field)`).toTranslate();
|
|
371
|
+
});
|
|
372
|
+
test('sum(many.one.field)', () => {
|
|
373
|
+
expect(modelX `sum(many.one.field)`).translationToFailWith('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`');
|
|
374
|
+
});
|
|
375
|
+
test('sum(many.one.one.field)', () => {
|
|
376
|
+
expect(modelX `sum(many.one.one.field)`).translationToFailWith('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`');
|
|
377
|
+
});
|
|
378
|
+
test('many.avg(field)', () => {
|
|
379
|
+
expect(modelX `many.avg(field)`).toTranslate();
|
|
380
|
+
});
|
|
381
|
+
test('one.avg(field)', () => {
|
|
382
|
+
expect(modelX `one.avg(field)`).toTranslate();
|
|
383
|
+
});
|
|
384
|
+
test('cross.avg(field)', () => {
|
|
385
|
+
expect(modelX `cross.avg(field)`).translationToFailWith('Cannot compute asymmetric aggregate across `join_cross` relationship `cross`; use `field.avg()`');
|
|
386
|
+
});
|
|
387
|
+
test('cross.avg(cross.field)', () => {
|
|
388
|
+
expect(modelX `cross.avg(cross.field)`).toTranslate();
|
|
389
|
+
});
|
|
390
|
+
test('one.column.sum()', () => {
|
|
391
|
+
expect(modelX `one.column.sum()`).toTranslate();
|
|
392
|
+
});
|
|
393
|
+
test('one.sum(one.column)', () => {
|
|
394
|
+
expect(modelX `one.sum(one.column)`).toTranslate();
|
|
395
|
+
});
|
|
396
|
+
test('source.sum(one.column)', () => {
|
|
397
|
+
expect(modelX `source.sum(one.column)`).toTranslate();
|
|
398
|
+
});
|
|
399
|
+
test('sum(one.column + one.column)', () => {
|
|
400
|
+
expect(modelX `sum(one.column + one.column)`).toTranslateWithWarnings('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`');
|
|
401
|
+
});
|
|
402
|
+
test('one.sum(one.column + one.column)', () => {
|
|
403
|
+
expect(modelX `one.sum(one.column + one.column)`).toTranslate();
|
|
404
|
+
});
|
|
405
|
+
test('source.sum(one.column + one.column)', () => {
|
|
406
|
+
expect(modelX `source.sum(one.column + one.column)`).toTranslate();
|
|
407
|
+
});
|
|
408
|
+
test('lag(sum(output))', () => {
|
|
409
|
+
expect((0, test_translator_1.model) `
|
|
410
|
+
##! m4warnings
|
|
411
|
+
run: a -> {
|
|
412
|
+
group_by: output is 1
|
|
413
|
+
calculate: bar is lag(sum(output))
|
|
414
|
+
}`).translationToFailWith("'output' is not defined");
|
|
415
|
+
});
|
|
200
416
|
});
|
|
201
417
|
describe('pick statements', () => {
|
|
202
418
|
test('full', () => {
|
|
@@ -301,133 +301,7 @@ describe('error cascading', () => {
|
|
|
301
301
|
'days(@2003 to err)': 'number',
|
|
302
302
|
};
|
|
303
303
|
const scalars = Object.keys(typedScalars);
|
|
304
|
-
const aggregates = [
|
|
305
|
-
'measure_err { where: true }',
|
|
306
|
-
'count(distinct err)',
|
|
307
|
-
'b.sum(err)',
|
|
308
|
-
'b.stddev(err)',
|
|
309
|
-
];
|
|
310
|
-
const ungroupedAggregates = ['all(measure_err)'];
|
|
311
|
-
test('dependent errors do not cascade', () => {
|
|
312
|
-
expect(`
|
|
313
|
-
source: a1 is a {
|
|
314
|
-
join_one: b with astr
|
|
315
|
-
dimension:
|
|
316
|
-
${'err is null'}
|
|
317
|
-
${scalars.map((d, i) => `e${i} is ${d}`).join('\n ')}
|
|
318
|
-
measure:
|
|
319
|
-
measure_err is count(distinct foo),
|
|
320
|
-
${[...aggregates, ...ungroupedAggregates]
|
|
321
|
-
.map((m, i) => `e${i + scalars.length} is ${m}`)
|
|
322
|
-
.join('\n ')}
|
|
323
|
-
}
|
|
324
|
-
`).translationToFailWith("Cannot define 'err', unexpected type: null", "'foo' is not defined");
|
|
325
|
-
});
|
|
326
|
-
test('error type inference is good', () => {
|
|
327
|
-
for (const scalar of scalars) {
|
|
328
|
-
const source = `
|
|
329
|
-
source: a1 is a {
|
|
330
|
-
dimension:
|
|
331
|
-
${'err is null'}
|
|
332
|
-
dim is length(${scalar}, 1)
|
|
333
|
-
}
|
|
334
|
-
`;
|
|
335
|
-
expect(source).translationToFailWith("Cannot define 'err', unexpected type: null", `No matching overload for function length(${typedScalars[scalar]}, number)`);
|
|
336
|
-
}
|
|
337
|
-
});
|
|
338
|
-
test('eval space of errors is preserved', () => {
|
|
339
|
-
expect(`
|
|
340
|
-
source: a1 is a {
|
|
341
|
-
join_one: b with astr
|
|
342
|
-
}
|
|
343
|
-
query: a1 -> {
|
|
344
|
-
group_by:
|
|
345
|
-
${'err is null'}
|
|
346
|
-
aggregate:
|
|
347
|
-
measure_err is count(distinct foo)
|
|
348
|
-
calculate:
|
|
349
|
-
${scalars
|
|
350
|
-
.map((d, i) => `e${i} is lag(${d})`)
|
|
351
|
-
.join('\n ')}
|
|
352
|
-
${aggregates
|
|
353
|
-
.map((m, i) => `e${i + scalars.length} is lag(${m})`)
|
|
354
|
-
.join('\n ')}
|
|
355
|
-
}
|
|
356
|
-
`).translationToFailWith("Cannot define 'err', unexpected type: null", "'foo' is not defined");
|
|
357
|
-
});
|
|
358
|
-
});
|
|
359
|
-
describe('error cascading', () => {
|
|
360
|
-
test('errors can appear in multiple top level objects', () => {
|
|
361
|
-
expect((0, test_translator_1.markSource) `
|
|
362
|
-
source: a1 is a { dimension: ${'x is count()'} }
|
|
363
|
-
source: a2 is a { dimension: ${'x is count()'} }
|
|
364
|
-
`).translationToFailWith('Cannot use an aggregate field in a dimension declaration, did you mean to use a measure declaration instead?', 'Cannot use an aggregate field in a dimension declaration, did you mean to use a measure declaration instead?');
|
|
365
|
-
});
|
|
366
|
-
const typedScalars = {
|
|
367
|
-
'err': 'error',
|
|
368
|
-
'@2003 ~ @2003 for err hours': 'boolean',
|
|
369
|
-
'err.hour': 'timestamp',
|
|
370
|
-
'err.month': 'date',
|
|
371
|
-
'day_of_week(err)': 'number',
|
|
372
|
-
'err::string': 'string',
|
|
373
|
-
'-err': 'number',
|
|
374
|
-
'err * 1': 'number',
|
|
375
|
-
'1 * err': 'number',
|
|
376
|
-
'err / 1': 'number',
|
|
377
|
-
'1 / err': 'number',
|
|
378
|
-
'err % 1': 'number',
|
|
379
|
-
'1 % err': 'number',
|
|
380
|
-
'err + 1': 'number',
|
|
381
|
-
'1 + err': 'number',
|
|
382
|
-
'err - 1': 'number',
|
|
383
|
-
'1 - err': 'number',
|
|
384
|
-
'@2003 ? err for 1 minute': 'boolean',
|
|
385
|
-
'@2003 ? @2003 for err minutes': 'boolean',
|
|
386
|
-
'3 ? > err & > 3': 'boolean',
|
|
387
|
-
'3 ? > 3 & > err': 'boolean',
|
|
388
|
-
'3 ? > err | > 3': 'boolean',
|
|
389
|
-
'3 ? > 3 | > err': 'boolean',
|
|
390
|
-
'err ? > 3': 'boolean',
|
|
391
|
-
'3 ? > err': 'boolean',
|
|
392
|
-
'1 > err': 'boolean',
|
|
393
|
-
'err > 1': 'boolean',
|
|
394
|
-
'1 >= err': 'boolean',
|
|
395
|
-
'err >= 1': 'boolean',
|
|
396
|
-
'1 < err': 'boolean',
|
|
397
|
-
'err < 1': 'boolean',
|
|
398
|
-
'1 <= err': 'boolean',
|
|
399
|
-
'err <= 1': 'boolean',
|
|
400
|
-
'1 = err': 'boolean',
|
|
401
|
-
'err = 1': 'boolean',
|
|
402
|
-
'1 != err': 'boolean',
|
|
403
|
-
'err != 1': 'boolean',
|
|
404
|
-
'1 ~ err': 'boolean',
|
|
405
|
-
'err ~ 1': 'boolean',
|
|
406
|
-
'1 !~ err': 'boolean',
|
|
407
|
-
'err !~ 1': 'boolean',
|
|
408
|
-
'not err': 'boolean',
|
|
409
|
-
'err and true': 'boolean',
|
|
410
|
-
'true and err': 'boolean',
|
|
411
|
-
'err or true': 'boolean',
|
|
412
|
-
'true or err': 'boolean',
|
|
413
|
-
'err ?? 1': 'number',
|
|
414
|
-
'1 ?? err': 'number',
|
|
415
|
-
'cast(err as number)': 'number',
|
|
416
|
-
'(err)': 'error',
|
|
417
|
-
'length(err)': 'number',
|
|
418
|
-
'pick err when true else false': 'boolean',
|
|
419
|
-
'pick true when err else false': 'boolean',
|
|
420
|
-
'pick true when true else err': 'boolean',
|
|
421
|
-
'days(err to @2003)': 'number',
|
|
422
|
-
'days(@2003 to err)': 'number',
|
|
423
|
-
};
|
|
424
|
-
const scalars = Object.keys(typedScalars);
|
|
425
|
-
const aggregates = [
|
|
426
|
-
'measure_err { where: true }',
|
|
427
|
-
'count(distinct err)',
|
|
428
|
-
'b.sum(err)',
|
|
429
|
-
'b.stddev(err)',
|
|
430
|
-
];
|
|
304
|
+
const aggregates = ['measure_err { where: true }'];
|
|
431
305
|
const ungroupedAggregates = ['all(measure_err)'];
|
|
432
306
|
test('dependent errors do not cascade', () => {
|
|
433
307
|
expect(`
|
|
@@ -579,7 +579,7 @@ describe('query:', () => {
|
|
|
579
579
|
query: a extend { join_one: aaa on astr=aaa.astr } -> {
|
|
580
580
|
group_by: aaa.big_i
|
|
581
581
|
aggregate: s is aaa.big_i.sum()
|
|
582
|
-
calculate: a is lag(big_i
|
|
582
|
+
calculate: a is lag(big_i)
|
|
583
583
|
}`).toTranslate();
|
|
584
584
|
});
|
|
585
585
|
});
|