@malloydata/malloy 0.0.395 → 0.0.396

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 (44) hide show
  1. package/dist/api/foundation/compile.d.ts +7 -6
  2. package/dist/api/foundation/compile.js +22 -6
  3. package/dist/api/foundation/runtime.d.ts +85 -5
  4. package/dist/api/foundation/runtime.js +183 -13
  5. package/dist/api/foundation/types.d.ts +2 -0
  6. package/dist/lang/ast/expressions/expr-func.js +30 -11
  7. package/dist/lang/ast/expressions/expr-given.js +1 -0
  8. package/dist/lang/ast/field-space/reference-field.js +1 -1
  9. package/dist/lang/ast/source-elements/sql-source.js +4 -0
  10. package/dist/lang/ast/source-elements/table-source.js +4 -0
  11. package/dist/lang/ast/statements/define-given.d.ts +1 -0
  12. package/dist/lang/ast/statements/define-given.js +7 -0
  13. package/dist/lang/ast/statements/import-statement.js +4 -0
  14. package/dist/lang/ast/types/annotation-elements.d.ts +1 -0
  15. package/dist/lang/ast/types/annotation-elements.js +10 -3
  16. package/dist/lang/ast/types/malloy-element.d.ts +1 -0
  17. package/dist/lang/ast/types/malloy-element.js +4 -0
  18. package/dist/lang/malloy-to-ast.d.ts +2 -1
  19. package/dist/lang/malloy-to-ast.js +11 -1
  20. package/dist/lang/parse-log.d.ts +1 -0
  21. package/dist/lang/parse-log.js +4 -0
  22. package/dist/lang/parse-malloy.d.ts +4 -1
  23. package/dist/lang/parse-malloy.js +26 -4
  24. package/dist/lang/test/test-translator.d.ts +19 -5
  25. package/dist/lang/test/test-translator.js +15 -12
  26. package/dist/lang/zone.d.ts +2 -0
  27. package/dist/lang/zone.js +10 -0
  28. package/dist/model/constant_expression_compiler.js +14 -5
  29. package/dist/model/expression_compiler.js +19 -17
  30. package/dist/model/field_instance.js +7 -3
  31. package/dist/model/given_binding.js +26 -21
  32. package/dist/model/index.d.ts +1 -0
  33. package/dist/model/index.js +3 -1
  34. package/dist/model/malloy_compile_error.d.ts +13 -0
  35. package/dist/model/malloy_compile_error.js +23 -0
  36. package/dist/model/malloy_types.d.ts +2 -0
  37. package/dist/model/query_model_impl.js +2 -1
  38. package/dist/model/query_node.d.ts +5 -5
  39. package/dist/model/query_node.js +21 -16
  40. package/dist/model/query_query.js +23 -11
  41. package/dist/model/sql_compiled.js +6 -3
  42. package/dist/version.d.ts +1 -1
  43. package/dist/version.js +1 -1
  44. package/package.json +4 -4
@@ -54,6 +54,12 @@ export declare class MalloyError extends Error {
54
54
  */
55
55
  constructor(message: string, problems?: LogMessage[]);
56
56
  }
