@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.
Files changed (129) hide show
  1. package/dist/api/foundation/build_targets.d.ts +50 -0
  2. package/dist/api/foundation/build_targets.js +221 -0
  3. package/dist/api/foundation/core.d.ts +54 -10
  4. package/dist/api/foundation/core.js +80 -17
  5. package/dist/api/foundation/index.d.ts +1 -1
  6. package/dist/api/foundation/runtime.d.ts +18 -1
  7. package/dist/api/foundation/runtime.js +35 -0
  8. package/dist/api/foundation/types.d.ts +93 -6
  9. package/dist/index.d.ts +1 -1
  10. package/dist/lang/ast/expressions/apply.d.ts +1 -1
  11. package/dist/lang/ast/expressions/apply.js +2 -2
  12. package/dist/lang/ast/expressions/binary-boolean.d.ts +1 -1
  13. package/dist/lang/ast/expressions/binary-boolean.js +1 -1
  14. package/dist/lang/ast/expressions/binary-numeric.d.ts +1 -1
  15. package/dist/lang/ast/expressions/binary-numeric.js +1 -1
  16. package/dist/lang/ast/expressions/boolean.d.ts +1 -1
  17. package/dist/lang/ast/expressions/boolean.js +1 -1
  18. package/dist/lang/ast/expressions/case.d.ts +1 -1
  19. package/dist/lang/ast/expressions/case.js +1 -1
  20. package/dist/lang/ast/expressions/constant-expression.d.ts +2 -1
  21. package/dist/lang/ast/expressions/constant-expression.js +6 -1
  22. package/dist/lang/ast/expressions/expr-aggregate-function.d.ts +1 -1
  23. package/dist/lang/ast/expressions/expr-aggregate-function.js +14 -7
  24. package/dist/lang/ast/expressions/expr-alternation-tree.d.ts +1 -1
  25. package/dist/lang/ast/expressions/expr-alternation-tree.js +1 -1
  26. package/dist/lang/ast/expressions/expr-array-literal.d.ts +1 -1
  27. package/dist/lang/ast/expressions/expr-array-literal.js +1 -1
  28. package/dist/lang/ast/expressions/expr-cast.d.ts +1 -1
  29. package/dist/lang/ast/expressions/expr-cast.js +1 -1
  30. package/dist/lang/ast/expressions/expr-coalesce.d.ts +1 -1
  31. package/dist/lang/ast/expressions/expr-coalesce.js +1 -1
  32. package/dist/lang/ast/expressions/expr-compare.d.ts +4 -4
  33. package/dist/lang/ast/expressions/expr-compare.js +4 -4
  34. package/dist/lang/ast/expressions/expr-count.d.ts +1 -1
  35. package/dist/lang/ast/expressions/expr-count.js +1 -1
  36. package/dist/lang/ast/expressions/expr-filter-expr.d.ts +1 -1
  37. package/dist/lang/ast/expressions/expr-filter-expr.js +1 -1
  38. package/dist/lang/ast/expressions/expr-func.d.ts +1 -1
  39. package/dist/lang/ast/expressions/expr-func.js +1 -1
  40. package/dist/lang/ast/expressions/expr-given.d.ts +1 -1
  41. package/dist/lang/ast/expressions/expr-given.js +1 -1
  42. package/dist/lang/ast/expressions/expr-granular-time.d.ts +1 -1
  43. package/dist/lang/ast/expressions/expr-granular-time.js +1 -1
  44. package/dist/lang/ast/expressions/expr-id-reference.d.ts +1 -1
  45. package/dist/lang/ast/expressions/expr-id-reference.js +1 -1
  46. package/dist/lang/ast/expressions/expr-minus.d.ts +1 -1
  47. package/dist/lang/ast/expressions/expr-minus.js +1 -1
  48. package/dist/lang/ast/expressions/expr-not.d.ts +1 -1
  49. package/dist/lang/ast/expressions/expr-not.js +1 -1
  50. package/dist/lang/ast/expressions/expr-now.d.ts +1 -1
  51. package/dist/lang/ast/expressions/expr-now.js +1 -1
  52. package/dist/lang/ast/expressions/expr-null.d.ts +3 -3
  53. package/dist/lang/ast/expressions/expr-null.js +14 -9
  54. package/dist/lang/ast/expressions/expr-number.d.ts +1 -1
  55. package/dist/lang/ast/expressions/expr-number.js +1 -1
  56. package/dist/lang/ast/expressions/expr-parens.d.ts +1 -1
  57. package/dist/lang/ast/expressions/expr-parens.js +5 -2
  58. package/dist/lang/ast/expressions/expr-props.d.ts +1 -1
  59. package/dist/lang/ast/expressions/expr-props.js +1 -1
  60. package/dist/lang/ast/expressions/expr-record-literal.d.ts +1 -1
  61. package/dist/lang/ast/expressions/expr-record-literal.js +1 -1
  62. package/dist/lang/ast/expressions/expr-regex.d.ts +1 -1
  63. package/dist/lang/ast/expressions/expr-regex.js +1 -1
  64. package/dist/lang/ast/expressions/expr-string.d.ts +1 -1
  65. package/dist/lang/ast/expressions/expr-string.js +1 -1
  66. package/dist/lang/ast/expressions/expr-time-extract.d.ts +1 -1
  67. package/dist/lang/ast/expressions/expr-time-extract.js +1 -1
  68. package/dist/lang/ast/expressions/expr-time.d.ts +1 -1
  69. package/dist/lang/ast/expressions/expr-time.js +1 -1
  70. package/dist/lang/ast/expressions/expr-ungroup.d.ts +1 -1
  71. package/dist/lang/ast/expressions/expr-ungroup.js +1 -1
  72. package/dist/lang/ast/expressions/for-range.d.ts +1 -1
  73. package/dist/lang/ast/expressions/for-range.js +1 -1
  74. package/dist/lang/ast/expressions/partial-compare.d.ts +1 -1
  75. package/dist/lang/ast/expressions/partial-compare.js +1 -1
  76. package/dist/lang/ast/expressions/pick-when.d.ts +1 -1
  77. package/dist/lang/ast/expressions/pick-when.js +1 -1
  78. package/dist/lang/ast/expressions/range.d.ts +1 -1
  79. package/dist/lang/ast/expressions/range.js +1 -1
  80. package/dist/lang/ast/expressions/time-literal.d.ts +2 -2
  81. package/dist/lang/ast/expressions/time-literal.js +2 -2
  82. package/dist/lang/ast/field-space/dynamic-space.js +20 -3
  83. package/dist/lang/ast/field-space/parameter-space.d.ts +1 -0
  84. package/dist/lang/ast/field-space/parameter-space.js +6 -0
  85. package/dist/lang/ast/field-space/passthrough-space.d.ts +1 -0
  86. package/dist/lang/ast/field-space/passthrough-space.js +3 -0
  87. package/dist/lang/ast/field-space/static-space.d.ts +8 -0
  88. package/dist/lang/ast/field-space/static-space.js +14 -0
  89. package/dist/lang/ast/query-builders/index-builder.js +1 -1
  90. package/dist/lang/ast/query-elements/query-arrow.d.ts +1 -1
  91. package/dist/lang/ast/query-elements/query-arrow.js +10 -4
  92. package/dist/lang/ast/query-elements/query-base.d.ts +1 -1
  93. package/dist/lang/ast/query-elements/query-base.js +1 -6
  94. package/dist/lang/ast/query-elements/query-raw.d.ts +1 -1
  95. package/dist/lang/ast/query-elements/query-raw.js +4 -2
  96. package/dist/lang/ast/query-elements/query-reference.d.ts +1 -1
  97. package/dist/lang/ast/query-elements/query-reference.js +9 -3
  98. package/dist/lang/ast/query-elements/query-refine.d.ts +1 -1
  99. package/dist/lang/ast/query-elements/query-refine.js +10 -4
  100. package/dist/lang/ast/query-properties/drill.d.ts +1 -1
  101. package/dist/lang/ast/query-properties/drill.js +1 -1
  102. package/dist/lang/ast/source-elements/query-source.js +1 -1
  103. package/dist/lang/ast/source-elements/sql-source.js +2 -2
  104. package/dist/lang/ast/source-query-elements/source-query-element.d.ts +27 -0
  105. package/dist/lang/ast/source-query-elements/source-query-element.js +33 -0
  106. package/dist/lang/ast/source-query-elements/sq-arrow.d.ts +2 -0
  107. package/dist/lang/ast/source-query-elements/sq-arrow.js +12 -6
  108. package/dist/lang/ast/source-query-elements/sq-refine.d.ts +2 -0
  109. package/dist/lang/ast/source-query-elements/sq-refine.js +12 -6
  110. package/dist/lang/ast/source-query-elements/sq-source.d.ts +1 -0
  111. package/dist/lang/ast/source-query-elements/sq-source.js +6 -3
  112. package/dist/lang/ast/sql-elements/sql-string.d.ts +1 -1
  113. package/dist/lang/ast/sql-elements/sql-string.js +25 -5
  114. package/dist/lang/ast/statements/import-statement.js +7 -10
  115. package/dist/lang/ast/types/expression-def.d.ts +50 -23
  116. package/dist/lang/ast/types/expression-def.js +127 -65
  117. package/dist/lang/ast/types/field-space.d.ts +14 -0
  118. package/dist/lang/ast/types/field-space.js +36 -0
  119. package/dist/lang/ast/types/query-element.d.ts +15 -1
  120. package/dist/lang/parse-log.d.ts +2 -0
  121. package/dist/lang/test/test-translator.d.ts +17 -0
  122. package/dist/lang/test/test-translator.js +46 -2
  123. package/dist/model/malloy_types.d.ts +0 -13
  124. package/dist/model/malloy_types.js +0 -19
  125. package/dist/model/persist_utils.d.ts +61 -10
  126. package/dist/model/persist_utils.js +172 -63
  127. package/dist/version.d.ts +1 -1
  128. package/dist/version.js +1 -1
  129. 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. Expression nodes know
66
- * how to write themselves as SQL (or rather, generate the
67
- * template for SQL required by the query writer)
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
- * The default implementation merely constructs LEFT OP RIGHT, but specialized
115
- * nodes like alternation trees or or partial comparison can control how
116
- * the appplication gets generated
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 expr The "other" (besdies 'this') value
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
- return applyBinary(fs, left, op, this);
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
- getExpression(fs) {
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
- queryComp(isRefOk: boolean): QueryComp;
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;
@@ -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 two connections:
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
- * Find persistent dependencies for a source or query, returning a nested DAG.
13
+ * One persistable source the walk found, and the sources it directly
14
+ * references.
14
15
  *
15
- * Walks the full dependency tree but only includes persistent sources in the
16
- * result. Non-persistent sources are "flattened out" - their persistent
17
- * dependencies bubble up to become direct dependencies of the caller.
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
- * Example: source_c (persist) -> source_b (NOT persist) -> source_a (persist)
20
- * Returns: [{sourceID: source_a, dependsOn: []}]
21
- * (source_b is flattened out, source_a becomes direct dependency)
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
- * @param root The source or query to find dependencies for
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
- * @returns Array of BuildNode representing the persistent dependency DAG
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
  /**