@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.
@@ -33,7 +33,7 @@ exports.errorFor = void 0;
33
33
  */
34
34
  function errorFor(reason) {
35
35
  return {
36
- dataType: 'unknown',
36
+ dataType: 'error',
37
37
  expressionType: 'scalar',
38
38
  value: [`_ERROR_${reason.replace(/ /g, '_')}`],
39
39
  evalSpace: 'constant',
@@ -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
- // TODO I've included this because it means that errors cascade a bit less...
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
- this.log(`Cannot do time truncation on type '${exprVal.dataType}'`);
74
- return (0, ast_utils_1.errorFor)('granularity typecheck');
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: (0, malloy_types_1.maxExpressionType)(first.expressionType, last.expressionType),
99
- evalSpace: (0, malloy_types_1.mergeEvalSpaces)(first.evalSpace, last.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
- this.log(`${this.extractText}() requires time type, not '${argV.dataType}'`);
129
- return (0, ast_utils_1.errorFor)(`${this.extractText} bad type ${argV.dataType}`);
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("../../..").TypeDesc[];
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
- this.log(`FOR duration count must be a number, not '${nV.dataType}'`);
50
- return (0, ast_utils_1.errorFor)('FOR not number');
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 || ev1.dataType === 'null' ? ev2 : ev1;
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 unknown, which means that a totally bad field
296
- // isn't sent to the compiler, where it will wig out. The downside is that in
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 !== 'unknown' && this.canContain(typeDesc)) {
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: 'unknown', expressionType: 'scalar', evalSpace: 'input' };
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
- return maybeEq || nullEq;
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: 'string',
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: `error_defining_${exprName}`,
70
- type: 'string',
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 === 'unknown') {
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: `error_defining_${exprName}`,
109
- type: 'string',
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 !== 'unknown' && rhs.dataType !== 'unknown') {
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
- if ((0, malloy_types_1.isTimeFieldType)(lhs.dataType)) {
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 || 'unknown';
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", "Cannot define 'b', value has unknown type");
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', "Cannot define 'c2', value has unknown type");
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', "Cannot define 'c2', value has unknown type");
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', "Cannot define 'c2', value has unknown type");
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)', "Cannot define 's', value has unknown type");
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.", "Cannot define 's', value has unknown type");
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 '+'", "Cannot define 's', value has unknown type");
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", "Cannot define 'y', value has unknown type");
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)', "Cannot define 'foo', value has unknown type");
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()', "Cannot define 'x', value has unknown type");
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'", "Cannot define 'd', value has unknown type");
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'", "Cannot define 'd', value has unknown type");
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'", "Cannot define 'd', value has unknown type");
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'", "Cannot define 'd', value has unknown type");
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'", "Cannot define 'd', value has unknown type");
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'", "Cannot define 'd', value has unknown type");
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' | 'unknown' | 'duration' | 'any' | 'regular expression';
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;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@malloydata/malloy",
3
- "version": "0.0.43-dev230621212545",
3
+ "version": "0.0.43-dev230622144455",
4
4
  "license": "MIT",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",