@sap/cds-compiler 2.5.0 → 2.10.4

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 (92) hide show
  1. package/CHANGELOG.md +191 -9
  2. package/bin/cdsc.js +2 -2
  3. package/doc/CHANGELOG_BETA.md +33 -3
  4. package/lib/api/main.js +29 -101
  5. package/lib/api/options.js +15 -11
  6. package/lib/api/validate.js +12 -8
  7. package/lib/backends.js +0 -81
  8. package/lib/base/keywords.js +32 -2
  9. package/lib/base/message-registry.js +63 -9
  10. package/lib/base/messages.js +63 -21
  11. package/lib/base/model.js +2 -3
  12. package/lib/checks/defaultValues.js +27 -2
  13. package/lib/checks/elements.js +1 -6
  14. package/lib/checks/foreignKeys.js +0 -6
  15. package/lib/checks/managedWithoutKeys.js +17 -0
  16. package/lib/checks/nonexpandableStructured.js +38 -0
  17. package/lib/checks/onConditions.js +9 -45
  18. package/lib/checks/queryNoDbArtifacts.js +25 -7
  19. package/lib/checks/selectItems.js +25 -2
  20. package/lib/checks/types.js +26 -2
  21. package/lib/checks/unknownMagic.js +38 -0
  22. package/lib/checks/utils.js +61 -0
  23. package/lib/checks/validator.js +60 -7
  24. package/lib/compiler/assert-consistency.js +16 -7
  25. package/lib/compiler/builtins.js +2 -0
  26. package/lib/compiler/checks.js +6 -4
  27. package/lib/compiler/definer.js +99 -42
  28. package/lib/compiler/index.js +73 -27
  29. package/lib/compiler/resolver.js +288 -157
  30. package/lib/compiler/shared.js +31 -11
  31. package/lib/edm/annotations/genericTranslation.js +182 -186
  32. package/lib/edm/csn2edm.js +103 -108
  33. package/lib/edm/edm.js +18 -21
  34. package/lib/edm/edmPreprocessor.js +361 -114
  35. package/lib/edm/edmUtils.js +103 -33
  36. package/lib/gen/Dictionary.json +22 -0
  37. package/lib/gen/language.checksum +1 -1
  38. package/lib/gen/language.interp +12 -1
  39. package/lib/gen/language.tokens +57 -53
  40. package/lib/gen/languageLexer.interp +10 -1
  41. package/lib/gen/languageLexer.js +770 -744
  42. package/lib/gen/languageLexer.tokens +49 -46
  43. package/lib/gen/languageParser.js +4713 -4279
  44. package/lib/json/from-csn.js +103 -45
  45. package/lib/json/to-csn.js +296 -117
  46. package/lib/language/antlrParser.js +4 -3
  47. package/lib/language/errorStrategy.js +1 -0
  48. package/lib/language/genericAntlrParser.js +21 -12
  49. package/lib/language/language.g4 +99 -31
  50. package/lib/main.d.ts +81 -3
  51. package/lib/main.js +30 -7
  52. package/lib/model/api.js +78 -0
  53. package/lib/model/csnRefs.js +329 -142
  54. package/lib/model/csnUtils.js +235 -58
  55. package/lib/model/enrichCsn.js +18 -1
  56. package/lib/model/revealInternalProperties.js +2 -1
  57. package/lib/modelCompare/compare.js +37 -20
  58. package/lib/optionProcessor.js +9 -3
  59. package/lib/render/.eslintrc.json +4 -1
  60. package/lib/render/DuplicateChecker.js +8 -5
  61. package/lib/render/toCdl.js +112 -33
  62. package/lib/render/toHdbcds.js +134 -64
  63. package/lib/render/toSql.js +91 -38
  64. package/lib/render/utils/common.js +8 -13
  65. package/lib/render/utils/sql.js +3 -3
  66. package/lib/sql-identifier.js +6 -1
  67. package/lib/transform/db/assertUnique.js +5 -6
  68. package/lib/transform/db/constraints.js +29 -13
  69. package/lib/transform/db/draft.js +8 -6
  70. package/lib/transform/db/expansion.js +582 -0
  71. package/lib/transform/db/flattening.js +325 -0
  72. package/lib/transform/db/groupByOrderBy.js +2 -2
  73. package/lib/transform/db/transformExists.js +284 -63
  74. package/lib/transform/forHanaNew.js +98 -381
  75. package/lib/transform/forOdataNew.js +21 -22
  76. package/lib/transform/localized.js +37 -10
  77. package/lib/transform/odata/attachPath.js +19 -4
  78. package/lib/transform/odata/generateForeignKeyElements.js +11 -10
  79. package/lib/transform/odata/referenceFlattener.js +60 -39
  80. package/lib/transform/odata/sortByAssociationDependency.js +2 -2
  81. package/lib/transform/odata/structuralPath.js +72 -0
  82. package/lib/transform/odata/structureFlattener.js +19 -18
  83. package/lib/transform/odata/typesExposure.js +22 -12
  84. package/lib/transform/transformUtilsNew.js +134 -78
  85. package/lib/transform/translateAssocsToJoins.js +17 -14
  86. package/lib/transform/universalCsnEnricher.js +67 -0
  87. package/lib/utils/file.js +0 -11
  88. package/lib/utils/moduleResolve.js +6 -8
  89. package/package.json +1 -1
  90. package/lib/json/walker.js +0 -26
  91. package/lib/transform/sqlite +0 -0
  92. package/lib/utils/string.js +0 -17
