@sap/cds-compiler 2.11.2 → 2.13.6

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 (140) hide show
  1. package/CHANGELOG.md +175 -2
  2. package/bin/.eslintrc.json +1 -2
  3. package/bin/cds_update_identifiers.js +10 -8
  4. package/bin/cdsc.js +23 -17
  5. package/bin/cdsse.js +2 -2
  6. package/bin/cdsv2m.js +3 -2
  7. package/doc/CHANGELOG_ARCHIVE.md +1 -1
  8. package/doc/CHANGELOG_BETA.md +25 -6
  9. package/doc/CHANGELOG_DEPRECATED.md +22 -6
  10. package/doc/NameResolution.md +21 -16
  11. package/lib/api/main.js +32 -79
  12. package/lib/api/options.js +3 -2
  13. package/lib/api/validate.js +2 -1
  14. package/lib/backends.js +16 -26
  15. package/lib/base/dictionaries.js +0 -8
  16. package/lib/base/error.js +26 -0
  17. package/lib/base/keywords.js +10 -19
  18. package/lib/base/location.js +9 -4
  19. package/lib/base/message-registry.js +75 -9
  20. package/lib/base/messages.js +31 -35
  21. package/lib/base/model.js +2 -62
  22. package/lib/base/optionProcessorHelper.js +246 -183
  23. package/lib/checks/.eslintrc.json +2 -0
  24. package/lib/checks/actionsFunctions.js +2 -1
  25. package/lib/checks/annotationsOData.js +1 -1
  26. package/lib/checks/cdsPersistence.js +2 -1
  27. package/lib/checks/emptyOrOnlyVirtual.js +2 -2
  28. package/lib/checks/enricher.js +17 -1
  29. package/lib/checks/foreignKeys.js +4 -4
  30. package/lib/checks/invalidTarget.js +3 -1
  31. package/lib/checks/managedInType.js +4 -4
  32. package/lib/checks/managedWithoutKeys.js +3 -1
  33. package/lib/checks/queryNoDbArtifacts.js +1 -3
  34. package/lib/checks/selectItems.js +4 -4
  35. package/lib/checks/sql-snippets.js +94 -0
  36. package/lib/checks/types.js +1 -1
  37. package/lib/checks/unknownMagic.js +1 -1
  38. package/lib/checks/validator.js +12 -7
  39. package/lib/compiler/assert-consistency.js +12 -8
  40. package/lib/compiler/base.js +0 -1
  41. package/lib/compiler/builtins.js +42 -21
  42. package/lib/compiler/checks.js +46 -12
  43. package/lib/compiler/cycle-detector.js +1 -1
  44. package/lib/compiler/define.js +1103 -0
  45. package/lib/compiler/extend.js +983 -0
  46. package/lib/compiler/finalize-parse-cdl.js +231 -0
  47. package/lib/compiler/index.js +46 -39
  48. package/lib/compiler/kick-start.js +190 -0
  49. package/lib/compiler/moduleLayers.js +4 -4
  50. package/lib/compiler/populate.js +1226 -0
  51. package/lib/compiler/propagator.js +113 -47
  52. package/lib/compiler/resolve.js +1433 -0
  53. package/lib/compiler/shared.js +100 -65
  54. package/lib/compiler/tweak-assocs.js +529 -0
  55. package/lib/compiler/utils.js +215 -33
  56. package/lib/edm/.eslintrc.json +5 -0
  57. package/lib/edm/annotations/genericTranslation.js +38 -25
  58. package/lib/edm/annotations/preprocessAnnotations.js +3 -3
  59. package/lib/edm/csn2edm.js +10 -9
  60. package/lib/edm/edm.js +19 -20
  61. package/lib/edm/edmPreprocessor.js +166 -95
  62. package/lib/edm/edmUtils.js +127 -34
  63. package/lib/gen/Dictionary.json +92 -43
  64. package/lib/gen/language.checksum +1 -1
  65. package/lib/gen/language.interp +11 -1
  66. package/lib/gen/language.tokens +86 -82
  67. package/lib/gen/languageLexer.interp +18 -1
  68. package/lib/gen/languageLexer.js +925 -847
  69. package/lib/gen/languageLexer.tokens +78 -74
  70. package/lib/gen/languageParser.js +5434 -4298
  71. package/lib/json/from-csn.js +59 -17
  72. package/lib/json/to-csn.js +189 -71
  73. package/lib/language/antlrParser.js +3 -3
  74. package/lib/language/docCommentParser.js +3 -3
  75. package/lib/language/errorStrategy.js +26 -8
  76. package/lib/language/genericAntlrParser.js +144 -53
  77. package/lib/language/language.g4 +424 -200
  78. package/lib/language/multiLineStringParser.js +536 -0
  79. package/lib/main.d.ts +550 -61
  80. package/lib/main.js +38 -11
  81. package/lib/model/api.js +3 -1
  82. package/lib/model/csnRefs.js +322 -198
  83. package/lib/model/csnUtils.js +226 -370
  84. package/lib/model/enrichCsn.js +124 -69
  85. package/lib/model/revealInternalProperties.js +29 -7
  86. package/lib/model/sortViews.js +10 -2
  87. package/lib/modelCompare/compare.js +17 -12
  88. package/lib/optionProcessor.js +8 -3
  89. package/lib/render/.eslintrc.json +1 -2
  90. package/lib/render/DuplicateChecker.js +1 -1
  91. package/lib/render/manageConstraints.js +36 -33
  92. package/lib/render/toCdl.js +174 -275
  93. package/lib/render/toHdbcds.js +203 -122
  94. package/lib/render/toRename.js +7 -10
  95. package/lib/render/toSql.js +161 -82
  96. package/lib/render/utils/common.js +22 -8
  97. package/lib/render/utils/sql.js +10 -7
  98. package/lib/render/utils/stringEscapes.js +111 -0
  99. package/lib/sql-identifier.js +1 -1
  100. package/lib/transform/.eslintrc.json +5 -0
  101. package/lib/transform/braceExpression.js +4 -2
  102. package/lib/transform/db/.eslintrc.json +2 -0
  103. package/lib/transform/db/applyTransformations.js +212 -0
  104. package/lib/transform/db/assertUnique.js +1 -1
  105. package/lib/transform/db/associations.js +187 -0
  106. package/lib/transform/db/cdsPersistence.js +150 -0
  107. package/lib/transform/db/constraints.js +61 -56
  108. package/lib/transform/db/expansion.js +50 -29
  109. package/lib/transform/db/flattening.js +556 -106
  110. package/lib/transform/db/groupByOrderBy.js +3 -1
  111. package/lib/transform/db/temporal.js +236 -0
  112. package/lib/transform/db/transformExists.js +103 -28
  113. package/lib/transform/db/views.js +92 -44
  114. package/lib/transform/draft/.eslintrc.json +38 -0
  115. package/lib/transform/{db/draft.js → draft/db.js} +9 -7
  116. package/lib/transform/draft/odata.js +227 -0
  117. package/lib/transform/forHanaNew.js +98 -783
  118. package/lib/transform/forOdataNew.js +22 -175
  119. package/lib/transform/localized.js +36 -32
  120. package/lib/transform/odata/generateForeignKeyElements.js +3 -3
  121. package/lib/transform/odata/referenceFlattener.js +95 -89
  122. package/lib/transform/odata/structureFlattener.js +1 -1
  123. package/lib/transform/odata/toFinalBaseType.js +86 -12
  124. package/lib/transform/odata/typesExposure.js +5 -5
  125. package/lib/transform/odata/utils.js +2 -2
  126. package/lib/transform/transformUtilsNew.js +47 -33
  127. package/lib/transform/translateAssocsToJoins.js +13 -30
  128. package/lib/transform/universalCsn/.eslintrc.json +36 -0
  129. package/lib/transform/universalCsn/coreComputed.js +170 -0
  130. package/lib/transform/universalCsn/universalCsnEnricher.js +715 -0
  131. package/lib/transform/universalCsn/utils.js +63 -0
  132. package/lib/utils/file.js +8 -3
  133. package/lib/utils/objectUtils.js +30 -0
  134. package/lib/utils/timetrace.js +8 -2
  135. package/package.json +1 -1
  136. package/share/messages/README.md +26 -0
  137. package/lib/compiler/definer.js +0 -2349
  138. package/lib/compiler/resolver.js +0 -2922
  139. package/lib/transform/db/helpers.js +0 -58
  140. package/lib/transform/universalCsnEnricher.js +0 -67
