@malloydata/malloy 0.0.428 → 0.0.429
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 +50 -0
- package/dist/api/foundation/build_targets.js +221 -0
- package/dist/api/foundation/core.d.ts +54 -10
- package/dist/api/foundation/core.js +80 -17
- package/dist/api/foundation/index.d.ts +1 -1
- package/dist/api/foundation/runtime.d.ts +18 -1
- package/dist/api/foundation/runtime.js +35 -0
- package/dist/api/foundation/types.d.ts +93 -6
- package/dist/index.d.ts +1 -1
- package/dist/lang/ast/field-space/dynamic-space.js +20 -3
- package/dist/lang/ast/query-elements/query-arrow.d.ts +1 -1
- package/dist/lang/ast/query-elements/query-arrow.js +10 -4
- package/dist/lang/ast/query-elements/query-base.d.ts +1 -1
- package/dist/lang/ast/query-elements/query-base.js +1 -6
- package/dist/lang/ast/query-elements/query-raw.d.ts +1 -1
- package/dist/lang/ast/query-elements/query-raw.js +4 -2
- package/dist/lang/ast/query-elements/query-reference.d.ts +1 -1
- package/dist/lang/ast/query-elements/query-reference.js +9 -3
- package/dist/lang/ast/query-elements/query-refine.d.ts +1 -1
- package/dist/lang/ast/query-elements/query-refine.js +10 -4
- package/dist/lang/ast/source-elements/query-source.js +1 -1
- package/dist/lang/ast/source-elements/sql-source.js +2 -2
- package/dist/lang/ast/source-query-elements/source-query-element.d.ts +27 -0
- package/dist/lang/ast/source-query-elements/source-query-element.js +33 -0
- package/dist/lang/ast/source-query-elements/sq-arrow.d.ts +2 -0
- package/dist/lang/ast/source-query-elements/sq-arrow.js +12 -6
- package/dist/lang/ast/source-query-elements/sq-refine.d.ts +2 -0
- package/dist/lang/ast/source-query-elements/sq-refine.js +12 -6
- package/dist/lang/ast/source-query-elements/sq-source.d.ts +1 -0
- package/dist/lang/ast/source-query-elements/sq-source.js +6 -3
- package/dist/lang/ast/sql-elements/sql-string.d.ts +1 -1
- package/dist/lang/ast/sql-elements/sql-string.js +25 -5
- package/dist/lang/ast/statements/import-statement.js +7 -10
- package/dist/lang/ast/types/query-element.d.ts +15 -1
- package/dist/lang/parse-log.d.ts +2 -0
- package/dist/lang/test/test-translator.d.ts +17 -0
- package/dist/lang/test/test-translator.js +46 -2
- package/dist/model/persist_utils.d.ts +61 -10
- package/dist/model/persist_utils.js +172 -63
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +4 -4
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import type { LogMessage } from '../../lang';
|
|
2
|
+
import type { PersistNode } from '../../model/persist_utils';
|
|
3
|
+
import type { Model, PersistSource } from './core';
|
|
4
|
+
import type { ConnectionBuild } from './types';
|
|
5
|
+
/**
|
|
6
|
+
* A walk node with its source already resolved.
|
|
7
|
+
*
|
|
8
|
+
* "Is this node a table, and which one" is decided here, once. Deciding it
|
|
9
|
+
* twice — once to collect connection digests and once to fold — means two
|
|
10
|
+
* predicates that have to agree, and a disagreement shows up as a digest
|
|
11
|
+
* missing for a connection that was right there.
|
|
12
|
+
*/
|
|
13
|
+
export interface ResolvedNode {
|
|
14
|
+
node: PersistNode;
|
|
15
|
+
/** The table this node builds, absent if it is a route or cannot resolve */
|
|
16
|
+
source: PersistSource | undefined;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Walk the model and resolve each node's source in one pass.
|
|
20
|
+
*
|
|
21
|
+
* Materializing is not laziness lost: the fold needs a connection digest per
|
|
22
|
+
* source and fetching those is async, so the walk has to be crossed twice.
|
|
23
|
+
*/
|
|
24
|
+
export declare function resolvePersistWalk(model: Model, tagParseLog: LogMessage[]): ResolvedNode[];
|
|
25
|
+
/**
|
|
26
|
+
* Fold a walk into the artifacts it produces, grouped by connection and in
|
|
27
|
+
* dependency order.
|
|
28
|
+
*
|
|
29
|
+
* The walk emits persistable *sources*; a builder needs *tables*, and several
|
|
30
|
+
* sources routinely land on one (see `BuildTarget`). This merges them by
|
|
31
|
+
* BuildID, keeping every source that mapped onto a target in `target.sources`.
|
|
32
|
+
*
|
|
33
|
+
* One pass suffices because the walk is in dependency order: by the time a
|
|
34
|
+
* source arrives, everything it references has already been placed, so its
|
|
35
|
+
* edges can be written immediately. A source that is not itself a table
|
|
36
|
+
* contributes its children's targets to whoever referenced it, which is how an
|
|
37
|
+
* edge survives a route.
|
|
38
|
+
*
|
|
39
|
+
* Targets come back in dependency order, but no coarser schedule than that —
|
|
40
|
+
* a builder wanting concurrency reads `dependsOn` and starts each target when
|
|
41
|
+
* its own dependencies finish, which waits on strictly less than batching by
|
|
42
|
+
* depth would.
|
|
43
|
+
*
|
|
44
|
+
* Separate from `Runtime.getBuildTargets` because given the digests it is
|
|
45
|
+
* pure, and can be tested without a connection.
|
|
46
|
+
*
|
|
47
|
+
* @param walk A resolved walk from {@link resolvePersistWalk}, in order
|
|
48
|
+
* @param connectionDigests One digest per connection named by a persist source
|
|
49
|
+
*/
|
|
50
|
+
export declare function mkBuildTargets(walk: ResolvedNode[], connectionDigests: Record<string, string>): ConnectionBuild[];
|
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/*
|
|
3
|
+
* Copyright Contributors to the Malloy project
|
|
4
|
+
* SPDX-License-Identifier: MIT
|
|
5
|
+
*/
|
|
6
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
+
exports.resolvePersistWalk = resolvePersistWalk;
|
|
8
|
+
exports.mkBuildTargets = mkBuildTargets;
|
|
9
|
+
/**
|
|
10
|
+
* Read a key this function's own construction guarantees is present.
|
|
11
|
+
*
|
|
12
|
+
* Every key here came out of `targets`, which is fully built before anything
|
|
13
|
+
* reads it back. Saying that once beats five bare `!`s that each look like a
|
|
14
|
+
* claim needing checking.
|
|
15
|
+
*/
|
|
16
|
+
function mustGet(map, key) {
|
|
17
|
+
const value = map.get(key);
|
|
18
|
+
if (value === undefined) {
|
|
19
|
+
throw new Error('build target ' + String(key) + ' vanished between passes');
|
|
20
|
+
}
|
|
21
|
+
return value;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Walk the model and resolve each node's source in one pass.
|
|
25
|
+
*
|
|
26
|
+
* Materializing is not laziness lost: the fold needs a connection digest per
|
|
27
|
+
* source and fetching those is async, so the walk has to be crossed twice.
|
|
28
|
+
*/
|
|
29
|
+
function resolvePersistWalk(model, tagParseLog) {
|
|
30
|
+
const resolved = [];
|
|
31
|
+
for (const node of model._walkPersistSources(tagParseLog)) {
|
|
32
|
+
resolved.push({
|
|
33
|
+
node,
|
|
34
|
+
source: node.persistent
|
|
35
|
+
? model._persistSourceFor(node.sourceID)
|
|
36
|
+
: undefined,
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
return resolved;
|
|
40
|
+
}
|
|
41
|
+
/** A BuildID is a hex digest, so it cannot contain the separator. */
|
|
42
|
+
function targetKey(connectionName, buildId) {
|
|
43
|
+
return `${connectionName}:${buildId}`;
|
|
44
|
+
}
|
|
45
|
+
/** Append without repeating. */
|
|
46
|
+
function addUnique(into, seen, keys) {
|
|
47
|
+
for (const key of keys) {
|
|
48
|
+
if (!seen.has(key)) {
|
|
49
|
+
seen.add(key);
|
|
50
|
+
into.push(key);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Fold a walk into the artifacts it produces, grouped by connection and in
|
|
56
|
+
* dependency order.
|
|
57
|
+
*
|
|
58
|
+
* The walk emits persistable *sources*; a builder needs *tables*, and several
|
|
59
|
+
* sources routinely land on one (see `BuildTarget`). This merges them by
|
|
60
|
+
* BuildID, keeping every source that mapped onto a target in `target.sources`.
|
|
61
|
+
*
|
|
62
|
+
* One pass suffices because the walk is in dependency order: by the time a
|
|
63
|
+
* source arrives, everything it references has already been placed, so its
|
|
64
|
+
* edges can be written immediately. A source that is not itself a table
|
|
65
|
+
* contributes its children's targets to whoever referenced it, which is how an
|
|
66
|
+
* edge survives a route.
|
|
67
|
+
*
|
|
68
|
+
* Targets come back in dependency order, but no coarser schedule than that —
|
|
69
|
+
* a builder wanting concurrency reads `dependsOn` and starts each target when
|
|
70
|
+
* its own dependencies finish, which waits on strictly less than batching by
|
|
71
|
+
* depth would.
|
|
72
|
+
*
|
|
73
|
+
* Separate from `Runtime.getBuildTargets` because given the digests it is
|
|
74
|
+
* pure, and can be tested without a connection.
|
|
75
|
+
*
|
|
76
|
+
* @param walk A resolved walk from {@link resolvePersistWalk}, in order
|
|
77
|
+
* @param connectionDigests One digest per connection named by a persist source
|
|
78
|
+
*/
|
|
79
|
+
function mkBuildTargets(walk, connectionDigests) {
|
|
80
|
+
var _a;
|
|
81
|
+
const targets = new Map();
|
|
82
|
+
// What each source hands to whoever referenced it: its own target if it is
|
|
83
|
+
// one, otherwise whatever it was a route to.
|
|
84
|
+
const contributes = new Map();
|
|
85
|
+
for (const { node, source } of walk) {
|
|
86
|
+
const childKeys = [];
|
|
87
|
+
const seen = new Set();
|
|
88
|
+
for (const dep of node.dependsOn) {
|
|
89
|
+
addUnique(childKeys, seen, (_a = contributes.get(dep)) !== null && _a !== void 0 ? _a : []);
|
|
90
|
+
}
|
|
91
|
+
if (source === undefined) {
|
|
92
|
+
// A route, or a source whose definition could not be resolved. Either
|
|
93
|
+
// way it is not a table; pass its dependencies up.
|
|
94
|
+
contributes.set(node.sourceID, childKeys);
|
|
95
|
+
continue;
|
|
96
|
+
}
|
|
97
|
+
const connectionName = source.connectionName;
|
|
98
|
+
const digest = connectionDigests[connectionName];
|
|
99
|
+
if (digest === undefined) {
|
|
100
|
+
throw new Error(`No connection digest for '${connectionName}', needed to compute the ` +
|
|
101
|
+
`BuildID of '${node.sourceID}'. Supply a digest for every connection ` +
|
|
102
|
+
'named by a persist source.');
|
|
103
|
+
}
|
|
104
|
+
const sql = source.getSQL();
|
|
105
|
+
const buildId = source.makeBuildId(digest, sql);
|
|
106
|
+
const key = targetKey(connectionName, buildId);
|
|
107
|
+
let target = targets.get(key);
|
|
108
|
+
if (target === undefined) {
|
|
109
|
+
target = {
|
|
110
|
+
buildId,
|
|
111
|
+
connectionName,
|
|
112
|
+
sql,
|
|
113
|
+
sources: [],
|
|
114
|
+
dependsOn: new Set(),
|
|
115
|
+
};
|
|
116
|
+
targets.set(key, target);
|
|
117
|
+
}
|
|
118
|
+
const mine = new Set();
|
|
119
|
+
for (const childKey of childKeys) {
|
|
120
|
+
// A child on my own key is an extension of me — the same table twice,
|
|
121
|
+
// not a dependency.
|
|
122
|
+
if (childKey === key)
|
|
123
|
+
continue;
|
|
124
|
+
mine.add(childKey);
|
|
125
|
+
}
|
|
126
|
+
// Intersect, do not union. Union makes cycles: `source: alias is base
|
|
127
|
+
// extend { join_one: mid }` where `mid` reads `base` merges alias onto
|
|
128
|
+
// base's target carrying an edge to `mid`, while `mid` depends on `base`.
|
|
129
|
+
// Only one of those two edges is real.
|
|
130
|
+
//
|
|
131
|
+
// Intersecting keeps the real ones because of the six-paths list at
|
|
132
|
+
// `walkPersistentDependencies`: those are every way one source's SQL can
|
|
133
|
+
// incorporate another's — except `CompositeSourceDef.sources[]`, which the
|
|
134
|
+
// walk skips, and which is why composites and persistence are documented
|
|
135
|
+
// as incompatible. So a source that compiles to this target's SQL
|
|
136
|
+
// necessarily reaches everything that SQL inlines: every set here is a
|
|
137
|
+
// superset of the real dependencies, and what differs between them is only
|
|
138
|
+
// the join over-approximation, which is what the intersection removes.
|
|
139
|
+
//
|
|
140
|
+
// The safety therefore rests on that list staying complete, composites
|
|
141
|
+
// being its one known and deliberate hole. A path added to the IR and not
|
|
142
|
+
// to the walk — or composites being made to work with persistence without
|
|
143
|
+
// revisiting this — would break it, and the symptom here is quieter than a
|
|
144
|
+
// wrong plan: a table built from inlined SQL instead of reading the one
|
|
145
|
+
// below it. No error, just the expensive query persistence exists to
|
|
146
|
+
// avoid.
|
|
147
|
+
if (target.sources.length === 0) {
|
|
148
|
+
target.dependsOn = mine;
|
|
149
|
+
}
|
|
150
|
+
else {
|
|
151
|
+
for (const had of [...target.dependsOn]) {
|
|
152
|
+
if (!mine.has(had))
|
|
153
|
+
target.dependsOn.delete(had);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
target.sources.push(source);
|
|
157
|
+
contributes.set(node.sourceID, [key]);
|
|
158
|
+
}
|
|
159
|
+
// Every target exists before any `dependsOn` is filled in, so references can
|
|
160
|
+
// point both ways round a diamond.
|
|
161
|
+
const built = new Map();
|
|
162
|
+
for (const [key, partial] of targets) {
|
|
163
|
+
built.set(key, {
|
|
164
|
+
buildId: partial.buildId,
|
|
165
|
+
connectionName: partial.connectionName,
|
|
166
|
+
sql: partial.sql,
|
|
167
|
+
sources: partial.sources,
|
|
168
|
+
dependsOn: [],
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
for (const [key, partial] of targets) {
|
|
172
|
+
const target = mustGet(built, key);
|
|
173
|
+
for (const depKey of partial.dependsOn) {
|
|
174
|
+
target.dependsOn.push(mustGet(built, depKey));
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
// Emit dependencies before dependents. Depth-first with a memo: a target is
|
|
178
|
+
// appended once, after everything it reads.
|
|
179
|
+
const byConnection = new Map();
|
|
180
|
+
const placed = new Set();
|
|
181
|
+
const openKeys = new Set();
|
|
182
|
+
const place = (key) => {
|
|
183
|
+
if (placed.has(key))
|
|
184
|
+
return;
|
|
185
|
+
if (openKeys.has(key)) {
|
|
186
|
+
// A real edge cannot make a cycle: a target's SQL contains its
|
|
187
|
+
// dependencies' SQL inline, so two targets cannot each contain the
|
|
188
|
+
// other. But an edge here is not always a real one — a source merging
|
|
189
|
+
// onto a target can bring a join the target's SQL never mentions, and
|
|
190
|
+
// that is what intersecting the merged sources' edges above removes. If
|
|
191
|
+
// one gets through anyway, say so rather than hang.
|
|
192
|
+
const names = mustGet(targets, key)
|
|
193
|
+
.sources.map(s => s.sourceID)
|
|
194
|
+
.join(', ');
|
|
195
|
+
throw new Error(`Cycle in the persistence build graph at: ${names}`);
|
|
196
|
+
}
|
|
197
|
+
openKeys.add(key);
|
|
198
|
+
for (const depKey of mustGet(targets, key).dependsOn) {
|
|
199
|
+
place(depKey);
|
|
200
|
+
}
|
|
201
|
+
openKeys.delete(key);
|
|
202
|
+
placed.add(key);
|
|
203
|
+
const target = mustGet(built, key);
|
|
204
|
+
const forConnection = byConnection.get(target.connectionName);
|
|
205
|
+
if (forConnection === undefined) {
|
|
206
|
+
byConnection.set(target.connectionName, [target]);
|
|
207
|
+
}
|
|
208
|
+
else {
|
|
209
|
+
forConnection.push(target);
|
|
210
|
+
}
|
|
211
|
+
};
|
|
212
|
+
for (const key of targets.keys()) {
|
|
213
|
+
place(key);
|
|
214
|
+
}
|
|
215
|
+
const connections = [];
|
|
216
|
+
for (const [connectionName, connectionTargets] of byConnection) {
|
|
217
|
+
connections.push({ connectionName, targets: connectionTargets });
|
|
218
|
+
}
|
|
219
|
+
return connections;
|
|
220
|
+
}
|
|
221
|
+
//# sourceMappingURL=build_targets.js.map
|
|
@@ -3,6 +3,7 @@ import type { BuildID, CompiledQuery, ConstantExpr, DocumentLocation, BooleanFie
|
|
|
3
3
|
import { QueryModel } from '../../model';
|
|
4
4
|
import type { Dialect } from '../../dialect';
|
|
5
5
|
import type { BuildGraph, CompileQueryOptions } from './types';
|
|
6
|
+
import type { PersistWalk } from '../../model/persist_utils';
|
|
6
7
|
import { Tag } from '@malloydata/malloy-tag';
|
|
7
8
|
import type { MalloyTagParse, TagParseSpec } from './annotation';
|
|
8
9
|
import { Annotations } from './annotation';
|
|
@@ -464,19 +465,44 @@ export declare class Model implements Taggable {
|
|
|
464
465
|
get exportedExplores(): Explore[];
|
|
465
466
|
get _modelDef(): ModelDef;
|
|
466
467
|
/**
|
|
467
|
-
*
|
|
468
|
+
* Require the `experimental.persistence` compiler flag.
|
|
468
469
|
*
|
|
469
|
-
*
|
|
470
|
-
*
|
|
470
|
+
* Read off the resolved model annotations (`.modelAnnotations`, the
|
|
471
|
+
* import/extend fold) rather than this model's own `##`, so the flag carries
|
|
472
|
+
* across extend.
|
|
473
|
+
*/
|
|
474
|
+
private requirePersistence;
|
|
475
|
+
/**
|
|
476
|
+
* Walk every persistable source this model reaches, in dependency order.
|
|
477
|
+
*
|
|
478
|
+
* The roots are every source and query the model names, plus its unnamed
|
|
479
|
+
* queries. They share one walk, so a source several of them reach is visited
|
|
480
|
+
* once.
|
|
481
|
+
*
|
|
482
|
+
* This is the raw material {@link Runtime.getBuildTargets} folds into
|
|
483
|
+
* tables. Nothing here is keyed by artifact: that needs a BuildID, and a
|
|
484
|
+
* model cannot reach a connection to compute one.
|
|
485
|
+
*/
|
|
486
|
+
_walkPersistSources(tagParseLog: LogMessage[]): PersistWalk;
|
|
487
|
+
/**
|
|
488
|
+
* The {@link PersistSource} for a sourceID, or undefined if this model
|
|
489
|
+
* cannot resolve it.
|
|
490
|
+
*/
|
|
491
|
+
_persistSourceFor(sourceID: string): PersistSource | undefined;
|
|
492
|
+
/**
|
|
493
|
+
* Every `#@ persist` source in the model, as a graph keyed by sourceID.
|
|
471
494
|
*
|
|
472
|
-
*
|
|
473
|
-
*
|
|
474
|
-
*
|
|
495
|
+
* Walks all queries and sources, including dependencies that arrived through
|
|
496
|
+
* an import and are in no namespace here, and returns the *roots* — the
|
|
497
|
+
* sources nothing else depends on — each carrying its dependencies in
|
|
498
|
+
* `dependsOn`.
|
|
475
499
|
*
|
|
476
|
-
*
|
|
477
|
-
*
|
|
478
|
-
*
|
|
479
|
-
* for
|
|
500
|
+
* @deprecated A builder wants tables, and this reports sources. Those are not
|
|
501
|
+
* one to one: several sources routinely map onto one table, so keying on
|
|
502
|
+
* sourceID leaves every builder to discover the mapping by hashing. Its roots
|
|
503
|
+
* are computed by sourceID too, so for a persisted source with an extension
|
|
504
|
+
* the root is the extension and the declaring source is never named. Use
|
|
505
|
+
* {@link Runtime.getBuildTargets}, which answers in tables.
|
|
480
506
|
*
|
|
481
507
|
* @return BuildPlan with graphs and sources map
|
|
482
508
|
*/
|
|
@@ -538,6 +564,24 @@ export declare class PersistSource implements Taggable {
|
|
|
538
564
|
get annotations(): Annotations;
|
|
539
565
|
/** The model annotations resolved for this source. */
|
|
540
566
|
get modelAnnotations(): Annotations;
|
|
567
|
+
/**
|
|
568
|
+
* Where this source was declared: the URL of the model that declared it, and
|
|
569
|
+
* the range of the `source:` statement.
|
|
570
|
+
*
|
|
571
|
+
* This is the handle to report a build failure against. A name is ambiguous
|
|
572
|
+
* across models, a sourceID is a name and a URL glued together, and a BuildID
|
|
573
|
+
* is a hash — none of them answer "where do I go to fix this," and a location
|
|
574
|
+
* does.
|
|
575
|
+
*
|
|
576
|
+
* The URL is whatever the model was loaded from, which may be a scheme only
|
|
577
|
+
* the caller understands. Rendering it for a human is the builder's job for
|
|
578
|
+
* the same reason `name=` is: the core supplied no URLReader and has no idea
|
|
579
|
+
* what these URLs mean.
|
|
580
|
+
*
|
|
581
|
+
* Undefined for a source with no recorded position — one synthesized rather
|
|
582
|
+
* than written down.
|
|
583
|
+
*/
|
|
584
|
+
get location(): DocumentLocation | undefined;
|
|
541
585
|
/**
|
|
542
586
|
* The connection name for this source.
|
|
543
587
|
*/
|
|
@@ -1107,30 +1107,69 @@ class Model {
|
|
|
1107
1107
|
return this.modelDef;
|
|
1108
1108
|
}
|
|
1109
1109
|
/**
|
|
1110
|
-
*
|
|
1110
|
+
* Require the `experimental.persistence` compiler flag.
|
|
1111
1111
|
*
|
|
1112
|
-
*
|
|
1113
|
-
*
|
|
1112
|
+
* Read off the resolved model annotations (`.modelAnnotations`, the
|
|
1113
|
+
* import/extend fold) rather than this model's own `##`, so the flag carries
|
|
1114
|
+
* across extend.
|
|
1115
|
+
*/
|
|
1116
|
+
requirePersistence(api) {
|
|
1117
|
+
const modelTag = this.modelAnnotations.parseAsTag('!').tag;
|
|
1118
|
+
if (!modelTag.has('experimental', 'persistence')) {
|
|
1119
|
+
throw new Error(`Model must have ##! experimental.persistence to use ${api}`);
|
|
1120
|
+
}
|
|
1121
|
+
}
|
|
1122
|
+
/**
|
|
1123
|
+
* Walk every persistable source this model reaches, in dependency order.
|
|
1114
1124
|
*
|
|
1115
|
-
*
|
|
1116
|
-
*
|
|
1117
|
-
*
|
|
1125
|
+
* The roots are every source and query the model names, plus its unnamed
|
|
1126
|
+
* queries. They share one walk, so a source several of them reach is visited
|
|
1127
|
+
* once.
|
|
1118
1128
|
*
|
|
1119
|
-
*
|
|
1120
|
-
*
|
|
1121
|
-
*
|
|
1122
|
-
|
|
1129
|
+
* This is the raw material {@link Runtime.getBuildTargets} folds into
|
|
1130
|
+
* tables. Nothing here is keyed by artifact: that needs a BuildID, and a
|
|
1131
|
+
* model cannot reach a connection to compute one.
|
|
1132
|
+
*/
|
|
1133
|
+
_walkPersistSources(tagParseLog) {
|
|
1134
|
+
this.requirePersistence('_walkPersistSources()');
|
|
1135
|
+
const roots = [];
|
|
1136
|
+
for (const obj of Object.values(this.modelDef.contents)) {
|
|
1137
|
+
if (obj.type === 'query' || (0, model_1.isSourceDef)(obj)) {
|
|
1138
|
+
roots.push(obj);
|
|
1139
|
+
}
|
|
1140
|
+
}
|
|
1141
|
+
roots.push(...this.modelDef.queryList);
|
|
1142
|
+
return (0, persist_utils_1.walkPersistentDependencies)(roots, this.modelDef, tagParseLog);
|
|
1143
|
+
}
|
|
1144
|
+
/**
|
|
1145
|
+
* The {@link PersistSource} for a sourceID, or undefined if this model
|
|
1146
|
+
* cannot resolve it.
|
|
1147
|
+
*/
|
|
1148
|
+
_persistSourceFor(sourceID) {
|
|
1149
|
+
const sourceDef = (0, source_def_utils_1.resolveSourceID)(this.modelDef, sourceID);
|
|
1150
|
+
return sourceDef
|
|
1151
|
+
? new PersistSource(new Explore(this.modelDef, sourceDef), this)
|
|
1152
|
+
: undefined;
|
|
1153
|
+
}
|
|
1154
|
+
/**
|
|
1155
|
+
* Every `#@ persist` source in the model, as a graph keyed by sourceID.
|
|
1156
|
+
*
|
|
1157
|
+
* Walks all queries and sources, including dependencies that arrived through
|
|
1158
|
+
* an import and are in no namespace here, and returns the *roots* — the
|
|
1159
|
+
* sources nothing else depends on — each carrying its dependencies in
|
|
1160
|
+
* `dependsOn`.
|
|
1161
|
+
*
|
|
1162
|
+
* @deprecated A builder wants tables, and this reports sources. Those are not
|
|
1163
|
+
* one to one: several sources routinely map onto one table, so keying on
|
|
1164
|
+
* sourceID leaves every builder to discover the mapping by hashing. Its roots
|
|
1165
|
+
* are computed by sourceID too, so for a persisted source with an extension
|
|
1166
|
+
* the root is the extension and the declaring source is never named. Use
|
|
1167
|
+
* {@link Runtime.getBuildTargets}, which answers in tables.
|
|
1123
1168
|
*
|
|
1124
1169
|
* @return BuildPlan with graphs and sources map
|
|
1125
1170
|
*/
|
|
1126
1171
|
getBuildPlan() {
|
|
1127
|
-
|
|
1128
|
-
// annotations (`.modelAnnotations`, the import/extend fold) rather than this
|
|
1129
|
-
// model's own `##` so the flag carries across extend.
|
|
1130
|
-
const modelTag = this.modelAnnotations.parseAsTag('!').tag;
|
|
1131
|
-
if (!modelTag.has('experimental', 'persistence')) {
|
|
1132
|
-
throw new Error('Model must have ##! experimental.persistence to use getBuildPlan()');
|
|
1133
|
-
}
|
|
1172
|
+
this.requirePersistence('getBuildPlan()');
|
|
1134
1173
|
const allDeps = [];
|
|
1135
1174
|
const tagParseLog = [];
|
|
1136
1175
|
// Walk all objects in the model to find persistent dependencies
|
|
@@ -1150,8 +1189,12 @@ class Model {
|
|
|
1150
1189
|
const rootNodes = (0, persist_utils_1.minimalBuildGraph)(allDeps);
|
|
1151
1190
|
// Build the sources map from all persistent sourceIDs encountered
|
|
1152
1191
|
const sourcesMap = {};
|
|
1192
|
+
const seen = new Set();
|
|
1153
1193
|
const collectSources = (nodes) => {
|
|
1154
1194
|
for (const node of nodes) {
|
|
1195
|
+
if (seen.has(node))
|
|
1196
|
+
continue;
|
|
1197
|
+
seen.add(node);
|
|
1155
1198
|
if (!(node.sourceID in sourcesMap)) {
|
|
1156
1199
|
const sourceDef = (0, source_def_utils_1.resolveSourceID)(this.modelDef, node.sourceID);
|
|
1157
1200
|
if (sourceDef) {
|
|
@@ -1257,6 +1300,26 @@ class PersistSource {
|
|
|
1257
1300
|
get modelAnnotations() {
|
|
1258
1301
|
return this.explore.modelAnnotations;
|
|
1259
1302
|
}
|
|
1303
|
+
/**
|
|
1304
|
+
* Where this source was declared: the URL of the model that declared it, and
|
|
1305
|
+
* the range of the `source:` statement.
|
|
1306
|
+
*
|
|
1307
|
+
* This is the handle to report a build failure against. A name is ambiguous
|
|
1308
|
+
* across models, a sourceID is a name and a URL glued together, and a BuildID
|
|
1309
|
+
* is a hash — none of them answer "where do I go to fix this," and a location
|
|
1310
|
+
* does.
|
|
1311
|
+
*
|
|
1312
|
+
* The URL is whatever the model was loaded from, which may be a scheme only
|
|
1313
|
+
* the caller understands. Rendering it for a human is the builder's job for
|
|
1314
|
+
* the same reason `name=` is: the core supplied no URLReader and has no idea
|
|
1315
|
+
* what these URLs mean.
|
|
1316
|
+
*
|
|
1317
|
+
* Undefined for a source with no recorded position — one synthesized rather
|
|
1318
|
+
* than written down.
|
|
1319
|
+
*/
|
|
1320
|
+
get location() {
|
|
1321
|
+
return this.persistableDef.location;
|
|
1322
|
+
}
|
|
1260
1323
|
/**
|
|
1261
1324
|
* The connection name for this source.
|
|
1262
1325
|
*/
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export type { Taggable, Loggable, ParseOptions, CompileOptions, CompileQueryOptions, BuildNode, BuildGraph, } from './types';
|
|
1
|
+
export type { Taggable, Loggable, ParseOptions, CompileOptions, CompileQueryOptions, BuildNode, BuildGraph, BuildTarget, BuildTargets, ConnectionBuild, } from './types';
|
|
2
2
|
export { EMPTY_BUILD_MANIFEST } from './types';
|
|
3
3
|
export { EmptyURLReader, InMemoryURLReader, FixedConnectionMap, hashForInvalidationKey, isInternalURL, readURL, getInvalidationKey, } from './readers';
|
|
4
4
|
export type { ModelCache, CachedModel } from './cache';
|
|
@@ -6,7 +6,7 @@ import type { Dialect } from '../../dialect';
|
|
|
6
6
|
import type { RunSQLOptions } from '../../run_sql_options';
|
|
7
7
|
import type { CacheManager } from './cache';
|
|
8
8
|
import type { MalloyConfig } from './config';
|
|
9
|
-
import type { ParseOptions, CompileOptions, CompileQueryOptions } from './types';
|
|
9
|
+
import type { ParseOptions, CompileOptions, CompileQueryOptions, BuildTargets } from './types';
|
|
10
10
|
import type { PreparedResult, Explore } from './core';
|
|
11
11
|
import { Model, PreparedQuery } from './core';
|
|
12
12
|
import type { DataRecord, Result } from './result';
|
|
@@ -285,6 +285,23 @@ export declare class Runtime {
|
|
|
285
285
|
* @return A promise of a compiled `PreparedQuery`.
|
|
286
286
|
*/
|
|
287
287
|
getQueryByName(model: ModelURL | ModelString, name: string, options?: ParseOptions & CompileOptions): Promise<PreparedQuery>;
|
|
288
|
+
/**
|
|
289
|
+
* What a builder has to build for this model, and in what order.
|
|
290
|
+
*
|
|
291
|
+
* This is the builder entry point. A model can only enumerate persistable
|
|
292
|
+
* *sources*, which is not the same set as the tables they produce — several
|
|
293
|
+
* sources routinely map onto one table, and only a connection digest can
|
|
294
|
+
* tell you which. That is why this lives on Runtime: it holds the
|
|
295
|
+
* connections, so it can finish the answer a model can only start.
|
|
296
|
+
*
|
|
297
|
+
* Connections are independent of one another; within one, targets come back
|
|
298
|
+
* in dependency order and each carries its own `dependsOn`. See
|
|
299
|
+
* {@link BuildTargets}.
|
|
300
|
+
*
|
|
301
|
+
* @param model A compiled model with `##! experimental.persistence`
|
|
302
|
+
* @return The targets to build, with any annotation parse messages
|
|
303
|
+
*/
|
|
304
|
+
getBuildTargets(model: Model): Promise<BuildTargets>;
|
|
288
305
|
}
|
|
289
306
|
export declare class ConnectionRuntime extends Runtime {
|
|
290
307
|
readonly rawConnections: Connection[];
|
|
@@ -11,6 +11,7 @@ const validate_table_path_1 = require("../../connection/validate_table_path");
|
|
|
11
11
|
const config_1 = require("./config");
|
|
12
12
|
const row_data_utils_1 = require("../../api/row_data_utils");
|
|
13
13
|
const readers_1 = require("./readers");
|
|
14
|
+
const build_targets_1 = require("./build_targets");
|
|
14
15
|
const core_1 = require("./core");
|
|
15
16
|
const compile_1 = require("./compile");
|
|
16
17
|
// =============================================================================
|
|
@@ -480,6 +481,40 @@ class Runtime {
|
|
|
480
481
|
getQueryByName(model, name, options) {
|
|
481
482
|
return this.loadQueryByName(model, name, options).getPreparedQuery();
|
|
482
483
|
}
|
|
484
|
+
/**
|
|
485
|
+
* What a builder has to build for this model, and in what order.
|
|
486
|
+
*
|
|
487
|
+
* This is the builder entry point. A model can only enumerate persistable
|
|
488
|
+
* *sources*, which is not the same set as the tables they produce — several
|
|
489
|
+
* sources routinely map onto one table, and only a connection digest can
|
|
490
|
+
* tell you which. That is why this lives on Runtime: it holds the
|
|
491
|
+
* connections, so it can finish the answer a model can only start.
|
|
492
|
+
*
|
|
493
|
+
* Connections are independent of one another; within one, targets come back
|
|
494
|
+
* in dependency order and each carries its own `dependsOn`. See
|
|
495
|
+
* {@link BuildTargets}.
|
|
496
|
+
*
|
|
497
|
+
* @param model A compiled model with `##! experimental.persistence`
|
|
498
|
+
* @return The targets to build, with any annotation parse messages
|
|
499
|
+
*/
|
|
500
|
+
async getBuildTargets(model) {
|
|
501
|
+
const tagParseLog = [];
|
|
502
|
+
const walk = (0, build_targets_1.resolvePersistWalk)(model, tagParseLog);
|
|
503
|
+
const connectionDigests = (0, model_1.mkSafeRecord)();
|
|
504
|
+
for (const { source } of walk) {
|
|
505
|
+
if (source === undefined)
|
|
506
|
+
continue;
|
|
507
|
+
const connectionName = source.connectionName;
|
|
508
|
+
if (!(connectionName in connectionDigests)) {
|
|
509
|
+
const connection = await this.connections.lookupConnection(connectionName);
|
|
510
|
+
connectionDigests[connectionName] = connection.getDigest();
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
return {
|
|
514
|
+
connections: (0, build_targets_1.mkBuildTargets)(walk, connectionDigests),
|
|
515
|
+
tagParseLog,
|
|
516
|
+
};
|
|
517
|
+
}
|
|
483
518
|
}
|
|
484
519
|
exports.Runtime = Runtime;
|
|
485
520
|
// =============================================================================
|