@malloydata/malloy 0.0.412 → 0.0.414

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 (45) hide show
  1. package/dist/api/foundation/core.d.ts +21 -0
  2. package/dist/api/foundation/core.js +30 -0
  3. package/dist/dialect/databricks/databricks.d.ts +2 -1
  4. package/dist/dialect/databricks/databricks.js +12 -6
  5. package/dist/dialect/dialect.d.ts +12 -1
  6. package/dist/dialect/dialect.js +25 -0
  7. package/dist/dialect/duckdb/duckdb.d.ts +2 -1
  8. package/dist/dialect/duckdb/duckdb.js +9 -2
  9. package/dist/dialect/mysql/mysql.d.ts +2 -1
  10. package/dist/dialect/mysql/mysql.js +14 -5
  11. package/dist/dialect/postgres/postgres.d.ts +2 -1
  12. package/dist/dialect/postgres/postgres.js +9 -2
  13. package/dist/dialect/snowflake/snowflake.d.ts +2 -1
  14. package/dist/dialect/snowflake/snowflake.js +11 -3
  15. package/dist/dialect/standardsql/standardsql.d.ts +2 -1
  16. package/dist/dialect/standardsql/standardsql.js +8 -2
  17. package/dist/dialect/trino/trino.d.ts +2 -1
  18. package/dist/dialect/trino/trino.js +8 -2
  19. package/dist/lang/ast/field-space/dynamic-space.js +14 -1
  20. package/dist/lang/ast/field-space/query-spaces.d.ts +1 -0
  21. package/dist/lang/ast/field-space/query-spaces.js +5 -0
  22. package/dist/lang/ast/query-items/field-references.d.ts +10 -1
  23. package/dist/lang/ast/query-items/field-references.js +67 -4
  24. package/dist/lang/ast/query-properties/nest.js +15 -0
  25. package/dist/lang/ast/source-elements/named-source.js +4 -1
  26. package/dist/lang/ast/statements/define-source.js +9 -2
  27. package/dist/lang/ast/types/malloy-element.js +3 -4
  28. package/dist/lang/parse-log.d.ts +10 -0
  29. package/dist/lang/parse-log.js +3 -0
  30. package/dist/model/expression_compiler.js +2 -2
  31. package/dist/model/field_instance.d.ts +1 -0
  32. package/dist/model/field_instance.js +14 -0
  33. package/dist/model/index.d.ts +2 -1
  34. package/dist/model/index.js +3 -1
  35. package/dist/model/malloy_types.d.ts +25 -6
  36. package/dist/model/nest-capability.d.ts +37 -0
  37. package/dist/model/nest-capability.js +31 -0
  38. package/dist/model/persist_utils.js +1 -1
  39. package/dist/model/query_query.d.ts +26 -5
  40. package/dist/model/query_query.js +256 -88
  41. package/dist/model/source_def_utils.d.ts +31 -7
  42. package/dist/model/source_def_utils.js +39 -9
  43. package/dist/version.d.ts +1 -1
  44. package/dist/version.js +1 -1
  45. package/package.json +4 -4
@@ -7,8 +7,11 @@ Object.defineProperty(exports, "__esModule", { value: true });
7
7
  exports.FieldReferences = exports.WildcardFieldReference = exports.ViewOrScalarFieldReference = exports.DimensionFieldReference = exports.MeasureFieldReference = exports.DeclareFieldReference = exports.ProjectFieldReference = exports.GroupByFieldReference = exports.AggregateFieldReference = exports.IndexFieldReference = exports.CalculateFieldReference = exports.DrillFieldReference = exports.ParameterFieldReference = exports.GroupedByReference = exports.PartitionByFieldReference = exports.RefineFromFieldReference = exports.ExpressionFieldReference = exports.AccessModifierFieldReference = exports.AcceptExceptFieldReference = exports.FieldReference = void 0;
8
8
  const reference_field_1 = require("../field-space/reference-field");
9
9
  const definition_list_1 = require("../types/definition-list");
10
+ const field_space_1 = require("../types/field-space");
10
11
  const malloy_element_1 = require("../types/malloy-element");
11
12
  const noteable_1 = require("../types/noteable");
13
+ const expr_id_reference_1 = require("../expressions/expr-id-reference");
14
+ const field_declaration_1 = require("./field-declaration");
12
15
  const typecheck_utils_1 = require("./typecheck_utils");
