@malloydata/malloy 0.0.430 → 0.0.432
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/build_targets.d.ts +6 -1
- package/dist/api/foundation/build_targets.js +6 -2
- package/dist/api/foundation/config.js +23 -1
- package/dist/api/foundation/config_compile.d.ts +12 -1
- package/dist/api/foundation/config_compile.js +36 -25
- package/dist/api/foundation/config_lookup.d.ts +2 -2
- package/dist/api/foundation/config_lookup.js +32 -5
- package/dist/api/foundation/core.d.ts +5 -1
- package/dist/api/foundation/core.js +6 -2
- package/dist/api/foundation/runtime.js +3 -1
- package/dist/connection/registry.d.ts +43 -12
- package/dist/connection/registry.js +0 -18
- package/dist/connection/types.d.ts +7 -1
- package/dist/model/index.d.ts +2 -1
- package/dist/model/index.js +2 -1
- package/dist/model/query_query.d.ts +7 -0
- package/dist/model/query_query.js +26 -36
- package/dist/model/sql_compiled.d.ts +40 -1
- package/dist/model/sql_compiled.js +63 -25
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +4 -4
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { BuildIdOptions } from '../../model';
|
|
1
2
|
import type { LogMessage } from '../../lang';
|
|
2
3
|
import type { PersistNode } from '../../model/persist_utils';
|
|
3
4
|
import type { Model, PersistSource } from './core';
|
|
@@ -46,5 +47,9 @@ export declare function resolvePersistWalk(model: Model, tagParseLog: LogMessage
|
|
|
46
47
|
*
|
|
47
48
|
* @param walk A resolved walk from {@link resolvePersistWalk}, in order
|
|
48
49
|
* @param connectionDigests One digest per connection named by a persist source
|
|
50
|
+
* @param buildIdOpts What the BuildID's SQL is compiled under. The compiler
|
|
51
|
+
* recomputes the same key at serve time through `buildIdOptions()`; supply a
|
|
52
|
+
* different `virtualMap` here and the table is built under a key nothing
|
|
53
|
+
* looks up.
|
|
49
54
|
*/
|
|
50
|
-
export declare function mkBuildTargets(walk: ResolvedNode[], connectionDigests: Record<string, string
|
|
55
|
+
export declare function mkBuildTargets(walk: ResolvedNode[], connectionDigests: Record<string, string>, buildIdOpts: BuildIdOptions): ConnectionBuild[];
|
|
@@ -75,8 +75,12 @@ function addUnique(into, seen, keys) {
|
|
|
75
75
|
*
|
|
76
76
|
* @param walk A resolved walk from {@link resolvePersistWalk}, in order
|
|
77
77
|
* @param connectionDigests One digest per connection named by a persist source
|
|
78
|
+
* @param buildIdOpts What the BuildID's SQL is compiled under. The compiler
|
|
79
|
+
* recomputes the same key at serve time through `buildIdOptions()`; supply a
|
|
80
|
+
* different `virtualMap` here and the table is built under a key nothing
|
|
81
|
+
* looks up.
|
|
78
82
|
*/
|
|
79
|
-
function mkBuildTargets(walk, connectionDigests) {
|
|
83
|
+
function mkBuildTargets(walk, connectionDigests, buildIdOpts) {
|
|
80
84
|
var _a;
|
|
81
85
|
const targets = new Map();
|
|
82
86
|
// What each source hands to whoever referenced it: its own target if it is
|
|
@@ -101,7 +105,7 @@ function mkBuildTargets(walk, connectionDigests) {
|
|
|
101
105
|
`BuildID of '${node.sourceID}'. Supply a digest for every connection ` +
|
|
102
106
|
'named by a persist source.');
|
|
103
107
|
}
|
|
104
|
-
const sql = source.getSQL();
|
|
108
|
+
const sql = source.getSQL(buildIdOpts);
|
|
105
109
|
const buildId = source.makeBuildId(digest, sql);
|
|
106
110
|
const key = targetKey(connectionName, buildId);
|
|
107
111
|
let target = targets.get(key);
|
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
7
|
exports.MalloyConfig = exports.Manifest = void 0;
|
|
8
8
|
exports.isBuildManifestEntry = isBuildManifestEntry;
|
|
9
|
+
const registry_1 = require("../../connection/registry");
|
|
9
10
|
const config_compile_1 = require("./config_compile");
|
|
10
11
|
const config_resolve_1 = require("./config_resolve");
|
|
11
12
|
const config_lookup_1 = require("./config_lookup");
|
|
@@ -165,7 +166,7 @@ class MalloyConfig {
|
|
|
165
166
|
// it happens async at `lookupConnection` time, so overlays that touch
|
|
166
167
|
// IO (secret stores, session reads) have a natural async seam.
|
|
167
168
|
const prepared = (0, config_resolve_1.prepareConfig)(compiled.compiled, mergedOverlays, log);
|
|
168
|
-
this._managedLookup = (0, config_lookup_1.buildManagedLookup)(prepared.compiledConnections, mergedOverlays, log);
|
|
169
|
+
this._managedLookup = (0, config_lookup_1.buildManagedLookup)(prepared.compiledConnections, rawConnectionEntries(pojo), mergedOverlays, log);
|
|
169
170
|
this._connections = this._managedLookup;
|
|
170
171
|
this._overlays = mergedOverlays;
|
|
171
172
|
this.virtualMap = toVirtualMap(prepared.virtualMap, log);
|
|
@@ -476,6 +477,27 @@ function toVirtualMap(raw, log) {
|
|
|
476
477
|
function isRecord(value) {
|
|
477
478
|
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
478
479
|
}
|
|
480
|
+
/**
|
|
481
|
+
* The connection entries exactly as authored, kept alongside the compiled tree
|
|
482
|
+
* and handed to each factory as `rawConfigData`. Compilation replaces an
|
|
483
|
+
* overlay reference with a `ConfigReference` node and resolution replaces that
|
|
484
|
+
* with the value, so this is the only place a connector can still see *which*
|
|
485
|
+
* overlay a value came from — which is what BigQuery's `getDigest` needs to
|
|
486
|
+
* tell two impersonated identities apart.
|
|
487
|
+
*
|
|
488
|
+
* No validation here beyond the shape: entries the compiler rejected are
|
|
489
|
+
* absent from `compiledConnections` and so are never looked up.
|
|
490
|
+
*/
|
|
491
|
+
function rawConnectionEntries(pojo) {
|
|
492
|
+
const entries = {};
|
|
493
|
+
if (!isRecord(pojo) || !isRecord(pojo['connections']))
|
|
494
|
+
return entries;
|
|
495
|
+
for (const [name, entry] of Object.entries(pojo['connections'])) {
|
|
496
|
+
if ((0, registry_1.isConnectionConfigEntry)(entry))
|
|
497
|
+
entries[name] = entry;
|
|
498
|
+
}
|
|
499
|
+
return entries;
|
|
500
|
+
}
|
|
479
501
|
function isBuildManifestEntry(value) {
|
|
480
502
|
return isRecord(value) && typeof value['tableName'] === 'string';
|
|
481
503
|
}
|
|
@@ -3,7 +3,7 @@ import type { LogMessage } from '../../lang/parse-log';
|
|
|
3
3
|
* A compiled config node. The compiler produces a tree of these; the resolver
|
|
4
4
|
* walks the tree against a ConfigOverlays dict and produces a plain POJO.
|
|
5
5
|
*/
|
|
6
|
-
export type ConfigNode = ConfigDict | ConfigLiteral | ConfigReference;
|
|
6
|
+
export type ConfigNode = ConfigDict | ConfigLiteral | ConfigReference | ConfigInvalid;
|
|
7
7
|
export interface ConfigDict {
|
|
8
8
|
kind: 'dict';
|
|
9
9
|
entries: Record<string, ConfigNode>;
|
|
@@ -23,6 +23,17 @@ export interface ConfigReference {
|
|
|
23
23
|
/** Path into the overlay. */
|
|
24
24
|
path: string[];
|
|
25
25
|
}
|
|
26
|
+
/**
|
|
27
|
+
* A property the user wrote that can never produce a value — it failed its
|
|
28
|
+
* type check. Compilation records it rather than dropping it so that a
|
|
29
|
+
* `mustHaveValue` property can tell "the user wrote something unusable" apart
|
|
30
|
+
* from "the user wrote nothing." It resolves to `undefined` like any other
|
|
31
|
+
* failure; the difference is only that the key is still visible in the
|
|
32
|
+
* compiled tree.
|
|
33
|
+
*/
|
|
34
|
+
export interface ConfigInvalid {
|
|
35
|
+
kind: 'invalid';
|
|
36
|
+
}
|
|
26
37
|
export type SectionCompiler = (value: unknown, log: LogMessage[]) => ConfigNode | undefined;
|
|
27
38
|
/**
|
|
28
39
|
* Compile a POJO into a typed dictionary tree, collecting validation warnings.
|
|
@@ -110,34 +110,49 @@ function compileConnectionEntry(prefix, typeName, rawEntry, log) {
|
|
|
110
110
|
return entry;
|
|
111
111
|
}
|
|
112
112
|
/**
|
|
113
|
-
* Compile a single connection property value.
|
|
114
|
-
*
|
|
115
|
-
*
|
|
116
|
-
*
|
|
117
|
-
*
|
|
113
|
+
* Compile a single connection property value.
|
|
114
|
+
*
|
|
115
|
+
* At non-`json` property slots, a single-key object whose value is a string or
|
|
116
|
+
* string[] is recognized as an overlay reference. `json`-typed slots always
|
|
117
|
+
* pass through as literal data — this is the security invariant that prevents
|
|
118
|
+
* reference injection into structured config.
|
|
119
|
+
*
|
|
120
|
+
* A property's `source` overrides that reading in both directions. `'literal'`
|
|
121
|
+
* refuses a reference; `'overlay'` refuses a literal, which is also what lets
|
|
122
|
+
* a reference be recognized in a slot that would otherwise hold a dictionary.
|
|
118
123
|
*/
|
|
119
124
|
function compileConnectionProperty(path, propDef, value, log) {
|
|
120
|
-
if (value === undefined || value === null)
|
|
121
|
-
return undefined;
|
|
122
|
-
if (propDef.type === 'json') {
|
|
123
|
-
return { kind: 'value', value };
|
|
125
|
+
if (value === undefined || value === null) {
|
|
126
|
+
return propDef.mustHaveValue ? { kind: 'invalid' } : undefined;
|
|
124
127
|
}
|
|
125
128
|
const ref = asReferenceShape(value);
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
129
|
+
switch (propDef.source) {
|
|
130
|
+
case 'overlay':
|
|
131
|
+
if (ref === undefined) {
|
|
132
|
+
log.push(makeWarning(path, 'must name an overlay, as in {env: "NAME"}; a value written here ' +
|
|
133
|
+
'directly is not allowed'));
|
|
134
|
+
return { kind: 'invalid' };
|
|
135
|
+
}
|
|
136
|
+
return ref;
|
|
137
|
+
case 'literal':
|
|
138
|
+
if (ref !== undefined) {
|
|
139
|
+
log.push(makeWarning(path, 'must be written here directly and cannot name an overlay'));
|
|
140
|
+
return { kind: 'invalid' };
|
|
141
|
+
}
|
|
142
|
+
break;
|
|
143
|
+
default:
|
|
144
|
+
if (propDef.type === 'json') {
|
|
145
|
+
return { kind: 'value', value };
|
|
146
|
+
}
|
|
147
|
+
if (ref !== undefined) {
|
|
148
|
+
return ref;
|
|
149
|
+
}
|
|
150
|
+
break;
|
|
132
151
|
}
|
|
133
152
|
const typeError = checkValueType(value, propDef.type);
|
|
134
153
|
if (typeError) {
|
|
135
|
-
if (propDef.requireLiteralString) {
|
|
136
|
-
log.push(makeWarning(path, `must be a literal string, got ${describeConfigValue(value)}`));
|
|
137
|
-
return { kind: 'value', value };
|
|
138
|
-
}
|
|
139
154
|
log.push(makeWarning(path, `${typeError} (expected ${propDef.type})`));
|
|
140
|
-
return undefined;
|
|
155
|
+
return propDef.mustHaveValue ? { kind: 'invalid' } : undefined;
|
|
141
156
|
}
|
|
142
157
|
return { kind: 'value', value };
|
|
143
158
|
}
|
|
@@ -221,11 +236,6 @@ function makeWarning(path, message) {
|
|
|
221
236
|
code: 'config-validation',
|
|
222
237
|
};
|
|
223
238
|
}
|
|
224
|
-
function describeConfigValue(value) {
|
|
225
|
-
if (Array.isArray(value))
|
|
226
|
-
return 'array';
|
|
227
|
-
return typeof value;
|
|
228
|
-
}
|
|
229
239
|
function checkValueType(value, expectedType) {
|
|
230
240
|
switch (expectedType) {
|
|
231
241
|
case 'number':
|
|
@@ -245,6 +255,7 @@ function checkValueType(value, expectedType) {
|
|
|
245
255
|
return `should be a string, got ${typeof value}`;
|
|
246
256
|
break;
|
|
247
257
|
case 'json':
|
|
258
|
+
case 'opaque':
|
|
248
259
|
break;
|
|
249
260
|
}
|
|
250
261
|
return undefined;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { LogMessage } from '../../lang/parse-log';
|
|
2
|
-
import type { ManagedConnectionLookup } from '../../connection/registry';
|
|
2
|
+
import type { ConnectionConfigEntry, ManagedConnectionLookup } from '../../connection/registry';
|
|
3
3
|
import type { ConfigDict } from './config_compile';
|
|
4
4
|
import type { ConfigOverlays } from './config_overlays';
|
|
5
5
|
/**
|
|
@@ -28,4 +28,4 @@ import type { ConfigOverlays } from './config_overlays';
|
|
|
28
28
|
* intentional consequence of deferred resolution: we don't pay for
|
|
29
29
|
* warnings on connections nobody asks about.
|
|
30
30
|
*/
|
|
31
|
-
export declare function buildManagedLookup(compiledConnections: Record<string, ConfigDict>, overlays: ConfigOverlays, log: LogMessage[]): ManagedConnectionLookup;
|
|
31
|
+
export declare function buildManagedLookup(compiledConnections: Record<string, ConfigDict>, rawConnections: Record<string, ConnectionConfigEntry>, overlays: ConfigOverlays, log: LogMessage[]): ManagedConnectionLookup;
|
|
@@ -32,7 +32,7 @@ const registry_1 = require("../../connection/registry");
|
|
|
32
32
|
* intentional consequence of deferred resolution: we don't pay for
|
|
33
33
|
* warnings on connections nobody asks about.
|
|
34
34
|
*/
|
|
35
|
-
function buildManagedLookup(compiledConnections, overlays, log) {
|
|
35
|
+
function buildManagedLookup(compiledConnections, rawConnections, overlays, log) {
|
|
36
36
|
const entries = Object.entries(compiledConnections);
|
|
37
37
|
const firstConnectionName = entries.length > 0 ? entries[0][0] : undefined;
|
|
38
38
|
const cache = new Map();
|
|
@@ -51,7 +51,7 @@ function buildManagedLookup(compiledConnections, overlays, log) {
|
|
|
51
51
|
if (!compiledEntry) {
|
|
52
52
|
throw new Error(`No connection named "${connectionName}" found in config`);
|
|
53
53
|
}
|
|
54
|
-
const resolved = await resolveCompiledEntry(compiledEntry, overlays, log);
|
|
54
|
+
const resolved = await resolveCompiledEntry(connectionName, compiledEntry, overlays, log);
|
|
55
55
|
// compileConnections guarantees `is` is present and a string-valued
|
|
56
56
|
// literal node — resolveCompiledEntry preserves it. Defensive check
|
|
57
57
|
// in case a compiler bug sneaks through.
|
|
@@ -71,8 +71,7 @@ function buildManagedLookup(compiledConnections, overlays, log) {
|
|
|
71
71
|
connConfig[key] = value;
|
|
72
72
|
}
|
|
73
73
|
}
|
|
74
|
-
|
|
75
|
-
const connection = await typeDef.factory(connConfig);
|
|
74
|
+
const connection = await typeDef.factory(connConfig, rawConnections[connectionName]);
|
|
76
75
|
cache.set(connectionName, connection);
|
|
77
76
|
return connection;
|
|
78
77
|
},
|
|
@@ -97,7 +96,7 @@ function buildManagedLookup(compiledConnections, overlays, log) {
|
|
|
97
96
|
* followed by property-default application. Returns the plain POJO that
|
|
98
97
|
* gets handed to the factory.
|
|
99
98
|
*/
|
|
100
|
-
async function resolveCompiledEntry(entry, overlays, log) {
|
|
99
|
+
async function resolveCompiledEntry(connectionName, entry, overlays, log) {
|
|
101
100
|
const resolved = await resolveNode(entry, overlays, log);
|
|
102
101
|
// resolveNode returns `unknown`. The compileConnections pipeline
|
|
103
102
|
// guarantees every connection entry is an object dict with `is` set to
|
|
@@ -106,9 +105,35 @@ async function resolveCompiledEntry(entry, overlays, log) {
|
|
|
106
105
|
if (!(0, registry_1.isConnectionConfigEntry)(resolved)) {
|
|
107
106
|
throw new Error('Connection entry did not resolve to a valid {is: string, ...} dict');
|
|
108
107
|
}
|
|
108
|
+
// Before defaults, so that a declared default can't quietly satisfy a
|
|
109
|
+
// promise the user's own value failed to keep.
|
|
110
|
+
requireAuthoredValues(connectionName, entry, resolved);
|
|
109
111
|
await applyPropertyDefaults(resolved, overlays);
|
|
110
112
|
return resolved;
|
|
111
113
|
}
|
|
114
|
+
/**
|
|
115
|
+
* Enforce `mustHaveValue`: a property the user wrote must survive resolution.
|
|
116
|
+
*
|
|
117
|
+
* The compiled entry is the record of what was authored — a reference that
|
|
118
|
+
* resolved to nothing, or a value that failed its type check, is gone from
|
|
119
|
+
* `resolved` but still present here. Absence from both is fine; that's the
|
|
120
|
+
* user declining to set the property, which is what `optional` already means.
|
|
121
|
+
*/
|
|
122
|
+
function requireAuthoredValues(connectionName, entry, resolved) {
|
|
123
|
+
var _a;
|
|
124
|
+
for (const prop of (_a = (0, registry_1.getConnectionProperties)(resolved.is)) !== null && _a !== void 0 ? _a : []) {
|
|
125
|
+
if (!prop.mustHaveValue)
|
|
126
|
+
continue;
|
|
127
|
+
if (entry.entries[prop.name] === undefined)
|
|
128
|
+
continue;
|
|
129
|
+
if (resolved[prop.name] !== undefined)
|
|
130
|
+
continue;
|
|
131
|
+
throw new Error(`Connection "${connectionName}" sets "${prop.name}", but no value ` +
|
|
132
|
+
'arrived — an overlay reference that did not resolve, or a value that ' +
|
|
133
|
+
'was rejected. Omitting the property is allowed; this one cannot fall ' +
|
|
134
|
+
'back silently.');
|
|
135
|
+
}
|
|
136
|
+
}
|
|
112
137
|
/**
|
|
113
138
|
* Walk a node, awaiting any overlay calls. References that fail to resolve
|
|
114
139
|
* return `undefined`; the parent dict walker drops the corresponding
|
|
@@ -119,6 +144,8 @@ async function resolveNode(node, overlays, log) {
|
|
|
119
144
|
switch (node.kind) {
|
|
120
145
|
case 'value':
|
|
121
146
|
return node.value;
|
|
147
|
+
case 'invalid':
|
|
148
|
+
return undefined;
|
|
122
149
|
case 'reference':
|
|
123
150
|
return resolveReference(node, overlays, log);
|
|
124
151
|
case 'dict': {
|
|
@@ -611,7 +611,11 @@ export declare class PersistSource implements Taggable {
|
|
|
611
611
|
* For sql_select sources, returns the SQL string (with segment expansion).
|
|
612
612
|
* For query_source sources, compiles the inner query to SQL.
|
|
613
613
|
*
|
|
614
|
-
* @param options - Compile options
|
|
614
|
+
* @param options - Compile options. `buildManifest` and `connectionDigests`
|
|
615
|
+
* substitute already-built dependencies, giving the SQL to execute.
|
|
616
|
+
* Anything that changes the SQL without them — `virtualMap` — must match
|
|
617
|
+
* what the compiler will use, or the table is built under a key nothing
|
|
618
|
+
* looks up.
|
|
615
619
|
* @return The SQL string for this source.
|
|
616
620
|
*/
|
|
617
621
|
getSQL(options?: CompileQueryOptions): string;
|
|
@@ -1357,7 +1357,11 @@ class PersistSource {
|
|
|
1357
1357
|
* For sql_select sources, returns the SQL string (with segment expansion).
|
|
1358
1358
|
* For query_source sources, compiles the inner query to SQL.
|
|
1359
1359
|
*
|
|
1360
|
-
* @param options - Compile options
|
|
1360
|
+
* @param options - Compile options. `buildManifest` and `connectionDigests`
|
|
1361
|
+
* substitute already-built dependencies, giving the SQL to execute.
|
|
1362
|
+
* Anything that changes the SQL without them — `virtualMap` — must match
|
|
1363
|
+
* what the compiler will use, or the table is built under a key nothing
|
|
1364
|
+
* looks up.
|
|
1361
1365
|
* @return The SQL string for this source.
|
|
1362
1366
|
*/
|
|
1363
1367
|
getSQL(options) {
|
|
@@ -1365,7 +1369,7 @@ class PersistSource {
|
|
|
1365
1369
|
const queryModel = this.model.queryModel;
|
|
1366
1370
|
// Compile with finalize=false so this SQL is the bare source SELECT.
|
|
1367
1371
|
// The build-time key (makeBuildId over this SQL) must equal the serve-time
|
|
1368
|
-
// manifest lookup key, which
|
|
1372
|
+
// manifest lookup key, which persistedTableFor recomputes from the same
|
|
1369
1373
|
// unfinalized SELECT; finalizing would diverge the two on dialects with a
|
|
1370
1374
|
// final stage (Postgres) and mis-materialize the table.
|
|
1371
1375
|
if (sd.type === 'sql_select') {
|
|
@@ -511,7 +511,9 @@ class Runtime {
|
|
|
511
511
|
}
|
|
512
512
|
}
|
|
513
513
|
return {
|
|
514
|
-
connections: (0, build_targets_1.mkBuildTargets)(walk, connectionDigests
|
|
514
|
+
connections: (0, build_targets_1.mkBuildTargets)(walk, connectionDigests, {
|
|
515
|
+
virtualMap: this.virtualMap,
|
|
516
|
+
}),
|
|
515
517
|
tagParseLog,
|
|
516
518
|
};
|
|
517
519
|
}
|
|
@@ -1,12 +1,29 @@
|
|
|
1
1
|
import type { Connection, ConnectionConfig, LookupConnection } from './types';
|
|
2
2
|
/**
|
|
3
3
|
* A factory function that creates a Connection from a config object.
|
|
4
|
+
*
|
|
5
|
+
* `rawConfigData` is the connection's entry exactly as the user wrote it,
|
|
6
|
+
* before overlay references were resolved — so an overlay-supplied property
|
|
7
|
+
* still reads as `{tenantAuth: "acme"}` rather than as the value it produced.
|
|
8
|
+
* A connector needs it when a resolved value has no stable identity of its own
|
|
9
|
+
* but the connection's digest depends on which one it is; BigQuery's
|
|
10
|
+
* `authClient` is the case that exists today. It is undefined for hosts that
|
|
11
|
+
* build connections from an already-resolved config.
|
|
12
|
+
*
|
|
13
|
+
* Being unresolved, it is the *non-secret* twin of `config`: it holds
|
|
14
|
+
* `{env: "PGPASSWORD"}` where `config` holds the password.
|
|
4
15
|
*/
|
|
5
|
-
export type ConnectionTypeFactory = (config: ConnectionConfig) => Promise<Connection>;
|
|
16
|
+
export type ConnectionTypeFactory = (config: ConnectionConfig, rawConfigData?: ConnectionConfigEntry) => Promise<Connection>;
|
|
6
17
|
/**
|
|
7
18
|
* The type of a connection property value.
|
|
8
19
|
*/
|
|
9
|
-
export type ConnectionPropertyType = 'string' | 'number' | 'boolean' | 'password' | 'secret' | 'file' | 'json' | 'text'
|
|
20
|
+
export type ConnectionPropertyType = 'string' | 'number' | 'boolean' | 'password' | 'secret' | 'file' | 'json' | 'text'
|
|
21
|
+
/**
|
|
22
|
+
* A value with no declared shape that Malloy never inspects. Only reachable
|
|
23
|
+
* through an overlay — pair it with `source: 'overlay'`, since a config file
|
|
24
|
+
* has no way to write a live object down.
|
|
25
|
+
*/
|
|
26
|
+
| 'opaque';
|
|
10
27
|
/**
|
|
11
28
|
* Describes a single configuration property for a connection type.
|
|
12
29
|
*
|
|
@@ -35,12 +52,31 @@ export interface ConnectionPropertyDefinition {
|
|
|
35
52
|
/** For type 'file': extension filters for picker dialogs. */
|
|
36
53
|
fileFilters?: Record<string, string[]>;
|
|
37
54
|
/**
|
|
38
|
-
*
|
|
39
|
-
*
|
|
40
|
-
*
|
|
41
|
-
*
|
|
55
|
+
* Where this property's value is allowed to come from. Unset means either —
|
|
56
|
+
* a literal in the config file, or an overlay reference.
|
|
57
|
+
*
|
|
58
|
+
* `'literal'` refuses overlay references. For a property whose value is only
|
|
59
|
+
* meaningful if it was written down deliberately.
|
|
60
|
+
*
|
|
61
|
+
* `'overlay'` refuses literals: the config file can only *name* an overlay
|
|
62
|
+
* the host registered, never supply the value itself. This is also what
|
|
63
|
+
* makes a reference unambiguous in a slot that would otherwise hold a
|
|
64
|
+
* dictionary — if a literal isn't legal there, a single-key object can only
|
|
65
|
+
* be a reference.
|
|
42
66
|
*/
|
|
43
|
-
|
|
67
|
+
source?: 'literal' | 'overlay';
|
|
68
|
+
/**
|
|
69
|
+
* Writing this property is a promise that it will produce a value. Omitting
|
|
70
|
+
* it is fine; setting it and having nothing survive resolution is an error
|
|
71
|
+
* rather than a silent drop.
|
|
72
|
+
*
|
|
73
|
+
* For properties whose absence triggers a fallback that isn't safe — DuckDB's
|
|
74
|
+
* `securityPolicy`, where absent means the `none` policy, or a credential
|
|
75
|
+
* whose absence means the ambient one. Without this, a misspelled overlay
|
|
76
|
+
* source or an unset environment variable removes the property and the
|
|
77
|
+
* connection is built as if the user had never asked for it.
|
|
78
|
+
*/
|
|
79
|
+
mustHaveValue?: true;
|
|
44
80
|
}
|
|
45
81
|
/**
|
|
46
82
|
* A connection type definition: factory plus property metadata.
|
|
@@ -106,11 +142,6 @@ export declare function getRegisteredConnectionTypes(): string[];
|
|
|
106
142
|
* lookup to hand fully-resolved configs to the right factory.
|
|
107
143
|
*/
|
|
108
144
|
export declare function getConnectionTypeDef(typeName: string): ConnectionTypeDef | undefined;
|
|
109
|
-
/**
|
|
110
|
-
* Enforce registry-level literal-string requirements after overlay resolution
|
|
111
|
-
* and before a connection factory sees the config.
|
|
112
|
-
*/
|
|
113
|
-
export declare function validateConnectionConfigProperties(connectionName: string, typeName: string, config: ConnectionConfig): void;
|
|
114
145
|
/**
|
|
115
146
|
* Parse a JSON config string into a ConnectionsConfig.
|
|
116
147
|
* Entries without a valid `is` field are silently dropped.
|
|
@@ -10,7 +10,6 @@ exports.getConnectionProperties = getConnectionProperties;
|
|
|
10
10
|
exports.getConnectionTypeDisplayName = getConnectionTypeDisplayName;
|
|
11
11
|
exports.getRegisteredConnectionTypes = getRegisteredConnectionTypes;
|
|
12
12
|
exports.getConnectionTypeDef = getConnectionTypeDef;
|
|
13
|
-
exports.validateConnectionConfigProperties = validateConnectionConfigProperties;
|
|
14
13
|
exports.readConnectionsConfig = readConnectionsConfig;
|
|
15
14
|
exports.writeConnectionsConfig = writeConnectionsConfig;
|
|
16
15
|
exports.createConnectionsFromConfig = createConnectionsFromConfig;
|
|
@@ -67,22 +66,6 @@ function getRegisteredConnectionTypes() {
|
|
|
67
66
|
function getConnectionTypeDef(typeName) {
|
|
68
67
|
return registry.get(typeName);
|
|
69
68
|
}
|
|
70
|
-
/**
|
|
71
|
-
* Enforce registry-level literal-string requirements after overlay resolution
|
|
72
|
-
* and before a connection factory sees the config.
|
|
73
|
-
*/
|
|
74
|
-
function validateConnectionConfigProperties(connectionName, typeName, config) {
|
|
75
|
-
var _a, _b;
|
|
76
|
-
const props = (_b = (_a = registry.get(typeName)) === null || _a === void 0 ? void 0 : _a.properties) !== null && _b !== void 0 ? _b : [];
|
|
77
|
-
for (const prop of props) {
|
|
78
|
-
if (!prop.requireLiteralString)
|
|
79
|
-
continue;
|
|
80
|
-
const value = config[prop.name];
|
|
81
|
-
if (value !== undefined && typeof value !== 'string') {
|
|
82
|
-
throw new Error(`Connection "${connectionName}" property "${prop.name}" must be a literal string`);
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
69
|
/**
|
|
87
70
|
* Parse a JSON config string into a ConnectionsConfig.
|
|
88
71
|
* Entries without a valid `is` field are silently dropped.
|
|
@@ -144,7 +127,6 @@ function createConnectionsFromConfig(config, onConnectionCreated) {
|
|
|
144
127
|
connConfig[key] = value;
|
|
145
128
|
}
|
|
146
129
|
}
|
|
147
|
-
validateConnectionConfigProperties(connectionName, entry.is, connConfig);
|
|
148
130
|
const connection = await typeDef.factory(connConfig);
|
|
149
131
|
if (onConnectionCreated) {
|
|
150
132
|
onConnectionCreated(connectionName, connection);
|
|
@@ -49,9 +49,15 @@ export interface InfoConnection {
|
|
|
49
49
|
export type ConnectionParameterValue = string | number | boolean | null | Array<ConnectionParameterValue> | {
|
|
50
50
|
[key: string]: ConnectionParameterValue;
|
|
51
51
|
};
|
|
52
|
+
/**
|
|
53
|
+
* A value with no declared shape, supplied by a host overlay rather than
|
|
54
|
+
* written in a config file — an auth client, a session handle, anything live.
|
|
55
|
+
* Malloy never inspects one; it carries it from the overlay to the factory.
|
|
56
|
+
*/
|
|
57
|
+
export type OpaqueConnectionValue = object;
|
|
52
58
|
export interface ConnectionConfig {
|
|
53
59
|
name: string;
|
|
54
|
-
[key: string]: ConnectionParameterValue | undefined;
|
|
60
|
+
[key: string]: ConnectionParameterValue | OpaqueConnectionValue | undefined;
|
|
55
61
|
}
|
|
56
62
|
export interface ConnectionMetadata {
|
|
57
63
|
url?: string;
|
package/dist/model/index.d.ts
CHANGED
|
@@ -7,7 +7,8 @@ export { getResultStructDefForQuery, getResultStructDefForView, } from './query_
|
|
|
7
7
|
export { indent, composeSQLExpr, makeDigest, mkModelDef, mkModelID, pathToKey, typeDefToString, } from './utils';
|
|
8
8
|
export { getModelAnnotations } from './annotation_utils';
|
|
9
9
|
export { constantExprToSQL } from './constant_expression_compiler';
|
|
10
|
-
export { getCompiledSQL } from './sql_compiled';
|
|
10
|
+
export { getCompiledSQL, buildIdOptions } from './sql_compiled';
|
|
11
|
+
export type { BuildIdOptions } from './sql_compiled';
|
|
11
12
|
export { MalloyCompileError } from './malloy_compile_error';
|
|
12
13
|
export { mkSourceID, mkBuildID, mkQuerySourceDef, mkSQLSourceDef, mkTableSourceDef, resolveSourceID, resolveSourceRef, sourceNamespaceReference, registerSource, hasSourceRegistryEntry, } from './source_def_utils';
|
|
13
14
|
export type { NamespaceReference } from './source_def_utils';
|
package/dist/model/index.js
CHANGED
|
@@ -18,7 +18,7 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
|
18
18
|
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
19
19
|
};
|
|
20
20
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
21
|
-
exports.hasSourceRegistryEntry = exports.registerSource = exports.sourceNamespaceReference = exports.resolveSourceRef = exports.resolveSourceID = exports.mkTableSourceDef = exports.mkSQLSourceDef = exports.mkQuerySourceDef = exports.mkBuildID = exports.mkSourceID = exports.MalloyCompileError = exports.getCompiledSQL = exports.constantExprToSQL = exports.getModelAnnotations = exports.typeDefToString = exports.pathToKey = exports.mkModelID = exports.mkModelDef = exports.makeDigest = exports.composeSQLExpr = exports.indent = exports.getResultStructDefForView = exports.getResultStructDefForQuery = exports.QueryModel = exports.QueryQuery = exports.QueryStruct = exports.QueryField = void 0;
|
|
21
|
+
exports.hasSourceRegistryEntry = exports.registerSource = exports.sourceNamespaceReference = exports.resolveSourceRef = exports.resolveSourceID = exports.mkTableSourceDef = exports.mkSQLSourceDef = exports.mkQuerySourceDef = exports.mkBuildID = exports.mkSourceID = exports.MalloyCompileError = exports.buildIdOptions = exports.getCompiledSQL = exports.constantExprToSQL = exports.getModelAnnotations = exports.typeDefToString = exports.pathToKey = exports.mkModelID = exports.mkModelDef = exports.makeDigest = exports.composeSQLExpr = exports.indent = exports.getResultStructDefForView = exports.getResultStructDefForQuery = exports.QueryModel = exports.QueryQuery = exports.QueryStruct = exports.QueryField = void 0;
|
|
22
22
|
__exportStar(require("./malloy_types"), exports);
|
|
23
23
|
const query_node_1 = require("./query_node");
|
|
24
24
|
Object.defineProperty(exports, "QueryField", { enumerable: true, get: function () { return query_node_1.QueryField; } });
|
|
@@ -51,6 +51,7 @@ var constant_expression_compiler_1 = require("./constant_expression_compiler");
|
|
|
51
51
|
Object.defineProperty(exports, "constantExprToSQL", { enumerable: true, get: function () { return constant_expression_compiler_1.constantExprToSQL; } });
|
|
52
52
|
var sql_compiled_1 = require("./sql_compiled");
|
|
53
53
|
Object.defineProperty(exports, "getCompiledSQL", { enumerable: true, get: function () { return sql_compiled_1.getCompiledSQL; } });
|
|
54
|
+
Object.defineProperty(exports, "buildIdOptions", { enumerable: true, get: function () { return sql_compiled_1.buildIdOptions; } });
|
|
54
55
|
var malloy_compile_error_1 = require("./malloy_compile_error");
|
|
55
56
|
Object.defineProperty(exports, "MalloyCompileError", { enumerable: true, get: function () { return malloy_compile_error_1.MalloyCompileError; } });
|
|
56
57
|
var source_def_utils_1 = require("./source_def_utils");
|
|
@@ -6,6 +6,7 @@ import { QueryStruct, QueryField } from './query_node';
|
|
|
6
6
|
import { StageWriter } from './stage_writer';
|
|
7
7
|
import type { FieldInstance } from './field_instance';
|
|
8
8
|
import { FieldInstanceResult, FieldInstanceResultRoot } from './field_instance';
|
|
9
|
+
import type { CompileQueryCallback } from './sql_compiled';
|
|
9
10
|
interface OutputPipelinedSQL {
|
|
10
11
|
sqlFieldName: string;
|
|
11
12
|
pipelineSQL: string;
|
|
@@ -66,6 +67,12 @@ export declare class QueryQuery extends QueryField {
|
|
|
66
67
|
/** returns a fields and primary key of a struct for this query */
|
|
67
68
|
getResultStructDef(resultStruct?: FieldInstanceResult, isRoot?: boolean): QueryResultDef;
|
|
68
69
|
getStructSourceSQL(qs: QueryStruct, stageWriter: StageWriter): string;
|
|
70
|
+
/**
|
|
71
|
+
* A compile callback which writes to its own StageWriter rather than the
|
|
72
|
+
* one this query is building, so the SQL comes back as a string. Both the
|
|
73
|
+
* manifest lookup and inline expansion need a source's SQL in that form.
|
|
74
|
+
*/
|
|
75
|
+
protected isolatedQueryCompiler(): CompileQueryCallback;
|
|
69
76
|
/**
|
|
70
77
|
* Compile a Query into SQL stages. Used by both query_source compilation
|
|
71
78
|
* and getCompiledSQL for interpolated sources.
|
|
@@ -12,7 +12,6 @@ const query_node_1 = require("./query_node");
|
|
|
12
12
|
const stage_writer_1 = require("./stage_writer");
|
|
13
13
|
const field_instance_1 = require("./field_instance");
|
|
14
14
|
const sql_compiled_1 = require("./sql_compiled");
|
|
15
|
-
const source_def_utils_1 = require("./source_def_utils");
|
|
16
15
|
const malloy_compile_error_1 = require("./malloy_compile_error");
|
|
17
16
|
const nest_capability_1 = require("./nest-capability");
|
|
18
17
|
function pathToCol(path) {
|
|
@@ -532,7 +531,7 @@ class QueryQuery extends query_node_1.QueryField {
|
|
|
532
531
|
return outputStruct;
|
|
533
532
|
}
|
|
534
533
|
getStructSourceSQL(qs, stageWriter) {
|
|
535
|
-
var _a, _b, _c, _d;
|
|
534
|
+
var _a, _b, _c, _d, _e;
|
|
536
535
|
switch (qs.structDef.type) {
|
|
537
536
|
case 'table':
|
|
538
537
|
// tablePath is canonical SQL — translator pre-validated.
|
|
@@ -555,38 +554,21 @@ class QueryQuery extends query_node_1.QueryField {
|
|
|
555
554
|
return '{COMPOSITE SOURCE}';
|
|
556
555
|
case 'finalize':
|
|
557
556
|
return qs.structDef.name;
|
|
558
|
-
case 'sql_select':
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
557
|
+
case 'sql_select': {
|
|
558
|
+
// A hit is a table name, which is what a FROM clause wants; the
|
|
559
|
+
// inline form is a SELECT and has to be parenthesized to sit there.
|
|
560
|
+
const tableName = (0, sql_compiled_1.persistedTableFor)(qs.structDef, (_c = qs.prepareResultOptions) !== null && _c !== void 0 ? _c : {}, this.isolatedQueryCompiler());
|
|
561
|
+
if (tableName !== undefined) {
|
|
562
|
+
return tableName;
|
|
563
|
+
}
|
|
564
|
+
return `(${(0, sql_compiled_1.getCompiledSQL)(qs.structDef, (_d = qs.prepareResultOptions) !== null && _d !== void 0 ? _d : {}, this.isolatedQueryCompiler())})`;
|
|
565
|
+
}
|
|
564
566
|
case 'nest_source':
|
|
565
567
|
return qs.structDef.pipeSQL;
|
|
566
568
|
case 'query_source': {
|
|
567
|
-
const
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
const connDigest = (0, malloy_types_1.safeRecordGet)(connectionDigests, qs.structDef.connection);
|
|
571
|
-
if (connDigest) {
|
|
572
|
-
// Compile with empty opts to get manifest-ignorant SQL for BuildID
|
|
573
|
-
const fullRet = this.compileQueryToStages(qs.structDef.query, {}, undefined, false);
|
|
574
|
-
const buildId = (0, source_def_utils_1.mkBuildID)(connDigest, fullRet.sql);
|
|
575
|
-
const entry = buildManifest.entries[buildId];
|
|
576
|
-
if (entry) {
|
|
577
|
-
// Found in manifest - use persisted table.
|
|
578
|
-
// entry.tableName comes from the manifest, assumed canonical.
|
|
579
|
-
return entry.tableName;
|
|
580
|
-
}
|
|
581
|
-
if (buildManifest.strict) {
|
|
582
|
-
const base = `Persist source '${qs.structDef.sourceID}' not found ` +
|
|
583
|
-
`in manifest (buildId: ${buildId}); strict manifest mode ` +
|
|
584
|
-
'forbids fallback to live compilation.';
|
|
585
|
-
throw new malloy_compile_error_1.MalloyCompileError(buildManifest.loadError
|
|
586
|
-
? `${base}\n ${buildManifest.loadError}`
|
|
587
|
-
: base, 'runtime-manifest-strict-miss', qs.structDef.location);
|
|
588
|
-
}
|
|
589
|
-
}
|
|
569
|
+
const tableName = (0, sql_compiled_1.persistedTableFor)(qs.structDef, (_e = qs.prepareResultOptions) !== null && _e !== void 0 ? _e : {}, this.isolatedQueryCompiler());
|
|
570
|
+
if (tableName !== undefined) {
|
|
571
|
+
return tableName;
|
|
590
572
|
}
|
|
591
573
|
// Not in manifest - compile normally
|
|
592
574
|
const ret = this.compileQueryToStages(qs.structDef.query, qs.prepareResultOptions, stageWriter, qs.parent !== undefined);
|
|
@@ -596,6 +578,14 @@ class QueryQuery extends query_node_1.QueryField {
|
|
|
596
578
|
throw new Error(`Cannot create SQL StageWriter from '${(0, malloy_types_1.activeName)(qs.structDef)}' type '${qs.structDef.type}`);
|
|
597
579
|
}
|
|
598
580
|
}
|
|
581
|
+
/**
|
|
582
|
+
* A compile callback which writes to its own StageWriter rather than the
|
|
583
|
+
* one this query is building, so the SQL comes back as a string. Both the
|
|
584
|
+
* manifest lookup and inline expansion need a source's SQL in that form.
|
|
585
|
+
*/
|
|
586
|
+
isolatedQueryCompiler() {
|
|
587
|
+
return (query, opts) => this.compileQueryToStages(query, opts !== null && opts !== void 0 ? opts : {}, undefined, false).sql;
|
|
588
|
+
}
|
|
599
589
|
/**
|
|
600
590
|
* Compile a Query into SQL stages. Used by both query_source compilation
|
|
601
591
|
* and getCompiledSQL for interpolated sources.
|
|
@@ -1993,11 +1983,11 @@ class QueryQueryRaw extends QueryQuery {
|
|
|
1993
1983
|
if (this.parent.structDef.type !== 'sql_select') {
|
|
1994
1984
|
throw new Error('Invalid struct for QueryQueryRaw, currently only supports SQL');
|
|
1995
1985
|
}
|
|
1996
|
-
|
|
1997
|
-
|
|
1998
|
-
|
|
1999
|
-
|
|
2000
|
-
}));
|
|
1986
|
+
// No manifest lookup for the source itself: a raw pipeline's source is
|
|
1987
|
+
// always the `conn.sql(...)` written at the run site, which is unnamed and
|
|
1988
|
+
// so never persistent. Its `%{ }` dependencies do substitute, through
|
|
1989
|
+
// getCompiledSQL.
|
|
1990
|
+
return stageWriter.addStage((0, sql_compiled_1.getCompiledSQL)(this.parent.structDef, (_a = this.parent.prepareResultOptions) !== null && _a !== void 0 ? _a : {}, this.isolatedQueryCompiler()));
|
|
2001
1991
|
}
|
|
2002
1992
|
prepare() {
|
|
2003
1993
|
// Do nothing!
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { PrepareResultOptions, SQLSourceDef, PersistableSourceDef, Query } from './malloy_types';
|
|
1
|
+
import type { PrepareResultOptions, SQLSourceDef, PersistableSourceDef, Query, VirtualMap } from './malloy_types';
|
|
2
2
|
/**
|
|
3
3
|
* Callback type for compiling a Query to SQL.
|
|
4
4
|
* opts is optional - omit for "full SQL" (BuildID computation),
|
|
@@ -16,6 +16,45 @@ export type CompileQueryCallback = (query: Query, opts?: PrepareResultOptions) =
|
|
|
16
16
|
* @param compileQuery Callback to compile a Query to SQL
|
|
17
17
|
*/
|
|
18
18
|
export declare function getCompiledSQL(src: SQLSourceDef, opts: PrepareResultOptions, compileQuery: CompileQueryCallback): string;
|
|
19
|
+
/**
|
|
20
|
+
* The options a BuildID's SQL is compiled under — everything the builder and
|
|
21
|
+
* the compiler must both know, and nothing else.
|
|
22
|
+
*
|
|
23
|
+
* Narrow on purpose. A BuildID must not depend on what has already been built
|
|
24
|
+
* or build order would change the key, so this type cannot express a manifest;
|
|
25
|
+
* `mkBuildTargets` takes it for the same reason.
|
|
26
|
+
*/
|
|
27
|
+
export interface BuildIdOptions {
|
|
28
|
+
virtualMap?: VirtualMap;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Project compile options down to what a BuildID is computed under.
|
|
32
|
+
*
|
|
33
|
+
* `virtualMap` decides which table a virtual source reads, so it changes the
|
|
34
|
+
* SQL and has to be in the key; the builder supplies its own.
|
|
35
|
+
*
|
|
36
|
+
* `resolvedGivens` is deliberately absent even though it also changes the SQL.
|
|
37
|
+
* The builder has no way to produce one — `Runtime.getBuildTargets` takes no
|
|
38
|
+
* givens and `PersistSource.getSQL` never resolves them — so including it makes
|
|
39
|
+
* the two sides disagree: the compiler folds `$IS_ADMIN` to a literal while the
|
|
40
|
+
* builder re-derives it from the declaration and emits `'admin'='admin'`. Both
|
|
41
|
+
* sides therefore compile givens the same way, by not resolving them. The
|
|
42
|
+
* consequence — a source whose SQL depends on a supplied given cannot be
|
|
43
|
+
* persisted, and says so badly — is #3041.
|
|
44
|
+
*/
|
|
45
|
+
export declare function buildIdOptions(opts: PrepareResultOptions): BuildIdOptions;
|
|
46
|
+
/**
|
|
47
|
+
* Ask the manifest what table backs a persistable source.
|
|
48
|
+
*
|
|
49
|
+
* Every place the compiler needs SQL for a persistable source goes through
|
|
50
|
+
* here, so the rule is stated once: a source marked persistent is looked up
|
|
51
|
+
* by BuildID, a hit yields the table, a miss under `strict` throws, and
|
|
52
|
+
* anything else falls through to the source's own SQL.
|
|
53
|
+
*
|
|
54
|
+
* @return The table name, canonical SQL as the manifest supplied it, or
|
|
55
|
+
* undefined when the caller should emit the source's own SQL.
|
|
56
|
+
*/
|
|
57
|
+
export declare function persistedTableFor(source: PersistableSourceDef, opts: PrepareResultOptions, compileQuery: CompileQueryCallback): string | undefined;
|
|
19
58
|
/**
|
|
20
59
|
* Get the SQL for a PersistableSourceDef.
|
|
21
60
|
*
|
|
@@ -5,6 +5,8 @@
|
|
|
5
5
|
*/
|
|
6
6
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
7
|
exports.getCompiledSQL = getCompiledSQL;
|
|
8
|
+
exports.buildIdOptions = buildIdOptions;
|
|
9
|
+
exports.persistedTableFor = persistedTableFor;
|
|
8
10
|
exports.getSourceSQL = getSourceSQL;
|
|
9
11
|
const malloy_types_1 = require("./malloy_types");
|
|
10
12
|
const source_def_utils_1 = require("./source_def_utils");
|
|
@@ -47,34 +49,70 @@ function expandSegment(segment, opts, compileQuery) {
|
|
|
47
49
|
return expandQuery(segment, opts, compileQuery);
|
|
48
50
|
}
|
|
49
51
|
/**
|
|
50
|
-
*
|
|
52
|
+
* Project compile options down to what a BuildID is computed under.
|
|
53
|
+
*
|
|
54
|
+
* `virtualMap` decides which table a virtual source reads, so it changes the
|
|
55
|
+
* SQL and has to be in the key; the builder supplies its own.
|
|
56
|
+
*
|
|
57
|
+
* `resolvedGivens` is deliberately absent even though it also changes the SQL.
|
|
58
|
+
* The builder has no way to produce one — `Runtime.getBuildTargets` takes no
|
|
59
|
+
* givens and `PersistSource.getSQL` never resolves them — so including it makes
|
|
60
|
+
* the two sides disagree: the compiler folds `$IS_ADMIN` to a literal while the
|
|
61
|
+
* builder re-derives it from the declaration and emits `'admin'='admin'`. Both
|
|
62
|
+
* sides therefore compile givens the same way, by not resolving them. The
|
|
63
|
+
* consequence — a source whose SQL depends on a supplied given cannot be
|
|
64
|
+
* persisted, and says so badly — is #3041.
|
|
65
|
+
*/
|
|
66
|
+
function buildIdOptions(opts) {
|
|
67
|
+
return { virtualMap: opts.virtualMap };
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Ask the manifest what table backs a persistable source.
|
|
71
|
+
*
|
|
72
|
+
* Every place the compiler needs SQL for a persistable source goes through
|
|
73
|
+
* here, so the rule is stated once: a source marked persistent is looked up
|
|
74
|
+
* by BuildID, a hit yields the table, a miss under `strict` throws, and
|
|
75
|
+
* anything else falls through to the source's own SQL.
|
|
76
|
+
*
|
|
77
|
+
* @return The table name, canonical SQL as the manifest supplied it, or
|
|
78
|
+
* undefined when the caller should emit the source's own SQL.
|
|
79
|
+
*/
|
|
80
|
+
function persistedTableFor(source, opts, compileQuery) {
|
|
81
|
+
const { buildManifest, connectionDigests } = opts;
|
|
82
|
+
if (!buildManifest || !connectionDigests || !source.persistent) {
|
|
83
|
+
return undefined;
|
|
84
|
+
}
|
|
85
|
+
const connDigest = (0, malloy_types_1.safeRecordGet)(connectionDigests, source.connection);
|
|
86
|
+
if (connDigest === undefined) {
|
|
87
|
+
return undefined;
|
|
88
|
+
}
|
|
89
|
+
const buildId = (0, source_def_utils_1.mkBuildID)(connDigest, getSourceSQL(source, compileQuery, buildIdOptions(opts)));
|
|
90
|
+
const entry = buildManifest.entries[buildId];
|
|
91
|
+
if (entry) {
|
|
92
|
+
return entry.tableName;
|
|
93
|
+
}
|
|
94
|
+
if (buildManifest.strict) {
|
|
95
|
+
// `sourceID` is deleted, not inherited, when a source is modified, so it
|
|
96
|
+
// either names this source or is absent. `as` and `extends` survive a
|
|
97
|
+
// modification and would name the base, so neither stands in for it.
|
|
98
|
+
const named = source.sourceID
|
|
99
|
+
? `Persisted source '${source.sourceID}'`
|
|
100
|
+
: 'Persisted source';
|
|
101
|
+
const base = `${named} not found in manifest (buildId: ${buildId}); ` +
|
|
102
|
+
'strict manifest mode forbids fallback to live compilation.';
|
|
103
|
+
throw new malloy_compile_error_1.MalloyCompileError(buildManifest.loadError ? `${base}\n ${buildManifest.loadError}` : base, 'runtime-manifest-strict-miss', source.location);
|
|
104
|
+
}
|
|
105
|
+
return undefined;
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Expand a PersistableSourceDef as a `%{ }` segment.
|
|
51
109
|
* Always returns a subquery form: (SELECT * FROM table) or (inline SQL)
|
|
52
110
|
*/
|
|
53
111
|
function expandPersistableSource(source, opts, compileQuery) {
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
if (
|
|
57
|
-
|
|
58
|
-
if (connDigest) {
|
|
59
|
-
// Get the SQL for this source to compute BuildID (no opts = full SQL)
|
|
60
|
-
const sql = getSourceSQL(source, compileQuery);
|
|
61
|
-
const buildId = (0, source_def_utils_1.mkBuildID)(connDigest, sql);
|
|
62
|
-
const entry = buildManifest.entries[buildId];
|
|
63
|
-
if (entry) {
|
|
64
|
-
// Found in manifest - substitute with subquery from persisted table.
|
|
65
|
-
// entry.tableName is canonical SQL, supplied by the manifest builder.
|
|
66
|
-
return `(SELECT * FROM ${entry.tableName})`;
|
|
67
|
-
}
|
|
68
|
-
// Not in manifest
|
|
69
|
-
if (buildManifest.strict) {
|
|
70
|
-
const base = `Persist source '${source.sourceID}' not found in manifest ` +
|
|
71
|
-
`(buildId: ${buildId}); strict manifest mode forbids fallback ` +
|
|
72
|
-
'to live compilation.';
|
|
73
|
-
throw new malloy_compile_error_1.MalloyCompileError(buildManifest.loadError
|
|
74
|
-
? `${base}\n ${buildManifest.loadError}`
|
|
75
|
-
: base, 'runtime-manifest-strict-miss', source.location);
|
|
76
|
-
}
|
|
77
|
-
}
|
|
112
|
+
// A segment has to be usable as a subquery, so a hit is wrapped.
|
|
113
|
+
const tableName = persistedTableFor(source, opts, compileQuery);
|
|
114
|
+
if (tableName !== undefined) {
|
|
115
|
+
return `(SELECT * FROM ${tableName})`;
|
|
78
116
|
}
|
|
79
117
|
// No manifest or not found - expand inline as subquery
|
|
80
118
|
const sql = getSourceSQL(source, compileQuery, opts);
|
package/dist/version.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const MALLOY_VERSION = "0.0.
|
|
1
|
+
export declare const MALLOY_VERSION = "0.0.432";
|
package/dist/version.js
CHANGED
|
@@ -2,5 +2,5 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.MALLOY_VERSION = void 0;
|
|
4
4
|
// generated with 'generate-version-file' script; do not edit manually
|
|
5
|
-
exports.MALLOY_VERSION = '0.0.
|
|
5
|
+
exports.MALLOY_VERSION = '0.0.432';
|
|
6
6
|
//# sourceMappingURL=version.js.map
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@malloydata/malloy",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.432",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"exports": {
|
|
6
6
|
".": "./dist/index.js",
|
|
@@ -51,9 +51,9 @@
|
|
|
51
51
|
"generate-version-file": "VERSION=$(npm pkg get version --workspaces=false | tr -d \\\")\necho \"// generated with 'generate-version-file' script; do not edit manually\\nexport const MALLOY_VERSION = '$VERSION';\" > src/version.ts"
|
|
52
52
|
},
|
|
53
53
|
"dependencies": {
|
|
54
|
-
"@malloydata/malloy-filter": "0.0.
|
|
55
|
-
"@malloydata/malloy-interfaces": "0.0.
|
|
56
|
-
"@malloydata/malloy-tag": "0.0.
|
|
54
|
+
"@malloydata/malloy-filter": "0.0.432",
|
|
55
|
+
"@malloydata/malloy-interfaces": "0.0.432",
|
|
56
|
+
"@malloydata/malloy-tag": "0.0.432",
|
|
57
57
|
"@noble/hashes": "^1.8.0",
|
|
58
58
|
"antlr4ts": "^0.5.0-alpha.4",
|
|
59
59
|
"assert": "^2.0.0",
|