@sap/cds-compiler 2.11.4 → 2.12.0

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 (80) hide show
  1. package/CHANGELOG.md +58 -1
  2. package/bin/cds_update_identifiers.js +7 -7
  3. package/bin/cdsc.js +9 -10
  4. package/doc/CHANGELOG_ARCHIVE.md +1 -1
  5. package/doc/CHANGELOG_BETA.md +12 -0
  6. package/lib/api/main.js +2 -0
  7. package/lib/api/options.js +2 -2
  8. package/lib/base/message-registry.js +31 -2
  9. package/lib/base/model.js +1 -0
  10. package/lib/base/optionProcessorHelper.js +97 -69
  11. package/lib/checks/.eslintrc.json +2 -0
  12. package/lib/checks/actionsFunctions.js +2 -1
  13. package/lib/checks/foreignKeys.js +4 -4
  14. package/lib/checks/managedInType.js +4 -4
  15. package/lib/checks/queryNoDbArtifacts.js +1 -3
  16. package/lib/checks/sql-snippets.js +93 -0
  17. package/lib/checks/validator.js +8 -0
  18. package/lib/compiler/assert-consistency.js +5 -3
  19. package/lib/compiler/base.js +0 -1
  20. package/lib/compiler/checks.js +32 -9
  21. package/lib/compiler/definer.js +25 -4
  22. package/lib/compiler/index.js +1 -1
  23. package/lib/compiler/propagator.js +3 -2
  24. package/lib/compiler/resolver.js +97 -6
  25. package/lib/compiler/shared.js +12 -1
  26. package/lib/compiler/utils.js +7 -0
  27. package/lib/edm/annotations/genericTranslation.js +34 -17
  28. package/lib/edm/annotations/preprocessAnnotations.js +1 -1
  29. package/lib/edm/csn2edm.js +1 -1
  30. package/lib/edm/edm.js +8 -8
  31. package/lib/edm/edmPreprocessor.js +30 -23
  32. package/lib/edm/edmUtils.js +11 -12
  33. package/lib/gen/Dictionary.json +82 -40
  34. package/lib/gen/language.checksum +1 -1
  35. package/lib/gen/language.interp +3 -1
  36. package/lib/gen/language.tokens +15 -14
  37. package/lib/gen/languageLexer.interp +9 -1
  38. package/lib/gen/languageLexer.js +830 -779
  39. package/lib/gen/languageLexer.tokens +7 -6
  40. package/lib/gen/languageParser.js +2401 -2282
  41. package/lib/json/from-csn.js +47 -16
  42. package/lib/json/to-csn.js +17 -5
  43. package/lib/language/antlrParser.js +3 -3
  44. package/lib/language/docCommentParser.js +1 -1
  45. package/lib/language/genericAntlrParser.js +68 -51
  46. package/lib/language/language.g4 +128 -74
  47. package/lib/language/multiLineStringParser.js +536 -0
  48. package/lib/main.d.ts +5 -3
  49. package/lib/main.js +3 -2
  50. package/lib/model/csnRefs.js +116 -68
  51. package/lib/model/csnUtils.js +40 -48
  52. package/lib/model/enrichCsn.js +30 -14
  53. package/lib/optionProcessor.js +3 -3
  54. package/lib/render/DuplicateChecker.js +1 -1
  55. package/lib/render/manageConstraints.js +1 -1
  56. package/lib/render/toCdl.js +193 -79
  57. package/lib/render/toHdbcds.js +179 -95
  58. package/lib/render/toRename.js +7 -10
  59. package/lib/render/toSql.js +57 -40
  60. package/lib/render/utils/common.js +24 -5
  61. package/lib/render/utils/sql.js +6 -4
  62. package/lib/transform/braceExpression.js +4 -2
  63. package/lib/transform/db/associations.js +389 -0
  64. package/lib/transform/db/cdsPersistence.js +150 -0
  65. package/lib/transform/db/constraints.js +6 -4
  66. package/lib/transform/db/draft.js +3 -2
  67. package/lib/transform/db/expansion.js +4 -5
  68. package/lib/transform/db/flattening.js +5 -6
  69. package/lib/transform/db/temporal.js +236 -0
  70. package/lib/transform/db/transformExists.js +36 -23
  71. package/lib/transform/forHanaNew.js +35 -626
  72. package/lib/transform/forOdataNew.js +5 -4
  73. package/lib/transform/localized.js +3 -14
  74. package/lib/transform/odata/generateForeignKeyElements.js +2 -2
  75. package/lib/transform/transformUtilsNew.js +13 -13
  76. package/lib/transform/translateAssocsToJoins.js +8 -8
  77. package/lib/transform/universalCsnEnricher.js +217 -47
  78. package/lib/utils/file.js +2 -1
  79. package/lib/utils/timetrace.js +8 -2
  80. package/package.json +1 -1