@@ -0,0 +1,187 @@
1
+ 'use strict';
2
+
3
+ const {
4
+ applyTransformationsOnNonDictionary,
5
+ applyTransformations,
6
+ getUtils,
7
+ } = require('../../model/csnUtils');
8
+
9
+
10
+ /**
11
+ * In all .elements of entities and views (and their bound actions/functions), create the on-condition for
12
+ * a managed associations. This needs to happen after the .keys are expanded and the corresponding elements are created.
13
+ *
14
+ * @param {CSN.Model} csn
15
+ * @param {string} pathDelimiter
16
+ * @returns {CSN.Model} Return the input csn, with the transformations applied
17
+ */
18
+ function attachOnConditions(csn, pathDelimiter) {
19
+ const {
20
+ isManagedAssociation,
21
+ } = getUtils(csn);
22
+
23
+ const alreadyHandled = new WeakMap();
24
+ applyTransformations(csn, {
25
+ elements: (parent, prop, elements) => {
26
+ for (const elemName in elements) {
27
+ const elem = elements[elemName];
28
+ // (140) Generate the ON-condition for managed associations
29
+ if (isManagedAssociation(elem))
30
+ transformManagedAssociation(elem, elemName);
31
+ }
32
+ }, /* only for views and entities */
33
+ }, [], { skipIgnore: false, allowArtifact: artifact => (artifact.kind === 'entity') });
34
+
35
+ return csn;
36
+
37
+ /**
38
+ * Create the foreign key elements for a managed association and build the on-condition
39
+ *
40
+ * @param {Object} elem The association to process
41
+ * @param {string} elemName
42
+ * @returns {void}
43
+ */
44
+ function transformManagedAssociation(elem, elemName) {
45
+ // No need to run over this - we already did, possibly because it was referenced in the ON-Condition
46
+ // of another association - see a few lines lower
47
+ if (alreadyHandled.has(elem))
48
+ return;
49
+ // Assemble an ON-condition with the foreign keys created in earlier steps
50
+ const onCondParts = [];
51
+ let joinWithAnd = false;
52
+ if (elem.keys.length === 0) { // TODO: really kill instead of _ignore?
53
+ elem._ignore = true;
54
+ }
55
+ else {
56
+ for (const foreignKey of elem.keys) {
57
+ // Assemble left hand side of 'assoc.key = fkey'
58
+ const assocKeyArg = {
59
+ ref: [
60
+ elemName,
61
+ ].concat(foreignKey.ref),
62
+ };
63
+ const fkName = `${elemName}${pathDelimiter}${foreignKey.as}`;
64
+ const fKeyArg = {
65
+ ref: [
66
+ fkName,
67
+ ],
68
+ };
69
+
70
+ if (joinWithAnd) { // more than one FK
71
+ onCondParts.push('and');
72
+ }
73
+
74
+ onCondParts.push(
75
+ assocKeyArg
76
+ );
77
+ onCondParts.push('=');
78
+ onCondParts.push(fKeyArg);
79
+
80
+ if (!joinWithAnd)
81
+ joinWithAnd = true;
82
+ }
83
+ elem.on = onCondParts;
84
+ }
85
+
86
+ // If the managed association has a 'key' property => remove it as unmanaged assocs cannot be keys
87
+ // TODO: Are there other modifiers (like 'key') that are valid for managed, but not valid for unmanaged assocs?
88
+ if (elem.key)
89
+ delete elem.key;
90
+
91
+ // If the managed association has a 'not null' property => remove it
92
+ if (elem.notNull)
93
+ delete elem.notNull;
94
+
95
+ // The association is now unmanaged, i.e. actually it should no longer have foreign keys
96
+ // at all. But the processing of backlink associations below expects to have them, so
97
+ // we don't delete them
98
+ // TODO: maybe make non-enumerable, so we become recompilable in the future?
99
+
100
+ // Remember that we already processed this
101
+ alreadyHandled.set(elem, true);
102
+ }
103
+ }
104
+
105
+ /**
106
+ * @param {CSN.Model} csn
107
+ * @param {string} pathDelimiter
108
+ * @returns {(artifact: CSN.Artifact, artifactName: string) => void} Callback for forEachDefinition
109
+ */
110
+ function getManagedAssocStepsInOnConditionFinalizer(csn, pathDelimiter) {
111
+ const {
112
+ inspectRef,
113
+ } = getUtils(csn);
114
+
115
+ return handleManagedAssocStepsInOnCondition;
116
+
117
+ /**
118
+ * Loop over all elements and for all unmanaged associations translate
119
+ * <assoc base>.<managed assoc>.<fk> to <assoc base>.<managed assoc>_<fk>
120
+ *
121
+ * Or in other words: Allow using the foreign keys of managed associations in on-conditions
122
+ *
123
+ * @param {CSN.Artifact} artifact Artifact to check
124
+ * @param {string} artifactName Name of the artifact
125
+ */
126
+ function handleManagedAssocStepsInOnCondition(artifact, artifactName) {
127
+ for (const elemName in artifact.elements) {
128
+ const elem = artifact.elements[elemName];
129
+ // The association is an unmanaged on
130
+ if (!elem.keys && elem.target && elem.on) {
131
+ applyTransformationsOnNonDictionary(elem, 'on', {
132
+ ref: (refOwner, prop, ref, path) => {
133
+ // [<assoc base>.]<managed assoc>.<field>
134
+ if (ref.length > 1) {
135
+ const { links } = inspectRef(path);
136
+ if (links) {
137
+ // eslint-disable-next-line for-direction
138
+ for (let i = links.length - 1; i >= 0; i--) {
139
+ const link = links[i];
140
+ // We found the latest managed assoc path step
141
+ if (link.art && link.art.target && link.art.keys &&
142
+ // Doesn't work when ref-target (filter condition) or similar is used
143
+ !ref.slice(i).some(refElement => typeof refElement !== 'string')) {
144
+ // We join the managed assoc with everything following it
145
+ const sourceElementName = ref.slice(i).join(pathDelimiter);
146
+ const source = findSource(links, i - 1) || artifact;
147
+ // allow specifying managed assoc on the source side
148
+ const fks = link.art.keys.filter(fk => ref[i] + pathDelimiter + fk.ref[0] === sourceElementName);
149
+ if (fks && fks.length >= 1) {
150
+ const fk = fks[0];
151
+ const managedAssocStepName = refOwner.ref[i];
152
+ const fkName = `${managedAssocStepName}${pathDelimiter}${fk.as}`;
153
+ if (source && source.elements[fkName])
154
+ refOwner.ref = [ ...ref.slice(0, i), fkName ];
155
+ }
156
+ }
157
+ }
158
+ }
159
+ }
160
+ },
161
+ }, {}, [ 'definitions', artifactName, 'elements', elemName ]);
162
+ }
163
+ }
164
+
165
+ /**
166
+ * Find out where the managed association is
167
+ *
168
+ * @param {Array} links
169
+ * @param {number} startIndex
170
+ * @returns {Object| undefined} CSN definition of the source of the managed association
171
+ */
172
+ function findSource(links, startIndex) {
173
+ for (let i = startIndex; i >= 0; i--) {
174
+ const link = links[i];
175
+ // We found the latest assoc step - now check where that points to
176
+ if (link.art && link.art.target)
177
+ return csn.definitions[link.art.target];
178
+ }
179
+
180
+ return undefined;
181
+ }
182
+ }
183
+ }
184
+ module.exports = {
185
+ attachOnConditions,
186
+ getManagedAssocStepsInOnConditionFinalizer,
187
+ };
@@ -0,0 +1,150 @@
1
+ 'use strict';
2
+
3
+ const {
4
+ forEachGeneric, forEachMemberRecursively, hasAnnotationValue, isPersistedOnDatabase,
5
+ getUtils,
6
+ } = require('../../model/csnUtils');
7
+ const transformUtils = require('../transformUtilsNew');
8
+
9
+ const exists = '@cds.persistence.exists';
10
+
11
+ /**
12
+ * Return a callback function for forEachDefinition that marks artifacts that are abstract or @cds.persistence.exists/skip
13
+ * with _ignore.
14
+ *
15
+ * @returns {(artifact: CSN.Artifact, artifactName) => void} Callback function for forEachDefinition
16
+ */
17
+ function getAnnoProcessor() {
18
+ return handleCdsPersistence;
19
+ /**
20
+ * @param {CSN.Artifact} artifact
21
+ */
22
+ function handleCdsPersistence(artifact) {
23
+ const ignoreArtifact = (artifact.kind === 'entity') &&
24
+ (artifact.abstract ||
25
+ hasAnnotationValue(artifact, '@cds.persistence.skip') ||
26
+ hasAnnotationValue(artifact, exists));
27
+ if (ignoreArtifact)
28
+ artifact._ignore = true;
29
+ }
30
+ }
31
+
32
+ /**
33
+ * Return a callback function for forEachDefinition that marks associations with _ignore
34
+ * if their target does not reach the database, i.e. marked with @cds.persistence.skip or is abstract
35
+ *
36
+ * @param {CSN.Model} csn
37
+ * @param {CSN.Options} options
38
+ * @param {object} messageFunctions
39
+ * @param {Function} messageFunctions.info
40
+ * @returns {(artifact: CSN.Artifact, artifactName: string, prop: string, path: CSN.Path) => void} Callback function for forEachDefinition
41
+ */
42
+ function getAssocToSkippedIgnorer(csn, options, messageFunctions) {
43
+ const { info } = messageFunctions;
44
+ const doA2J = !(options.transformation === 'hdbcds' && options.sqlMapping === 'hdbcds');
45
+
46
+ const { isAssocOrComposition } = getUtils(csn);
47
+
48
+ return ignoreAssociationToSkippedTarget;
49
+ /**
50
+ * Associations that target a @cds.persistence.skip artifact must be removed
51
+ * from the persistence model
52
+ *
53
+ * @param {CSN.Artifact} artifact
54
+ * @param {string} artifactName
55
+ * @param {string} prop
56
+ * @param {CSN.Path} path
57
+ */
58
+ function ignoreAssociationToSkippedTarget(artifact, artifactName, prop, path) {
59
+ if (isPersistedOnDatabase(artifact)) {
60
+ // TODO: structure in CSN is artifact.query.[SELECT/SET].mixin
61
+ if (artifact.query) {
62
+ // If we do A2J, we don't need to check the mixin. Either it is used -> a join
63
+ // or published -> handled via elements/members. Unused mixins are removed anyway.
64
+ if (!doA2J && artifact.query.SELECT && artifact.query.SELECT.mixin)
65
+ forEachGeneric(artifact.query.SELECT, 'mixin', ignore, path.concat([ 'query', 'SELECT' ]));
66
+
67
+ else if (!doA2J && artifact.query.SET && artifact.query.SET.mixin)
68
+ forEachGeneric(artifact.query.SET, 'mixin', ignore, path.concat([ 'query', 'SET' ]));
69
+ }
70
+ forEachMemberRecursively(artifact, ignore, [ 'definitions', artifactName ]);
71
+ }
72
+ }
73
+
74
+ /**
75
+ * Mark the given member with _ignore if it is an association/composition and it's target is unreachable.
76
+ *
77
+ * @param {CSN.Element} member
78
+ * @param {string} memberName
79
+ * @param {string} prop
80
+ * @param {CSN.Path} path
81
+ * @todo Why do we check for @cds.persistence.exists here if the parent-function only calls this for skip/abstract?
82
+ */
83
+ function ignore(member, memberName, prop, path) {
84
+ if (options.sqlDialect === 'hana' && !member._ignore && member.target && isAssocOrComposition(member.type) && isUnreachableAssociationTarget(csn.definitions[member.target])) {
85
+ const targetAnnotation = hasAnnotationValue(csn.definitions[member.target], exists) ? exists : '@cds.persistence.skip';
86
+ info(null, path,
87
+ { target: member.target, anno: targetAnnotation },
88
+ 'Association has been removed as it\'s target $(TARGET) is annotated with $(ANNO)');
89
+ member._ignore = true;
90
+ }
91
+ }
92
+
93
+ /**
94
+ * Check whether the given artifact is an unreachable association target because it will not "realy" hit the database:
95
+ * - @cds.persistence.skip/exists
96
+ * - abstract
97
+ *
98
+ * @param {CSN.Artifact} art
99
+ * @returns {boolean}
100
+ */
101
+ function isUnreachableAssociationTarget(art) {
102
+ return !isPersistedOnDatabase(art) || hasAnnotationValue(art, exists);
103
+ }
104
+ }
105
+
106
+ /**
107
+ * Return a callback function for forEachDefinition that handles artifacts marked with @cds.persistence.table.
108
+ * If a .query artifact has this annotation, the .query will be deleted and it will be treated like a table.
109
+ *
110
+ * @param {CSN.Model} csn
111
+ * @param {CSN.Options} options
112
+ * @param {object} messageFunctions
113
+ * @param {Function} messageFunctions.error
114
+ * @returns {(artifact: CSN.Artifact, artifactName) => void} Callback function for forEachDefinition
115
+ */
116
+ function getPersistenceTableProcessor(csn, options, messageFunctions ) {
117
+ const { error } = messageFunctions;
118
+ const {
119
+ recurseElements,
120
+ } = transformUtils.getTransformers(csn, options, '_');
121
+
122
+ return handleQueryish;
123
+
124
+
125
+ /**
126
+ * @param {CSN.Artifact} artifact
127
+ * @param {string} artifactName
128
+ */
129
+ function handleQueryish(artifact, artifactName) {
130
+ const stripQueryish = artifact.query && hasAnnotationValue(artifact, '@cds.persistence.table');
131
+
132
+ if (stripQueryish) {
133
+ artifact.kind = 'entity';
134
+ delete artifact.query;
135
+
136
+ recurseElements(artifact, [ 'definitions', artifactName ], (member, path) => {
137
+ // All elements must have a type for this to work
138
+ if (!member._ignore && !member.kind && !member.type)
139
+ error(null, path, 'Expecting element to have a type if view is annotated with “@cds.persistence.table“');
140
+ });
141
+ }
142
+ }
143
+ }
144
+
145
+
146
+ module.exports = {
147
+ getAnnoProcessor,
148
+ getAssocToSkippedIgnorer,
149
+ getPersistenceTableProcessor,
150
+ };
@@ -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 };