@malloydata/malloy 0.0.393 → 0.0.394
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/api/foundation/core.d.ts +0 -4
- package/dist/api/foundation/core.js +13 -10
- package/dist/lang/ast/expressions/expr-compare.d.ts +15 -0
- package/dist/lang/ast/expressions/expr-compare.js +82 -2
- package/dist/lang/ast/statements/define-given.d.ts +2 -1
- package/dist/lang/ast/statements/define-given.js +52 -1
- package/dist/lang/ast/types/malloy-element.js +2 -0
- package/dist/lang/lib/Malloy/MalloyParser.d.ts +188 -167
- package/dist/lang/lib/Malloy/MalloyParser.js +2582 -2442
- package/dist/lang/lib/Malloy/MalloyParserListener.d.ts +24 -0
- package/dist/lang/lib/Malloy/MalloyParserVisitor.d.ts +15 -0
- package/dist/lang/malloy-to-ast.d.ts +9 -2
- package/dist/lang/malloy-to-ast.js +37 -2
- package/dist/lang/parse-log.d.ts +22 -0
- package/dist/lang/parse-log.js +6 -0
- package/dist/lang/test/expr-to-str.js +3 -0
- package/dist/model/expression_compiler.js +38 -11
- package/dist/model/given_binding.d.ts +15 -0
- package/dist/model/given_binding.js +35 -0
- package/dist/model/inline_expr.d.ts +30 -0
- package/dist/model/inline_expr.js +184 -0
- package/dist/model/malloy_types.d.ts +19 -1
- package/dist/model/query_query.d.ts +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +4 -4
|
@@ -42,6 +42,7 @@ import { ExprCompareContext } from "./MalloyParser";
|
|
|
42
42
|
import { ExprWarnLikeContext } from "./MalloyParser";
|
|
43
43
|
import { ExprNullCheckContext } from "./MalloyParser";
|
|
44
44
|
import { ExprWarnInContext } from "./MalloyParser";
|
|
45
|
+
import { ExprInGivenContext } from "./MalloyParser";
|
|
45
46
|
import { ExprApplyContext } from "./MalloyParser";
|
|
46
47
|
import { ExprNotContext } from "./MalloyParser";
|
|
47
48
|
import { ExprLogicalAndContext } from "./MalloyParser";
|
|
@@ -104,6 +105,7 @@ import { DefineQueryContext } from "./MalloyParser";
|
|
|
104
105
|
import { DefineGivenStatementContext } from "./MalloyParser";
|
|
105
106
|
import { GivenDefListContext } from "./MalloyParser";
|
|
106
107
|
import { GivenDefContext } from "./MalloyParser";
|
|
108
|
+
import { GivenModifierContext } from "./MalloyParser";
|
|
107
109
|
import { GivenNameDefContext } from "./MalloyParser";
|
|
108
110
|
import { GivenTypeContext } from "./MalloyParser";
|
|
109
111
|
import { TopLevelAnonQueryDefContext } from "./MalloyParser";
|
|
@@ -792,6 +794,18 @@ export interface MalloyParserListener extends ParseTreeListener {
|
|
|
792
794
|
* @param ctx the parse tree
|
|
793
795
|
*/
|
|
794
796
|
exitExprWarnIn?: (ctx: ExprWarnInContext) => void;
|
|
797
|
+
/**
|
|
798
|
+
* Enter a parse tree produced by the `exprInGiven`
|
|
799
|
+
* labeled alternative in `MalloyParser.fieldExpr`.
|
|
800
|
+
* @param ctx the parse tree
|
|
801
|
+
*/
|
|
802
|
+
enterExprInGiven?: (ctx: ExprInGivenContext) => void;
|
|
803
|
+
/**
|
|
804
|
+
* Exit a parse tree produced by the `exprInGiven`
|
|
805
|
+
* labeled alternative in `MalloyParser.fieldExpr`.
|
|
806
|
+
* @param ctx the parse tree
|
|
807
|
+
*/
|
|
808
|
+
exitExprInGiven?: (ctx: ExprInGivenContext) => void;
|
|
795
809
|
/**
|
|
796
810
|
* Enter a parse tree produced by the `exprApply`
|
|
797
811
|
* labeled alternative in `MalloyParser.fieldExpr`.
|
|
@@ -1506,6 +1520,16 @@ export interface MalloyParserListener extends ParseTreeListener {
|
|
|
1506
1520
|
* @param ctx the parse tree
|
|
1507
1521
|
*/
|
|
1508
1522
|
exitGivenDef?: (ctx: GivenDefContext) => void;
|
|
1523
|
+
/**
|
|
1524
|
+
* Enter a parse tree produced by `MalloyParser.givenModifier`.
|
|
1525
|
+
* @param ctx the parse tree
|
|
1526
|
+
*/
|
|
1527
|
+
enterGivenModifier?: (ctx: GivenModifierContext) => void;
|
|
1528
|
+
/**
|
|
1529
|
+
* Exit a parse tree produced by `MalloyParser.givenModifier`.
|
|
1530
|
+
* @param ctx the parse tree
|
|
1531
|
+
*/
|
|
1532
|
+
exitGivenModifier?: (ctx: GivenModifierContext) => void;
|
|
1509
1533
|
/**
|
|
1510
1534
|
* Enter a parse tree produced by `MalloyParser.givenNameDef`.
|
|
1511
1535
|
* @param ctx the parse tree
|
|
@@ -42,6 +42,7 @@ import { ExprCompareContext } from "./MalloyParser";
|
|
|
42
42
|
import { ExprWarnLikeContext } from "./MalloyParser";
|
|
43
43
|
import { ExprNullCheckContext } from "./MalloyParser";
|
|
44
44
|
import { ExprWarnInContext } from "./MalloyParser";
|
|
45
|
+
import { ExprInGivenContext } from "./MalloyParser";
|
|
45
46
|
import { ExprApplyContext } from "./MalloyParser";
|
|
46
47
|
import { ExprNotContext } from "./MalloyParser";
|
|
47
48
|
import { ExprLogicalAndContext } from "./MalloyParser";
|
|
@@ -104,6 +105,7 @@ import { DefineQueryContext } from "./MalloyParser";
|
|
|
104
105
|
import { DefineGivenStatementContext } from "./MalloyParser";
|
|
105
106
|
import { GivenDefListContext } from "./MalloyParser";
|
|
106
107
|
import { GivenDefContext } from "./MalloyParser";
|
|
108
|
+
import { GivenModifierContext } from "./MalloyParser";
|
|
107
109
|
import { GivenNameDefContext } from "./MalloyParser";
|
|
108
110
|
import { GivenTypeContext } from "./MalloyParser";
|
|
109
111
|
import { TopLevelAnonQueryDefContext } from "./MalloyParser";
|
|
@@ -580,6 +582,13 @@ export interface MalloyParserVisitor<Result> extends ParseTreeVisitor<Result> {
|
|
|
580
582
|
* @return the visitor result
|
|
581
583
|
*/
|
|
582
584
|
visitExprWarnIn?: (ctx: ExprWarnInContext) => Result;
|
|
585
|
+
/**
|
|
586
|
+
* Visit a parse tree produced by the `exprInGiven`
|
|
587
|
+
* labeled alternative in `MalloyParser.fieldExpr`.
|
|
588
|
+
* @param ctx the parse tree
|
|
589
|
+
* @return the visitor result
|
|
590
|
+
*/
|
|
591
|
+
visitExprInGiven?: (ctx: ExprInGivenContext) => Result;
|
|
583
592
|
/**
|
|
584
593
|
* Visit a parse tree produced by the `exprApply`
|
|
585
594
|
* labeled alternative in `MalloyParser.fieldExpr`.
|
|
@@ -999,6 +1008,12 @@ export interface MalloyParserVisitor<Result> extends ParseTreeVisitor<Result> {
|
|
|
999
1008
|
* @return the visitor result
|
|
1000
1009
|
*/
|
|
1001
1010
|
visitGivenDef?: (ctx: GivenDefContext) => Result;
|
|
1011
|
+
/**
|
|
1012
|
+
* Visit a parse tree produced by `MalloyParser.givenModifier`.
|
|
1013
|
+
* @param ctx the parse tree
|
|
1014
|
+
* @return the visitor result
|
|
1015
|
+
*/
|
|
1016
|
+
visitGivenModifier?: (ctx: GivenModifierContext) => Result;
|
|
1002
1017
|
/**
|
|
1003
1018
|
* Visit a parse tree produced by `MalloyParser.givenNameDef`.
|
|
1004
1019
|
* @param ctx the parse tree
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { ParserRuleContext } from 'antlr4ts';
|
|
2
|
-
import type { ParseTree } from 'antlr4ts/tree';
|
|
2
|
+
import type { ParseTree, TerminalNode } from 'antlr4ts/tree';
|
|
3
3
|
import { AbstractParseTreeVisitor } from 'antlr4ts/tree/AbstractParseTreeVisitor';
|
|
4
4
|
import type { MalloyParserVisitor } from './lib/Malloy/MalloyParserVisitor';
|
|
5
5
|
import type * as parse from './lib/Malloy/MalloyParser';
|
|
@@ -72,7 +72,13 @@ export declare class MalloyToAST extends AbstractParseTreeVisitor<ast.MalloyElem
|
|
|
72
72
|
protected getFieldName(cx: HasID): ast.FieldName;
|
|
73
73
|
protected getModelEntryName(cx: HasID): ast.ModelEntryReference;
|
|
74
74
|
defaultResult(): ast.MalloyElement;
|
|
75
|
-
|
|
75
|
+
/**
|
|
76
|
+
* Attach a source location to an AST element and return it. The range
|
|
77
|
+
* is taken from the supplied parse-tree node — either a
|
|
78
|
+
* `ParserRuleContext` (entire rule's token span) or a `TerminalNode`
|
|
79
|
+
* (just that one token).
|
|
80
|
+
*/
|
|
81
|
+
protected astAt<MT extends ast.MalloyElement>(el: MT, cx: ParserRuleContext | TerminalNode): MT;
|
|
76
82
|
protected getSourceCode(cx: ParserRuleContext): string;
|
|
77
83
|
protected getFilterElement(cx: parse.FieldExprContext): ast.FilterElement;
|
|
78
84
|
protected getFieldDefs(cxList: parse.FieldDefContext[], makeFieldDef: ast.FieldDeclarationConstructor): ast.AtomicFieldDeclaration[];
|
|
@@ -275,6 +281,7 @@ export declare class MalloyToAST extends AbstractParseTreeVisitor<ast.MalloyElem
|
|
|
275
281
|
visitExprArrayLiteral(pcx: parse.ExprArrayLiteralContext): ast.ArrayLiteral;
|
|
276
282
|
visitExprWarnLike(pcx: parse.ExprWarnLikeContext): ast.ExprCompare;
|
|
277
283
|
visitExprNullCheck(pcx: parse.ExprNullCheckContext): ast.ExprIsNull;
|
|
284
|
+
visitExprInGiven(pcx: parse.ExprInGivenContext): ast.ExprInGiven;
|
|
278
285
|
visitExprWarnIn(pcx: parse.ExprWarnInContext): ast.ExprLegacyIn;
|
|
279
286
|
visitTickFilterString(pcx: parse.TickFilterStringContext): ast.ExprFilterExpression;
|
|
280
287
|
visitTripFilterString(pcx: parse.TripFilterStringContext): ast.ExprFilterExpression;
|
|
@@ -195,10 +195,19 @@ class MalloyToAST extends AbstractParseTreeVisitor_1.AbstractParseTreeVisitor {
|
|
|
195
195
|
defaultResult() {
|
|
196
196
|
return new ast.Unimplemented();
|
|
197
197
|
}
|
|
198
|
+
/**
|
|
199
|
+
* Attach a source location to an AST element and return it. The range
|
|
200
|
+
* is taken from the supplied parse-tree node — either a
|
|
201
|
+
* `ParserRuleContext` (entire rule's token span) or a `TerminalNode`
|
|
202
|
+
* (just that one token).
|
|
203
|
+
*/
|
|
198
204
|
astAt(el, cx) {
|
|
205
|
+
const range = cx instanceof antlr4ts_1.ParserRuleContext
|
|
206
|
+
? this.rangeFromContext(cx)
|
|
207
|
+
: (0, utils_1.rangeFromToken)(this.parseInfo.sourceInfo, cx.symbol);
|
|
199
208
|
el.location = {
|
|
200
209
|
url: this.parseInfo.sourceURL,
|
|
201
|
-
range
|
|
210
|
+
range,
|
|
202
211
|
};
|
|
203
212
|
return el;
|
|
204
213
|
}
|
|
@@ -317,7 +326,25 @@ class MalloyToAST extends AbstractParseTreeVisitor_1.AbstractParseTreeVisitor {
|
|
|
317
326
|
this.contextError(afterIs, 'given-no-tags-after-is', 'Annotations are not allowed between `is` and the default value of a given; place them above the declaration');
|
|
318
327
|
}
|
|
319
328
|
}
|
|
320
|
-
|
|
329
|
+
// The modifier slot accepts any identifier and rejects anything but
|
|
330
|
+
// `inline` at AST-build — this keeps `inline` from being a reserved
|
|
331
|
+
// word elsewhere in the language (fields, sources, view names, etc.
|
|
332
|
+
// can still be called `inline`). The cost: a slightly later error
|
|
333
|
+
// surface if someone misspells the modifier.
|
|
334
|
+
// Case-insensitive match: Malloy keywords are case-insensitive in
|
|
335
|
+
// the lexer, so accept `inline`, `INLINE`, `Inline`, etc.
|
|
336
|
+
const modCx = pcx.givenModifier();
|
|
337
|
+
let inline = false;
|
|
338
|
+
if (modCx) {
|
|
339
|
+
const modText = (0, parse_utils_1.getId)(modCx);
|
|
340
|
+
if (modText.toLowerCase() === 'inline') {
|
|
341
|
+
inline = true;
|
|
342
|
+
}
|
|
343
|
+
else {
|
|
344
|
+
this.contextError(modCx, 'invalid-given-modifier', { modifier: modText });
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
const decl = new ast.GivenDeclaration(name, typeDef, defVal, inline);
|
|
321
348
|
decl.extendNote({ notes: this.getNotes(pcx.tags()) });
|
|
322
349
|
return this.astAt(decl, pcx);
|
|
323
350
|
}
|
|
@@ -1766,6 +1793,14 @@ class MalloyToAST extends AbstractParseTreeVisitor_1.AbstractParseTreeVisitor {
|
|
|
1766
1793
|
const expr = pcx.fieldExpr();
|
|
1767
1794
|
return this.astAt(new ast.ExprIsNull(this.getFieldExpr(expr), pcx.NOT() ? '!=' : '='), pcx);
|
|
1768
1795
|
}
|
|
1796
|
+
visitExprInGiven(pcx) {
|
|
1797
|
+
this.inExperiment('givens', pcx);
|
|
1798
|
+
const lhs = this.getFieldExpr(pcx.fieldExpr());
|
|
1799
|
+
const isNot = !!pcx.NOT();
|
|
1800
|
+
const givenName = pcx.GIVEN_REF().text.slice(1);
|
|
1801
|
+
const givenRef = this.astAt(new ast.GivenReference(givenName), pcx.GIVEN_REF());
|
|
1802
|
+
return this.astAt(new ast.ExprInGiven(lhs, isNot, givenRef), pcx);
|
|
1803
|
+
}
|
|
1769
1804
|
visitExprWarnIn(pcx) {
|
|
1770
1805
|
const expr = this.getFieldExpr(pcx.fieldExpr());
|
|
1771
1806
|
const isNot = !!pcx.NOT();
|
package/dist/lang/parse-log.d.ts
CHANGED
|
@@ -292,6 +292,28 @@ type MessageParameterTypes = {
|
|
|
292
292
|
'given-reference-not-implemented': string;
|
|
293
293
|
'given-not-found': string;
|
|
294
294
|
'given-no-tags-after-is': string;
|
|
295
|
+
'in-given-rhs-not-array': {
|
|
296
|
+
givenName: string;
|
|
297
|
+
actualType: string;
|
|
298
|
+
};
|
|
299
|
+
'in-given-rhs-not-basic-array': {
|
|
300
|
+
givenName: string;
|
|
301
|
+
elementType: string;
|
|
302
|
+
};
|
|
303
|
+
'in-given-type-mismatch': {
|
|
304
|
+
lhsType: string;
|
|
305
|
+
elementType: string;
|
|
306
|
+
};
|
|
307
|
+
'inline-no-default': {
|
|
308
|
+
name: string;
|
|
309
|
+
};
|
|
310
|
+
'inline-bad-operator': {
|
|
311
|
+
name: string;
|
|
312
|
+
operators: string;
|
|
313
|
+
};
|
|
314
|
+
'invalid-given-modifier': {
|
|
315
|
+
modifier: string;
|
|
316
|
+
};
|
|
295
317
|
'illegal-filter-type': string;
|
|
296
318
|
'invalid-source-from-given': string;
|
|
297
319
|
'aggregate-analytic-in-select': string;
|
package/dist/lang/parse-log.js
CHANGED
|
@@ -95,6 +95,12 @@ exports.MESSAGE_FORMATTERS = {
|
|
|
95
95
|
'case-else-type-does-not-match': e => `Case else type ${e.elseType} does not match return type ${e.returnType}`,
|
|
96
96
|
'case-when-must-be-boolean': e => `Case when expression must be boolean, not ${e.whenType}`,
|
|
97
97
|
'case-when-type-does-not-match': e => `Case when type ${e.whenType} does not match value type ${e.valueType}`,
|
|
98
|
+
'in-given-rhs-not-array': e => `\`in $${e.givenName}\` requires \`${e.givenName}\` to be an array , but it is \`${e.actualType}\``,
|
|
99
|
+
'in-given-rhs-not-basic-array': e => `\`in $${e.givenName}\` requires \`${e.givenName}\` to be an array of a basic type (string, number, etc.), but its element type is \`${e.elementType}\``,
|
|
100
|
+
'in-given-type-mismatch': e => `\`in\` left-hand side type \`${e.lhsType}\` does not match the array element type \`${e.elementType}\``,
|
|
101
|
+
'inline-no-default': e => `inline given \`${e.name}\` must have a value — there is nothing to inline without one`,
|
|
102
|
+
'inline-bad-operator': e => `inline given \`${e.name}\` uses operator(s) not allowed in inline expressions: ${e.operators}`,
|
|
103
|
+
'invalid-given-modifier': e => `Unknown modifier \`${e.modifier}\` on \`given:\` declaration; the only modifier allowed here is \`inline\``,
|
|
98
104
|
};
|
|
99
105
|
function makeLogMessage(code, parameters, options) {
|
|
100
106
|
var _a, _b, _c, _d, _e;
|
|
@@ -185,6 +185,23 @@ function compileExpr(resultSet, context, expr, state = new utils_1.GenerateState
|
|
|
185
185
|
const oneOf = expr.kids.oneOf.map(o => o.sql).join(',');
|
|
186
186
|
return `${expr.kids.e.sql} ${expr.not ? 'NOT IN' : 'IN'} (${oneOf})`;
|
|
187
187
|
}
|
|
188
|
+
case 'inGiven': {
|
|
189
|
+
const bound = resolveGivenBoundExpr(context, expr.givenRef.id, expr.givenRef.refName);
|
|
190
|
+
// null binding collapses to empty-set semantics — not the SQL
|
|
191
|
+
// `IN (NULL)` shape, which has confusing NULL-membership rules.
|
|
192
|
+
if (bound.node === 'null') {
|
|
193
|
+
return expr.not ? 'TRUE' : 'FALSE';
|
|
194
|
+
}
|
|
195
|
+
if (bound.node !== 'arrayLiteral') {
|
|
196
|
+
throw new Error(`Internal compiler error: 'inGiven' bound to '${bound.node}', expected 'arrayLiteral'. The translator should have rejected a non-array given here.`);
|
|
197
|
+
}
|
|
198
|
+
if (bound.kids.values.length === 0) {
|
|
199
|
+
return expr.not ? 'TRUE' : 'FALSE';
|
|
200
|
+
}
|
|
201
|
+
const elemSqls = bound.kids.values.map(v => exprToSQL(resultSet, context, v, state));
|
|
202
|
+
const verb = expr.not ? 'NOT IN' : 'IN';
|
|
203
|
+
return `${expr.e.sql} ${verb} (${elemSqls.join(',')})`;
|
|
204
|
+
}
|
|
188
205
|
case 'like':
|
|
189
206
|
case '!like': {
|
|
190
207
|
const likeIt = expr.node === 'like' ? 'LIKE' : 'NOT LIKE';
|
|
@@ -599,20 +616,30 @@ function generateParameterFragment(resultSet, context, expr, state) {
|
|
|
599
616
|
}
|
|
600
617
|
throw new Error(`Can't generate SQL, no value for ${expr.path}`);
|
|
601
618
|
}
|
|
602
|
-
|
|
619
|
+
/**
|
|
620
|
+
* Resolve a given to the Expr that should stand in for it at SQL emit:
|
|
621
|
+
* supplied value if the caller bound one, otherwise the declaration's
|
|
622
|
+
* default. Throws when neither is available — that case is a query the
|
|
623
|
+
* compiler can't satisfy.
|
|
624
|
+
*
|
|
625
|
+
* Shared by `generateGivenFragment` ($NAME directly in an expression)
|
|
626
|
+
* and the `'inGiven'` SQL-emit case ($ARR in `expr in $ARR`).
|
|
627
|
+
*/
|
|
628
|
+
function resolveGivenBoundExpr(context, id, refName) {
|
|
603
629
|
var _a, _b;
|
|
604
|
-
const id = expr.id;
|
|
605
630
|
const supplied = (_b = (_a = context.prepareResultOptions) === null || _a === void 0 ? void 0 : _a.resolvedGivens) === null || _b === void 0 ? void 0 : _b.get(id);
|
|
606
|
-
if (supplied !== undefined)
|
|
607
|
-
return
|
|
608
|
-
}
|
|
609
|
-
// The default may itself be a `$OTHER`-bearing expression — recursive
|
|
610
|
-
// compile handles default chains.
|
|
631
|
+
if (supplied !== undefined)
|
|
632
|
+
return supplied;
|
|
611
633
|
const decl = context.getModel().givens[id];
|
|
612
|
-
if ((decl === null || decl === void 0 ? void 0 : decl.default) !== undefined)
|
|
613
|
-
return
|
|
614
|
-
|
|
615
|
-
|
|
634
|
+
if ((decl === null || decl === void 0 ? void 0 : decl.default) !== undefined)
|
|
635
|
+
return decl.default;
|
|
636
|
+
throw new Error(unsatisfiedGivenMessage(refName));
|
|
637
|
+
}
|
|
638
|
+
function generateGivenFragment(resultSet, context, expr, state) {
|
|
639
|
+
// The bound expr may itself be a `$OTHER`-bearing expression; recursive
|
|
640
|
+
// compile handles default chains.
|
|
641
|
+
const bound = resolveGivenBoundExpr(context, expr.id, expr.refName);
|
|
642
|
+
return exprToSQL(resultSet, context, bound, state);
|
|
616
643
|
}
|
|
617
644
|
function unsatisfiedGivenMessage(refName) {
|
|
618
645
|
return (`Given '${refName}' has no value and no default. ` +
|
|
@@ -1,2 +1,17 @@
|
|
|
1
1
|
import type { Expr, GivenID, GivenValue, ModelDef } from './malloy_types';
|
|
2
2
|
export declare function resolveSuppliedGivens(supplied: Record<string, GivenValue> | undefined, modelDef: ModelDef | undefined): Map<GivenID, Expr>;
|
|
3
|
+
/**
|
|
4
|
+
* Evaluate every `inline` given's default to a literal Expr and add it
|
|
5
|
+
* to the bound map. Mutates and returns `bound` for convenience.
|
|
6
|
+
*
|
|
7
|
+
* Givens already in `bound` (caller supplied a value) are left alone —
|
|
8
|
+
* the caller's value wins over the inline default. Inline givens with
|
|
9
|
+
* no default are skipped here; the translator already logged the
|
|
10
|
+
* `inline-no-default` error at declaration time.
|
|
11
|
+
*
|
|
12
|
+
* Iteration follows `modelDef.contents` insertion order, which (by
|
|
13
|
+
* Malloy's no-forward-refs rule) is also topological order: if inline
|
|
14
|
+
* A's default references inline B, B's declaration came first and is
|
|
15
|
+
* already in the map by the time A runs.
|
|
16
|
+
*/
|
|
17
|
+
export declare function evaluateInlineGivens(bound: Map<GivenID, Expr>, modelDef: ModelDef | undefined): Map<GivenID, Expr>;
|
|
@@ -5,8 +5,10 @@
|
|
|
5
5
|
*/
|
|
6
6
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
7
|
exports.resolveSuppliedGivens = resolveSuppliedGivens;
|
|
8
|
+
exports.evaluateInlineGivens = evaluateInlineGivens;
|
|
8
9
|
const luxon_1 = require("luxon");
|
|
9
10
|
const closest_match_1 = require("../util/closest_match");
|
|
11
|
+
const inline_expr_1 = require("./inline_expr");
|
|
10
12
|
const malloy_types_1 = require("./malloy_types");
|
|
11
13
|
function resolveSuppliedGivens(supplied, modelDef) {
|
|
12
14
|
var _a;
|
|
@@ -43,6 +45,39 @@ function resolveSuppliedGivens(supplied, modelDef) {
|
|
|
43
45
|
}
|
|
44
46
|
return out;
|
|
45
47
|
}
|
|
48
|
+
/**
|
|
49
|
+
* Evaluate every `inline` given's default to a literal Expr and add it
|
|
50
|
+
* to the bound map. Mutates and returns `bound` for convenience.
|
|
51
|
+
*
|
|
52
|
+
* Givens already in `bound` (caller supplied a value) are left alone —
|
|
53
|
+
* the caller's value wins over the inline default. Inline givens with
|
|
54
|
+
* no default are skipped here; the translator already logged the
|
|
55
|
+
* `inline-no-default` error at declaration time.
|
|
56
|
+
*
|
|
57
|
+
* Iteration follows `modelDef.contents` insertion order, which (by
|
|
58
|
+
* Malloy's no-forward-refs rule) is also topological order: if inline
|
|
59
|
+
* A's default references inline B, B's declaration came first and is
|
|
60
|
+
* already in the map by the time A runs.
|
|
61
|
+
*/
|
|
62
|
+
function evaluateInlineGivens(bound, modelDef) {
|
|
63
|
+
var _a;
|
|
64
|
+
if (!modelDef)
|
|
65
|
+
return bound;
|
|
66
|
+
const givens = (_a = modelDef.givens) !== null && _a !== void 0 ? _a : {};
|
|
67
|
+
for (const [, entry] of Object.entries(modelDef.contents)) {
|
|
68
|
+
if (entry.type !== 'given')
|
|
69
|
+
continue;
|
|
70
|
+
if (bound.has(entry.id))
|
|
71
|
+
continue;
|
|
72
|
+
const decl = givens[entry.id];
|
|
73
|
+
if (!(decl === null || decl === void 0 ? void 0 : decl.inline))
|
|
74
|
+
continue;
|
|
75
|
+
if (decl.default === undefined)
|
|
76
|
+
continue;
|
|
77
|
+
bound.set(entry.id, (0, inline_expr_1.inlineExpr)(decl.default, bound));
|
|
78
|
+
}
|
|
79
|
+
return bound;
|
|
80
|
+
}
|
|
46
81
|
function valueToExpr(path, type, value) {
|
|
47
82
|
var _a, _b;
|
|
48
83
|
if (value === null) {
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import type { Expr, GivenID } from './malloy_types';
|
|
2
|
+
/**
|
|
3
|
+
* Operator node types allowed inside an `inline` given's default. Single
|
|
4
|
+
* source of truth — both the translator-side validator
|
|
5
|
+
* (`GivenDeclaration.execute`) and the bind-time evaluator below read
|
|
6
|
+
* this set when deciding whether an expression is reducible to a
|
|
7
|
+
* literal at bind time.
|
|
8
|
+
*
|
|
9
|
+
* Add a new operator here AND a corresponding `case` to `inlineExpr`'s
|
|
10
|
+
* switch in the same commit; the two are intentionally co-located so
|
|
11
|
+
* they can't drift apart.
|
|
12
|
+
*/
|
|
13
|
+
export declare const INLINE_OPS: ReadonlySet<string>;
|
|
14
|
+
/**
|
|
15
|
+
* Leaf-shaped node types that are valid inside an inline expression but
|
|
16
|
+
* aren't "operators." Literals are returned as-is by the evaluator;
|
|
17
|
+
* `given` nodes resolve through the bound values map.
|
|
18
|
+
*/
|
|
19
|
+
export declare const INLINE_LEAVES: ReadonlySet<string>;
|
|
20
|
+
/**
|
|
21
|
+
* Bind-time evaluator for `inline` given defaults. Walks the Expr tree,
|
|
22
|
+
* recursing on bound values for given-refs, and returns a literal Expr
|
|
23
|
+
* (string/number/boolean/null/arrayLiteral).
|
|
24
|
+
*
|
|
25
|
+
* Throws on any node outside `INLINE_OPS ∪ INLINE_LEAVES`. The
|
|
26
|
+
* translator's pre-flight check should have rejected such defaults
|
|
27
|
+
* already, so a throw here flags a compiler bug rather than a caller
|
|
28
|
+
* error.
|
|
29
|
+
*/
|
|
30
|
+
export declare function inlineExpr(e: Expr, bound: Map<GivenID, Expr>): Expr;
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/*
|
|
3
|
+
* Copyright Contributors to the Malloy project
|
|
4
|
+
* SPDX-License-Identifier: MIT
|
|
5
|
+
*/
|
|
6
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
+
exports.INLINE_LEAVES = exports.INLINE_OPS = void 0;
|
|
8
|
+
exports.inlineExpr = inlineExpr;
|
|
9
|
+
/**
|
|
10
|
+
* Operator node types allowed inside an `inline` given's default. Single
|
|
11
|
+
* source of truth — both the translator-side validator
|
|
12
|
+
* (`GivenDeclaration.execute`) and the bind-time evaluator below read
|
|
13
|
+
* this set when deciding whether an expression is reducible to a
|
|
14
|
+
* literal at bind time.
|
|
15
|
+
*
|
|
16
|
+
* Add a new operator here AND a corresponding `case` to `inlineExpr`'s
|
|
17
|
+
* switch in the same commit; the two are intentionally co-located so
|
|
18
|
+
* they can't drift apart.
|
|
19
|
+
*/
|
|
20
|
+
exports.INLINE_OPS = new Set([
|
|
21
|
+
'and',
|
|
22
|
+
'or',
|
|
23
|
+
'not',
|
|
24
|
+
'=',
|
|
25
|
+
'!=',
|
|
26
|
+
'>',
|
|
27
|
+
'<',
|
|
28
|
+
'>=',
|
|
29
|
+
'<=',
|
|
30
|
+
'inGiven',
|
|
31
|
+
'()',
|
|
32
|
+
]);
|
|
33
|
+
/**
|
|
34
|
+
* Leaf-shaped node types that are valid inside an inline expression but
|
|
35
|
+
* aren't "operators." Literals are returned as-is by the evaluator;
|
|
36
|
+
* `given` nodes resolve through the bound values map.
|
|
37
|
+
*/
|
|
38
|
+
exports.INLINE_LEAVES = new Set([
|
|
39
|
+
'stringLiteral',
|
|
40
|
+
'numberLiteral',
|
|
41
|
+
'true',
|
|
42
|
+
'false',
|
|
43
|
+
'null',
|
|
44
|
+
'arrayLiteral',
|
|
45
|
+
'given',
|
|
46
|
+
]);
|
|
47
|
+
/**
|
|
48
|
+
* Bind-time evaluator for `inline` given defaults. Walks the Expr tree,
|
|
49
|
+
* recursing on bound values for given-refs, and returns a literal Expr
|
|
50
|
+
* (string/number/boolean/null/arrayLiteral).
|
|
51
|
+
*
|
|
52
|
+
* Throws on any node outside `INLINE_OPS ∪ INLINE_LEAVES`. The
|
|
53
|
+
* translator's pre-flight check should have rejected such defaults
|
|
54
|
+
* already, so a throw here flags a compiler bug rather than a caller
|
|
55
|
+
* error.
|
|
56
|
+
*/
|
|
57
|
+
function inlineExpr(e, bound) {
|
|
58
|
+
switch (e.node) {
|
|
59
|
+
case 'stringLiteral':
|
|
60
|
+
case 'numberLiteral':
|
|
61
|
+
case 'true':
|
|
62
|
+
case 'false':
|
|
63
|
+
case 'null':
|
|
64
|
+
case 'arrayLiteral':
|
|
65
|
+
return e;
|
|
66
|
+
case 'given': {
|
|
67
|
+
const v = bound.get(e.id);
|
|
68
|
+
if (v === undefined) {
|
|
69
|
+
throw new Error(`inlineExpr: given '${e.refName}' has no bound value and no default — translator should have caught this earlier`);
|
|
70
|
+
}
|
|
71
|
+
return inlineExpr(v, bound);
|
|
72
|
+
}
|
|
73
|
+
case '()':
|
|
74
|
+
return inlineExpr(e.e, bound);
|
|
75
|
+
case 'not': {
|
|
76
|
+
const inner = inlineExpr(e.e, bound);
|
|
77
|
+
return toBoolLiteral(!exprAsBool(inner));
|
|
78
|
+
}
|
|
79
|
+
case 'and':
|
|
80
|
+
case 'or': {
|
|
81
|
+
const left = exprAsBool(inlineExpr(e.kids.left, bound));
|
|
82
|
+
const right = exprAsBool(inlineExpr(e.kids.right, bound));
|
|
83
|
+
return toBoolLiteral(e.node === 'and' ? left && right : left || right);
|
|
84
|
+
}
|
|
85
|
+
case '=':
|
|
86
|
+
case '!=':
|
|
87
|
+
case '>':
|
|
88
|
+
case '<':
|
|
89
|
+
case '>=':
|
|
90
|
+
case '<=': {
|
|
91
|
+
const left = inlineExpr(e.kids.left, bound);
|
|
92
|
+
const right = inlineExpr(e.kids.right, bound);
|
|
93
|
+
return toBoolLiteral(compareLiterals(e.node, left, right));
|
|
94
|
+
}
|
|
95
|
+
case 'inGiven': {
|
|
96
|
+
const lhs = inlineExpr(e.e, bound);
|
|
97
|
+
const arrBound = bound.get(e.givenRef.id);
|
|
98
|
+
if (arrBound === undefined) {
|
|
99
|
+
throw new Error(`inlineExpr: given '${e.givenRef.refName}' has no bound value and no default — translator should have caught this earlier`);
|
|
100
|
+
}
|
|
101
|
+
if (arrBound.node === 'null') {
|
|
102
|
+
return toBoolLiteral(e.not);
|
|
103
|
+
}
|
|
104
|
+
if (arrBound.node !== 'arrayLiteral') {
|
|
105
|
+
throw new Error(`inlineExpr: 'inGiven' bound to '${arrBound.node}', expected 'arrayLiteral'`);
|
|
106
|
+
}
|
|
107
|
+
const found = arrBound.kids.values.some(v => compareLiterals('=', lhs, inlineExpr(v, bound)));
|
|
108
|
+
return toBoolLiteral(e.not ? !found : found);
|
|
109
|
+
}
|
|
110
|
+
default:
|
|
111
|
+
throw new Error(`inlineExpr: unexpected node '${e.node}' — translator should have rejected this`);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
function toBoolLiteral(b) {
|
|
115
|
+
return { node: b ? 'true' : 'false' };
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Read a literal Expr as a JS boolean. The evaluator only ever produces
|
|
119
|
+
* 'true' / 'false' / 'null' / scalar literals here; anything else means
|
|
120
|
+
* we tried to use a non-boolean where a boolean was expected, which is a
|
|
121
|
+
* translator type-check bug.
|
|
122
|
+
*/
|
|
123
|
+
function exprAsBool(e) {
|
|
124
|
+
if (e.node === 'true')
|
|
125
|
+
return true;
|
|
126
|
+
if (e.node === 'false')
|
|
127
|
+
return false;
|
|
128
|
+
// `null` in boolean position is treated as false — matches the
|
|
129
|
+
// implicit SQL coalesce that Malloy does for filter expressions.
|
|
130
|
+
if (e.node === 'null')
|
|
131
|
+
return false;
|
|
132
|
+
throw new Error(`inlineExpr: expected boolean literal, got '${e.node}' — translator should have type-checked this`);
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* JS-side comparison of two literal Exprs. Only the literal nodes
|
|
136
|
+
* produced by the evaluator are supported.
|
|
137
|
+
*
|
|
138
|
+
* SQL nullability: any operand being null produces false (matching
|
|
139
|
+
* Malloy's COALESCE-to-true / COALESCE-to-false behavior elsewhere).
|
|
140
|
+
*/
|
|
141
|
+
function compareLiterals(op, left, right) {
|
|
142
|
+
if (left.node === 'null' || right.node === 'null') {
|
|
143
|
+
// Mirror SQL: comparisons with NULL are unknown; treat as false for
|
|
144
|
+
// = / > / < / >= / <=, true for !=.
|
|
145
|
+
return op === '!=';
|
|
146
|
+
}
|
|
147
|
+
const l = literalToJS(left);
|
|
148
|
+
const r = literalToJS(right);
|
|
149
|
+
switch (op) {
|
|
150
|
+
case '=':
|
|
151
|
+
return l === r;
|
|
152
|
+
case '!=':
|
|
153
|
+
return l !== r;
|
|
154
|
+
case '>':
|
|
155
|
+
return l > r;
|
|
156
|
+
case '<':
|
|
157
|
+
return l < r;
|
|
158
|
+
case '>=':
|
|
159
|
+
return l >= r;
|
|
160
|
+
case '<=':
|
|
161
|
+
return l <= r;
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
/**
|
|
165
|
+
* Convert a literal Expr to a comparable JS value. Numbers come through
|
|
166
|
+
* `numberLiteral` as a string (full precision preserved); we parse to a
|
|
167
|
+
* JS number for ordering — bind-time precision loss here is fine
|
|
168
|
+
* because the result of an inline expression is a boolean for SQL.
|
|
169
|
+
*/
|
|
170
|
+
function literalToJS(e) {
|
|
171
|
+
switch (e.node) {
|
|
172
|
+
case 'stringLiteral':
|
|
173
|
+
return e.literal;
|
|
174
|
+
case 'numberLiteral':
|
|
175
|
+
return Number(e.literal);
|
|
176
|
+
case 'true':
|
|
177
|
+
return true;
|
|
178
|
+
case 'false':
|
|
179
|
+
return false;
|
|
180
|
+
default:
|
|
181
|
+
throw new Error(`inlineExpr: cannot compare non-literal node '${e.node}'`);
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
//# sourceMappingURL=inline_expr.js.map
|
|
@@ -36,7 +36,7 @@ export type AnyExpr = ExprE | ExprOptionalE | ExprWithKids | ExprLeaf;
|
|
|
36
36
|
export declare function exprHasKids(e: AnyExpr): e is ExprWithKids;
|
|
37
37
|
export declare function exprHasE(e: AnyExpr): e is ExprE;
|
|
38
38
|
export declare function exprIsLeaf(e: AnyExpr): boolean;
|
|
39
|
-
export type Expr = BinaryExpr | UnaryExpr | FunctionCallNode | OutputFieldNode | FilterCondition | FilteredExpr | AggregateExpr | EmptyExpr | UngroupNode | FunctionParameterNode | SpreadExpr | AggregateOrderByNode | AggregateLimitNode | FieldnameNode | SourceReferenceNode | ParameterNode | GivenRefNode | NowNode | MeasureTimeExpr | TimeExtractExpr | TimeDeltaExpr | TimeTruncExpr | DateLiteralNode | TimestampLiteralNode | TimestamptzLiteralNode | TypecastExpr | RegexMatchExpr | RegexLiteralNode | FilterMatchExpr | FilterLiteralExpr | StringLiteralNode | NumberLiteralNode | BooleanLiteralNode | RecordLiteralNode | ArrayLiteralNode | FunctionOrderBy | GenericSQLExpr | NullNode | CaseExpr | InCompareExpr | CompositeFieldExpr | ErrorNode;
|
|
39
|
+
export type Expr = BinaryExpr | UnaryExpr | FunctionCallNode | OutputFieldNode | FilterCondition | FilteredExpr | AggregateExpr | EmptyExpr | UngroupNode | FunctionParameterNode | SpreadExpr | AggregateOrderByNode | AggregateLimitNode | FieldnameNode | SourceReferenceNode | ParameterNode | GivenRefNode | NowNode | MeasureTimeExpr | TimeExtractExpr | TimeDeltaExpr | TimeTruncExpr | DateLiteralNode | TimestampLiteralNode | TimestamptzLiteralNode | TypecastExpr | RegexMatchExpr | RegexLiteralNode | FilterMatchExpr | FilterLiteralExpr | StringLiteralNode | NumberLiteralNode | BooleanLiteralNode | RecordLiteralNode | ArrayLiteralNode | FunctionOrderBy | GenericSQLExpr | NullNode | CaseExpr | InCompareExpr | InGivenExpr | CompositeFieldExpr | ErrorNode;
|
|
40
40
|
export type BinaryOperator = '+' | '-' | '*' | '%' | '/' | 'and' | 'or' | '=' | '!=' | '>' | '<' | '>=' | '<=' | 'coalesce' | 'like' | '!like';
|
|
41
41
|
export interface BinaryExpr extends ExprWithKids {
|
|
42
42
|
node: BinaryOperator;
|
|
@@ -289,6 +289,19 @@ export interface InCompareExpr extends ExprWithKids {
|
|
|
289
289
|
oneOf: Expr[];
|
|
290
290
|
};
|
|
291
291
|
}
|
|
292
|
+
/**
|
|
293
|
+
* Test against a runtime-bound array given: `expr in $ARRAY_GIVEN`.
|
|
294
|
+
*
|
|
295
|
+
* Uses ExprE (one child `e`, the LHS). The given reference is embedded
|
|
296
|
+
* as a top-level field rather than a kid so the auto-visitor in
|
|
297
|
+
* `compileExpr` doesn't descend into it; resolution and per-element
|
|
298
|
+
* SQL emission happen in the `case 'inGiven':` handler.
|
|
299
|
+
*/
|
|
300
|
+
export interface InGivenExpr extends ExprE {
|
|
301
|
+
node: 'inGiven';
|
|
302
|
+
not: boolean;
|
|
303
|
+
givenRef: GivenRefNode;
|
|
304
|
+
}
|
|
292
305
|
export type ExpressionType = 'scalar' | 'aggregate' | 'scalar_analytic' | 'aggregate_analytic' | 'ungrouped_aggregate';
|
|
293
306
|
export interface Expression {
|
|
294
307
|
e?: Expr;
|
|
@@ -341,6 +354,11 @@ export interface Given extends HasLocation, HasAnnotation {
|
|
|
341
354
|
* its own default. Empty/undefined when the default is a closed
|
|
342
355
|
* literal. */
|
|
343
356
|
givenUsage?: GivenUsage;
|
|
357
|
+
/** Marked with the `inline` modifier — the default is eager-evaluated
|
|
358
|
+
* to a literal at bind time and substituted as that literal in SQL.
|
|
359
|
+
* Translator validates the default is eager-evaluable; bind-time
|
|
360
|
+
* evaluator (`model/inline_expr.ts`) performs the reduction. */
|
|
361
|
+
inline?: boolean;
|
|
344
362
|
}
|
|
345
363
|
export interface GivenEntry {
|
|
346
364
|
type: 'given';
|
|
@@ -103,7 +103,7 @@ export declare class QueryQuery extends QueryField {
|
|
|
103
103
|
generateTurtlePipelineSQL(fi: FieldInstanceResult, stageWriter: StageWriter, sourceSQLExpression: string): {
|
|
104
104
|
structDef: QueryResultDef;
|
|
105
105
|
pipeOut: any;
|
|
106
|
-
repeatedResultType: "
|
|
106
|
+
repeatedResultType: "inline" | "nested" | "inline_all_numbers";
|
|
107
107
|
};
|
|
108
108
|
generateComplexSQL(stageWriter: StageWriter): string;
|
|
109
109
|
generateSQL(stageWriter: StageWriter): string;
|
package/dist/version.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const MALLOY_VERSION = "0.0.
|
|
1
|
+
export declare const MALLOY_VERSION = "0.0.394";
|
package/dist/version.js
CHANGED
|
@@ -2,5 +2,5 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.MALLOY_VERSION = void 0;
|
|
4
4
|
// generated with 'generate-version-file' script; do not edit manually
|
|
5
|
-
exports.MALLOY_VERSION = '0.0.
|
|
5
|
+
exports.MALLOY_VERSION = '0.0.394';
|
|
6
6
|
//# sourceMappingURL=version.js.map
|