@@ -0,0 +1,389 @@
1
+ 'use strict';
2
+
3
+ const {
4
+ cloneCsn, forEachGeneric,
5
+ forAllElements,
6
+ getUtils,
7
+ } = require('../../model/csnUtils');
8
+
9
+ const transformUtils = require('../transformUtilsNew');
10
+ const { setProp } = require('../../base/model');
11
+
12
+
13
+ /**
14
+ * Return a callback function for forEachDefinition that flattens the foreign keys of managed associations.
15
+ * Foreign keys that are managed associations are replaced by their respective foreign keys.
16
+ *
17
+ * @param {CSN.Model} csn
18
+ * @param {CSN.Options} options
19
+ * @param {string} pathDelimiter
20
+ * @returns {(artifact: CSN.Artifact, artifactName: string) => void} Callback for forEachDefinition
21
+ */
22
+ function getForeignKeyFlattener(csn, options, pathDelimiter) {
23
+ const doA2J = !(options.transformation === 'hdbcds' && options.sqlMapping === 'hdbcds');
24
+ const {
25
+ isManagedAssociationElement,
26
+ isStructured,
27
+ inspectRef,
28
+ } = getUtils(csn);
29
+
30
+ const {
31
+ flattenStructuredElement,
32
+ flattenStructStepsInRef,
33
+ } = transformUtils.getTransformers(csn, options, pathDelimiter);
34
+
35
+ return handleManagedAssociationFKs;
36
+
37
+ /**
38
+ * Flatten and create the foreign key elements of managed associaitons
39
+ *
40
+ * @param {CSN.Artifact} art
41
+ * @param {string} artName
42
+ */
43
+ function handleManagedAssociationFKs(art, artName) {
44
+ if ((art.kind === 'entity' || art.kind === 'view') && doA2J) {
45
+ forAllElements(art, artName, (parent, elements, pathToElements) => {
46
+ forEachGeneric(parent, 'elements', (element, elemName) => {
47
+ if (element.keys && isManagedAssociationElement(element)) {
48
+ // replace foreign keys that are managed associations by their respective foreign keys
49
+ flattenFKs(element, elemName, [ ...pathToElements, elemName ]);
50
+ }
51
+ });
52
+ });
53
+ }
54
+ }
55
+
56
+ /**
57
+ * Flattens all foreign keys
58
+ *
59
+ * Structures will be resolved to individual elements with scalar types
60
+ *
61
+ * Associations will be replaced by their respective foreign keys
62
+ *
63
+ * If a structure contains an assoc, this will also be resolved and vice versa
64
+ *
65
+ * @param {CSN.Element} assoc
66
+ * @param {string} assocName
67
+ * @param {CSN.Path} path
68
+ */
69
+ function flattenFKs(assoc, assocName, path) {
70
+ let finished = false;
71
+ while (!finished) {
72
+ const newKeys = [];
73
+ finished = processKeys(assoc, assocName, path, newKeys);
74
+ assoc.keys = newKeys;
75
+ }
76
+ assoc.keys = assoc.keys.filter(o => !o.$toDelete);
77
+ }
78
+
79
+ /**
80
+ * Walk over the keys and replace structures by their leafs, managed associations by their foreign keys and keep scalar values as-is.
81
+ *
82
+ * @param {CSN.Element} assoc
83
+ * @param {string} assocName
84
+ * @param {CSN.Path} path
85
+ * @param {object[]} collector New keys array to collect the flattened stuff in
86
+ * @returns {boolean} True if all keys are scalar - false if there are things that still need to be processed.
87
+ */
88
+ function processKeys(assoc, assocName, path, collector) {
89
+ let finished = true;
90
+ for (let i = 0; i < assoc.keys.length; i++) {
91
+ const pathToKey = path.concat([ 'keys', i ]);
92
+ const { art } = inspectRef(pathToKey);
93
+ const { ref } = assoc.keys[i];
94
+ if (isStructured(art)) {
95
+ finished = false;
96
+ // Mark this element to filter it later - not needed after expansion
97
+ setProp(assoc.keys[i], '$toDelete', true);
98
+ const flat = flattenStructuredElement(art, ref[ref.length - 1], [], pathToKey);
99
+ Object.keys(flat).forEach((flatElemName) => {
100
+ const key = assoc.keys[i];
101
+ const clone = cloneCsn(assoc.keys[i], options);
102
+ if (clone.as) {
103
+ const lastRef = clone.ref[clone.ref.length - 1];
104
+ // Cut off the last ref part from the beginning of the flat name
105
+ const flatBaseName = flatElemName.slice(lastRef.length);
106
+ // Join it to the existing table alias
107
+ clone.as += flatBaseName;
108
+ // do not loose the $ref for nested keys
109
+ if (key.$ref) {
110
+ let aliasedLeaf = key.$ref[key.$ref.length - 1];
111
+ aliasedLeaf += flatBaseName;
112
+ setProp(clone, '$ref', key.$ref.slice(0, key.$ref.length - 1).concat(aliasedLeaf));
113
+ }
114
+ }
115
+ if (clone.ref) {
116
+ clone.ref[clone.ref.length - 1] = flatElemName;
117
+ // Now we need to properly flatten the whole ref
118
+ clone.ref = flattenStructStepsInRef(clone.ref, pathToKey);
119
+ }
120
+ if (!clone.as)
121
+ clone.as = flatElemName;
122
+ // Directly work on csn.definitions - this way the changes take effect in csnRefs/inspectRef immediately
123
+ // Add the newly generated foreign keys to the end - they will be picked up later on
124
+ // Recursive solutions run into call stack issues
125
+ collector.push(clone);
126
+ });
127
+ }
128
+ else if (art.target) {
129
+ finished = false;
130
+ // Mark this element to filter it later - not needed after expansion
131
+ setProp(assoc.keys[i], '$toDelete', true);
132
+ // Directly work on csn.definitions - this way the changes take effect in csnRefs/inspectRef immediately
133
+ // Add the newly generated foreign keys to the end - they will be picked up later on
134
+ // Recursive solutions run into call stack issues
135
+ art.keys.forEach(key => collector.push(cloneAndExtendRef(key, assoc.keys[i])));
136
+ }
137
+ else if (assoc.keys[i].ref && !assoc.keys[i].as) {
138
+ assoc.keys[i].as = assoc.keys[i].ref[assoc.keys[i].ref.length - 1];
139
+ collector.push(assoc.keys[i]);
140
+ }
141
+ else {
142
+ collector.push(assoc.keys[i]);
143
+ }
144
+ }
145
+ return finished;
146
+ }
147
+
148
+ /**
149
+ * Clone base and extend the .ref and .as of the clone with the .ref and .as of ref.
150
+ *
151
+ * @param {object} key A foreign key entry (of a managed assoc as a fk of another assoc)
152
+ * @param {object} base The fk-ref that has key as a fk
153
+ * @returns {object} The clone of base
154
+ */
155
+ function cloneAndExtendRef(key, base) {
156
+ const { ref } = base;
157
+ const clone = cloneCsn(base, options);
158
+ if (key.ref) {
159
+ // We build a ref that contains the aliased fk - that element will be created later on, so this ref is not resolvable yet
160
+ // Therefore we keep it as $ref - ref is the non-aliased, resolvable "clone"
161
+ // Later on, after we know that these foreign key elements are created, we replace ref with this $ref
162
+ let $ref;
163
+ if (base.$ref) {
164
+ // if a base $ref is provided, use it to correctly resolve association chains
165
+ const refChain = [ base.$ref[base.$ref.length - 1] ].concat(key.as || key.ref);
166
+ $ref = base.$ref.slice(0, base.$ref.length - 1).concat(refChain);
167
+ }
168
+ else {
169
+ $ref = base.ref.concat( key.as || key.ref); // Keep along the aliases
170
+ }
171
+ setProp(clone, '$ref', $ref);
172
+ clone.ref = clone.ref.concat(key.ref);
173
+ }
174
+
175
+ if (!clone.as && clone.ref && clone.ref.length > 0)
176
+ clone.as = ref[ref.length - 1] + pathDelimiter + (key.as || key.ref.join(pathDelimiter));
177
+ else
178
+ clone.as += pathDelimiter + (key.as || key.ref.join(pathDelimiter));
179
+
180
+ return clone;
181
+ }
182
+ }
183
+
184
+
185
+ /**
186
+ * Return a callback function for forEachDefinition that
187
+ *
188
+ * @param {CSN.Model} csn
189
+ * @param {CSN.Options} options
190
+ * @param {string} pathDelimiter
191
+ * @param {object} messageFunctions
192
+ * @param {Function} messageFunctions.error
193
+ * @returns {(artifact: CSN.Artifact, artifactName: string) => void} Callback for forEachDefinition
194
+ */
195
+ function getForeignKeyElementCreator(csn, options, pathDelimiter, messageFunctions) {
196
+ const { error } = messageFunctions;
197
+ const doA2J = !(options.transformation === 'hdbcds' && options.sqlMapping === 'hdbcds');
198
+ const {
199
+ isManagedAssociationElement,
200
+ } = getUtils(csn);
201
+
202
+ const {
203
+ flattenStructStepsInRef, getForeignKeyArtifact,
204
+ } = transformUtils.getTransformers(csn, options, pathDelimiter);
205
+
206
+
207
+ return createForeignKeyElements;
208
+
209
+ /**
210
+ * Create the foreign key elements for managed associations.
211
+ * Create them in-place, right after the corresponding association.
212
+ *
213
+ *
214
+ * @param {CSN.Artifact} art
215
+ * @param {string} artName
216
+ */
217
+ function createForeignKeyElements(art, artName) {
218
+ if ((art.kind === 'entity' || art.kind === 'view') && doA2J) {
219
+ forAllElements(art, artName, (parent, elements, pathToElements) => {
220
+ const elementsArray = [];
221
+ forEachGeneric(parent, 'elements', (element, elemName) => {
222
+ elementsArray.push([ elemName, element ]);
223
+ if (element.keys && isManagedAssociationElement(element)) {
224
+ for (let i = 0; i < element.keys.length; i++) {
225
+ const foreignKey = element.keys[i];
226
+ const path = [ ...pathToElements, elemName, 'keys', i ];
227
+ foreignKey.ref = flattenStructStepsInRef(foreignKey.ref, path);
228
+ const [ fkName, fkElem ] = getForeignKeyArtifact(element, elemName, foreignKey, path);
229
+ if (parent.elements[fkName]) {
230
+ error(null, [ ...pathToElements, elemName ], { name: fkName, art: elemName },
231
+ 'Generated foreign key element $(NAME) for association $(ART) conflicts with existing element');
232
+ }
233
+ else {
234
+ elementsArray.push([ fkName, fkElem ]);
235
+ }
236
+ applyCachedAlias(foreignKey);
237
+ // join ref array as the struct / assoc steps are not necessary anymore
238
+ foreignKey.ref = [ foreignKey.ref.join(pathDelimiter) ];
239
+ }
240
+ }
241
+ });
242
+
243
+ // Don't fake consistency of the model by adding empty elements {}
244
+ if (elementsArray.length === 0)
245
+ return;
246
+
247
+ parent.elements = elementsArray.reduce((previous, [ name, element ]) => {
248
+ previous[name] = element;
249
+ return previous;
250
+ }, Object.create(null));
251
+ });
252
+ }
253
+
254
+ /**
255
+ * We save the aliased representation of the fk in $ref - I suppose to stay resolvable?
256
+ *
257
+ * We now use this $ref for ref and delete it.
258
+ *
259
+ * @param {object} foreignKey
260
+ * @todo With nested projections, we solve similar problems, maybe adapt?
261
+ */
262
+ function applyCachedAlias(foreignKey) {
263
+ // If we have a $ref use that - it resolves aliased FKs correctly
264
+ if (foreignKey.$ref) {
265
+ foreignKey.ref = foreignKey.$ref;
266
+ delete foreignKey.$ref;
267
+ }
268
+ }
269
+ }
270
+ }
271
+
272
+ /**
273
+ * Return a callback function for forEachDefinition that
274
+ *
275
+ * @param {CSN.Model} csn
276
+ * @param {CSN.Options} options
277
+ * @param {string} pathDelimiter
278
+ * @returns {(artifact: CSN.Artifact, artifactName: string) => void} Callback for forEachDefinition
279
+ */
280
+ function getManagedAssociationTransformer(csn, options, pathDelimiter) {
281
+ const doA2J = !(options.transformation === 'hdbcds' && options.sqlMapping === 'hdbcds');
282
+ const {
283
+ isManagedAssociationElement,
284
+ } = getUtils(csn);
285
+
286
+ return handleAssociations;
287
+
288
+ /**
289
+ *
290
+ * Generate foreign keys for managed associations
291
+ * Forbid aliases for foreign keys
292
+ *
293
+ * @param {CSN.Artifact} artifact
294
+ * @param {string} artifactName
295
+ */
296
+ function handleAssociations(artifact, artifactName) {
297
+ // Do things specific for entities and views (pass 1)
298
+ if (artifact.kind === 'entity' || artifact.kind === 'view') {
299
+ const alreadyHandled = new WeakMap();
300
+ forAllElements(artifact, artifactName, (parent, elements) => {
301
+ for (const elemName in elements) {
302
+ const elem = elements[elemName];
303
+ // (140) Generate foreign key elements and ON-condition for managed associations
304
+ // (unless explicitly asked to keep assocs unchanged)
305
+ if (doA2J && isManagedAssociationElement(elem))
306
+ transformManagedAssociation(parent, artifactName, elem, elemName, alreadyHandled);
307
+ }
308
+ });
309
+ }
310
+ }
311
+
312
+ /**
313
+ * Create the foreign key elements for a managed association and build the on-condition
314
+ *
315
+ * @param {CSN.Artifact} artifact
316
+ * @param {string} artifactName
317
+ * @param {Object} elem The association to process
318
+ * @param {string} elemName
319
+ * @param {WeakMap} alreadyHandled To cache which elements were already processed
320
+ * @returns {void}
321
+ */
322
+ function transformManagedAssociation(artifact, artifactName, elem, elemName, alreadyHandled) {
323
+ // No need to run over this - we already did, possibly because it was referenced in the ON-Condition
324
+ // of another association - see a few lines lower
325
+ if (alreadyHandled.has(elem))
326
+ return;
327
+ // Generate foreign key elements for managed associations, and assemble an ON-condition with them
328
+ const onCondParts = [];
329
+ let joinWithAnd = false;
330
+ if (elem.keys.length === 0) {
331
+ elem._ignore = true;
332
+ }
333
+ else {
334
+ for (const foreignKey of elem.keys) {
335
+ // Assemble left hand side of 'assoc.key = fkey'
336
+ const assocKeyArg = {
337
+ ref: [
338
+ elemName,
339
+ ].concat(foreignKey.ref),
340
+ };
341
+ const fkName = `${elemName}${pathDelimiter}${foreignKey.as}`;
342
+ const fKeyArg = {
343
+ ref: [
344
+ fkName,
345
+ ],
346
+ };
347
+
348
+ if (joinWithAnd) { // more than one FK
349
+ onCondParts.push('and');
350
+ }
351
+
352
+ onCondParts.push(
353
+ assocKeyArg
354
+ );
355
+ onCondParts.push('=');
356
+ onCondParts.push(fKeyArg);
357
+
358
+ if (!joinWithAnd)
359
+ joinWithAnd = true;
360
+ }
361
+ elem.on = onCondParts;
362
+ }
363
+
364
+ // If the managed association has a 'key' property => remove it as unmanaged assocs cannot be keys
365
+ // TODO: Are there other modifiers (like 'key') that are valid for managed, but not valid for unmanaged assocs?
366
+ if (elem.key)
367
+ delete elem.key;
368
+
369
+
370
+ // If the managed association has a 'not null' property => remove it
371
+ if (elem.notNull)
372
+ delete elem.notNull;
373
+
374
+
375
+ // The association is now unmanaged, i.e. actually it should no longer have foreign keys
376
+ // at all. But the processing of backlink associations below expects to have them, so
377
+ // we don't delete them (but mark them as implicit so that toCdl does not render them)
378
+
379
+ // Remember that we already processed this
380
+ alreadyHandled.set(elem, true);
381
+ }
382
+ }
383
+
384
+
385
+ module.exports = {
386
+ getForeignKeyFlattener,
387
+ getForeignKeyElementCreator,
388
+ getManagedAssociationTransformer,
389
+ };
@@ -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' || artifact.kind === 'view') &&
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 wether 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
+ };
@@ -299,13 +299,15 @@ function createReferentialConstraints(csn, options) {
299
299
 
300
300
  return true;
301
301
  }
