@malloydata/malloy 0.0.275 → 0.0.276

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.
@@ -171,7 +171,7 @@ class QueryOperationSpace extends refined_space_1.RefinedSpace {
171
171
  var _a;
172
172
  const reference = joinPath.map(n => new field_space_1.FieldName(n));
173
173
  this.astEl.has({ reference });
174
- const lookup = this.exprSpace.lookup(reference);
174
+ const lookup = this.exprSpace.lookup(reference, 'private');
175
175
  // Should always be found...
176
176
  if (lookup.found && lookup.found instanceof struct_space_field_base_1.StructSpaceFieldBase) {
177
177
  return (0, composite_source_utils_1.joinedFieldUsage)(joinPath.slice(0, -1), (_a = lookup.found.fieldDef().onFieldUsage) !== null && _a !== void 0 ? _a : (0, composite_source_utils_1.emptyFieldUsage)());
@@ -23,29 +23,68 @@ class CompositeSource extends source_1.Source {
23
23
  return this.withParameters(parameterSpace, []);
24
24
  }
25
25
  withParameters(parameterSpace, pList) {
26
- const sourceDefs = this.sources.map(source => source.withParameters(parameterSpace, pList));
27
- const connection = sourceDefs[0].connection;
28
- const dialect = sourceDefs[0].dialect;
29
- const name = 'composite_source';
30
- const fields = [];
31
- const fieldsByName = new Map();
32
- this.sources.forEach((source, index) => {
33
- var _a;
34
- const sourceDef = sourceDefs[index];
35
- // Check that connections all match; don't bother checking dialect, since it will
36
- // match if the connection matches.
37
- if (sourceDef.connection !== connection) {
38
- source.logError('composite-source-connection-mismatch', `All sources in a composite source must share the same connection; connection \`${sourceDef.connection}\` differs from previous connection \`${connection}\``);
26
+ const sourceDefs = this.sources.map(source => {
27
+ return {
28
+ sourceDef: source.withParameters(parameterSpace, pList),
29
+ logTo: source,
30
+ };
31
+ });
32
+ return composeSources(sourceDefs, this);
33
+ }
34
+ }
35
+ exports.CompositeSource = CompositeSource;
36
+ function composeSources(sources, compositeCodeSource) {
37
+ const connection = sources[0].sourceDef.connection;
38
+ const dialect = sources[0].sourceDef.dialect;
39
+ const name = 'composite_source';
40
+ const fieldsByName = new Map();
41
+ const joinsToCompose = new Map();
42
+ sources.forEach(source => {
43
+ var _a;
44
+ const sourceDef = source.sourceDef;
45
+ // Check that connections all match; don't bother checking dialect, since it will
46
+ // match if the connection matches.
47
+ if (sourceDef.connection !== connection) {
48
+ source.logTo.logError('composite-source-connection-mismatch', `All sources in a composite source must share the same connection; connection \`${sourceDef.connection}\` differs from previous connection \`${connection}\``);
49
+ }
50
+ for (const field of sourceDef.fields) {
51
+ const fieldName = (_a = field.as) !== null && _a !== void 0 ? _a : field.name;
52
+ if (field.accessModifier === 'private') {
53
+ continue;
39
54
  }
40
- for (const field of sourceDef.fields) {
41
- if (!(0, malloy_types_1.isAtomic)(field)) {
42
- source.logWarning('composite-source-atomic-fields-only', `Only atomic fields are supported in composite sources; field \`${field.name}\` is not atomic and will be ignored`);
43
- continue;
55
+ /**
56
+ * compose(flights -> {nest: by_carrier})
57
+ *
58
+ *
59
+ *
60
+ *
61
+ */
62
+ // does not handle compose(flights -> {nest: by_carrier})
63
+ if ((0, malloy_types_1.isJoined)(field) && (0, malloy_types_1.isSourceDef)(field)) {
64
+ const existingJoins = joinsToCompose.get(fieldName);
65
+ if (existingJoins) {
66
+ existingJoins.sources.push(field);
67
+ if (field.join !== existingJoins.join) {
68
+ source.logTo.logError('composite-source-connection-mismatch', `Composited joins must have the same join type; \`${field.join}\` differs from previous type \`${existingJoins.join}\``);
69
+ }
70
+ if (field.matrixOperation !== existingJoins.matrixOperation) {
71
+ source.logTo.logError('composite-source-connection-mismatch', `Composited joins must have the same matrix operation; \`${field.matrixOperation}\` differs from previous operation \`${existingJoins.matrixOperation}\``);
72
+ }
73
+ if (field.accessModifier === 'internal') {
74
+ existingJoins.accessModifier = 'internal';
75
+ }
44
76
  }
45
- if (field.accessModifier === 'private') {
46
- continue;
77
+ else {
78
+ joinsToCompose.set(fieldName, {
79
+ sources: [field],
80
+ join: field.join,
81
+ matrixOperation: field.matrixOperation,
82
+ accessModifier: field.accessModifier,
83
+ });
47
84
  }
48
- const fieldName = (_a = field.as) !== null && _a !== void 0 ? _a : field.name;
85
+ // TODO ensure that there isn't also a normal field with this name...
86
+ }
87
+ else if ((0, malloy_types_1.isAtomic)(field)) {
49
88
  const existing = fieldsByName.get(fieldName);
50
89
  if (existing === undefined) {
51
90
  const compositeField = {
@@ -53,32 +92,72 @@ class CompositeSource extends source_1.Source {
53
92
  name: fieldName,
54
93
  as: undefined,
55
94
  e: { node: 'compositeField' },
56
- fieldUsage: [{ path: [fieldName], at: this.codeLocation }],
57
- code: this.code,
58
- location: this.codeLocation,
95
+ fieldUsage: [
96
+ { path: [fieldName], at: compositeCodeSource.codeLocation },
97
+ ],
98
+ code: compositeCodeSource.code,
99
+ location: compositeCodeSource.codeLocation,
59
100
  // A composite field's grouping may differ from slice to slice
60
101
  requiresGroupBy: undefined,
61
102
  };
62
103
  fieldsByName.set(fieldName, compositeField);
63
- fields.push(compositeField);
64
104
  }
65
- else if (field.accessModifier === 'internal') {
66
- existing.accessModifier = 'internal';
105
+ else {
106
+ if (field.accessModifier === 'internal') {
107
+ existing.accessModifier = 'internal';
108
+ }
109
+ if (!(malloy_types_1.TD.eq(field, existing) ||
110
+ // Handle the case where both fields don't have a raw type...
111
+ // TODO ask MToy about this
112
+ (field.type === 'sql native' &&
113
+ existing.type === 'sql native' &&
114
+ field.rawType === existing.rawType))) {
115
+ source.logTo.logError('composite-field-type-mismatch', `field \`${field.name}\` must have the same type in all composite inputs: ${prettyType(field)} does not match ${prettyType(existing)}`);
116
+ }
67
117
  }
68
118
  }
69
- });
70
- return {
71
- type: 'composite',
72
- // TODO Use sourceRefs rather than sourceDefs when possible to avoid potential
73
- // explosion of source defs...
74
- sources: sourceDefs,
75
- connection,
76
- fields,
77
- dialect,
78
- name,
79
- parameters: sourceDefs[0].parameters,
119
+ else {
120
+ source.logTo.logWarning('composite-source-atomic-fields-only', `Only atomic fields are supported in composite sources; field \`${field.name}\` is not atomic and will be ignored`);
121
+ }
122
+ // TODO actually typecheck the existing field against the new field...
123
+ }
124
+ });
125
+ for (const [joinName, sourcesInJoin] of joinsToCompose.entries()) {
126
+ if (fieldsByName.has(joinName)) {
127
+ compositeCodeSource.logError('composite-field-type-mismatch', `field \`${joinName}\` must be a join in all sources or none`);
128
+ }
129
+ const composedSource = composeSources(sourcesInJoin.sources.map(s => ({
130
+ sourceDef: s,
131
+ logTo: compositeCodeSource,
132
+ })), compositeCodeSource);
133
+ const compositeJoin = {
134
+ ...composedSource,
135
+ // We don't need to store the sources, since this is just a placeholder for typechecking
136
+ // the composite resolver will use the joins from the original input sources regardless.
137
+ sources: [],
138
+ join: sourcesInJoin.join,
139
+ name: joinName,
140
+ matrixOperation: sourcesInJoin.matrixOperation,
141
+ onExpression: undefined,
142
+ onCompositeFieldUsage: [],
143
+ accessModifier: sourcesInJoin.accessModifier,
80
144
  };
145
+ fieldsByName.set(joinName, compositeJoin);
81
146
  }
147
+ return {
148
+ type: 'composite',
149
+ // TODO Use sourceRefs rather than sourceDefs when possible to avoid potential
150
+ // explosion of source defs...
151
+ sources: sources.map(s => s.sourceDef),
152
+ connection,
153
+ fields: [...fieldsByName.values()],
154
+ dialect,
155
+ name,
156
+ // TODO actually compose the parameters?
157
+ parameters: sources[0].sourceDef.parameters,
158
+ };
159
+ }
160
+ function prettyType(a) {
161
+ return `\`${a.type}\``;
82
162
  }
83
- exports.CompositeSource = CompositeSource;
84
163
  //# sourceMappingURL=composite-source.js.map
@@ -157,6 +157,7 @@ type MessageParameterTypes = {
157
157
  'field-list-edit-not-found': string;
158
158
  'unexpected-element-type': string;
159
159
  'field-not-found': string;
160
+ 'composite-field-type-mismatch': string;
160
161
  'invalid-composite-source-input': string;
161
162
  'invalid-composite-field-usage': {
162
163
  newUsage: FieldUsage[];
@@ -35,6 +35,11 @@ type CompositeError = {
35
35
  };
36
36
  };
37
37
  type CompositeIssue = {
38
+ type: 'join-failed';
39
+ failures: CompositeFailure[];
40
+ path: string[];
41
+ firstUsage: FieldUsage;
42
+ } | {
38
43
  type: 'missing-field';
39
44
  field: FieldUsage;
40
45
  } | {
@@ -100,9 +100,21 @@ sources) {
100
100
  filterList: [...((_b = source.filterList) !== null && _b !== void 0 ? _b : []), ...((_c = base.filterList) !== null && _c !== void 0 ? _c : [])],
101
101
  };
102
102
  const joinError = processJoins(path, base, rootFields, nests, expandedCategorized);
103
- // Fourth point where we abort: if a join failed we just completely give up
104
- if (joinError.error !== undefined) {
105
- return { error: joinError.error };
103
+ // Fourth point where we abort: if a join failed
104
+ if (joinError.errors.length > 0) {
105
+ for (const error of joinError.errors) {
106
+ if (error.error.code !== 'no_suitable_composite_source_input') {
107
+ return { error: error.error };
108
+ }
109
+ fail({
110
+ type: 'join-failed',
111
+ failures: error.error.data.failures,
112
+ path: error.error.data.path,
113
+ firstUsage: error.firstUsage,
114
+ });
115
+ }
116
+ abort();
117
+ continue overSources;
106
118
  }
107
119
  joinsProcessed = true;
108
120
  if (nests !== undefined) {
@@ -146,8 +158,8 @@ sources) {
146
158
  };
147
159
  }
148
160
  const joinResult = processJoins(path, base, rootFields, nests, categorizeFieldUsage(expanded.result));
149
- if (joinResult.error !== undefined) {
150
- return { error: joinResult.error };
161
+ if (joinResult.errors.length > 0) {
162
+ return { error: joinResult.errors[0].error };
151
163
  }
152
164
  anyComposites || (anyComposites = joinResult.anyComposites);
153
165
  }
@@ -290,6 +302,7 @@ function processJoins(path, base, rootFields, nests, categorizedFieldUsage) {
290
302
  var _a, _b;
291
303
  let anyComposites = false;
292
304
  const fieldsByName = {};
305
+ const errors = [];
293
306
  for (const field of base.fields) {
294
307
  fieldsByName[(_a = field.as) !== null && _a !== void 0 ? _a : field.name] = field;
295
308
  }
@@ -297,20 +310,24 @@ function processJoins(path, base, rootFields, nests, categorizedFieldUsage) {
297
310
  const newPath = [...path, joinName];
298
311
  const join = fieldsByName[joinName];
299
312
  if (join === undefined) {
300
- return {
313
+ errors.push({
301
314
  error: {
302
315
  code: 'composite_source_not_defined',
303
316
  data: { path: newPath },
304
317
  },
305
- };
318
+ firstUsage: joinedUsage[0],
319
+ });
320
+ continue;
306
321
  }
307
322
  if (!(0, malloy_types_1.isJoined)(join)) {
308
- return {
323
+ errors.push({
309
324
  error: {
310
325
  code: 'composite_source_not_a_join',
311
326
  data: { path: newPath },
312
327
  },
313
- };
328
+ firstUsage: joinedUsage[0],
329
+ });
330
+ continue;
314
331
  }
315
332
  else if (!(0, malloy_types_1.isSourceDef)(join)) {
316
333
  // Non-source join, like an array, skip it (no need to resolve)
@@ -318,19 +335,25 @@ function processJoins(path, base, rootFields, nests, categorizedFieldUsage) {
318
335
  }
319
336
  const resolved = _resolveCompositeSources(newPath, join, genRootFields(rootFields, path, base.fields), nests, joinedUsage);
320
337
  if ('error' in resolved) {
321
- return resolved;
338
+ errors.push({
339
+ error: resolved.error,
340
+ firstUsage: joinedUsage[0],
341
+ });
342
+ continue;
322
343
  }
323
344
  if (!resolved.anyComposites) {
324
345
  continue;
325
346
  }
326
347
  anyComposites = true;
327
348
  if (!(0, malloy_types_1.isJoinable)(resolved.success)) {
328
- return {
349
+ errors.push({
329
350
  error: {
330
351
  code: 'composite_source_is_not_joinable',
331
352
  data: { path: newPath },
332
353
  },
333
- };
354
+ firstUsage: joinedUsage[0],
355
+ });
356
+ continue;
334
357
  }
335
358
  fieldsByName[joinName] = {
336
359
  ...resolved.success,
@@ -340,7 +363,7 @@ function processJoins(path, base, rootFields, nests, categorizedFieldUsage) {
340
363
  };
341
364
  base.fields = Object.values(fieldsByName);
342
365
  }
343
- return { anyComposites };
366
+ return { anyComposites, errors };
344
367
  }
345
368
  function resolveCompositeSources(source, segment, fieldUsage) {
346
369
  var _a;
@@ -449,7 +472,8 @@ function fieldUsageJoinPaths(fieldUsage) {
449
472
  exports.fieldUsageJoinPaths = fieldUsageJoinPaths;
450
473
  function isCompositeField(fieldDef) {
451
474
  var _a;
452
- return 'e' in fieldDef && ((_a = fieldDef.e) === null || _a === void 0 ? void 0 : _a.node) === 'compositeField';
475
+ return (('e' in fieldDef && ((_a = fieldDef.e) === null || _a === void 0 ? void 0 : _a.node) === 'compositeField') ||
476
+ ((0, malloy_types_1.isJoined)(fieldDef) && fieldDef.type === 'composite'));
453
477
  }
454
478
  function getNonCompositeFields(source) {
455
479
  return source.fields.filter(f => !isCompositeField(f));
@@ -773,10 +797,14 @@ function logCompositeError(error, logTo) {
773
797
  const fieldRef = `\`${issue.field.path.join('.')}\``;
774
798
  logTo.logError('could-not-resolve-composite-source', `Could not resolve composite source: missing field ${fieldRef} in ${source}${requiredFields}`, { at: issue.field.at });
775
799
  }
776
- else {
800
+ else if (issue.type === 'missing-required-group-by') {
777
801
  const fieldRef = `\`${issue.requiredGroupBy.path.join('.')}\``;
778
802
  logTo.logError('could-not-resolve-composite-source', `Could not resolve composite source: missing group by ${fieldRef} as required in ${source}${requiredFields}`, { at: issue.requiredGroupBy.at });
779
803
  }
804
+ else {
805
+ const joinRef = `\`${issue.path.join('.')}\``;
806
+ logTo.logError('could-not-resolve-composite-source', `Could not resolve composite source: join ${joinRef} could not be resolved in ${source}${requiredFields}`, { at: issue.firstUsage.at });
807
+ }
780
808
  }
781
809
  }
782
810
  }
package/dist/version.d.ts CHANGED
@@ -1 +1 @@
1
- export declare const MALLOY_VERSION = "0.0.275";
1
+ export declare const MALLOY_VERSION = "0.0.276";
package/dist/version.js CHANGED
@@ -2,5 +2,5 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.MALLOY_VERSION = void 0;
4
4
  // generated with 'generate-version-file' script; do not edit manually
5
- exports.MALLOY_VERSION = '0.0.275';
5
+ exports.MALLOY_VERSION = '0.0.276';
6
6
  //# sourceMappingURL=version.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@malloydata/malloy",
3
- "version": "0.0.275",
3
+ "version": "0.0.276",
4
4
  "license": "MIT",
5
5
  "exports": {
6
6
  ".": "./dist/index.js",
@@ -41,9 +41,9 @@
41
41
  "generate-version-file": "VERSION=$(npm pkg get version --workspaces=false | tr -d \\\")\necho \"// generated with 'generate-version-file' script; do not edit manually\\nexport const MALLOY_VERSION = '$VERSION';\" > src/version.ts"
42
42
  },
43
43
  "dependencies": {
44
- "@malloydata/malloy-filter": "0.0.275",
45
- "@malloydata/malloy-interfaces": "0.0.275",
46
- "@malloydata/malloy-tag": "0.0.275",
44
+ "@malloydata/malloy-filter": "0.0.276",
45
+ "@malloydata/malloy-interfaces": "0.0.276",
46
+ "@malloydata/malloy-tag": "0.0.276",
47
47
  "antlr4ts": "^0.5.0-alpha.4",
48
48
  "assert": "^2.0.0",
49
49
  "jaro-winkler": "^0.2.8",