@malloydata/malloy 0.0.303 → 0.0.305
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/dialect/dialect.d.ts +1 -1
- package/dist/dialect/duckdb/duckdb.d.ts +1 -1
- package/dist/dialect/duckdb/duckdb.js +2 -6
- package/dist/dialect/mysql/mysql.d.ts +1 -1
- package/dist/dialect/mysql/mysql.js +2 -6
- package/dist/dialect/postgres/postgres.d.ts +1 -1
- package/dist/dialect/postgres/postgres.js +2 -6
- package/dist/dialect/snowflake/snowflake.d.ts +1 -1
- package/dist/dialect/snowflake/snowflake.js +2 -5
- package/dist/dialect/standardsql/standardsql.d.ts +1 -1
- package/dist/dialect/standardsql/standardsql.js +2 -6
- package/dist/dialect/trino/trino.d.ts +1 -1
- package/dist/dialect/trino/trino.js +2 -6
- package/dist/index.d.ts +1 -1
- package/dist/index.js +2 -3
- package/dist/lang/ast/expressions/expr-aggregate-function.js +12 -2
- package/dist/lang/ast/expressions/expr-count.js +3 -1
- package/dist/lang/ast/expressions/expr-func.js +34 -10
- package/dist/lang/ast/expressions/expr-props.js +1 -1
- package/dist/lang/ast/expressions/expr-ungroup.js +7 -3
- package/dist/lang/ast/expressions/function-ordering.d.ts +19 -5
- package/dist/lang/ast/expressions/function-ordering.js +61 -9
- package/dist/lang/ast/field-space/include-utils.js +1 -1
- package/dist/lang/ast/field-space/index-field-space.js +3 -1
- package/dist/lang/ast/field-space/query-spaces.js +20 -11
- package/dist/lang/ast/query-builders/index-builder.js +1 -1
- package/dist/lang/ast/query-builders/reduce-builder.js +1 -1
- package/dist/lang/ast/query-elements/query-arrow.js +14 -4
- package/dist/lang/ast/query-elements/query-base.d.ts +1 -0
- package/dist/lang/ast/query-elements/query-base.js +14 -4
- package/dist/lang/ast/query-elements/query-refine.js +2 -0
- package/dist/lang/ast/query-properties/drill.js +1 -1
- package/dist/lang/ast/source-properties/join.js +6 -2
- package/dist/lang/ast/statements/define-source.js +1 -1
- package/dist/lang/ast/types/expr-value.js +1 -1
- package/dist/lang/ast/view-elements/reference-view.js +4 -1
- package/dist/lang/ast/view-elements/refine-utils.js +1 -1
- package/dist/{model/composite_source_utils.d.ts → lang/composite-source-utils.d.ts} +4 -17
- package/dist/{model/composite_source_utils.js → lang/composite-source-utils.js} +274 -44
- package/dist/lang/test/parse-expects.d.ts +1 -1
- package/dist/lang/test/parse-expects.js +6 -2
- package/dist/lang/test/test-translator.js +1 -1
- package/dist/malloy.js +1 -1
- package/dist/model/expression_compiler.d.ts +27 -0
- package/dist/model/expression_compiler.js +780 -0
- package/dist/model/field_instance.d.ts +108 -0
- package/dist/model/field_instance.js +520 -0
- package/dist/model/index.d.ts +5 -1
- package/dist/model/index.js +25 -4
- package/dist/model/join_instance.d.ts +18 -0
- package/dist/model/join_instance.js +71 -0
- package/dist/model/malloy_types.d.ts +48 -2
- package/dist/model/malloy_types.js +39 -1
- package/dist/model/query_model.d.ts +2 -0
- package/dist/model/query_model.js +7 -0
- package/dist/model/query_model_contract.d.ts +32 -0
- package/dist/model/query_model_contract.js +7 -0
- package/dist/model/query_model_impl.d.ts +30 -0
- package/dist/model/query_model_impl.js +266 -0
- package/dist/model/query_node.d.ts +132 -0
- package/dist/model/query_node.js +638 -0
- package/dist/model/query_query.d.ts +86 -0
- package/dist/model/query_query.js +1724 -0
- package/dist/model/sql_block.js +2 -2
- package/dist/model/stage_writer.d.ts +25 -0
- package/dist/model/stage_writer.js +120 -0
- package/dist/model/utils.d.ts +18 -1
- package/dist/model/utils.js +66 -1
- package/dist/to_stable.js +3 -4
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +4 -4
- package/dist/model/malloy_query.d.ts +0 -391
- package/dist/model/malloy_query.js +0 -3926
|
@@ -3,9 +3,10 @@
|
|
|
3
3
|
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
4
4
|
*
|
|
5
5
|
* This source code is licensed under the MIT license found in the
|
|
6
|
-
*
|
|
6
|
+
* LICENSE file in the root directory of this source tree.
|
|
7
7
|
*/
|
|
8
8
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
|
+
exports.getExpandedSegment = getExpandedSegment;
|
|
9
10
|
exports.getPartitionCompositeDesc = getPartitionCompositeDesc;
|
|
10
11
|
exports.resolveCompositeSources = resolveCompositeSources;
|
|
11
12
|
exports.fieldUsagePaths = fieldUsagePaths;
|
|
@@ -24,10 +25,10 @@ exports.pathEq = pathEq;
|
|
|
24
25
|
exports.pathBegins = pathBegins;
|
|
25
26
|
exports.hasCompositesAnywhere = hasCompositesAnywhere;
|
|
26
27
|
exports.logCompositeError = logCompositeError;
|
|
27
|
-
exports.compileFilterExpression = compileFilterExpression;
|
|
28
28
|
const malloy_filter_1 = require("@malloydata/malloy-filter");
|
|
29
|
-
const malloy_types_1 = require("
|
|
30
|
-
const utils_1 = require("
|
|
29
|
+
const malloy_types_1 = require("../model/malloy_types");
|
|
30
|
+
const utils_1 = require("./utils");
|
|
31
|
+
const utils_2 = require("../model/utils");
|
|
31
32
|
const annotation_1 = require("../annotation");
|
|
32
33
|
function _resolveCompositeSources(path, source, rootFields, nests, fieldUsage,
|
|
33
34
|
// for resolving nested composites; the list of sources to try
|
|
@@ -38,7 +39,7 @@ sources) {
|
|
|
38
39
|
let anyComposites = false;
|
|
39
40
|
let joinsProcessed = false;
|
|
40
41
|
const nonCompositeFields = getNonCompositeFields(source);
|
|
41
|
-
const expandedForError = onlyCompositeUsage(
|
|
42
|
+
const expandedForError = onlyCompositeUsage(_expandFieldUsage(fieldUsage, rootFields).result, source.fields);
|
|
42
43
|
if (source.type === 'composite') {
|
|
43
44
|
let found = false;
|
|
44
45
|
anyComposites = true;
|
|
@@ -63,9 +64,9 @@ sources) {
|
|
|
63
64
|
fieldNames.add((_a = field.as) !== null && _a !== void 0 ? _a : field.name);
|
|
64
65
|
}
|
|
65
66
|
}
|
|
66
|
-
const fieldUsageWithWheres = (_b = mergeFieldUsage(
|
|
67
|
+
const fieldUsageWithWheres = (_b = mergeFieldUsage(getFieldUsageFromFilterList(inputSource), fieldUsage)) !== null && _b !== void 0 ? _b : [];
|
|
67
68
|
const fieldsForLookup = [...nonCompositeFields, ...inputSource.fields];
|
|
68
|
-
const expanded =
|
|
69
|
+
const expanded = _expandFieldUsage(fieldUsageWithWheres, fieldsForLookup);
|
|
69
70
|
if (expanded.missingFields.length > 0) {
|
|
70
71
|
// A lookup failed while expanding, which means this source certainly won't work
|
|
71
72
|
for (const missingField of expanded.missingFields) {
|
|
@@ -83,7 +84,7 @@ sources) {
|
|
|
83
84
|
const expandedCategorized = categorizeFieldUsage(expanded.result);
|
|
84
85
|
const compositeUsageInThisSource = onlyCompositeUsage(expandedCategorized.sourceUsage, source.fields);
|
|
85
86
|
for (const usage of compositeUsageInThisSource) {
|
|
86
|
-
if (!fieldNames.has(usage.path[0])) {
|
|
87
|
+
if (usage.path.length > 0 && !fieldNames.has(usage.path[0])) {
|
|
87
88
|
fail({
|
|
88
89
|
type: 'missing-field',
|
|
89
90
|
field: usage,
|
|
@@ -176,7 +177,7 @@ sources) {
|
|
|
176
177
|
}
|
|
177
178
|
else if (source.partitionComposite !== undefined) {
|
|
178
179
|
anyComposites = true;
|
|
179
|
-
const expanded =
|
|
180
|
+
const expanded = _expandFieldUsage(fieldUsage, rootFields).result;
|
|
180
181
|
// TODO possibly abort if expanded has missing fields...
|
|
181
182
|
const expandedCategorized = categorizeFieldUsage(expanded);
|
|
182
183
|
const { partitionFilter, issues } = getPartitionCompositeFilter(source.partitionComposite, expandedCategorized.sourceUsage);
|
|
@@ -198,7 +199,7 @@ sources) {
|
|
|
198
199
|
};
|
|
199
200
|
}
|
|
200
201
|
if (!joinsProcessed) {
|
|
201
|
-
const expanded =
|
|
202
|
+
const expanded = _expandFieldUsage(fieldUsage, getJoinFields(rootFields, path));
|
|
202
203
|
if (expanded.missingFields.length > 0) {
|
|
203
204
|
return {
|
|
204
205
|
error: {
|
|
@@ -228,40 +229,192 @@ function onlyCompositeUsage(fieldUsage, fields) {
|
|
|
228
229
|
}
|
|
229
230
|
});
|
|
230
231
|
}
|
|
231
|
-
function
|
|
232
|
+
function getExpandedSegment(segment, inputSource) {
|
|
232
233
|
var _a;
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
const
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
//
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
234
|
+
if (segment.type === 'raw')
|
|
235
|
+
return segment;
|
|
236
|
+
const sourceExtensions = (0, malloy_types_1.isQuerySegment)(segment)
|
|
237
|
+
? (_a = segment.extendSource) !== null && _a !== void 0 ? _a : []
|
|
238
|
+
: [];
|
|
239
|
+
const fields = mergeFields(inputSource.fields, sourceExtensions);
|
|
240
|
+
const collectedUngroupings = [];
|
|
241
|
+
let updatedSegment = segment;
|
|
242
|
+
if ((0, malloy_types_1.isQuerySegment)(segment)) {
|
|
243
|
+
// Single walk through query fields
|
|
244
|
+
const updatedQueryFields = segment.queryFields.map(field => {
|
|
245
|
+
if (field.type === 'fieldref') {
|
|
246
|
+
// Just return it - field usage already in segment.fieldUsage
|
|
247
|
+
return field;
|
|
248
|
+
}
|
|
249
|
+
else if (field.type === 'turtle') {
|
|
250
|
+
if (field.pipeline.length === 0)
|
|
251
|
+
return field;
|
|
252
|
+
// Process entire turtle pipeline
|
|
253
|
+
const updatedPipeline = [];
|
|
254
|
+
let turtleInput = inputSource; // First stage sees parent's input
|
|
255
|
+
for (const stage of field.pipeline) {
|
|
256
|
+
const processedStage = getExpandedSegment(stage, turtleInput);
|
|
257
|
+
updatedPipeline.push(processedStage);
|
|
258
|
+
if (processedStage.type === 'raw')
|
|
259
|
+
continue;
|
|
260
|
+
// Collect ungroupings from turtle with adjusted paths
|
|
261
|
+
if (processedStage.expandedUngroupings) {
|
|
262
|
+
const adjusted = processedStage.expandedUngroupings.map(u => ({
|
|
263
|
+
...u,
|
|
264
|
+
path: [field.name, ...u.path],
|
|
265
|
+
}));
|
|
266
|
+
collectedUngroupings.push(...adjusted);
|
|
267
|
+
}
|
|
268
|
+
turtleInput = processedStage.outputStruct;
|
|
269
|
+
}
|
|
270
|
+
return { ...field, pipeline: updatedPipeline };
|
|
271
|
+
}
|
|
272
|
+
else {
|
|
273
|
+
// Regular fields - only collect ungroupings
|
|
274
|
+
collectedUngroupings.push(...(field.ungroupings || []));
|
|
275
|
+
return field;
|
|
276
|
+
}
|
|
277
|
+
});
|
|
278
|
+
updatedSegment = { ...segment, queryFields: updatedQueryFields };
|
|
279
|
+
}
|
|
280
|
+
const allFieldUsage = mergeFieldUsage(getFieldUsageFromFilterList(inputSource), segment.fieldUsage);
|
|
281
|
+
const expanded = _expandFieldUsage(allFieldUsage || [], fields);
|
|
282
|
+
// Merge ungroupings from direct collection and field expansion
|
|
283
|
+
const allUngroupings = [...collectedUngroupings, ...expanded.ungroupings];
|
|
284
|
+
return {
|
|
285
|
+
...updatedSegment,
|
|
286
|
+
expandedFieldUsage: expanded.result,
|
|
287
|
+
activeJoins: expanded.activeJoins,
|
|
288
|
+
expandedUngroupings: allUngroupings,
|
|
289
|
+
};
|
|
290
|
+
}
|
|
291
|
+
function getJoin(jdMap, joinKey, joinPath) {
|
|
292
|
+
if (!jdMap[joinKey]) {
|
|
293
|
+
jdMap[joinKey] = { path: joinPath, dependsOn: new Set() };
|
|
294
|
+
}
|
|
295
|
+
return jdMap[joinKey];
|
|
296
|
+
}
|
|
297
|
+
function findActiveJoins(dependencies) {
|
|
298
|
+
const sorted = [];
|
|
299
|
+
const visited = new Set();
|
|
300
|
+
const visiting = new Set();
|
|
301
|
+
const visit = (key) => {
|
|
302
|
+
if (visited.has(key))
|
|
303
|
+
return;
|
|
304
|
+
if (visiting.has(key)) {
|
|
305
|
+
return;
|
|
246
306
|
}
|
|
247
|
-
|
|
307
|
+
visiting.add(key);
|
|
308
|
+
const dep = dependencies[key];
|
|
309
|
+
if (dep) {
|
|
310
|
+
// Visit all dependencies first (depth-first)
|
|
311
|
+
for (const depKey of dep.dependsOn) {
|
|
312
|
+
visit(depKey);
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
visiting.delete(key);
|
|
316
|
+
visited.add(key);
|
|
317
|
+
// Add this join's path to the sorted list after its dependencies
|
|
318
|
+
if (dep) {
|
|
319
|
+
sorted.push({ path: dep.path });
|
|
320
|
+
}
|
|
321
|
+
};
|
|
322
|
+
// Visit all joins in the dependency graph
|
|
323
|
+
for (const key of Object.keys(dependencies)) {
|
|
324
|
+
visit(key);
|
|
325
|
+
}
|
|
326
|
+
return sorted;
|
|
327
|
+
}
|
|
328
|
+
/**
|
|
329
|
+
* Given a list of field usage requests, expand to include all the join on and join filter expressions
|
|
330
|
+
* needed to be able to reference those fields.
|
|
331
|
+
*
|
|
332
|
+
* @returns An object containing:
|
|
333
|
+
* - `result`: The expanded field usage, including usages from necessary joins
|
|
334
|
+
* - `missingFields`: References to fields which could not be resolved
|
|
335
|
+
* - `activeJoins`: Topologically sorted list of joins needed to resolve these uses
|
|
336
|
+
*/
|
|
337
|
+
function _expandFieldUsage(fieldUsage, fields) {
|
|
338
|
+
var _a, _b, _c;
|
|
339
|
+
const seen = {};
|
|
340
|
+
const missingFields = [];
|
|
341
|
+
const toProcess = [];
|
|
342
|
+
const activeJoinGraph = {};
|
|
343
|
+
const ungroupings = [];
|
|
344
|
+
// Initialize: mark original inputs and add them to processing queue
|
|
345
|
+
for (const usage of fieldUsage) {
|
|
346
|
+
const seenKey = (0, utils_2.pathToKey)('field', usage.path);
|
|
347
|
+
seen[seenKey] = usage;
|
|
348
|
+
toProcess.push(usage);
|
|
349
|
+
}
|
|
350
|
+
// Process the expanding queue
|
|
351
|
+
const fieldNameSpace = buildNamespace(fields);
|
|
352
|
+
for (let i = 0; i < toProcess.length; i++) {
|
|
353
|
+
const reference = toProcess[i];
|
|
354
|
+
if (reference.path.length === 0)
|
|
355
|
+
continue;
|
|
356
|
+
const def = inNamespace(reference.path, fieldNameSpace);
|
|
357
|
+
if (!def) {
|
|
248
358
|
missingFields.push(reference);
|
|
249
359
|
continue;
|
|
250
360
|
}
|
|
251
361
|
if ((0, malloy_types_1.isAtomic)(def)) {
|
|
252
362
|
const fieldUsage = (_a = def.fieldUsage) !== null && _a !== void 0 ? _a : [];
|
|
253
|
-
|
|
363
|
+
// Add the atomic field's dependencies to the queue
|
|
364
|
+
const refPath = reference.path.slice(0, -1);
|
|
365
|
+
for (const usage of joinedFieldUsage(refPath, fieldUsage)) {
|
|
366
|
+
const key = (0, utils_2.pathToKey)('field', usage.path);
|
|
367
|
+
if (!seen[key]) {
|
|
368
|
+
seen[key] = usage;
|
|
369
|
+
toProcess.push(usage);
|
|
370
|
+
}
|
|
371
|
+
else if (usage.uniqueKeyRequirement) {
|
|
372
|
+
seen[key].uniqueKeyRequirement = {
|
|
373
|
+
isCount: (_b = usage.uniqueKeyRequirement.isCount) !== null && _b !== void 0 ? _b : (_c = seen[key].uniqueKeyRequirement) === null || _c === void 0 ? void 0 : _c.isCount,
|
|
374
|
+
};
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
if (def.ungroupings) {
|
|
378
|
+
ungroupings.push(...joinedUngroupings(refPath, def.ungroupings));
|
|
379
|
+
}
|
|
254
380
|
}
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
381
|
+
// For paths through joins, additionaly track join relationships
|
|
382
|
+
for (let joinLen = 1; joinLen < reference.path.length; joinLen++) {
|
|
383
|
+
const joinPath = reference.path.slice(0, joinLen);
|
|
384
|
+
const joinDef = inNamespace(joinPath, fieldNameSpace);
|
|
385
|
+
if (!joinDef)
|
|
386
|
+
break;
|
|
387
|
+
const joinKey = (0, utils_2.pathToKey)('join', joinPath);
|
|
388
|
+
const thisDep = getJoin(activeJoinGraph, joinKey, joinPath);
|
|
389
|
+
if ((0, malloy_types_1.isJoined)(joinDef) && !thisDep.checked) {
|
|
390
|
+
thisDep.checked = true;
|
|
391
|
+
const joinFieldUsage = getJoinFieldUsage(joinDef, joinPath);
|
|
392
|
+
// Add join's field dependencies to the queue
|
|
393
|
+
for (const usage of joinFieldUsage) {
|
|
394
|
+
const key = (0, utils_2.pathToKey)('field', usage.path);
|
|
395
|
+
if (!seen[key]) {
|
|
396
|
+
seen[key] = usage;
|
|
397
|
+
toProcess.push(usage);
|
|
398
|
+
}
|
|
399
|
+
// Track join-to-join dependencies
|
|
400
|
+
const isInternalReference = usage.path.length === joinPath.length + 1 &&
|
|
401
|
+
pathBegins(usage.path, joinPath);
|
|
402
|
+
if (!isInternalReference && usage.path.length > 1) {
|
|
403
|
+
const dependencyPath = usage.path.slice(0, -1);
|
|
404
|
+
const dependencyKey = (0, utils_2.pathToKey)('join', dependencyPath);
|
|
405
|
+
getJoin(activeJoinGraph, dependencyKey, dependencyPath);
|
|
406
|
+
thisDep.dependsOn.add(dependencyKey);
|
|
407
|
+
}
|
|
408
|
+
}
|
|
261
409
|
}
|
|
262
410
|
}
|
|
263
411
|
}
|
|
264
|
-
return {
|
|
412
|
+
return {
|
|
413
|
+
result: Object.values(seen),
|
|
414
|
+
missingFields,
|
|
415
|
+
activeJoins: findActiveJoins(activeJoinGraph),
|
|
416
|
+
ungroupings,
|
|
417
|
+
};
|
|
265
418
|
}
|
|
266
419
|
function categorizeFieldUsage(fieldUsage) {
|
|
267
420
|
var _a;
|
|
@@ -271,7 +424,7 @@ function categorizeFieldUsage(fieldUsage) {
|
|
|
271
424
|
joinUsage: {},
|
|
272
425
|
};
|
|
273
426
|
for (const usage of fieldUsage) {
|
|
274
|
-
if (usage.path.length
|
|
427
|
+
if (usage.path.length <= 1) {
|
|
275
428
|
categorized.sourceUsage.push(usage);
|
|
276
429
|
}
|
|
277
430
|
else {
|
|
@@ -497,18 +650,25 @@ function processJoins(path, base, rootFields, nests, categorizedFieldUsage) {
|
|
|
497
650
|
}
|
|
498
651
|
return { anyComposites, errors };
|
|
499
652
|
}
|
|
653
|
+
function segmentFieldUsage(segment) {
|
|
654
|
+
var _a;
|
|
655
|
+
return ((_a = ((0, malloy_types_1.isQuerySegment)(segment) || (0, malloy_types_1.isIndexSegment)(segment)
|
|
656
|
+
? segment.fieldUsage
|
|
657
|
+
: undefined)) !== null && _a !== void 0 ? _a : emptyFieldUsage());
|
|
658
|
+
}
|
|
500
659
|
function getFieldUsageFromFilterList(source) {
|
|
501
660
|
var _a;
|
|
502
661
|
return ((_a = source.filterList) !== null && _a !== void 0 ? _a : []).flatMap(filter => { var _a; return (_a = filter.fieldUsage) !== null && _a !== void 0 ? _a : []; });
|
|
503
662
|
}
|
|
504
|
-
function resolveCompositeSources(source, segment
|
|
663
|
+
function resolveCompositeSources(source, segment) {
|
|
505
664
|
var _a, _b;
|
|
665
|
+
const fieldUsage = segmentFieldUsage(segment);
|
|
506
666
|
const sourceExtensions = (0, malloy_types_1.isQuerySegment)(segment)
|
|
507
667
|
? (_a = segment.extendSource) !== null && _a !== void 0 ? _a : []
|
|
508
668
|
: [];
|
|
509
669
|
const nestLevels = extractNestLevels(segment);
|
|
510
670
|
const fields = mergeFields(source.fields, sourceExtensions);
|
|
511
|
-
const fieldUsageWithWheres = (_b = mergeFieldUsage(
|
|
671
|
+
const fieldUsageWithWheres = (_b = mergeFieldUsage(getFieldUsageFromFilterList(source), fieldUsage)) !== null && _b !== void 0 ? _b : [];
|
|
512
672
|
const result = _resolveCompositeSources([], source, fields, nestLevels, fieldUsageWithWheres);
|
|
513
673
|
if ('success' in result) {
|
|
514
674
|
if (result.anyComposites) {
|
|
@@ -623,7 +783,7 @@ function nestLevelsAt(nests, at) {
|
|
|
623
783
|
return {
|
|
624
784
|
fieldsReferencedDirectly: fieldUsageAt(nests.fieldsReferencedDirectly, at),
|
|
625
785
|
nested: nests.nested.map(n => nestLevelsAt(n, at)),
|
|
626
|
-
fieldsReferenced: fieldUsageAt(nests.
|
|
786
|
+
fieldsReferenced: fieldUsageAt(nests.fieldsReferenced, at),
|
|
627
787
|
ungroupings: ungroupingsAt(nests.ungroupings, at),
|
|
628
788
|
requiredGroupBys: (_a = requiredGroupBysAt(nests.requiredGroupBys, at)) !== null && _a !== void 0 ? _a : [],
|
|
629
789
|
singleValueFilters: nests.singleValueFilters,
|
|
@@ -664,6 +824,7 @@ function joinedUngroupings(joinPath, ungroupings) {
|
|
|
664
824
|
...u,
|
|
665
825
|
fieldUsage: joinedFieldUsage(joinPath, u.fieldUsage),
|
|
666
826
|
requiresGroupBy: joinedRequiredGroupBys(joinPath, u.requiresGroupBy),
|
|
827
|
+
path: joinPath, // Set the path to the join path
|
|
667
828
|
}));
|
|
668
829
|
}
|
|
669
830
|
function extractNestLevels(segment) {
|
|
@@ -688,7 +849,32 @@ function extractNestLevels(segment) {
|
|
|
688
849
|
}
|
|
689
850
|
else if (field.type === 'turtle') {
|
|
690
851
|
const head = field.pipeline[0];
|
|
691
|
-
|
|
852
|
+
const nestedLevels = extractNestLevels(head);
|
|
853
|
+
// Check if the nested query has ANY unique key requirements
|
|
854
|
+
let hasNestedUniqueKeyReqs = nestedLevels.fieldsReferenced.some(usage => usage.uniqueKeyRequirement);
|
|
855
|
+
// Also check the head segment's fieldUsage directly
|
|
856
|
+
if ((0, malloy_types_1.isQuerySegment)(head) && head.fieldUsage) {
|
|
857
|
+
const hasDirectUniqueKeyReqs = head.fieldUsage.some(usage => usage.uniqueKeyRequirement);
|
|
858
|
+
if (hasDirectUniqueKeyReqs) {
|
|
859
|
+
hasNestedUniqueKeyReqs = true;
|
|
860
|
+
}
|
|
861
|
+
}
|
|
862
|
+
// If the nested query has any unique key requirements, parent needs unique input
|
|
863
|
+
if (hasNestedUniqueKeyReqs) {
|
|
864
|
+
fieldsReferenced.push({
|
|
865
|
+
path: [],
|
|
866
|
+
uniqueKeyRequirement: { isCount: true },
|
|
867
|
+
at: head.referencedAt,
|
|
868
|
+
});
|
|
869
|
+
}
|
|
870
|
+
const adjustedUngroupings = nestedLevels.ungroupings.map(u => ({
|
|
871
|
+
...u,
|
|
872
|
+
path: [field.name, ...u.path],
|
|
873
|
+
}));
|
|
874
|
+
nested.push(nestLevelsAt({
|
|
875
|
+
...nestedLevels,
|
|
876
|
+
ungroupings: adjustedUngroupings,
|
|
877
|
+
}, head.referencedAt));
|
|
692
878
|
}
|
|
693
879
|
else {
|
|
694
880
|
const fieldUsage = (_a = field.fieldUsage) !== null && _a !== void 0 ? _a : [];
|
|
@@ -734,7 +920,7 @@ function getSingleValueFilterFields(filter) {
|
|
|
734
920
|
function isSingleValueFilterNode(e) {
|
|
735
921
|
if (e.node === 'filterMatch') {
|
|
736
922
|
if (e.kids.expr.node === 'field') {
|
|
737
|
-
const result =
|
|
923
|
+
const result = translateFilterExpression(e.dataType, e.kids.filterExpr);
|
|
738
924
|
if (!result)
|
|
739
925
|
return [];
|
|
740
926
|
if ((result.parsed.operator === 'null' && !result.parsed.not) ||
|
|
@@ -783,6 +969,8 @@ function expandRefs(nests, fields) {
|
|
|
783
969
|
const joinPathsProcessed = [];
|
|
784
970
|
const missingFields = [];
|
|
785
971
|
for (let i = 0; i < references.length; i++) {
|
|
972
|
+
if (references[i].path.length === 0)
|
|
973
|
+
continue;
|
|
786
974
|
const field = references[i];
|
|
787
975
|
let def;
|
|
788
976
|
try {
|
|
@@ -795,6 +983,16 @@ function expandRefs(nests, fields) {
|
|
|
795
983
|
const joinPath = field.path.slice(0, -1);
|
|
796
984
|
if ((0, malloy_types_1.isTurtle)(def)) {
|
|
797
985
|
const head = def.pipeline[0];
|
|
986
|
+
const nestedLevels = extractNestLevels(head);
|
|
987
|
+
// Update paths for ungroupings in nested turtle
|
|
988
|
+
const adjustedLevels = {
|
|
989
|
+
...nestedLevels,
|
|
990
|
+
ungroupings: nestedLevels.ungroupings.map(u => ({
|
|
991
|
+
...u,
|
|
992
|
+
path: field.path, // This IS the correct path to the turtle field
|
|
993
|
+
})),
|
|
994
|
+
};
|
|
995
|
+
newNests.push(adjustedLevels);
|
|
798
996
|
newNests.push(extractNestLevels(head));
|
|
799
997
|
}
|
|
800
998
|
else if ((0, malloy_types_1.isAtomic)(def)) {
|
|
@@ -852,6 +1050,7 @@ function expandRefs(nests, fields) {
|
|
|
852
1050
|
missingFields.push(...((_d = expanded.missingFields) !== null && _d !== void 0 ? _d : []));
|
|
853
1051
|
nested.push(expanded.result);
|
|
854
1052
|
unsatisfiableGroupBys.push(...expanded.result.unsatisfiableGroupBys);
|
|
1053
|
+
allUngroupings.push(...expanded.result.ungroupings);
|
|
855
1054
|
}
|
|
856
1055
|
return {
|
|
857
1056
|
result: {
|
|
@@ -860,6 +1059,7 @@ function expandRefs(nests, fields) {
|
|
|
860
1059
|
unsatisfiableGroupBys,
|
|
861
1060
|
nested,
|
|
862
1061
|
singleValueFilters: nests.singleValueFilters,
|
|
1062
|
+
ungroupings: allUngroupings,
|
|
863
1063
|
},
|
|
864
1064
|
missingFields: missingFields.length > 0 ? missingFields : undefined,
|
|
865
1065
|
};
|
|
@@ -867,11 +1067,11 @@ function expandRefs(nests, fields) {
|
|
|
867
1067
|
function getJoinFieldUsage(join, joinPath) {
|
|
868
1068
|
var _a, _b;
|
|
869
1069
|
return ((_b = mergeFieldUsage(
|
|
1070
|
+
// For `fieldUsage` from join `where`s, we need the path including the join name
|
|
1071
|
+
joinedFieldUsage(joinPath, (0, malloy_types_1.isSourceDef)(join) ? getFieldUsageFromFilterList(join) : []),
|
|
870
1072
|
// For `fieldUsage` from join `on`, we need the path excluding the join name, since it's
|
|
871
1073
|
// already rooted at the parent
|
|
872
|
-
joinedFieldUsage(joinPath.slice(0, -1), (_a = join.fieldUsage) !== null && _a !== void 0 ? _a : [])
|
|
873
|
-
// For `fieldUsage` from join `where`s, we need the path including the join name
|
|
874
|
-
joinedFieldUsage(joinPath, (0, malloy_types_1.isSourceDef)(join) ? getFieldUsageFromFilterList(join) : []))) !== null && _b !== void 0 ? _b : []);
|
|
1074
|
+
joinedFieldUsage(joinPath.slice(0, -1), (_a = join.fieldUsage) !== null && _a !== void 0 ? _a : []))) !== null && _b !== void 0 ? _b : []);
|
|
875
1075
|
}
|
|
876
1076
|
function isUngroupedBy(ungrouping, groupedBy) {
|
|
877
1077
|
if (ungrouping.ungroupedFields === '*')
|
|
@@ -912,6 +1112,36 @@ function pathEq(a, b) {
|
|
|
912
1112
|
function pathBegins(path, prefix) {
|
|
913
1113
|
return path.length >= prefix.length && prefix.every((s, i) => path[i] === s);
|
|
914
1114
|
}
|
|
1115
|
+
function buildNamespace(fields) {
|
|
1116
|
+
var _a;
|
|
1117
|
+
const namespace = {
|
|
1118
|
+
fields: {},
|
|
1119
|
+
nested: {},
|
|
1120
|
+
};
|
|
1121
|
+
for (const field of fields) {
|
|
1122
|
+
const name = (_a = field.as) !== null && _a !== void 0 ? _a : field.name;
|
|
1123
|
+
namespace.fields[name] = field;
|
|
1124
|
+
// If it's a join with nested fields, recursively build its hierarchy
|
|
1125
|
+
if ((0, malloy_types_1.isJoined)(field) && field.fields) {
|
|
1126
|
+
namespace.nested[name] = buildNamespace(field.fields);
|
|
1127
|
+
}
|
|
1128
|
+
}
|
|
1129
|
+
return namespace;
|
|
1130
|
+
}
|
|
1131
|
+
function inNamespace(path, space) {
|
|
1132
|
+
const head = path[0];
|
|
1133
|
+
const def = space.fields[head];
|
|
1134
|
+
if (def === undefined)
|
|
1135
|
+
return def;
|
|
1136
|
+
if (path.length === 1) {
|
|
1137
|
+
return def;
|
|
1138
|
+
}
|
|
1139
|
+
const nested = space.nested[head];
|
|
1140
|
+
if (!nested) {
|
|
1141
|
+
return undefined;
|
|
1142
|
+
}
|
|
1143
|
+
return inNamespace(path.slice(1), nested);
|
|
1144
|
+
}
|
|
915
1145
|
function lookup(field, fields) {
|
|
916
1146
|
const [head, ...rest] = field;
|
|
917
1147
|
const def = fields.find(f => { var _a; return ((_a = f.as) !== null && _a !== void 0 ? _a : f.name) === head; });
|
|
@@ -1033,7 +1263,7 @@ function logCompositeError(error, logTo) {
|
|
|
1033
1263
|
logTo.logError('could-not-resolve-composite-source', 'Could not resolve composite source');
|
|
1034
1264
|
}
|
|
1035
1265
|
}
|
|
1036
|
-
function
|
|
1266
|
+
function translateFilterExpression(ft, fexpr) {
|
|
1037
1267
|
if (fexpr.node !== 'filterLiteral') {
|
|
1038
1268
|
return undefined;
|
|
1039
1269
|
}
|
|
@@ -1060,4 +1290,4 @@ function compileFilterExpression(ft, fexpr) {
|
|
|
1060
1290
|
}
|
|
1061
1291
|
return undefined;
|
|
1062
1292
|
}
|
|
1063
|
-
//# sourceMappingURL=
|
|
1293
|
+
//# sourceMappingURL=composite-source-utils.js.map
|
|
@@ -23,6 +23,7 @@
|
|
|
23
23
|
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
24
24
|
*/
|
|
25
25
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
26
|
+
const model_1 = require("../../model");
|
|
26
27
|
const expr_to_str_1 = require("./expr-to-str");
|
|
27
28
|
const test_translator_1 = require("./test-translator");
|
|
28
29
|
function rangeToStr(loc) {
|
|
@@ -241,8 +242,11 @@ expect.extend({
|
|
|
241
242
|
return badRefs;
|
|
242
243
|
}
|
|
243
244
|
const actual = bx.generated().fieldUsage;
|
|
244
|
-
const actualPaths = actual.map(u => u.path);
|
|
245
|
-
|
|
245
|
+
const actualPaths = actual.filter(u => (0, model_1.bareFieldUsage)(u)).map(u => u.path);
|
|
246
|
+
// there is no guarantee of the order of field usage data, so we sort the two lists
|
|
247
|
+
// so i need to compare sorted versions of the two lists... we can sort on path.join('.)
|
|
248
|
+
// maybe make a lambda for that and pass it to sort
|
|
249
|
+
const pass = this.equals(actualPaths.sort((a, b) => a.join('.').localeCompare(b.join('.'))), paths.sort((a, b) => a.join('.').localeCompare(b.join('.'))));
|
|
246
250
|
const msg = pass
|
|
247
251
|
? `Matched: ${actual}`
|
|
248
252
|
: this.utils.diff(paths, actualPaths);
|
package/dist/malloy.js
CHANGED
|
@@ -2098,7 +2098,7 @@ class ModelMaterializer extends FluentState {
|
|
|
2098
2098
|
group_by: fieldName
|
|
2099
2099
|
aggregate: cardinality is count(fieldValue)
|
|
2100
2100
|
nest: values is {
|
|
2101
|
-
|
|
2101
|
+
group_by: fieldValue, weight
|
|
2102
2102
|
order_by: weight desc
|
|
2103
2103
|
limit: ${limit}
|
|
2104
2104
|
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { Expr, FunctionCallNode, FunctionOverloadDef, FunctionOrderBy, FieldnameNode, OutputFieldNode, GenericSQLExpr, FilteredExpr, UngroupNode, ParameterNode, SpreadExpr, AggregateExpr, SourceReferenceNode, CaseExpr } from './malloy_types';
|
|
2
|
+
import { type FieldInstanceResult } from './field_instance';
|
|
3
|
+
import { GenerateState } from './utils';
|
|
4
|
+
import type { QueryStruct, QueryField } from './query_node';
|
|
5
|
+
/**
|
|
6
|
+
* Converts an expression to SQL.
|
|
7
|
+
* This function was extracted from QueryField.exprToSQL to break circular dependencies.
|
|
8
|
+
*/
|
|
9
|
+
export declare function exprToSQL(field: QueryField, resultSet: FieldInstanceResult, context: QueryStruct, exprToTranslate: Expr, state?: GenerateState): string;
|
|
10
|
+
export declare function generateFunctionCallExpression(field: QueryField, resultSet: FieldInstanceResult, context: QueryStruct, frag: FunctionCallNode, state: GenerateState): string;
|
|
11
|
+
export declare function generateFieldFragment(field: QueryField, resultSet: FieldInstanceResult, context: QueryStruct, expr: FieldnameNode, state: GenerateState): string;
|
|
12
|
+
export declare function generateOutputFieldFragment(field: QueryField, resultSet: FieldInstanceResult, _context: QueryStruct, frag: OutputFieldNode, _state: GenerateState): string;
|
|
13
|
+
export declare function generateParameterFragment(field: QueryField, resultSet: FieldInstanceResult, context: QueryStruct, expr: ParameterNode, state: GenerateState): string;
|
|
14
|
+
export declare function generateFilterFragment(field: QueryField, resultSet: FieldInstanceResult, context: QueryStruct, expr: FilteredExpr, state: GenerateState): string;
|
|
15
|
+
export declare function generateDimFragment(field: QueryField, resultSet: FieldInstanceResult, context: QueryStruct, expr: Expr, state: GenerateState): string;
|
|
16
|
+
export declare function generateUngroupedFragment(field: QueryField, resultSet: FieldInstanceResult, context: QueryStruct, expr: UngroupNode, state: GenerateState): string;
|
|
17
|
+
export declare function generateDistinctKeyIfNecessary(field: QueryField, resultSet: FieldInstanceResult, context: QueryStruct, structPath: string[] | undefined): string | undefined;
|
|
18
|
+
export declare function generateSumFragment(field: QueryField, resultSet: FieldInstanceResult, context: QueryStruct, expr: AggregateExpr, state: GenerateState): string;
|
|
19
|
+
export declare function generateSymmetricFragment(field: QueryField, resultSet: FieldInstanceResult, context: QueryStruct, expr: AggregateExpr, state: GenerateState): string;
|
|
20
|
+
export declare function generateAvgFragment(field: QueryField, resultSet: FieldInstanceResult, context: QueryStruct, expr: AggregateExpr, state: GenerateState): string;
|
|
21
|
+
export declare function generateCountFragment(field: QueryField, resultSet: FieldInstanceResult, context: QueryStruct, expr: AggregateExpr, state: GenerateState): string;
|
|
22
|
+
export declare function generateSpread(field: QueryField, _resultSet: FieldInstanceResult, _context: QueryStruct, _frag: SpreadExpr, _state: GenerateState): string;
|
|
23
|
+
export declare function generateSourceReference(field: QueryField, resultSet: FieldInstanceResult, context: QueryStruct, expr: SourceReferenceNode): string;
|
|
24
|
+
export declare function generateCaseSQL(field: QueryField, pf: CaseExpr): string;
|
|
25
|
+
export declare function getFunctionOrderBy(field: QueryField, resultSet: FieldInstanceResult, context: QueryStruct, state: GenerateState, orderBy: FunctionOrderBy[], args: Expr[], overload: FunctionOverloadDef): string | undefined;
|
|
26
|
+
export declare function getAnalyticPartitions(field: QueryField, resultStruct: FieldInstanceResult, extraPartitionFields?: string[]): string[];
|
|
27
|
+
export declare function stringsFromSQLExpression(field: QueryField, resultSet: FieldInstanceResult, context: QueryStruct, e: GenericSQLExpr, state: GenerateState): Generator<string, void, unknown>;
|