302
+ const runtimeChecks = options.assertIntegrityType && options.assertIntegrityType.toUpperCase() === RT;
303
+ const compilerChecks = options.assertIntegrityType && options.assertIntegrityType.toUpperCase() === DB;
302
304
 
303
305
  if ((!options.assertIntegrity || options.assertIntegrity === true || options.assertIntegrity === 'true') &&
304
- (!options.assertIntegrityType || options.assertIntegrityType === RT))
306
+ (!options.assertIntegrityType || runtimeChecks))
305
307
  return assertForIntegrityTypeRT();
306
308
 
307
309
  if ((!options.assertIntegrity || options.assertIntegrity === true || options.assertIntegrity === 'true') &&
308
- options.assertIntegrityType === DB)
310
+ compilerChecks)
309
311
  return assertForIntegrityTypeDB();
310
312
 
311
313
  if ((options.assertIntegrity === 'individual'))
@@ -313,7 +315,7 @@ function createReferentialConstraints(csn, options) {
313
315
 
314
316
  // The default for the assertIntegrityType is 'RT', no constraints in that case
315
317
  if ((!options.assertIntegrity || options.assertIntegrity === true) &&
316
- (!options.assertIntegrityType || options.assertIntegrityType === RT))
318
+ (!options.assertIntegrityType || runtimeChecks))
317
319
  return true;
