@sap/cds-compiler 2.11.4 → 2.13.8

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 (133) hide show
  1. package/CHANGELOG.md +159 -1
  2. package/bin/cds_update_identifiers.js +7 -7
  3. package/bin/cdsc.js +22 -23
  4. package/bin/cdsse.js +2 -2
  5. package/doc/CHANGELOG_ARCHIVE.md +1 -1
  6. package/doc/CHANGELOG_BETA.md +25 -6
  7. package/doc/CHANGELOG_DEPRECATED.md +22 -6
  8. package/doc/NameResolution.md +21 -16
  9. package/lib/api/main.js +30 -63
  10. package/lib/api/options.js +5 -5
  11. package/lib/api/validate.js +0 -5
  12. package/lib/backends.js +15 -23
  13. package/lib/base/dictionaries.js +0 -8
  14. package/lib/base/error.js +26 -0
  15. package/lib/base/keywords.js +7 -17
  16. package/lib/base/location.js +9 -4
  17. package/lib/base/message-registry.js +52 -2
  18. package/lib/base/messages.js +16 -26
  19. package/lib/base/model.js +2 -62
  20. package/lib/base/optionProcessorHelper.js +246 -183
  21. package/lib/checks/.eslintrc.json +2 -0
  22. package/lib/checks/actionsFunctions.js +2 -1
  23. package/lib/checks/annotationsOData.js +1 -1
  24. package/lib/checks/cdsPersistence.js +2 -1
  25. package/lib/checks/enricher.js +17 -1
  26. package/lib/checks/foreignKeys.js +4 -4
  27. package/lib/checks/invalidTarget.js +3 -1
  28. package/lib/checks/managedInType.js +4 -4
  29. package/lib/checks/managedWithoutKeys.js +3 -1
  30. package/lib/checks/queryNoDbArtifacts.js +1 -3
  31. package/lib/checks/selectItems.js +4 -4
  32. package/lib/checks/sql-snippets.js +94 -0
  33. package/lib/checks/types.js +1 -1
  34. package/lib/checks/validator.js +12 -7
  35. package/lib/compiler/assert-consistency.js +10 -6
  36. package/lib/compiler/base.js +0 -1
  37. package/lib/compiler/builtins.js +8 -6
  38. package/lib/compiler/checks.js +46 -12
  39. package/lib/compiler/cycle-detector.js +1 -1
  40. package/lib/compiler/define.js +1103 -0
  41. package/lib/compiler/extend.js +983 -0
  42. package/lib/compiler/finalize-parse-cdl.js +231 -0
  43. package/lib/compiler/index.js +33 -14
  44. package/lib/compiler/kick-start.js +190 -0
  45. package/lib/compiler/moduleLayers.js +4 -4
  46. package/lib/compiler/populate.js +1226 -0
  47. package/lib/compiler/propagator.js +113 -47
  48. package/lib/compiler/resolve.js +1433 -0
  49. package/lib/compiler/shared.js +76 -38
  50. package/lib/compiler/tweak-assocs.js +529 -0
  51. package/lib/compiler/utils.js +204 -33
  52. package/lib/edm/.eslintrc.json +5 -0
  53. package/lib/edm/annotations/genericTranslation.js +38 -25
  54. package/lib/edm/annotations/preprocessAnnotations.js +3 -3
  55. package/lib/edm/csn2edm.js +10 -9
  56. package/lib/edm/edm.js +19 -20
  57. package/lib/edm/edmPreprocessor.js +166 -95
  58. package/lib/edm/edmUtils.js +127 -34
  59. package/lib/gen/Dictionary.json +92 -43
  60. package/lib/gen/language.checksum +1 -1
  61. package/lib/gen/language.interp +11 -1
  62. package/lib/gen/language.tokens +86 -82
  63. package/lib/gen/languageLexer.interp +18 -1
  64. package/lib/gen/languageLexer.js +925 -847
  65. package/lib/gen/languageLexer.tokens +78 -74
  66. package/lib/gen/languageParser.js +5434 -4298
  67. package/lib/json/from-csn.js +59 -17
  68. package/lib/json/to-csn.js +143 -71
  69. package/lib/language/antlrParser.js +3 -3
  70. package/lib/language/docCommentParser.js +3 -3
  71. package/lib/language/genericAntlrParser.js +144 -54
  72. package/lib/language/language.g4 +424 -203
  73. package/lib/language/multiLineStringParser.js +536 -0
  74. package/lib/main.d.ts +472 -61
  75. package/lib/main.js +38 -11
  76. package/lib/model/api.js +3 -1
  77. package/lib/model/csnRefs.js +321 -204
  78. package/lib/model/csnUtils.js +224 -263
  79. package/lib/model/enrichCsn.js +97 -40
  80. package/lib/model/revealInternalProperties.js +27 -6
  81. package/lib/model/sortViews.js +2 -1
  82. package/lib/modelCompare/compare.js +17 -12
  83. package/lib/optionProcessor.js +7 -6
  84. package/lib/render/DuplicateChecker.js +1 -1
  85. package/lib/render/manageConstraints.js +36 -33
  86. package/lib/render/toCdl.js +174 -275
  87. package/lib/render/toHdbcds.js +201 -115
  88. package/lib/render/toRename.js +7 -10
  89. package/lib/render/toSql.js +149 -75
  90. package/lib/render/utils/common.js +22 -8
  91. package/lib/render/utils/sql.js +10 -7
  92. package/lib/render/utils/stringEscapes.js +111 -0
  93. package/lib/sql-identifier.js +1 -1
  94. package/lib/transform/.eslintrc.json +5 -0
  95. package/lib/transform/braceExpression.js +4 -2
  96. package/lib/transform/db/.eslintrc.json +2 -0
  97. package/lib/transform/db/applyTransformations.js +35 -12
  98. package/lib/transform/db/assertUnique.js +1 -1
  99. package/lib/transform/db/associations.js +187 -0
  100. package/lib/transform/db/cdsPersistence.js +150 -0
  101. package/lib/transform/db/constraints.js +61 -56
  102. package/lib/transform/db/expansion.js +50 -29
  103. package/lib/transform/db/flattening.js +552 -105
  104. package/lib/transform/db/groupByOrderBy.js +3 -1
  105. package/lib/transform/db/temporal.js +236 -0
  106. package/lib/transform/db/transformExists.js +94 -28
  107. package/lib/transform/db/views.js +5 -4
  108. package/lib/transform/draft/.eslintrc.json +38 -0
  109. package/lib/transform/{db/draft.js → draft/db.js} +9 -7
  110. package/lib/transform/draft/odata.js +227 -0
  111. package/lib/transform/forHanaNew.js +94 -801
  112. package/lib/transform/forOdataNew.js +22 -175
  113. package/lib/transform/localized.js +36 -32
  114. package/lib/transform/odata/generateForeignKeyElements.js +3 -3
  115. package/lib/transform/odata/referenceFlattener.js +95 -89
  116. package/lib/transform/odata/structureFlattener.js +1 -1
  117. package/lib/transform/odata/toFinalBaseType.js +86 -12
  118. package/lib/transform/odata/typesExposure.js +5 -5
  119. package/lib/transform/odata/utils.js +2 -2
  120. package/lib/transform/transformUtilsNew.js +47 -33
  121. package/lib/transform/translateAssocsToJoins.js +10 -27
  122. package/lib/transform/universalCsn/.eslintrc.json +36 -0
  123. package/lib/transform/universalCsn/coreComputed.js +170 -0
  124. package/lib/transform/universalCsn/universalCsnEnricher.js +715 -0
  125. package/lib/transform/universalCsn/utils.js +63 -0
  126. package/lib/utils/file.js +2 -1
  127. package/lib/utils/objectUtils.js +30 -0
  128. package/lib/utils/timetrace.js +8 -2
  129. package/package.json +1 -1
  130. package/share/messages/README.md +26 -0
  131. package/lib/compiler/definer.js +0 -2340
  132. package/lib/compiler/resolver.js +0 -2988
  133. package/lib/transform/universalCsnEnricher.js +0 -67