57
+ type CompileRequest = Compilable & CompileOptions & CompileQueryOptions & ParseOptions & {
58
+ urlReader: URLReader;
59
+ connections: LookupConnection<InfoConnection>;
60
+ model?: Model;
61
+ cacheManager?: CacheManager;
62
+ };
57
63
  export declare class Malloy {
58
64
  static get version(): string;
59
65
  /**
@@ -109,12 +115,7 @@ export declare class Malloy {
109
115
  * @param model A compiled model to build upon (optional).
110
116
  * @return A (promise of a) compiled `Model`.
111
117
  */
112
- static compile({ url, source, parse, urlReader, connections, model, refreshSchemaCache, noThrowOnError, eventStream, importBaseURL, cacheManager, }: {
113
- urlReader: URLReader;
114
- connections: LookupConnection<InfoConnection>;
115
- model?: Model;
116
- cacheManager?: CacheManager;
117
- } & Compilable & CompileOptions & CompileQueryOptions & ParseOptions): Promise<Model>;
118
+ static compile(req: CompileRequest): Promise<Model>;
118
119
  /**
119
120
  * A dialect must provide a response for every table, or the translator loop
120
121
  * will never exit. Because there was a time when this happened, we throw
@@ -37,9 +37,6 @@ class MalloyError extends Error {
37
37
  }
38
38
  }
39
39
  exports.MalloyError = MalloyError;
40
- // =============================================================================
41
- // Malloy Static Class
42
- // =============================================================================
43
40
  class Malloy {
44
41
  static get version() {
45
42
  return version_1.MALLOY_VERSION;
@@ -70,6 +67,7 @@ class Malloy {
70
67
  return (0, registry_1.getRegisteredConnectionTypes)();
71
68
  }
72
69
  static _parse(source, url, eventStream, options, invalidationKey) {
70
+ var _a;
73
71
  if (url === undefined) {
74
72
  url = new URL(MALLOY_INTERNAL_URL);
75
73
  }
@@ -79,7 +77,7 @@ class Malloy {
79
77
  }
80
78
  const translator = new lang_1.MalloyTranslator(url.toString(), importBaseURL.toString(), {
81
79
  urls: { [url.toString()]: source },
82
- }, eventStream);
80
+ }, eventStream, (_a = options === null || options === void 0 ? void 0 : options.restrictedMode) !== null && _a !== void 0 ? _a : false);
83
81
  if (options === null || options === void 0 ? void 0 : options.testEnvironment) {
84
82
  translator.allDialectsEnabled = true;
85
83
  }
@@ -110,8 +108,19 @@ class Malloy {
110
108
  * @param model A compiled model to build upon (optional).
111
109
  * @return A (promise of a) compiled `Model`.
112
110
  */
113
- static async compile({ url, source, parse, urlReader, connections, model, refreshSchemaCache, noThrowOnError, eventStream, importBaseURL, cacheManager, }) {
111
+ static async compile(req) {
114
112
  var _a, _b, _c, _d, _e;
113
+ let { url, source, importBaseURL, cacheManager } = req;
114
+ const { parse, urlReader, connections, model, refreshSchemaCache, noThrowOnError, eventStream, restrictedMode, } = req;
115
+ if (restrictedMode) {
116
+ // Restricted-mode compiles do not participate in the model-def
117
+ // cache. The cache key is the URL, but restricted vs. unrestricted
118
+ // produces different validation outcomes, so allowing a restricted
119
+ // compile to serve from (or write to) the same cache as
120
+ // unrestricted compiles would let restricted mode be bypassed by a
121
+ // prior unrestricted compile of the same URL.
122
+ cacheManager = undefined;
123
+ }
115
124
  let refreshTimestamp;
116
125
  if (refreshSchemaCache) {
117
126
  refreshTimestamp =
@@ -145,6 +154,13 @@ class Malloy {
145
154
  // It's not cached, so we may need to get the actual source
146
155
  const _url = url.toString();
147
156
  if (parse !== undefined) {
157
+ // A pre-parsed translator's restrictedMode was fixed at parse
158
+ // time and cannot be changed here. Loudly reject mismatched
159
+ // requests rather than silently inheriting the parse-time value.
160
+ if (restrictedMode !== undefined &&
161
+ parse._translator.restrictedMode !== restrictedMode) {
162
+ throw new Error(`Malloy.compile: restrictedMode (${restrictedMode}) does not match the pre-parsed translator's restrictedMode (${parse._translator.restrictedMode}). Set restrictedMode at parse time.`);
163
+ }
148
164
  translator = parse._translator;
149
165
  const invalidationKey = (_a = parse._invalidationKey) !== null && _a !== void 0 ? _a : (await (0, readers_1.getInvalidationKey)(urlReader, url));
150
166
  invalidationKeys[_url] = invalidationKey;
@@ -161,7 +177,7 @@ class Malloy {
161
177
  }
162
178
  translator = new lang_1.MalloyTranslator(_url, importBaseURL.toString(), {
163
179
  urls: { [_url]: source },
164
- }, eventStream);
180
+ }, eventStream, restrictedMode !== null && restrictedMode !== void 0 ? restrictedMode : false);
165
181
  }
166
182
  for (;;) {
167
183
  const result = translator.translate(model === null || model === void 0 ? void 0 : model._modelDef);
@@ -1,6 +1,7 @@
1
1
  import type { Connection, LookupConnection } from '../../connection/types';
2
2
  import type { URLReader, EventStream } from '../../runtime_types';
3
3
  import type { ModelDef, Query as InternalQuery, SearchIndexResult, SearchValueMapResult, QueryRunStats, BuildManifest, GivenValue, VirtualMap } from '../../model';
4
+ import type { LogMessage } from '../../lang';
4
5
  import type { Dialect } from '../../dialect';
5
6
  import type { RunSQLOptions } from '../../run_sql_options';
6
7
  import type { CacheManager } from './cache';
@@ -345,6 +346,50 @@ export declare class ModelMaterializer extends FluentState<Model> {
345
346
  * or loading further related objects.
346
347
  */
347
348
  loadQuery(query: QueryString | QueryURL, options?: ParseOptions & CompileOptions & CompileQueryOptions): QueryMaterializer;
349
+ /**
350
+ * Load a Malloy query whose text comes from an untrusted source — an
351
+ * MCP client, an LLM-authored query, a UI field, an HTTP request body
352
+ * — and should compile against this already-loaded trusted model.
353
+ * Use this in preference to `loadQuery` when the caller of your
354
+ * service is not the author of the model and may write Malloy that
355
+ * reaches past the model's curated surface.
356
+ *
357
+ * Restricted-mode compilation rejects these constructs:
358
+ *
359
+ * - `import` statements
360
+ * - `given:` declarations (restricted queries may still reference
361
+ * givens the model declared, via `$NAME`)
362
+ * - `##!` compiler-flag annotations
363
+ * - `connection.table(...)` and `connection.sql(...)` source forms
364
+ * - `name!type(args)` raw-SQL function calls
365
+ * - The `sql_*` family of built-in functions (`sql_number`,
366
+ * `sql_string`, `sql_date`, `sql_timestamp`, `sql_boolean`)
367
+ *
368
+ * The model's existing surface — sources, queries, dimensions,
369
+ * measures, functions, givens declared by the model author — is
370
+ * fully available regardless of whether the model's own definitions
371
+ * use any of the forbidden constructs.
372
+ *
373
+ * Errors reach the caller in the same way as any other Malloy
374
+ * compile: `.validate()` returns a `LogMessage[]`, and `.run()` /
375
+ * `.getPreparedResult()` / `.getSQL()` throw `MalloyError` whose
376
+ * `.problems` is the same array. The list holds every compile
377
+ * problem — ordinary translator and SQL-compile errors as well as
378
+ * restricted-mode rejections. The restricted-mode subset is
379
+ * identifiable by `code: 'restricted-construct-forbidden'` and
380
+ * `errorTag: 'restricted-mode'`; restricted-mode rejection messages
381
+ * quote the offending source text and state the rule. Multiple
382
+ * violations are reported in one compile.
383
+ *
384
+ * The input is required to be a string. Restricted text arrives from
385
+ * an untrusted caller as bytes the host already has in hand; there
386
+ * is no host-side trust mechanism for fetching it via a URL.
387
+ *
388
+ * @param text The Malloy text to compile as a restricted query.
389
+ * @return A `QueryMaterializer` capable of materializing or running
390
+ * the query.
391
+ */
392
+ loadRestrictedQuery(text: string): QueryMaterializer;
348
393
  /**
349
394
  * Extend a Malloy model by URL or contents.
350
395
  *
@@ -404,16 +449,47 @@ export declare class ModelMaterializer extends FluentState<Model> {
404
449
  * @return A promise to the compiled model that is loaded.
405
450
  */
406
451
  getModel(): Promise<Model>;
452
+ /**
453
+ * Compile this model and return any problems as structured
454
+ * `LogMessage`s; empty array means clean. Non-throwing.
455
+ *
456
+ * Only translator-time problems surface here; SQL-compile problems
457
+ * are per-query and live on `QueryMaterializer.validate()`.
458
+ *
459
+ * A subsequent `getModel()` reuses the cached materialize.
460
+ */
461
+ validate(): Promise<LogMessage[]>;
407
462
  }
408
- /**
409
- * An object representing the task of loading a `Query`, capable of
410
- * materializing the query (via `getPreparedQuery()`) or extending the task to load
411
- * prepared results or run the query (via e.g. `loadPreparedResult()` or `run()`).
412
- */
413
463
  export declare class QueryMaterializer extends FluentState<PreparedQuery> {
414
464
  protected runtime: Runtime;
415
465
  private readonly compileQueryOptions;
466
+ /**
467
+ * Memoizes the no-options compile so `validate()` + a follow-up
468
+ * `getPreparedResult()` / `run()` shares one compilation. Calls with
469
+ * explicit options bypass — options aren't hashed.
470
+ */
471
+ private _compileAttempt;
416
472
  constructor(runtime: Runtime, materialize: () => Promise<PreparedQuery>, options?: CompileQueryOptions);
473
+ /**
474
+ * `MalloyError` (translator) is unwrapped into its `problems` array.
475
+ * `MalloyCompileError` (SQL compiler) becomes one structured problem.
476
+ * Any other `Error` is treated as an invariant violation and surfaces
477
+ * as one `code: 'compiler-bug'` problem.
478
+ */
479
+ private _compileAndCollect;
480
+ private compileAttempt;
481
+ /**
482
+ * Compile this query and return any problems as structured
483
+ * `LogMessage`s; empty array means clean. Non-throwing.
484
+ *
485
+ * Surfaces translator-time errors (may be several) and compile-time
486
+ * errors (at most one — the compiler is fail-fast). LogMessages carry
487
+ * `code` and, where the IR has it, `at: DocumentLocation`.
488
+ *
489
+ * A subsequent no-options `getPreparedResult()` / `getSQL()` / `run()`
490
+ * reuses the cached compile.
491
+ */
492
+ validate(options?: CompileQueryOptions): Promise<LogMessage[]>;
417
493
  /**
418
494
  * Run this loaded `Query`.
419
495
  *
@@ -431,6 +507,10 @@ export declare class QueryMaterializer extends FluentState<PreparedQuery> {
431
507
  /**
432
508
  * Materialize the prepared result of this loaded query.
433
509
  *
510
+ * Throws `MalloyError` on failure, with `.problems` populated for both
511
+ * translator and compiler errors. For non-throwing access to the same
512
+ * problem list, use `validate()`.
513
+ *
434
514
  * @return A promise of the prepared result of this loaded query.
435
515
  */
436
516
  getPreparedResult(options?: CompileQueryOptions): Promise<PreparedResult>;
@@ -614,6 +614,67 @@ class ModelMaterializer extends FluentState {
614
614
  return queryModel.preparedQuery;
615
615
  });
616
616
  }
617
+ /**
618
+ * Load a Malloy query whose text comes from an untrusted source — an
619
+ * MCP client, an LLM-authored query, a UI field, an HTTP request body
620
+ * — and should compile against this already-loaded trusted model.
621
+ * Use this in preference to `loadQuery` when the caller of your
622
+ * service is not the author of the model and may write Malloy that
623
+ * reaches past the model's curated surface.
624
+ *
625
+ * Restricted-mode compilation rejects these constructs:
626
+ *
627
+ * - `import` statements
628
+ * - `given:` declarations (restricted queries may still reference
629
+ * givens the model declared, via `$NAME`)
630
+ * - `##!` compiler-flag annotations
631
+ * - `connection.table(...)` and `connection.sql(...)` source forms
632
+ * - `name!type(args)` raw-SQL function calls
633
+ * - The `sql_*` family of built-in functions (`sql_number`,
634
+ * `sql_string`, `sql_date`, `sql_timestamp`, `sql_boolean`)
635
+ *
636
+ * The model's existing surface — sources, queries, dimensions,
637
+ * measures, functions, givens declared by the model author — is
638
+ * fully available regardless of whether the model's own definitions
639
+ * use any of the forbidden constructs.
640
+ *
641
+ * Errors reach the caller in the same way as any other Malloy
642
+ * compile: `.validate()` returns a `LogMessage[]`, and `.run()` /
643
+ * `.getPreparedResult()` / `.getSQL()` throw `MalloyError` whose
644
+ * `.problems` is the same array. The list holds every compile
645
+ * problem — ordinary translator and SQL-compile errors as well as
646
+ * restricted-mode rejections. The restricted-mode subset is
647
+ * identifiable by `code: 'restricted-construct-forbidden'` and
648
+ * `errorTag: 'restricted-mode'`; restricted-mode rejection messages
649
+ * quote the offending source text and state the rule. Multiple
650
+ * violations are reported in one compile.
651
+ *
652
+ * The input is required to be a string. Restricted text arrives from
653
+ * an untrusted caller as bytes the host already has in hand; there
654
+ * is no host-side trust mechanism for fetching it via a URL.
655
+ *
656
+ * @param text The Malloy text to compile as a restricted query.
657
+ * @return A `QueryMaterializer` capable of materializing or running
658
+ * the query.
659
+ */
660
+ loadRestrictedQuery(text) {
661
+ return this.makeQueryMaterializer(async () => {
662
+ const urlReader = this.runtime.urlReader;
663
+ const connections = this.runtime.connections;
664
+ const testEnvironment = this.runtime.isTestRuntime ? true : undefined;
665
+ const model = await this.getModel();
666
+ const queryModel = await compile_1.Malloy.compile({
667
+ source: text,
668
+ restrictedMode: true,
669
+ urlReader,
670
+ connections,
671
+ model,
672
+ testEnvironment,
673
+ ...this.compileQueryOptions,
674
+ });
675
+ return queryModel.preparedQuery;
676
+ });
677
+ }
617
678
  /**
618
679
  * Extend a Malloy model by URL or contents.
619
680
  *
@@ -775,6 +836,36 @@ class ModelMaterializer extends FluentState {
775
836
  getModel() {
776
837
  return this.materialize();
777
838
  }
839
+ /**
840
+ * Compile this model and return any problems as structured
841
+ * `LogMessage`s; empty array means clean. Non-throwing.
842
+ *
843
+ * Only translator-time problems surface here; SQL-compile problems
844
+ * are per-query and live on `QueryMaterializer.validate()`.
845
+ *
846
+ * A subsequent `getModel()` reuses the cached materialize.
847
+ */
848
+ async validate() {
849
+ try {
850
+ await this.materialize();
851
+ return [];
852
+ }
853
+ catch (e) {
854
+ if (e instanceof compile_1.MalloyError)
855
+ return e.problems;
856
+ if (e instanceof Error) {
857
+ return [
858
+ {
859
+ message: 'Internal compiler error (likely a Malloy bug — please file ' +
860
+ `an issue): ${e.message}`,
861
+ severity: 'error',
862
+ code: 'compiler-bug',
863
+ },
864
+ ];
865
+ }
866
+ throw e;
867
+ }
868
+ }
778
869
  }
779
870
  exports.ModelMaterializer = ModelMaterializer;
780
871
  // =============================================================================
@@ -787,17 +878,78 @@ function runSQLOptionsWithAnnotations(preparedResult, givenOptions) {
787
878
  ...givenOptions,
788
879
  };
789
880
  }
790
- /**
791
- * An object representing the task of loading a `Query`, capable of
792
- * materializing the query (via `getPreparedQuery()`) or extending the task to load
793
- * prepared results or run the query (via e.g. `loadPreparedResult()` or `run()`).
794
- */
795
881
  class QueryMaterializer extends FluentState {
796
882
  constructor(runtime, materialize, options) {
797
883
  super(runtime, materialize);
798
884
  this.runtime = runtime;
799
885
  this.compileQueryOptions = options;
800
886
  }
887
+ /**
888
+ * `MalloyError` (translator) is unwrapped into its `problems` array.
889
+ * `MalloyCompileError` (SQL compiler) becomes one structured problem.
890
+ * Any other `Error` is treated as an invariant violation and surfaces
891
+ * as one `code: 'compiler-bug'` problem.
892
+ */
893
+ async _compileAndCollect(options) {
894
+ try {
895
+ const preparedResult = await this.loadPreparedResult(options).getPreparedResult();
896
+ return { preparedResult, problems: [] };
897
+ }
898
+ catch (e) {
899
+ if (e instanceof compile_1.MalloyError) {
900
+ return { problems: e.problems };
901
+ }
902
+ if (e instanceof model_1.MalloyCompileError) {
903
+ return {
904
+ problems: [
905
+ {
906
+ message: e.message,
907
+ severity: 'error',
908
+ code: e.code,
909
+ at: e.at,
910
+ },
911
+ ],
912
+ };
913
+ }
914
+ if (e instanceof Error) {
915
+ return {
916
+ problems: [
917
+ {
918
+ message: 'Internal compiler error (likely a Malloy bug — please file ' +
919
+ `an issue): ${e.message}`,
920
+ severity: 'error',
921
+ code: 'compiler-bug',
922
+ },
923
+ ],
924
+ };
925
+ }
926
+ throw e;
927
+ }
928
+ }
929
+ compileAttempt(options) {
930
+ if (options === undefined && this._compileAttempt) {
931
+ return this._compileAttempt;
932
+ }
933
+ const p = this._compileAndCollect(options);
934
+ if (options === undefined) {
935
+ this._compileAttempt = p;
936
+ }
937
+ return p;
938
+ }
939
+ /**
940
+ * Compile this query and return any problems as structured
941
+ * `LogMessage`s; empty array means clean. Non-throwing.
942
+ *
943
+ * Surfaces translator-time errors (may be several) and compile-time
944
+ * errors (at most one — the compiler is fail-fast). LogMessages carry
945
+ * `code` and, where the IR has it, `at: DocumentLocation`.
946
+ *
947
+ * A subsequent no-options `getPreparedResult()` / `getSQL()` / `run()`
948
+ * reuses the cached compile.
949
+ */
950
+ async validate(options) {
951
+ return (await this.compileAttempt(options)).problems;
952
+ }
801
953
  /**
802
954
  * Run this loaded `Query`.
803
955
  *
@@ -862,7 +1014,9 @@ class QueryMaterializer extends FluentState {
862
1014
  if (!modelTag.has('experimental', 'persistence')) {
863
1015
  if (explicitManifest) {
864
1016
  // Explicitly passed non-empty manifest requires persistence support
865
- throw new Error('Model must have ##! experimental.persistence to use buildManifest');
1017
+ throw new model_1.MalloyCompileError('A non-empty `buildManifest` was supplied, but the model is ' +
1018
+ 'missing `##! experimental.persistence`. Add the flag to the ' +
1019
+ 'model or pass `EMPTY_BUILD_MANIFEST` to suppress substitution.', 'runtime-manifest-needs-persistence-flag', undefined);
866
1020
  }
867
1021
  // Runtime-level manifest (e.g. from config): silently ignore
868
1022
  buildManifest = undefined;
@@ -894,7 +1048,8 @@ class QueryMaterializer extends FluentState {
894
1048
  if (finalizedSet.size > 0 && mergedOptions.givens) {
895
1049
  for (const name of Object.keys(mergedOptions.givens)) {
896
1050
  if (finalizedSet.has(name)) {
897
- throw new Error(`Cannot supply '${name}' per-query: it is finalized at the runtime layer (config.finalizeGivens).`);
1051
+ throw new model_1.MalloyCompileError(`Cannot supply given '${name}' per-query: it is finalized at ` +
1052
+ 'the runtime layer via `config.finalizeGivens`.', 'runtime-given-finalized', undefined);
898
1053
  }
899
1054
  }
900
1055
  }
@@ -921,7 +1076,7 @@ class QueryMaterializer extends FluentState {
921
1076
  const queryGivenUsage = (_c = preparedQuery._query.givenUsage) !== null && _c !== void 0 ? _c : [];
922
1077
  if (finalizedSet.size > 0 && queryGivenUsage.length > 0) {
923
1078
  const referencedIds = new Set(queryGivenUsage.map(g => g.id));
924
- const missing = [];
1079
+ const missingProblems = [];
925
1080
  for (const [surfaceName, entry] of Object.entries(preparedQuery._modelDef.contents)) {
926
1081
  if (entry.type !== 'given')
927
1082
  continue;
@@ -931,10 +1086,18 @@ class QueryMaterializer extends FluentState {
931
1086
  continue;
932
1087
  if (mergedGivens && surfaceName in mergedGivens)
933
1088
  continue;
934
- missing.push(surfaceName);
1089
+ const firstUse = queryGivenUsage.find(u => u.id === entry.id);
1090
+ missingProblems.push({
1091
+ message: `Finalized given '${surfaceName}' has no resolved value. ` +
1092
+ 'It must be supplied in `givensPath` or in the Runtime ' +
1093
+ "constructor's `givens`.",
1094
+ severity: 'error',
1095
+ code: 'runtime-given-finalized-missing',
1096
+ at: firstUse === null || firstUse === void 0 ? void 0 : firstUse.at,
1097
+ });
935
1098
  }
936
- if (missing.length > 0) {
937
- throw new Error(`Query references finalized given(s) with no resolved value: ${missing.join(', ')}. Each finalized given the query needs must have a value in givensPath or in the Runtime constructor's \`givens\`.`);
1099
+ if (missingProblems.length > 0) {
1100
+ throw new compile_1.MalloyError(missingProblems.map(p => p.message).join('\n'), missingProblems);
938
1101
  }
939
1102
  }
940
1103
  // Build PrepareResultOptions from CompileQueryOptions + connectionDigests.
@@ -954,10 +1117,17 @@ class QueryMaterializer extends FluentState {
954
1117
  /**
955
1118
  * Materialize the prepared result of this loaded query.
956
1119
  *
1120
+ * Throws `MalloyError` on failure, with `.problems` populated for both
1121
+ * translator and compiler errors. For non-throwing access to the same
1122
+ * problem list, use `validate()`.
1123
+ *
957
1124
  * @return A promise of the prepared result of this loaded query.
958
1125
  */
959
- getPreparedResult(options) {
960
- return this.loadPreparedResult(options).getPreparedResult();
1126
+ async getPreparedResult(options) {
1127
+ const attempt = await this.compileAttempt(options);
1128
+ if (attempt.preparedResult)
1129
+ return attempt.preparedResult;
1130
+ throw new compile_1.MalloyError(attempt.problems.map(p => p.message).join('\n') || 'Compilation failed.', attempt.problems);
961
1131
  }
962
1132
  /**
963
1133
  * Materialize the SQL of this loaded query.
@@ -19,6 +19,8 @@ export interface Loggable {
19
19
  export interface ParseOptions {
20
20
  importBaseURL?: URL;
21
21
  testEnvironment?: boolean;
22
+ /** Reject language constructs that reach outside the trusted model. */
23
+ restrictedMode?: boolean;
22
24
  }
23
25
  /** Options for how to run the Malloy semantic checker/translator */
24
26
  export interface CompileOptions {
@@ -58,13 +58,25 @@ Object.defineProperty(exports, "__esModule", { value: true });
58
58
  exports.ExprFunc = void 0;
59
59
  const malloy_types_1 = require("../../../model/malloy_types");
60
60
  const ast_utils_1 = require("../ast-utils");
61
+ const utils_1 = require("../../../model/utils");
61
62
  const struct_space_field_base_1 = require("../field-space/struct-space-field-base");
62
63
  const expr_value_1 = require("../types/expr-value");
63
64
  const expression_def_1 = require("../types/expression-def");
64
65
  const field_space_1 = require("../types/field-space");
65
- const utils_1 = require("../../../model/utils");
66
+ const utils_2 = require("../../../model/utils");
66
67
  const TDU = __importStar(require("../typedesc-utils"));
67
68
  const composite_source_utils_1 = require("../../composite-source-utils");
69
+ // Built-in functions that take a string literal of user-supplied SQL
70
+ // and emit it directly. Gated by experimental.sql_functions in plain
71
+ // Malloy; rejected unconditionally in restricted queries because they
72
+ // are a raw-SQL escape hatch by definition.
73
+ const SQL_FUNCTION_NAMES = [
74
+ 'sql_number',
75
+ 'sql_string',
76
+ 'sql_date',
77
+ 'sql_timestamp',
78
+ 'sql_boolean',
79
+ ];
68
80
  class ExprFunc extends expression_def_1.ExpressionDef {
69
81
  constructor(name, args, isRaw, explicitType, source) {
70
82
  super({ args: args });
@@ -86,6 +98,12 @@ class ExprFunc extends expression_def_1.ExpressionDef {
86
98
  return true;
87
99
  }
88
100
  getExpression(fs) {
101
+ if (this.isRaw && this.isRestricted()) {
102
+ const typeStr = this.explicitType
103
+ ? (0, utils_1.typeDefToString)(this.explicitType)
104
+ : '';
105
+ return this.loggedErrorExpr('restricted-construct-forbidden', `\`${this.name}!${typeStr}(...)\` cannot be used in a restricted query — direct SQL function calls (\`!type(...)\`) are not permitted.`);
106
+ }
89
107
  return this.getPropsExpression(fs);
90
108
  }
91
109
  findFunctionDef(dialect) {
@@ -126,7 +144,7 @@ class ExprFunc extends expression_def_1.ExpressionDef {
126
144
  const dataType = (_b = this.explicitType) !== null && _b !== void 0 ? _b : inferredType;
127
145
  return (0, expr_value_1.computedExprValue)({
128
146
  dataType,
129
- value: (0, utils_1.composeSQLExpr)(funcCall),
147
+ value: (0, utils_2.composeSQLExpr)(funcCall),
130
148
  from: argExprsWithoutImplicit,
131
149
  });
132
150
  }
@@ -290,13 +308,10 @@ class ExprFunc extends expression_def_1.ExpressionDef {
290
308
  frag.partitionBy = partitionByFields;
291
309
  }
292
310
  const sqlFunctionFieldUsage = [];
293
- if ([
294
- 'sql_number',
295
- 'sql_string',
296
- 'sql_date',
297
- 'sql_timestamp',
298
- 'sql_boolean',
299
- ].includes(func.name)) {
311
+ if (SQL_FUNCTION_NAMES.includes(func.name)) {
312
+ if (this.isRestricted()) {
313
+ return this.loggedErrorExpr('restricted-construct-forbidden', `\`${this.name}(...)\` cannot be used in a restricted query — the \`sql_*\` function family emits user-supplied SQL directly, which is not permitted.`);
314
+ }
300
315
  if (!this.inExperiment('sql_functions', true)) {
301
316
  return this.loggedErrorExpr('sql-functions-experiment-not-enabled', `Cannot use sql_function \`${func.name}\`; use \`sql_functions\` experiment to enable this behavior`);
302
317
  }
@@ -327,7 +342,11 @@ class ExprFunc extends expression_def_1.ExpressionDef {
327
342
  return this.loggedErrorExpr('filter-expression-error', 'Filter expressions cannot be used in sql_ functions');
328
343
  }
329
344
  if (result.found.refType === 'parameter') {
330
- expr.push({ node: 'parameter', path: part.path });
345
+ expr.push({
346
+ node: 'parameter',
347
+ path: part.path,
348
+ at: this.args[0].location,
349
+ });
331
350
  }
332
351
  else {
333
352
  sqlFunctionFieldUsage.push({
@@ -343,7 +362,7 @@ class ExprFunc extends expression_def_1.ExpressionDef {
343
362
  }
344
363
  }
345
364
  }
346
- funcCall = (0, utils_1.composeSQLExpr)(expr);
365
+ funcCall = (0, utils_2.composeSQLExpr)(expr);
347
366
  }
348
367
  }
349
368
  const maxEvalSpace = (0, malloy_types_1.mergeEvalSpaces)(...argExprs.map(e => e.evalSpace));
@@ -55,6 +55,7 @@ class GivenReference extends expression_def_1.ExpressionDef {
55
55
  node: 'given',
56
56
  id: entry.id,
57
57
  refName: this.name,
58
+ at: this.location,
58
59
  };
59
60
  return {
60
61
  ...(0, expr_value_1.literalExprValue)({ value: refNode, dataType: given.type }),
@@ -88,7 +88,7 @@ class ReferenceField extends space_field_1.SpaceField {
88
88
  if (malloy_types_1.TD.isAtomic(foundType)) {
89
89
  this.queryFieldDef = {
90
90
  ...(0, malloy_types_1.mkFieldDef)(TDU.atomicDef(foundType), path[0]),
91
- e: { node: 'parameter', path },
91
+ e: { node: 'parameter', path, at: this.fieldRef.location },
92
92
  };
93
93
  }
94
94
  else {
@@ -100,6 +100,10 @@ class SQLSource extends source_1.Source {
100
100
  }
101
101
  getSourceDef() {
102
102
  var _a;
103
+ if (this.isRestricted()) {
104
+ this.logError('restricted-construct-forbidden', `\`${this.connectionName.refString}.sql(...)\` cannot be used in a restricted query — raw SQL is not permitted.`);
105
+ return error_factory_1.ErrorFactory.structDef;
106
+ }
103
107
  if (!this.validateConnectionName()) {
104
108
  return error_factory_1.ErrorFactory.structDef;
105
109
  }
@@ -94,6 +94,10 @@ class TableMethodSource extends TableSource {
94
94
  }
95
95
  getTableInfo() {
96
96
  var _a;
97
+ if (this.isRestricted()) {
98
+ this.logError('restricted-construct-forbidden', `\`${this.connectionName.refString}.table(...)\` cannot be used in a restricted query — direct table access is not permitted.`);
99
+ return undefined;
100
+ }
97
101
  const connection = this.modelEntry(this.connectionName);
98
102
  const name = this.connectionName.refString;
99
103
  if (connection === undefined) {
@@ -21,4 +21,5 @@ export declare class DefineGivens extends DocStatementList {
21
21
  elementType: string;
22
22
  readonly givens: GivenDeclaration[];
23
23
  constructor(givens: GivenDeclaration[]);
24
+ executeList(doc: Document): import("../../translate-response").ModelDataRequest;
24
25
  }
@@ -209,6 +209,13 @@ class DefineGivens extends malloy_element_1.DocStatementList {
209
209
  this.elementType = 'defineGivens';
210
210
  this.givens = givens;
211
211
  }
212
+ executeList(doc) {
213
+ if (this.isRestricted()) {
214
+ this.logError('restricted-construct-forbidden', '`given:` cannot declare new givens in a restricted query — only `$NAME` references to existing givens are allowed.');
215
+ return undefined;
216
+ }
217
+ return super.executeList(doc);
218
+ }
212
219
  }
213
220
  exports.DefineGivens = DefineGivens;
214
221
  //# sourceMappingURL=define-given.js.map
@@ -89,6 +89,10 @@ class ImportStatement extends malloy_element_1.ListOf {
89
89
  }
90
90
  execute(doc) {
91
91
  var _a;
92
+ if (this.isRestricted()) {
93
+ this.logError('restricted-construct-forbidden', `\`import "${this.url}"\` cannot be used in a restricted query — file imports are not permitted.`);
94
+ return;
95
+ }
92
96
  const trans = this.translator();
93
97
  if (!trans) {
94
98
  this.logError('no-translator-for-import', 'Cannot import without translation context');
@@ -12,6 +12,7 @@ export declare class ObjectAnnotation extends MalloyElement implements QueryProp
12
12
  }
13
13
  export declare class ModelAnnotation extends ObjectAnnotation implements DocStatement {
14
14
  elementType: string;
15
+ getCompilerFlagNotes(): Note[];
15
16
  getCompilerFlagLines(): string[];
16
17
  execute(doc: Document): void;
17
18
  }