@malloydata/malloy 0.0.428 → 0.0.430
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
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
*/
|
|
6
6
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
7
|
exports.checkPersistAnnotation = checkPersistAnnotation;
|
|
8
|
+
exports.walkPersistentDependencies = walkPersistentDependencies;
|
|
8
9
|
exports.findPersistentDependencies = findPersistentDependencies;
|
|
9
10
|
exports.minimalBuildGraph = minimalBuildGraph;
|
|
10
11
|
const malloy_types_1 = require("./malloy_types");
|
|
@@ -50,15 +51,26 @@ function isPersistent(sourceID, modelDef, tagParseLog) {
|
|
|
50
51
|
return value.persist;
|
|
51
52
|
}
|
|
52
53
|
/**
|
|
53
|
-
*
|
|
54
|
+
* Walk everything a source or query references, and emit the sources that
|
|
55
|
+
* matter to persistence — in dependency order, each one after everything it
|
|
56
|
+
* depends on.
|
|
54
57
|
*
|
|
55
|
-
*
|
|
56
|
-
*
|
|
57
|
-
*
|
|
58
|
+
* A source is emitted if it is persistent — something to build — or if it
|
|
59
|
+
* is the *route* to something persistent. Everything else is dropped; a
|
|
60
|
+
* source with nothing persistent beneath it is of no interest to anybody.
|
|
58
61
|
*
|
|
59
|
-
*
|
|
60
|
-
*
|
|
61
|
-
*
|
|
62
|
+
* One rule decides both halves, applied on the way back up:
|
|
63
|
+
*
|
|
64
|
+
* keep me if I am persistent, or if any child survived
|
|
65
|
+
*
|
|
66
|
+
* The second clause needs no lookahead. A child only survived under this same
|
|
67
|
+
* rule, so a surviving child *is* the proof that something persistent lies
|
|
68
|
+
* below. For `a(persist) → b → c(persist) → d`, d has nothing beneath it
|
|
69
|
+
* and is dropped, c is kept for being persistent, b is kept for leading to c.
|
|
70
|
+
*
|
|
71
|
+
* Because a node arrives only after everything it depends on, a consumer can
|
|
72
|
+
* fold the stream in a single pass with no second lookup — whether it wants
|
|
73
|
+
* every source touched, or only the ones that are tables.
|
|
62
74
|
*
|
|
63
75
|
* ## The 6 Dependency Paths in the IR
|
|
64
76
|
*
|
|
@@ -75,58 +87,91 @@ function isPersistent(sourceID, modelDef, tagParseLog) {
|
|
|
75
87
|
* Note: CompositeSourceDef.sources[] is ignored - composite sources and
|
|
76
88
|
* persistence may be incompatible features.
|
|
77
89
|
*
|
|
78
|
-
*
|
|
90
|
+
* Joins are followed whether or not a query activates them, so what comes back
|
|
91
|
+
* is what a source *could* depend on. An unused join is a dependency the SQL
|
|
92
|
+
* will not contain — an over-approximation, which costs ordering freedom and
|
|
93
|
+
* never correctness.
|
|
94
|
+
*
|
|
95
|
+
* @param roots The sources and queries to walk, sharing one memo
|
|
79
96
|
* @param modelDef The model definition containing the source registry
|
|
80
|
-
* @
|
|
97
|
+
* @param tagParseLog Collects errors from parsing `#@` annotations
|
|
81
98
|
*/
|
|
82
|
-
function
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
99
|
+
function* walkPersistentDependencies(roots, modelDef, tagParseLog = []) {
|
|
100
|
+
// Memoized, not merely visited: a source reached a second time reports the
|
|
101
|
+
// same answer it gave the first time, so every dependent records the edge
|
|
102
|
+
// rather than only the first one to arrive. The memo spans every root, so a
|
|
103
|
+
// subtree two model objects both reach is walked once and yielded once.
|
|
104
|
+
const done = new Map();
|
|
105
|
+
const openIDs = new Set();
|
|
106
|
+
function* processSourceID(sourceID) {
|
|
107
|
+
const memo = done.get(sourceID);
|
|
108
|
+
if (memo !== undefined) {
|
|
109
|
+
return memo;
|
|
87
110
|
}
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
if (
|
|
111
|
+
// A source cannot reach itself, but a malformed registry could; stop rather
|
|
112
|
+
// than recur forever.
|
|
113
|
+
if (openIDs.has(sourceID)) {
|
|
91
114
|
return [];
|
|
92
115
|
}
|
|
93
|
-
|
|
94
|
-
const
|
|
95
|
-
|
|
96
|
-
|
|
116
|
+
openIDs.add(sourceID);
|
|
117
|
+
const sourceDef = (0, source_def_utils_1.resolveSourceID)(modelDef, sourceID);
|
|
118
|
+
let result = [];
|
|
119
|
+
if (sourceDef) {
|
|
120
|
+
const dependsOn = yield* processSourceDef(sourceDef);
|
|
121
|
+
const persistent = isPersistent(sourceID, modelDef, tagParseLog);
|
|
122
|
+
// Kept for being a table, or for being the road to one. Yielded after
|
|
123
|
+
// its dependencies, which is what lets a consumer fold in one pass.
|
|
124
|
+
if (persistent || dependsOn.length > 0) {
|
|
125
|
+
yield { sourceID, persistent, dependsOn };
|
|
126
|
+
result = [sourceID];
|
|
127
|
+
}
|
|
97
128
|
}
|
|
98
|
-
|
|
99
|
-
|
|
129
|
+
openIDs.delete(sourceID);
|
|
130
|
+
done.set(sourceID, result);
|
|
131
|
+
return result;
|
|
132
|
+
}
|
|
133
|
+
/** Concatenate what several paths found, without repeating a source. */
|
|
134
|
+
function* gather(parts) {
|
|
135
|
+
const found = [];
|
|
136
|
+
const seen = new Set();
|
|
137
|
+
for (const part of parts) {
|
|
138
|
+
for (const id of yield* part) {
|
|
139
|
+
if (!seen.has(id)) {
|
|
140
|
+
seen.add(id);
|
|
141
|
+
found.push(id);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
100
144
|
}
|
|
145
|
+
return found;
|
|
101
146
|
}
|
|
102
|
-
function processSourceDef(source) {
|
|
103
|
-
const
|
|
147
|
+
function* processSourceDef(source) {
|
|
148
|
+
const parts = [];
|
|
104
149
|
// Path 4: PersistableSourceDef.extends
|
|
105
150
|
if ((0, malloy_types_1.isPersistableSourceDef)(source) && source.extends) {
|
|
106
|
-
|
|
151
|
+
parts.push(processSourceID(source.extends));
|
|
107
152
|
}
|
|
108
153
|
// Path 6: QuerySourceDef.query
|
|
109
154
|
if (source.type === 'query_source') {
|
|
110
|
-
|
|
155
|
+
parts.push(processQuery(source.query));
|
|
111
156
|
}
|
|
112
157
|
// Path 5: SQLSourceDef.selectSegments[]
|
|
113
158
|
if (source.type === 'sql_select' && source.selectSegments) {
|
|
114
159
|
for (const segment of source.selectSegments) {
|
|
115
|
-
|
|
160
|
+
parts.push(processSQLSegment(segment));
|
|
116
161
|
}
|
|
117
162
|
}
|
|
118
163
|
// Path 3: SourceDef.fields[] - joins defined on the source
|
|
119
164
|
for (const field of source.fields) {
|
|
120
165
|
if ((0, malloy_types_1.isJoined)(field) && (0, malloy_types_1.isSourceDef)(field)) {
|
|
121
|
-
|
|
166
|
+
parts.push(processJoinedSource(field));
|
|
122
167
|
}
|
|
123
168
|
}
|
|
124
|
-
return
|
|
169
|
+
return yield* gather(parts);
|
|
125
170
|
}
|
|
126
|
-
function processQuery(query) {
|
|
127
|
-
const
|
|
171
|
+
function* processQuery(query) {
|
|
172
|
+
const parts = [];
|
|
128
173
|
// Path 1: Query.structRef
|
|
129
|
-
|
|
174
|
+
parts.push(processStructRef(query.structRef));
|
|
130
175
|
// Path 2: Query.pipeline[].extendSource[]
|
|
131
176
|
for (const segment of query.pipeline) {
|
|
132
177
|
if (segment.type === 'reduce' ||
|
|
@@ -136,80 +181,140 @@ function findPersistentDependencies(root, modelDef, tagParseLog = []) {
|
|
|
136
181
|
if (querySegment.extendSource) {
|
|
137
182
|
for (const field of querySegment.extendSource) {
|
|
138
183
|
if ((0, malloy_types_1.isJoined)(field) && (0, malloy_types_1.isSourceDef)(field)) {
|
|
139
|
-
|
|
184
|
+
parts.push(processJoinedSource(field));
|
|
140
185
|
}
|
|
141
186
|
}
|
|
142
187
|
}
|
|
143
188
|
}
|
|
144
189
|
}
|
|
145
|
-
return
|
|
190
|
+
return yield* gather(parts);
|
|
146
191
|
}
|
|
147
|
-
function processJoinedSource(source) {
|
|
192
|
+
function* processJoinedSource(source) {
|
|
148
193
|
// If it has an sourceID, go through the registry
|
|
149
194
|
if ((0, malloy_types_1.isPersistableSourceDef)(source) && source.sourceID) {
|
|
150
|
-
return processSourceID(source.sourceID);
|
|
195
|
+
return yield* processSourceID(source.sourceID);
|
|
151
196
|
}
|
|
152
197
|
// Otherwise walk through it transparently
|
|
153
|
-
return processSourceDef(source);
|
|
198
|
+
return yield* processSourceDef(source);
|
|
154
199
|
}
|
|
155
|
-
function processStructRef(ref) {
|
|
200
|
+
function* processStructRef(ref) {
|
|
156
201
|
if (typeof ref === 'string') {
|
|
157
202
|
const source = resolveSource(modelDef, ref);
|
|
158
203
|
if (!source)
|
|
159
204
|
return [];
|
|
160
205
|
if ((0, malloy_types_1.isPersistableSourceDef)(source) && source.sourceID) {
|
|
161
|
-
return processSourceID(source.sourceID);
|
|
206
|
+
return yield* processSourceID(source.sourceID);
|
|
162
207
|
}
|
|
163
|
-
return processSourceDef(source);
|
|
208
|
+
return yield* processSourceDef(source);
|
|
164
209
|
}
|
|
165
210
|
else if ((0, malloy_types_1.isSourceDef)(ref)) {
|
|
166
211
|
if ((0, malloy_types_1.isPersistableSourceDef)(ref) && ref.sourceID) {
|
|
167
|
-
return processSourceID(ref.sourceID);
|
|
212
|
+
return yield* processSourceID(ref.sourceID);
|
|
168
213
|
}
|
|
169
|
-
return processSourceDef(ref);
|
|
214
|
+
return yield* processSourceDef(ref);
|
|
170
215
|
}
|
|
171
216
|
return [];
|
|
172
217
|
}
|
|
173
|
-
function processSQLSegment(segment) {
|
|
218
|
+
function* processSQLSegment(segment) {
|
|
174
219
|
if ((0, malloy_types_1.isSegmentSQL)(segment)) {
|
|
175
220
|
return [];
|
|
176
221
|
}
|
|
177
222
|
else if ((0, malloy_types_1.isSegmentSource)(segment)) {
|
|
178
223
|
if ((0, malloy_types_1.isPersistableSourceDef)(segment) && segment.sourceID) {
|
|
179
|
-
return processSourceID(segment.sourceID);
|
|
224
|
+
return yield* processSourceID(segment.sourceID);
|
|
180
225
|
}
|
|
181
|
-
return processSourceDef(segment);
|
|
226
|
+
return yield* processSourceDef(segment);
|
|
182
227
|
}
|
|
183
228
|
else {
|
|
184
229
|
// It's a Query
|
|
185
|
-
return processQuery(segment);
|
|
230
|
+
return yield* processQuery(segment);
|
|
186
231
|
}
|
|
187
232
|
}
|
|
188
|
-
// Entry point:
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
//
|
|
195
|
-
// processSourceID so it gets included in the result if persistent
|
|
233
|
+
// Entry point: a Query has a required 'structRef', a SourceDef does not.
|
|
234
|
+
return yield* gather(roots.map(root => {
|
|
235
|
+
if ('structRef' in root) {
|
|
236
|
+
return processQuery(root);
|
|
237
|
+
}
|
|
238
|
+
// A root that is persistable and named goes through processSourceID so
|
|
239
|
+
// it appears in its own right if it is persistent.
|
|
196
240
|
if ((0, malloy_types_1.isPersistableSourceDef)(root) && root.sourceID) {
|
|
197
241
|
return processSourceID(root.sourceID);
|
|
198
242
|
}
|
|
199
243
|
return processSourceDef(root);
|
|
244
|
+
}));
|
|
245
|
+
}
|
|
246
|
+
/**
|
|
247
|
+
* The persistent sources a source or query depends on, as a nested DAG.
|
|
248
|
+
*
|
|
249
|
+
* A source that is not itself persistent never appears; its persistent
|
|
250
|
+
* dependencies become direct dependencies of whoever referenced it. So
|
|
251
|
+
* `c (persist) → b → a (persist)` yields `[{sourceID: a, dependsOn: []}]`.
|
|
252
|
+
*
|
|
253
|
+
* @deprecated The nested shape exists for `Model.getBuildPlan()`, which is on
|
|
254
|
+
* its way out. Consume {@link walkPersistentDependencies} directly.
|
|
255
|
+
*/
|
|
256
|
+
function findPersistentDependencies(root, modelDef, tagParseLog = []) {
|
|
257
|
+
var _a, _b;
|
|
258
|
+
// What each source contributes to whoever referenced it: itself if it is a
|
|
259
|
+
// table, otherwise whatever it was a route to. Post-order means every
|
|
260
|
+
// dependency is already in the map by the time its dependent arrives.
|
|
261
|
+
const contributes = new Map();
|
|
262
|
+
const walk = walkPersistentDependencies([root], modelDef, tagParseLog);
|
|
263
|
+
let step = walk.next();
|
|
264
|
+
while (!step.done) {
|
|
265
|
+
const node = step.value;
|
|
266
|
+
// Two of a source's references can lead to one table — a join and an
|
|
267
|
+
// extend of the same thing, say. That is one dependency, listed once.
|
|
268
|
+
const dependsOn = [];
|
|
269
|
+
const seen = new Set();
|
|
270
|
+
for (const id of node.dependsOn) {
|
|
271
|
+
for (const dep of (_a = contributes.get(id)) !== null && _a !== void 0 ? _a : []) {
|
|
272
|
+
if (!seen.has(dep)) {
|
|
273
|
+
seen.add(dep);
|
|
274
|
+
dependsOn.push(dep);
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
contributes.set(node.sourceID, node.persistent ? [{ sourceID: node.sourceID, dependsOn }] : dependsOn);
|
|
279
|
+
step = walk.next();
|
|
200
280
|
}
|
|
281
|
+
// The generator's return value is what the root itself reached — the only
|
|
282
|
+
// thing a `for...of` would throw away.
|
|
283
|
+
//
|
|
284
|
+
// Deduplicated because two routes can land on one table: a source reached
|
|
285
|
+
// through two different `#@ -persist` wrappers contributes the same node
|
|
286
|
+
// twice, and this function has always returned each node once.
|
|
287
|
+
const roots = [];
|
|
288
|
+
const rootSeen = new Set();
|
|
289
|
+
for (const id of step.value) {
|
|
290
|
+
for (const node of (_b = contributes.get(id)) !== null && _b !== void 0 ? _b : []) {
|
|
291
|
+
if (!rootSeen.has(node)) {
|
|
292
|
+
rootSeen.add(node);
|
|
293
|
+
roots.push(node);
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
return roots;
|
|
201
298
|
}
|
|
202
299
|
/**
|
|
203
300
|
* Collect all sourceIDs from a BuildNode forest (for analysis only).
|
|
301
|
+
*
|
|
302
|
+
* A forest shares nodes — one memoized node is reachable from every dependent
|
|
303
|
+
* that reads it — so the walk tracks which nodes it has already descended into.
|
|
204
304
|
*/
|
|
205
305
|
function collectAllSourceIDs(nodes) {
|
|
206
306
|
const result = new Set();
|
|
207
|
-
|
|
307
|
+
const seen = new Set();
|
|
308
|
+
function visit(node) {
|
|
309
|
+
if (seen.has(node))
|
|
310
|
+
return;
|
|
311
|
+
seen.add(node);
|
|
208
312
|
result.add(node.sourceID);
|
|
209
|
-
for (const
|
|
210
|
-
|
|
211
|
-
}
|
|
313
|
+
for (const dep of node.dependsOn)
|
|
314
|
+
visit(dep);
|
|
212
315
|
}
|
|
316
|
+
for (const node of nodes)
|
|
317
|
+
visit(node);
|
|
213
318
|
return result;
|
|
214
319
|
}
|
|
215
320
|
/**
|
|
@@ -217,14 +322,18 @@ function collectAllSourceIDs(nodes) {
|
|
|
217
322
|
*/
|
|
218
323
|
function collectAllDependedOn(nodes) {
|
|
219
324
|
const result = new Set();
|
|
220
|
-
|
|
325
|
+
const seen = new Set();
|
|
326
|
+
function visit(node) {
|
|
327
|
+
if (seen.has(node))
|
|
328
|
+
return;
|
|
329
|
+
seen.add(node);
|
|
221
330
|
for (const dep of node.dependsOn) {
|
|
222
331
|
result.add(dep.sourceID);
|
|
223
|
-
|
|
224
|
-
for (const id of collectAllDependedOn(node.dependsOn)) {
|
|
225
|
-
result.add(id);
|
|
332
|
+
visit(dep);
|
|
226
333
|
}
|
|
227
334
|
}
|
|
335
|
+
for (const node of nodes)
|
|
336
|
+
visit(node);
|
|
228
337
|
return result;
|
|
229
338
|
}
|
|
230
339
|
/**
|
package/dist/version.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const MALLOY_VERSION = "0.0.
|
|
1
|
+
export declare const MALLOY_VERSION = "0.0.430";
|
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.430';
|
|
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.430",
|
|
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.430",
|
|
55
|
+
"@malloydata/malloy-interfaces": "0.0.430",
|
|
56
|
+
"@malloydata/malloy-tag": "0.0.430",
|
|
57
57
|
"@noble/hashes": "^1.8.0",
|
|
58
58
|
"antlr4ts": "^0.5.0-alpha.4",
|
|
59
59
|
"assert": "^2.0.0",
|