@@ -1,8 +1,9 @@
1
1
  'use strict';
2
2
 
3
3
  const { forEachDefinition } = require('../../base/model');
4
- const { forAllElements, hasAnnotationValue, getResultingName } = require('../../model/csnUtils');
4
+ const { applyTransformations, hasAnnotationValue, getResultingName } = require('../../model/csnUtils');
5
5
  const { csnRefs } = require('../../model/csnRefs');
6
+ const { forEach, forEachKey } = require('../../utils/objectUtils');
6
7
 
7
8
  const COMPOSITION = 'cds.Composition';
8
9
  const ASSOCIATION = 'cds.Association';
@@ -28,34 +29,36 @@ function createReferentialConstraints(csn, options) {
28
29
  // compositions must be processed first, as the <up_> links for them must result in `ON DELETE CASCADE`
29
30
  const compositions = [];
30
31
  const associations = [];
31
- forEachDefinition(csn, (artifact, artifactName) => {
32
- if (!artifact.query && artifact.kind === 'entity') {
33
- forAllElements(artifact, artifactName, (parent, elements, path) => {
34
- // Step I: iterate compositions, enrich dependent keys for <up_> association in target entity of composition
35
- for (const elementName in elements) {
36
- const element = elements[elementName];
37
- if (element.type === COMPOSITION && element.$selfOnCondition) {
38
- compositions.push({
39
- fn: () => {
40
- foreignKeyConstraintForUpLinkOfComposition(element, parent, path.concat([ elementName ]));
41
- },
42
- });
43
- }
32
+ applyTransformations(csn, {
33
+ elements: (parent, prop, elements, path) => {
34
+ // Step I: iterate compositions, enrich dependent keys for <up_> association in target entity of composition
35
+ for (const elementName in elements) {
36
+ const element = elements[elementName];
37
+ const ePath = path.concat([ 'elements', elementName ]); // Save a copy in this scope for the late callback
38
+ if (element.type === COMPOSITION && element.$selfOnCondition) {
39
+ compositions.push({
40
+ fn: () => {
41
+ foreignKeyConstraintForUpLinkOfComposition(element, parent, ePath);
42
+ },
43
+ });
44
44
  }
45
- // Step II: iterate associations, enrich dependent keys (in entity containing the association)
46
- for (const elementName in elements) {
47
- const element = elements[elementName];
48
- if (element.keys && isToOne(element) && element.type === ASSOCIATION || element.type === COMPOSITION && treatCompositionLikeAssociation(element)) {
49
- associations.push({
50
- fn: () => {
51
- foreignKeyConstraintForAssociation(element, path.concat([ elementName ]));
52
- },
53
- });
54
- }
45
+ }
46
+
47
+ // Step II: iterate associations, enrich dependent keys (in entity containing the association)
48
+ for (const elementName in elements) {
49
+ const element = elements[elementName];
50
+ const ePath = path.concat([ 'elements', elementName ]); // Save a copy in this scope for the late callback
51
+ if (element.keys && isToOne(element) && element.type === ASSOCIATION || element.type === COMPOSITION && treatCompositionLikeAssociation(element)) {
52
+ associations.push({
53
+ fn: () => {
54
+ foreignKeyConstraintForAssociation(element, ePath );
55
+ },
56
+ });
55
57
  }
56
- });
57
- }
58
- });
58
+ }
59
+ },
60
+ }, [], { skipIgnore: false, skipArtifact: a => a.query || a.kind !== 'entity' });
61
+
59
62
  // create constraints on foreign keys
60
63
  // always process unmanaged first, up_ links must be flagged
61
64
  // before they are processed
@@ -215,8 +218,7 @@ function createReferentialConstraints(csn, options) {
215
218
  * Skip referential constraint if the parent table (association target, or artifact where composition is defined)
216
219
  * of the relation is:
217
220
  * - a query
218
- * - TODO: Revisit -- annotated with '@cds.persistence.skip:true'
219
- * - TODO: Revisit -- annotated with '@cds.persistence.exists:true'
221
+ * - annotated with '@cds.persistence.skip:true'
220
222
  *
221
223
  * The following decision table reflects the current implementation:
222
224
  *
@@ -299,13 +301,15 @@ function createReferentialConstraints(csn, options) {
299
301
 
300
302
  return true;
301
303
  }
304
+ const runtimeChecks = options.assertIntegrityType && options.assertIntegrityType.toUpperCase() === RT;
305
+ const compilerChecks = options.assertIntegrityType && options.assertIntegrityType.toUpperCase() === DB;
302
306
 
303
307
  if ((!options.assertIntegrity || options.assertIntegrity === true || options.assertIntegrity === 'true') &&
304
- (!options.assertIntegrityType || options.assertIntegrityType === RT))
308
+ (!options.assertIntegrityType || runtimeChecks))
305
309
  return assertForIntegrityTypeRT();
306
310
 
307
311
  if ((!options.assertIntegrity || options.assertIntegrity === true || options.assertIntegrity === 'true') &&
308
- options.assertIntegrityType === DB)
312
+ compilerChecks)
309
313
  return assertForIntegrityTypeDB();
310
314
 
311
315
  if ((options.assertIntegrity === 'individual'))
@@ -313,7 +317,7 @@ function createReferentialConstraints(csn, options) {
313
317
 
314
318
  // The default for the assertIntegrityType is 'RT', no constraints in that case
315
319
  if ((!options.assertIntegrity || options.assertIntegrity === true) &&
316
- (!options.assertIntegrityType || options.assertIntegrityType === RT))
320
+ (!options.assertIntegrityType || runtimeChecks))
317
321
  return true;
318
322
 
319
323
  if (!element.keys || !isToOne(element))
@@ -385,7 +389,7 @@ function createReferentialConstraints(csn, options) {
385
389
  * @returns {boolean}
386
390
  */
387
391
  function isAssertIntegrityAnnotationSetTo(value) {
388
- return hasAnnotationValue(element, '@assert.integrity', value);
392
+ return hasAnnotationValue(element, '@assert.integrity', value, true);
389
393
  }
390
394
 
391
395
  /**
@@ -489,18 +493,20 @@ function createReferentialConstraints(csn, options) {
489
493
  const dependentKey = [ elementName ];
490
494
  const onDeleteRules = new Set();
491
495
  onDeleteRules.add($foreignKeyConstraint.onDelete);
492
- // find all other $foreignKeyConstraint with same $sourceAssociation and same parentTable
493
- Object.entries(artifact.elements)
494
- .filter(([ , e ]) => e.$foreignKeyConstraint &&
495
- e.$foreignKeyConstraint.sourceAssociation === $foreignKeyConstraint.sourceAssociation &&
496
- e.$foreignKeyConstraint.parentTable === $foreignKeyConstraint.parentTable)
497
- .forEach(([ foreignKeyName, foreignKey ]) => {
498
- const $foreignKeyConstraintCopy = Object.assign({}, foreignKey.$foreignKeyConstraint);
499
- delete foreignKey.$foreignKeyConstraint;
500
- parentKey.push($foreignKeyConstraintCopy.parentKey);
501
- dependentKey.push(foreignKeyName);
502
- onDeleteRules.add($foreignKeyConstraintCopy.onDelete);
503
- });
496
+ forEach(artifact.elements, (foreignKeyName, foreignKey) => {
497
+ // find all other `$foreignKeyConstraint`s with same `$sourceAssociation` and same `parentTable`
498
+ const matchingForeignKeyFound = foreignKey.$foreignKeyConstraint &&
499
+ foreignKey.$foreignKeyConstraint.sourceAssociation === $foreignKeyConstraint.sourceAssociation &&
500
+ foreignKey.$foreignKeyConstraint.parentTable === $foreignKeyConstraint.parentTable;
501
+ if (!matchingForeignKeyFound)
502
+ return;
503
+
504
+ const $foreignKeyConstraintCopy = Object.assign({}, foreignKey.$foreignKeyConstraint);
505
+ delete foreignKey.$foreignKeyConstraint;
506
+ parentKey.push($foreignKeyConstraintCopy.parentKey);
507
+ dependentKey.push(foreignKeyName);
508
+ onDeleteRules.add($foreignKeyConstraintCopy.onDelete);
509
+ });
504
510
  // onDelete Rule is the "weakest" rule applicable. Precedence: RESTRICT > SET NULL > CASCADE
505
511
  const onDelete = onDeleteRules.has('RESTRICT') ? 'RESTRICT' : 'CASCADE';
506
512
  let onDeleteRemark = null;
@@ -508,14 +514,14 @@ function createReferentialConstraints(csn, options) {
508
514
  if (options.testMode && onDelete === 'CASCADE')
509
515
  onDeleteRemark = `Up_ link for Composition "${$foreignKeyConstraint.upLinkFor}" implies existential dependency`;
510
516
  referentialConstraints[`${getResultingName(csn, 'quoted', artifactName)}_${$foreignKeyConstraint.sourceAssociation}`] = {
511
- identifier: `${getResultingName(csn, options.forHana.names, artifactName)}_${$foreignKeyConstraint.sourceAssociation}`,
517
+ // constraint identifier start with `c__` to avoid name clashes
518
+ identifier: `c__${getResultingName(csn, options.forHana.names, artifactName)}_${$foreignKeyConstraint.sourceAssociation}`,
512
519
  foreignKey: dependentKey,
513
520
  parentKey,
514
521
  dependentTable: artifactName,
515
522
  parentTable,
516
523
  onDelete,
517
524
  onDeleteRemark, // explain why this particular rule is chosen
518
- // TODO: do we want to switch off validation / enforcement via annotation on association?
519
525
  validated: $foreignKeyConstraint.validated,
520
526
  enforced: $foreignKeyConstraint.enforced,
521
527
  };
@@ -543,15 +549,14 @@ function assertConstraintIdentifierUniqueness(artifact, artifactName, path, erro
543
549
  if (!(artifact.$tableConstraints && artifact.$tableConstraints.referential && artifact.$tableConstraints.unique))
544
550
  return;
545
551
 
546
- Object.keys(artifact.$tableConstraints.unique)
547
- .map(id => `${artifactName}_${id}`) // final unique constraint identifier will be generated in renderer likewise
548
- .forEach((uniqueConstraintIdentifier) => {
549
- if (artifact.$tableConstraints.referential[uniqueConstraintIdentifier]) {
550
- error(null, path,
551
- { name: uniqueConstraintIdentifier, art: artifactName },
552
- 'Duplicate constraint name $(NAME) in artifact $(ART)');
553
- }
554
- });
552
+ forEachKey(artifact.$tableConstraints.unique, (uniqueConstraintKey) => {
553
+ const uniqueConstraintIdentifier = `${artifactName}_${uniqueConstraintKey}`; // final unique constraint identifier will be generated in renderer likewise
554
+ if (artifact.$tableConstraints.referential[uniqueConstraintIdentifier]) {
555
+ error(null, path,
556
+ { name: uniqueConstraintIdentifier, art: artifactName },
557
+ 'Duplicate constraint name $(NAME) in artifact $(ART)');
558
+ }
559
+ });
555
560
  }
556
561
 
557
562
  module.exports = { createReferentialConstraints, assertConstraintIdentifierUniqueness };
@@ -20,8 +20,9 @@ const { setProp, isBetaEnabled } = require('../../base/model');
20
20
  * @param {Function} messageFunctions.error
21
21
  * @param {Function} messageFunctions.info
22
22
  * @param {Function} messageFunctions.throwWithError
23
+ * @param {object} iterateOptions
23
24
  */
24
- function expandStructureReferences(csn, options, pathDelimiter, { error, info, throwWithError }) {
25
+ function expandStructureReferences(csn, options, pathDelimiter, { error, info, throwWithError }, iterateOptions = {}) {
25
26
  const {
26
27
  isStructured, get$combined, getFinalBaseType, getServiceName,
27
28
  } = getUtils(csn);
@@ -39,7 +40,11 @@ function expandStructureReferences(csn, options, pathDelimiter, { error, info, t
39
40
  const artifact = csn.definitions[path[1]];
40
41
  if (!hasAnnotationValue(artifact, '@cds.persistence.table')) {
41
42
  const root = get$combined({ SELECT: parent });
42
- parent.columns = replaceStar(root, columns, parent.excluding);
43
+ // TODO: replace with the correct options.transformation?
44
+ // Do not expand the * in OData for a moment, not to introduce changes
45
+ // while the OData CSN is still official
46
+ if (!options.toOdata)
47
+ parent.columns = replaceStar(root, columns, parent.excluding);
43
48
  parent.columns = expand(parent.columns, path.concat('columns'), true);
44
49
  }
45
50
  },
@@ -49,7 +54,7 @@ function expandStructureReferences(csn, options, pathDelimiter, { error, info, t
49
54
  orderBy: (parent, name, orderBy, path) => {
50
55
  parent.orderBy = expand(orderBy, path.concat('orderBy'));
51
56
  },
52
- });
57
+ }, [], iterateOptions);
53
58
 
54
59
  /**
55
60
  * Turn .expand/.inline into normal refs. @cds.persistence.skip .expand with to-many (and all transitive views).
@@ -71,16 +76,25 @@ function expandStructureReferences(csn, options, pathDelimiter, { error, info, t
71
76
  if (!hasAnnotationValue(artifact, '@cds.persistence.table')) {
72
77
  const rewritten = rewrite(root, parent.columns, parent.excluding);
73
78
  parent.columns = rewritten.columns;
74
- if (rewritten.toMany.length > 0) {
79
+ /*
80
+ * Do not remove unexpandable many columns in OData
81
+ */
82
+ if (rewritten.toMany.length > 0 && !options.toOdata) {
75
83
  markAsToDummyfy(artifact, path[1]);
76
84
  if (getServiceName(path[1]) === null)
77
85
  error( null, [ 'definitions', path[1] ], { name: path[1] }, 'Unexpected .expand with to-many association in entity $(NAME), which is outside any service');
78
86
  }
87
+ else {
88
+ parent.columns = rewritten.columns;
89
+ }
79
90
  }
80
91
  },
81
92
  });
