@malloydata/malloy 0.0.428 → 0.0.430

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 (42) hide show
  1. package/dist/api/foundation/build_targets.d.ts +50 -0
  2. package/dist/api/foundation/build_targets.js +221 -0
  3. package/dist/api/foundation/core.d.ts +54 -10
  4. package/dist/api/foundation/core.js +80 -17
  5. package/dist/api/foundation/index.d.ts +1 -1
  6. package/dist/api/foundation/runtime.d.ts +18 -1
  7. package/dist/api/foundation/runtime.js +35 -0
  8. package/dist/api/foundation/types.d.ts +93 -6
  9. package/dist/index.d.ts +1 -1
  10. package/dist/lang/ast/field-space/dynamic-space.js +20 -3
  11. package/dist/lang/ast/query-elements/query-arrow.d.ts +1 -1
  12. package/dist/lang/ast/query-elements/query-arrow.js +10 -4
  13. package/dist/lang/ast/query-elements/query-base.d.ts +1 -1
  14. package/dist/lang/ast/query-elements/query-base.js +1 -6
  15. package/dist/lang/ast/query-elements/query-raw.d.ts +1 -1
  16. package/dist/lang/ast/query-elements/query-raw.js +4 -2
  17. package/dist/lang/ast/query-elements/query-reference.d.ts +1 -1
  18. package/dist/lang/ast/query-elements/query-reference.js +9 -3
  19. package/dist/lang/ast/query-elements/query-refine.d.ts +1 -1
  20. package/dist/lang/ast/query-elements/query-refine.js +10 -4
  21. package/dist/lang/ast/source-elements/query-source.js +1 -1
  22. package/dist/lang/ast/source-elements/sql-source.js +2 -2
  23. package/dist/lang/ast/source-query-elements/source-query-element.d.ts +27 -0
  24. package/dist/lang/ast/source-query-elements/source-query-element.js +33 -0
  25. package/dist/lang/ast/source-query-elements/sq-arrow.d.ts +2 -0
  26. package/dist/lang/ast/source-query-elements/sq-arrow.js +12 -6
  27. package/dist/lang/ast/source-query-elements/sq-refine.d.ts +2 -0
  28. package/dist/lang/ast/source-query-elements/sq-refine.js +12 -6
  29. package/dist/lang/ast/source-query-elements/sq-source.d.ts +1 -0
  30. package/dist/lang/ast/source-query-elements/sq-source.js +6 -3
  31. package/dist/lang/ast/sql-elements/sql-string.d.ts +1 -1
  32. package/dist/lang/ast/sql-elements/sql-string.js +25 -5
  33. package/dist/lang/ast/statements/import-statement.js +7 -10
  34. package/dist/lang/ast/types/query-element.d.ts +15 -1
  35. package/dist/lang/parse-log.d.ts +2 -0
  36. package/dist/lang/test/test-translator.d.ts +17 -0
  37. package/dist/lang/test/test-translator.js +46 -2
  38. package/dist/model/persist_utils.d.ts +61 -10
  39. package/dist/model/persist_utils.js +172 -63
  40. package/dist/version.d.ts +1 -1
  41. package/dist/version.js +1 -1
  42. package/package.json +4 -4
@@ -1,5 +1,7 @@
1
1
  import type { EventStream } from '../../runtime_types';
2
- import type { BuildManifest, GivenValue, VirtualMap } from '../../model';
2
+ import type { BuildID, BuildManifest, GivenValue, VirtualMap } from '../../model';
3
+ import type { LogMessage } from '../../lang';
4
+ import type { PersistSource } from './core';
3
5
  /**
4
6
  * An empty BuildManifest with no entries and strict mode off.
5
7
  * Use this to explicitly suppress manifest substitution in a query:
@@ -57,6 +59,12 @@ export interface CompileQueryOptions {
57
59
  /**
58
60
  * A node in the build graph (recursive DAG structure).
59
61
  * Uses sourceID (sourceName@modelURL) for identity.
62
+ *
63
+ * A node reached twice is the *same object* both times, so the graph shares
64
+ * nodes rather than copying them. Anything walking it needs a seen-set on node
65
+ * identity, or a chain of diamonds is exponential.
66
+ *
67
+ * @deprecated Produced only by {@link BuildPlan}. Use `BuildTarget`.
60
68
  */
