@malloydata/malloy 0.0.43-dev230621212545 → 0.0.43-dev230622144455
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/lang/ast/ast-utils.js +1 -1
- package/dist/lang/ast/expressions/expr-coalesce.js +4 -0
- package/dist/lang/ast/expressions/expr-func.js +6 -34
- package/dist/lang/ast/expressions/expr-granular-time.js +14 -2
- package/dist/lang/ast/expressions/expr-minus.js +2 -2
- package/dist/lang/ast/expressions/expr-time-extract.js +22 -4
- package/dist/lang/ast/expressions/for-range.d.ts +1 -1
- package/dist/lang/ast/expressions/for-range.js +10 -2
- package/dist/lang/ast/expressions/pick-when.js +5 -1
- package/dist/lang/ast/field-space/query-spaces.js +3 -6
- package/dist/lang/ast/field-space/reference-field.js +1 -1
- package/dist/lang/ast/fragtype-utils.d.ts +1 -1
- package/dist/lang/ast/fragtype-utils.js +3 -2
- package/dist/lang/ast/parameters/constant-parameter.js +1 -1
- package/dist/lang/ast/query-items/field-declaration.js +5 -8
- package/dist/lang/ast/types/expression-def.js +29 -3
- package/dist/lang/ast/types/malloy-element.js +0 -8
- package/dist/lang/ast/types/space-param.js +1 -1
- package/dist/lang/test/parse.spec.js +261 -16
- package/dist/malloy.d.ts +2 -1
- package/dist/malloy.js +3 -0
- package/dist/model/malloy_types.d.ts +6 -3
- package/package.json +1 -1
|
@@ -48,8 +48,12 @@ class ExprCoalesce extends expression_def_1.ExpressionDef {
|
|
|
48
48
|
* SQL, but I decided that is will happen when the "expressions are true
|
|
49
49
|
* trees" rewrite happens.
|
|
50
50
|
*/
|
|
51
|
+
if (!fragtype_utils_1.FT.typeEq(maybeNull, whenNull)) {
|
|
52
|
+
this.log(`Mismatched types for coalesce (${maybeNull.dataType}, ${whenNull.dataType})`);
|
|
53
|
+
}
|
|
51
54
|
return {
|
|
52
55
|
...whenNull,
|
|
56
|
+
dataType: maybeNull.dataType === 'error' ? whenNull.dataType : maybeNull.dataType,
|
|
53
57
|
expressionType: (0, model_1.maxExpressionType)(maybeNull.expressionType, whenNull.expressionType),
|
|
54
58
|
value: (0, model_1.mkExpr) `COALESCE(${maybeNull.value},${whenNull.value})`,
|
|
55
59
|
evalSpace: (0, model_1.mergeEvalSpaces)(maybeNull.evalSpace, whenNull.evalSpace),
|
|
@@ -70,21 +70,11 @@ class ExprFunc extends expression_def_1.ExpressionDef {
|
|
|
70
70
|
const func = (_c = this.modelEntry(this.name.toLowerCase())) === null || _c === void 0 ? void 0 : _c.entry;
|
|
71
71
|
if (func === undefined) {
|
|
72
72
|
this.log(`Unknown function '${this.name}'. Use '${this.name}!(...)' to call a SQL function directly.`);
|
|
73
|
-
return
|
|
74
|
-
dataType: 'unknown',
|
|
75
|
-
expressionType: 'scalar',
|
|
76
|
-
value: [],
|
|
77
|
-
evalSpace: 'constant',
|
|
78
|
-
};
|
|
73
|
+
return (0, ast_utils_1.errorFor)('unknown function');
|
|
79
74
|
}
|
|
80
75
|
else if (func.type !== 'function') {
|
|
81
76
|
this.log(`Cannot call '${this.name}', which is of type ${func.type}`);
|
|
82
|
-
return
|
|
83
|
-
dataType: 'unknown',
|
|
84
|
-
expressionType: 'scalar',
|
|
85
|
-
value: [],
|
|
86
|
-
evalSpace: 'constant',
|
|
87
|
-
};
|
|
77
|
+
return (0, ast_utils_1.errorFor)('called non function');
|
|
88
78
|
}
|
|
89
79
|
// Find the 'implicit argument' for aggregate functions called like `some_join.some_field.agg(...args)`
|
|
90
80
|
// where the full arg list is `(some_field, ...args)`.
|
|
@@ -127,12 +117,7 @@ class ExprFunc extends expression_def_1.ExpressionDef {
|
|
|
127
117
|
this.log(`No matching overload for function ${this.name}(${argExprs
|
|
128
118
|
.map(e => e.dataType)
|
|
129
119
|
.join(', ')})`);
|
|
130
|
-
return
|
|
131
|
-
dataType: 'unknown',
|
|
132
|
-
expressionType: 'scalar',
|
|
133
|
-
value: [],
|
|
134
|
-
evalSpace: 'constant',
|
|
135
|
-
};
|
|
120
|
+
return (0, ast_utils_1.errorFor)('no matching overload');
|
|
136
121
|
}
|
|
137
122
|
const { overload, expressionTypeErrors, evalSpaceErrors, nullabilityErrors } = result;
|
|
138
123
|
// Report errors for expression type mismatch
|
|
@@ -171,12 +156,7 @@ class ExprFunc extends expression_def_1.ExpressionDef {
|
|
|
171
156
|
this.log(`Cannot call function ${this.name}(${argExprs
|
|
172
157
|
.map(e => e.dataType)
|
|
173
158
|
.join(', ')}) with source`);
|
|
174
|
-
return
|
|
175
|
-
dataType: 'unknown',
|
|
176
|
-
expressionType,
|
|
177
|
-
value: [],
|
|
178
|
-
evalSpace: 'constant',
|
|
179
|
-
};
|
|
159
|
+
return (0, ast_utils_1.errorFor)('cannot call with source');
|
|
180
160
|
}
|
|
181
161
|
const funcCall = [
|
|
182
162
|
{
|
|
@@ -189,12 +169,7 @@ class ExprFunc extends expression_def_1.ExpressionDef {
|
|
|
189
169
|
];
|
|
190
170
|
if (type.dataType === 'any') {
|
|
191
171
|
this.log(`Invalid return type ${type.dataType} for function '${this.name}'`);
|
|
192
|
-
return
|
|
193
|
-
dataType: 'unknown',
|
|
194
|
-
expressionType,
|
|
195
|
-
value: [],
|
|
196
|
-
evalSpace: 'constant',
|
|
197
|
-
};
|
|
172
|
+
return (0, ast_utils_1.errorFor)('invalid return type');
|
|
198
173
|
}
|
|
199
174
|
const maxEvalSpace = (0, malloy_types_1.mergeEvalSpaces)(...argExprs.map(e => e.evalSpace));
|
|
200
175
|
// If the merged eval space of all args is constant, the result is constant.
|
|
@@ -240,10 +215,7 @@ function findOverload(func, args) {
|
|
|
240
215
|
// does not make sense to limit function calls to not allow nulls, since have
|
|
241
216
|
// so little control over nullability.
|
|
242
217
|
arg.dataType === 'null' ||
|
|
243
|
-
|
|
244
|
-
// I think we may want to add an `error` type for nodes generated from errors,
|
|
245
|
-
// then make `error` propagate without generating more errors.
|
|
246
|
-
arg.dataType === 'unknown';
|
|
218
|
+
arg.dataType === 'error';
|
|
247
219
|
// Check expression type errors
|
|
248
220
|
if (paramT.expressionType) {
|
|
249
221
|
const expressionTypeMatch = (0, malloy_types_1.isExpressionTypeLEQ)(arg.expressionType, paramT.expressionType);
|
|
@@ -70,8 +70,20 @@ class ExprGranularTime extends expression_def_1.ExpressionDef {
|
|
|
70
70
|
}
|
|
71
71
|
return tsVal;
|
|
72
72
|
}
|
|
73
|
-
|
|
74
|
-
|
|
73
|
+
if (exprVal.dataType !== 'error') {
|
|
74
|
+
this.log(`Cannot do time truncation on type '${exprVal.dataType}'`);
|
|
75
|
+
}
|
|
76
|
+
const returnType = exprVal.dataType === 'error'
|
|
77
|
+
? (0, malloy_types_1.isDateUnit)(timeframe)
|
|
78
|
+
? 'date'
|
|
79
|
+
: 'timestamp'
|
|
80
|
+
: exprVal.dataType;
|
|
81
|
+
return {
|
|
82
|
+
...exprVal,
|
|
83
|
+
dataType: returnType,
|
|
84
|
+
value: (0, ast_utils_1.errorFor)('granularity typecheck').value,
|
|
85
|
+
evalSpace: 'constant',
|
|
86
|
+
};
|
|
75
87
|
}
|
|
76
88
|
apply(fs, op, left) {
|
|
77
89
|
return this.getRange(fs).apply(fs, op, left);
|
|
@@ -37,9 +37,9 @@ class ExprMinus extends expression_def_1.ExpressionDef {
|
|
|
37
37
|
const expr = this.expr.getExpression(fs);
|
|
38
38
|
if (this.typeCheck(this.expr, expr)) {
|
|
39
39
|
if (expr.value.length > 1) {
|
|
40
|
-
return { ...expr, value: ['-(', ...expr.value, ')'] };
|
|
40
|
+
return { ...expr, dataType: 'number', value: ['-(', ...expr.value, ')'] };
|
|
41
41
|
}
|
|
42
|
-
return { ...expr, value: ['-', ...expr.value] };
|
|
42
|
+
return { ...expr, dataType: 'number', value: ['-', ...expr.value] };
|
|
43
43
|
}
|
|
44
44
|
return (0, ast_utils_1.errorFor)('negate requires number');
|
|
45
45
|
}
|
|
@@ -54,6 +54,16 @@ class ExprTimeExtract extends expression_def_1.ExpressionDef {
|
|
|
54
54
|
if (from instanceof range_1.Range) {
|
|
55
55
|
let first = from.first.getExpression(fs);
|
|
56
56
|
let last = from.last.getExpression(fs);
|
|
57
|
+
const expressionType = (0, malloy_types_1.maxExpressionType)(first.expressionType, last.expressionType);
|
|
58
|
+
const evalSpace = (0, malloy_types_1.mergeEvalSpaces)(first.evalSpace, last.evalSpace);
|
|
59
|
+
if (first.dataType === 'error' || last.dataType === 'error') {
|
|
60
|
+
return {
|
|
61
|
+
dataType: 'number',
|
|
62
|
+
expressionType,
|
|
63
|
+
evalSpace,
|
|
64
|
+
value: (0, ast_utils_1.errorFor)('extract from error').value,
|
|
65
|
+
};
|
|
66
|
+
}
|
|
57
67
|
if (!(0, malloy_types_1.isTimeFieldType)(first.dataType)) {
|
|
58
68
|
from.first.log(`Can't extract ${extractTo} from '${first.dataType}'`);
|
|
59
69
|
return (0, ast_utils_1.errorFor)(`${extractTo} bad type ${first.dataType}`);
|
|
@@ -95,8 +105,8 @@ class ExprTimeExtract extends expression_def_1.ExpressionDef {
|
|
|
95
105
|
}
|
|
96
106
|
return {
|
|
97
107
|
dataType: 'number',
|
|
98
|
-
expressionType
|
|
99
|
-
evalSpace
|
|
108
|
+
expressionType,
|
|
109
|
+
evalSpace,
|
|
100
110
|
value: [
|
|
101
111
|
{
|
|
102
112
|
type: 'dialect',
|
|
@@ -125,8 +135,16 @@ class ExprTimeExtract extends expression_def_1.ExpressionDef {
|
|
|
125
135
|
],
|
|
126
136
|
};
|
|
127
137
|
}
|
|
128
|
-
|
|
129
|
-
|
|
138
|
+
if (argV.dataType !== 'error') {
|
|
139
|
+
this.log(`${this.extractText}() requires time type, not '${argV.dataType}'`);
|
|
140
|
+
}
|
|
141
|
+
return {
|
|
142
|
+
dataType: 'number',
|
|
143
|
+
expressionType: argV.expressionType,
|
|
144
|
+
evalSpace: argV.evalSpace,
|
|
145
|
+
value: (0, ast_utils_1.errorFor)(`${this.extractText} bad type ${argV.dataType}`)
|
|
146
|
+
.value,
|
|
147
|
+
};
|
|
130
148
|
}
|
|
131
149
|
}
|
|
132
150
|
throw this.internalError(`Illegal extraction unit '${this.extractText}'`);
|
|
@@ -7,7 +7,7 @@ export declare class ForRange extends ExpressionDef {
|
|
|
7
7
|
readonly duration: ExpressionDef;
|
|
8
8
|
readonly timeframe: Timeframe;
|
|
9
9
|
elementType: string;
|
|
10
|
-
legalChildTypes: import("
|
|
10
|
+
legalChildTypes: import("../../../model").TypeDesc[];
|
|
11
11
|
constructor(from: ExpressionDef, duration: ExpressionDef, timeframe: Timeframe);
|
|
12
12
|
apply(fs: FieldSpace, op: string, expr: ExpressionDef): ExprValue;
|
|
13
13
|
requestExpression(_fs: FieldSpace): undefined;
|
|
@@ -23,6 +23,7 @@
|
|
|
23
23
|
*/
|
|
24
24
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
25
25
|
exports.ForRange = void 0;
|
|
26
|
+
const model_1 = require("../../../model");
|
|
26
27
|
const ast_utils_1 = require("../ast-utils");
|
|
27
28
|
const fragtype_utils_1 = require("../fragtype-utils");
|
|
28
29
|
const time_utils_1 = require("../time-utils");
|
|
@@ -46,8 +47,15 @@ class ForRange extends expression_def_1.ExpressionDef {
|
|
|
46
47
|
}
|
|
47
48
|
const nV = this.duration.getExpression(fs);
|
|
48
49
|
if (nV.dataType !== 'number') {
|
|
49
|
-
|
|
50
|
-
|
|
50
|
+
if (nV.dataType !== 'error') {
|
|
51
|
+
this.log(`FOR duration count must be a number, not '${nV.dataType}'`);
|
|
52
|
+
}
|
|
53
|
+
return {
|
|
54
|
+
dataType: 'boolean',
|
|
55
|
+
evalSpace: (0, model_1.mergeEvalSpaces)(startV.evalSpace, checkV.evalSpace),
|
|
56
|
+
expressionType: (0, model_1.maxExpressionType)(startV.expressionType, checkV.expressionType),
|
|
57
|
+
value: (0, ast_utils_1.errorFor)('for not number').value,
|
|
58
|
+
};
|
|
51
59
|
}
|
|
52
60
|
const units = this.timeframe.text;
|
|
53
61
|
// If the duration resolution is smaller than date, we have
|
|
@@ -30,7 +30,11 @@ const expression_def_1 = require("../types/expression-def");
|
|
|
30
30
|
const malloy_element_1 = require("../types/malloy-element");
|
|
31
31
|
const utils_1 = require("./utils");
|
|
32
32
|
function typeCoalesce(ev1, ev2) {
|
|
33
|
-
return ev1 === undefined ||
|
|
33
|
+
return ev1 === undefined ||
|
|
34
|
+
ev1.dataType === 'null' ||
|
|
35
|
+
ev1.dataType === 'error'
|
|
36
|
+
? ev2
|
|
37
|
+
: ev1;
|
|
34
38
|
}
|
|
35
39
|
class Pick extends expression_def_1.ExpressionDef {
|
|
36
40
|
constructor(choices, elsePick) {
|
|
@@ -292,14 +292,11 @@ class QuerySpace extends refined_space_1.RefinedSpace {
|
|
|
292
292
|
}
|
|
293
293
|
else {
|
|
294
294
|
const typeDesc = field.typeDesc();
|
|
295
|
-
// Filter out fields whose type is
|
|
296
|
-
// isn't sent to the compiler, where it will wig out.
|
|
297
|
-
// subsequent stages of a query, the field is not in the input struct. But since
|
|
298
|
-
// the error message says "Cannot define x which has unknown type", it makes sense
|
|
299
|
-
// that it wouldn't be "defined" in later stages.
|
|
295
|
+
// Filter out fields whose type is 'error', which means that a totally bad field
|
|
296
|
+
// isn't sent to the compiler, where it will wig out.
|
|
300
297
|
// TODO Figure out how to make errors generated by `canContain` go in the right place,
|
|
301
298
|
// maybe by adding a logable element to SpaceFields.
|
|
302
|
-
if (typeDesc.dataType !== '
|
|
299
|
+
if (typeDesc.dataType !== 'error' && this.canContain(typeDesc)) {
|
|
303
300
|
fields.push(fieldQueryDef);
|
|
304
301
|
}
|
|
305
302
|
}
|
|
@@ -45,7 +45,7 @@ class ReferenceField extends space_field_1.SpaceField {
|
|
|
45
45
|
if (this.res !== undefined && this.res.found) {
|
|
46
46
|
return this.res.found.typeDesc();
|
|
47
47
|
}
|
|
48
|
-
return { dataType: '
|
|
48
|
+
return { dataType: 'error', expressionType: 'scalar', evalSpace: 'input' };
|
|
49
49
|
}
|
|
50
50
|
}
|
|
51
51
|
exports.ReferenceField = ReferenceField;
|
|
@@ -21,7 +21,7 @@ export declare class FT {
|
|
|
21
21
|
* @param right Right type
|
|
22
22
|
* @param nullOk True if a NULL is an acceptable match
|
|
23
23
|
*/
|
|
24
|
-
static typeEq(left: TypeDesc, right: TypeDesc, nullOk?: boolean): boolean;
|
|
24
|
+
static typeEq(left: TypeDesc, right: TypeDesc, nullOk?: boolean, errorOk?: boolean): boolean;
|
|
25
25
|
/**
|
|
26
26
|
*
|
|
27
27
|
* For error messages, returns a comma seperated list of readable names
|
|
@@ -59,10 +59,11 @@ class FT {
|
|
|
59
59
|
* @param right Right type
|
|
60
60
|
* @param nullOk True if a NULL is an acceptable match
|
|
61
61
|
*/
|
|
62
|
-
static typeEq(left, right, nullOk = false) {
|
|
62
|
+
static typeEq(left, right, nullOk = false, errorOk = true) {
|
|
63
63
|
const maybeEq = left.dataType === right.dataType;
|
|
64
64
|
const nullEq = nullOk && (left.dataType === 'null' || right.dataType === 'null');
|
|
65
|
-
|
|
65
|
+
const errorEq = errorOk && (left.dataType === 'error' || right.dataType === 'error');
|
|
66
|
+
return maybeEq || nullEq || errorEq;
|
|
66
67
|
}
|
|
67
68
|
/**
|
|
68
69
|
*
|
|
@@ -37,7 +37,7 @@ class ConstantParameter extends has_parameter_1.HasParameter {
|
|
|
37
37
|
this.log(`Unexpected expression type '${cVal.dataType}'`);
|
|
38
38
|
return {
|
|
39
39
|
value: ['XXX-type-mismatch-error-XXX'],
|
|
40
|
-
type: '
|
|
40
|
+
type: 'error',
|
|
41
41
|
name: this.name,
|
|
42
42
|
constant: true,
|
|
43
43
|
};
|
|
@@ -66,8 +66,8 @@ class FieldDeclaration extends malloy_element_1.MalloyElement {
|
|
|
66
66
|
catch (error) {
|
|
67
67
|
this.log(`Cannot define '${exprName}', ${error.message}`);
|
|
68
68
|
return {
|
|
69
|
-
name:
|
|
70
|
-
type: '
|
|
69
|
+
name: exprName,
|
|
70
|
+
type: 'error',
|
|
71
71
|
};
|
|
72
72
|
}
|
|
73
73
|
const compressValue = (0, utils_1.compressExpr)(exprValue.value);
|
|
@@ -96,17 +96,14 @@ class FieldDeclaration extends malloy_element_1.MalloyElement {
|
|
|
96
96
|
}
|
|
97
97
|
const circularDef = exprFS instanceof DefSpace && exprFS.foundCircle;
|
|
98
98
|
if (!circularDef) {
|
|
99
|
-
if (exprValue.dataType
|
|
100
|
-
this.log(`Cannot define '${exprName}', value has unknown type`);
|
|
101
|
-
}
|
|
102
|
-
else {
|
|
99
|
+
if (exprValue.dataType !== 'error') {
|
|
103
100
|
const badType = fragtype_utils_1.FT.inspect(exprValue);
|
|
104
101
|
this.log(`Cannot define '${exprName}', unexpected type: ${badType}`);
|
|
105
102
|
}
|
|
106
103
|
}
|
|
107
104
|
return {
|
|
108
|
-
name:
|
|
109
|
-
type: '
|
|
105
|
+
name: exprName,
|
|
106
|
+
type: 'error',
|
|
110
107
|
};
|
|
111
108
|
}
|
|
112
109
|
}
|
|
@@ -67,7 +67,7 @@ class ExpressionDef extends malloy_element_1.MalloyElement {
|
|
|
67
67
|
* @param eVal ...list of expressions that must match legalChildTypes
|
|
68
68
|
*/
|
|
69
69
|
typeCheck(eNode, eVal) {
|
|
70
|
-
if (!fragtype_utils_1.FT.in(eVal, this.legalChildTypes)) {
|
|
70
|
+
if (eVal.dataType !== 'error' && !fragtype_utils_1.FT.in(eVal, this.legalChildTypes)) {
|
|
71
71
|
eNode.log(`'${this.elementType}' Can't use type ${fragtype_utils_1.FT.inspect(eVal)}`);
|
|
72
72
|
return false;
|
|
73
73
|
}
|
|
@@ -232,6 +232,9 @@ function nullCompare(left, op, right) {
|
|
|
232
232
|
function equality(fs, left, op, right) {
|
|
233
233
|
const lhs = left.getExpression(fs);
|
|
234
234
|
const rhs = right.getExpression(fs);
|
|
235
|
+
const err = errorCascade('boolean', lhs, rhs);
|
|
236
|
+
if (err)
|
|
237
|
+
return err;
|
|
235
238
|
// Unsupported types can be compare with null
|
|
236
239
|
const checkUnsupport = lhs.dataType === 'unsupported' || rhs.dataType === 'unsupported';
|
|
237
240
|
if (checkUnsupport) {
|
|
@@ -245,7 +248,7 @@ function equality(fs, left, op, right) {
|
|
|
245
248
|
}
|
|
246
249
|
}
|
|
247
250
|
let value = timeCompare(left, lhs, op, rhs) || (0, utils_1.compose)(lhs.value, op, rhs.value);
|
|
248
|
-
if (lhs.dataType !== '
|
|
251
|
+
if (lhs.dataType !== 'error' && rhs.dataType !== 'error') {
|
|
249
252
|
switch (op) {
|
|
250
253
|
case '~':
|
|
251
254
|
case '!~': {
|
|
@@ -285,6 +288,9 @@ function equality(fs, left, op, right) {
|
|
|
285
288
|
function compare(fs, left, op, right) {
|
|
286
289
|
const lhs = left.getExpression(fs);
|
|
287
290
|
const rhs = right.getExpression(fs);
|
|
291
|
+
const err = errorCascade('boolean', lhs, rhs);
|
|
292
|
+
if (err)
|
|
293
|
+
return err;
|
|
288
294
|
const expressionType = (0, malloy_types_1.maxExpressionType)(lhs.expressionType, rhs.expressionType);
|
|
289
295
|
const noCompare = unsupportError(left, lhs, right, rhs);
|
|
290
296
|
if (noCompare) {
|
|
@@ -312,6 +318,9 @@ function allAre(oneType, ...values) {
|
|
|
312
318
|
function numeric(fs, left, op, right) {
|
|
313
319
|
const lhs = left.getExpression(fs);
|
|
314
320
|
const rhs = right.getExpression(fs);
|
|
321
|
+
const err = errorCascade('number', lhs, rhs);
|
|
322
|
+
if (err)
|
|
323
|
+
return err;
|
|
315
324
|
const noGo = unsupportError(left, lhs, right, rhs);
|
|
316
325
|
if (noGo) {
|
|
317
326
|
return noGo;
|
|
@@ -335,7 +344,11 @@ function delta(fs, left, op, right) {
|
|
|
335
344
|
if (noGo) {
|
|
336
345
|
return noGo;
|
|
337
346
|
}
|
|
338
|
-
|
|
347
|
+
const timeLHS = (0, malloy_types_1.isTimeFieldType)(lhs.dataType);
|
|
348
|
+
const err = errorCascade(timeLHS ? 'error' : 'number', lhs, rhs);
|
|
349
|
+
if (err)
|
|
350
|
+
return err;
|
|
351
|
+
if (timeLHS) {
|
|
339
352
|
let duration = right;
|
|
340
353
|
if (rhs.dataType !== 'duration') {
|
|
341
354
|
if ((0, granular_result_1.isGranularResult)(lhs)) {
|
|
@@ -385,6 +398,9 @@ function applyBinary(fs, left, op, right) {
|
|
|
385
398
|
left.log('Cannot operate with unsupported type');
|
|
386
399
|
return noGo;
|
|
387
400
|
}
|
|
401
|
+
const err = errorCascade('number', num, denom);
|
|
402
|
+
if (err)
|
|
403
|
+
return err;
|
|
388
404
|
if (num.dataType !== 'number') {
|
|
389
405
|
left.log('Numerator for division must be a number');
|
|
390
406
|
}
|
|
@@ -411,6 +427,16 @@ function applyBinary(fs, left, op, right) {
|
|
|
411
427
|
return (0, ast_utils_1.errorFor)('applybinary bad operator');
|
|
412
428
|
}
|
|
413
429
|
exports.applyBinary = applyBinary;
|
|
430
|
+
function errorCascade(dataType, ...es) {
|
|
431
|
+
if (es.some(e => e.dataType === 'error')) {
|
|
432
|
+
return {
|
|
433
|
+
dataType,
|
|
434
|
+
expressionType: (0, malloy_types_1.maxOfExpressionTypes)(es.map(e => e.expressionType)),
|
|
435
|
+
value: ["'cascading error'"],
|
|
436
|
+
evalSpace: (0, malloy_types_1.mergeEvalSpaces)(...es.map(e => e.evalSpace)),
|
|
437
|
+
};
|
|
438
|
+
}
|
|
439
|
+
}
|
|
414
440
|
/**
|
|
415
441
|
* Return an error if a binary operation includes unsupported types.
|
|
416
442
|
*/
|
|
@@ -297,14 +297,6 @@ class RunList extends ListOf {
|
|
|
297
297
|
}
|
|
298
298
|
executeList(doc) {
|
|
299
299
|
while (this.execCursor < this.elements.length) {
|
|
300
|
-
if (doc.errorsExist()) {
|
|
301
|
-
// TODO make a better way to stop cascading errors -- this way means that if there are
|
|
302
|
-
// actual different errors in multiple places, we only see the first one. A better way
|
|
303
|
-
// might be to make things that have error pass all kinds of typechecks, which prevents
|
|
304
|
-
// the real cascade.
|
|
305
|
-
// This stops cascading errors
|
|
306
|
-
return;
|
|
307
|
-
}
|
|
308
300
|
const el = this.elements[this.execCursor];
|
|
309
301
|
if (isDocStatement(el)) {
|
|
310
302
|
const resp = el.execute(doc);
|
|
@@ -40,7 +40,7 @@ class AbstractParameter extends SpaceParam {
|
|
|
40
40
|
return this.astParam.parameter();
|
|
41
41
|
}
|
|
42
42
|
typeDesc() {
|
|
43
|
-
const type = this.astParam.type || '
|
|
43
|
+
const type = this.astParam.type || 'error';
|
|
44
44
|
// TODO Not sure whether params are considered "input space". It seems like they
|
|
45
45
|
// could be input or constant, depending on usage.
|
|
46
46
|
return { dataType: type, expressionType: 'scalar', evalSpace: 'input' };
|
|
@@ -319,7 +319,7 @@ describe('model statements', () => {
|
|
|
319
319
|
group_by: b is c2
|
|
320
320
|
}`).translationToFailWith(
|
|
321
321
|
// c2 is not defined because group_by doesn't know to look in the output space
|
|
322
|
-
"'c2' is not defined"
|
|
322
|
+
"'c2' is not defined");
|
|
323
323
|
});
|
|
324
324
|
test('cannot use analytic in order_by, preserved over refinement', () => {
|
|
325
325
|
expect(`query: a1 is a -> {
|
|
@@ -334,19 +334,19 @@ describe('model statements', () => {
|
|
|
334
334
|
expect(`query: a1 is a -> {
|
|
335
335
|
group_by: c is 1
|
|
336
336
|
aggregate: c2 is all(all(sum(ai)))
|
|
337
|
-
}`).translationToFailWith('all() expression must not already be ungrouped'
|
|
337
|
+
}`).translationToFailWith('all() expression must not already be ungrouped');
|
|
338
338
|
});
|
|
339
339
|
test('cannot aggregate an ungrouped', () => {
|
|
340
340
|
expect(`query: a1 is a -> {
|
|
341
341
|
group_by: c is 1
|
|
342
342
|
aggregate: c2 is sum(all(sum(ai)))
|
|
343
|
-
}`).translationToFailWith('Aggregate expression cannot be aggregate'
|
|
343
|
+
}`).translationToFailWith('Aggregate expression cannot be aggregate');
|
|
344
344
|
});
|
|
345
345
|
test('cannot aggregate an aggregate', () => {
|
|
346
346
|
expect(`query: a1 is a -> {
|
|
347
347
|
group_by: c is 1
|
|
348
348
|
aggregate: c2 is sum(sum(ai))
|
|
349
|
-
}`).translationToFailWith('Aggregate expression cannot be aggregate'
|
|
349
|
+
}`).translationToFailWith('Aggregate expression cannot be aggregate');
|
|
350
350
|
});
|
|
351
351
|
test('can use field def in group_by, preserved over refinement', () => {
|
|
352
352
|
expect(`query: a1 is a -> {
|
|
@@ -375,12 +375,12 @@ describe('model statements', () => {
|
|
|
375
375
|
test('function no matching overload', () => {
|
|
376
376
|
expect(`query: a -> {
|
|
377
377
|
group_by: s is floor('a', 'b')
|
|
378
|
-
}`).translationToFailWith('No matching overload for function floor(string, string)'
|
|
378
|
+
}`).translationToFailWith('No matching overload for function floor(string, string)');
|
|
379
379
|
});
|
|
380
380
|
test('unknown function', () => {
|
|
381
381
|
expect(`query: a -> {
|
|
382
382
|
group_by: s is asdfasdf()
|
|
383
|
-
}`).translationToFailWith("Unknown function 'asdfasdf'. Use 'asdfasdf!(...)' to call a SQL function directly."
|
|
383
|
+
}`).translationToFailWith("Unknown function 'asdfasdf'. Use 'asdfasdf!(...)' to call a SQL function directly.");
|
|
384
384
|
});
|
|
385
385
|
test('can select different overload', () => {
|
|
386
386
|
expect('query: a -> { group_by: s is concat() }').toTranslate();
|
|
@@ -402,7 +402,7 @@ describe('model statements', () => {
|
|
|
402
402
|
test('function return type incorrect', () => {
|
|
403
403
|
expect(`query: a -> {
|
|
404
404
|
group_by: s is floor(1.2) + 'a'
|
|
405
|
-
}`).translationToFailWith("Non numeric('number,string') value with '+'"
|
|
405
|
+
}`).translationToFailWith("Non numeric('number,string') value with '+'");
|
|
406
406
|
});
|
|
407
407
|
test('can use output value in calculate', () => {
|
|
408
408
|
expect(`query: a -> {
|
|
@@ -414,7 +414,7 @@ describe('model statements', () => {
|
|
|
414
414
|
expect(`query: a -> {
|
|
415
415
|
group_by: x is 1
|
|
416
416
|
group_by: y is x
|
|
417
|
-
}`).translationToFailWith("'x' is not defined"
|
|
417
|
+
}`).translationToFailWith("'x' is not defined");
|
|
418
418
|
});
|
|
419
419
|
test('lag can check that other args are constant', () => {
|
|
420
420
|
expect(`query: a -> {
|
|
@@ -471,7 +471,7 @@ describe('model statements', () => {
|
|
|
471
471
|
expect(`query: a {join_one: b with astr } -> {
|
|
472
472
|
group_by: b
|
|
473
473
|
calculate: foo is lag(b)
|
|
474
|
-
}`).translationToFailWith('No matching overload for function lag(struct)'
|
|
474
|
+
}`).translationToFailWith('No matching overload for function lag(struct)');
|
|
475
475
|
});
|
|
476
476
|
// TODO this doesn't work today, we're not rigorous enough with integer
|
|
477
477
|
// subtypes. But we should probably make this typecheck properly.
|
|
@@ -485,7 +485,7 @@ describe('model statements', () => {
|
|
|
485
485
|
test('cannot use stddev with no arguments', () => {
|
|
486
486
|
expect(`query: a -> {
|
|
487
487
|
aggregate: x is stddev()
|
|
488
|
-
}`).translationToFailWith('No matching overload for function stddev()'
|
|
488
|
+
}`).translationToFailWith('No matching overload for function stddev()');
|
|
489
489
|
});
|
|
490
490
|
test('can use stddev with postfix syntax', () => {
|
|
491
491
|
expect(`query: a -> {
|
|
@@ -1230,6 +1230,9 @@ describe('expressions', () => {
|
|
|
1230
1230
|
test('null-check (??)', () => {
|
|
1231
1231
|
expect((0, test_translator_1.expr) `ai ?? 7`).toTranslate();
|
|
1232
1232
|
});
|
|
1233
|
+
test('coalesce type mismatch', () => {
|
|
1234
|
+
expect(new test_translator_1.BetaExpression('ai ?? @2003')).translationToFailWith('Mismatched types for coalesce (number, date)');
|
|
1235
|
+
});
|
|
1233
1236
|
test('disallow date OP number', () => {
|
|
1234
1237
|
expect(new test_translator_1.BetaExpression('@2001 = 7')).translationToFailWith('Cannot compare a date to a number');
|
|
1235
1238
|
});
|
|
@@ -1357,7 +1360,7 @@ describe('expressions', () => {
|
|
|
1357
1360
|
source: na is a + { dimension: d is
|
|
1358
1361
|
pick 7 when true and true
|
|
1359
1362
|
}
|
|
1360
|
-
`).translationToFailWith("pick incomplete, missing 'else'"
|
|
1363
|
+
`).translationToFailWith("pick incomplete, missing 'else'");
|
|
1361
1364
|
});
|
|
1362
1365
|
test('n-ary with mismatch when clauses', () => {
|
|
1363
1366
|
expect((0, test_translator_1.markSource) `
|
|
@@ -1366,7 +1369,7 @@ describe('expressions', () => {
|
|
|
1366
1369
|
pick '7' when true or true
|
|
1367
1370
|
else 7
|
|
1368
1371
|
}
|
|
1369
|
-
`).translationToFailWith("pick type 'string', expected 'number'"
|
|
1372
|
+
`).translationToFailWith("pick type 'string', expected 'number'");
|
|
1370
1373
|
});
|
|
1371
1374
|
test('n-ary with mismatched else clause', () => {
|
|
1372
1375
|
expect((0, test_translator_1.markSource) `
|
|
@@ -1374,28 +1377,28 @@ describe('expressions', () => {
|
|
|
1374
1377
|
pick 7 when true and true
|
|
1375
1378
|
else '7'
|
|
1376
1379
|
}
|
|
1377
|
-
`).translationToFailWith("else type 'string', expected 'number'"
|
|
1380
|
+
`).translationToFailWith("else type 'string', expected 'number'");
|
|
1378
1381
|
});
|
|
1379
1382
|
test('applied else mismatch', () => {
|
|
1380
1383
|
expect((0, test_translator_1.markSource) `
|
|
1381
1384
|
source: na is a + { dimension: d is
|
|
1382
1385
|
7 ? pick 7 when 7 else 'not seven'
|
|
1383
1386
|
}
|
|
1384
|
-
`).translationToFailWith("else type 'string', expected 'number'"
|
|
1387
|
+
`).translationToFailWith("else type 'string', expected 'number'");
|
|
1385
1388
|
});
|
|
1386
1389
|
test('applied default mismatch', () => {
|
|
1387
1390
|
expect((0, test_translator_1.markSource) `
|
|
1388
1391
|
source: na is a + { dimension: d is
|
|
1389
1392
|
7 ? pick 'seven' when 7
|
|
1390
1393
|
}
|
|
1391
|
-
`).translationToFailWith("pick default type 'number', expected 'string'"
|
|
1394
|
+
`).translationToFailWith("pick default type 'number', expected 'string'");
|
|
1392
1395
|
});
|
|
1393
1396
|
test('applied when mismatch', () => {
|
|
1394
1397
|
expect((0, test_translator_1.markSource) `
|
|
1395
1398
|
source: na is a + { dimension: d is
|
|
1396
1399
|
7 ? pick 'seven' when 7 pick 6 when 6
|
|
1397
1400
|
}
|
|
1398
|
-
`).translationToFailWith("pick type 'number', expected 'string'"
|
|
1401
|
+
`).translationToFailWith("pick type 'number', expected 'string'");
|
|
1399
1402
|
});
|
|
1400
1403
|
});
|
|
1401
1404
|
test('paren and applied div', () => {
|
|
@@ -2365,6 +2368,248 @@ describe('translation need error locations', () => {
|
|
|
2365
2368
|
expect(m).translationToFailWith(/Bad table!/);
|
|
2366
2369
|
});
|
|
2367
2370
|
});
|
|
2371
|
+
describe('error cascading', () => {
|
|
2372
|
+
test('errors can appear in multiple top level objects', () => {
|
|
2373
|
+
expect((0, test_translator_1.markSource) `
|
|
2374
|
+
source: a1 is a { dimension: ${'x is count()'} }
|
|
2375
|
+
source: a2 is a { dimension: ${'x is count()'} }
|
|
2376
|
+
`).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?');
|
|
2377
|
+
});
|
|
2378
|
+
const typedScalars = {
|
|
2379
|
+
'err': 'error',
|
|
2380
|
+
'@2003 ~ @2003 for err hours': 'boolean',
|
|
2381
|
+
'err.hour': 'timestamp',
|
|
2382
|
+
'err.month': 'date',
|
|
2383
|
+
'day_of_week(err)': 'number',
|
|
2384
|
+
'err::string': 'string',
|
|
2385
|
+
'-err': 'number',
|
|
2386
|
+
'err * 1': 'number',
|
|
2387
|
+
'1 * err': 'number',
|
|
2388
|
+
'err / 1': 'number',
|
|
2389
|
+
'1 / err': 'number',
|
|
2390
|
+
'err % 1': 'number',
|
|
2391
|
+
'1 % err': 'number',
|
|
2392
|
+
'err + 1': 'number',
|
|
2393
|
+
'1 + err': 'number',
|
|
2394
|
+
'err - 1': 'number',
|
|
2395
|
+
'1 - err': 'number',
|
|
2396
|
+
'@2003 ? err for 1 minute': 'boolean',
|
|
2397
|
+
'@2003 ? @2003 for err minutes': 'boolean',
|
|
2398
|
+
'3 ? > err & > 3': 'boolean',
|
|
2399
|
+
'3 ? > 3 & > err': 'boolean',
|
|
2400
|
+
'3 ? > err | > 3': 'boolean',
|
|
2401
|
+
'3 ? > 3 | > err': 'boolean',
|
|
2402
|
+
'err ? > 3': 'boolean',
|
|
2403
|
+
'3 ? > err': 'boolean',
|
|
2404
|
+
'1 > err': 'boolean',
|
|
2405
|
+
'err > 1': 'boolean',
|
|
2406
|
+
'1 >= err': 'boolean',
|
|
2407
|
+
'err >= 1': 'boolean',
|
|
2408
|
+
'1 < err': 'boolean',
|
|
2409
|
+
'err < 1': 'boolean',
|
|
2410
|
+
'1 <= err': 'boolean',
|
|
2411
|
+
'err <= 1': 'boolean',
|
|
2412
|
+
'1 = err': 'boolean',
|
|
2413
|
+
'err = 1': 'boolean',
|
|
2414
|
+
'1 != err': 'boolean',
|
|
2415
|
+
'err != 1': 'boolean',
|
|
2416
|
+
'1 ~ err': 'boolean',
|
|
2417
|
+
'err ~ 1': 'boolean',
|
|
2418
|
+
'1 !~ err': 'boolean',
|
|
2419
|
+
'err !~ 1': 'boolean',
|
|
2420
|
+
'not err': 'boolean',
|
|
2421
|
+
'err and true': 'boolean',
|
|
2422
|
+
'true and err': 'boolean',
|
|
2423
|
+
'err or true': 'boolean',
|
|
2424
|
+
'true or err': 'boolean',
|
|
2425
|
+
'err ?? 1': 'number',
|
|
2426
|
+
'1 ?? err': 'number',
|
|
2427
|
+
'cast(err as number)': 'number',
|
|
2428
|
+
'(err)': 'error',
|
|
2429
|
+
'length(err)': 'number',
|
|
2430
|
+
'pick err when true else false': 'boolean',
|
|
2431
|
+
'pick true when err else false': 'boolean',
|
|
2432
|
+
'pick true when true else err': 'boolean',
|
|
2433
|
+
'days(err to @2003)': 'number',
|
|
2434
|
+
'days(@2003 to err)': 'number',
|
|
2435
|
+
};
|
|
2436
|
+
const scalars = Object.keys(typedScalars);
|
|
2437
|
+
const aggregates = [
|
|
2438
|
+
'measure_err { where: true }',
|
|
2439
|
+
'count(distinct err)',
|
|
2440
|
+
'b.sum(err)',
|
|
2441
|
+
'b.stddev(err)',
|
|
2442
|
+
];
|
|
2443
|
+
const ungroupedAggregates = ['all(measure_err)'];
|
|
2444
|
+
test('dependent errors do not cascade', () => {
|
|
2445
|
+
expect(`
|
|
2446
|
+
source: a1 is a {
|
|
2447
|
+
join_one: b with astr
|
|
2448
|
+
dimension:
|
|
2449
|
+
${'err is null'}
|
|
2450
|
+
${scalars.map((d, i) => `e${i} is ${d}`).join('\n ')}
|
|
2451
|
+
measure:
|
|
2452
|
+
measure_err is count(distinct foo),
|
|
2453
|
+
${[...aggregates, ...ungroupedAggregates]
|
|
2454
|
+
.map((m, i) => `e${i + scalars.length} is ${m}`)
|
|
2455
|
+
.join('\n ')}
|
|
2456
|
+
}
|
|
2457
|
+
`).translationToFailWith("Cannot define 'err', unexpected type: null", "'foo' is not defined");
|
|
2458
|
+
});
|
|
2459
|
+
test('error type inference is good', () => {
|
|
2460
|
+
for (const scalar of scalars) {
|
|
2461
|
+
const source = `
|
|
2462
|
+
source: a1 is a {
|
|
2463
|
+
dimension:
|
|
2464
|
+
${'err is null'}
|
|
2465
|
+
dim is length(${scalar}, 1)
|
|
2466
|
+
}
|
|
2467
|
+
`;
|
|
2468
|
+
expect(source).translationToFailWith("Cannot define 'err', unexpected type: null", `No matching overload for function length(${typedScalars[scalar]}, number)`);
|
|
2469
|
+
}
|
|
2470
|
+
});
|
|
2471
|
+
test('eval space of errors is preserved', () => {
|
|
2472
|
+
expect(`
|
|
2473
|
+
source: a1 is a {
|
|
2474
|
+
join_one: b with astr
|
|
2475
|
+
}
|
|
2476
|
+
query: a1 -> {
|
|
2477
|
+
group_by:
|
|
2478
|
+
${'err is null'}
|
|
2479
|
+
aggregate:
|
|
2480
|
+
measure_err is count(distinct foo)
|
|
2481
|
+
calculate:
|
|
2482
|
+
${scalars
|
|
2483
|
+
.map((d, i) => `e${i} is lag(${d})`)
|
|
2484
|
+
.join('\n ')}
|
|
2485
|
+
${aggregates
|
|
2486
|
+
.map((m, i) => `e${i + scalars.length} is lag(${m})`)
|
|
2487
|
+
.join('\n ')}
|
|
2488
|
+
}
|
|
2489
|
+
`).translationToFailWith("Cannot define 'err', unexpected type: null", "'foo' is not defined");
|
|
2490
|
+
});
|
|
2491
|
+
});
|
|
2492
|
+
describe('error cascading', () => {
|
|
2493
|
+
test('errors can appear in multiple top level objects', () => {
|
|
2494
|
+
expect((0, test_translator_1.markSource) `
|
|
2495
|
+
source: a1 is a { dimension: ${'x is count()'} }
|
|
2496
|
+
source: a2 is a { dimension: ${'x is count()'} }
|
|
2497
|
+
`).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?');
|
|
2498
|
+
});
|
|
2499
|
+
const typedScalars = {
|
|
2500
|
+
'err': 'error',
|
|
2501
|
+
'@2003 ~ @2003 for err hours': 'boolean',
|
|
2502
|
+
'err.hour': 'timestamp',
|
|
2503
|
+
'err.month': 'date',
|
|
2504
|
+
'day_of_week(err)': 'number',
|
|
2505
|
+
'err::string': 'string',
|
|
2506
|
+
'-err': 'number',
|
|
2507
|
+
'err * 1': 'number',
|
|
2508
|
+
'1 * err': 'number',
|
|
2509
|
+
'err / 1': 'number',
|
|
2510
|
+
'1 / err': 'number',
|
|
2511
|
+
'err % 1': 'number',
|
|
2512
|
+
'1 % err': 'number',
|
|
2513
|
+
'err + 1': 'number',
|
|
2514
|
+
'1 + err': 'number',
|
|
2515
|
+
'err - 1': 'number',
|
|
2516
|
+
'1 - err': 'number',
|
|
2517
|
+
'@2003 ? err for 1 minute': 'boolean',
|
|
2518
|
+
'@2003 ? @2003 for err minutes': 'boolean',
|
|
2519
|
+
'3 ? > err & > 3': 'boolean',
|
|
2520
|
+
'3 ? > 3 & > err': 'boolean',
|
|
2521
|
+
'3 ? > err | > 3': 'boolean',
|
|
2522
|
+
'3 ? > 3 | > err': 'boolean',
|
|
2523
|
+
'err ? > 3': 'boolean',
|
|
2524
|
+
'3 ? > err': 'boolean',
|
|
2525
|
+
'1 > err': 'boolean',
|
|
2526
|
+
'err > 1': 'boolean',
|
|
2527
|
+
'1 >= err': 'boolean',
|
|
2528
|
+
'err >= 1': 'boolean',
|
|
2529
|
+
'1 < err': 'boolean',
|
|
2530
|
+
'err < 1': 'boolean',
|
|
2531
|
+
'1 <= err': 'boolean',
|
|
2532
|
+
'err <= 1': 'boolean',
|
|
2533
|
+
'1 = err': 'boolean',
|
|
2534
|
+
'err = 1': 'boolean',
|
|
2535
|
+
'1 != err': 'boolean',
|
|
2536
|
+
'err != 1': 'boolean',
|
|
2537
|
+
'1 ~ err': 'boolean',
|
|
2538
|
+
'err ~ 1': 'boolean',
|
|
2539
|
+
'1 !~ err': 'boolean',
|
|
2540
|
+
'err !~ 1': 'boolean',
|
|
2541
|
+
'not err': 'boolean',
|
|
2542
|
+
'err and true': 'boolean',
|
|
2543
|
+
'true and err': 'boolean',
|
|
2544
|
+
'err or true': 'boolean',
|
|
2545
|
+
'true or err': 'boolean',
|
|
2546
|
+
'err ?? 1': 'number',
|
|
2547
|
+
'1 ?? err': 'number',
|
|
2548
|
+
'cast(err as number)': 'number',
|
|
2549
|
+
'(err)': 'error',
|
|
2550
|
+
'length(err)': 'number',
|
|
2551
|
+
'pick err when true else false': 'boolean',
|
|
2552
|
+
'pick true when err else false': 'boolean',
|
|
2553
|
+
'pick true when true else err': 'boolean',
|
|
2554
|
+
'days(err to @2003)': 'number',
|
|
2555
|
+
'days(@2003 to err)': 'number',
|
|
2556
|
+
};
|
|
2557
|
+
const scalars = Object.keys(typedScalars);
|
|
2558
|
+
const aggregates = [
|
|
2559
|
+
'measure_err { where: true }',
|
|
2560
|
+
'count(distinct err)',
|
|
2561
|
+
'b.sum(err)',
|
|
2562
|
+
'b.stddev(err)',
|
|
2563
|
+
];
|
|
2564
|
+
const ungroupedAggregates = ['all(measure_err)'];
|
|
2565
|
+
test('dependent errors do not cascade', () => {
|
|
2566
|
+
expect(`
|
|
2567
|
+
source: a1 is a {
|
|
2568
|
+
join_one: b with astr
|
|
2569
|
+
dimension:
|
|
2570
|
+
${'err is null'}
|
|
2571
|
+
${scalars.map((d, i) => `e${i} is ${d}`).join('\n ')}
|
|
2572
|
+
measure:
|
|
2573
|
+
measure_err is count(distinct foo),
|
|
2574
|
+
${[...aggregates, ...ungroupedAggregates]
|
|
2575
|
+
.map((m, i) => `e${i + scalars.length} is ${m}`)
|
|
2576
|
+
.join('\n ')}
|
|
2577
|
+
}
|
|
2578
|
+
`).translationToFailWith("Cannot define 'err', unexpected type: null", "'foo' is not defined");
|
|
2579
|
+
});
|
|
2580
|
+
test('error type inference is good', () => {
|
|
2581
|
+
for (const scalar of scalars) {
|
|
2582
|
+
const source = `
|
|
2583
|
+
source: a1 is a {
|
|
2584
|
+
dimension:
|
|
2585
|
+
${'err is null'}
|
|
2586
|
+
dim is length(${scalar}, 1)
|
|
2587
|
+
}
|
|
2588
|
+
`;
|
|
2589
|
+
expect(source).translationToFailWith("Cannot define 'err', unexpected type: null", `No matching overload for function length(${typedScalars[scalar]}, number)`);
|
|
2590
|
+
}
|
|
2591
|
+
});
|
|
2592
|
+
test('eval space of errors is preserved', () => {
|
|
2593
|
+
expect(`
|
|
2594
|
+
source: a1 is a {
|
|
2595
|
+
join_one: b with astr
|
|
2596
|
+
}
|
|
2597
|
+
query: a1 -> {
|
|
2598
|
+
group_by:
|
|
2599
|
+
${'err is null'}
|
|
2600
|
+
aggregate:
|
|
2601
|
+
measure_err is count(distinct foo)
|
|
2602
|
+
calculate:
|
|
2603
|
+
${scalars
|
|
2604
|
+
.map((d, i) => `e${i} is lag(${d})`)
|
|
2605
|
+
.join('\n ')}
|
|
2606
|
+
${aggregates
|
|
2607
|
+
.map((m, i) => `e${i + scalars.length} is lag(${m})`)
|
|
2608
|
+
.join('\n ')}
|
|
2609
|
+
}
|
|
2610
|
+
`).translationToFailWith("Cannot define 'err', unexpected type: null", "'foo' is not defined");
|
|
2611
|
+
});
|
|
2612
|
+
});
|
|
2368
2613
|
describe('pipeline comprehension', () => {
|
|
2369
2614
|
test('second query gets namespace from first', () => {
|
|
2370
2615
|
expect(`
|
package/dist/malloy.d.ts
CHANGED
|
@@ -511,7 +511,8 @@ export declare enum AtomicFieldType {
|
|
|
511
511
|
Date = "date",
|
|
512
512
|
Timestamp = "timestamp",
|
|
513
513
|
Json = "json",
|
|
514
|
-
Unsupported = "unsupported"
|
|
514
|
+
Unsupported = "unsupported",
|
|
515
|
+
Error = "error"
|
|
515
516
|
}
|
|
516
517
|
export declare class AtomicField extends Entity {
|
|
517
518
|
protected fieldTypeDef: FieldTypeDef;
|
package/dist/malloy.js
CHANGED
|
@@ -1030,6 +1030,7 @@ var AtomicFieldType;
|
|
|
1030
1030
|
AtomicFieldType["Timestamp"] = "timestamp";
|
|
1031
1031
|
AtomicFieldType["Json"] = "json";
|
|
1032
1032
|
AtomicFieldType["Unsupported"] = "unsupported";
|
|
1033
|
+
AtomicFieldType["Error"] = "error";
|
|
1033
1034
|
})(AtomicFieldType = exports.AtomicFieldType || (exports.AtomicFieldType = {}));
|
|
1034
1035
|
class AtomicField extends Entity {
|
|
1035
1036
|
constructor(fieldTypeDef, parent, source) {
|
|
@@ -1053,6 +1054,8 @@ class AtomicField extends Entity {
|
|
|
1053
1054
|
return AtomicFieldType.Json;
|
|
1054
1055
|
case 'unsupported':
|
|
1055
1056
|
return AtomicFieldType.Unsupported;
|
|
1057
|
+
case 'error':
|
|
1058
|
+
return AtomicFieldType.Error;
|
|
1056
1059
|
}
|
|
1057
1060
|
}
|
|
1058
1061
|
isIntrinsic() {
|
|
@@ -269,7 +269,7 @@ declare type HasExpression = FieldDef & JustExpression;
|
|
|
269
269
|
export declare function hasExpression(f: FieldDef): f is HasExpression;
|
|
270
270
|
export declare type TimeFieldType = 'date' | 'timestamp';
|
|
271
271
|
export declare function isTimeFieldType(s: string): s is TimeFieldType;
|
|
272
|
-
export declare type AtomicFieldType = 'string' | 'number' | TimeFieldType | 'boolean' | 'unsupported' | 'json';
|
|
272
|
+
export declare type AtomicFieldType = 'string' | 'number' | TimeFieldType | 'boolean' | 'unsupported' | 'json' | 'error';
|
|
273
273
|
export declare function isAtomicFieldType(s: string): s is AtomicFieldType;
|
|
274
274
|
/** All scalars can have an optional expression */
|
|
275
275
|
export interface FieldAtomicDef extends NamedObject, Expression, ResultMetadata {
|
|
@@ -300,6 +300,9 @@ export interface FieldUnsupportedDef extends FieldAtomicDef {
|
|
|
300
300
|
type: 'unsupported';
|
|
301
301
|
rawType?: string;
|
|
302
302
|
}
|
|
303
|
+
export interface FieldErrorDef extends FieldAtomicDef {
|
|
304
|
+
type: 'error';
|
|
305
|
+
}
|
|
303
306
|
export declare type DateUnit = 'day' | 'week' | 'month' | 'quarter' | 'year';
|
|
304
307
|
export declare function isDateUnit(str: string): str is DateUnit;
|
|
305
308
|
export declare type TimestampUnit = DateUnit | 'hour' | 'minute' | 'second';
|
|
@@ -480,7 +483,7 @@ export interface StructDef extends NamedObject, ResultStructMetadata, Filtered {
|
|
|
480
483
|
queryTimezone?: string;
|
|
481
484
|
dialect: string;
|
|
482
485
|
}
|
|
483
|
-
export declare type ExpressionValueType = AtomicFieldType | 'null' | '
|
|
486
|
+
export declare type ExpressionValueType = AtomicFieldType | 'null' | 'duration' | 'any' | 'regular expression';
|
|
484
487
|
export declare type FieldValueType = ExpressionValueType | 'turtle' | 'struct';
|
|
485
488
|
export interface ExpressionTypeDesc {
|
|
486
489
|
dataType: FieldValueType;
|
|
@@ -528,7 +531,7 @@ export interface SQLBlockStructDef extends StructDef {
|
|
|
528
531
|
}
|
|
529
532
|
export declare function isSQLBlockStruct(sd: StructDef): sd is SQLBlockStructDef;
|
|
530
533
|
/** any of the different field types */
|
|
531
|
-
export declare type FieldTypeDef = FieldStringDef | FieldDateDef | FieldTimestampDef | FieldNumberDef | FieldBooleanDef | FieldJSONDef | FieldUnsupportedDef;
|
|
534
|
+
export declare type FieldTypeDef = FieldStringDef | FieldDateDef | FieldTimestampDef | FieldNumberDef | FieldBooleanDef | FieldJSONDef | FieldUnsupportedDef | FieldErrorDef;
|
|
532
535
|
export declare function isFieldTypeDef(f: FieldDef): f is FieldTypeDef;
|
|
533
536
|
export declare function isFieldTimeBased(f: FieldDef): f is FieldTimestampDef | FieldDateDef;
|
|
534
537
|
export declare function isFieldStructDef(f: FieldDef): f is StructDef;
|