@malloydata/malloy 0.0.393 → 0.0.395

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 (71) hide show
  1. package/dist/api/foundation/config.d.ts +2 -3
  2. package/dist/api/foundation/config.js +23 -11
  3. package/dist/api/foundation/core.d.ts +0 -4
  4. package/dist/api/foundation/core.js +14 -11
  5. package/dist/api/foundation/runtime.js +21 -1
  6. package/dist/api/util.js +4 -0
  7. package/dist/connection/base_connection.js +6 -0
  8. package/dist/connection/validate_table_path.d.ts +10 -0
  9. package/dist/connection/validate_table_path.js +56 -0
  10. package/dist/dialect/databricks/databricks.d.ts +4 -4
  11. package/dist/dialect/databricks/databricks.js +17 -22
  12. package/dist/dialect/dialect.d.ts +100 -4
  13. package/dist/dialect/dialect.js +145 -1
  14. package/dist/dialect/duckdb/duckdb.d.ts +2 -3
  15. package/dist/dialect/duckdb/duckdb.js +12 -14
  16. package/dist/dialect/duckdb/table-path-parser.d.ts +2 -0
  17. package/dist/dialect/duckdb/table-path-parser.js +57 -0
  18. package/dist/dialect/index.d.ts +2 -0
  19. package/dist/dialect/index.js +4 -1
  20. package/dist/dialect/mysql/mysql.d.ts +4 -4
  21. package/dist/dialect/mysql/mysql.js +25 -20
  22. package/dist/dialect/pg_impl.d.ts +3 -1
  23. package/dist/dialect/pg_impl.js +6 -3
  24. package/dist/dialect/postgres/postgres.d.ts +1 -3
  25. package/dist/dialect/postgres/postgres.js +8 -16
  26. package/dist/dialect/snowflake/snowflake.d.ts +4 -4
  27. package/dist/dialect/snowflake/snowflake.js +11 -27
  28. package/dist/dialect/standardsql/standardsql.d.ts +6 -4
  29. package/dist/dialect/standardsql/standardsql.js +36 -15
  30. package/dist/dialect/table-path.d.ts +54 -0
  31. package/dist/dialect/table-path.js +144 -0
  32. package/dist/dialect/trino/trino.d.ts +0 -3
  33. package/dist/dialect/trino/trino.js +7 -20
  34. package/dist/index.d.ts +2 -2
  35. package/dist/index.js +4 -2
  36. package/dist/lang/ast/expressions/expr-compare.d.ts +15 -0
  37. package/dist/lang/ast/expressions/expr-compare.js +82 -2
  38. package/dist/lang/ast/source-elements/table-source.d.ts +1 -7
  39. package/dist/lang/ast/source-elements/table-source.js +20 -19
  40. package/dist/lang/ast/statements/define-given.d.ts +2 -1
  41. package/dist/lang/ast/statements/define-given.js +52 -1
  42. package/dist/lang/ast/types/malloy-element.js +2 -0
  43. package/dist/lang/lib/Malloy/MalloyParser.d.ts +188 -167
  44. package/dist/lang/lib/Malloy/MalloyParser.js +2582 -2442
  45. package/dist/lang/lib/Malloy/MalloyParserListener.d.ts +24 -0
  46. package/dist/lang/lib/Malloy/MalloyParserVisitor.d.ts +15 -0
  47. package/dist/lang/malloy-to-ast.d.ts +9 -2
  48. package/dist/lang/malloy-to-ast.js +37 -2
  49. package/dist/lang/parse-log.d.ts +23 -0
  50. package/dist/lang/parse-log.js +6 -0
  51. package/dist/lang/parse-malloy.js +37 -7
  52. package/dist/lang/parse-tree-walkers/find-external-references.d.ts +2 -15
  53. package/dist/lang/parse-tree-walkers/find-external-references.js +6 -23
  54. package/dist/lang/test/expr-to-str.js +3 -0
  55. package/dist/lang/translate-response.d.ts +1 -1
  56. package/dist/model/expression_compiler.js +38 -11
  57. package/dist/model/filter_compilers.js +1 -1
  58. package/dist/model/given_binding.d.ts +15 -0
  59. package/dist/model/given_binding.js +35 -0
  60. package/dist/model/inline_expr.d.ts +30 -0
  61. package/dist/model/inline_expr.js +184 -0
  62. package/dist/model/malloy_types.d.ts +19 -1
  63. package/dist/model/query_model_impl.js +7 -7
  64. package/dist/model/query_query.d.ts +1 -1
  65. package/dist/model/query_query.js +37 -33
  66. package/dist/model/sql_compiled.d.ts +2 -4
  67. package/dist/model/sql_compiled.js +14 -15
  68. package/dist/test/test-models.js +2 -2
  69. package/dist/version.d.ts +1 -1
  70. package/dist/version.js +1 -1
  71. 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