82
93
 
83
- dummyfy();
94
+ // OData must keep @cds.persistence.skip definitions
95
+ // to present them in the API (and CSN)
96
+ if (!options.toOdata)
97
+ dummyfy();
84
98
 
85
99
  cleanup.forEach(fn => fn());
86
100
 
@@ -88,23 +102,26 @@ function expandStructureReferences(csn, options, pathDelimiter, { error, info, t
88
102
 
89
103
 
90
104
  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
- });
105
+ // OData must allow navigations to @cds.persistence.skip targets
106
+ // as valid navigations in the API
107
+ if (!options.toOdata) {
108
+ applyTransformations(csn, {
109
+ target: (parent, name, target, path) => {
110
+ if (toDummyfy.indexOf(target) !== -1) {
111
+ publishing.push({
112
+ parent, name, target, path: [ ...path ],
113
+ });
114
+ }
115
+ },
116
+ from: check,
117
+ columns: check,
118
+ where: check,
119
+ groupBy: check,
120
+ orderBy: check,
121
+ having: check,
122
+ limit: check,
123
+ });
124
+ }
108
125
 
109
126
 
110
127
  /**
@@ -239,15 +256,14 @@ function expandStructureReferences(csn, options, pathDelimiter, { error, info, t
239
256
  * @param {CSN.Artifact} root All elements visible fromt he query source ($combined)
240
257
  * @param {CSN.Column[]} columns
241
258
  * @param {string[]} excluding
242
- * @returns {{columns: Array, toManys: Array}} Object with rewritten columns (.expand/.inline) and with any .expand + to-many
259
+ * @returns {{columns: Array, toMany: Array}} Object with rewritten columns (.expand/.inline) and with any .expand + to-many
243
260
  */
