@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.
- package/dist/api/foundation/compile.d.ts +7 -6
- package/dist/api/foundation/compile.js +22 -6
- package/dist/api/foundation/runtime.d.ts +85 -5
- package/dist/api/foundation/runtime.js +183 -13
- package/dist/api/foundation/types.d.ts +2 -0
- package/dist/lang/ast/expressions/expr-func.js +30 -11
- package/dist/lang/ast/expressions/expr-given.js +1 -0
- package/dist/lang/ast/field-space/reference-field.js +1 -1
- package/dist/lang/ast/source-elements/sql-source.js +4 -0
- package/dist/lang/ast/source-elements/table-source.js +4 -0
- package/dist/lang/ast/statements/define-given.d.ts +1 -0
- package/dist/lang/ast/statements/define-given.js +7 -0
- package/dist/lang/ast/statements/import-statement.js +4 -0
- package/dist/lang/ast/types/annotation-elements.d.ts +1 -0
- package/dist/lang/ast/types/annotation-elements.js +10 -3
- package/dist/lang/ast/types/malloy-element.d.ts +1 -0
- package/dist/lang/ast/types/malloy-element.js +4 -0
- package/dist/lang/malloy-to-ast.d.ts +2 -1
- package/dist/lang/malloy-to-ast.js +11 -1
- package/dist/lang/parse-log.d.ts +1 -0
- package/dist/lang/parse-log.js +4 -0
- package/dist/lang/parse-malloy.d.ts +4 -1
- package/dist/lang/parse-malloy.js +26 -4
- package/dist/lang/test/test-translator.d.ts +19 -5
- package/dist/lang/test/test-translator.js +15 -12
- package/dist/lang/zone.d.ts +2 -0
- package/dist/lang/zone.js +10 -0
- package/dist/model/constant_expression_compiler.js +14 -5
- package/dist/model/expression_compiler.js +19 -17
- package/dist/model/field_instance.js +7 -3
- package/dist/model/given_binding.js +26 -21
- package/dist/model/index.d.ts +1 -0
- package/dist/model/index.js +3 -1
- package/dist/model/malloy_compile_error.d.ts +13 -0
- package/dist/model/malloy_compile_error.js +23 -0
- package/dist/model/malloy_types.d.ts +2 -0
- package/dist/model/query_model_impl.js +2 -1
- package/dist/model/query_node.d.ts +5 -5
- package/dist/model/query_node.js +21 -16
- package/dist/model/query_query.js +23 -11
- package/dist/model/sql_compiled.js +6 -3
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- 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(
|
|
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(
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
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 (
|
|
937
|
-
throw new
|
|
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
|
-
|
|
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
|
|
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,
|
|
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
|
-
|
|
295
|
-
|
|
296
|
-
|
|
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({
|
|
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,
|
|
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));
|
|
@@ -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) {
|
|
@@ -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
|
}
|