318
320
 
319
321
  if (!element.keys || !isToOne(element))
@@ -385,7 +387,7 @@ function createReferentialConstraints(csn, options) {
385
387
  * @returns {boolean}
386
388
  */
387
389
  function isAssertIntegrityAnnotationSetTo(value) {
388
- return hasAnnotationValue(element, '@assert.integrity', value);
390
+ return hasAnnotationValue(element, '@assert.integrity', value, true);
389
391
  }
390
392
 
391
393
  /**
@@ -134,7 +134,8 @@ function generateDrafts(csn, options, pathDelimiter, messageFunctions) {
134
134
  warning(null, [ 'definitions', artifactName ], 'Entity annotated with “@odata.draft.enabled” should have one key element of type “cds.UUID”');
135
135
 
136
136
 
137
- const matchingService = getMatchingService(artifactName);
137
+ // Ignore boolean return value. We know that we're inside a service or else we wouldn't have reached this code.
138
+ const matchingService = getMatchingService(artifactName) || '';
138
139
  // Generate the DraftAdministrativeData projection into the service, unless there is already one
139
140
  const draftAdminDataProjectionName = `${matchingService}.DraftAdministrativeData`;
140
141
  let draftAdminDataProjection = csn.definitions[draftAdminDataProjectionName];
@@ -336,7 +337,7 @@ function generateDrafts(csn, options, pathDelimiter, messageFunctions) {
336
337
  * Get the service name containing the artifact.
337
338
  *
338
339
  * @param {string} artifactName Absolute name of the artifact
339
- * @returns {boolean|string} Name of the service or false if no match is found.
340
+ * @returns {false|string} Name of the service or false if no match is found.
340
341
  */
341
342
  function getMatchingService(artifactName) {
342
343
  const matches = [];
@@ -239,15 +239,14 @@ function expandStructureReferences(csn, options, pathDelimiter, { error, info, t
239
239
  * @param {CSN.Artifact} root All elements visible fromt he query source ($combined)
240
240
  * @param {CSN.Column[]} columns
241
241
  * @param {string[]} excluding
242
- * @returns {{columns: Array, toManys: Array}} Object with rewritten columns (.expand/.inline) and with any .expand + to-many
242
+ * @returns {{columns: Array, toMany: Array}} Object with rewritten columns (.expand/.inline) and with any .expand + to-many
243
243
  */
244
244
  function rewrite(root, columns, excluding) {
245
245
  const allToMany = [];
246
246
  const newThing = [];
247
247
  // Replace stars - needs to happen here since the .expand/.inline first path step affects the root *
248
248
  columns = replaceStar(root, columns, excluding);
249
- for (let i = 0; i < columns.length; i++) {
250
- const col = columns[i];
249
+ for (const col of columns) {
251
250
  if (col.expand) {
252
251
  // 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
252
  const { expanded, toManys } = expandInline(root, col, col.ref || [], [ dbName(col) ]);
@@ -477,6 +476,7 @@ function expandStructureReferences(csn, options, pathDelimiter, { error, info, t
477
476
  */
478
477
  function expandRef(art, ref, alias, isKey, withAlias) {
479
478
  const expanded = [];
479
+ /** @type {Array<[CSN.Element, any[], any[]]>} */
480
480
  const stack = [ [ art, ref, [ alias || ref[ref.length - 1] ] ] ];
481
481
  while (stack.length > 0) {
482
482
  const [ current, currentRef, currentAlias ] = stack.pop();
@@ -564,8 +564,7 @@ function expandStructureReferences(csn, options, pathDelimiter, { error, info, t
564
564
  }
565
565
  }
566
566
  // Finally: Replace the stars and leave out the shadowed things
567
- for (let i = 0; i < subs.length; i++) {
568
- const sub = subs[i];
567
+ for (const sub of subs) {
569
568
  if (sub !== '*' && !replaced[dbName(sub)])
570
569
  final.push(sub);
571
570
  else if (sub === '*')
@@ -249,10 +249,9 @@ function flattenElements(csn, options, pathDelimiter, error) {
249
249
 
250
250
  if (flatElement.type && isAssocOrComposition(flatElement.type) && flatElement.on) {
251
251
  // Make refs resolvable by fixing the first ref step
252
- for (let i = 0; i < flatElement.on.length; i++) {
253
- const onPart = flatElement.on[i];
252
+ for (const onPart of flatElement.on) {
254
253
  if (onPart.ref) {
255
- const firstRef = flatElement.on[i].ref[0];
254
+ const firstRef = onPart.ref[0];
256
255
 
257
256
  /*
258
257
  when element is defined in the current name resolution scope, like
@@ -269,7 +268,7 @@ function flattenElements(csn, options, pathDelimiter, error) {
269
268
  const possibleFlatName = prefix + pathDelimiter + firstRef;
270
269
 
271
270
  if (flatElems[possibleFlatName])
272
- flatElement.on[i].ref[0] = possibleFlatName;
271
+ onPart.ref[0] = possibleFlatName;
273
272
  }
274
273
  }
275
274
  }
@@ -305,11 +304,11 @@ function flattenElements(csn, options, pathDelimiter, error) {
305
304
  /**
306
305
  * Walk the element chain
307
306
  *
308
- * @param {CSN.Element} e
307
+ * @param {object} e
309
308
  * @param {string} name
310
309
  */
311
310
  function walkElements(e, name) {
312
- if (isBuiltinType(e)) {
311
+ if (isBuiltinType(e.type)) {
313
312
  branches[subbranchNames.concat(name).join(pathDelimiter)] = subbranchElements.concat(e);
314
313
  }
315
314
  else {