244
261
  function rewrite(root, columns, excluding) {
245
262
  const allToMany = [];
246
263
  const newThing = [];
247
264
  // Replace stars - needs to happen here since the .expand/.inline first path step affects the root *
248
265
  columns = replaceStar(root, columns, excluding);
249
- for (let i = 0; i < columns.length; i++) {
250
- const col = columns[i];
266
+ for (const col of columns) {
251
267
  if (col.expand) {
252
268
  // 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
269
  const { expanded, toManys } = expandInline(root, col, col.ref || [], [ dbName(col) ]);
@@ -270,7 +286,7 @@ function expandStructureReferences(csn, options, pathDelimiter, { error, info, t
270
286
  }
271
287
 
272
288
  /**
273
- * Check wether the given object is a to-many association
289
+ * Check whether the given object is a to-many association
274
290
  *
275
291
  * @param {CSN.Element} obj
276
292
  * @returns {boolean}
@@ -302,6 +318,11 @@ function expandStructureReferences(csn, options, pathDelimiter, { error, info, t
302
318
  while (stack.length > 0) {
303
319
  const [ base, current, currentRef, currentAlias ] = stack.pop();
304
320
  if (isToMany(current) && current.expand) {
321
+ expanded.push({
322
+ expand: current.expand,
323
+ ref: currentRef,
324
+ as: currentAlias.join(pathDelimiter),
325
+ });
305
326
  toManys.push({ art: current, ref: currentRef, as: currentAlias.join(pathDelimiter) });
306
327
  }
307
328
  else if (current.expand) {
@@ -436,7 +457,7 @@ function expandStructureReferences(csn, options, pathDelimiter, { error, info, t
436
457
  *
437
458
  * @param {Array} thing
438
459
  * @param {CSN.Path} path
439
- * @param {boolean} [withAlias=false] Wether to "expand" the (implicit) alias aswell.
460
+ * @param {boolean} [withAlias=false] Whether to "expand" the (implicit) alias aswell.
440
461
  * @returns {Array} New array - with all structured things expanded
441
462
  */
442
463
  function expand(thing, path, withAlias = false) {
@@ -477,6 +498,7 @@ function expandStructureReferences(csn, options, pathDelimiter, { error, info, t
477
498
  */
478
499
  function expandRef(art, ref, alias, isKey, withAlias) {
479
500
  const expanded = [];
501
+ /** @type {Array<[CSN.Element, any[], any[]]>} */
480
502
  const stack = [ [ art, ref, [ alias || ref[ref.length - 1] ] ] ];
481
503
  while (stack.length > 0) {
482
504
  const [ current, currentRef, currentAlias ] = stack.pop();
@@ -564,8 +586,7 @@ function expandStructureReferences(csn, options, pathDelimiter, { error, info, t
564
586
  }
565
587
  }
566
588
  // Finally: Replace the stars and leave out the shadowed things
567
- for (let i = 0; i < subs.length; i++) {
568
- const sub = subs[i];
589
+ for (const sub of subs) {
569
590
  if (sub !== '*' && !replaced[dbName(sub)])
570
591
  final.push(sub);
571
592
  else if (sub === '*')