@malloydata/malloy 0.0.119-dev240123183113 → 0.0.119-dev240124170348

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 (52) hide show
  1. package/dist/index.d.ts +2 -2
  2. package/dist/index.js +1 -3
  3. package/dist/lang/ast/error-factory.js +3 -3
  4. package/dist/lang/ast/expressions/expr-aggregate-function.d.ts +6 -4
  5. package/dist/lang/ast/expressions/expr-aggregate-function.js +24 -13
  6. package/dist/lang/ast/expressions/expr-count.js +1 -1
  7. package/dist/lang/ast/expressions/expr-func.js +4 -4
  8. package/dist/lang/ast/expressions/expr-id-reference.js +2 -2
  9. package/dist/lang/ast/field-space/dynamic-space.js +1 -1
  10. package/dist/lang/ast/field-space/index-field-space.d.ts +5 -3
  11. package/dist/lang/ast/field-space/index-field-space.js +85 -16
  12. package/dist/lang/ast/field-space/query-input-space.d.ts +3 -3
  13. package/dist/lang/ast/field-space/query-spaces.d.ts +15 -12
  14. package/dist/lang/ast/field-space/query-spaces.js +60 -57
  15. package/dist/lang/ast/field-space/reference-field.d.ts +0 -5
  16. package/dist/lang/ast/field-space/reference-field.js +16 -46
  17. package/dist/lang/ast/query-builders/index-builder.d.ts +2 -2
  18. package/dist/lang/ast/query-builders/partial-builder.js +28 -1
  19. package/dist/lang/ast/query-builders/project-builder.d.ts +8 -4
  20. package/dist/lang/ast/query-builders/project-builder.js +5 -6
  21. package/dist/lang/ast/query-builders/reduce-builder.d.ts +13 -8
  22. package/dist/lang/ast/query-builders/reduce-builder.js +12 -9
  23. package/dist/lang/ast/query-items/field-declaration.d.ts +1 -1
  24. package/dist/lang/ast/query-items/field-references.d.ts +5 -1
  25. package/dist/lang/ast/query-items/field-references.js +17 -1
  26. package/dist/lang/ast/query-properties/qop-desc.d.ts +2 -2
  27. package/dist/lang/ast/source-properties/joins.js +1 -1
  28. package/dist/lang/ast/types/field-space.d.ts +2 -2
  29. package/dist/lang/ast/types/query-builder.d.ts +2 -2
  30. package/dist/lang/ast/view-elements/qop-desc-view.d.ts +3 -3
  31. package/dist/lang/ast/view-elements/reference-view.d.ts +3 -3
  32. package/dist/lang/ast/view-elements/reference-view.js +1 -1
  33. package/dist/lang/ast/view-elements/refine-utils.js +20 -18
  34. package/dist/lang/ast/view-elements/view-arrow.d.ts +2 -2
  35. package/dist/lang/ast/view-elements/view-refine.d.ts +3 -3
  36. package/dist/lang/ast/view-elements/view.d.ts +4 -4
  37. package/dist/lang/field-utils.d.ts +3 -3
  38. package/dist/lang/field-utils.js +9 -8
  39. package/dist/lang/test/annotation.spec.js +26 -27
  40. package/dist/lang/test/expressions.spec.js +43 -40
  41. package/dist/lang/test/field-symbols.spec.js +4 -4
  42. package/dist/lang/test/locations.spec.js +13 -6
  43. package/dist/lang/test/query.spec.js +44 -56
  44. package/dist/lang/test/test-translator.d.ts +3 -2
  45. package/dist/lang/test/test-translator.js +30 -24
  46. package/dist/malloy.js +1 -1
  47. package/dist/model/malloy_query.d.ts +14 -36
  48. package/dist/model/malloy_query.js +145 -312
  49. package/dist/model/malloy_types.d.ts +18 -23
  50. package/dist/model/malloy_types.js +2 -15
  51. package/dist/tags.js +19 -4
  52. package/package.json +1 -1
@@ -27,6 +27,9 @@ const dialect_1 = require("../dialect");
27
27
  const standardsql_1 = require("../dialect/standardsql/standardsql");
28
28
  const malloy_types_1 = require("./malloy_types");
29
29
  const utils_1 = require("./utils");
30
+ function pathToCol(path) {
31
+ return path.map(el => encodeURIComponent(el)).join('/');
32
+ }
30
33
  // quote a string for SQL use. Perhaps should be in dialect.