@@ -0,0 +1,582 @@
1
+ 'use strict';
2
+
3
+ const {
4
+ hasAnnotationValue, getUtils,
5
+ applyTransformations,
6
+ setDependencies,
7
+ walkCsnPath,
8
+ } = require('../../model/csnUtils');
9
+ const { csnRefs, implicitAs } = require('../../model/csnRefs');
10
+ const { setProp, isBetaEnabled } = require('../../base/model');
11
+
12
+ /**
13
+ * For keys, columns, groupBy and orderBy, expand structured things.
14
+ * Replace them with their flattened leaves, keeping the overall order intact.
15
+ *
16
+ * @param {CSN.Model} csn
17
+ * @param {CSN.Options} options
18
+ * @param {string} pathDelimiter
19
+ * @param {object} messageFunctions
20
+ * @param {Function} messageFunctions.error
21
+ * @param {Function} messageFunctions.info
22
+ * @param {Function} messageFunctions.throwWithError
23
+ */
24
+ function expandStructureReferences(csn, options, pathDelimiter, { error, info, throwWithError }) {
25
+ const {
26
+ isStructured, get$combined, getFinalBaseType, getServiceName,
27
+ } = getUtils(csn);
28
+ let { effectiveType, inspectRef } = csnRefs(csn);
29
+
30
+ if (isBetaEnabled(options, 'nestedProjections'))
31
+ rewriteExpandInline();
32
+
33
+
34
+ applyTransformations(csn, {
35
+ keys: (parent, name, keys, path) => {
36
+ parent.keys = expand(keys, path.concat('keys'), true);
37
+ },
38
+ columns: (parent, name, columns, path) => {
39
+ const artifact = csn.definitions[path[1]];
40
+ if (!hasAnnotationValue(artifact, '@cds.persistence.table')) {
41
+ const root = get$combined({ SELECT: parent });
42
+ parent.columns = replaceStar(root, columns, parent.excluding);
43
+ parent.columns = expand(parent.columns, path.concat('columns'), true);
44
+ }
45
+ },
46
+ groupBy: (parent, name, groupBy, path) => {
47
+ parent.groupBy = expand(groupBy, path.concat('groupBy'));
48
+ },
49
+ orderBy: (parent, name, orderBy, path) => {
50
+ parent.orderBy = expand(orderBy, path.concat('orderBy'));
51
+ },
52
+ });
53
+
54
+ /**
55
+ * Turn .expand/.inline into normal refs. @cds.persistence.skip .expand with to-many (and all transitive views).
56
+ * For such skipped things, error for usage of assoc pointing to them and and ignore publishing of assoc pointing to them.
57
+ */
58
+ function rewriteExpandInline() {
59
+ const { cleanup, _dependents } = setDependencies(csn);
60
+
61
+ const entity = findAnEntity();
62
+ const toDummyfy = [];
63
+
64
+ applyTransformations(csn, {
65
+ columns: (parent, name, columns, path) => {
66
+ const artifact = csn.definitions[path[1]];
67
+ // get$combined expects a SET/SELECT - so we wrap the parent
68
+ // (which is the thing inside SET/SELECT)
69
+ // We can directly use SELECT here, as only projections and SELECT can have .columns
70
+ const root = get$combined({ SELECT: parent });
71
+ if (!hasAnnotationValue(artifact, '@cds.persistence.table')) {
72
+ const rewritten = rewrite(root, parent.columns, parent.excluding);
73
+ parent.columns = rewritten.columns;
74
+ if (rewritten.toMany.length > 0) {
75
+ markAsToDummyfy(artifact, path[1]);
76
+ if (getServiceName(path[1]) === null)
77
+ error( null, [ 'definitions', path[1] ], { name: path[1] }, 'Unexpected .expand with to-many association in entity $(NAME), which is outside any service');
78
+ }
79
+ }
80
+ },
81
+ });
82
+
83
+ dummyfy();
84
+
85
+ cleanup.forEach(fn => fn());
86
+
87
+ ({ effectiveType, inspectRef } = csnRefs(csn));
88
+
89
+
90
+ const publishing = [];
91
+
92
+ applyTransformations(csn, {
93
+ target: (parent, name, target, path) => {
94
+ if (toDummyfy.indexOf(target) !== -1) {
95
+ publishing.push({
96
+ parent, name, target, path: [ ...path ],
97
+ });
98
+ }
99
+ },
100
+ from: check,
101
+ columns: check,
102
+ where: check,
103
+ groupBy: check,
104
+ orderBy: check,
105
+ having: check,
106
+ limit: check,
107
+ });
108
+
109
+
110
+ /**
111
+ * Check for usage of associations to skipped.
112
+ * While we're at it, kill publishing of such assocs in columns.
113
+ *
114
+ * @param {object} parent
115
+ * @param {string} name
116
+ * @param {Array} parts
117
+ * @param {CSN.Path} path
118
+ */
119
+ function check(parent, name, parts, path) {
120
+ const inColumns = name === 'columns';
121
+ const kill = [];
122
+ for (let i = 0; i < parts.length; i++) {
123
+ const obj = parts[i];
124
+ if (!(obj && obj.ref) || obj.$scope === 'alias')
125
+ continue;
126
+
127
+ const links = obj._links || inspectRef(path.concat([ name, i ])).links;
128
+
129
+ if (!links)
130
+ continue;
131
+
132
+ // Don't check the last element - to allow association publishing in columns
133
+ for (let j = 0; j < (inColumns ? links.length - 1 : links.length); j++) {
134
+ const link = links[j];
135
+ if (!link)
136
+ continue;
137
+
138
+ const { art } = link;
139
+ if (!art)
140
+ continue;
141
+
142
+ const pathStep = obj.ref[j].id ? obj.ref[j].id : obj.ref[j];
143
+ const target = art.target ? art.target : pathStep;
144
+ if (toDummyfy.indexOf(target) !== -1) {
145
+ error( null, obj.$path, {
146
+ id: pathStep, elemref: obj, name,
147
+ }, 'Unexpected “@cds.persistence.skip” annotation on Association target $(NAME) of $(ID) in path $(ELEMREF) was skipped because of .expand in conjunction with to-many');
148
+ }
149
+ }
150
+
151
+ if (inColumns) {
152
+ const { art } = links[links.length - 1];
153
+
154
+ if (art) {
155
+ const pathStep = obj.ref[obj.ref.length - 1].id ? obj.ref[obj.ref.length - 1].id : obj.ref[obj.ref.length - 1];
156
+ const target = art.target ? art.target : pathStep;
157
+ if (toDummyfy.indexOf(target) !== -1)
158
+ kill.push(i);
159
+ }
160
+ }
161
+ }
162
+
163
+ for (let i = kill.length - 1; i >= 0; i--)
164
+ parent[name].splice(kill[i]);
165
+ }
166
+
167
+ // We would be broken if we continue with assoc usage to now skipped
168
+ throwWithError();
169
+
170
+
171
+ for (const {
172
+ parent, target, path,
173
+ } of publishing) {
174
+ const last = parent.$path[parent.$path.length - 1];
175
+ const grandparent = walkCsnPath(csn, parent.$path.slice(0, -1));
176
+
177
+ if (typeof last === 'number')
178
+ grandparent.splice(last);
179
+ else
180
+ delete grandparent[last];
181
+
182
+ info(null, path, { name: last, target }, 'Ignoring association $(NAME) with target $(TARGET), because it was skipped because of .expand in conjunction with to-many');
183
+ }
184
+
185
+ /**
186
+ * Mark the given artifact and all (transitively) dependent artifacts as `toDummify`.
187
+ * This means that they will be replaced with simple dummy views in @dummify
188
+ *
189
+ * @param {CSN.Artifact} artifact
190
+ * @param {string} name
191
+ */
192
+ function markAsToDummyfy(artifact, name) {
193
+ const stack = [ [ artifact, name ] ];
194
+ while (stack.length > 0) {
195
+ const [ a, n ] = stack.pop();
196
+ if (a[_dependents]) {
197
+ Object.entries(a[_dependents]).forEach(([ dependentName, dependent ]) => {
198
+ stack.push([ dependent, dependentName ]);
199
+ });
200
+ }
201
+ toDummyfy.push(n);
202
+ }
203
+ }
204
+
205
+ /**
206
+ * Replace the artifacts in `toDummify` with simple dummy views as produced by createDummyView.
207
+ */
208
+ function dummyfy() {
209
+ for (const artifactName of [ ...new Set(toDummyfy) ])
210
+ csn.definitions[artifactName] = createDummyView(entity);
211
+ }
212
+
213
+
214
+ /**
215
+ * Get the next base for resolving a *.
216
+ * Keep the current base unless we are now navigating into a structure or association.
217
+ *
218
+ * @param {CSN.Column} parent
219
+ * @param {CSN.Artifact} base The current base
220
+ * @returns {CSN.Artifact}
221
+ */
222
+ function nextBase(parent, base) {
223
+ if (parent.ref) {
224
+ const finalBaseType = getFinalBaseType(parent._art.type);
225
+ const art = parent._art;
226
+
227
+ if (finalBaseType === 'cds.Association' || finalBaseType === 'cds.Composition')
228
+ return csn.definitions[art.target].elements;
229
+
230
+ return art.elements || finalBaseType.elements;
231
+ }
232
+
233
+ return base;
234
+ }
235
+
236
+ /**
237
+ * Rewrite expand and inline to "normal" refs
238
+ *
239
+ * @param {CSN.Artifact} root All elements visible fromt he query source ($combined)
240
+ * @param {CSN.Column[]} columns
241
+ * @param {string[]} excluding
242
+ * @returns {{columns: Array, toManys: Array}} Object with rewritten columns (.expand/.inline) and with any .expand + to-many
243
+ */
244
+ function rewrite(root, columns, excluding) {
245
+ const allToMany = [];
246
+ const newThing = [];
247
+ // Replace stars - needs to happen here since the .expand/.inline first path step affects the root *
248
+ columns = replaceStar(root, columns, excluding);
249
+ for (let i = 0; i < columns.length; i++) {
250
+ const col = columns[i];
251
+ if (col.expand) {
252
+ // TODO: Can col.ref be empty without an as? Assumption is it cannot - if it has, it's an error, we throw, compiler checks.
253
+ const { expanded, toManys } = expandInline(root, col, col.ref || [], [ dbName(col) ]);
254
+
255
+ allToMany.push(...toManys);
256
+ newThing.push(...expanded);
257
+ }
258
+ else if (col.inline) {
259
+ const { expanded, toManys } = expandInline(root, col, col.ref || [], []);
260
+
261
+ allToMany.push(...toManys);
262
+ newThing.push(...expanded);
263
+ }
264
+ else {
265
+ newThing.push(col);
266
+ }
267
+ }
268
+
269
+ return { columns: newThing, toMany: allToMany };
270
+ }
271
+
272
+ /**
273
+ * Check wether the given object is a to-many association
274
+ *
275
+ * @param {CSN.Element} obj
276
+ * @returns {boolean}
277
+ */
278
+ function isToMany(obj) {
279
+ if (!obj._art)
280
+ return false;
281
+ const eType = effectiveType(obj._art);
282
+ return (eType.type === 'cds.Association' || eType.type === 'cds.Composition') && eType.cardinality && eType.cardinality.max !== 1;
283
+ }
284
+
285
+ /**
286
+ * Rewrite the expand/inline. For expand, keep along the alias - for inline, only leaf-alias has effect.
287
+ * Expand * into the corresponding leaves - correctly handling .exlcluding and shadowing.
288
+ *
289
+ * Iterative, to not run into stack overflow.
290
+ *
291
+ * @param {CSN.Artifact} root All elements visible fromt he query source ($combined)
292
+ * @param {CSN.Column} col Column to expand
293
+ * @param {Array} ref Ref so far
294
+ * @param {Array} alias Any start-alias
295
+ * @returns {{expanded: Array, toManys: Array}} Object with expanded .expand/.inline and with any .expand + to-many
296
+ */
297
+ function expandInline(root, col, ref, alias) {
298
+ const toManys = [];
299
+ const expanded = [];
300
+ const stack = [ [ root, col, ref, alias ] ];
301
+
302
+ while (stack.length > 0) {
303
+ const [ base, current, currentRef, currentAlias ] = stack.pop();
304
+ if (isToMany(current) && current.expand) {
305
+ toManys.push({ art: current, ref: currentRef, as: currentAlias.join(pathDelimiter) });
306
+ }
307
+ else if (current.expand) {
308
+ current.expand = replaceStar(nextBase(current, base), current.expand, current.excluding);
309
+ for (let i = current.expand.length - 1; i >= 0; i--) {
310
+ const sub = current.expand[i];
311
+ stack.push([ nextBase(current, base), sub, sub.ref ? currentRef.concat(sub.ref) : currentRef, !sub.inline ? currentAlias.concat(dbName(sub)) : currentAlias ]);
312
+ }
313
+ }
314
+ else if (current.inline) {
315
+ current.inline = replaceStar(nextBase(current, base), current.inline, current.excluding);
316
+ for (let i = current.inline.length - 1; i >= 0; i--) {
317
+ const sub = current.inline[i];
318
+ stack.push([ nextBase(current, base), sub, sub.ref ? currentRef.concat(sub.ref) : currentRef, !sub.inline ? currentAlias.concat(dbName(sub)) : currentAlias ]);
319
+ }
320
+ }
321
+ else if (current.xpr || current.args) {
322
+ // We need to re-write refs in the .xpr/.args so they stay resolvable - we need to prepend the currentRef
323
+ rewriteXprArgs(current, currentRef);
324
+ expanded.push(Object.assign({}, current, { as: currentAlias.join(pathDelimiter) } ));
325
+ }
326
+ else if (current.val !== undefined || current.func !== undefined) {
327
+ expanded.push(Object.assign(current, { as: currentAlias.join(pathDelimiter) }));
328
+ }
329
+ else {
330
+ expanded.push({ ref: currentRef, as: currentAlias.join(pathDelimiter) });
331
+ }
332
+ }
333
+
334
+ return { expanded, toManys };
335
+ }
336
+
337
+ /**
338
+ * Rewrite refs in the .xpr/.args to stay resolvable
339
+ *
340
+ * @param {object} parent Thing that has an .xpr/.args
341
+ * @param {string[]} ref Ref so far
342
+ */
343
+ function rewriteXprArgs(parent, ref) {
344
+ const stack = [ [ parent, ref ] ];
345
+ while (stack.length > 0) {
346
+ const [ current, currentRef ] = stack.pop();
347
+ if (current.xpr) {
348
+ for (let i = 0; i < current.xpr.length; i++) {
349
+ const part = current.xpr[i];
350
+ if (part.ref) {
351
+ part.ref = currentRef.concat(part.ref);
352
+ // part.as = currentAlias.concat(part.as || part.ref[ref.length - 1]).join(pathDelimiter);
353
+ current.xpr[i] = part;
354
+ stack.push([ part, part.ref ]);
355
+ }
356
+ else {
357
+ stack.push([ part, currentRef ]);
358
+ }
359
+ }
360
+ }
361
+ if (current.args) {
362
+ for (let i = 0; i < current.args.length; i++) {
363
+ const part = current.args[i];
364
+ if (part.ref) {
365
+ part.ref = currentRef.concat(part.ref);
366
+ // part.as = currentAlias.concat(part.as || part.ref[ref.length - 1]).join(pathDelimiter);
367
+ current.args[i] = part;
368
+ stack.push([ part, part.ref ]);
369
+ }
370
+ else {
371
+ stack.push([ part, currentRef ]);
372
+ }
373
+ }
374
+ }
375
+ }
376
+ }
377
+
378
+ /**
379
+ * Find any entity from the model so we can use it as the query source for our dummies.
380
+ *
381
+ * @returns {string|null} Name of any entity
382
+ */
383
+ function findAnEntity() {
384
+ for (const [ name, artifact ] of Object.entries(csn.definitions)) {
385
+ if (artifact.kind === 'entity' && !artifact.query)
386
+ return name;
387
+ }
388
+ return null;
389
+ }
390
+
391
+ /**
392
+ * Create a simple dummy view marked with @cds.persistence.skip
393
+ *
394
+ * @param {string} source
395
+ * @returns {CSN.Artifact}
396
+ */
397
+ function createDummyView(source) {
398
+ const elements = Object.create(null);
399
+ elements.one = {
400
+ '@Core.Computed': true,
401
+ type: 'cds.Integer',
402
+ };
403
+ const artifact = {
404
+ '@cds.persistence.skip': true,
405
+ kind: 'entity',
406
+ query: {
407
+ SELECT: {
408
+ from: {
409
+ ref: [
410
+ source,
411
+ ],
412
+ },
413
+ columns: [
414
+ {
415
+ val: 1,
416
+ as: 'one',
417
+ cast: {
418
+ type: 'cds.Integer',
419
+ },
420
+ },
421
+ ],
422
+ },
423
+ },
424
+ elements,
425
+ };
426
+
427
+ setProp(artifact, '$wasToMany', true);
428
+
429
+ return artifact;
430
+ }
431
+ }
432
+
433
+
434
+ /**
435
+ * Process thing and expand all structured refs inside
436
+ *
437
+ * @param {Array} thing
438
+ * @param {CSN.Path} path
439
+ * @param {boolean} [withAlias=false] Wether to "expand" the (implicit) alias aswell.
440
+ * @returns {Array} New array - with all structured things expanded
441
+ */
442
+ function expand(thing, path, withAlias = false) {
443
+ const newThing = [];
444
+ for (let i = 0; i < thing.length; i++) {
445
+ const col = thing[i];
446
+ if (col.ref && col.$scope !== '$magic') {
447
+ const _art = col._art || inspectRef(path.concat(i)).art;
448
+ if (_art && isStructured(_art))
449
+ newThing.push(...expandRef(_art, col.ref, col.as, col.key || false, withAlias));
450
+
451
+ else
452
+ newThing.push(col);
453
+ }
454
+ else if (col.ref && col.$scope === '$magic' && col.ref[0] === '$user' && !col.as) {
455
+ col.as = implicitAs(col.ref);
456
+ newThing.push(col);
457
+ }
458
+ else {
459
+ newThing.push(col);
460
+ }
461
+ }
462
+
463
+ return newThing;
464
+ }
465
+
466
+ /**
467
+ * Expand the ref and - if requested - expand the alias with it.
468
+ *
469
+ * Iterative, to not run into stack overflow.
470
+ *
471
+ * @param {CSN.Element} art
472
+ * @param {Array} ref
473
+ * @param {Array} alias
474
+ * @param {boolean} isKey True if the ref obj has property key: true
475
+ * @param {boolean} withAlias
476
+ * @returns {Array}
477
+ */
478
+ function expandRef(art, ref, alias, isKey, withAlias) {
479
+ const expanded = [];
480
+ const stack = [ [ art, ref, [ alias || ref[ref.length - 1] ] ] ];
481
+ while (stack.length > 0) {
482
+ const [ current, currentRef, currentAlias ] = stack.pop();
483
+ if (isStructured(current)) {
484
+ for (const [ n, e ] of Object.entries(current.elements || effectiveType(current).elements).reverse())
485
+ stack.push([ e, currentRef.concat(n), currentAlias.concat(n) ]);
486
+ }
487
+ else {
488
+ const obj = { ref: currentRef };
489
+ if (withAlias) {
490
+ const newAlias = currentAlias.join(pathDelimiter);
491
+ // if (alias !== undefined) // explicit alias
492
+ obj.as = newAlias;
493
+ // alias was implicit - to later distinguish expanded s -> s.a from explicitly written s.a
494
+ if (alias === undefined)
495
+ setProp(obj, '$implicitAlias', true);
496
+ }
497
+ if (isKey)
498
+ obj.key = true;
499
+ expanded.push(obj);
500
+ }
501
+ }
502
+
503
+ return expanded;
504
+ }
505
+
506
+ /**
507
+ * Get the effective name produced by the object
508
+ *
509
+ * @param {object} part A thing with a ref/as/func
510
+ * @returns {string}
511
+ */
512
+ function dbName(part) {
513
+ if (part.as)
514
+ return part.as;
515
+ else if (part.ref)
516
+ return implicitAs(part.ref);
517
+ else if (part.func)
518
+ return part.func;
519
+ return null;
520
+ }
521
+
522
+ /**
523
+ * Replace the star and correctly put shadowed things in the right place.
524
+ *
525
+ * @param {Object} base The raw set of things a * can expand to
526
+ * @param {Array} subs Things - the .expand/.inline or .columns
527
+ * @param {string[]} [excluding=[]]
528
+ * @returns {Array} If there was a star, expand it and handle shadowing/excluding, else just return subs
529
+ */
530
+ function replaceStar(base, subs, excluding = []) {
531
+ const stars = [];
532
+ const names = Object.create(null);
533
+ for (let i = 0; i < subs.length; i++) {
534
+ const sub = subs[i];
535
+ if (sub !== '*') {
536
+ const name = dbName(sub);
537
+ names[name] = i;
538
+ }
539
+ else {
540
+ // There should only be one * - but be prepared for more than one
541
+ stars.push(i);
542
+ }
543
+ }
544
+
545
+
546
+ // We have stars - replace/expand them
547
+ if (stars.length > 0) {
548
+ const replaced = Object.create(null);
549
+ const final = [];
550
+ const star = [];
551
+ // Build the result of a * - for later use
552
+ for (const part of Object.keys(base)) {
553
+ if (excluding.indexOf(part) === -1) {
554
+ // The thing is shadowed - ignore names present because of .inline, as those "disappear"
555
+ if (names[part] !== undefined && !subs[names[part]].inline) {
556
+ replaced[part] = true;
557
+ star.push(subs[names[part]]);
558
+ }
559
+ else { // the thing is not shadowed - use the name from the base
560
+ star.push({ ref: [ part ] });
561
+ }
562
+ }
563
+ }
564
+ // Finally: Replace the stars and leave out the shadowed things
565
+ for (let i = 0; i < subs.length; i++) {
566
+ const sub = subs[i];
567
+ if (sub !== '*' && !replaced[dbName(sub)])
568
+ final.push(sub);
569
+ else if (sub === '*')
570
+ final.push(...star);
571
+ }
572
+
573
+ return final;
574
+ }
575
+
576
+ return subs;
577
+ }
578
+ }
579
+
580
+ module.exports = {
581
+ expandStructureReferences,
582
+ };