@malloydata/malloy 0.0.219 → 0.0.220-dev241204170603

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 (71) hide show
  1. package/dist/connection/base_connection.js +1 -1
  2. package/dist/dialect/dialect.d.ts +6 -3
  3. package/dist/dialect/dialect.js +4 -11
  4. package/dist/dialect/duckdb/dialect_functions.js +1 -1
  5. package/dist/dialect/duckdb/duckdb.d.ts +4 -4
  6. package/dist/dialect/duckdb/duckdb.js +10 -16
  7. package/dist/dialect/duckdb/function_overrides.js +1 -1
  8. package/dist/dialect/functions/malloy_standard_functions.js +1 -1
  9. package/dist/dialect/index.d.ts +3 -1
  10. package/dist/dialect/index.js +3 -1
  11. package/dist/dialect/mysql/dialect_functions.js +1 -1
  12. package/dist/dialect/mysql/function_overrides.js +1 -1
  13. package/dist/dialect/mysql/index.js +1 -1
  14. package/dist/dialect/mysql/mysql.d.ts +9 -6
  15. package/dist/dialect/mysql/mysql.js +36 -25
  16. package/dist/dialect/pg_impl.d.ts +4 -1
  17. package/dist/dialect/pg_impl.js +11 -1
  18. package/dist/dialect/postgres/dialect_functions.js +1 -1
  19. package/dist/dialect/postgres/function_overrides.js +1 -1
  20. package/dist/dialect/postgres/postgres.d.ts +6 -4
  21. package/dist/dialect/postgres/postgres.js +27 -12
  22. package/dist/dialect/snowflake/dialect_functions.js +1 -1
  23. package/dist/dialect/snowflake/function_overrides.js +1 -1
  24. package/dist/dialect/snowflake/snowflake.d.ts +5 -3
  25. package/dist/dialect/snowflake/snowflake.js +76 -20
  26. package/dist/dialect/standardsql/dialect_functions.js +1 -1
  27. package/dist/dialect/standardsql/function_overrides.js +1 -1
  28. package/dist/dialect/standardsql/standardsql.d.ts +5 -3
  29. package/dist/dialect/standardsql/standardsql.js +16 -101
  30. package/dist/dialect/tiny_parser.d.ts +41 -0
  31. package/dist/dialect/tiny_parser.js +126 -0
  32. package/dist/dialect/trino/dialect_functions.js +1 -1
  33. package/dist/dialect/trino/function_overrides.js +1 -1
  34. package/dist/dialect/trino/trino.d.ts +3 -3
  35. package/dist/dialect/trino/trino.js +40 -16
  36. package/dist/index.d.ts +3 -3
  37. package/dist/index.js +3 -2
  38. package/dist/lang/ast/expressions/case.js +1 -1
  39. package/dist/lang/ast/expressions/expr-array-literal.d.ts +9 -0
  40. package/dist/lang/ast/expressions/expr-array-literal.js +93 -0
  41. package/dist/lang/ast/expressions/expr-record-literal.d.ts +1 -1
  42. package/dist/lang/ast/expressions/expr-record-literal.js +56 -28
  43. package/dist/lang/ast/field-space/join-space-field.d.ts +1 -1
  44. package/dist/lang/ast/field-space/join-space-field.js +2 -2
  45. package/dist/lang/ast/field-space/project-field-space.js +1 -1
  46. package/dist/lang/ast/field-space/reference-field.js +1 -1
  47. package/dist/lang/ast/field-space/static-space.d.ts +1 -2
  48. package/dist/lang/ast/field-space/static-space.js +15 -5
  49. package/dist/lang/ast/field-space/view-field.d.ts +2 -2
  50. package/dist/lang/ast/index.d.ts +1 -0
  51. package/dist/lang/ast/index.js +1 -0
  52. package/dist/lang/ast/query-items/field-declaration.js +25 -11
  53. package/dist/lang/ast/query-properties/filters.js +0 -2
  54. package/dist/lang/ast/source-properties/join.js +1 -1
  55. package/dist/lang/ast/types/binary_operators.js +1 -1
  56. package/dist/lang/ast/types/dialect-name-space.js +1 -1
  57. package/dist/lang/lib/Malloy/MalloyParser.d.ts +1 -1
  58. package/dist/lang/lib/Malloy/MalloyParser.js +12 -16
  59. package/dist/lang/malloy-to-ast.d.ts +1 -1
  60. package/dist/lang/malloy-to-ast.js +3 -3
  61. package/dist/lang/parse-log.d.ts +1 -0
  62. package/dist/lang/test/query.spec.js +6 -0
  63. package/dist/lang/test/test-translator.js +11 -2
  64. package/dist/model/malloy_query.d.ts +29 -24
  65. package/dist/model/malloy_query.js +261 -146
  66. package/dist/model/malloy_types.d.ts +12 -15
  67. package/dist/model/malloy_types.js +2 -7
  68. package/dist/model/materialization/utils.js +1 -1
  69. package/dist/version.d.ts +1 -1
  70. package/dist/version.js +1 -1
  71. package/package.json +1 -1
@@ -173,13 +173,6 @@ class StageWriter {
173
173
  }
174
174
  }
175
175
  }