13
16
  class FieldReference extends malloy_element_1.ListOf {
14
17
  constructor(names) {
@@ -16,16 +19,64 @@ class FieldReference extends malloy_element_1.ListOf {
16
19
  this.isNoteableObj = true;
17
20
  this.extendNote = noteable_1.extendNoteMethod;
18
21
  }
22
+ // The `name is path` declaration constructor used to auto-resolve an output
23
+ // name collision for this kind of reference, or undefined if this reference
24
+ // does not produce an output column named for its last path element (e.g.
25
+ // index references, view references). Overridden by the references that do.
26
+ autoRenameDeclarationCtor() {
27
+ return undefined;
28
+ }
19
29
  makeEntry(fs) {
20
30
  const refName = this.outputName;
21
- if (fs.entry(refName)) {
22
- this.logError('output-name-conflict', `Output already has a field named '${refName}'`);
23
- }
24
- else {
31
+ if (fs.entry(refName) === undefined) {
25
32
  // In a QuerySpace, this needs to be able to find the thing to which it refers
26
33
  const fromFS = fs.isQueryFieldSpace() ? fs.inputSpace() : fs;
27
34
  fs.newEntry(refName, this, new reference_field_1.ReferenceField(this, fromFS));
35
+ return;
28
36
  }
37
+ // The natural (last-segment) name is taken. A dotted reference can be
38
+ // auto-renamed using its full path; a single-name reference has no prefix
39
+ // to disambiguate with, so it stays a hard error.
40
+ const declCtor = this.autoRenameDeclarationCtor();
41
+ if (declCtor && this.list.length > 1 && fs.isQueryFieldSpace()) {
42
+ this.autoRename(fs, fs.outputSpace().synthesizedNames, declCtor, refName);
43
+ return;
44
+ }
45
+ if (fs.isQueryFieldSpace() &&
46
+ fs.outputSpace().synthesizedNames.has(refName)) {
47
+ this.logError('output-name-conflict', `Output already has a field named '${refName}', which was ` +
48
+ 'auto-generated by renaming an ambiguous reference; name one of the ' +
49
+ "conflicting references explicitly with 'is' to resolve this");
50
+ }
51
+ else {
52
+ this.logError('output-name-conflict', `Output already has a field named '${refName}'`);
53
+ }
54
+ }
55
+ // Rewrite `path.to.field` into the equivalent `autoName is path.to.field`
56
+ // declaration — the same AST the user would write to fix it by hand — and
57
+ // let its normal makeEntry produce the (renamed) output field.
58
+ autoRename(fs, synthesizedNames, declCtor, refName) {
59
+ // `path.join('_')`, then `_2`, `_3`, ... until free. The synthetic name
60
+ // yields to whatever is already in the output, so a user-written name wins.
61
+ const base = this.path.join('_');
62
+ let autoName = base;
63
+ let n = 2;
64
+ while (fs.entry(autoName) !== undefined) {
65
+ autoName = `${base}_${n++}`;
66
+ }
67
+ synthesizedNames.add(autoName);
68
+ this.logWarning('output-field-auto-renamed', `Output already has a field named '${refName}', so '${this.refString}' ` +
69
+ `was renamed to '${autoName}'. Write '${autoName} is ${this.refString}' ` +
70
+ 'to set the name explicitly and silence this warning.');
71
+ const renamedRef = new ExpressionFieldReference(this.list.map(n => new field_space_1.FieldName(n.refString)));
72
+ const decl = new declCtor(new expr_id_reference_1.ExprIdReference(renamedRef), autoName);
73
+ if (this.note) {
74
+ decl.note = this.note;
75
+ }
76
+ // Attach under this reference so the synthetic nodes inherit its location
77
+ // and resolve back to the translator for logging.
78
+ this.has({ autoRenamed: decl });
79
+ decl.makeEntry(fs);
29
80
  }
30
81
  getName() {
31
82
  return this.nameString;
@@ -161,6 +212,9 @@ class CalculateFieldReference extends FieldReference {
161
212
  super(...arguments);
162
213
  this.elementType = 'calculateFieldReference';
163
214
  }
215
+ autoRenameDeclarationCtor() {
216
+ return field_declaration_1.CalculateFieldDeclaration;
217
+ }
164
218
  typecheck(type) {
165
219
  (0, typecheck_utils_1.typecheckCalculate)(type, this);
166
220
  }
@@ -188,6 +242,9 @@ class AggregateFieldReference extends FieldReference {
188
242
  super(...arguments);
189
243
  this.elementType = 'aggregateFieldReference';
190
244
  }
245
+ autoRenameDeclarationCtor() {
246
+ return field_declaration_1.AggregateFieldDeclaration;
247
+ }
191
248
  typecheck(type) {
192
249
  (0, typecheck_utils_1.typecheckAggregate)(type, this);
193
250
  }
@@ -198,6 +255,9 @@ class GroupByFieldReference extends FieldReference {
198
255
  super(...arguments);
199
256
  this.elementType = 'groupByFieldReference';
200
257
  }
258
+ autoRenameDeclarationCtor() {
259
+ return field_declaration_1.GroupByFieldDeclaration;
260
+ }
201
261
  typecheck(type) {
202
262
  (0, typecheck_utils_1.typecheckGroupBy)(type, this);
203
263
  }
@@ -208,6 +268,9 @@ class ProjectFieldReference extends FieldReference {
208
268
  super(...arguments);
209
269
  this.elementType = 'projectFieldReference';
210
270
  }
271
+ autoRenameDeclarationCtor() {
272
+ return field_declaration_1.ProjectFieldDeclaration;
273
+ }
211
274
  typecheck(type) {
212
275
  (0, typecheck_utils_1.typecheckProject)(type, this);
213
276
  }
@@ -39,6 +39,7 @@ var __importStar = (this && this.__importStar) || (function () {
39
39
  Object.defineProperty(exports, "__esModule", { value: true });
40
40
  exports.NestFieldDeclaration = void 0;
41
41
  const model = __importStar(require("../../../model/malloy_types"));
42
+ const nest_capability_1 = require("../../../model/nest-capability");
42
43
  const query_utils_1 = require("../query-utils");
43
44
  const view_field_declaration_1 = require("../source-properties/view-field-declaration");
44
45
  const query_property_interface_1 = require("../types/query-property-interface");
@@ -63,6 +64,20 @@ class NestFieldDeclaration extends view_field_declaration_1.ViewFieldDeclaration
63
64
  ? pipeline[0].refSummary
64
65
  : undefined;
65
66
  const checkedPipeline = (0, query_utils_1.detectAndRemovePartialStages)(pipeline, this);
67
+ // Reject — at translate time, with a located message — any stage of this
68
+ // nest the compiler can't emit for this dialect, rather than producing
69
+ // broken SQL. The compiler asks segment-at-a-time as it recurses; the
70
+ // translator holds the whole pipeline, so it asks every stage.
71
+ const dialect = fs.dialectObj();
72
+ if (dialect) {
73
+ for (let i = 0; i < checkedPipeline.length; i++) {
74
+ const strategy = (0, nest_capability_1.nestStrategy)(checkedPipeline[i], dialect, i < checkedPipeline.length - 1);
75
+ if (strategy.kind === 'unsupported') {
76
+ this.logError(strategy.reason, { dialect: dialect.name });
77
+ break;
78
+ }
79
+ }
80
+ }
66
81
  const pipelineWithDrillPaths = (0, drill_1.attachDrillPaths)(checkedPipeline, this.name);
67
82
  this.turtleDef = {
68
83
  type: 'turtle',
@@ -91,7 +91,10 @@ class NamedSource extends source_1.Source {
91
91
  else {
92
92
  (_a = this.document()) === null || _a === void 0 ? void 0 : _a.checkExperimentalDialect(this, entry.dialect);
93
93
  if ((0, malloy_types_1.isSourceDef)(entry)) {
94
- return { ...entry };
94
+ // This struct is created as an unmodified reference to `entry`. Mark it
95
+ // with entry's own identity; the modification path (DynamicSpace) clears
96
+ // this so only true references carry a referenceID.
97
+ return { ...entry, referenceID: entry.sourceID };
95
98
  }
96
99
  }
97
100
  // I think this is now a never
@@ -58,9 +58,16 @@ class DefineSource extends malloy_element_1.MalloyElement {
58
58
  }
59
59
  : { ...this.note };
60
60
  }
61
- if ((0, malloy_types_1.isPersistableSourceDef)(entry)) {
61
+ if ((0, malloy_types_1.isSourceDef)(entry)) {
62
+ // Every source gets a stable identity for its own definition. referenceID
63
+ // is left as it arrived: set to the referenced source's sourceID for an
64
+ // unmodified rename (`source: a is b`), and absent for a source that
65
+ // defines its own shape (table/sql/query, or a modified/extended source,
66
+ // which DynamicSpace cleared).
62
67
  entry.sourceID = (0, source_def_utils_1.mkSourceID)(this.name, (_b = this.location) === null || _b === void 0 ? void 0 : _b.url);
63
- entry.persistent = (0, persist_utils_1.checkPersistAnnotation)(entry).persist;
68
+ if ((0, malloy_types_1.isPersistableSourceDef)(entry)) {
69
+ entry.persistent = (0, persist_utils_1.checkPersistAnnotation)(entry).persist;
70
+ }
64
71
  }
65
72
  entry.partitionComposite =
66
73
  (_d = (0, composite_source_utils_1.getPartitionCompositeDesc)(this.note, structDef, (_c = this.sourceExpr) !== null && _c !== void 0 ? _c : this)) !== null && _d !== void 0 ? _d : structDef.partitionComposite;
@@ -665,10 +665,9 @@ class Document extends MalloyElement {
665
665
  this.modelWasModified = true;
666
666
  }
667
667
  this.documentModel.set(str, ent);
668
- // Maintain sourceRegistry for persistable sources with sourceID
669
- if ((0, malloy_types_1.isSourceDef)(ent.entry) &&
670
- (0, malloy_types_1.isPersistableSourceDef)(ent.entry) &&
671
- ent.entry.sourceID) {
668
+ // Maintain the source-id table so sourceID/referenceID values resolve to
669
+ // their SourceDef. Every named source is registered by its sourceID.
670
+ if ((0, malloy_types_1.isSourceDef)(ent.entry) && ent.entry.sourceID) {
672
671
  this.documentSrcRegistry[ent.entry.sourceID] = {
673
672
  entry: {
674
673
  type: 'source_registry_reference',
@@ -61,6 +61,15 @@ type MessageParameterTypes = {
61
61
  'experimental-dialect-not-enabled': {
62
62
  dialect: string;
63
63
  };
64
+ 'nesting-unsupported': {
65
+ dialect: string;
66
+ };
67
+ 'nested-multi-stage-unsupported': {
68
+ dialect: string;
69
+ };
70
+ 'nested-projection-limit-unsupported': {
71
+ dialect: string;
72
+ };
64
73
  'sql-native-not-allowed-in-expression': {
65
74
  rawType: string | undefined;
66
75
  };
@@ -185,6 +194,7 @@ type MessageParameterTypes = {
185
194
  'invalid-type-for-field-definition': string;
186
195
  'circular-reference-in-field-definition': string;
187
196
  'output-name-conflict': string;
197
+ 'output-field-auto-renamed': string;
188
198
  'select-of-view': string;
189
199
  'select-of-analytic': string;
190
200
  'select-of-aggregate': string;
@@ -55,6 +55,9 @@ exports.MESSAGE_FORMATTERS = {
55
55
  tag: 'pick-values-must-match',
56
56
  }),
57
57
  'experimental-dialect-not-enabled': e => `Requires compiler flag '##! experimental.dialect.${e.dialect}'`,
58
+ 'nesting-unsupported': e => `'${e.dialect}' does not support nested queries`,
59
+ 'nested-multi-stage-unsupported': e => `'${e.dialect}' does not support a multi-stage pipeline ('->') in a nested query`,
60
+ 'nested-projection-limit-unsupported': e => `'${e.dialect}' does not support 'limit:' on a nested 'select:'`,
58
61
  'pick-missing-else': "pick incomplete, missing 'else'",
59
62
  'pick-missing-value': 'pick with no value can only be used with apply',
60
63
  'pick-illegal-partial': 'pick with partial when can only be used with apply',
@@ -143,7 +143,7 @@ function compileExpr(resultSet, context, expr, state = new utils_1.GenerateState
143
143
  else {
144
144
  throw new Error(`Internal Error: Unknown aggregate function ${expr.function}`);
145
145
  }
146
- if (resultSet.root().isComplexQuery) {
146
+ if (resultSet.root().isComplexQuery && resultSet.root().emitsGroupSet) {
147
147
  let groupSet = resultSet.groupSet;
148
148
  if (state.totalGroupSet !== -1) {
149
149
  groupSet = state.totalGroupSet;
@@ -411,7 +411,7 @@ function generateAsymmetricStringAggExpression(resultSet, context, value, separa
411
411
  return context.dialect.sqlStringAggDistinct(distinctKey, valueSQL, separatorSQL);
412
412
  }
413
413
  function generateAnalyticFragment(dialect, resultStruct, context, expr, overload, state, args, partitionByFields, funcOrdering) {
414
- const isComplex = resultStruct.root().isComplexQuery;
414
+ const isComplex = resultStruct.root().isComplexQuery && resultStruct.root().emitsGroupSet;
415
415
  const partitionFields = getAnalyticPartitions(resultStruct, partitionByFields);
416
416
  const allPartitions = [
417
417
  ...(isComplex ? ['group_set'] : []),
@@ -96,6 +96,7 @@ export declare class FieldInstanceResultRoot extends FieldInstanceResult {
96
96
  joins: Map<string, JoinInstance>;
97
97
  havings: AndChain;
98
98
  isComplexQuery: boolean;
99
+ emitsGroupSet: boolean;
99
100
  queryUsesPartitioning: boolean;
100
101
  computeOnlyGroups: number[];
101
102
  elimatedComputeGroups: boolean;
@@ -222,6 +222,15 @@ class FieldInstanceResult {
222
222
  maxDepth = r.maxDepth;
223
223
  }
224
224
  }
225
+ else if (fir.firstSegment.type === 'project') {
226
+ // A projection nest is grain-preserving (one array element per
227
+ // in-scope row), so it rides on the enclosing scope's group_set
228
+ // rather than getting its own. Without this it pins to group_set 0,
229
+ // and inside a deeper nest its array-agg FILTER never lines up with
230
+ // the enclosing group_set, producing empty arrays. (A first-stage
231
+ // projection; any following stages recurse in their own scope.)
232
+ fir.groupSet = this.groupSet;
233
+ }
225
234
  }
226
235
  }
227
236
  this.childGroups = children;
@@ -401,6 +410,11 @@ class FieldInstanceResultRoot extends FieldInstanceResult {
401
410
  this.joins = new Map();
402
411
  this.havings = new utils_1.AndChain();
403
412
  this.isComplexQuery = false;
413
+ // True when the query emits a `group_set` column to demux fanned-out rows
414
+ // (the general path). The single-group-set fast path (generateSingleGroupSetSQL)
415
+ // sets this false: there is no group_set column, so aggregate and window
416
+ // expressions must not wrap themselves in `CASE WHEN group_set=N`.
417
+ this.emitsGroupSet = true;
404
418
  this.queryUsesPartitioning = false;
405
419
  this.computeOnlyGroups = [];
406
420
  this.elimatedComputeGroups = false;
@@ -9,4 +9,5 @@ export { getModelAnnotations } from './annotation_utils';
9
9
  export { constantExprToSQL } from './constant_expression_compiler';
10
10
  export { getCompiledSQL } from './sql_compiled';
11
11
  export { MalloyCompileError } from './malloy_compile_error';
12
- export { mkSourceID, mkBuildID, mkQuerySourceDef, mkSQLSourceDef, mkTableSourceDef, resolveSourceID, registerSource, hasSourceRegistryEntry, } from './source_def_utils';
12
+ export { mkSourceID, mkBuildID, mkQuerySourceDef, mkSQLSourceDef, mkTableSourceDef, resolveSourceID, resolveSourceRef, sourceNamespaceReference, registerSource, hasSourceRegistryEntry, } from './source_def_utils';
13
+ export type { NamespaceReference } from './source_def_utils';
@@ -18,7 +18,7 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
18
18
  for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
19
19
  };
20
20
  Object.defineProperty(exports, "__esModule", { value: true });
21
- exports.hasSourceRegistryEntry = exports.registerSource = exports.resolveSourceID = exports.mkTableSourceDef = exports.mkSQLSourceDef = exports.mkQuerySourceDef = exports.mkBuildID = exports.mkSourceID = exports.MalloyCompileError = exports.getCompiledSQL = exports.constantExprToSQL = exports.getModelAnnotations = exports.typeDefToString = exports.pathToKey = exports.mkModelID = exports.mkModelDef = exports.makeDigest = exports.composeSQLExpr = exports.indent = exports.getResultStructDefForView = exports.getResultStructDefForQuery = exports.QueryModel = exports.QueryQuery = exports.QueryStruct = exports.QueryField = void 0;
21
+ exports.hasSourceRegistryEntry = exports.registerSource = exports.sourceNamespaceReference = exports.resolveSourceRef = exports.resolveSourceID = exports.mkTableSourceDef = exports.mkSQLSourceDef = exports.mkQuerySourceDef = exports.mkBuildID = exports.mkSourceID = exports.MalloyCompileError = exports.getCompiledSQL = exports.constantExprToSQL = exports.getModelAnnotations = exports.typeDefToString = exports.pathToKey = exports.mkModelID = exports.mkModelDef = exports.makeDigest = exports.composeSQLExpr = exports.indent = exports.getResultStructDefForView = exports.getResultStructDefForQuery = exports.QueryModel = exports.QueryQuery = exports.QueryStruct = exports.QueryField = void 0;
22
22
  __exportStar(require("./malloy_types"), exports);
23
23
  const query_node_1 = require("./query_node");
24
24
  Object.defineProperty(exports, "QueryField", { enumerable: true, get: function () { return query_node_1.QueryField; } });
@@ -60,6 +60,8 @@ Object.defineProperty(exports, "mkQuerySourceDef", { enumerable: true, get: func
60
60
  Object.defineProperty(exports, "mkSQLSourceDef", { enumerable: true, get: function () { return source_def_utils_1.mkSQLSourceDef; } });
61
61
  Object.defineProperty(exports, "mkTableSourceDef", { enumerable: true, get: function () { return source_def_utils_1.mkTableSourceDef; } });
62
62
  Object.defineProperty(exports, "resolveSourceID", { enumerable: true, get: function () { return source_def_utils_1.resolveSourceID; } });
63
+ Object.defineProperty(exports, "resolveSourceRef", { enumerable: true, get: function () { return source_def_utils_1.resolveSourceRef; } });
64
+ Object.defineProperty(exports, "sourceNamespaceReference", { enumerable: true, get: function () { return source_def_utils_1.sourceNamespaceReference; } });
63
65
  Object.defineProperty(exports, "registerSource", { enumerable: true, get: function () { return source_def_utils_1.registerSource; } });
64
66
  Object.defineProperty(exports, "hasSourceRegistryEntry", { enumerable: true, get: function () { return source_def_utils_1.hasSourceRegistryEntry; } });
65
67
  //# sourceMappingURL=index.js.map
@@ -858,6 +858,22 @@ interface SourceDefBase extends StructDefBase, Filtered, ResultStructMetadata {
858
858
  primaryKey?: PrimaryKeyRef;
859
859
  dialect: string;
860
860
  partitionComposite?: PartitionCompositeDesc;
861
+ /**
862
+ * Identity of this source's own definition (`name@modelURL`). Set for every
863
+ * source when it is defined. The persistence machinery reads this (gated by
864
+ * `isPersistableSourceDef`).
865
+ */
866
+ sourceID?: SourceID;
867
+ /**
868
+ * Set only when this source was created as an unmodified reference to another
869
+ * source (`source: a is b`, or a plain join) — then it holds the `sourceID`
870
+ * of the *immediately* referenced source. Absent when the source defines its
871
+ * own shape (table/sql/query, or a modified/extended source). So
872
+ * `referenceID !== undefined` means "created as a reference", and the value
873
+ * resolves through `ModelDef.sourceRegistry` to the referenced source and its
874
+ * namespace name (see `sourceNamespaceReference`).
875
+ */
876
+ referenceID?: SourceID;
861
877
  }
862
878
  /** which field is the primary key in this struct */
863
879
  export type PrimaryKeyRef = string;
@@ -878,7 +894,7 @@ export interface SQLStringSegment {
878
894
  export type SQLPhraseSegment = Query | PersistableSourceDef | SQLStringSegment;
879
895
  export declare function isSegmentSQL(f: SQLPhraseSegment): f is SQLStringSegment;
880
896
  export declare function isSegmentSource(f: SQLPhraseSegment): f is PersistableSourceDef;
881
- /** Format: "name@modelUrl" - uniquely identifies a source for persistence */
897
+ /** Format: "name@modelUrl" - uniquely identifies a defined source */
882
898
  export type SourceID = string;
883
899
  /** Created with `mkGivenID`. */
884
900
  export type GivenID = string;
@@ -915,7 +931,6 @@ export interface SourceRegistryValue {
915
931
  }
916
932
  export declare function isSourceRegistryReference(entry: SourceRegistryEntry): entry is SourceRegistryReference;
917
933
  export interface PersistableSourceProperties {
918
- sourceID?: SourceID;
919
934
  extends?: SourceID;
920
935
  persistent?: boolean;
921
936
  }
@@ -943,13 +958,17 @@ export declare function isSourceDef(sd: NamedModelObject | FieldDef): sd is Sour
943
958
  /**
944
959
  * Union of all source definition types.
945
960
  *
946
- * IMPORTANT: Never use object spread to copy a SourceDef. Use the factory
947
- * methods in source_def_utils.ts to merge changes into a source def:
961
+ * When building a *persisted/derived* source in the compiler, use the factory
962
+ * methods in source_def_utils.ts rather than object spread:
948
963
  * - mkSQLSourceDef(base, ...) - create SQLSourceDef from base
949
964
  * - mkQuerySourceDef(base, ...) - create QuerySourceDef from base
950
965
  *
951
- * These factories explicitly copy only safe fields, preventing accidental
952
- * propagation of sourceID/extends which must only be set in DefineSource.
966
+ * These factories explicitly copy only safe fields, dropping the identity
967
+ * fields (sourceID/referenceID/extends/persistent) so they are not propagated
968
+ * onto a freshly built source. In the translator, by contrast, object spread is
969
+ * used deliberately to *carry* sourceID/referenceID through an unmodified
970
+ * reference; DefineSource then sets them, and DynamicSpace clears referenceID
971
+ * on the modification path.
953
972
  */
954
973
  export type SourceDef = TableSourceDef | SQLSourceDef | QuerySourceDef | QueryResultDef | FinalizeSourceDef | NestSourceDef | VirtualSourceDef | CompositeSourceDef;
955
974
  /** Sources that can be persisted (materialized to tables) */
@@ -0,0 +1,37 @@
1
+ import type { Dialect } from '../dialect/dialect';
2
+ import type { PipeSegment } from './malloy_types';
3
+ /**
4
+ * Why the compiler can't emit a segment for a dialect. The translator maps each
5
+ * reason to a located, user-facing message (these values are also parse-log
6
+ * codes); the compiler throws a `MalloyCompileError` with the reason.
7
+ */
8
+ export type NestUnsupported = 'nesting-unsupported' | 'nested-multi-stage-unsupported' | 'nested-projection-limit-unsupported';
9
+ /**
10
+ * How the compiler should emit one stage of a nest's pipeline — or, if the
11
+ * dialect can't, why not. This is THE single decision shared by the two callers
12
+ * that hold a dialect: the compiler (`generateTurtleSQL`) dispatches on `kind`
13
+ * and reads `limit`; the translator (`nest.ts`) turns `unsupported` into a
14
+ * located error. "How do I emit this" and "can I emit this" are one answer, so
15
+ * neither side can drift from the other.
16
+ *
17
+ * It is a *segment* question, not a pipeline one. The compiler compiles the
18
+ * first stage of a pipeline and recurses on the rest, so every stage is asked
19
+ * in turn as "the first stage" — pipeline length is irrelevant. `hasSuccessor`
20
+ * (is a stage materialized after this one?) is the only positional input, and
21
+ * it is what the multi-stage gate keys on.
22
+ *
23
+ * The dialect-free half of this — "is this stage an inline projection?" — is a
24
+ * plain `segment.type === 'project'`, used directly by the structural callers
25
+ * (group_set assignment, where-clause placement) that legitimately have no
26
+ * dialect; group_set numbering is not dialect-dependent.
27
+ */
28
+ export type NestStrategy = {
29
+ kind: 'reduce';
30
+ } | {
31
+ kind: 'project';
32
+ limit: number | undefined;
33
+ } | {
34
+ kind: 'unsupported';
35
+ reason: NestUnsupported;
36
+ };
37
+ export declare function nestStrategy(segment: PipeSegment, dialect: Dialect, hasSuccessor: boolean): NestStrategy;
@@ -0,0 +1,31 @@
1
+ "use strict";
2
+ /*
3
+ * Copyright Contributors to the Malloy project
4
+ * SPDX-License-Identifier: MIT
5
+ */
6
+ Object.defineProperty(exports, "__esModule", { value: true });
7
+ exports.nestStrategy = nestStrategy;
8
+ const malloy_types_1 = require("./malloy_types");
9
+ function nestStrategy(segment, dialect, hasSuccessor) {
10
+ if (!dialect.supportsNesting) {
11
+ return { kind: 'unsupported', reason: 'nesting-unsupported' };
12
+ }
13
+ // A stage with another stage after it must be materialized as a nested
14
+ // pipeline stage, which not every dialect can do inside a view.
15
+ if (hasSuccessor && !dialect.supportsPipelinesInViews) {
16
+ return { kind: 'unsupported', reason: 'nested-multi-stage-unsupported' };
17
+ }
18
+ if ((0, malloy_types_1.isProjectSegment)(segment)) {
19
+ // A projection applies `limit:` by slicing its aggregated array; some
20
+ // dialects can't slice an aggregate (see sqlAggregateTurtle).
21
+ if (segment.limit !== undefined && !dialect.supportsNestedProjectionLimit) {
22
+ return {
23
+ kind: 'unsupported',
24
+ reason: 'nested-projection-limit-unsupported',
25
+ };
26
+ }
27
+ return { kind: 'project', limit: segment.limit };
28
+ }
29
+ return { kind: 'reduce' };
30
+ }
31
+ //# sourceMappingURL=nest-capability.js.map
@@ -145,7 +145,7 @@ function findPersistentDependencies(root, modelDef, tagParseLog = []) {
145
145
  return results;
146
146
  }
147
147
  function processJoinedSource(source) {
148
- // If it has a sourceID, go through the registry
148
+ // If it has an sourceID, go through the registry
149
149
  if ((0, malloy_types_1.isPersistableSourceDef)(source) && source.sourceID) {
150
150
  return processSourceID(source.sourceID);
151
151
  }
@@ -14,11 +14,14 @@ type StageGroupMaping = {
14
14
  fromGroup: number;
15
15
  toGroup: number;
16
16
  };
17
+ interface StageOutputColumn {
18
+ sql: string;
19
+ name: string;
20
+ isDimension: boolean;
21
+ }
17
22
  type StageOutputContext = {
18
- sql: string[];
23
+ columns: StageOutputColumn[];
19
24
  lateralJoinSQLExpressions: LateralJoinExpression[];
20
- dimensionIndexes: number[];
21
- fieldIndex: number;
22
25
  groupsAggregated: StageGroupMaping[];
23
26
  outputPipelinedSQL: OutputPipelinedSQL[];
24
27
  };
@@ -88,8 +91,26 @@ export declare class QueryQuery extends QueryField {
88
91
  */
89
92
  collectArrayJoins(ji: JoinInstance): JoinInstance[];
90
93
  genereateSQLOrderBy(queryDef: QuerySegment, resultStruct: FieldInstanceResult): string;
94
+ /**
95
+ * The single-scan form, with no `group_set` fan-out: one `SELECT` over the
96
+ * scan, grouped by the dimensions. Used both for a query with no nests and --
97
+ * via canUseSingleGroupSetSQL -- for one whose only nests are single-stage
98
+ * grain-preserving projections, which become an array-agg in the same SELECT.
99
+ * That array-agg needs no `FILTER (WHERE group_set=N)` (every row is in the
100
+ * one group), and aggregate/window expressions must not gate themselves by
101
+ * group_set either, so emitsGroupSet is cleared (see FieldInstanceResultRoot).
102
+ */
91
103
  generateSimpleSQL(stageWriter: StageWriter): string;
92
- generatePipelinedStages(outputPipelinedSQL: OutputPipelinedSQL[], lastStageName: string, stageWriter: StageWriter): string;
104
+ /**
105
+ * Can this query take the single-group-set fast path? True when the query
106
+ * needs no group-set fan-out: it has nests (so it's "complex") but every one
107
+ * is a single-stage grain-preserving projection riding group_set 0, and there
108
+ * are no reduce nests, ungroupings, or totals (all of which would mint a
109
+ * second group set and push maxGroupSet above 0). Anything outside that shape
110
+ * falls back to the general demux path, which is correct for every case.
111
+ */
112
+ canUseSingleGroupSetSQL(): boolean;
113
+ generatePipelinedStages(outputPipelinedSQL: OutputPipelinedSQL[], lastStageName: string, stageWriter: StageWriter, priorStageColumns: StageOutputColumn[]): string;
93
114
  generateStage0Fields(resultSet: FieldInstanceResult, output: StageOutputContext, stageWriter: StageWriter): void;
94
115
  generateSQLWhereChildren(resultStruct: FieldInstanceResult): AndChain;
95
116
  generateSQLWhereTurtled(): string;
@@ -99,7 +120,7 @@ export declare class QueryQuery extends QueryField {
99
120
  generateSQLDepthN(depth: number, stageWriter: StageWriter, stageName: string): string;
100
121
  genereateSQLCombineTurtles(stageWriter: StageWriter, stage0Name: string): string;
101
122
  buildDialectFieldList(resultStruct: FieldInstanceResult): DialectFieldList;
102
- generateTurtleSQL(resultStruct: FieldInstanceResult, stageWriter: StageWriter, sqlFieldName: string, outputPipelinedSQL: OutputPipelinedSQL[]): string;
123
+ generateTurtleSQL(resultStruct: FieldInstanceResult, stageWriter: StageWriter, sqlFieldName: string, outputPipelinedSQL: OutputPipelinedSQL[], omitGroupSetFilter?: boolean): string;
103
124
  generateTurtlePipelineSQL(fi: FieldInstanceResult, stageWriter: StageWriter, sourceSQLExpression: string): {
104
125
  structDef: QueryResultDef;
105
126
  pipeOut: any;