31
34
  function generateSQLStringLiteral(sourceString) {
32
35
  return `'${sourceString}'`;
@@ -330,17 +333,30 @@ class QueryField extends QueryNode {
330
333
  generateSpread(_resultSet, _context, _frag, _state) {
331
334
  throw new Error('Unexpanded spread encountered during SQL generation');
332
335
  }
333
- generateParameterFragment(resultSet, context, expr, state) {
336
+ generateParameterFragment(resultSet, context, expr, _state) {
337
+ /*
338
+ mtoy todo parameters and paths figure this out
339
+
334
340
  // find the structDef and return the path to the field...
335
341
  const param = context.parameters()[expr.path];
336
- if ((0, malloy_types_1.isValueParameter)(param)) {
337
- if (param.value) {
338
- return this.generateExpressionFromExpr(resultSet, context, param.value, state);
339
- }
340
- }
341
- else if (param.condition) {
342
- return this.generateExpressionFromExpr(resultSet, context, param.condition, state);
343
- }
342
+ if (isValueParameter(param)) {
343
+ if (param.value) {
344
+ return this.generateExpressionFromExpr(
345
+ resultSet,
346
+ context,
347
+ param.value,
348
+ state
349
+ );
350
+ }
351
+ } else if (param.condition) {
352
+ return this.generateExpressionFromExpr(
353
+ resultSet,
354
+ context,
355
+ param.condition,
356
+ state
357
+ );
358
+ }
359
+ */
344
360
  throw new Error(`Can't generate SQL, no value for ${expr.path}`);
345
361
  }
346
362
  generateFilterFragment(resultSet, context, expr, state) {
@@ -479,9 +495,6 @@ class QueryField extends QueryNode {
479
495
  generateDialect(resultSet, context, expr, state) {
480
496
  return this.generateExpressionFromExpr(resultSet, context, context.dialect.dialectExpr(resultSet.getQueryInfo(), expr), state);
481
497
  }
482
- generateFieldReference(resultSet, context, expr, state) {
483
- return this.generateFieldFragment(resultSet, context, { type: 'field', path: expr.path }, state);
484
- }
485
498
  generateSqlString(resultSet, context, expr, state) {
486
499
  return expr.e
487
500
  .map(part => typeof part === 'string'
@@ -667,9 +680,6 @@ class QueryField extends QueryNode {
667
680
  else if (expr.type === 'source-reference') {
668
681
  s += this.generateSourceReference(resultSet, context, expr);
669
682
  }
670
- else if (expr.type === 'field-reference') {
671
- s += this.generateFieldReference(resultSet, context, expr, state);
672
- }
673
683
  else {
674
684
  throw new Error(`Internal Error: Unknown expression fragment ${JSON.stringify(expr, undefined, 2)}`);
675
685
  }
@@ -699,7 +709,7 @@ function isAggregateField(f) {
699
709
  return f instanceof QueryAtomicField && f.isAggregate();
700
710
  }
701
711
  function isScalarField(f) {
702
- return f instanceof QueryAtomicField && !f.isCalculated();
712
+ return f instanceof QueryAtomicField && !f.isCalculated() && !f.isAggregate();
703
713
  }
704
714
  class QueryAtomicField extends QueryField {
705
715
  includeInWildcard() {
@@ -750,10 +760,10 @@ class QueryFieldStruct extends QueryAtomicField {
750
760
  onExpression: [
751
761
  {
752
762
  type: 'field',
753
- path: this.primaryKey,
763
+ path: [this.primaryKey],
754
764
  },
755
765
  '=',
756
- { type: 'field', path: foreignKeyName },
766
+ { type: 'field', path: [foreignKeyName] },
757
767
  ],
758
768
  },
759
769
  };
@@ -1411,136 +1421,14 @@ class QueryQuery extends QueryField {
1411
1421
  return (this.parent.fieldDef.structSource.type === 'sql' &&
1412
1422
  this.parent.fieldDef.structSource.method === 'nested');
1413
1423
  }
1414
- getFieldList() {
1415
- switch (this.firstSegment.type) {
1416
- // case "index":
1417
- // return this.firstSegment.fields || [];
1418
- case 'reduce':
1419
- return this.firstSegment.fields;
1420
- // probably need some way of checking type class of field here...
1421
- // project should only contain scalars
1422
- case 'project':
1423
- return this.firstSegment.fields;
1424
- default:
1425
- throw new Error(`Query contains no fields ${JSON.stringify(this.fieldDef)}`);
1426
- }
1427
- }
1428
1424
  // get a field ref and expand it.
1429
1425
  expandField(f) {
1430
- let as;
1431
- let field;
1432
- // if it is a string
1433
- if (typeof f === 'string') {
1434
- field = this.parent.getQueryFieldByName(f);
1435
- }
1436
- else if ('type' in f) {
1437
- field = this.parent.makeQueryField(f);
1438
- }
1439
- // or FilteredAliasedName or a hacked timestamp field.
1440
- else if ('name' in f && 'as' in f) {
1441
- field = this.parent.getQueryFieldByName(f.name);
1442
- // QueryFieldStructs return new names...
1443
- as = field.fieldDef.as || f.as;
1444
- if (field instanceof QueryFieldStruct) {
1445
- throw new Error('Syntax currently disallowed. Semantics up for discussion');
1446
- }
1447
- // Types of aliased fields.
1448
- // turtles
1449
- // Timestamps and Dates (are just fine to leave as is).
1450
- // measures
1451
- // let e: Expr;
1452
- if (field instanceof QueryQuery) {
1453
- const newFieldDef = structuredClone(field.fieldDef);
1454
- newFieldDef.as = f.name;
1455
- newFieldDef.filterList = f.filterList;
1456
- field = QueryQuery.makeQuery(newFieldDef, this.parent, undefined, this.isJoinedSubquery);
1457
- }
1458
- else if (!(field instanceof QueryFieldTimestamp ||
1459
- field instanceof QueryFieldDate)) {
1460
- throw new Error(`No longer generate code this way. \n ${JSON.stringify(f, undefined, 2)}`);
1461
- // // its a measure
1462
- // e = [{ type: "field", path: field.getFullOutputName() }];
1463
- // if ("filterList" in f && f.filterList) {
1464
- // e = [{ type: "filterExpression", filterList: f.filterList, e: e }];
1465
- // }
1466
- // const newFieldDef = {
1467
- // type: field.fieldDef.type,
1468
- // name: f.as,
1469
- // e,
1470
- // };
1471
- // field = this.parent.makeQueryField(newFieldDef as FieldDef);
1472
- }
1473
- // or inline field FieldTypeDef
1474
- }
1475
- else {
1476
- throw new Error(`Unrecognized field definition ${JSON.stringify(f, undefined, 2)}`);
1477
- }
1478
- if (!as) {
1479
- as = field.getIdentifier();
1480
- }
1426
+ const field = f.type === 'fieldref'
1427
+ ? this.parent.getQueryFieldReference(f.path, f.annotation)
1428
+ : this.parent.makeQueryField(f);
1429
+ const as = field.getIdentifier();
1481
1430
  return { as, field };
1482
1431
  }
1483
- expandDependantField(resultStruct, fieldRef) {
1484
- this.expandField(fieldRef);
1485
- }
1486
- // find all the fieldNames in the struct (and children)
1487
- // that match the filter
1488
- expandWildCardStruct(struct, expandChildren, filter = undefined) {
1489
- let fieldNames = [];
1490
- const structs = [];
1491
- for (const [_name, f] of struct.nameMap) {
1492
- if (f instanceof QueryAtomicField &&
1493
- isScalarField(f) &&
1494
- f.includeInWildcard() &&
1495
- !this.parent.dialect.ignoreInProject(f.fieldDef.name) &&
1496
- (!filter || filter(f))) {
1497
- // fieldNames.push(`${struct.getFullOutputName()}${name}`);
1498
- fieldNames.push(f.getFullOutputName());
1499
- }
1500
- else if (f instanceof QueryStruct && expandChildren) {
1501
- structs.push(f);
1502
- }
1503
- }
1504
- for (const s of structs) {
1505
- fieldNames = fieldNames.concat(this.expandWildCardStruct(s, expandChildren, filter));
1506
- }
1507
- return fieldNames;
1508
- }
1509
- // Do any '*' expansion.
1510
- expandWildCards(fields, filter = undefined) {
1511
- let ret = [];
1512
- for (const f of fields) {
1513
- if (typeof f !== 'string') {
1514
- ret.push(f);
1515
- }
1516
- else {
1517
- const fieldName = f;
1518
- const path = fieldName.split('.');
1519
- if (!path[path.length - 1].startsWith('*')) {
1520
- ret.push(f);
1521
- }
1522
- else {
1523
- const expandChildren = path.pop() === '**';
1524
- let struct = this.parent;
1525
- let pathElementName;
1526
- while (path.length > 0 && (pathElementName = path.shift())) {
1527
- const structNode = struct.getChildByName(pathElementName);
1528
- if (structNode === undefined) {
1529
- throw new Error(`Nested source not found '${pathElementName}'`);
1530
- }
1531
- if (structNode instanceof QueryStruct) {
1532
- struct = structNode;
1533
- }
1534
- else {
1535
- throw new Error(`'${pathElementName}' is not a source object`);
1536
- }
1537
- }
1538
- ret = ret.concat(this.expandWildCardStruct(struct, expandChildren, filter));
1539
- }
1540
- }
1541
- }
1542
- return ret;
1543
- }
1544
1432
  addDependantPath(resultStruct, context, path, uniqueKeyPossibleUse, joinStack) {
1545
1433
  const node = context.getFieldByName(path);
1546
1434
  let struct;
@@ -1668,9 +1556,17 @@ class QueryQuery extends QueryField {
1668
1556
  this.addDependantExpr(resultStruct, field.parent, field.fieldDef.e, []);
1669
1557
  }
1670
1558
  }
1559
+ getSegmentFields(resultStruct) {
1560
+ const fs = resultStruct.firstSegment;
1561
+ return fs.type === 'index'
1562
+ ? fs.indexFields
1563
+ : (0, malloy_types_1.isQuerySegment)(fs)
1564
+ ? fs.queryFields
1565
+ : [];
1566
+ }
1671
1567
  expandFields(resultStruct) {
1672
1568
  let resultIndex = 1;
1673
- for (const f of this.expandWildCards(resultStruct.firstSegment.fields)) {
1569
+ for (const f of this.getSegmentFields(resultStruct)) {
1674
1570
  const { as, field } = this.expandField(f);
1675
1571
  if (field instanceof QueryTurtle || field instanceof QueryQuery) {
1676
1572
  if (this.firstSegment.type === 'project') {
@@ -1834,7 +1730,7 @@ class QueryQuery extends QueryField {
1834
1730
  const resultType = fi.getRepeatedResultType() === 'nested' ? 'nested' : 'inline';
1835
1731
  structDef.name = name;
1836
1732
  structDef.structRelationship = {
1837
- field: name,
1733
+ fieldName: name,
1838
1734
  type: resultType,
1839
1735
  isArray: false,
1840
1736
  };
@@ -1998,7 +1894,7 @@ class QueryQuery extends QueryField {
1998
1894
  if (qs.parent === undefined || ji.parent === undefined) {
1999
1895
  throw new Error('Internal Error, nested structure with no parent.');
2000
1896
  }
2001
- const fieldExpression = this.parent.dialect.sqlFieldReference(qs.parent.getSQLIdentifier(), structRelationship.field, 'struct', qs.parent.fieldDef.structRelationship.type === 'nested', this.parent.fieldDef.structRelationship.type === 'nested' &&
1897
+ const fieldExpression = this.parent.dialect.sqlFieldReference(qs.parent.getSQLIdentifier(), structRelationship.fieldName, 'struct', qs.parent.fieldDef.structRelationship.type === 'nested', this.parent.fieldDef.structRelationship.type === 'nested' &&
2002
1898
  this.parent.fieldDef.structRelationship.isArray);
2003
1899
  // we need to generate primary key. If parent has a primary key combine
2004
1900
  // console.log(ji.alias, fieldExpression, this.inNestedPipeline());
@@ -2622,20 +2518,21 @@ class QueryQueryProject extends QueryQuery {
2622
2518
  class QueryQueryIndexStage extends QueryQuery {
2623
2519
  constructor(fieldDef, parent, stageWriter, isJoinedSubquery) {
2624
2520
  super(fieldDef, parent, stageWriter, isJoinedSubquery);
2521
+ this.indexPaths = {};
2625
2522
  this.fieldDef = fieldDef;
2626
2523
  }
2627
- // get a field ref and expand it.
2628
2524
  expandField(f) {
2629
- const field = this.parent.getFieldByName(f);
2630
- return { as: f, field };
2525
+ const as = f.path.join('.');
2526
+ const field = this.parent.getQueryFieldByName(f.path);
2527
+ return { as, field };
2631
2528
  }
2632
2529
  expandFields(resultStruct) {
2633
2530
  let resultIndex = 1;
2634
2531
  const groupIndex = resultStruct.groupSet;
2635
2532
  this.maxGroupSet = groupIndex;
2636
- const fieldNames = this.firstSegment.fields || [];
2637
- for (const f of fieldNames) {
2533
+ for (const f of this.firstSegment.indexFields) {
2638
2534
  const { as, field } = this.expandField(f);
2535
+ this.indexPaths[as] = f.path;
2639
2536
  resultStruct.addField(as, field, {
2640
2537
  resultIndex,
2641
2538
  type: 'result',
@@ -2647,7 +2544,7 @@ class QueryQueryIndexStage extends QueryQuery {
2647
2544
  }
2648
2545
  const measure = this.firstSegment.weightMeasure;
2649
2546
  if (measure !== undefined) {
2650
- const f = this.parent.getFieldByName(measure);
2547
+ const f = this.parent.getFieldByName([measure]);
2651
2548
  resultStruct.addField(measure, f, {
2652
2549
  resultIndex,
2653
2550
  type: 'result',
@@ -2660,6 +2557,7 @@ class QueryQueryIndexStage extends QueryQuery {
2660
2557
  let measureSQL = 'COUNT(*)';
2661
2558
  const dialect = this.parent.dialect;
2662
2559
  const fieldNameColumn = dialect.sqlMaybeQuoteIdentifier('fieldName');
2560
+ const fieldPathColumn = dialect.sqlMaybeQuoteIdentifier('fieldPath');
2663
2561
  const fieldValueColumn = dialect.sqlMaybeQuoteIdentifier('fieldValue');
2664
2562
  const fieldTypeColumn = dialect.sqlMaybeQuoteIdentifier('fieldType');
2665
2563
  const fieldRangeColumn = dialect.sqlMaybeQuoteIdentifier('fieldRange');
@@ -2674,7 +2572,8 @@ class QueryQueryIndexStage extends QueryQuery {
2674
2572
  const fi = field;
2675
2573
  if (fi.fieldUsage.type === 'result' && isScalarField(fi.f)) {
2676
2574
  const expression = fi.f.generateExpression(this.rootResult);
2677
- fields.push({ name, type: fi.f.fieldDef.type, expression });
2575
+ const path = this.indexPaths[name] || [];
2576
+ fields.push({ name, path, type: fi.f.fieldDef.type, expression });
2678
2577
  }
2679
2578
  }
2680
2579
  let s = 'SELECT\n group_set,\n';
@@ -2682,13 +2581,19 @@ class QueryQueryIndexStage extends QueryQuery {
2682
2581
  for (let i = 0; i < fields.length; i++) {
2683
2582
  s += ` WHEN ${i} THEN '${fields[i].name}'\n`;
2684
2583
  }
2685
- s += ` END as ${fieldNameColumn},`;
2584
+ s += ` END as ${fieldNameColumn},\n`;
2585
+ s += ' CASE group_set\n';
2586
+ for (let i = 0; i < fields.length; i++) {
2587
+ const path = pathToCol(fields[i].path);
2588
+ s += ` WHEN ${i} THEN '${path}'\n`;
2589
+ }
2590
+ s += ` END as ${fieldPathColumn},\n`;
2686
2591
  s += ' CASE group_set\n';
2687
2592
  for (let i = 0; i < fields.length; i++) {
2688
2593
  s += ` WHEN ${i} THEN '${fields[i].type}'\n`;
2689
2594
  }
2690
2595
  s += ` END as ${fieldTypeColumn},`;
2691
- s += ' CASE group_set WHEN 99999 THEN NULL\n';
2596
+ s += ` CASE group_set WHEN 99999 THEN ${dialect.castToString('NULL')}\n`;
2692
2597
  for (let i = 0; i < fields.length; i++) {
2693
2598
  if (fields[i].type === 'string') {
2694
2599
  s += ` WHEN ${i} THEN ${fields[i].expression}\n`;
@@ -2717,7 +2622,7 @@ class QueryQueryIndexStage extends QueryQuery {
2717
2622
  s += this.generateSQLJoins(stageWriter);
2718
2623
  s += dialect.sqlGroupSetTable(fields.length) + '\n';
2719
2624
  s += this.generateSQLFilters(this.rootResult, 'where').sql('where');
2720
- s += 'GROUP BY 1,2,3,4\n';
2625
+ s += 'GROUP BY 1,2,3,4,5\n';
2721
2626
  // limit
2722
2627
  if (!(0, malloy_types_1.isRawSegment)(this.firstSegment) && this.firstSegment.limit) {
2723
2628
  s += `LIMIT ${this.firstSegment.limit}\n`;
@@ -2726,6 +2631,7 @@ class QueryQueryIndexStage extends QueryQuery {
2726
2631
  const resultStage = stageWriter.addStage(s);
2727
2632
  this.resultStage = stageWriter.addStage(`SELECT
2728
2633
  ${fieldNameColumn},
2634
+ ${fieldPathColumn},
2729
2635
  ${fieldTypeColumn},
2730
2636
  COALESCE(${fieldValueColumn}, ${fieldRangeColumn}) as ${fieldValueColumn},
2731
2637
  weight
@@ -2755,90 +2661,57 @@ class QueryQueryRaw extends QueryQuery {
2755
2661
  class QueryQueryIndex extends QueryQuery {
2756
2662
  constructor(fieldDef, parent, stageWriter, isJoinedSubquery) {
2757
2663
  super(fieldDef, parent, stageWriter, isJoinedSubquery);
2758
- this.rootFields = [];
2759
- this.fanPrefixMap = {};
2664
+ this.stages = [];
2760
2665
  this.fieldDef = fieldDef;
2761
- this.findFanPrefexes(parent);
2762
- }
2763
- // we want to generate a different query for each
2764
- // nested structure so we don't do a crazy cross product.
2765
- findFanPrefexes(qs) {
2766
- for (const [_name, f] of qs.nameMap) {
2767
- if (f instanceof QueryStruct &&
2768
- (f.fieldDef.structRelationship.type === 'many' ||
2769
- f.fieldDef.structRelationship.type === 'nested') &&
2770
- f.fieldDef.fields.length > 1 && // leave arrays in parent.
2771
- this.parent.dialect.dontUnionIndex === false) {
2772
- const key = f.getFullOutputName();
2773
- this.fanPrefixMap[key] = [];
2774
- this.findFanPrefexes(f);
2775
- }
2776
- }
2777
- }
2778
- expandIndexWildCards() {
2779
- // if no fields were specified, look in the parent struct for strings.
2780
- let fieldNames = this.firstSegment.fields || [];
2781
- if (fieldNames.length === 0) {
2782
- fieldNames.push('**');
2783
- }
2784
- fieldNames = this.expandWildCards(fieldNames, qf => ['string', 'number', 'timestamp', 'date'].indexOf(qf.fieldDef.type) !==
2785
- -1);
2786
- return fieldNames;
2666
+ this.fieldsToStages();
2787
2667
  }
2788
- // return the number of stages it is going to take to generate this index.
2789
- getStageFields() {
2790
- const s = [];
2791
- if (this.rootFields.length > 0) {
2792
- s.push(this.rootFields);
2793
- }
2794
- for (const fieldList of Object.values(this.fanPrefixMap)) {
2795
- if (fieldList.length > 0) {
2796
- s.push(fieldList);
2797
- }
2668
+ fieldsToStages() {
2669
+ const indexSeg = this.firstSegment;
2670
+ if (this.parent.dialect.dontUnionIndex) {
2671
+ this.stages = [indexSeg.indexFields];
2672
+ return;
2798
2673
  }
2799
- return s;
2800
- }
2801
- // Map fields into stages based on their level of repeated nesting
2802
- //
2803
- mapFieldsIntoStages(fieldNames) {
2804
- // find all the fanned prefixes, longest ones first.
2805
- const fannedPrefixes = Object.keys(this.fanPrefixMap).sort((k1, k2) => {
2806
- if (k1.length < k2.length) {
2807
- return 1;
2808
- }
2809
- if (k1.length > k2.length) {
2810
- return -1;
2811
- }
2812
- return 0;
2813
- });
2814
- // Find the deepest fanned prefix
2815
- for (const fn of fieldNames) {
2816
- let found = false;
2817
- for (const prefix of fannedPrefixes) {
2818
- if (fn.startsWith(prefix)) {
2819
- this.fanPrefixMap[prefix].push(fn);
2820
- found = true;
2674
+ // Collect the field references by unique path, the final
2675
+ // index will be a union indexes from each unique path
2676
+ const stageMap = {};
2677
+ for (const fref of indexSeg.indexFields) {
2678
+ if (fref.path.length > 1) {
2679
+ const stageRoot = pathToCol(fref.path.slice(0, fref.path.length - 1));
2680
+ const stage = stageMap[stageRoot];
2681
+ if (stage === undefined) {
2682
+ const f = this.parent.nameMap.get(fref.path[0]);
2683
+ if (f instanceof QueryStruct &&
2684
+ (f.fieldDef.structRelationship.type === 'many' ||
2685
+ f.fieldDef.structRelationship.type === 'nested') &&
2686
+ f.fieldDef.fields.length > 1) {
2687
+ const toStage = [fref];
2688
+ stageMap[stageRoot] = toStage;
2689
+ this.stages.push(toStage);
2690
+ continue;
2691
+ }
2692
+ }
2693
+ else {
2694
+ stage.push(fref);
2695
+ continue;
2821
2696
  }
2822
2697
  }
2823
- if (!found) {
2824
- this.rootFields.push(fn);
2698
+ if (this.stages[0] === undefined) {
2699
+ this.stages[0] = [];
2825
2700
  }
2701
+ this.stages[0].push(fref);
2826
2702
  }
2827
2703
  }
2828
- expandFields(_resultStruct) {
2829
- const fieldNames = this.expandIndexWildCards();
2830
- this.mapFieldsIntoStages(fieldNames);
2831
- }
2704
+ expandFields(_resultStruct) { }
2832
2705
  generateSQL(stageWriter) {
2833
- const stages = this.getStageFields();
2706
+ const indexSeg = this.firstSegment;
2834
2707
  const outputStageNames = [];
2835
- for (const fields of stages) {
2708
+ for (const fields of this.stages) {
2836
2709
  const q = new QueryQueryIndexStage({
2837
2710
  ...this.fieldDef,
2838
2711
  pipeline: [
2839
2712
  {
2840
- ...this.fieldDef.pipeline[0],
2841
- fields: fields,
2713
+ ...indexSeg,
2714
+ indexFields: fields,
2842
2715
  },
2843
2716
  ],
2844
2717
  }, this.parent, stageWriter, this.isJoinedSubquery);
@@ -2854,7 +2727,11 @@ class QueryQueryIndex extends QueryQuery {
2854
2727
  }
2855
2728
  return this.resultStage;
2856
2729
  }
2857
- /** All Indexes have the same output schema */
2730
+ /**
2731
+ * All Indexes have the same output schema.
2732
+ * fieldName is deprecated, dots in fieldName may or may not be join nodes
2733
+ * fieldPath is a URL encoded slash separated path
2734
+ */
2858
2735
  getResultStructDef() {
2859
2736
  const ret = {
2860
2737
  type: 'struct',
@@ -2862,6 +2739,7 @@ class QueryQueryIndex extends QueryQuery {
2862
2739
  dialect: this.parent.fieldDef.dialect,
2863
2740
  fields: [
2864
2741
  { type: 'string', name: 'fieldName' },
2742
+ { type: 'string', name: 'fieldPath' },
2865
2743
  { type: 'string', name: 'fieldValue' },
2866
2744
  { type: 'string', name: 'fieldType' },
2867
2745
  { type: 'number', name: 'weight', numberType: 'integer' },
@@ -2963,32 +2841,6 @@ class QueryStruct extends QueryNode {
2963
2841
  return ret;
2964
2842
  }
2965
2843
  }
2966
- // when structs are referenced in queries, incorporate the
2967
- // primary key of struct and add the struct as a join to the result.
2968
- getAsQueryField() {
2969
- if (this.fieldDef.primaryKey === undefined) {
2970
- throw new Error(`Joined explores can only be included in queries if a primary key is defined: '${this.getFullOutputName()}' has no primary key`);
2971
- }
2972
- const pkField = this.getPrimaryKeyField(this.fieldDef);
2973
- const pkType = pkField.fieldDef.type;
2974
- if (pkType !== 'string' && pkType !== 'number') {
2975
- throw new Error(`Unknown Primary key data type for ${pkField.fieldDef.name}`);
2976
- }
2977
- const aliasName = (0, malloy_types_1.getIdentifier)(this.fieldDef);
2978
- const pkName = this.fieldDef.primaryKey;
2979
- const fieldDef = {
2980
- type: pkType,
2981
- name: `${aliasName}_id`,
2982
- e: [
2983
- {
2984
- type: 'field',
2985
- // path: pkField.getFullOutputName(),
2986
- path: pkField.getIdentifier(),
2987
- },
2988
- ],
2989
- };
2990
- return new QueryFieldStruct(fieldDef, this, `${aliasName}.${pkName}`);
2991
- }
2992
2844
  getSQLIdentifier() {
2993
2845
  if (this.unnestWithNumbers() && this.parent !== undefined) {
2994
2846
  const x = this.parent.getSQLIdentifier() +
@@ -3149,27 +3001,6 @@ class QueryStruct extends QueryNode {
3149
3001
  throw new Error(`unknown field definition ${JSON.stringify(field)}`);
3150
3002
  }
3151
3003
  }
3152
- /**
3153
- * return a field if it exists, make one if we are passed a field definition.
3154
- */
3155
- getOrMakeField(fieldRef) {
3156
- if (typeof fieldRef === 'string') {
3157
- return this.getFieldByName(fieldRef);
3158
- }
3159
- else {
3160
- return this.makeQueryField(fieldRef);
3161
- }
3162
- }
3163
- /** returns a dimension for the given name or make one. */
3164
- getOrMakeDimension(fieldRef) {
3165
- const dim = this.getOrMakeField(fieldRef);
3166
- if (dim instanceof QueryAtomicField && isScalarField(dim)) {
3167
- return dim;
3168
- }
3169
- else {
3170
- throw new Error(`${fieldRef} is not of type a scalar'`);
3171
- }
3172
- }
3173
3004
  structSourceSQL(stageWriter) {
3174
3005
  switch (this.fieldDef.structSource.type) {
3175
3006
  case 'table': {
@@ -3221,37 +3052,43 @@ class QueryStruct extends QueryNode {
3221
3052
  }
3222
3053
  primaryKey() {
3223
3054
  if (this.fieldDef.primaryKey) {
3224
- return this.getDimensionByName(this.fieldDef.primaryKey);
3055
+ return this.getDimensionByName([this.fieldDef.primaryKey]);
3225
3056
  }
3226
3057
  else {
3227
3058
  return undefined;
3228
3059
  }
3229
3060
  }
3230
- /** get the componennts of a field path */
3231
- static resolvePath(name) {
3232
- return name.split('.');
3233
- }
3234
3061
  getChildByName(name) {
3235
3062
  return this.nameMap.get(name);
3236
3063
  }
3237
- /** convert a name into a field reference */
3238
- getFieldByName(name) {
3239
- const path = QueryStruct.resolvePath(name);
3240
- let ret = this;
3241
- for (const n of path) {
3242
- const r = ret.getChildByName(n);
3064
+ /** convert a path into a field reference */
3065
+ getFieldByName(path) {
3066
+ return path.reduce((lookIn, childName) => {
3067
+ const r = lookIn.getChildByName(childName);
3243
3068
  if (r === undefined) {
3244
- throw new Error(`Path not found ${name}`);
3069
+ throw new Error(path.length === 1
3070
+ ? `'${childName}' not found`
3071
+ : `'${childName}' not found in '${path.join('.')}'`);
3245
3072
  }
3246
- ret = r;
3247
- }
3248
- return ret;
3073
+ return r;
3074
+ }, this);
3249
3075
  }
3250
3076
  // structs referenced in queries are converted to fields.
3251
3077
  getQueryFieldByName(name) {
3252
- let field = this.getFieldByName(name);
3078
+ const field = this.getFieldByName(name);
3253
3079
  if (field instanceof QueryStruct) {
3254
- field = field.getAsQueryField();
3080
+ throw new Error(`Cannot reference ${name} as a scalar'`);
3081
+ }
3082
+ return field;
3083
+ }
3084
+ getQueryFieldReference(name, refAnnoatation) {
3085
+ const field = this.getQueryFieldByName(name);
3086
+ if (refAnnoatation) {
3087
+ // Made the field object from the source, but the annotations were computed by the compiler
3088
+ // and have noth the source and reference annotations included, use those.
3089
+ const newDef = { ...field.fieldDef };
3090
+ newDef.annotation = refAnnoatation;
3091
+ field.fieldDef = newDef;
3255
3092
  }
3256
3093
  return field;
3257
3094
  }
@@ -3281,12 +3118,12 @@ class QueryStruct extends QueryNode {
3281
3118
  return struct;
3282
3119
  }
3283
3120
  else {
3284
- throw new Error(`Error: Path to structure not found '${name}'`);
3121
+ throw new Error(`Error: Path to structure not found '${name.join('.')}'`);
3285
3122
  }
3286
3123
  }
3287
3124
  getDistinctKey() {
3288
3125
  if (this.fieldDef.structRelationship.type !== 'inline') {
3289
- return this.getDimensionByName('__distinct_key');
3126
+ return this.getDimensionByName(['__distinct_key']);
3290
3127
  }
3291
3128
  else if (this.parent) {
3292
3129
  return this.parent.getDistinctKey();
@@ -3341,22 +3178,6 @@ class QueryModel {
3341
3178
  }
3342
3179
  }
3343
3180
  }
3344
- parseQueryPath(name) {
3345
- const path = name.split('.');
3346
- let struct;
3347
- if ((struct = this.structs.get(path[0]))) {
3348
- if (path.length > 1) {
3349
- path.shift();
3350
- }
3351
- else {
3352
- throw new Error(`No query specified in Struct '${path[0]}'`);
3353
- }
3354
- return { queryName: path.join('.'), struct };
3355
- }
3356
- else {
3357
- throw new Error(`Cannot find Struct '${path[0]}' Model`);
3358
- }
3359
- }
3360
3181
  getStructByName(name) {
3361
3182
  let s;
3362
3183
  if ((s = this.structs.get(name))) {
@@ -3443,17 +3264,27 @@ class QueryModel {
3443
3264
  }
3444
3265
  // make a search index if one isn't modelled.
3445
3266
  const struct = this.getStructByName(explore);
3267
+ let indexStar = [];
3268
+ for (const [fn, fv] of struct.nameMap) {
3269
+ if (!(fv instanceof QueryStruct)) {
3270
+ if (isScalarField(fv) && fv.includeInWildcard()) {
3271
+ indexStar.push({ type: 'fieldref', path: [fn] });
3272
+ }
3273
+ }
3274
+ }
3275
+ indexStar = indexStar.sort((a, b) => a.path[0].localeCompare(b.path[0]));
3446
3276
  const indexQuery = {
3447
3277
  structRef: explore,
3448
3278
  pipeline: [
3449
3279
  {
3450
3280
  type: 'index',
3451
- fields: ['*'],
3281
+ indexFields: indexStar,
3452
3282
  sample: struct.dialect.defaultSampling,
3453
3283
  },
3454
3284
  ],
3455
3285
  };
3456
3286
  const fieldNameColumn = struct.dialect.sqlMaybeQuoteIdentifier('fieldName');
3287
+ const fieldPathColumn = struct.dialect.sqlMaybeQuoteIdentifier('fieldPath');
3457
3288
  const fieldValueColumn = struct.dialect.sqlMaybeQuoteIdentifier('fieldValue');
3458
3289
  const fieldTypeColumn = struct.dialect.sqlMaybeQuoteIdentifier('fieldType');
3459
3290
  // if we've compiled the SQL before use it otherwise
@@ -3464,10 +3295,11 @@ class QueryModel {
3464
3295
  }
3465
3296
  let query = `SELECT
3466
3297
  ${fieldNameColumn},
3298
+ ${fieldPathColumn},
3467
3299
  ${fieldValueColumn},
3468
3300
  ${fieldTypeColumn},
3469
3301
  weight,
3470
- CASE WHEN lower(${fieldValueColumn}) LIKE lower(${generateSQLStringLiteral(searchValue + '%')}) THEN 1 ELSE 0 END as match_first
3302
+ CASE WHEN lower(${fieldValueColumn}) LIKE lower(${generateSQLStringLiteral(searchValue + '%')}) THEN 1 ELSE 0 END as match_first
3471
3303
  FROM ${await connection.manifestTemporaryTable(sqlPDT)}
3472
3304
  WHERE lower(${fieldValueColumn}) LIKE lower(${generateSQLStringLiteral('%' + searchValue + '%')}) ${searchField !== undefined
3473
3305
  ? ` AND ${fieldNameColumn} = '` + searchField + "' \n"
@@ -3478,6 +3310,7 @@ class QueryModel {
3478
3310
  if (struct.dialect.hasFinalStage) {
3479
3311
  query = `WITH __stage0 AS(\n${query}\n)\n${struct.dialect.sqlFinalStage('__stage0', [
3480
3312
  fieldNameColumn,
3313
+ fieldPathColumn,
3481
3314
  fieldValueColumn,
3482
3315
  fieldTypeColumn,
3483
3316
  'weight',