176
- // type QueryNodeType =
177
- // | "abstract"
178
- // | "dimension"
179
- // | "measure"
180
- // | "query"
181
- // | "turtle"
182
- // | "struct";
183
176
  class GenerateState {
184
177
  constructor() {
185
178
  this.totalGroupSet = -1;
@@ -234,6 +227,9 @@ class QueryField extends QueryNode {
234
227
  }
235
228
  return parent;
236
229
  }
230
+ isAtomic() {
231
+ return (0, malloy_types_1.isAtomic)(this.fieldDef);
232
+ }
237
233
  caseGroup(groupSets, s) {
238
234
  if (groupSets.length === 0) {
239
235
  return s;
@@ -900,33 +896,47 @@ class QueryField extends QueryNode {
900
896
  return (parentDef.type === 'array' &&
901
897
  parentDef.elementTypeDef.type !== 'record_element');
902
898
  }
903
- getExpr() {
899
+ generateExpression(resultSet) {
900
+ // If the field itself is an expression, generate it ..
904
901
  if ((0, malloy_types_1.hasExpression)(this.fieldDef)) {
905
- return this.fieldDef.e;
902
+ return this.exprToSQL(resultSet, this.parent, this.fieldDef.e);
906
903
  }
907
- const pType = this.parent.structDef.type;
908
- return {
909
- node: 'genericSQLExpr',
910
- kids: { args: [] },
911
- src: [
912
- this.parent.dialect.sqlFieldReference(this.parent.getSQLIdentifier(), this.fieldDef.name, this.fieldDef.type, pType === 'record' || pType === 'array' || pType === 'nest_source', (0, malloy_types_1.isScalarArray)(this.parent.structDef)),
913
- ],
914
- };
915
- }
916
- generateExpression(resultSet) {
917
- return this.exprToSQL(resultSet, this.parent, this.getExpr());
904
+ // The field itself is not an expression, so we would like
905
+ // to generate a dotted path to the field, EXCEPT ...
906
+ // some of the steps in the dotting might not exist
907
+ // in the namespace of their parent, but rather be record
908
+ // expressions which should be evaluated in the namespace
909
+ // of their parent.
910
+ // So we walk the tree and ask each one to compute itself
911
+ for (let ancestor = this.parent; ancestor !== undefined; ancestor = ancestor.parent) {
912
+ if (ancestor.structDef.type === 'record' &&
913
+ (0, malloy_types_1.hasExpression)(ancestor.structDef) &&
914
+ ancestor.recordAlias === undefined) {
915
+ if (!ancestor.parent) {
916
+ throw new Error('Inconcievable record ancestor with expression but no parent');
917
+ }
918
+ const aliasValue = this.exprToSQL(resultSet, ancestor.parent, ancestor.structDef.e);
919
+ ancestor.informOfAliasValue(aliasValue);
920
+ }
921
+ }
922
+ return this.parent.sqlChildReference(this.fieldDef.name, this.parent.structDef.type === 'record'
923
+ ? {
924
+ result: resultSet,
925
+ field: this,
926
+ }
927
+ : undefined);
918
928
  }
919
929
  }
920
930
  function isCalculatedField(f) {
921
- return f instanceof QueryAtomicField && f.isCalculated();
931
+ return f instanceof AbstractQueryAtomic && f.isCalculated();
922
932
  }
923
933
  function isAggregateField(f) {
924
- return f instanceof QueryAtomicField && f.isAggregate();
934
+ return f instanceof AbstractQueryAtomic && f.isAggregate();
925
935
  }
926
936
  function isScalarField(f) {
927
- return f instanceof QueryAtomicField && !f.isCalculated() && !f.isAggregate();
937
+ return (f instanceof AbstractQueryAtomic && !f.isCalculated() && !f.isAggregate());
928
938
  }
929
- class QueryAtomicField extends QueryField {
939
+ class AbstractQueryAtomic extends QueryField {
930
940
  constructor(fieldDef, parent, refId) {
931
941
  super(fieldDef, parent, refId);
932
942
  this.fieldDef = fieldDef; // wish I didn't have to do this
@@ -948,28 +958,32 @@ class QueryAtomicField extends QueryField {
948
958
  hasExpression() {
949
959
  return (0, malloy_types_1.hasExpression)(this.fieldDef);
950
960
  }
961
+ isAtomic() {
962
+ return true;
963
+ }
951
964
  }
952
965
  // class QueryMeasure extends QueryField {}
953
- class QueryFieldString extends QueryAtomicField {
966
+ class QueryFieldString extends AbstractQueryAtomic {
954
967
  }
955
- class QueryFieldNumber extends QueryAtomicField {
968
+ class QueryFieldNumber extends AbstractQueryAtomic {
956
969
  }
957
- class QueryFieldBoolean extends QueryAtomicField {
970
+ class QueryFieldBoolean extends AbstractQueryAtomic {
958
971
  }
959
- class QueryFieldJSON extends QueryAtomicField {
972
+ class QueryFieldJSON extends AbstractQueryAtomic {
960
973
  }
961
- class QueryFieldUnsupported extends QueryAtomicField {
974
+ class QueryFieldUnsupported extends AbstractQueryAtomic {
962
975
  }
963
- class QueryFieldDate extends QueryAtomicField {
976
+ class QueryFieldDate extends AbstractQueryAtomic {
964
977
  generateExpression(resultSet) {
965
978
  const fd = this.fieldDef;
979
+ const superExpr = super.generateExpression(resultSet);
966
980
  if (!fd.timeframe) {
967
- return super.generateExpression(resultSet);
981
+ return superExpr;
968
982
  }
969
983
  else {
970
984
  const truncated = {
971
985
  node: 'trunc',
972
- e: (0, malloy_types_1.mkTemporal)(this.getExpr(), 'date'),
986
+ e: (0, malloy_types_1.mkTemporal)({ node: 'genericSQLExpr', src: [superExpr], kids: { args: [] } }, 'date'),
973
987
  units: fd.timeframe,
974
988
  };
975
989
  return this.exprToSQL(resultSet, this.parent, truncated);
@@ -985,7 +999,7 @@ class QueryFieldDate extends QueryAtomicField {
985
999
  return new QueryFieldDate(fieldDef, this.parent);
986
1000
  }
987
1001
  }
988
- class QueryFieldTimestamp extends QueryAtomicField {
1002
+ class QueryFieldTimestamp extends AbstractQueryAtomic {
989
1003
  // clone ourselves on demand as a timeframe.
990
1004
  getChildByName(name) {
991
1005
  const fieldDef = {
@@ -996,7 +1010,7 @@ class QueryFieldTimestamp extends QueryAtomicField {
996
1010
  return new QueryFieldTimestamp(fieldDef, this.parent);
997
1011
  }
998
1012
  }
999
- class QueryFieldDistinctKey extends QueryAtomicField {
1013
+ class QueryFieldDistinctKey extends AbstractQueryAtomic {
1000
1014
  generateExpression(resultSet) {
1001
1015
  var _a;
1002
1016
  if (this.parent.primaryKey()) {
@@ -1006,11 +1020,11 @@ class QueryFieldDistinctKey extends QueryAtomicField {
1006
1020
  else if (this.parent.structDef.type === 'array') {
1007
1021
  const parentKey = (_a = this.parent.parent) === null || _a === void 0 ? void 0 : _a.getDistinctKey().generateExpression(resultSet);
1008
1022
  return this.parent.dialect.sqlMakeUnnestKey(parentKey || '', // shouldn't have to do this...
1009
- this.parent.dialect.sqlFieldReference(this.parent.getIdentifier(), '__row_id', 'string', true, false));
1023
+ this.parent.dialect.sqlFieldReference(this.parent.getIdentifier(), 'table', '__row_id', 'string'));
1010
1024
  }
1011
1025
  else {
1012
1026
  // return this.parent.getIdentifier() + "." + "__distinct_key";
1013
- return this.parent.dialect.sqlFieldReference(this.parent.getIdentifier(), '__distinct_key', 'string', false, false);
1027
+ return this.parent.dialect.sqlFieldReference(this.parent.getIdentifier(), 'table', '__distinct_key', 'string');
1014
1028
  }
1015
1029
  }
1016
1030
  includeInWildcard() {
@@ -1219,7 +1233,7 @@ class FieldInstanceResult {
1219
1233
  if (isScalarField(f.f)) {
1220
1234
  return 'nested';
1221
1235
  }
1222
- if (f.f instanceof QueryStruct) {
1236
+ if (f.f instanceof QueryFieldStruct) {
1223
1237
  ret = 'inline';
1224
1238
  }
1225
1239
  }
@@ -1313,7 +1327,9 @@ class FieldInstanceResult {
1313
1327
  }
1314
1328
  findJoins(query) {
1315
1329
  for (const dim of this.fields()) {
1316
- this.addStructToJoin(dim.f.getJoinableParent(), query, dim.f.uniqueKeyPossibleUse(), []);
1330
+ if (!(dim.f instanceof QueryFieldStruct)) {
1331
+ this.addStructToJoin(dim.f.getJoinableParent(), query, dim.f.uniqueKeyPossibleUse(), []);
1332
+ }
1317
1333
  }
1318
1334
  for (const s of this.structs()) {
1319
1335
  s.findJoins(query);
@@ -1526,9 +1542,6 @@ class JoinInstance {
1526
1542
  return getDialectFieldList(this.queryStruct.structDef);
1527
1543
  }
1528
1544
  }
1529
- /** nested query */
1530
- class QueryTurtle extends QueryField {
1531
- }
1532
1545
  /**
1533
1546
  * Used by the translator to get the output StructDef of a pipe segment
1534
1547
  *
@@ -1622,20 +1635,30 @@ class QueryQuery extends QueryField {
1622
1635
  return { as, field };
1623
1636
  }
1624
1637
  addDependantPath(resultStruct, context, path, uniqueKeyPossibleUse, joinStack) {
1625
- const node = context.getFieldByName(path);
1626
- let struct;
1627
- if (node instanceof QueryField) {
1628
- struct = node.parent;
1629
- }
1630
- else if (node instanceof QueryStruct) {
1631
- struct = node;
1632
- }
1633
- else {
1634
- throw new Error('Internal Error: Unknown object type');
1638
+ if (path.length === 0) {
1639
+ return;
1635
1640
  }
1641
+ const node = context.getFieldByName(path);
1642
+ const joinableParent = node instanceof QueryFieldStruct
1643
+ ? node.queryStruct.getJoinableParent()
1644
+ : node.parent.getJoinableParent();
1636
1645
  resultStruct
1637
1646
  .root()
1638
- .addStructToJoin(struct.getJoinableParent(), this, uniqueKeyPossibleUse, joinStack);
1647
+ .addStructToJoin(joinableParent, this, uniqueKeyPossibleUse, joinStack);
1648
+ }
1649
+ findRecordAliases(context, path) {
1650
+ for (const seg of path) {
1651
+ const field = context.getFieldByName([seg]);
1652
+ if (field instanceof QueryFieldStruct) {
1653
+ const qs = field.queryStruct;
1654
+ if (qs.structDef.type === 'record' &&
1655
+ (0, malloy_types_1.hasExpression)(qs.structDef) &&
1656
+ qs.parent) {
1657
+ qs.informOfAliasValue(this.exprToSQL(this.rootResult, qs.parent, qs.structDef.e));
1658
+ }
1659
+ context = qs;
1660
+ }
1661
+ }
1639
1662
  }
1640
1663
  addDependantExpr(resultStruct, context, e, joinStack) {
1641
1664
  var _a;
@@ -1679,6 +1702,7 @@ class QueryQuery extends QueryField {
1679
1702
  }
1680
1703
  }
1681
1704
  if (expr.node === 'field') {
1705
+ this.findRecordAliases(context, expr.path);
1682
1706
  const field = context.getDimensionOrMeasureByName(expr.path);
1683
1707
  if ((0, malloy_types_1.hasExpression)(field.fieldDef)) {
1684
1708
  this.addDependantExpr(resultStruct, field.parent, field.fieldDef.e, joinStack);
@@ -1692,6 +1716,7 @@ class QueryQuery extends QueryField {
1692
1716
  else if (expr.node === 'aggregate') {
1693
1717
  if ((0, malloy_types_1.isAsymmetricExpr)(expr)) {
1694
1718
  if (expr.structPath) {
1719
+ this.findRecordAliases(context, expr.structPath);
1695
1720
  this.addDependantPath(resultStruct, context, expr.structPath, expr.function, joinStack);
1696
1721
  }
1697
1722
  else {
@@ -1719,7 +1744,7 @@ class QueryQuery extends QueryField {
1719
1744
  let resultIndex = 1;
1720
1745
  for (const f of this.getSegmentFields(resultStruct)) {
1721
1746
  const { as, field } = this.expandField(f);
1722
- if (field instanceof QueryTurtle || field instanceof QueryQuery) {
1747
+ if (field instanceof QueryQuery) {
1723
1748
  if (this.firstSegment.type === 'project') {
1724
1749
  throw new Error(`Nested views cannot be used in select - '${field.fieldDef.name}'`);
1725
1750
  }
@@ -1727,7 +1752,7 @@ class QueryQuery extends QueryField {
1727
1752
  this.expandFields(fir);
1728
1753
  resultStruct.add(as, fir);
1729
1754
  }
1730
- else if (field instanceof QueryAtomicField) {
1755
+ else if (field instanceof AbstractQueryAtomic) {
1731
1756
  resultStruct.addField(as, field, {
1732
1757
  resultIndex,
1733
1758
  type: 'result',
@@ -1738,16 +1763,12 @@ class QueryQuery extends QueryField {
1738
1763
  throw new Error(`Aggregate Fields cannot be used in select - '${field.fieldDef.name}'`);
1739
1764
  }
1740
1765
  }
1741
- // } else if (field instanceof QueryStruct) {
1742
- // // this could probably be optimized. We are adding the primary key of the joined structure
1743
- // // instead of the foreignKey. We have to do this in at least the INNER join case
1744
- // // so i'm just going to let the SQL database do the optimization (which is pretty rudimentary)
1745
- // const pkFieldDef = field.getAsQueryField();
1746
- // resultStruct.addField(as, pkFieldDef, {
1747
- // resultIndex,
1748
- // type: "result",
1749
- // });
1750
- // resultStruct.addStructToJoin(field, false);
1766
+ }
1767
+ else if (field instanceof QueryFieldStruct) {
1768
+ resultStruct.addField(as, field, {
1769
+ resultIndex,
1770
+ type: 'result',
1771
+ });
1751
1772
  }
1752
1773
  // else if (
1753
1774
  // this.firstSegment.type === "project" &&
@@ -1818,8 +1839,8 @@ class QueryQuery extends QueryField {
1818
1839
  const alwaysJoins = (_a = stage.alwaysJoins) !== null && _a !== void 0 ? _a : [];
1819
1840
  for (const joinName of alwaysJoins) {
1820
1841
  const qs = this.parent.getChildByName(joinName);
1821
- if (qs instanceof QueryStruct) {
1822
- rootResult.addStructToJoin(qs, this, undefined, []);
1842
+ if (qs instanceof QueryFieldStruct) {
1843
+ rootResult.addStructToJoin(qs.queryStruct, this, undefined, []);
1823
1844
  }
1824
1845
  }
1825
1846
  }
@@ -1922,10 +1943,6 @@ class QueryQuery extends QueryField {
1922
1943
  }
1923
1944
  else if (fi instanceof FieldInstanceField) {
1924
1945
  if (fi.fieldUsage.type === 'result') {
1925
- // mtoy todo -- remember why you commented this out
1926
- // if (fi.f instanceof QueryFieldStruct) {
1927
- // fields.push(fi.f.getAsJoinedStructDef(name));
1928
- // }
1929
1946
  // if there is only one dimension, it is the primaryKey
1930
1947
  // if there are more, primaryKey is undefined.
1931
1948
  if (isScalarField(fi.f)) {
@@ -1937,16 +1954,25 @@ class QueryQuery extends QueryField {
1937
1954
  }
1938
1955
  dimCount++;
1939
1956
  }
1940
- const location = fi.f.fieldDef.location;
1941
- const annotation = fi.f.fieldDef.annotation;
1957
+ // Remove computations because they are all resolved
1958
+ let fOut = fi.f.fieldDef;
1959
+ if ((0, malloy_types_1.hasExpression)(fOut)) {
1960
+ fOut = { ...fOut };
1961
+ // "as" because delete needs the property to be optional
1962
+ delete fOut.e;
1963
+ delete fOut.code;
1964
+ delete fOut.expressionType;
1965
+ }
1966
+ const location = fOut.location;
1967
+ const annotation = fOut.annotation;
1942
1968
  // build out the result fields...
1943
- switch (fi.f.fieldDef.type) {
1969
+ switch (fOut.type) {
1944
1970
  case 'boolean':
1945
1971
  case 'json':
1946
1972
  case 'string':
1947
1973
  fields.push({
1948
1974
  name,
1949
- type: fi.f.fieldDef.type,
1975
+ type: fOut.type,
1950
1976
  resultMetadata,
1951
1977
  location,
1952
1978
  annotation,
@@ -1954,8 +1980,8 @@ class QueryQuery extends QueryField {
1954
1980
  break;
1955
1981
  case 'date':
1956
1982
  case 'timestamp': {
1957
- const timeframe = fi.f.fieldDef.timeframe;
1958
- const fd = { type: fi.f.fieldDef.type };
1983
+ const timeframe = fOut.timeframe;
1984
+ const fd = { type: fOut.type };
1959
1985
  if (timeframe) {
1960
1986
  fd.timeframe = timeframe;
1961
1987
  }
@@ -1971,7 +1997,7 @@ class QueryQuery extends QueryField {
1971
1997
  case 'number':
1972
1998
  fields.push({
1973
1999
  name,
1974
- numberType: fi.f.fieldDef.numberType,
2000
+ numberType: fOut.numberType,
1975
2001
  type: 'number',
1976
2002
  resultMetadata,
1977
2003
  location,
@@ -1979,10 +2005,13 @@ class QueryQuery extends QueryField {
1979
2005
  });
1980
2006
  break;
1981
2007
  case 'sql native':
1982
- fields.push({ ...fi.f.fieldDef, resultMetadata, location });
2008
+ case 'record':
2009
+ case 'array': {
2010
+ fields.push({ ...fOut, resultMetadata });
1983
2011
  break;
2012
+ }
1984
2013
  default:
1985
- throw new Error(`unknown Field Type in query ${JSON.stringify(fi.f.fieldDef)}`);
2014
+ throw new Error(`unknown Field Type in query ${JSON.stringify(fOut)}`);
1986
2015
  }
1987
2016
  }
1988
2017
  }
@@ -2002,7 +2031,7 @@ class QueryQuery extends QueryField {
2002
2031
  }
2003
2032
  return outputStruct;
2004
2033
  }
2005
- generateSQLJoinBlock(stageWriter, ji) {
2034
+ generateSQLJoinBlock(stageWriter, ji, depth) {
2006
2035
  var _a;
2007
2036
  let s = '';
2008
2037
  const qs = ji.queryStruct;
@@ -2063,7 +2092,7 @@ class QueryQuery extends QueryField {
2063
2092
  let select = `SELECT ${ji.alias}.*`;
2064
2093
  let joins = '';
2065
2094
  for (const childJoin of ji.children) {
2066
- joins += this.generateSQLJoinBlock(stageWriter, childJoin);
2095
+ joins += this.generateSQLJoinBlock(stageWriter, childJoin, depth + 1);
2067
2096
  select += `, ${this.parent.dialect.sqlSelectAliasAsStruct(childJoin.alias, getDialectFieldList(childJoin.queryStruct.structDef))} AS ${childJoin.alias}`;
2068
2097
  }
2069
2098
  select += `\nFROM ${structSQL} AS ${ji.alias}\n${joins}\nWHERE ${conditions === null || conditions === void 0 ? void 0 : conditions.join(' AND ')}\n`;
@@ -2075,10 +2104,27 @@ class QueryQuery extends QueryField {
2075
2104
  if (qs.parent === undefined || ji.parent === undefined) {
2076
2105
  throw new Error('Internal Error, nested structure with no parent.');
2077
2106
  }
2078
- const fieldExpression = this.parent.dialect.sqlFieldReference(qs.parent.getSQLIdentifier(), qsDef.name, 'struct', qs.parent.structDef.type === 'array', (0, malloy_types_1.isScalarArray)(this.parent.structDef));
2107
+ // We need an SQL expression which results in the array for us to pass to un-nest
2108
+ let arrayExpression;
2109
+ if ((0, malloy_types_1.hasExpression)(qsDef)) {
2110
+ // If this array is NOT contained in the parent, but a computed entity
2111
+ // then the thing we are joining is not "parent.childName", but
2112
+ // the expression which is built in that namespace
2113
+ arrayExpression = this.exprToSQL(this.rootResult, qs.parent, qsDef.e);
2114
+ }
2115
+ else {
2116
+ // If this is a reference through an expression at the top level,
2117
+ // need to generate the expression because the expression is written
2118
+ // in the top level, this call is being used to generate the join.
2119
+ // Below the top level, the expression will have been written into
2120
+ // a join at the top level, and the name will exist.
2121
+ // ... not sure this is the right way to do this
2122
+ // ... the test for this is called "source repeated record containing an array"
2123
+ arrayExpression = qs.parent.sqlChildReference(qsDef.name, depth === 0 ? { result: this.rootResult, field: this } : undefined);
2124
+ }
2079
2125
  // we need to generate primary key. If parent has a primary key combine
2080
2126
  // console.log(ji.alias, fieldExpression, this.inNestedPipeline());
2081
- s += `${this.parent.dialect.sqlUnnestAlias(fieldExpression, ji.alias, ji.getDialectFieldList(), ji.makeUniqueKey, (0, malloy_types_1.isScalarArray)(qsDef), this.inNestedPipeline())}\n`;
2127
+ s += `${this.parent.dialect.sqlUnnestAlias(arrayExpression, ji.alias, ji.getDialectFieldList(), ji.makeUniqueKey, (0, malloy_types_1.isScalarArray)(qsDef), this.inNestedPipeline())}\n`;
2082
2128
  }
2083
2129
  else if (qsDef.type === 'record') {
2084
2130
  throw new Error('Internal Error: records should never appear in join trees');
@@ -2087,7 +2133,7 @@ class QueryQuery extends QueryField {
2087
2133
  throw new Error(`Join type not implemented ${qs.structDef.type}`);
2088
2134
  }
2089
2135
  for (const childJoin of ji.children) {
2090
- s += this.generateSQLJoinBlock(stageWriter, childJoin);
2136
+ s += this.generateSQLJoinBlock(stageWriter, childJoin, depth + 1);
2091
2137
  }
2092
2138
  return s;
2093
2139
  }
@@ -2134,7 +2180,7 @@ class QueryQuery extends QueryField {
2134
2180
  throw new Error('Internal Error, queries must start from a basetable');
2135
2181
  }
2136
2182
  for (const childJoin of ji.children) {
2137
- s += this.generateSQLJoinBlock(stageWriter, childJoin);
2183
+ s += this.generateSQLJoinBlock(stageWriter, childJoin, 0);
2138
2184
  }
2139
2185
  return s;
2140
2186
  }
@@ -2716,9 +2762,10 @@ class QueryQuery extends QueryField {
2716
2762
  };
2717
2763
  pipeline.shift();
2718
2764
  for (const transform of pipeline) {
2719
- const s = new QueryStruct(structDef, undefined, {
2720
- model: this.parent.getModel(),
2721
- }, this.parent.prepareResultOptions);
2765
+ const parent = this.parent.parent
2766
+ ? { struct: this.parent.parent }
2767
+ : { model: this.parent.getModel() };
2768
+ const s = new QueryStruct(structDef, undefined, parent, this.parent.prepareResultOptions);
2722
2769
  const q = QueryQuery.makeQuery({ type: 'turtle', name: '~computeLastStage~', pipeline: [transform] }, s, stageWriter, this.isJoinedSubquery);
2723
2770
  q.prepare(stageWriter);
2724
2771
  lastStageName = q.generateSQL(stageWriter);
@@ -2761,7 +2808,7 @@ class QueryQueryIndexStage extends QueryQuery {
2761
2808
  resultIndex,
2762
2809
  type: 'result',
2763
2810
  });
2764
- if (field instanceof QueryAtomicField) {
2811
+ if (field instanceof AbstractQueryAtomic) {
2765
2812
  this.addDependancies(resultStruct, field);
2766
2813
  }
2767
2814
  resultIndex++;
@@ -2876,7 +2923,7 @@ class QueryQueryRaw extends QueryQuery {
2876
2923
  }
2877
2924
  getResultStructDef() {
2878
2925
  if (!(0, malloy_types_1.isSourceDef)(this.parent.structDef)) {
2879
- throw new Error(`Result cannot by type ${this.parent.structDef.type}`);
2926
+ throw new Error(`Result cannot be type ${this.parent.structDef.type}`);
2880
2927
  }
2881
2928
  return { ...this.parent.structDef, type: 'query_result' };
2882
2929
  }
@@ -2906,10 +2953,9 @@ class QueryQueryIndex extends QueryQuery {
2906
2953
  const stage = stageMap[stageRoot];
2907
2954
  if (stage === undefined) {
2908
2955
  const f = this.parent.nameMap.get(fref.path[0]);
2909
- if (f instanceof QueryStruct &&
2910
- (0, malloy_types_1.isJoined)(f.structDef) &&
2911
- f.structDef.join === 'many' &&
2912
- f.structDef.fields.length > 1) {
2956
+ if (f instanceof QueryFieldStruct &&
2957
+ f.fieldDef.join === 'many' &&
2958
+ f.fieldDef.fields.length > 1) {
2913
2959
  const toStage = [fref];
2914
2960
  stageMap[stageRoot] = toStage;
2915
2961
  this.stages.push(toStage);
@@ -2978,18 +3024,39 @@ class QueryQueryIndex extends QueryQuery {
2978
3024
  return ret;
2979
3025
  }
2980
3026
  }
2981
- /** Structure object as it is used to build a query */
2982
3027
  /*
2983
- Sometimes this is built from a field def, as in a join
2984
-
2985
- But sometimes this is built as the intermediate stage between pipelines
2986
- and in that case it doesn't have a fieldDef which bugs me because querynode
2987
- always has a fielddef so i think that is wrong too
2988
-
2989
- */
2990
- class QueryStruct extends QueryNode {
2991
- constructor(structDef, sourceArguments, parent, prepareResultOptions, referenceId) {
2992
- super(referenceId);
3028
+ * The input to a query will always be a QueryStruct. A QueryStruct is also a namespace
3029
+ * for tracking joins, and so a QueryFieldStruct is a QueryField which has a QueryStruct.
3030
+ *
3031
+ * This is a result of it being impossible to inherit both from QueryStruct and QueryField
3032
+ * for array and record types.
3033
+ */
3034
+ class QueryFieldStruct extends QueryField {
3035
+ constructor(jfd, sourceArguments, parent, prepareResultOptions, referenceId) {
3036
+ super(jfd, parent, referenceId);
3037
+ this.fieldDef = jfd;
3038
+ this.queryStruct = new QueryStruct(jfd, sourceArguments, { struct: parent }, prepareResultOptions);
3039
+ }
3040
+ /*
3041
+ * Proxy the field-like methods that QueryStruct implements, eventually
3042
+ * those probably should be in here ... I thought this would be important
3043
+ * but maybe it isn't, it doesn't fix the problem I am working on ...
3044
+ */
3045
+ // mtoy todo review with lloyd if any of these are needed, had to NOT
3046
+ // do getIdentifier to pass a test, didn't stop to think why.
3047
+ // getIdentifier() {
3048
+ // return this.queryStruct.getIdentifier();
3049
+ // }
3050
+ getJoinableParent() {
3051
+ return this.queryStruct.getJoinableParent();
3052
+ }
3053
+ getFullOutputName() {
3054
+ return this.queryStruct.getFullOutputName();
3055
+ }
3056
+ }
3057
+ /** Structure object as it is used to build a query */
3058
+ class QueryStruct {
3059
+ constructor(structDef, sourceArguments, parent, prepareResultOptions) {
2993
3060
  this.structDef = structDef;
2994
3061
  this.sourceArguments = sourceArguments;
2995
3062
  this.prepareResultOptions = prepareResultOptions;
@@ -3014,6 +3081,9 @@ class QueryStruct extends QueryNode {
3014
3081
  this.dialect = (0, dialect_1.getDialect)(structDef.dialect);
3015
3082
  this.addFieldsFromFieldList(structDef.fields);
3016
3083
  }
3084
+ informOfAliasValue(av) {
3085
+ this.recordAlias = av;
3086
+ }
3017
3087
  maybeEmitParameterizedSourceUsage() {
3018
3088
  var _a;
3019
3089
  if ((0, malloy_types_1.isSourceDef)(this.structDef)) {
@@ -3076,15 +3146,15 @@ class QueryStruct extends QueryNode {
3076
3146
  addFieldsFromFieldList(fields) {
3077
3147
  for (const field of fields) {
3078
3148
  const as = (0, malloy_types_1.getIdentifier)(field);
3079
- if ((0, malloy_types_1.isJoined)(field)) {
3080
- this.addFieldToNameMap(as, new QueryStruct(field, undefined, { struct: this }, this.prepareResultOptions));
3081
- }
3082
- else if (field.type === 'turtle') {
3149
+ if (field.type === 'turtle') {
3083
3150
  this.addFieldToNameMap(as, QueryQuery.makeQuery(field, this, undefined, false));
3084
3151
  }
3085
- else {
3152
+ else if ((0, malloy_types_1.isAtomic)(field) || (0, malloy_types_1.isJoinedSource)(field)) {
3086
3153
  this.addFieldToNameMap(as, this.makeQueryField(field));
3087
3154
  }
3155
+ else {
3156
+ throw new Error('mtoy did nit add field');
3157
+ }
3088
3158
  }
3089
3159
  // if we don't have distinct key yet for this struct, add it.
3090
3160
  if (!this.nameMap.has('__distinct_key')) {
@@ -3133,15 +3203,47 @@ class QueryStruct extends QueryNode {
3133
3203
  return this.getIdentifier();
3134
3204
  }
3135
3205
  }
3206
+ sqlChildReference(name, expand) {
3207
+ let parentRef = this.getSQLIdentifier();
3208
+ if (expand && (0, malloy_types_1.isAtomic)(this.structDef) && (0, malloy_types_1.hasExpression)(this.structDef)) {
3209
+ parentRef = expand.field.exprToSQL(expand.result, this, this.structDef.e);
3210
+ }
3211
+ let refType = 'table';
3212
+ if (this.structDef.type === 'record') {
3213
+ refType = 'record';
3214
+ }
3215
+ else if (this.structDef.type === 'array') {
3216
+ refType =
3217
+ this.structDef.elementTypeDef.type === 'record_element'
3218
+ ? 'array[record]'
3219
+ : 'array[scalar]';
3220
+ }
3221
+ else if (this.structDef.type === 'nest_source') {
3222
+ refType = 'nest source';
3223
+ }
3224
+ const child = this.getChildByName(name);
3225
+ const childType = (child === null || child === void 0 ? void 0 : child.fieldDef.type) || 'unknown';
3226
+ return this.dialect.sqlFieldReference(parentRef, refType, name, childType);
3227
+ }
3136
3228
  // return the name of the field in SQL
3137
3229
  getIdentifier() {
3138
3230
  // if it is the root table, use provided alias if we have one.
3139
3231
  if ((0, malloy_types_1.isBaseTable)(this.structDef)) {
3140
3232
  return 'base';
3141
3233
  }
3234
+ // If this is a synthetic column, return the expression rather than the name
3235
+ // because the name will not exist. Only for records because the other types
3236
+ // will have joins and thus be in the namespace. We can't compute it here
3237
+ // because we don't have access to the Query to call exprToSQL.
3238
+ if (this.structDef.type === 'record' && (0, malloy_types_1.hasExpression)(this.structDef)) {
3239
+ if (this.recordAlias) {
3240
+ return this.recordAlias;
3241
+ }
3242
+ throw new Error('INTERNAL ERROR, record field alias not pre-computed');
3243
+ }
3142
3244
  // if this is an inline object, include the parents alias.
3143
3245
  if (this.structDef.type === 'record' && this.parent) {
3144
- return (this.parent.getSQLIdentifier() + '.' + (0, malloy_types_1.getIdentifier)(this.structDef));
3246
+ return this.parent.sqlChildReference((0, malloy_types_1.getIdentifier)(this.structDef), undefined);
3145
3247
  }
3146
3248
  // we are somewhere in the join tree. Make sure the alias is unique.
3147
3249
  return this.getAliasIdentifier();
@@ -3222,8 +3324,8 @@ class QueryStruct extends QueryNode {
3222
3324
  }
3223
3325
  }
3224
3326
  for (const [, v] of this.nameMap) {
3225
- if (v instanceof QueryStruct) {
3226
- v.resolveQueryFields();
3327
+ if (v instanceof QueryFieldStruct) {
3328
+ v.queryStruct.resolveQueryFields();
3227
3329
  }
3228
3330
  }
3229
3331
  }
@@ -3255,6 +3357,13 @@ class QueryStruct extends QueryNode {
3255
3357
  /** makes a new queryable field object from a fieldDef */
3256
3358
  makeQueryField(field, referenceId) {
3257
3359
  switch (field.type) {
3360
+ case 'array':
3361
+ case 'record':
3362
+ case 'query_source':
3363
+ case 'table':
3364
+ case 'sql_select':
3365
+ case 'composite':
3366
+ return new QueryFieldStruct(field, undefined, this, this.prepareResultOptions);
3258
3367
  case 'string':
3259
3368
  return new QueryFieldString(field, this, referenceId);
3260
3369
  case 'date':
@@ -3269,13 +3378,10 @@ class QueryStruct extends QueryNode {
3269
3378
  return new QueryFieldJSON(field, this, referenceId);
3270
3379
  case 'sql native':
3271
3380
  return new QueryFieldUnsupported(field, this, referenceId);
3272
- // case "reduce":
3273
- // case "project":
3274
- // case "index":
3275
3381
  case 'turtle':
3276
- return new QueryTurtle(field, this, referenceId);
3382
+ return QueryQuery.makeQuery(field, this, undefined, false);
3277
3383
  default:
3278
- throw new Error(`unknown field definition ${JSON.stringify(field)}`);
3384
+ throw new Error(`unknown field definition ${(JSON.stringify(field), undefined, 2)}`);
3279
3385
  }
3280
3386
  }
3281
3387
  structSourceSQL(stageWriter) {
@@ -3323,35 +3429,43 @@ class QueryStruct extends QueryNode {
3323
3429
  }
3324
3430
  /** convert a path into a field reference */
3325
3431
  getFieldByName(path) {
3326
- return path.reduce((lookIn, childName) => {
3327
- const r = lookIn.getChildByName(childName);
3328
- if (r === undefined) {
3329
- throw new Error(path.length === 1
3330
- ? `'${childName}' not found`
3331
- : `'${childName}' not found in '${path.join('.')}'`);
3432
+ let found = undefined;
3433
+ let lookIn = this;
3434
+ let notFound = path[0];
3435
+ for (const n of path) {
3436
+ found = lookIn === null || lookIn === void 0 ? void 0 : lookIn.getChildByName(n);
3437
+ if (!found) {
3438
+ notFound = n;
3439
+ break;
3332
3440
  }
3333
- return r;
3334
- }, this);
3441
+ lookIn =
3442
+ found instanceof QueryFieldStruct ? found.queryStruct : undefined;
3443
+ }
3444
+ if (found === undefined) {
3445
+ const pathErr = path.length > 1 ? ` in ${path.join('.')}` : '';
3446
+ throw new Error(`${notFound} not found${pathErr}`);
3447
+ }
3448
+ return found;
3335
3449
  }
3336
3450
  // structs referenced in queries are converted to fields.
3337
3451
  getQueryFieldByName(name) {
3338
3452
  const field = this.getFieldByName(name);
3339
- if (field instanceof QueryStruct) {
3453
+ if (field instanceof QueryFieldStruct) {
3340
3454
  throw new Error(`Cannot reference ${name.join('.')} as a scalar'`);
3341
3455
  }
3342
3456
  return field;
3343
3457
  }
3344
- getQueryFieldReference(name, annotation) {
3345
- const field = this.getFieldByName(name);
3458
+ getQueryFieldReference(path, annotation) {
3459
+ const field = this.getFieldByName(path);
3346
3460
  if (annotation) {
3347
3461
  if (field.parent === undefined) {
3348
- throw new Error('Unexpected reference to orphaned query field');
3462
+ throw new Error('Inconcievable, field reference to orphaned query field');
3349
3463
  }
3350
3464
  // Made a field object from the source, but the annotations were computed by the compiler
3351
3465
  // when it generated the reference, and has both the source and reference annotations included.
3352
- if (field instanceof QueryStruct) {
3353
- const newDef = { ...field.structDef, annotation };
3354
- return new QueryStruct(newDef, undefined, field.parent, {}, field.referenceId);
3466
+ if (field instanceof QueryFieldStruct) {
3467
+ const newDef = { ...field.fieldDef, annotation };
3468
+ return new QueryFieldStruct(newDef, undefined, field.parent, {}, field.referenceId);
3355
3469
  }
3356
3470
  else {
3357
3471
  const newDef = { ...field.fieldDef, annotation };
@@ -3361,29 +3475,30 @@ class QueryStruct extends QueryNode {
3361
3475
  return field;
3362
3476
  }
3363
3477
  getDimensionOrMeasureByName(name) {
3364
- const query = this.getFieldByName(name);
3365
- if (query instanceof QueryAtomicField) {
3366
- return query;
3478
+ const field = this.getFieldByName(name);
3479
+ if (!field.isAtomic()) {
3480
+ throw new Error(`${name} is not an atomic field? Inconceivable!`);
3367
3481
  }
3368
- throw new Error(`${name} is not an atomic field? Inconceivable!`);
3482
+ return field;
3369
3483
  }
3370
3484
  /** returns a query object for the given name */
3371
3485
  getDimensionByName(name) {
3372
- const query = this.getFieldByName(name);
3373
- if (query instanceof QueryAtomicField && isScalarField(query)) {
3374
- return query;
3486
+ const field = this.getFieldByName(name);
3487
+ if (field.isAtomic() && isScalarField(field)) {
3488
+ return field;
3375
3489
  }
3376
3490
  throw new Error(`${name} is not an atomic scalar field? Inconceivable!`);
3377
3491
  }
3378
3492
  /** returns a query object for the given name */
3379
3493
  getStructByName(name) {
3380
- const struct = this.getFieldByName(name);
3381
- if (struct instanceof QueryStruct) {
3382
- return struct;
3494
+ if (name.length === 0) {
3495
+ return this;
3383
3496
  }
3384
- else {
3385
- throw new Error(`Error: Path to structure not found '${name.join('.')}'`);
3497
+ const struct = this.getFieldByName(name);
3498
+ if (struct instanceof QueryFieldStruct) {
3499
+ return struct.queryStruct;
3386
3500
  }
3501
+ throw new Error(`Error: Path to structure not found '${name.join('.')}'`);
3387
3502
  }
3388
3503
  getDistinctKey() {
3389
3504
  if (this.structDef.type !== 'record') {
@@ -3532,7 +3647,7 @@ class QueryModel {
3532
3647
  const struct = this.getStructByName(explore);
3533
3648
  let indexStar = [];
3534
3649
  for (const [fn, fv] of struct.nameMap) {
3535
- if (!(fv instanceof QueryStruct)) {
3650
+ if (!(fv instanceof QueryFieldStruct)) {
3536
3651
  if (isScalarField(fv) && fv.includeInWildcard()) {
3537
3652
  indexStar.push({ type: 'fieldref', path: [fn] });
3538
3653
  }