@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.
Files changed (74) hide show
  1. package/dist/dialect/dialect.d.ts +1 -1
  2. package/dist/dialect/duckdb/duckdb.d.ts +1 -1
  3. package/dist/dialect/duckdb/duckdb.js +2 -6
  4. package/dist/dialect/mysql/mysql.d.ts +1 -1
  5. package/dist/dialect/mysql/mysql.js +2 -6
  6. package/dist/dialect/postgres/postgres.d.ts +1 -1
  7. package/dist/dialect/postgres/postgres.js +2 -6
  8. package/dist/dialect/snowflake/snowflake.d.ts +1 -1
  9. package/dist/dialect/snowflake/snowflake.js +2 -5
  10. package/dist/dialect/standardsql/standardsql.d.ts +1 -1
  11. package/dist/dialect/standardsql/standardsql.js +2 -6
  12. package/dist/dialect/trino/trino.d.ts +1 -1
  13. package/dist/dialect/trino/trino.js +2 -6
  14. package/dist/index.d.ts +1 -1
  15. package/dist/index.js +2 -3
  16. package/dist/lang/ast/expressions/expr-aggregate-function.js +12 -2
  17. package/dist/lang/ast/expressions/expr-count.js +3 -1
  18. package/dist/lang/ast/expressions/expr-func.js +34 -10
  19. package/dist/lang/ast/expressions/expr-props.js +1 -1
  20. package/dist/lang/ast/expressions/expr-ungroup.js +7 -3
  21. package/dist/lang/ast/expressions/function-ordering.d.ts +19 -5
  22. package/dist/lang/ast/expressions/function-ordering.js +61 -9
  23. package/dist/lang/ast/field-space/include-utils.js +1 -1
  24. package/dist/lang/ast/field-space/index-field-space.js +3 -1
  25. package/dist/lang/ast/field-space/query-spaces.js +20 -11
  26. package/dist/lang/ast/query-builders/index-builder.js +1 -1
  27. package/dist/lang/ast/query-builders/reduce-builder.js +1 -1
  28. package/dist/lang/ast/query-elements/query-arrow.js +14 -4
  29. package/dist/lang/ast/query-elements/query-base.d.ts +1 -0
  30. package/dist/lang/ast/query-elements/query-base.js +14 -4
  31. package/dist/lang/ast/query-elements/query-refine.js +2 -0
  32. package/dist/lang/ast/query-properties/drill.js +1 -1
  33. package/dist/lang/ast/source-properties/join.js +6 -2
  34. package/dist/lang/ast/statements/define-source.js +1 -1
  35. package/dist/lang/ast/types/expr-value.js +1 -1
  36. package/dist/lang/ast/view-elements/reference-view.js +4 -1
  37. package/dist/lang/ast/view-elements/refine-utils.js +1 -1
  38. package/dist/{model/composite_source_utils.d.ts → lang/composite-source-utils.d.ts} +4 -17
  39. package/dist/{model/composite_source_utils.js → lang/composite-source-utils.js} +274 -44
  40. package/dist/lang/test/parse-expects.d.ts +1 -1
  41. package/dist/lang/test/parse-expects.js +6 -2
  42. package/dist/lang/test/test-translator.js +1 -1
  43. package/dist/malloy.js +1 -1
  44. package/dist/model/expression_compiler.d.ts +27 -0
  45. package/dist/model/expression_compiler.js +780 -0
  46. package/dist/model/field_instance.d.ts +108 -0
  47. package/dist/model/field_instance.js +520 -0
  48. package/dist/model/index.d.ts +5 -1
  49. package/dist/model/index.js +25 -4
  50. package/dist/model/join_instance.d.ts +18 -0
  51. package/dist/model/join_instance.js +71 -0
  52. package/dist/model/malloy_types.d.ts +48 -2
  53. package/dist/model/malloy_types.js +39 -1
  54. package/dist/model/query_model.d.ts +2 -0
  55. package/dist/model/query_model.js +7 -0
  56. package/dist/model/query_model_contract.d.ts +32 -0
  57. package/dist/model/query_model_contract.js +7 -0
  58. package/dist/model/query_model_impl.d.ts +30 -0
  59. package/dist/model/query_model_impl.js +266 -0
  60. package/dist/model/query_node.d.ts +132 -0
  61. package/dist/model/query_node.js +638 -0
  62. package/dist/model/query_query.d.ts +86 -0
  63. package/dist/model/query_query.js +1724 -0
  64. package/dist/model/sql_block.js +2 -2
  65. package/dist/model/stage_writer.d.ts +25 -0
  66. package/dist/model/stage_writer.js +120 -0
  67. package/dist/model/utils.d.ts +18 -1
  68. package/dist/model/utils.js +66 -1
  69. package/dist/to_stable.js +3 -4
  70. package/dist/version.d.ts +1 -1
  71. package/dist/version.js +1 -1
  72. package/package.json +4 -4
  73. package/dist/model/malloy_query.d.ts +0 -391
  74. 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
