@malloydata/malloy 0.0.427 → 0.0.429
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/build_targets.d.ts +50 -0
- package/dist/api/foundation/build_targets.js +221 -0
- package/dist/api/foundation/core.d.ts +54 -10
- package/dist/api/foundation/core.js +80 -17
- package/dist/api/foundation/index.d.ts +1 -1
- package/dist/api/foundation/runtime.d.ts +18 -1
- package/dist/api/foundation/runtime.js +35 -0
- package/dist/api/foundation/types.d.ts +93 -6
- package/dist/index.d.ts +1 -1
- package/dist/lang/ast/expressions/apply.d.ts +1 -1
- package/dist/lang/ast/expressions/apply.js +2 -2
- package/dist/lang/ast/expressions/binary-boolean.d.ts +1 -1
- package/dist/lang/ast/expressions/binary-boolean.js +1 -1
- package/dist/lang/ast/expressions/binary-numeric.d.ts +1 -1
- package/dist/lang/ast/expressions/binary-numeric.js +1 -1
- package/dist/lang/ast/expressions/boolean.d.ts +1 -1
- package/dist/lang/ast/expressions/boolean.js +1 -1
- package/dist/lang/ast/expressions/case.d.ts +1 -1
- package/dist/lang/ast/expressions/case.js +1 -1
- package/dist/lang/ast/expressions/constant-expression.d.ts +2 -1
- package/dist/lang/ast/expressions/constant-expression.js +6 -1
- package/dist/lang/ast/expressions/expr-aggregate-function.d.ts +1 -1
- package/dist/lang/ast/expressions/expr-aggregate-function.js +14 -7
- package/dist/lang/ast/expressions/expr-alternation-tree.d.ts +1 -1
- package/dist/lang/ast/expressions/expr-alternation-tree.js +1 -1
- package/dist/lang/ast/expressions/expr-array-literal.d.ts +1 -1
- package/dist/lang/ast/expressions/expr-array-literal.js +1 -1
- package/dist/lang/ast/expressions/expr-cast.d.ts +1 -1
- package/dist/lang/ast/expressions/expr-cast.js +1 -1
- package/dist/lang/ast/expressions/expr-coalesce.d.ts +1 -1
- package/dist/lang/ast/expressions/expr-coalesce.js +1 -1
- package/dist/lang/ast/expressions/expr-compare.d.ts +4 -4
- package/dist/lang/ast/expressions/expr-compare.js +4 -4
- package/dist/lang/ast/expressions/expr-count.d.ts +1 -1
- package/dist/lang/ast/expressions/expr-count.js +1 -1
- package/dist/lang/ast/expressions/expr-filter-expr.d.ts +1 -1
- package/dist/lang/ast/expressions/expr-filter-expr.js +1 -1
- package/dist/lang/ast/expressions/expr-func.d.ts +1 -1
- package/dist/lang/ast/expressions/expr-func.js +1 -1
- package/dist/lang/ast/expressions/expr-given.d.ts +1 -1
- package/dist/lang/ast/expressions/expr-given.js +1 -1
- package/dist/lang/ast/expressions/expr-granular-time.d.ts +1 -1
- package/dist/lang/ast/expressions/expr-granular-time.js +1 -1
- package/dist/lang/ast/expressions/expr-id-reference.d.ts +1 -1
- package/dist/lang/ast/expressions/expr-id-reference.js +1 -1
- package/dist/lang/ast/expressions/expr-minus.d.ts +1 -1
- package/dist/lang/ast/expressions/expr-minus.js +1 -1
- package/dist/lang/ast/expressions/expr-not.d.ts +1 -1
- package/dist/lang/ast/expressions/expr-not.js +1 -1
- package/dist/lang/ast/expressions/expr-now.d.ts +1 -1
- package/dist/lang/ast/expressions/expr-now.js +1 -1
- package/dist/lang/ast/expressions/expr-null.d.ts +3 -3
- package/dist/lang/ast/expressions/expr-null.js +14 -9
- package/dist/lang/ast/expressions/expr-number.d.ts +1 -1
- package/dist/lang/ast/expressions/expr-number.js +1 -1
- package/dist/lang/ast/expressions/expr-parens.d.ts +1 -1
- package/dist/lang/ast/expressions/expr-parens.js +5 -2
- package/dist/lang/ast/expressions/expr-props.d.ts +1 -1
- package/dist/lang/ast/expressions/expr-props.js +1 -1
- package/dist/lang/ast/expressions/expr-record-literal.d.ts +1 -1
- package/dist/lang/ast/expressions/expr-record-literal.js +1 -1
- package/dist/lang/ast/expressions/expr-regex.d.ts +1 -1
- package/dist/lang/ast/expressions/expr-regex.js +1 -1
- package/dist/lang/ast/expressions/expr-string.d.ts +1 -1
- package/dist/lang/ast/expressions/expr-string.js +1 -1
- package/dist/lang/ast/expressions/expr-time-extract.d.ts +1 -1
- package/dist/lang/ast/expressions/expr-time-extract.js +1 -1
- package/dist/lang/ast/expressions/expr-time.d.ts +1 -1
- package/dist/lang/ast/expressions/expr-time.js +1 -1
- package/dist/lang/ast/expressions/expr-ungroup.d.ts +1 -1
- package/dist/lang/ast/expressions/expr-ungroup.js +1 -1
- package/dist/lang/ast/expressions/for-range.d.ts +1 -1
- package/dist/lang/ast/expressions/for-range.js +1 -1
- package/dist/lang/ast/expressions/partial-compare.d.ts +1 -1
- package/dist/lang/ast/expressions/partial-compare.js +1 -1
- package/dist/lang/ast/expressions/pick-when.d.ts +1 -1
- package/dist/lang/ast/expressions/pick-when.js +1 -1
- package/dist/lang/ast/expressions/range.d.ts +1 -1
- package/dist/lang/ast/expressions/range.js +1 -1
- package/dist/lang/ast/expressions/time-literal.d.ts +2 -2
- package/dist/lang/ast/expressions/time-literal.js +2 -2
- package/dist/lang/ast/field-space/dynamic-space.js +20 -3
- package/dist/lang/ast/field-space/parameter-space.d.ts +1 -0
- package/dist/lang/ast/field-space/parameter-space.js +6 -0
- package/dist/lang/ast/field-space/passthrough-space.d.ts +1 -0
- package/dist/lang/ast/field-space/passthrough-space.js +3 -0
- package/dist/lang/ast/field-space/static-space.d.ts +8 -0
- package/dist/lang/ast/field-space/static-space.js +14 -0
- package/dist/lang/ast/query-builders/index-builder.js +1 -1
- package/dist/lang/ast/query-elements/query-arrow.d.ts +1 -1
- package/dist/lang/ast/query-elements/query-arrow.js +10 -4
- package/dist/lang/ast/query-elements/query-base.d.ts +1 -1
- package/dist/lang/ast/query-elements/query-base.js +1 -6
- package/dist/lang/ast/query-elements/query-raw.d.ts +1 -1
- package/dist/lang/ast/query-elements/query-raw.js +4 -2
- package/dist/lang/ast/query-elements/query-reference.d.ts +1 -1
- package/dist/lang/ast/query-elements/query-reference.js +9 -3
- package/dist/lang/ast/query-elements/query-refine.d.ts +1 -1
- package/dist/lang/ast/query-elements/query-refine.js +10 -4
- package/dist/lang/ast/query-properties/drill.d.ts +1 -1
- package/dist/lang/ast/query-properties/drill.js +1 -1
- package/dist/lang/ast/source-elements/query-source.js +1 -1
- package/dist/lang/ast/source-elements/sql-source.js +2 -2
- package/dist/lang/ast/source-query-elements/source-query-element.d.ts +27 -0
- package/dist/lang/ast/source-query-elements/source-query-element.js +33 -0
- package/dist/lang/ast/source-query-elements/sq-arrow.d.ts +2 -0
- package/dist/lang/ast/source-query-elements/sq-arrow.js +12 -6
- package/dist/lang/ast/source-query-elements/sq-refine.d.ts +2 -0
- package/dist/lang/ast/source-query-elements/sq-refine.js +12 -6
- package/dist/lang/ast/source-query-elements/sq-source.d.ts +1 -0
- package/dist/lang/ast/source-query-elements/sq-source.js +6 -3
- package/dist/lang/ast/sql-elements/sql-string.d.ts +1 -1
- package/dist/lang/ast/sql-elements/sql-string.js +25 -5
- package/dist/lang/ast/statements/import-statement.js +7 -10
- package/dist/lang/ast/types/expression-def.d.ts +50 -23
- package/dist/lang/ast/types/expression-def.js +127 -65
- package/dist/lang/ast/types/field-space.d.ts +14 -0
- package/dist/lang/ast/types/field-space.js +36 -0
- package/dist/lang/ast/types/query-element.d.ts +15 -1
- package/dist/lang/parse-log.d.ts +2 -0
- package/dist/lang/test/test-translator.d.ts +17 -0
- package/dist/lang/test/test-translator.js +46 -2
- package/dist/model/malloy_types.d.ts +0 -13
- package/dist/model/malloy_types.js +0 -19
- package/dist/model/persist_utils.d.ts +61 -10
- package/dist/model/persist_utils.js +172 -63
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +4 -4
|
@@ -39,7 +39,6 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
39
39
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
40
40
|
exports.ExprDuration = exports.ExpressionDef = exports.ATNodeType = void 0;
|
|
41
41
|
exports.getMorphicValue = getMorphicValue;
|
|
42
|
-
exports.applyBinary = applyBinary;
|
|
43
42
|
exports.checkFilterExpression = checkFilterExpression;
|
|
44
43
|
const malloy_types_1 = require("../../../model/malloy_types");
|
|
45
44
|
const TDU = __importStar(require("../typedesc-utils"));
|
|
@@ -52,6 +51,31 @@ const binary_operators_1 = require("./binary_operators");
|
|
|
52
51
|
const malloy_filter_1 = require("@malloydata/malloy-filter");
|
|
53
52
|
class TypeMismatch extends Error {
|
|
54
53
|
}
|
|
54
|
+
/**
|
|
55
|
+
* Mark a value as belonging to everyone. Writing into a memoized value writes
|
|
56
|
+
* into every other holder's copy and into whatever the IR already stored;
|
|
57
|
+
* freezing turns that from a silent miscompile into a TypeError naming the
|
|
58
|
+
* line which did it. Own fields only -- walking every Expr on every
|
|
59
|
+
* evaluation would cost more than the memo saves.
|
|
60
|
+
*/
|
|
61
|
+
function shareable(v) {
|
|
62
|
+
var _a, _b;
|
|
63
|
+
Object.freeze(v);
|
|
64
|
+
for (const share of [
|
|
65
|
+
v.value,
|
|
66
|
+
v.morphic,
|
|
67
|
+
v.refSummary,
|
|
68
|
+
(_a = v.refSummary) === null || _a === void 0 ? void 0 : _a.fieldUsage,
|
|
69
|
+
(_b = v.refSummary) === null || _b === void 0 ? void 0 : _b.givenUsage,
|
|
70
|
+
v.requiresGroupBy,
|
|
71
|
+
v.ungroupings,
|
|
72
|
+
]) {
|
|
73
|
+
if (share !== undefined) {
|
|
74
|
+
Object.freeze(share);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
return v;
|
|
78
|
+
}
|
|
55
79
|
/** Node types in an alternation tree */
|
|
56
80
|
var ATNodeType;
|
|
57
81
|
(function (ATNodeType) {
|
|
@@ -62,9 +86,9 @@ var ATNodeType;
|
|
|
62
86
|
})(ATNodeType || (exports.ATNodeType = ATNodeType = {}));
|
|
63
87
|
/**
|
|
64
88
|
* Root node for any element in an expression. These essentially
|
|
65
|
-
* create a sub-tree in the larger AST.
|
|
66
|
-
*
|
|
67
|
-
*
|
|
89
|
+
* create a sub-tree in the larger AST. An ExpressionDef, when
|
|
90
|
+
* given a FieldSpace, can be evaluated to produce an ExprValue
|
|
91
|
+
* which is the IR for an Expr along with its type and other metadata.
|
|
68
92
|
*/
|
|
69
93
|
class ExpressionDef extends malloy_element_1.MalloyElement {
|
|
70
94
|
constructor() {
|
|
@@ -74,6 +98,53 @@ class ExpressionDef extends malloy_element_1.MalloyElement {
|
|
|
74
98
|
granular() {
|
|
75
99
|
return false;
|
|
76
100
|
}
|
|
101
|
+
/**
|
|
102
|
+
* Returns the "translation" or Expr tree for SQL generation. When asking
|
|
103
|
+
* for a translation you may pass the types you can accept, allowing
|
|
104
|
+
* the translation code a chance to convert to match your expectations
|
|
105
|
+
* @param space Namespace for looking up field references
|
|
106
|
+
*
|
|
107
|
+
* THE RETURNED ExprValue IS SHARED -- TREAT IT AS IMMUTABLE. Asking twice
|
|
108
|
+
* yields the same object, so to derive a value from one, build a new one:
|
|
109
|
+
* `{...operand, type: 'boolean'}` or `computedExprValue({…, from: [operand]})`.
|
|
110
|
+
*
|
|
111
|
+
* DO NOT OVERRIDE THIS. Nodes implement `computeExpression`; an override
|
|
112
|
+
* here bypasses the memo, which for `+`/`-` chains is the difference
|
|
113
|
+
* between linear and exponential compile time.
|
|
114
|
+
*/
|
|
115
|
+
getExpression(fs) {
|
|
116
|
+
const initialGeneration = fs.generation();
|
|
117
|
+
if (this.memoValue !== undefined &&
|
|
118
|
+
this.memoFs === fs &&
|
|
119
|
+
this.memoGen === initialGeneration) {
|
|
120
|
+
return this.memoValue;
|
|
121
|
+
}
|
|
122
|
+
// If we get here and memoFs is set and memoGen is not set, then somewhere
|
|
123
|
+
// inside this expression is a recursive call asking this node for the
|
|
124
|
+
// value it is in the middle of computing. There is nothing to return and
|
|
125
|
+
// computing one would recurse again, so throw. An abandoned computation
|
|
126
|
+
// does not look like this -- see the finally below.
|
|
127
|
+
if (this.memoFs !== undefined && this.memoGen === undefined) {
|
|
128
|
+
throw this.internalError(`ExpressionDef.getExpression memoization failure on ${this.elementType}`);
|
|
129
|
+
}
|
|
130
|
+
this.memoFs = fs;
|
|
131
|
+
this.memoGen = undefined;
|
|
132
|
+
try {
|
|
133
|
+
const value = this.computeExpression(fs);
|
|
134
|
+
if (initialGeneration === fs.generation()) {
|
|
135
|
+
this.memoGen = initialGeneration;
|
|
136
|
+
this.memoValue = shareable(value);
|
|
137
|
+
}
|
|
138
|
+
return value;
|
|
139
|
+
}
|
|
140
|
+
finally {
|
|
141
|
+
if (this.memoGen === undefined) {
|
|
142
|
+
// Threw, or a name was rebound while we ran. Leave no stamp: a caller
|
|
143
|
+
// which recovers must get a fresh computation, not our remains.
|
|
144
|
+
this.memoFs = undefined;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
}
|
|
77
148
|
/**
|
|
78
149
|
* Some operators want to give the right hand value a chance to
|
|
79
150
|
* rewrite itself. This requests a translation for a rewrite,
|
|
@@ -110,17 +181,38 @@ class ExpressionDef extends malloy_element_1.MalloyElement {
|
|
|
110
181
|
return undefined;
|
|
111
182
|
}
|
|
112
183
|
/**
|
|
113
|
-
* This is the operation which makes partial comparison and value trees work
|
|
114
|
-
*
|
|
115
|
-
*
|
|
116
|
-
* the
|
|
184
|
+
* This is the operation which makes partial comparison and value trees work.
|
|
185
|
+
* `this` is the RIGHT operand; the left arrives here as an argument. All of
|
|
186
|
+
* the magic of malloy expressions eventually flows through here, where an
|
|
187
|
+
* operator is applied to two values -- depending on the operator and the
|
|
188
|
+
* value types that may transform the values or even the operator.
|
|
189
|
+
*
|
|
190
|
+
* Specialized nodes like alternation trees and partial comparisons override
|
|
191
|
+
* this to control how the application gets generated. They are the reason
|
|
192
|
+
* the right operand is in charge: a partial has no bound left operand, and
|
|
193
|
+
* only learns it here.
|
|
117
194
|
* @param fs The symbol table
|
|
118
195
|
* @param op The operator being applied
|
|
119
|
-
* @param
|
|
196
|
+
* @param left The "other" (besides 'this') value
|
|
120
197
|
* @return The translated expression
|
|
121
198
|
*/
|
|
122
199
|
apply(fs, op, left, _warnOnComplexTree = false) {
|
|
123
|
-
|
|
200
|
+
if ((0, binary_operators_1.isEquality)(op)) {
|
|
201
|
+
return equality(fs, left, op, this);
|
|
202
|
+
}
|
|
203
|
+
if ((0, binary_operators_1.isComparison)(op)) {
|
|
204
|
+
return compare(fs, left, op, this);
|
|
205
|
+
}
|
|
206
|
+
if (op === '+' || op === '-') {
|
|
207
|
+
return delta(fs, left, op, this);
|
|
208
|
+
}
|
|
209
|
+
if (op === '*') {
|
|
210
|
+
return numeric(fs, left, op, this);
|
|
211
|
+
}
|
|
212
|
+
if (op === '/' || op === '%') {
|
|
213
|
+
return divmod(fs, left, op, this);
|
|
214
|
+
}
|
|
215
|
+
return left.loggedErrorExpr('unexpected-binary-operator', `Cannot use ${op} operator here`);
|
|
124
216
|
}
|
|
125
217
|
canSupportPartitionBy() {
|
|
126
218
|
return false;
|
|
@@ -178,7 +270,7 @@ class ExprDuration extends ExpressionDef {
|
|
|
178
270
|
}
|
|
179
271
|
return super.apply(fs, op, left);
|
|
180
272
|
}
|
|
181
|
-
|
|
273
|
+
computeExpression(fs) {
|
|
182
274
|
const num = this.n.getExpression(fs);
|
|
183
275
|
return (0, expr_value_1.computedErrorExprValue)({
|
|
184
276
|
dataType: { type: 'duration' },
|
|
@@ -402,6 +494,30 @@ function numeric(fs, left, op, right) {
|
|
|
402
494
|
}
|
|
403
495
|
return (0, ast_utils_1.errorFor)('numbers required');
|
|
404
496
|
}
|
|
497
|
+
function divmod(fs, left, op, right) {
|
|
498
|
+
const num = left.getExpression(fs);
|
|
499
|
+
const denom = right.getExpression(fs);
|
|
500
|
+
const noGo = unsupportError(left, num, right, denom);
|
|
501
|
+
if (noGo)
|
|
502
|
+
return noGo;
|
|
503
|
+
const err = errorCascade('number', num, denom);
|
|
504
|
+
if (err)
|
|
505
|
+
return err;
|
|
506
|
+
if (num.type !== 'number') {
|
|
507
|
+
left.logError('arithmetic-operation-type-mismatch', 'Numerator must be a number');
|
|
508
|
+
}
|
|
509
|
+
else if (denom.type !== 'number') {
|
|
510
|
+
right.logError('arithmetic-operation-type-mismatch', 'Denominator must be a number');
|
|
511
|
+
}
|
|
512
|
+
else {
|
|
513
|
+
return (0, expr_value_1.computedExprValue)({
|
|
514
|
+
dataType: mergeNumberTypes(num, denom, op),
|
|
515
|
+
value: { node: op, kids: { left: num.value, right: denom.value } },
|
|
516
|
+
from: [num, denom],
|
|
517
|
+
});
|
|
518
|
+
}
|
|
519
|
+
return (0, ast_utils_1.errorFor)('divide type mismatch');
|
|
520
|
+
}
|
|
405
521
|
function delta(fs, left, op, right) {
|
|
406
522
|
const lhs = left.getExpression(fs);
|
|
407
523
|
const rhs = right.getExpression(fs);
|
|
@@ -430,60 +546,6 @@ function delta(fs, left, op, right) {
|
|
|
430
546
|
}
|
|
431
547
|
return numeric(fs, left, op, right);
|
|
432
548
|
}
|
|
433
|
-
/**
|
|
434
|
-
* All of the magic of malloy expressions eventually flows to here,
|
|
435
|
-
* where an operator is applied to two values. Depending on the
|
|
436
|
-
* operator and value types this may involve transformations of
|
|
437
|
-
* the values or even the operator.
|
|
438
|
-
* @param fs FieldSpace for the symbols
|
|
439
|
-
* @param left Left value
|
|
440
|
-
* @param op The operator
|
|
441
|
-
* @param right Right Value
|
|
442
|
-
* @return ExprValue of the expression
|
|
443
|
-
*/
|
|
444
|
-
function applyBinary(fs, left, op, right) {
|
|
445
|
-
if ((0, binary_operators_1.isEquality)(op)) {
|
|
446
|
-
return equality(fs, left, op, right);
|
|
447
|
-
}
|
|
448
|
-
if ((0, binary_operators_1.isComparison)(op)) {
|
|
449
|
-
return compare(fs, left, op, right);
|
|
450
|
-
}
|
|
451
|
-
if (op === '+' || op === '-') {
|
|
452
|
-
return delta(fs, left, op, right);
|
|
453
|
-
}
|
|
454
|
-
if (op === '*') {
|
|
455
|
-
return numeric(fs, left, op, right);
|
|
456
|
-
}
|
|
457
|
-
if (op === '/' || op === '%') {
|
|
458
|
-
const num = left.getExpression(fs);
|
|
459
|
-
const denom = right.getExpression(fs);
|
|
460
|
-
const noGo = unsupportError(left, num, right, denom);
|
|
461
|
-
if (noGo)
|
|
462
|
-
return noGo;
|
|
463
|
-
const err = errorCascade('number', num, denom);
|
|
464
|
-
if (err)
|
|
465
|
-
return err;
|
|
466
|
-
if (num.type !== 'number') {
|
|
467
|
-
left.logError('arithmetic-operation-type-mismatch', 'Numerator must be a number');
|
|
468
|
-
}
|
|
469
|
-
else if (denom.type !== 'number') {
|
|
470
|
-
right.logError('arithmetic-operation-type-mismatch', 'Denominator must be a number');
|
|
471
|
-
}
|
|
472
|
-
else {
|
|
473
|
-
const divmod = {
|
|
474
|
-
node: op,
|
|
475
|
-
kids: { left: num.value, right: denom.value },
|
|
476
|
-
};
|
|
477
|
-
return (0, expr_value_1.computedExprValue)({
|
|
478
|
-
dataType: mergeNumberTypes(num, denom, op),
|
|
479
|
-
value: divmod,
|
|
480
|
-
from: [num, denom],
|
|
481
|
-
});
|
|
482
|
-
}
|
|
483
|
-
return (0, ast_utils_1.errorFor)('divide type mismatch');
|
|
484
|
-
}
|
|
485
|
-
return left.loggedErrorExpr('unexpected-binary-operator', `Cannot use ${op} operator here`);
|
|
486
|
-
}
|
|
487
549
|
function errorCascade(type, ...es) {
|
|
488
550
|
if (es.some(e => e.type === 'error')) {
|
|
489
551
|
return (0, expr_value_1.computedExprValue)({
|
|
@@ -4,6 +4,14 @@ import type { QueryOperationSpace } from '../field-space/query-spaces';
|
|
|
4
4
|
import type { LookupResult } from './lookup-result';
|
|
5
5
|
import { MalloyElement } from './malloy-element';
|
|
6
6
|
import type { SpaceEntry } from './space-entry';
|
|
7
|
+
export declare function currentGeneration(): number;
|
|
8
|
+
/**
|
|
9
|
+
* Called by every field space operation that binds, rebinds or unbinds a
|
|
10
|
+
* name. Lazily materializing a space's entries from its StructDef is not
|
|
11
|
+
* such an operation -- the bindings were already logically present, so
|
|
12
|
+
* nothing a cache observed can have changed.
|
|
13
|
+
*/
|
|
14
|
+
export declare function noteRebinding(): void;
|
|
7
15
|
/**
|
|
8
16
|
* A FieldSpace is a hierarchy of namespaces, where the leaf nodes
|
|
9
17
|
* are fields. A FieldSpace can lookup fields, and generate a StructDef
|
|
@@ -20,6 +28,12 @@ export interface FieldSpace {
|
|
|
20
28
|
connectionName(): string;
|
|
21
29
|
isQueryFieldSpace(): this is QueryFieldSpace;
|
|
22
30
|
accessProtectionLevel(): AccessModifierLabel;
|
|
31
|
+
/**
|
|
32
|
+
* A value which changes whenever any name binding changes. A result
|
|
33
|
+
* computed by consulting this space may be reused only while this is
|
|
34
|
+
* unchanged. See `noteRebinding` above.
|
|
35
|
+
*/
|
|
36
|
+
generation(): number;
|
|
23
37
|
}
|
|
24
38
|
export interface SourceFieldSpace extends FieldSpace {
|
|
25
39
|
structDef(): SourceDef;
|
|
@@ -5,7 +5,43 @@
|
|
|
5
5
|
*/
|
|
6
6
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
7
|
exports.FieldName = void 0;
|
|
8
|
+
exports.currentGeneration = currentGeneration;
|
|
9
|
+
exports.noteRebinding = noteRebinding;
|
|
8
10
|
const malloy_element_1 = require("./malloy-element");
|
|
11
|
+
/**
|
|
12
|
+
* Some field spaces are static, some are dynamic. We are computing a very
|
|
13
|
+
* conservative process wide "generation" which moves forward when any
|
|
14
|
+
* dynamic field space changes. We can be very certain that if the field space
|
|
15
|
+
* generation has not changed, computations depending on the contents of
|
|
16
|
+
* field spaces are still valid.
|
|
17
|
+
*
|
|
18
|
+
* A more fine grained validity is hard to compute and possibly a fragile
|
|
19
|
+
* computation. Spaces delegate to each other -- a passthrough wraps another
|
|
20
|
+
* space, a query output space resolves names through its input space -- so a
|
|
21
|
+
* per-space generation has to forward through every one of those paths, and a
|
|
22
|
+
* path we missed is a stale cache we never notice. We think this simple
|
|
23
|
+
* approach will be good enough for a long time, possibly forever.
|
|
24
|
+
*
|
|
25
|
+
* This does not cover a computation which reads something other than a field
|
|
26
|
+
* space. `GivenReference` ignores the FieldSpace it is handed and reads the
|
|
27
|
+
* Document namespace, which never moves this counter. Nothing is known to
|
|
28
|
+
* depend on that, since document definitions happen between statements, but
|
|
29
|
+
* do not assume a cache is covered here just because it consults a field
|
|
30
|
+
* space as well.
|
|
31
|
+
*/
|
|
32
|
+
let bindingGeneration = 0;
|
|
33
|
+
function currentGeneration() {
|
|
34
|
+
return bindingGeneration;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Called by every field space operation that binds, rebinds or unbinds a
|
|
38
|
+
* name. Lazily materializing a space's entries from its StructDef is not
|
|
39
|
+
* such an operation -- the bindings were already logically present, so
|
|
40
|
+
* nothing a cache observed can have changed.
|
|
41
|
+
*/
|
|
42
|
+
function noteRebinding() {
|
|
43
|
+
bindingGeneration += 1;
|
|
44
|
+
}
|
|
9
45
|
class FieldName extends malloy_element_1.MalloyElement {
|
|
10
46
|
constructor(name) {
|
|
11
47
|
super();
|
|
@@ -2,7 +2,21 @@ import type { MalloyElement } from './malloy-element';
|
|
|
2
2
|
import type { Query } from '../../../model/malloy_types';
|
|
3
3
|
import type { QueryComp } from './query-comp';
|
|
4
4
|
export interface QueryElement extends MalloyElement {
|
|
5
|
-
|
|
5
|
+
/**
|
|
6
|
+
* Both arguments say what looseness the caller will accept.
|
|
7
|
+
*
|
|
8
|
+
* @param isRefOk `structRef` may be left as the name of a source. False
|
|
9
|
+
* means expand it, which is what a caller writing this query into
|
|
10
|
+
* something that must stand on its own needs.
|
|
11
|
+
* @param isPartialOk The pipeline may contain `partial` segments, whose
|
|
12
|
+
* query class is not decided yet. Only the base of a refinement can
|
|
13
|
+
* accept those, because the refinement is what decides the class.
|
|
14
|
+
* False means the pipeline is finished and the compiler can read it —
|
|
15
|
+
* any partial found is made a `reduce` and reported as
|
|
16
|
+
* `ambiguous-view-type`, so passing false decides where that error
|
|
17
|
+
* lands and when it is logged relative to the rest of the pipeline.
|
|
18
|
+
*/
|
|
19
|
+
queryComp(isRefOk: boolean, isPartialOk: boolean): QueryComp;
|
|
6
20
|
query(isRefOk?: boolean): Query;
|
|
7
21
|
}
|
|
8
22
|
export declare function isQueryElement(e: MalloyElement): e is QueryElement;
|
package/dist/lang/parse-log.d.ts
CHANGED
|
@@ -248,11 +248,13 @@ type MessageParameterTypes = {
|
|
|
248
248
|
'unexpected-source-property': string;
|
|
249
249
|
'aggregate-in-source-filter': string;
|
|
250
250
|
'invalid-connection-for-sql-source': string;
|
|
251
|
+
'sql-source-connection-mismatch': string;
|
|
251
252
|
'failed-to-fetch-sql-source-schema': string;
|
|
252
253
|
'invalid-sql-source': string;
|
|
253
254
|
'non-top-level-sql-source': string;
|
|
254
255
|
'failed-to-fetch-table-schema': string;
|
|
255
256
|
'invalid-connection-for-table-source': string;
|
|
257
|
+
'join-connection-mismatch': string;
|
|
256
258
|
'join-on-primary-key-type-mismatch': string;
|
|
257
259
|
'join-primary-key-not-found': string;
|
|
258
260
|
'join-with-without-primary-key': string;
|
|
@@ -19,6 +19,13 @@ export declare function pretty(thing: unknown): string;
|
|
|
19
19
|
export declare function humanify(value: unknown): string;
|
|
20
20
|
export declare const TEST_DIALECT = "duckdb";
|
|
21
21
|
export declare const aTableDef: TableSourceDef;
|
|
22
|
+
/**
|
|
23
|
+
* A second connection speaking the same dialect as `_db_`. Two sources can
|
|
24
|
+
* then compile to identical SQL on different connections, which isolates the
|
|
25
|
+
* connection check from the dialect check, and is exactly the case where
|
|
26
|
+
* persistence could forget that a build target is a table on a connection.
|
|
27
|
+
*/
|
|
28
|
+
export declare const db2TableDef: TableSourceDef;
|
|
22
29
|
export declare const bqTableDef: SourceDef;
|
|
23
30
|
export declare const pgTableDef: SourceDef;
|
|
24
31
|
/**
|
|
@@ -128,4 +135,14 @@ export declare function warningMessage(message: string | RegExp): {
|
|
|
128
135
|
message: string | RegExp;
|
|
129
136
|
severity: LogSeverity;
|
|
130
137
|
};
|
|
138
|
+
/**
|
|
139
|
+
* Drain a translation's SQL schema requests, answering each with the schema of
|
|
140
|
+
* `aTable` on the connection which asked, until it stops asking. A `sql()`
|
|
141
|
+
* source needs a schema before the translation can finish, and nothing in a
|
|
142
|
+
* TestTranslator provides one.
|
|
143
|
+
*
|
|
144
|
+
* @param selectStr What to record as the block's SELECT. Defaults to the
|
|
145
|
+
* statement which was requested; pass a value to hand back something else.
|
|
146
|
+
*/
|
|
147
|
+
export declare function answerSQLSchemaRequests(translator: TestTranslator, selectStr?: string): void;
|
|
131
148
|
export {};
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* SPDX-License-Identifier: MIT
|
|
6
6
|
*/
|
|
7
7
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
8
|
-
exports.BetaExpression = exports.TestTranslator = exports.TestChildTranslator = exports.mockSchema = exports.pgTableDef = exports.bqTableDef = exports.aTableDef = exports.TEST_DIALECT = void 0;
|
|
8
|
+
exports.BetaExpression = exports.TestTranslator = exports.TestChildTranslator = exports.mockSchema = exports.pgTableDef = exports.bqTableDef = exports.db2TableDef = exports.aTableDef = exports.TEST_DIALECT = void 0;
|
|
9
9
|
exports.pretty = pretty;
|
|
10
10
|
exports.humanify = humanify;
|
|
11
11
|
exports.getExplore = getExplore;
|
|
@@ -24,6 +24,7 @@ exports.error = error;
|
|
|
24
24
|
exports.warning = warning;
|
|
25
25
|
exports.errorMessage = errorMessage;
|
|
26
26
|
exports.warningMessage = warningMessage;
|
|
27
|
+
exports.answerSQLSchemaRequests = answerSQLSchemaRequests;
|
|
27
28
|
const util_1 = require("util");
|
|
28
29
|
const malloy_types_1 = require("../../model/malloy_types");
|
|
29
30
|
const ast_1 = require("../ast");
|
|
@@ -162,6 +163,16 @@ exports.aTableDef = {
|
|
|
162
163
|
{ type: 'timestamptz', name: 'atstz' }, // duckdb supports timestamptz
|
|
163
164
|
],
|
|
164
165
|
};
|
|
166
|
+
/**
|
|
167
|
+
* A second connection speaking the same dialect as `_db_`. Two sources can
|
|
168
|
+
* then compile to identical SQL on different connections, which isolates the
|
|
169
|
+
* connection check from the dialect check, and is exactly the case where
|
|
170
|
+
* persistence could forget that a build target is a table on a connection.
|
|
171
|
+
*/
|
|
172
|
+
exports.db2TableDef = {
|
|
173
|
+
...exports.aTableDef,
|
|
174
|
+
connection: '_db2_',
|
|
175
|
+
};
|
|
165
176
|
// BigQuery-compatible table definition (no timestamptz support)
|
|
166
177
|
exports.bqTableDef = {
|
|
167
178
|
type: 'table',
|
|
@@ -188,6 +199,7 @@ exports.pgTableDef = {
|
|
|
188
199
|
*/
|
|
189
200
|
exports.mockSchema = [
|
|
190
201
|
exports.aTableDef,
|
|
202
|
+
exports.db2TableDef,
|
|
191
203
|
exports.bqTableDef,
|
|
192
204
|
exports.pgTableDef,
|
|
193
205
|
{
|
|
@@ -331,11 +343,15 @@ class TestTranslator extends parse_malloy_1.MalloyTranslator {
|
|
|
331
343
|
this.testSrc = testSrc;
|
|
332
344
|
this.allDialectsEnabled = true;
|
|
333
345
|
/*
|
|
334
|
-
* There are
|
|
346
|
+
* There are four connections:
|
|
335
347
|
* _db_ - duckdb dialect, with the following tables ...
|
|
336
348
|
* aTable, malloytest.carriers, malloytest.flights, malloytest.airports
|
|
349
|
+
* _db2_ - duckdb dialect, with one table, aTable. A second connection
|
|
350
|
+
* on the same dialect as _db_, for cross-connection tests.
|
|
337
351
|
* _bq_ - bigquery/standardsql dialect, with one table
|
|
338
352
|
* aTable
|
|
353
|
+
* _pg_ - postgres dialect, with one table
|
|
354
|
+
* aTable
|
|
339
355
|
*
|
|
340
356
|
* The "aTable" table is a mocked table with one column of each type.
|
|
341
357
|
* The _bq_ version does not have the timestamptz column, and when
|
|
@@ -361,6 +377,7 @@ class TestTranslator extends parse_malloy_1.MalloyTranslator {
|
|
|
361
377
|
...(0, utils_1.mkModelDef)(testURI, testURI),
|
|
362
378
|
contents: {
|
|
363
379
|
_db_: { type: 'connection', name: '_db_' },
|
|
380
|
+
_db2_: { type: 'connection', name: '_db2_' },
|
|
364
381
|
_bq_: { type: 'connection', name: '_bq_' },
|
|
365
382
|
_pg_: { type: 'connection', name: '_pg_' },
|
|
366
383
|
a: { ...exports.aTableDef, primaryKey: 'astr', name: 'a' },
|
|
@@ -417,6 +434,7 @@ class TestTranslator extends parse_malloy_1.MalloyTranslator {
|
|
|
417
434
|
this.schemaZone.define(`${actualSchema.connection}:${actualSchema.tablePath}`, actualSchema);
|
|
418
435
|
}
|
|
419
436
|
this.connectionDialectZone.define('_db_', exports.TEST_DIALECT);
|
|
437
|
+
this.connectionDialectZone.define('_db2_', exports.TEST_DIALECT);
|
|
420
438
|
this.connectionDialectZone.define('_bq_', 'standardsql');
|
|
421
439
|
this.connectionDialectZone.define('_pg_', 'postgres');
|
|
422
440
|
for (const flag of (_e = options.compilerFlags) !== null && _e !== void 0 ? _e : []) {
|
|
@@ -694,4 +712,30 @@ function errorMessage(message) {
|
|
|
694
712
|
function warningMessage(message) {
|
|
695
713
|
return { message, severity: 'warn' };
|
|
696
714
|
}
|
|
715
|
+
/**
|
|
716
|
+
* Drain a translation's SQL schema requests, answering each with the schema of
|
|
717
|
+
* `aTable` on the connection which asked, until it stops asking. A `sql()`
|
|
718
|
+
* source needs a schema before the translation can finish, and nothing in a
|
|
719
|
+
* TestTranslator provides one.
|
|
720
|
+
*
|
|
721
|
+
* @param selectStr What to record as the block's SELECT. Defaults to the
|
|
722
|
+
* statement which was requested; pass a value to hand back something else.
|
|
723
|
+
*/
|
|
724
|
+
function answerSQLSchemaRequests(translator, selectStr) {
|
|
725
|
+
for (;;) {
|
|
726
|
+
const compileSQL = translator.translate().compileSQL;
|
|
727
|
+
if (compileSQL === undefined)
|
|
728
|
+
return;
|
|
729
|
+
const key = (0, sql_block_1.sqlKey)(compileSQL.connection, compileSQL.selectStr);
|
|
730
|
+
const schema = {
|
|
731
|
+
type: 'sql_select',
|
|
732
|
+
name: key,
|
|
733
|
+
dialect: exports.TEST_DIALECT,
|
|
734
|
+
connection: compileSQL.connection,
|
|
735
|
+
selectStr: selectStr !== null && selectStr !== void 0 ? selectStr : compileSQL.selectStr,
|
|
736
|
+
fields: exports.aTableDef.fields,
|
|
737
|
+
};
|
|
738
|
+
translator.update({ compileSQL: { [key]: schema } });
|
|
739
|
+
}
|
|
740
|
+
}
|
|
697
741
|
//# sourceMappingURL=test-translator.js.map
|
|
@@ -801,19 +801,6 @@ export declare function mkRefSummary({ fieldUsage, givenUsage, }: {
|
|
|
801
801
|
* untouched without each call site having to know about them.
|
|
802
802
|
*/
|
|
803
803
|
export declare function mapFieldUsage(rs: RefSummary | undefined, fn: (u: FieldUsageEntry) => FieldUsageEntry): RefSummary | undefined;
|
|
804
|
-
/**
|
|
805
|
-
* Mutating setter for a node's `fieldUsage`. When the node already has a
|
|
806
|
-
* `refSummary`, replaces just the `fieldUsage` slice (preserving `givenUsage`
|
|
807
|
-
* and any future RefSummary fields). When it doesn't, creates a fresh
|
|
808
|
-
* `refSummary` with the supplied usages.
|
|
809
|
-
*
|
|
810
|
-
* Use at sites that mutate an already-constructed IR node; for sites that
|
|
811
|
-
* build a node from a literal, write `{fieldUsage: [...]}` directly or use
|
|
812
|
-
* `mkRefSummary` for possibly-undefined inputs.
|
|
813
|
-
*/
|
|
814
|
-
export declare function setFieldUsage(target: {
|
|
815
|
-
refSummary?: RefSummary;
|
|
816
|
-
}, usages: FieldUsage): void;
|
|
817
804
|
export interface QuerySegment extends Filtered, Ordered, SegmentUsageSummary {
|
|
818
805
|
type: 'reduce' | 'project' | 'partial';
|
|
819
806
|
queryFields: QueryFieldDef[];
|
|
@@ -64,7 +64,6 @@ exports.fieldUsageFrom = fieldUsageFrom;
|
|
|
64
64
|
exports.givenUsageFrom = givenUsageFrom;
|
|
65
65
|
exports.mkRefSummary = mkRefSummary;
|
|
66
66
|
exports.mapFieldUsage = mapFieldUsage;
|
|
67
|
-
exports.setFieldUsage = setFieldUsage;
|
|
68
67
|
exports.isSegmentSQL = isSegmentSQL;
|
|
69
68
|
exports.isSegmentSource = isSegmentSource;
|
|
70
69
|
exports.isSourceRegistryReference = isSourceRegistryReference;
|
|
@@ -491,24 +490,6 @@ function mkRefSummary({ fieldUsage, givenUsage, }) {
|
|
|
491
490
|
function mapFieldUsage(rs, fn) {
|
|
492
491
|
return rs && { ...rs, fieldUsage: rs.fieldUsage.map(fn) };
|
|
493
492
|
}
|
|
494
|
-
/**
|
|
495
|
-
* Mutating setter for a node's `fieldUsage`. When the node already has a
|
|
496
|
-
* `refSummary`, replaces just the `fieldUsage` slice (preserving `givenUsage`
|
|
497
|
-
* and any future RefSummary fields). When it doesn't, creates a fresh
|
|
498
|
-
* `refSummary` with the supplied usages.
|
|
499
|
-
*
|
|
500
|
-
* Use at sites that mutate an already-constructed IR node; for sites that
|
|
501
|
-
* build a node from a literal, write `{fieldUsage: [...]}` directly or use
|
|
502
|
-
* `mkRefSummary` for possibly-undefined inputs.
|
|
503
|
-
*/
|
|
504
|
-
function setFieldUsage(target, usages) {
|
|
505
|
-
if (target.refSummary) {
|
|
506
|
-
target.refSummary.fieldUsage = usages;
|
|
507
|
-
}
|
|
508
|
-
else {
|
|
509
|
-
target.refSummary = { fieldUsage: usages };
|
|
510
|
-
}
|
|
511
|
-
}
|
|
512
493
|
function isSegmentSQL(f) {
|
|
513
494
|
return 'sql' in f;
|
|
514
495
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { ModelDef, SourceDef, Query } from './malloy_types';
|
|
1
|
+
import type { ModelDef, SourceDef, Query, SourceID } from './malloy_types';
|
|
2
2
|
import type { LogMessage } from '../lang';
|
|
3
3
|
import type { BuildNode } from '../api/foundation/types';
|
|
4
4
|
/**
|
|
@@ -10,15 +10,50 @@ export declare function checkPersistAnnotation(source: SourceDef): {
|
|
|
10
10
|
log: LogMessage[];
|
|
11
11
|
};
|
|
12
12
|
/**
|
|
13
|
-
*
|
|
13
|
+
* One persistable source the walk found, and the sources it directly
|
|
14
|
+
* references.
|
|
14
15
|
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
16
|
+
* Edges are sourceIDs rather than nested nodes, so there is no shared object
|
|
17
|
+
* to be careful about and nothing to recurse through — a consumer folds the
|
|
18
|
+
* stream in one pass.
|
|
19
|
+
*/
|
|
20
|
+
export interface PersistNode {
|
|
21
|
+
sourceID: SourceID;
|
|
22
|
+
/**
|
|
23
|
+
* Whether this source is itself something to build. `false` means it is a
|
|
24
|
+
* *route*: it materializes nothing, but it is how the walk reached something
|
|
25
|
+
* that does.
|
|
26
|
+
*/
|
|
27
|
+
persistent: boolean;
|
|
28
|
+
/** The sources it reaches directly, each of which was yielded already */
|
|
29
|
+
dependsOn: SourceID[];
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* A walk in progress: yields a {@link PersistNode} per source kept, and
|
|
33
|
+
* finally returns the sources the roots themselves reached.
|
|
34
|
+
*/
|
|
35
|
+
export type PersistWalk = Generator<PersistNode, SourceID[]>;
|
|
36
|
+
/**
|
|
37
|
+
* Walk everything a source or query references, and emit the sources that
|
|
38
|
+
* matter to persistence — in dependency order, each one after everything it
|
|
39
|
+
* depends on.
|
|
40
|
+
*
|
|
41
|
+
* A source is emitted if it is persistent — something to build — or if it
|
|
42
|
+
* is the *route* to something persistent. Everything else is dropped; a
|
|
43
|
+
* source with nothing persistent beneath it is of no interest to anybody.
|
|
44
|
+
*
|
|
45
|
+
* One rule decides both halves, applied on the way back up:
|
|
46
|
+
*
|
|
47
|
+
* keep me if I am persistent, or if any child survived
|
|
18
48
|
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
* (
|
|
49
|
+
* The second clause needs no lookahead. A child only survived under this same
|
|
50
|
+
* rule, so a surviving child *is* the proof that something persistent lies
|
|
51
|
+
* below. For `a(persist) → b → c(persist) → d`, d has nothing beneath it
|
|
52
|
+
* and is dropped, c is kept for being persistent, b is kept for leading to c.
|
|
53
|
+
*
|
|
54
|
+
* Because a node arrives only after everything it depends on, a consumer can
|
|
55
|
+
* fold the stream in a single pass with no second lookup — whether it wants
|
|
56
|
+
* every source touched, or only the ones that are tables.
|
|
22
57
|
*
|
|
23
58
|
* ## The 6 Dependency Paths in the IR
|
|
24
59
|
*
|
|
@@ -35,9 +70,25 @@ export declare function checkPersistAnnotation(source: SourceDef): {
|
|
|
35
70
|
* Note: CompositeSourceDef.sources[] is ignored - composite sources and
|
|
36
71
|
* persistence may be incompatible features.
|
|
37
72
|
*
|
|
38
|
-
*
|
|
73
|
+
* Joins are followed whether or not a query activates them, so what comes back
|
|
74
|
+
* is what a source *could* depend on. An unused join is a dependency the SQL
|
|
75
|
+
* will not contain — an over-approximation, which costs ordering freedom and
|
|
76
|
+
* never correctness.
|
|
77
|
+
*
|
|
78
|
+
* @param roots The sources and queries to walk, sharing one memo
|
|
39
79
|
* @param modelDef The model definition containing the source registry
|
|
40
|
-
* @
|
|
80
|
+
* @param tagParseLog Collects errors from parsing `#@` annotations
|
|
81
|
+
*/
|
|
82
|
+
export declare function walkPersistentDependencies(roots: (SourceDef | Query)[], modelDef: ModelDef, tagParseLog?: LogMessage[]): PersistWalk;
|
|
83
|
+
/**
|
|
84
|
+
* The persistent sources a source or query depends on, as a nested DAG.
|
|
85
|
+
*
|
|
86
|
+
* A source that is not itself persistent never appears; its persistent
|
|
87
|
+
* dependencies become direct dependencies of whoever referenced it. So
|
|
88
|
+
* `c (persist) → b → a (persist)` yields `[{sourceID: a, dependsOn: []}]`.
|
|
89
|
+
*
|
|
90
|
+
* @deprecated The nested shape exists for `Model.getBuildPlan()`, which is on
|
|
91
|
+
* its way out. Consume {@link walkPersistentDependencies} directly.
|
|
41
92
|
*/
|
|
42
93
|
export declare function findPersistentDependencies(root: SourceDef | Query, modelDef: ModelDef, tagParseLog?: LogMessage[]): BuildNode[];
|
|
43
94
|
/**
|