@malloydata/malloy 0.0.433 → 0.0.435
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 +2 -2
- package/dist/api/foundation/compile.js +8 -9
- package/dist/api/foundation/config.js +6 -15
- package/dist/api/foundation/runtime.d.ts +2 -8
- package/dist/api/foundation/runtime.js +11 -39
- package/dist/api/foundation/types.d.ts +1 -1
- package/dist/connection/validate_table_path.d.ts +1 -2
- package/dist/connection/validate_table_path.js +1 -2
- package/dist/dialect/databricks/databricks.js +6 -6
- package/dist/dialect/dialect.d.ts +35 -3
- package/dist/dialect/dialect.js +67 -5
- package/dist/dialect/dialect_map.js +2 -0
- package/dist/dialect/duckdb/duckdb.js +7 -21
- package/dist/dialect/index.d.ts +2 -1
- package/dist/dialect/index.js +3 -1
- package/dist/dialect/mysql/mysql.js +8 -8
- package/dist/dialect/pg_impl.js +12 -12
- package/dist/dialect/postgres/postgres.js +3 -3
- package/dist/dialect/snowflake/snowflake.js +12 -12
- package/dist/dialect/sqlserver/dialect_functions.d.ts +2 -0
- package/dist/dialect/sqlserver/dialect_functions.js +42 -0
- package/dist/dialect/sqlserver/function_overrides.d.ts +2 -0
- package/dist/dialect/sqlserver/function_overrides.js +55 -0
- package/dist/dialect/sqlserver/index.d.ts +1 -0
- package/dist/dialect/sqlserver/index.js +22 -0
- package/dist/dialect/sqlserver/sqlserver.d.ts +102 -0
- package/dist/dialect/sqlserver/sqlserver.js +538 -0
- package/dist/dialect/sqlserver/windows_zones.d.ts +1 -0
- package/dist/dialect/sqlserver/windows_zones.js +613 -0
- package/dist/dialect/standardsql/standardsql.d.ts +1 -1
- package/dist/dialect/standardsql/standardsql.js +16 -13
- package/dist/dialect/trino/trino.d.ts +3 -3
- package/dist/dialect/trino/trino.js +32 -28
- package/dist/index.d.ts +1 -1
- package/dist/index.js +5 -3
- package/dist/lang/ast/source-elements/named-source.js +0 -2
- package/dist/lang/ast/source-elements/refined-source.js +1 -1
- package/dist/lang/ast/source-elements/sql-source.js +2 -4
- package/dist/lang/ast/source-elements/table-source.js +1 -2
- package/dist/lang/ast/source-properties/timezone-statement.d.ts +4 -3
- package/dist/lang/ast/source-properties/timezone-statement.js +14 -12
- package/dist/lang/ast/statements/import-statement.js +4 -0
- package/dist/lang/ast/types/malloy-element.d.ts +0 -7
- package/dist/lang/ast/types/malloy-element.js +0 -18
- package/dist/lang/composite-source-utils.js +10 -3
- package/dist/lang/malloy-to-ast.js +2 -8
- package/dist/lang/parse-malloy.d.ts +20 -4
- package/dist/lang/parse-malloy.js +50 -15
- package/dist/lang/parse-tree-walkers/find-external-references.d.ts +3 -3
- package/dist/lang/parse-tree-walkers/find-external-references.js +17 -16
- package/dist/lang/test/test-translator.d.ts +12 -2
- package/dist/lang/test/test-translator.js +16 -2
- package/dist/model/expression_compiler.js +1 -1
- package/dist/model/join_instance.d.ts +1 -0
- package/dist/model/join_instance.js +9 -1
- package/dist/model/query_query.d.ts +28 -1
- package/dist/model/query_query.js +299 -156
- package/dist/model/source_def_utils.d.ts +7 -1
- package/dist/model/source_def_utils.js +20 -0
- package/dist/model/sql_block.d.ts +2 -2
- package/dist/model/sql_block.js +11 -47
- package/dist/model/sql_compiled.d.ts +3 -1
- package/dist/model/sql_compiled.js +8 -6
- package/dist/model/stage_writer.js +3 -3
- package/dist/model/utils.d.ts +2 -2
- package/dist/model/utils.js +2 -3
- package/dist/test/index.js +16 -8
- package/dist/test/matchers.d.ts +1 -1
- package/dist/test/matchers.js +1 -1
- package/dist/test/test-models.js +6 -13
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/femto-config.motly +15 -1
- package/package.json +7 -5
|
@@ -2,7 +2,7 @@ import type { LogMessage } from '../../lang';
|
|
|
2
2
|
import type { Connection, InfoConnection, LookupConnection, FetchSchemaOptions } from '../../connection/types';
|
|
3
3
|
import type { ConnectionTypeDef, ConnectionPropertyDefinition } from '../../connection/registry';
|
|
4
4
|
import type { URLReader, EventStream } from '../../runtime_types';
|
|
5
|
-
import type { SQLSourceDef, QueryRunStats } from '../../model';
|
|
5
|
+
import type { SQLSourceDef, QueryRunStats, VirtualMap } from '../../model';
|
|
6
6
|
import type { RunSQLOptions } from '../../run_sql_options';
|
|
7
7
|
import type { CacheManager } from './cache';
|
|
8
8
|
import { Parse } from './document';
|
|
@@ -35,7 +35,7 @@ export interface MalloyCompileOptions {
|
|
|
35
35
|
noThrowOnError?: boolean;
|
|
36
36
|
eventStream?: EventStream;
|
|
37
37
|
importBaseURL?: URL;
|
|
38
|
-
|
|
38
|
+
virtualMap?: VirtualMap;
|
|
39
39
|
}
|
|
40
40
|
export interface MalloyRunOptions {
|
|
41
41
|
connection?: Connection;
|
|
@@ -88,9 +88,6 @@ class Malloy {
|
|
|
88
88
|
const translator = new lang_1.MalloyTranslator(url.toString(), importBaseURL.toString(), {
|
|
89
89
|
urls: { [url.toString()]: source },
|
|
90
90
|
}, eventStream, (_a = options === null || options === void 0 ? void 0 : options.restrictedMode) !== null && _a !== void 0 ? _a : false);
|
|
91
|
-
if (options === null || options === void 0 ? void 0 : options.testEnvironment) {
|
|
92
|
-
translator.allDialectsEnabled = true;
|
|
93
|
-
}
|
|
94
91
|
return new document_1.Parse(translator, invalidationKey);
|
|
95
92
|
}
|
|
96
93
|
static parse({ url, urlReader, source, eventStream, options, }) {
|
|
@@ -119,9 +116,10 @@ class Malloy {
|
|
|
119
116
|
* @return A (promise of a) compiled `Model`.
|
|
120
117
|
*/
|
|
121
118
|
static async compile(req) {
|
|
122
|
-
var _a, _b, _c, _d, _e;
|
|
119
|
+
var _a, _b, _c, _d, _e, _f;
|
|
123
120
|
let { url, source, importBaseURL, cacheManager } = req;
|
|
124
121
|
const { parse, urlReader, connections, model, refreshSchemaCache, noThrowOnError, eventStream, restrictedMode, method, } = req;
|
|
122
|
+
const virtualMap = (_a = req.virtualMap) !== null && _a !== void 0 ? _a : parse === null || parse === void 0 ? void 0 : parse._translator.virtualMap;
|
|
125
123
|
if (restrictedMode) {
|
|
126
124
|
// Restricted-mode compiles do not participate in the model-def
|
|
127
125
|
// cache. The cache key is the URL, but restricted vs. unrestricted
|
|
@@ -172,7 +170,8 @@ class Malloy {
|
|
|
172
170
|
throw new Error(`Malloy.compile: restrictedMode (${restrictedMode}) does not match the pre-parsed translator's restrictedMode (${parse._translator.restrictedMode}). Set restrictedMode at parse time.`);
|
|
173
171
|
}
|
|
174
172
|
translator = parse._translator;
|
|
175
|
-
|
|
173
|
+
translator.virtualMap = virtualMap;
|
|
174
|
+
const invalidationKey = (_b = parse._invalidationKey) !== null && _b !== void 0 ? _b : (await (0, readers_1.getInvalidationKey)(urlReader, url));
|
|
176
175
|
invalidationKeys[_url] = invalidationKey;
|
|
177
176
|
}
|
|
178
177
|
else {
|
|
@@ -187,7 +186,7 @@ class Malloy {
|
|
|
187
186
|
}
|
|
188
187
|
translator = new lang_1.MalloyTranslator(_url, importBaseURL.toString(), {
|
|
189
188
|
urls: { [_url]: source },
|
|
190
|
-
}, eventStream, restrictedMode !== null && restrictedMode !== void 0 ? restrictedMode : false);
|
|
189
|
+
}, eventStream, restrictedMode !== null && restrictedMode !== void 0 ? restrictedMode : false, virtualMap);
|
|
191
190
|
}
|
|
192
191
|
for (;;) {
|
|
193
192
|
const result = translator.translate(model === null || model === void 0 ? void 0 : model._modelDef);
|
|
@@ -208,14 +207,14 @@ class Malloy {
|
|
|
208
207
|
const existingQueryModel = !result.modelWasModified && model
|
|
209
208
|
? model.getExistingQueryModel()
|
|
210
209
|
: undefined;
|
|
211
|
-
return new core_1.Model(result.modelDef, result.problems || [], [...((
|
|
210
|
+
return new core_1.Model(result.modelDef, result.problems || [], [...((_c = model === null || model === void 0 ? void 0 : model.fromSources) !== null && _c !== void 0 ? _c : []), ...((_d = result.fromSources) !== null && _d !== void 0 ? _d : [])], existingQueryModel);
|
|
212
211
|
}
|
|
213
212
|
else if (noThrowOnError) {
|
|
214
213
|
const emptyModel = (0, model_1.mkModelDef)('modelDidNotCompile');
|
|
215
214
|
const modelFromCompile = (model === null || model === void 0 ? void 0 : model._modelDef) || emptyModel;
|
|
216
215
|
return new core_1.Model(modelFromCompile, result.problems || [], [
|
|
217
|
-
...((
|
|
218
|
-
...((
|
|
216
|
+
...((_e = model === null || model === void 0 ? void 0 : model.fromSources) !== null && _e !== void 0 ? _e : []),
|
|
217
|
+
...((_f = result.fromSources) !== null && _f !== void 0 ? _f : []),
|
|
219
218
|
]);
|
|
220
219
|
}
|
|
221
220
|
else {
|
|
@@ -169,7 +169,7 @@ class MalloyConfig {
|
|
|
169
169
|
this._managedLookup = (0, config_lookup_1.buildManagedLookup)(prepared.compiledConnections, rawConnectionEntries(pojo), mergedOverlays, log);
|
|
170
170
|
this._connections = this._managedLookup;
|
|
171
171
|
this._overlays = mergedOverlays;
|
|
172
|
-
this.virtualMap = toVirtualMap(prepared.virtualMap
|
|
172
|
+
this.virtualMap = toVirtualMap(prepared.virtualMap);
|
|
173
173
|
this.configURL = configURL;
|
|
174
174
|
this.rootDirectory = rootDirectory;
|
|
175
175
|
this.manifestPath = prepared.manifestPath;
|
|
@@ -444,10 +444,10 @@ function validateURLString(value, fieldName, log) {
|
|
|
444
444
|
}
|
|
445
445
|
/**
|
|
446
446
|
* Convert the raw virtualMap POJO shape (a dict of dicts of strings) into
|
|
447
|
-
* the runtime Map-of-Maps representation.
|
|
448
|
-
*
|
|
447
|
+
* the runtime Map-of-Maps representation. Table paths are checked against
|
|
448
|
+
* the executing dialect when a virtual source is substituted into SQL.
|
|
449
449
|
*/
|
|
450
|
-
function toVirtualMap(raw
|
|
450
|
+
function toVirtualMap(raw) {
|
|
451
451
|
if (!isRecord(raw))
|
|
452
452
|
return undefined;
|
|
453
453
|
const outer = new Map();
|
|
@@ -456,18 +456,9 @@ function toVirtualMap(raw, log) {
|
|
|
456
456
|
continue;
|
|
457
457
|
const innerMap = new Map();
|
|
458
458
|
for (const [virtualName, tablePath] of Object.entries(inner)) {
|
|
459
|
-
if (typeof tablePath
|
|
460
|
-
|
|
461
|
-
const invalid = (0, validate_table_path_1.validateCanonicalTablePathAnyDialect)(tablePath);
|
|
462
|
-
if (invalid !== undefined) {
|
|
463
|
-
log.push({
|
|
464
|
-
message: `virtualMap entry '${connName}.${virtualName}': ${invalid}`,
|
|
465
|
-
severity: 'error',
|
|
466
|
-
code: 'config-validation',
|
|
467
|
-
});
|
|
468
|
-
continue;
|
|
459
|
+
if (typeof tablePath === 'string') {
|
|
460
|
+
innerMap.set(virtualName, tablePath);
|
|
469
461
|
}
|
|
470
|
-
innerMap.set(virtualName, tablePath);
|
|
471
462
|
}
|
|
472
463
|
if (innerMap.size > 0)
|
|
473
464
|
outer.set(connName, innerMap);
|
|
@@ -42,7 +42,6 @@ declare class FluentState<T> {
|
|
|
42
42
|
* An environment for compiling and running Malloy queries.
|
|
43
43
|
*/
|
|
44
44
|
export declare class Runtime {
|
|
45
|
-
isTestRuntime: boolean;
|
|
46
45
|
private _urlReader;
|
|
47
46
|
private _connections;
|
|
48
47
|
private _eventStream;
|
|
@@ -172,13 +171,8 @@ export declare class Runtime {
|
|
|
172
171
|
/** @internal */
|
|
173
172
|
_invalidateGivensCache(): void;
|
|
174
173
|
/**
|
|
175
|
-
*
|
|
176
|
-
*
|
|
177
|
-
* against this map. Can be overridden per-query via
|
|
178
|
-
* CompileQueryOptions.virtualMap.
|
|
179
|
-
*
|
|
180
|
-
* When constructed with a MalloyConfig, falls through to
|
|
181
|
-
* config.virtualMap.
|
|
174
|
+
* Default virtual-source bindings for translation and query SQL generation.
|
|
175
|
+
* Falls back to config.virtualMap when no runtime map is assigned.
|
|
182
176
|
*/
|
|
183
177
|
get virtualMap(): VirtualMap | undefined;
|
|
184
178
|
set virtualMap(map: VirtualMap | undefined);
|
|
@@ -51,7 +51,6 @@ class FluentState {
|
|
|
51
51
|
class Runtime {
|
|
52
52
|
constructor({ urlReader, connections, connection, config, buildManifest, eventStream, cacheManager, givens, }) {
|
|
53
53
|
var _a, _b;
|
|
54
|
-
this.isTestRuntime = false;
|
|
55
54
|
if (config !== undefined) {
|
|
56
55
|
this._config = config;
|
|
57
56
|
connections = connections !== null && connections !== void 0 ? connections : config.connections;
|
|
@@ -302,13 +301,8 @@ class Runtime {
|
|
|
302
301
|
this._resolvedGivensPromise = undefined;
|
|
303
302
|
}
|
|
304
303
|
/**
|
|
305
|
-
*
|
|
306
|
-
*
|
|
307
|
-
* against this map. Can be overridden per-query via
|
|
308
|
-
* CompileQueryOptions.virtualMap.
|
|
309
|
-
*
|
|
310
|
-
* When constructed with a MalloyConfig, falls through to
|
|
311
|
-
* config.virtualMap.
|
|
304
|
+
* Default virtual-source bindings for translation and query SQL generation.
|
|
305
|
+
* Falls back to config.virtualMap when no runtime map is assigned.
|
|
312
306
|
*/
|
|
313
307
|
get virtualMap() {
|
|
314
308
|
var _a, _b;
|
|
@@ -355,16 +349,9 @@ class Runtime {
|
|
|
355
349
|
*/
|
|
356
350
|
loadModel(source, options) {
|
|
357
351
|
const { refreshSchemaCache, noThrowOnError } = options || {};
|
|
358
|
-
if (this.isTestRuntime) {
|
|
359
|
-
if (options === undefined) {
|
|
360
|
-
options = { testEnvironment: true };
|
|
361
|
-
}
|
|
362
|
-
else {
|
|
363
|
-
options = { ...options, testEnvironment: true };
|
|
364
|
-
}
|
|
365
|
-
}
|
|
366
352
|
const compilable = source instanceof URL ? { url: source } : { source };
|
|
367
353
|
return new ModelMaterializer(this, async () => {
|
|
354
|
+
var _a;
|
|
368
355
|
const m = await compile_1.Malloy.compile({
|
|
369
356
|
...compilable,
|
|
370
357
|
urlReader: this.urlReader,
|
|
@@ -373,8 +360,8 @@ class Runtime {
|
|
|
373
360
|
noThrowOnError,
|
|
374
361
|
eventStream: this.eventStream,
|
|
375
362
|
importBaseURL: options === null || options === void 0 ? void 0 : options.importBaseURL,
|
|
376
|
-
testEnvironment: options === null || options === void 0 ? void 0 : options.testEnvironment,
|
|
377
363
|
cacheManager: this.cacheManager,
|
|
364
|
+
virtualMap: (_a = options === null || options === void 0 ? void 0 : options.virtualMap) !== null && _a !== void 0 ? _a : this.virtualMap,
|
|
378
365
|
});
|
|
379
366
|
return this._withRuntimeContext(m);
|
|
380
367
|
}, options);
|
|
@@ -634,12 +621,13 @@ class ModelMaterializer extends FluentState {
|
|
|
634
621
|
// `restrictedMode` and `method` are named to hold them back: this entry
|
|
635
622
|
// point has never honored them, and `loadRestrictedQuery` is how a caller
|
|
636
623
|
// asks for restricted compilation.
|
|
637
|
-
const { importBaseURL,
|
|
624
|
+
const { importBaseURL, refreshSchemaCache, noThrowOnError, restrictedMode: _restrictedMode, method: _method, ...callQueryOptions } = options !== null && options !== void 0 ? options : {};
|
|
638
625
|
const compileQueryOptions = {
|
|
639
626
|
...this.compileQueryOptions,
|
|
640
627
|
...callQueryOptions,
|
|
641
628
|
};
|
|
642
629
|
return this.makeQueryMaterializer(async () => {
|
|
630
|
+
var _a;
|
|
643
631
|
const compilable = query instanceof URL ? { url: query } : { source: query };
|
|
644
632
|
const queryModel = await compile_1.Malloy.compile({
|
|
645
633
|
...compilable,
|
|
@@ -649,8 +637,8 @@ class ModelMaterializer extends FluentState {
|
|
|
649
637
|
refreshSchemaCache,
|
|
650
638
|
noThrowOnError,
|
|
651
639
|
importBaseURL,
|
|
652
|
-
testEnvironment: testEnvironment || this.runtime.isTestRuntime,
|
|
653
640
|
...compileQueryOptions,
|
|
641
|
+
virtualMap: (_a = compileQueryOptions.virtualMap) !== null && _a !== void 0 ? _a : this.runtime.virtualMap,
|
|
654
642
|
});
|
|
655
643
|
return queryModel.preparedQuery;
|
|
656
644
|
}, compileQueryOptions);
|
|
@@ -702,7 +690,6 @@ class ModelMaterializer extends FluentState {
|
|
|
702
690
|
return this.makeQueryMaterializer(async () => {
|
|
703
691
|
const urlReader = this.runtime.urlReader;
|
|
704
692
|
const connections = this.runtime.connections;
|
|
705
|
-
const testEnvironment = this.runtime.isTestRuntime ? true : undefined;
|
|
706
693
|
const model = await this.getModel();
|
|
707
694
|
const queryModel = await compile_1.Malloy.compile({
|
|
708
695
|
source: text,
|
|
@@ -711,7 +698,6 @@ class ModelMaterializer extends FluentState {
|
|
|
711
698
|
urlReader,
|
|
712
699
|
connections,
|
|
713
700
|
model,
|
|
714
|
-
testEnvironment,
|
|
715
701
|
...this.compileQueryOptions,
|
|
716
702
|
});
|
|
717
703
|
return queryModel.preparedQuery;
|
|
@@ -725,14 +711,8 @@ class ModelMaterializer extends FluentState {
|
|
|
725
711
|
* or loading further related objects.
|
|
726
712
|
*/
|
|
727
713
|
extendModel(query, options) {
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
options = { testEnvironment: true };
|
|
731
|
-
}
|
|
732
|
-
else {
|
|
733
|
-
options = { ...options, testEnvironment: true };
|
|
734
|
-
}
|
|
735
|
-
}
|
|
714
|
+
var _a, _b;
|
|
715
|
+
const virtualMap = (_a = options === null || options === void 0 ? void 0 : options.virtualMap) !== null && _a !== void 0 ? _a : (_b = this.compileQueryOptions) === null || _b === void 0 ? void 0 : _b.virtualMap;
|
|
736
716
|
return new ModelMaterializer(this.runtime, async () => {
|
|
737
717
|
const urlReader = this.runtime.urlReader;
|
|
738
718
|
const connections = this.runtime.connections;
|
|
@@ -747,11 +727,11 @@ class ModelMaterializer extends FluentState {
|
|
|
747
727
|
refreshSchemaCache: options === null || options === void 0 ? void 0 : options.refreshSchemaCache,
|
|
748
728
|
noThrowOnError: options === null || options === void 0 ? void 0 : options.noThrowOnError,
|
|
749
729
|
importBaseURL: options === null || options === void 0 ? void 0 : options.importBaseURL,
|
|
750
|
-
testEnvironment: options === null || options === void 0 ? void 0 : options.testEnvironment,
|
|
751
730
|
...this.compileQueryOptions,
|
|
731
|
+
virtualMap: virtualMap !== null && virtualMap !== void 0 ? virtualMap : this.runtime.virtualMap,
|
|
752
732
|
});
|
|
753
733
|
return this.runtime._withRuntimeContext(queryModel);
|
|
754
|
-
}, options);
|
|
734
|
+
}, { ...options, virtualMap });
|
|
755
735
|
}
|
|
756
736
|
async search(sourceName, searchTerm, limit = 1000, searchField = undefined) {
|
|
757
737
|
const model = await this.materialize();
|
|
@@ -1064,15 +1044,7 @@ class QueryMaterializer extends FluentState {
|
|
|
1064
1044
|
connectionDigests[connName] = conn.getDigest();
|
|
1065
1045
|
}
|
|
1066
1046
|
}
|
|
1067
|
-
// Use virtualMap from options if provided, otherwise fall back to Runtime's.
|
|
1068
1047
|
const virtualMap = (_b = mergedOptions.virtualMap) !== null && _b !== void 0 ? _b : this.runtime.virtualMap;
|
|
1069
|
-
if (virtualMap) {
|
|
1070
|
-
for (const [connName, inner] of virtualMap) {
|
|
1071
|
-
for (const [virtualName, tablePath] of inner) {
|
|
1072
|
-
(0, validate_table_path_1.requireCanonicalTablePathAnyDialect)(tablePath, `virtualMap entry '${connName}.${virtualName}'`);
|
|
1073
|
-
}
|
|
1074
|
-
}
|
|
1075
|
-
}
|
|
1076
1048
|
// Per-query supply for a finalized given is rejected at API entry
|
|
1077
1049
|
// — the finalized-givens set is the runtime's "this can't be
|
|
1078
1050
|
// overridden by the caller" guarantee. Fail before any IO so misuse
|
|
@@ -20,7 +20,6 @@ export interface Loggable {
|
|
|
20
20
|
}
|
|
21
21
|
export interface ParseOptions {
|
|
22
22
|
importBaseURL?: URL;
|
|
23
|
-
testEnvironment?: boolean;
|
|
24
23
|
/** Reject language constructs that reach outside the trusted model. */
|
|
25
24
|
restrictedMode?: boolean;
|
|
26
25
|
/**
|
|
@@ -43,6 +42,7 @@ export type CompileMethod = 'loadModel' | 'extendModel' | 'query';
|
|
|
43
42
|
export interface CompileOptions {
|
|
44
43
|
refreshSchemaCache?: boolean | number;
|
|
45
44
|
noThrowOnError?: boolean;
|
|
45
|
+
virtualMap?: VirtualMap;
|
|
46
46
|
}
|
|
47
47
|
/** Options given to the Malloy compiler (QueryModel) */
|
|
48
48
|
export interface CompileQueryOptions {
|
|
@@ -2,8 +2,7 @@
|
|
|
2
2
|
export declare function validateCanonicalTablePath(dialectName: string, tablePath: string): string | undefined;
|
|
3
3
|
/**
|
|
4
4
|
* Validate against any registered dialect. Used at boundaries where the
|
|
5
|
-
* destination dialect isn't synchronously known (
|
|
6
|
-
* entries).
|
|
5
|
+
* destination dialect isn't synchronously known (manifest entries).
|
|
7
6
|
*/
|
|
8
7
|
export declare function validateCanonicalTablePathAnyDialect(tablePath: string): string | undefined;
|
|
9
8
|
/** Throw if `tablePath` isn't canonical SQL in any registered dialect. */
|
|
@@ -28,8 +28,7 @@ function validateCanonicalTablePath(dialectName, tablePath) {
|
|
|
28
28
|
}
|
|
29
29
|
/**
|
|
30
30
|
* Validate against any registered dialect. Used at boundaries where the
|
|
31
|
-
* destination dialect isn't synchronously known (
|
|
32
|
-
* entries).
|
|
31
|
+
* destination dialect isn't synchronously known (manifest entries).
|
|
33
32
|
*/
|
|
34
33
|
function validateCanonicalTablePathAnyDialect(tablePath) {
|
|
35
34
|
let suggestion;
|
|
@@ -304,13 +304,13 @@ class DatabricksDialect extends dialect_1.Dialect {
|
|
|
304
304
|
// Databricks has no timestamptz type; timestamps are stored as UTC
|
|
305
305
|
// Convert from UTC to local timezone for civil time operations
|
|
306
306
|
return {
|
|
307
|
-
sql: `FROM_UTC_TIMESTAMP(${expr},
|
|
307
|
+
sql: `FROM_UTC_TIMESTAMP(${expr}, ${this.sqlTimezoneLiteral(timezone)})`,
|
|
308
308
|
typeDef: { type: 'timestamp' },
|
|
309
309
|
};
|
|
310
310
|
}
|
|
311
311
|
sqlConvertFromCivilTime(expr, timezone, _destTypeDef) {
|
|
312
312
|
// Convert from local timezone back to UTC
|
|
313
|
-
return `TO_UTC_TIMESTAMP(${expr},
|
|
313
|
+
return `TO_UTC_TIMESTAMP(${expr}, ${this.sqlTimezoneLiteral(timezone)})`;
|
|
314
314
|
}
|
|
315
315
|
sqlTruncate(expr, unit, _typeDef, _inCivilTime, _timezone) {
|
|
316
316
|
// Databricks DATE_TRUNC starts weeks on Monday; Malloy wants Sunday.
|
|
@@ -342,7 +342,7 @@ class DatabricksDialect extends dialect_1.Dialect {
|
|
|
342
342
|
if (malloy_types_1.TD.isTimestamp(te.e.typeDef)) {
|
|
343
343
|
const tz = (0, dialect_1.qtz)(qi);
|
|
344
344
|
if (tz) {
|
|
345
|
-
extractFrom = `FROM_UTC_TIMESTAMP(${extractFrom},
|
|
345
|
+
extractFrom = `FROM_UTC_TIMESTAMP(${extractFrom}, ${this.sqlTimezoneLiteral(tz)})`;
|
|
346
346
|
}
|
|
347
347
|
}
|
|
348
348
|
if (extractionMap[te.units]) {
|
|
@@ -356,10 +356,10 @@ class DatabricksDialect extends dialect_1.Dialect {
|
|
|
356
356
|
const { op, srcTypeDef, dstTypeDef, dstSQLType } = this.sqlCastPrep(cast);
|
|
357
357
|
const tz = (0, dialect_1.qtz)(qi);
|
|
358
358
|
if (op === 'timestamp::date' && tz) {
|
|
359
|
-
return `CAST(FROM_UTC_TIMESTAMP(${srcSQL},
|
|
359
|
+
return `CAST(FROM_UTC_TIMESTAMP(${srcSQL}, ${this.sqlTimezoneLiteral(tz)}) AS DATE)`;
|
|
360
360
|
}
|
|
361
361
|
else if (op === 'date::timestamp' && tz) {
|
|
362
|
-
return `TO_UTC_TIMESTAMP(CAST(${srcSQL} AS TIMESTAMP_NTZ),
|
|
362
|
+
return `TO_UTC_TIMESTAMP(CAST(${srcSQL} AS TIMESTAMP_NTZ), ${this.sqlTimezoneLiteral(tz)})`;
|
|
363
363
|
}
|
|
364
364
|
if (!malloy_types_1.TD.eq(srcTypeDef, dstTypeDef)) {
|
|
365
365
|
if (cast.safe) {
|
|
@@ -381,7 +381,7 @@ class DatabricksDialect extends dialect_1.Dialect {
|
|
|
381
381
|
sqlTimestampLiteral(qi, literal, timezone) {
|
|
382
382
|
const tz = timezone || (0, dialect_1.qtz)(qi);
|
|
383
383
|
if (tz) {
|
|
384
|
-
return `TO_UTC_TIMESTAMP(TIMESTAMP_NTZ '${literal}',
|
|
384
|
+
return `TO_UTC_TIMESTAMP(TIMESTAMP_NTZ '${literal}', ${this.sqlTimezoneLiteral(tz)})`;
|
|
385
385
|
}
|
|
386
386
|
return `TIMESTAMP '${literal}'`;
|
|
387
387
|
}
|
|
@@ -1,11 +1,20 @@
|
|
|
1
1
|
import type { Expr, Sampling, AtomicTypeDef, MeasureTimeExpr, TimeExtractExpr, TypecastExpr, RegexMatchExpr, TimeLiteralExpr, RecordLiteralNode, ArrayLiteralNode, BasicAtomicTypeDef, OrderBy, TimestampUnit, ATimestampTypeDef, TimeExpr, TemporalFieldType } from '../model/malloy_types';
|
|
2
2
|
import type { DialectFunctionOverloadDef } from './functions';
|
|
3
3
|
import type { ValidateTablePathResult } from './table-path';
|
|
4
|
+
/**
|
|
5
|
+
* One field of a struct, for the dialect methods which emit SQL over it.
|
|
6
|
+
*
|
|
7
|
+
* `typeDef` is the Malloy type, for the dialects which have to declare a SQL
|
|
8
|
+
* type for the field -- the ROW and STRUCT types they build. `sqlExpression`
|
|
9
|
+
* is SQL producing the value, valid in the scope the consumer emits into.
|
|
10
|
+
* `rawName` is the field's name with no quoting: a consumer needing it as an
|
|
11
|
+
* identifier quotes it itself, because only the consumer knows whether it
|
|
12
|
+
* wants an identifier or a string literal holding the name.
|
|
13
|
+
*/
|
|
4
14
|
interface DialectField {
|
|
5
15
|
typeDef: AtomicTypeDef;
|
|
6
16
|
sqlExpression: string;
|
|
7
17
|
rawName: string;
|
|
8
|
-
sqlOutputName: string;
|
|
9
18
|
}
|
|
10
19
|
export type DialectFieldList = DialectField[];
|
|
11
20
|
/**
|
|
@@ -74,6 +83,7 @@ export declare function qtz(qi: QueryInfo): string | undefined;
|
|
|
74
83
|
*/
|
|
75
84
|
export declare function turtleGroupSetCondition(groupSet: number | undefined, filterSQL: string | undefined): string | undefined;
|
|
76
85
|
export type OrderByClauseType = 'output_name' | 'ordinal' | 'expression';
|
|
86
|
+
export type GroupByClauseType = 'ordinal' | 'expression';
|
|
77
87
|
export type OrderByRequest = 'query' | 'turtle' | 'analytical';
|
|
78
88
|
export type BooleanTypeSupport = 'supported' | 'simulated' | 'none';
|
|
79
89
|
/**
|
|
@@ -106,6 +116,14 @@ export declare abstract class Dialect {
|
|
|
106
116
|
abstract supportsSafeCast: boolean;
|
|
107
117
|
abstract supportsNesting: boolean;
|
|
108
118
|
abstract experimental: boolean;
|
|
119
|
+
/**
|
|
120
|
+
* What the compiler consults, not `experimental` itself. The test harness
|
|
121
|
+
* calls `acceptExperimental()` on the dialect under test so the shared
|
|
122
|
+
* suites can run on an experimental dialect without every test enabling it.
|
|
123
|
+
*/
|
|
124
|
+
private experimentalAccepted;
|
|
125
|
+
isExperimental(): boolean;
|
|
126
|
+
acceptExperimental(): void;
|
|
109
127
|
cantPartitionWindowFunctionsOnExpressions: boolean;
|
|
110
128
|
sqlLateralJoinBag(_expressions: LateralJoinExpression[]): string;
|
|
111
129
|
supportsPipelinesInViews: boolean;
|
|
@@ -114,6 +132,9 @@ export declare abstract class Dialect {
|
|
|
114
132
|
hasTimestamptz: boolean;
|
|
115
133
|
readsNestedData: boolean;
|
|
116
134
|
orderByClause: OrderByClauseType;
|
|
135
|
+
groupByClause: GroupByClauseType;
|
|
136
|
+
/** The ordering and row limit which end a SELECT stage */
|
|
137
|
+
sqlOrderByLimit(orderTerms: string[], limit: number | undefined): string;
|
|
117
138
|
nullMatchesFunctionSignature: boolean;
|
|
118
139
|
supportsSelectReplace: boolean;
|
|
119
140
|
supportsBigIntPrecision: boolean;
|
|
@@ -131,6 +152,7 @@ export declare abstract class Dialect {
|
|
|
131
152
|
compoundObjectInSchema: boolean;
|
|
132
153
|
booleanType: BooleanTypeSupport;
|
|
133
154
|
likeEscape: boolean;
|
|
155
|
+
likeExtraWildcards: string[];
|
|
134
156
|
/**
|
|
135
157
|
* Mappings from integer value ranges to Malloy number types.
|
|
136
158
|
*
|
|
@@ -188,6 +210,7 @@ export declare abstract class Dialect {
|
|
|
188
210
|
abstract sqlAnyValue(groupSet: number, fieldName: string): string;
|
|
189
211
|
abstract sqlAggregateTurtle(groupSet: number | undefined, fieldList: DialectFieldList, orderBy: CompiledOrderBy[] | undefined, limit?: number, filterSQL?: string): string;
|
|
190
212
|
sqlTurtleOrderByClause(orderBy: CompiledOrderBy[]): string;
|
|
213
|
+
sqlCombineLastStageOrderBy(orderBy: OrderBy[] | undefined, fieldList: DialectFieldList): string;
|
|
191
214
|
abstract sqlAnyValueTurtle(groupSet: number, fieldList: DialectFieldList): string;
|
|
192
215
|
abstract sqlAnyValueLastTurtle(name: string, groupSet: number, sqlName: string): string;
|
|
193
216
|
abstract sqlCoaleseMeasuresInline(groupSet: number, fieldList: DialectFieldList): string;
|
|
@@ -457,6 +480,16 @@ export declare abstract class Dialect {
|
|
|
457
480
|
* the civil time in the specified timezone
|
|
458
481
|
*/
|
|
459
482
|
abstract sqlTimestamptzLiteral(qi: QueryInfo, literal: string, timezone: string): string;
|
|
483
|
+
/**
|
|
484
|
+
* Reject characters that can break SQL literals or
|
|
485
|
+
* be changed by SQL formatting. Timezone-name validity belongs to the
|
|
486
|
+
* database, whose formats may include offsets and POSIX TZ strings.
|
|
487
|
+
* Static so the translator can apply the same rule before dialect resolution.
|
|
488
|
+
*/
|
|
489
|
+
static isSafeTimezoneName(timezone: string): boolean;
|
|
490
|
+
/** Return an unquoted timezone for inclusion in a larger SQL literal. */
|
|
491
|
+
protected checkedTimezoneName(timezone: string): string;
|
|
492
|
+
sqlTimezoneLiteral(timezone: string): string;
|
|
460
493
|
/**
|
|
461
494
|
* Render a Malloy string as a SQL string literal. The escape style is
|
|
462
495
|
* driven by `stringLiteralStyle`; dialects normally do not override
|
|
@@ -483,14 +516,13 @@ export declare abstract class Dialect {
|
|
|
483
516
|
exprToSQL(qi: QueryInfo, df: Expr): string | undefined;
|
|
484
517
|
sqlSumDistinct(_key: string, _value: string, _funcName: string): string;
|
|
485
518
|
sqlAggDistinct(_key: string, _values: string[], _func: (valNames: string[]) => string): string;
|
|
486
|
-
sqlSampleTable(tableSQL: string, sample: Sampling | undefined): string;
|
|
519
|
+
sqlSampleTable(tableSQL: string, sample: Sampling | undefined, _onBaseTable?: boolean): string;
|
|
487
520
|
/**
|
|
488
521
|
* MySQL is NULLs first, all other dialects have a way to make NULLs last.
|
|
489
522
|
* isBaseOrdering is a hack to allow the MySQL dialect to partially implement
|
|
490
523
|
* NULLs last, but should go away once MySQL fully implements NULLs last.
|
|
491
524
|
*/
|
|
492
525
|
sqlOrderBy(orderTerms: string[], _orderFor?: OrderByRequest): string;
|
|
493
|
-
sqlTzStr(qi: QueryInfo): string;
|
|
494
526
|
sqlMakeUnnestKey(key: string, rowKey: string): string;
|
|
495
527
|
sqlStringAggDistinct(distinctKey: string, valueSQL: string, separatorSQL: string): string;
|
|
496
528
|
abstract sqlTypeToMalloyType(sqlType: string): BasicAtomicTypeDef;
|
package/dist/dialect/dialect.js
CHANGED
|
@@ -81,6 +81,12 @@ function turtleGroupSetCondition(groupSet, filterSQL) {
|
|
|
81
81
|
}
|
|
82
82
|
class Dialect {
|
|
83
83
|
constructor() {
|
|
84
|
+
/**
|
|
85
|
+
* What the compiler consults, not `experimental` itself. The test harness
|
|
86
|
+
* calls `acceptExperimental()` on the dialect under test so the shared
|
|
87
|
+
* suites can run on an experimental dialect without every test enabling it.
|
|
88
|
+
*/
|
|
89
|
+
this.experimentalAccepted = false;
|
|
84
90
|
// -- we should add flags with default values from now on so as to not break
|
|
85
91
|
// dialects outside our repository
|
|
86
92
|
//
|
|
@@ -106,6 +112,9 @@ class Dialect {
|
|
|
106
112
|
this.readsNestedData = true;
|
|
107
113
|
// ORDER BY 1 DESC
|
|
108
114
|
this.orderByClause = 'ordinal';
|
|
115
|
+
// GROUP BY 1, or GROUP BY the dimension expressions for an engine whose
|
|
116
|
+
// GROUP BY takes no ordinals
|
|
117
|
+
this.groupByClause = 'ordinal';
|
|
109
118
|
// null will match in a function signature
|
|
110
119
|
this.nullMatchesFunctionSignature = true;
|
|
111
120
|
// support select * replace(...)
|
|
@@ -147,6 +156,8 @@ class Dialect {
|
|
|
147
156
|
this.booleanType = 'supported';
|
|
148
157
|
// Like characters are escaped with ESCAPE clause
|
|
149
158
|
this.likeEscape = true;
|
|
159
|
+
// Characters LIKE reads as wildcards beyond % and _, escaped like them
|
|
160
|
+
this.likeExtraWildcards = [];
|
|
150
161
|
/**
|
|
151
162
|
* Mappings from integer value ranges to Malloy number types.
|
|
152
163
|
*
|
|
@@ -209,6 +220,12 @@ class Dialect {
|
|
|
209
220
|
*/
|
|
210
221
|
this.identifierEscapeStyle = exports.EscapeStyle.Unset;
|
|
211
222
|
}
|
|
223
|
+
isExperimental() {
|
|
224
|
+
return this.experimental && !this.experimentalAccepted;
|
|
225
|
+
}
|
|
226
|
+
acceptExperimental() {
|
|
227
|
+
this.experimentalAccepted = true;
|
|
228
|
+
}
|
|
212
229
|
// Generate the lateral join bag clause for window function partitioning.
|
|
213
230
|
// The expressions are dimension fields that need to be referenced by name
|
|
214
231
|
// in PARTITION BY clauses. Must be overridden by any dialect that sets
|
|
@@ -219,6 +236,14 @@ class Dialect {
|
|
|
219
236
|
}
|
|
220
237
|
throw new Error('Internal error: sqlLateralJoinBag called but cantPartitionWindowFunctionsOnExpressions is false');
|
|
221
238
|
}
|
|
239
|
+
/** The ordering and row limit which end a SELECT stage */
|
|
240
|
+
sqlOrderByLimit(orderTerms, limit) {
|
|
241
|
+
let s = orderTerms.length > 0 ? this.sqlOrderBy(orderTerms, 'query') + '\n' : '';
|
|
242
|
+
if (limit !== undefined) {
|
|
243
|
+
s += `LIMIT ${limit}\n`;
|
|
244
|
+
}
|
|
245
|
+
return s;
|
|
246
|
+
}
|
|
222
247
|
/**
|
|
223
248
|
* Determine the Malloy number type for a numeric literal.
|
|
224
249
|
*/
|
|
@@ -323,6 +348,20 @@ class Dialect {
|
|
|
323
348
|
const terms = orderBy.map(o => ` ${o.field} ${o.dir.toUpperCase()}`);
|
|
324
349
|
return ' ' + this.sqlOrderBy(terms, 'turtle');
|
|
325
350
|
}
|
|
351
|
+
// Format the ordering of the last stage of a nest's pipeline into an ORDER BY
|
|
352
|
+
// clause for the aggregate which packs that stage's rows into an array. An
|
|
353
|
+
// ordinal names an output field by number, counting from one; the field list
|
|
354
|
+
// is a zero-based array.
|
|
355
|
+
sqlCombineLastStageOrderBy(orderBy, fieldList) {
|
|
356
|
+
if (orderBy === undefined || orderBy.length === 0) {
|
|
357
|
+
return '';
|
|
358
|
+
}
|
|
359
|
+
const terms = orderBy.map(o => {
|
|
360
|
+
const name = typeof o.field === 'string' ? o.field : fieldList[o.field - 1].rawName;
|
|
361
|
+
return `${this.sqlQuoteIdentifier(name)} ${o.dir || 'asc'}`;
|
|
362
|
+
});
|
|
363
|
+
return ` ORDER BY ${terms.join(', ')}`;
|
|
364
|
+
}
|
|
326
365
|
sqlFinalStage(_lastStageName, _fields) {
|
|
327
366
|
throw new Error('Dialect has no final Stage but called Anyway');
|
|
328
367
|
}
|
|
@@ -482,6 +521,25 @@ class Dialect {
|
|
|
482
521
|
}
|
|
483
522
|
return sql;
|
|
484
523
|
}
|
|
524
|
+
/**
|
|
525
|
+
* Reject characters that can break SQL literals or
|
|
526
|
+
* be changed by SQL formatting. Timezone-name validity belongs to the
|
|
527
|
+
* database, whose formats may include offsets and POSIX TZ strings.
|
|
528
|
+
* Static so the translator can apply the same rule before dialect resolution.
|
|
529
|
+
*/
|
|
530
|
+
static isSafeTimezoneName(timezone) {
|
|
531
|
+
return !/['"`\\\r\n]/.test(timezone);
|
|
532
|
+
}
|
|
533
|
+
/** Return an unquoted timezone for inclusion in a larger SQL literal. */
|
|
534
|
+
checkedTimezoneName(timezone) {
|
|
535
|
+
if (!Dialect.isSafeTimezoneName(timezone)) {
|
|
536
|
+
throw new Error(`Invalid timezone ${JSON.stringify(timezone)}: quotes, backslashes, and line breaks are not allowed`);
|
|
537
|
+
}
|
|
538
|
+
return timezone;
|
|
539
|
+
}
|
|
540
|
+
sqlTimezoneLiteral(timezone) {
|
|
541
|
+
return `'${this.checkedTimezoneName(timezone)}'`;
|
|
542
|
+
}
|
|
485
543
|
/**
|
|
486
544
|
* Render a Malloy string as a SQL string literal. The escape style is
|
|
487
545
|
* driven by `stringLiteralStyle`; dialects normally do not override
|
|
@@ -590,7 +648,9 @@ class Dialect {
|
|
|
590
648
|
_func) {
|
|
591
649
|
return 'sqlAggDistinct called but not implemented';
|
|
592
650
|
}
|
|
593
|
-
|
|
651
|
+
// onBaseTable: the source is a table the engine can sample in place; a
|
|
652
|
+
// dialect whose sampling reads the table itself ignores a sample elsewhere
|
|
653
|
+
sqlSampleTable(tableSQL, sample, _onBaseTable = true) {
|
|
594
654
|
if (sample !== undefined) {
|
|
595
655
|
throw new Error(`Sampling is not supported on dialect ${this.name}.`);
|
|
596
656
|
}
|
|
@@ -604,9 +664,6 @@ class Dialect {
|
|
|
604
664
|
sqlOrderBy(orderTerms, _orderFor) {
|
|
605
665
|
return `ORDER BY ${orderTerms.join(',')}`;
|
|
606
666
|
}
|
|
607
|
-
sqlTzStr(qi) {
|
|
608
|
-
return `"${qi.queryTimezone}"`;
|
|
609
|
-
}
|
|
610
667
|
sqlMakeUnnestKey(key, rowKey) {
|
|
611
668
|
return this.concat(key, "'x'", rowKey);
|
|
612
669
|
}
|
|
@@ -664,6 +721,11 @@ class Dialect {
|
|
|
664
721
|
escapeActive = false;
|
|
665
722
|
escapeClause = true;
|
|
666
723
|
}
|
|
724
|
+
else if (this.likeEscape && this.likeExtraWildcards.includes(c)) {
|
|
725
|
+
escaped += '^' + c;
|
|
726
|
+
escapeActive = false;
|
|
727
|
+
escapeClause = true;
|
|
728
|
+
}
|
|
667
729
|
else {
|
|
668
730
|
if (escapeActive) {
|
|
669
731
|
if (this.likeEscape) {
|
|
@@ -690,7 +752,7 @@ class Dialect {
|
|
|
690
752
|
*/
|
|
691
753
|
sqlBoolean(bv) {
|
|
692
754
|
if (this.booleanType === 'none') {
|
|
693
|
-
return bv ? '(1=1)' : '(1
|
|
755
|
+
return bv ? '(1=1)' : '(1=0)';
|
|
694
756
|
}
|
|
695
757
|
return bv ? 'true' : 'false';
|
|
696
758
|
}
|
|
@@ -14,6 +14,7 @@ const standardsql_1 = require("./standardsql");
|
|
|
14
14
|
const trino_1 = require("./trino");
|
|
15
15
|
const mysql_1 = require("./mysql");
|
|
16
16
|
const databricks_1 = require("./databricks");
|
|
17
|
+
const sqlserver_1 = require("./sqlserver");
|
|
17
18
|
const dialectMap = new Map();
|
|
18
19
|
function getDialect(name) {
|
|
19
20
|
const d = dialectMap.get(name);
|
|
@@ -36,4 +37,5 @@ registerDialect(new trino_1.TrinoDialect());
|
|
|
36
37
|
registerDialect(new trino_1.PrestoDialect());
|
|
37
38
|
registerDialect(new mysql_1.MySQLDialect());
|
|
38
39
|
registerDialect(new databricks_1.DatabricksDialect());
|
|
40
|
+
registerDialect(new sqlserver_1.SQLServerDialect());
|
|
39
41
|
//# sourceMappingURL=dialect_map.js.map
|