- * LICENSE file in the root directory of this source tree.
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("./malloy_types");
30
- const utils_1 = require("../lang/utils");
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(expandFieldUsage(fieldUsage, rootFields).result, source.fields);
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(fieldUsage, getFieldUsageFromFilterList(inputSource))) !== null && _b !== void 0 ? _b : [];
67
+ const fieldUsageWithWheres = (_b = mergeFieldUsage(getFieldUsageFromFilterList(inputSource), fieldUsage)) !== null && _b !== void 0 ? _b : [];
67
68
  const fieldsForLookup = [...nonCompositeFields, ...inputSource.fields];
68
- const expanded = expandFieldUsage(fieldUsageWithWheres, fieldsForLookup);
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 = expandFieldUsage(fieldUsage, rootFields).result;
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 = expandFieldUsage(fieldUsage, getJoinFields(rootFields, path));
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 expandFieldUsage(fieldUsage, fields) {
232
+ function getExpandedSegment(segment, inputSource) {
232
233
  var _a;
233
- const allFieldPathsReferenced = [...fieldUsage];
234
- const joinPathsProcessed = [];
235
- const missingFields = [];
236
- for (let i = 0; i < allFieldPathsReferenced.length; i++) {
237
- const reference = allFieldPathsReferenced[i];
238
- const referenceJoinPath = reference.path.slice(0, -1);
239
- // Look up this referenced field; if it is a composite field, then add it to the list
240
- // of composite fields found;
241
- // if it has composite usage, add those usages to the list of fields to look up next
242
- // if it doesn't exist, then this source won't work.
243
- let def;
244
- try {
245
- def = lookup(reference.path, fields);
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
- catch {
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
- allFieldPathsReferenced.push(...fieldUsageAt(joinedFieldUsage(referenceJoinPath, fieldUsage), reference.at).filter(u1 => !allFieldPathsReferenced.some(u2 => pathEq(u1.path, u2.path))));
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
- if (reference.path.length > 1) {
256
- if (!joinPathsProcessed.some(p => pathEq(p, referenceJoinPath))) {
257
- joinPathsProcessed.push(referenceJoinPath);
258
- const join = lookup(referenceJoinPath, fields);
259
- const joinFieldUsage = getJoinFieldUsage(join, referenceJoinPath);
260
- allFieldPathsReferenced.push(...fieldUsageAt(joinFieldUsage, reference.at).filter(u1 => !allFieldPathsReferenced.some(u2 => pathEq(u1.path, u2.path))));
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 { result: allFieldPathsReferenced, missingFields };
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 === 1) {
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, fieldUsage) {
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(fieldUsage, getFieldUsageFromFilterList(source))) !== null && _b !== void 0 ? _b : [];
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.fieldsReferencedDirectly, at),
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
- nested.push(nestLevelsAt(extractNestLevels(head), head.referencedAt));
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 = compileFilterExpression(e.dataType, e.kids.filterExpr);
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 compileFilterExpression(ft, fexpr) {
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=composite_source_utils.js.map
1293
+ //# sourceMappingURL=composite-source-utils.js.map
@@ -1,4 +1,4 @@
1
- import type { DocumentLocation } from '../../model';
1
+ import { type DocumentLocation } from '../../model';
2
2
  import type { LogSeverity } from '../parse-log';
3
3
  type MessageProblemSpec = {
4
4
  severity: LogSeverity;
@@ -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
- const pass = this.equals(actualPaths, paths);
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);
@@ -564,7 +564,7 @@ function getSelectOneStruct(sqlBlock) {
564
564
  type: 'sql_select',
565
565
  name: key,
566
566
  dialect: 'standardsql',
567
- connection: 'bigquery',
567
+ connection: '_db_',
568
568
  selectStr: sqlBlock.selectStr,
569
569
  fields: [{ type: 'number', name: 'one' }],
570
570
  },
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
- select: fieldValue, weight
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>;