61
69
  export interface BuildNode {
62
70
  /** Source identity: "sourceName@modelURL" */
@@ -67,15 +75,94 @@ export interface BuildNode {
67
75
  /**
68
76
  * An ordered build plan for sources on a single connection.
69
77
  *
70
- * The leveled array structure determines build order: sources in the same
71
- * level can be built in parallel, levels must be built sequentially.
78
+ * `nodes` is typed for a leveled schedule that was never produced it always
79
+ * holds exactly one entry, the roots, with the real ordering in each node's
80
+ * `dependsOn`.
72
81
  *
73
- * Builders can group graphs by `connectionName` to parallelize across
74
- * different database connections.
82
+ * @deprecated Use `BuildTargets`, which reports tables rather than sources.
75
83
  */
76
84
  export interface BuildGraph {
77
85
  /** The connection all sources in this graph run on */
78
86
  connectionName: string;
79
- /** The leveled build nodes */
87
+ /** Always one entry: the root nodes */
80
88
  nodes: BuildNode[][];
81
89
  }
90
+ /**
91
+ * One artifact: a table to build, and every source that maps onto it.
92
+ *
93
+ * A `BuildNode` is a source; a `BuildTarget` is a table. The two are not the
94
+ * same count. `#@ persist` is an annotation, so extending or renaming a
95
+ * persisted source inherits it — while `extend` never changes the source's
96
+ * SQL — and several sources routinely name one table. The manifest is keyed by
97
+ * `BuildID`, so those sources share one entry no matter how many plan nodes
98
+ * they occupy. `Runtime.getBuildTargets()` does that merge once, in the core,
99
+ * instead of leaving each builder to discover it by hashing.
100
+ */
101
+ export interface BuildTarget {
102
+ /** Manifest key for this artifact: a hash of the connection digest and `sql` */
103
+ buildId: BuildID;
104
+ /** The connection this artifact is built on */
105
+ connectionName: string;
106
+ /**
107
+ * The SQL the BuildID is computed from: fully inlined, no manifest
108
+ * substitution, so it is the same string whatever else has been built.
109
+ *
110
+ * It is not the SQL to execute. That one substitutes the tables built so far
111
+ * — `source.getSQL({buildManifest, connectionDigests})` — so it can only be
112
+ * computed as the build walks, and only by the builder holding the manifest.
113
+ */
114
+ sql: string;
115
+ /** Targets that must exist before this one can be built */
116
+ dependsOn: BuildTarget[];
117
+ /** Every persist source in the model that maps onto this artifact */
118
+ sources: PersistSource[];
119
+ }
120
+ /**
121
+ * Everything one connection has to build.
122
+ *
123
+ * `targets` is in dependency order — everything a target depends on appears
124
+ * before it — so the simplest correct builder is a loop:
125
+ *
126
+ * ```typescript
127
+ * for (const target of targets) await build(target);
128
+ * ```
129
+ *
130
+ * A builder that wants concurrency uses `target.dependsOn` and starts each one
131
+ * the moment its own dependencies finish:
132
+ *
133
+ * ```typescript
134
+ * const done = new Map<BuildTarget, Promise<void>>();
135
+ * for (const target of targets) {
136
+ * done.set(target, (async () => {
137
+ * await Promise.all(target.dependsOn.map(d => done.get(d)));
138
+ * await build(target);
139
+ * })());
140
+ * }
141
+ * await Promise.all(done.values());
142
+ * ```
143
+ *
144
+ * That is the maximum available: nothing waits for anything it does not read.
145
+ * Batching the targets into rounds instead — everything at depth 0, then
146
+ * everything at depth 1 — is easier to write and strictly worse, because a
147
+ * target then waits on unrelated work that happens to share its depth.
148
+ */
149
+ export interface ConnectionBuild {
150
+ /** The connection everything here is built on */
151
+ connectionName: string;
152
+ /** Every table to build, dependencies before dependents */
153
+ targets: BuildTarget[];
154
+ }
155
+ /**
156
+ * The build schedule for a model.
157
+ *
158
+ * Connections come first because they are the largest cut of parallelism: a
159
+ * query cannot cross a connection, so no dependency ever does either, and each
160
+ * entry here is a wholly independent build that needs no coordination with any
161
+ * other.
162
+ */
163
+ export interface BuildTargets {
164
+ /** One per connection, mutually independent */
165
+ connections: ConnectionBuild[];
166
+ /** Errors and warnings from parsing `#@` annotations on persistable sources */
167
+ tagParseLog: LogMessage[];
168
+ }
package/dist/index.d.ts CHANGED
@@ -25,7 +25,7 @@ export { routeOf, payloadOf, annotationsForRoute, tagFromAnnotations, } from './
25
25
  /** @deprecated — use the `.annotations` view on a Foundation entity
26
26
  * (`entity.annotations.parseAsTag(route)` / `.texts(route)`). */
27
27
  export { annotationToTag, annotationToTaglines, } from './api/foundation/annotation';
28
- export type { BuildGraph, BuildNode, BuildPlan } from './api/foundation';
28
+ export type { BuildGraph, BuildNode, BuildPlan, BuildTarget, BuildTargets, ConnectionBuild, } from './api/foundation';
29
29
  export { PersistSource, EMPTY_BUILD_MANIFEST } from './api/foundation';
30
30
  export { Reference } from './api/foundation';
31
31
  export type { ReferenceKind } from './api/foundation';
@@ -114,10 +114,17 @@ class DynamicSpace extends static_space_1.StaticSpace {
114
114
  }
115
115
  }
116
116
  this.sourceDef = { ...this.fromSource, fields: [] };
117
- // This is a freshly built (modified) source: it presents a new exported
118
- // shape, so it is no longer a reference to the source it was built from.
119
- // DefineSource resets referenceID to this source's own sourceID.
117
+ // A freshly built (modified) source presents a new exported shape, so it
118
+ // is neither a reference to what it was built from nor that source's own
119
+ // definition. Both identities go; `DefineSource` stamps fresh ones if
120
+ // this ends up with a name, and an inline `extend` never does.
121
+ //
122
+ // Dropping `sourceID` also means the persistence walk descends *through*
123
+ // an unnamed modification rather than resolving it by id — which is how
124
+ // an inline `extend` used as a query's structRef stopped hiding the
125
+ // sources it added.
120
126
  delete this.sourceDef.referenceID;
127
+ delete this.sourceDef.sourceID;
121
128
  this.sourceDef.parameters = parameters;
122
129
  const fieldIndices = new Map();
123
130
  // Need to process the entities in specific order
@@ -141,6 +148,16 @@ class DynamicSpace extends static_space_1.StaticSpace {
141
148
  if (field instanceof join_space_field_1.JoinSpaceField) {
142
149
  const joinStruct = field.join.getStructDef(parameterSpace);
143
150
  if (!error_factory_1.ErrorFactory.didCreate(joinStruct)) {
151
+ // A query runs on one connection, so everything reachable from
152
+ // the source it runs on must live on that connection. Record and
153
+ // array joins are part of the row, and so have no connection.
154
+ // A base whose schema could not be fetched has the error
155
+ // connection, which is not worth complaining about twice.
156
+ if (model.isSourceDef(joinStruct) &&
157
+ !error_factory_1.ErrorFactory.didCreate(this.fromSource) &&
158
+ joinStruct.connection !== this.connectionName()) {
159
+ field.join.sourceExpr.logError('join-connection-mismatch', `Cannot join '${name}', which is on connection '${joinStruct.connection}', into a source on connection '${this.connectionName()}'`);
160
+ }
144
161
  fieldIndices.set(name, this.sourceDef.fields.length);
145
162
  this.sourceDef.fields.push(joinStruct);
146
163
  field.join.fixupJoinOn(this, joinStruct);
@@ -13,5 +13,5 @@ export declare class QueryArrow extends QueryBase implements QueryElement {
13
13
  readonly view: View;
14
14
  elementType: string;
15
15
  constructor(source: Source | QueryElement, view: View);
16
- queryComp(isRefOk: boolean): QueryComp;
16
+ queryComp(isRefOk: boolean, isPartialOk: boolean): QueryComp;
17
17
  }
@@ -10,6 +10,7 @@ const source_1 = require("../source-elements/source");
10
10
  const static_space_1 = require("../field-space/static-space");
11
11
  const query_base_1 = require("./query-base");
12
12
  const composite_source_utils_1 = require("../../composite-source-utils");
13
+ const query_utils_1 = require("../query-utils");
13
14
  /**
14
15
  * A query operation that adds segments to a LHS source or query.
15
16
  *
@@ -22,7 +23,7 @@ class QueryArrow extends query_base_1.QueryBase {
22
23
  this.view = view;
23
24
  this.elementType = 'arrow';
24
25
  }
25
- queryComp(isRefOk) {
26
+ queryComp(isRefOk, isPartialOk) {
26
27
  var _a;
27
28
  let inputStruct;
28
29
  let queryBase;
@@ -46,7 +47,9 @@ class QueryArrow extends query_base_1.QueryBase {
46
47
  }
47
48
  else {
48
49
  // We are adding a second stage to the given "source" query; we get the query and add a segment
49
- const lhsQuery = this.source.queryComp(isRefOk);
50
+ // The LHS stages are complete stages, nothing will refine them, so they
51
+ // are never allowed to be partial no matter what this caller accepts.
52
+ const lhsQuery = this.source.queryComp(isRefOk, false);
50
53
  queryBase = lhsQuery.query;
51
54
  inputStruct = lhsQuery.outputStruct;
52
55
  fieldSpace = new static_space_1.StaticSourceSpace(lhsQuery.outputStruct, 'public');
@@ -77,12 +80,15 @@ class QueryArrow extends query_base_1.QueryBase {
77
80
  : // Otherwise just use the `inputStruct`
78
81
  inputStruct, rhsPipeline),
79
82
  ];
83
+ const finalPipeline = isPartialOk
84
+ ? pipelineWithExpandedFieldUsage
85
+ : (0, query_utils_1.detectAndRemovePartialStages)(pipelineWithExpandedFieldUsage, this);
80
86
  return {
81
87
  query: {
82
88
  ...query,
83
89
  compositeResolvedSourceDef,
84
- pipeline: pipelineWithExpandedFieldUsage,
85
- givenUsage: (0, composite_source_utils_1.computeQueryGivenUsage)(pipelineWithExpandedFieldUsage),
90
+ pipeline: finalPipeline,
91
+ givenUsage: (0, composite_source_utils_1.computeQueryGivenUsage)(finalPipeline),
86
92
  },
87
93
  outputStruct,
88
94
  inputStruct,
@@ -2,7 +2,7 @@ import { type PipeSegment, type Query, type SourceDef } from '../../../model/mal
2
2
  import { MalloyElement } from '../types/malloy-element';
3
3
  import type { QueryComp } from '../types/query-comp';
4
4
  export declare abstract class QueryBase extends MalloyElement {
5
- abstract queryComp(isRefOk: boolean): QueryComp;
5
+ abstract queryComp(isRefOk: boolean, isPartialOk: boolean): QueryComp;
6
6
  protected expandRefUsage(inputSource: SourceDef, pipeline: PipeSegment[]): PipeSegment[];
7
7
  protected resolveCompositeSource(inputSource: SourceDef, pipeline: PipeSegment[]): SourceDef | undefined;
8
8
  query(isRefOk?: boolean): Query;
@@ -8,7 +8,6 @@ exports.QueryBase = void 0;
8
8
  const composite_source_utils_1 = require("../../composite-source-utils");
9
9
  const malloy_types_1 = require("../../../model/malloy_types");
10
10
  const error_factory_1 = require("../error-factory");
11
- const query_utils_1 = require("../query-utils");
12
11
  const malloy_element_1 = require("../types/malloy-element");
13
12
  class QueryBase extends malloy_element_1.MalloyElement {
14
13
  expandRefUsage(inputSource, pipeline) {
@@ -40,11 +39,7 @@ class QueryBase extends malloy_element_1.MalloyElement {
40
39
  return undefined;
41
40
  }
42
41
  query(isRefOk = true) {
43
- const { query } = this.queryComp(isRefOk);
44
- return {
45
- ...query,
46
- pipeline: (0, query_utils_1.detectAndRemovePartialStages)(query.pipeline, this),
47
- };
42
+ return this.queryComp(isRefOk, false).query;
48
43
  }
49
44
  }
50
45
  exports.QueryBase = QueryBase;
@@ -15,6 +15,6 @@ export declare class QueryRaw extends MalloyElement implements QueryElement {
15
15
  readonly source: Source;
16
16
  elementType: string;
17
17
  constructor(source: Source);
18
- queryComp(isRefOk: boolean): QueryComp;
18
+ queryComp(isRefOk: boolean, _isPartialOk: boolean): QueryComp;
19
19
  query(isRefOk?: boolean): Query;
20
20
  }
@@ -21,7 +21,9 @@ class QueryRaw extends malloy_element_1.MalloyElement {
21
21
  this.source = source;
22
22
  this.elementType = 'query-raw';
23
23
  }
24
- queryComp(isRefOk) {
24
+ // A raw segment has no query class to decide, so it is never partial and
25
+ // `isPartialOk` has nothing to say here.
26
+ queryComp(isRefOk, _isPartialOk) {
25
27
  const invoked = isRefOk
26
28
  ? this.source.structRef(undefined)
27
29
  : { structRef: this.source.getSourceDef(undefined) };
@@ -40,7 +42,7 @@ class QueryRaw extends malloy_element_1.MalloyElement {
40
42
  };
41
43
  }
42
44
  query(isRefOk = true) {
43
- return this.queryComp(isRefOk).query;
45
+ return this.queryComp(isRefOk, false).query;
44
46
  }
45
47
  }
46
48
  exports.QueryRaw = QueryRaw;
@@ -12,6 +12,6 @@ export declare class QueryReference extends MalloyElement implements QueryElemen
12
12
  readonly name: ModelEntryReference;
13
13
  elementType: string;
14
14
  constructor(name: ModelEntryReference);
15
- queryComp(isRefOk: boolean): QueryComp;
15
+ queryComp(isRefOk: boolean, isPartialOk: boolean): QueryComp;
16
16
  query(isRefOk?: boolean): Query;
17
17
  }
@@ -8,6 +8,7 @@ exports.QueryReference = void 0;
8
8
  const error_factory_1 = require("../error-factory");
9
9
  const malloy_element_1 = require("../types/malloy-element");
10
10
  const query_head_struct_1 = require("./query-head-struct");
11
+ const query_utils_1 = require("../query-utils");
11
12
  const malloy_types_1 = require("../../../model/malloy_types");
12
13
  /**
13
14
  * A query operation that is just a reference to an existing query.
@@ -20,7 +21,7 @@ class QueryReference extends malloy_element_1.MalloyElement {
20
21
  this.name = name;
21
22
  this.elementType = 'query-reference';
22
23
  }
23
- queryComp(isRefOk) {
24
+ queryComp(isRefOk, isPartialOk) {
24
25
  const headEntry = this.modelEntry(this.name);
25
26
  const query = headEntry === null || headEntry === void 0 ? void 0 : headEntry.entry;
26
27
  const oops = function () {
@@ -45,7 +46,12 @@ class QueryReference extends malloy_element_1.MalloyElement {
45
46
  ? query
46
47
  : { ...query, structRef: inputStruct };
47
48
  return {
48
- query: unRefedQuery,
49
+ query: isPartialOk
50
+ ? unRefedQuery
51
+ : {
52
+ ...unRefedQuery,
53
+ pipeline: (0, query_utils_1.detectAndRemovePartialStages)(unRefedQuery.pipeline, this),
54
+ },
49
55
  outputStruct,
50
56
  inputStruct,
51
57
  };
@@ -54,7 +60,7 @@ class QueryReference extends malloy_element_1.MalloyElement {
54
60
  return oops();
55
61
  }
56
62
  query(isRefOk = true) {
57
- return this.queryComp(isRefOk).query;
63
+ return this.queryComp(isRefOk, false).query;
58
64
  }
59
65
  }
60
66
  exports.QueryReference = QueryReference;
@@ -12,5 +12,5 @@ export declare class QueryRefine extends QueryBase implements QueryElement {
12
12
  readonly refinement: View;
13
13
  elementType: string;
14
14
  constructor(base: QueryElement, refinement: View);
15
- queryComp(isRefOk: boolean): QueryComp;
15
+ queryComp(isRefOk: boolean, isPartialOk: boolean): QueryComp;
16
16
  }
@@ -8,6 +8,7 @@ exports.QueryRefine = void 0;
8
8
  const static_space_1 = require("../field-space/static-space");
9
9
  const query_base_1 = require("./query-base");
10
10
  const composite_source_utils_1 = require("../../composite-source-utils");
11
+ const query_utils_1 = require("../query-utils");
11
12
  /**
12
13
  * A query operation that consists of an exisitng query with refinements.
13
14
  *
@@ -20,8 +21,10 @@ class QueryRefine extends query_base_1.QueryBase {
20
21
  this.refinement = refinement;
21
22
  this.elementType = 'query-refine';
22
23
  }
23
- queryComp(isRefOk) {
24
- const q = this.base.queryComp(isRefOk);
24
+ queryComp(isRefOk, isPartialOk) {
25
+ // The refinement is what decides the query class, so the base is the one
26
+ // place a partial segment is expected rather than an error.
27
+ const q = this.base.queryComp(isRefOk, true);
25
28
  const inputFS = new static_space_1.StaticSourceSpace(q.inputStruct, 'public');
26
29
  const pipeline = this.refinement.refine(inputFS, q.query.pipeline, undefined);
27
30
  const query = {
@@ -30,12 +33,15 @@ class QueryRefine extends query_base_1.QueryBase {
30
33
  };
31
34
  const compositeResolvedSourceDef = this.resolveCompositeSource(q.inputStruct, pipeline);
32
35
  const pipelineWithExpandedFieldUsage = this.expandRefUsage(compositeResolvedSourceDef !== null && compositeResolvedSourceDef !== void 0 ? compositeResolvedSourceDef : q.inputStruct, pipeline);
36
+ const finalPipeline = isPartialOk
37
+ ? pipelineWithExpandedFieldUsage
38
+ : (0, query_utils_1.detectAndRemovePartialStages)(pipelineWithExpandedFieldUsage, this);
33
39
  return {
34
40
  query: {
35
41
  ...query,
36
42
  compositeResolvedSourceDef,
37
- pipeline: pipelineWithExpandedFieldUsage,
38
- givenUsage: (0, composite_source_utils_1.computeQueryGivenUsage)(pipelineWithExpandedFieldUsage),
43
+ pipeline: finalPipeline,
44
+ givenUsage: (0, composite_source_utils_1.computeQueryGivenUsage)(finalPipeline),
39
45
  },
40
46
  // TODO bleh
41
47
  outputStruct: pipeline[pipeline.length - 1].outputStruct,
@@ -18,7 +18,7 @@ class QuerySource extends source_1.Source {
18
18
  return this.withParameters(parameterSpace, undefined);
19
19
  }
20
20
  withParameters(parameterSpace, pList) {
21
- const comp = this.query.queryComp(false);
21
+ const comp = this.query.queryComp(false, false);
22
22
  const queryStruct = (0, source_def_utils_1.mkQuerySourceDef)(comp.outputStruct, comp.query, `QuerySource-${(0, uuid_1.v4)()}`);
23
23
  return {
24
24
  ...queryStruct,
@@ -22,7 +22,7 @@ class SQLSource extends source_1.Source {
22
22
  const partialModel = this.select.containsQueries
23
23
  ? doc.modelDef()
24
24
  : undefined;
25
- const [valid, phrases] = this.select.sqlPhrases();
25
+ const [valid, phrases] = this.select.sqlPhrases(this.connectionName.refString);
26
26
  if (valid) {
27
27
  return (0, sql_block_1.getSourceRequest)(phrases, this.connectionName.refString, partialModel);
28
28
  }
@@ -123,7 +123,7 @@ class SQLSource extends source_1.Source {
123
123
  location: this.location,
124
124
  };
125
125
  // Use factory to create SQLSourceDef without propagating sourceID/extends
126
- const [_valid, phrases] = this.select.sqlPhrases();
126
+ const [_valid, phrases] = this.select.sqlPhrases(this.connectionName.refString);
127
127
  const selectSegments = this.select.containsQueries ? phrases : undefined;
128
128
  const locStruct = (0, source_def_utils_1.mkSQLSourceDef)(baseStruct, lookup.value.selectStr, selectSegments);
129
129
  return locStruct;
@@ -9,10 +9,37 @@ import type { LogMessageOptions, MessageCode, MessageParameterType } from '../..
9
9
  * a query.
10
10
  */
11
11
  export declare abstract class SourceQueryElement extends MalloyElement {
12
+ /** Set once this element has reported why it could not produce a value. */
12
13
  errored: boolean;
13
14
  getSource(): Source | undefined;
14
15
  getQuery(): QueryElement | undefined;
15
16
  isSource(): boolean;
17
+ /**
18
+ * Report the one message which explains why this expression could not be
19
+ * turned into a source or a query.
20
+ *
21
+ * Every element in a source/query expression says something when it fails,
22
+ * so an outer element's generic complaint ("could not get source for
23
+ * query") would stack on top of the specific cause already reported
24
+ * beneath it. The message is therefore logged only when nothing in this
25
+ * subtree has spoken yet, and `errored` is set either way, which is what
26
+ * silences the elements above.
27
+ *
28
+ * This is not protection against logging the same message twice —
29
+ * `MalloyElement.log` already drops a repeat of one message at one
30
+ * location. It is one message per failed expression.
31
+ *
32
+ * The case it does not serve is two unrelated complaints about the same
33
+ * element: a second `sqLog` is dropped. Use `sqClaimError` for those.
34
+ */
16
35
  sqLog<T extends MessageCode>(code: T, parameters: MessageParameterType<T>, options?: LogMessageOptions): T;
36
+ /**
37
+ * Take the one error report this expression is allowed, for an element with
38
+ * more than one complaint to make. False means something below has already
39
+ * spoken. True means the caller owns the report and should `logError` each
40
+ * of its complaints; nothing above will speak after that.
41
+ */
42
+ sqClaimError(): boolean;
43
+ /** True until this element, or anything below it, has reported a failure. */
17
44
  isErrorFree(): boolean;
18
45
  }
@@ -15,6 +15,7 @@ const malloy_element_1 = require("../types/malloy-element");
15
15
  class SourceQueryElement extends malloy_element_1.MalloyElement {
16
16
  constructor() {
17
17
  super(...arguments);
18
+ /** Set once this element has reported why it could not produce a value. */
18
19
  this.errored = false;
19
20
  }
20
21
  getSource() {
@@ -26,6 +27,24 @@ class SourceQueryElement extends malloy_element_1.MalloyElement {
26
27
  isSource() {
27
28
  return false;
28
29
  }
30
+ /**
31
+ * Report the one message which explains why this expression could not be
32
+ * turned into a source or a query.
33
+ *
34
+ * Every element in a source/query expression says something when it fails,
35
+ * so an outer element's generic complaint ("could not get source for
36
+ * query") would stack on top of the specific cause already reported
37
+ * beneath it. The message is therefore logged only when nothing in this
38
+ * subtree has spoken yet, and `errored` is set either way, which is what
39
+ * silences the elements above.
40
+ *
41
+ * This is not protection against logging the same message twice —
42
+ * `MalloyElement.log` already drops a repeat of one message at one
43
+ * location. It is one message per failed expression.
44
+ *
45
+ * The case it does not serve is two unrelated complaints about the same
46
+ * element: a second `sqLog` is dropped. Use `sqClaimError` for those.
47
+ */
29
48
  sqLog(code, parameters, options) {
30
49
  if (this.isErrorFree()) {
31
50
  this.logError(code, parameters, options);
@@ -33,6 +52,20 @@ class SourceQueryElement extends malloy_element_1.MalloyElement {
33
52
  this.errored = true;
34
53
  return code;
35
54
  }
55
+ /**
56
+ * Take the one error report this expression is allowed, for an element with
57
+ * more than one complaint to make. False means something below has already
58
+ * spoken. True means the caller owns the report and should `logError` each
59
+ * of its complaints; nothing above will speak after that.
60
+ */
61
+ sqClaimError() {
62
+ if (!this.isErrorFree()) {
63
+ return false;
64
+ }
65
+ this.errored = true;
66
+ return true;
67
+ }
68
+ /** True until this element, or anything below it, has reported a failure. */
36
69
  isErrorFree() {
37
70
  if (this.errored) {
38
71
  return false;
@@ -16,6 +16,8 @@ export declare class SQArrow extends SourceQueryElement {
16
16
  readonly applyTo: SourceQueryElement;
17
17
  readonly operation: View;
18
18
  elementType: string;
19
+ asQuery?: QueryElement;
20
+ asSource?: Source;
19
21
  constructor(applyTo: SourceQueryElement, operation: View);
20
22
  getQuery(): QueryElement | undefined;
21
23
  getSource(): Source | undefined;
@@ -26,6 +26,9 @@ class SQArrow extends source_query_element_1.SourceQueryElement {
26
26
  this.elementType = 'sq-arrow';
27
27
  }
28
28
  getQuery() {
29
+ if (this.asQuery) {
30
+ return this.asQuery;
31
+ }
29
32
  const lhs = this.applyTo.isSource()
30
33
  ? this.applyTo.getSource()
31
34
  : this.applyTo.getQuery();
@@ -33,19 +36,22 @@ class SQArrow extends source_query_element_1.SourceQueryElement {
33
36
  this.sqLog('failed-to-compute-arrow-source', 'Could not get LHS of arrow operation');
34
37
  return;
35
38
  }
36
- const arr = new query_arrow_1.QueryArrow(lhs, this.operation);
37
- this.has({ query: arr });
38
- return arr;
39
+ this.asQuery = new query_arrow_1.QueryArrow(lhs, this.operation);
40
+ this.has({ query: this.asQuery });
41
+ return this.asQuery;
39
42
  }
40
43
  getSource() {
44
+ if (this.asSource) {
45
+ return this.asSource;
46
+ }
41
47
  const query = this.getQuery();
42
48
  if (!query) {
43
49
  this.sqLog('failed-to-compute-source-from-query', "Couldn't comprehend query well enough to make a source");
44
50
  return;
45
51
  }
46
- const asSource = new query_source_1.QuerySource(query);
47
- this.has({ asSource });
48
- return asSource;
52
+ this.asSource = new query_source_1.QuerySource(query);
53
+ this.has({ asSource: this.asSource });
54
+ return this.asSource;
49
55
  }
50
56
  }
51
57
  exports.SQArrow = SQArrow;
@@ -12,6 +12,8 @@ export declare class SQRefine extends SourceQueryElement {
12
12
  readonly toRefine: SourceQueryElement;
13
13
  readonly refine: View;
14
14
  elementType: string;
15
+ asQuery?: QueryRefine;
16
+ asSource?: QuerySource;
15
17
  constructor(toRefine: SourceQueryElement, refine: View);
16
18
  getQuery(): QueryRefine | undefined;
17
19
  getSource(): QuerySource | undefined;
@@ -23,6 +23,9 @@ class SQRefine extends source_query_element_1.SourceQueryElement {
23
23
  this.elementType = 'sq-refine';
24
24
  }
25
25
  getQuery() {
26
+ if (this.asQuery) {
27
+ return this.asQuery;
28
+ }
26
29
  if (this.toRefine.isSource()) {
27
30
  if (this.toRefine instanceof sq_reference_1.SQReference) {
28
31
  this.sqLog('illegal-refinement-of-source', `Cannot add view refinements to '${this.toRefine.ref.refString}' because it is a source`);
@@ -34,17 +37,20 @@ class SQRefine extends source_query_element_1.SourceQueryElement {
34
37
  }
35
38
  const refinedQuery = this.toRefine.getQuery();
36
39
  if (refinedQuery) {
37
- const resultQuery = new query_refine_1.QueryRefine(refinedQuery, this.refine);
38
- this.has({ query: resultQuery });
39
- return resultQuery;
40
+ this.asQuery = new query_refine_1.QueryRefine(refinedQuery, this.refine);
41
+ this.has({ query: this.asQuery });
42
+ return this.asQuery;
40
43
  }
41
44
  }
42
45
  getSource() {
46
+ if (this.asSource) {
47
+ return this.asSource;
48
+ }
43
49
  const query = this.getQuery();
44
50
  if (query) {
45
- const queryAsSource = new query_source_1.QuerySource(query);
46
- this.has({ queryAsSource });
47
- return queryAsSource;
51
+ this.asSource = new query_source_1.QuerySource(query);
52
+ this.has({ queryAsSource: this.asSource });
53
+ return this.asSource;
48
54
  }
49
55
  }
50
56
  }
@@ -10,6 +10,7 @@ import { QueryRaw } from '../query-elements/query-raw';
10
10
  export declare class SQSource extends SourceQueryElement {
11
11
  readonly theSource: Source;
12
12
  elementType: string;
13
+ asQuery?: QueryRaw;
13
14
  constructor(theSource: Source);
14
15
  isSource(): boolean;
15
16
  getSource(): Source;
@@ -27,10 +27,13 @@ class SQSource extends source_query_element_1.SourceQueryElement {
27
27
  return this.theSource;
28
28
  }
29
29
  getQuery() {
30
+ if (this.asQuery) {
31
+ return this.asQuery;
32
+ }
30
33
  if (this.theSource instanceof sql_source_1.SQLSource) {
31
- const rawQuery = new query_raw_1.QueryRaw(this.theSource);
32
- this.has({ rawQuery });
33
- return rawQuery;
34
+ this.asQuery = new query_raw_1.QueryRaw(this.theSource);
35
+ this.has({ rawQuery: this.asQuery });
36
+ return this.asQuery;
34
37
  }
35
38
  else {
36
39
  this.sqLog('invalid-source-as-query', 'This source cannot be used as a query');
@@ -8,6 +8,6 @@ export declare class SQLString extends MalloyElement {
8
8
  containsQueries: boolean;
9
9
  complete(): void;
10
10
  push(el: string | MalloyElement): void;
11
- sqlPhrases(): [boolean, SQLPhraseSegment[]];
11
+ sqlPhrases(forConnection: string): [boolean, SQLPhraseSegment[]];
12
12
  }
13
13
  export {};