- protected astAt<MT extends ast.MalloyElement>(el: MT, cx: ParserRuleContext): MT;
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: this.rangeFromContext(cx),
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
- const decl = new ast.GivenDeclaration(name, typeDef, defVal);
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();
@@ -275,6 +275,7 @@ type MessageParameterTypes = {
275
275
  'parameter-name-conflict': string;
276
276
  'parameter-shadowing-field': string;
277
277
  'invalid-import-url': string;
278
+ 'invalid-table-path': string;
278
279
  'no-translator-for-import': string;
279
280
  'name-conflict-on-selective-import': string;
280
281
  'selective-import-not-found': string;
@@ -292,6 +293,28 @@ type MessageParameterTypes = {
292
293
  'given-reference-not-implemented': string;
293
294
  'given-not-found': string;
294
295
  'given-no-tags-after-is': string;
296
+ 'in-given-rhs-not-array': {
297
+ givenName: string;
298
+ actualType: string;
299
+ };
300
+ 'in-given-rhs-not-basic-array': {
301
+ givenName: string;
302
+ elementType: string;
303
+ };
304
+ 'in-given-type-mismatch': {
305
+ lhsType: string;
306
+ elementType: string;
307
+ };
308
+ 'inline-no-default': {
309
+ name: string;
310
+ };
311
+ 'inline-bad-operator': {
312
+ name: string;
313
+ operators: string;
314
+ };
315
+ 'invalid-given-modifier': {
316
+ modifier: string;
317
+ };
295
318
  'illegal-filter-type': string;
296
319
  'invalid-source-from-given': string;
297
320
  'aggregate-analytic-in-select': string;
@@ -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;
@@ -61,6 +61,7 @@ const ast = __importStar(require("./ast"));
61
61
  const malloy_to_ast_1 = require("./malloy-to-ast");
62
62
  const parse_log_1 = require("./parse-log");
63
63
  const find_external_references_1 = require("./parse-tree-walkers/find-external-references");
64
+ const dialect_1 = require("../dialect");
64
65
  const zone_1 = require("./zone");
65
66
  const document_symbol_walker_1 = require("./parse-tree-walkers/document-symbol-walker");
66
67
  const document_completion_walker_1 = require("./parse-tree-walkers/document-completion-walker");
@@ -140,12 +141,9 @@ class ImportsAndTablesStep {
140
141
  }
141
142
  if (!this.parseReferences) {
142
143
  this.parseReferences = (0, find_external_references_1.findReferences)(that, parseReq.parse.tokenStream, parseReq.parse.root);
143
- for (const ref in this.parseReferences.tables) {
144
- that.root.schemaZone.reference(ref, {
145
- url: that.sourceURL,
146
- range: this.parseReferences.tables[ref].firstReference,
147
- });
148
- }
144
+ // Register connection dialects and imports immediately. Table
145
+ // references are deferred until dialects are resolved, because
146
+ // validating a table path requires knowing its dialect's grammar.
149
147
  for (const connName in this.parseReferences.connectionDialects) {
150
148
  that.root.connectionDialectZone.reference(connName, {
151
149
  url: that.sourceURL,
@@ -180,17 +178,49 @@ class ImportsAndTablesStep {
180
178
  return { timingInfo: parseReq.timingInfo };
181
179
  }
182
180
  let allMissing = {};
181
+ // Validate each table reference against its dialect's grammar (if
182
+ // the dialect is resolved) and register the canonical entry. Bad
183
+ // paths are silently dropped — the AST step re-validates and logs
184
+ // an error at the precise source range. Entries whose dialect
185
+ // isn't resolved yet are preserved unchanged and re-processed on a
186
+ // later step.
187
+ {
188
+ const refs = this.parseReferences.tables;
189
+ const canonical = {};
190
+ for (const rawKey in refs) {
191
+ const info = refs[rawKey];
192
+ let { tablePath } = info;
193
+ const dialectName = that.root.connectionDialectZone.get(info.connectionName);
194
+ if (dialectName !== undefined) {
195
+ const result = (0, dialect_1.getDialect)(dialectName).sqlValidateTableName(tablePath);
196
+ if (!result.ok)
197
+ continue;
198
+ tablePath = result.canonical;
199
+ }
200
+ const key = `${info.connectionName}:${tablePath}`;
201
+ canonical[key] = { ...info, tablePath };
202
+ that.root.schemaZone.reference(key, {
203
+ url: that.sourceURL,
204
+ range: info.firstReference,
205
+ });
206
+ }
207
+ this.parseReferences.tables = canonical;
208
+ }
183
209
  const missingTables = that.root.schemaZone.getUndefined();
184
210
  if (missingTables) {
185
211
  const tables = {};
186
212
  for (const key of missingTables) {
187
213
  const info = this.parseReferences.tables[key];
214
+ if (info === undefined)
215
+ continue;
188
216
  tables[key] = {
189
217
  connectionName: info.connectionName,
190
218
  tablePath: info.tablePath,
191
219
  };
192
220
  }
193
- allMissing = { tables };
221
+ if (Object.keys(tables).length > 0) {
222
+ allMissing = { tables };
223
+ }
194
224
  }
195
225
  const missingDialects = that.root.connectionDialectZone.getUndefined();
196
226
  if (missingDialects) {
@@ -4,27 +4,14 @@ import type { DocumentRange } from '../../model/malloy_types';
4
4
  import type { MalloyTranslation } from '../parse-malloy';
5
5
  type NeedImports = Record<string, DocumentRange>;
6
6
  type NeedTables = Record<string, {
7
- connectionName: string | undefined;
7
+ connectionName: string;
8
8
  tablePath: string;
9
9
  firstReference: DocumentRange;
10
10
  }>;
11
11
  type NeedConnectionDialects = Record<string, {
12
12
  firstReference: DocumentRange;
13
13
  }>;
14
- export declare function constructTableKey(connectionName: string | undefined, tablePath: string): string;
15
- /**
16
- * This function parses an old-style `tableURI` into a connection name and
17
- * table path. The name includes `deprecated` because it should only be used
18
- * in the (deprecated) old-style `table('conn:tab')` syntax. Any use of this
19
- * anywhere else is bad.
20
- * @param tableURI The sting that is passed into the `table('conn:tab')` syntax.
21
- * @returns A connection name and table path.
22
- * @deprecated
23
- */
24
- export declare function deprecatedParseTableURI(tableURI: string): {
25
- connectionName?: string;
26
- tablePath: string;
27
- };
14
+ export declare function constructTableKey(connectionName: string, tablePath: string): string;
28
15
  export interface FindReferencesData {
29
16
  tables: NeedTables;
30
17
  urls: NeedImports;
@@ -23,7 +23,6 @@
23
23
  */
24
24
  Object.defineProperty(exports, "__esModule", { value: true });
25
25
  exports.constructTableKey = constructTableKey;
26
- exports.deprecatedParseTableURI = deprecatedParseTableURI;
27
26
  exports.findReferences = findReferences;
28
27
  const ParseTreeWalker_1 = require("antlr4ts/tree/ParseTreeWalker");
29
28
  const parse_utils_1 = require("../parse-utils");
@@ -70,6 +69,11 @@ class FindExternalReferences {
70
69
  const tablePath = getPlainString(pcx.tablePath());
71
70
  const reference = this.trans.rangeFromContext(pcx);
72
71
  this.registerTableReference(connId, tablePath, reference);
72
+ // Register a need for the connection's dialect so the validator in
73
+ // ImportsAndTablesStep can run against it.
74
+ if (!this.needConnectionDialects[connId]) {
75
+ this.needConnectionDialects[connId] = { firstReference: reference };
76
+ }
73
77
  }
74
78
  enterVirtualSource(pcx) {
75
79
  const connId = (0, parse_utils_1.getId)(pcx.connectionId());
@@ -87,28 +91,7 @@ class FindExternalReferences {
87
91
  }
88
92
  }
89
93
  function constructTableKey(connectionName, tablePath) {
90
- return connectionName === undefined
91
- ? tablePath
92
- : `${connectionName}:${tablePath}`;
93
- }
94
- /**
95
- * This function parses an old-style `tableURI` into a connection name and
96
- * table path. The name includes `deprecated` because it should only be used
97
- * in the (deprecated) old-style `table('conn:tab')` syntax. Any use of this
98
- * anywhere else is bad.
99
- * @param tableURI The sting that is passed into the `table('conn:tab')` syntax.
100
- * @returns A connection name and table path.
101
- * @deprecated
102
- */
103
- function deprecatedParseTableURI(tableURI) {
104
- const parts = tableURI.match(/^([^:]*):(.*)$/);
105
- if (parts) {
106
- const [, firstPart, secondPart] = parts;
107
- return { connectionName: firstPart, tablePath: secondPart };
108
- }
109
- else {
110
- return { tablePath: tableURI };
111
- }
94
+ return `${connectionName}:${tablePath}`;
112
95
  }
113
96
  function findReferences(trans, tokens, parseTree) {
114
97
  const finder = new FindExternalReferences(trans, tokens);
@@ -82,6 +82,9 @@ function exprToStr(e, symbols) {
82
82
  .map(o => `${subExpr(o)}`)
83
83
  .join(',')}}}`;
84
84
  }
85
+ case 'inGiven': {
86
+ return `{${subExpr(e.e)} ${e.not ? 'not in' : 'in'} $${e.givenRef.refName}}`;
87
+ }
85
88
  case 'genericSQLExpr': {
86
89
  let sql = '';
87
90
  let i = 0;
@@ -22,7 +22,7 @@ export interface ProblemResponse {
22
22
  export type FatalResponse = FinalResponse & ProblemResponse;
23
23
  export interface NeedSchemaData {
24
24
  tables: Record<string, {
25
- connectionName: string | undefined;
25
+ connectionName: string;
26
26
  tablePath: string;
27
27
  }>;
28
28
  }
@@ -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
- function generateGivenFragment(resultSet, context, expr, state) {
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 exprToSQL(resultSet, context, supplied, state);
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 exprToSQL(resultSet, context, decl.default, state);
614
- }
615
- throw new Error(unsatisfiedGivenMessage(expr.refName));
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. ` +
@@ -129,7 +129,7 @@ exports.FilterCompilers = {
129
129
  // For some databases checking NULL combined with a boolean check
130
130
  // is faster than a COALESCE, for now, just detect if the expression
131
131
  // is just a column reference, and if so, don't use COALECSE.
132
- const quoteChar = d.sqlMaybeQuoteIdentifier('select')[0];
132
+ const quoteChar = d.sqlQuoteIdentifier('select')[0];
133
133
  const isColumn = x.match(`^[()${quoteChar}\\w.]+$`);
134
134
  if (isColumn) {
135
135
  if (bc.operator === 'true') {
@@ -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;