@sap/cds-compiler 5.9.2 → 6.0.10

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 (111) hide show
  1. package/CHANGELOG.md +109 -319
  2. package/README.md +1 -1
  3. package/bin/cds_update_identifiers.js +3 -5
  4. package/bin/cdsc.js +22 -8
  5. package/bin/cdshi.js +1 -1
  6. package/bin/cdsse.js +4 -4
  7. package/doc/CHANGELOG_BETA.md +11 -0
  8. package/doc/CHANGELOG_DEPRECATED.md +29 -0
  9. package/lib/api/main.js +8 -5
  10. package/lib/api/options.js +12 -10
  11. package/lib/base/builtins.js +1 -0
  12. package/lib/base/message-registry.js +190 -96
  13. package/lib/base/messages.js +29 -20
  14. package/lib/base/model.js +14 -24
  15. package/lib/checks/actionsFunctions.js +10 -20
  16. package/lib/checks/annotationsOData.js +1 -1
  17. package/lib/checks/elements.js +30 -10
  18. package/lib/checks/enums.js +31 -0
  19. package/lib/checks/foreignKeys.js +2 -2
  20. package/lib/checks/hasPersistedElements.js +5 -0
  21. package/lib/checks/invalidTarget.js +1 -1
  22. package/lib/checks/managedWithoutKeys.js +5 -4
  23. package/lib/checks/queryNoDbArtifacts.js +10 -8
  24. package/lib/checks/types.js +5 -5
  25. package/lib/checks/validator.js +6 -4
  26. package/lib/compiler/assert-consistency.js +12 -9
  27. package/lib/compiler/checks.js +18 -50
  28. package/lib/compiler/define.js +6 -6
  29. package/lib/compiler/extend.js +2 -1
  30. package/lib/compiler/generate.js +14 -17
  31. package/lib/compiler/populate.js +8 -31
  32. package/lib/compiler/propagator.js +21 -35
  33. package/lib/compiler/resolve.js +35 -22
  34. package/lib/compiler/shared.js +7 -1
  35. package/lib/compiler/tweak-assocs.js +1 -1
  36. package/lib/compiler/utils.js +1 -1
  37. package/lib/edm/annotations/edmJson.js +20 -15
  38. package/lib/edm/annotations/genericTranslation.js +7 -8
  39. package/lib/edm/csn2edm.js +46 -50
  40. package/lib/edm/edm.js +8 -7
  41. package/lib/edm/edmPreprocessor.js +37 -85
  42. package/lib/edm/edmUtils.js +2 -2
  43. package/lib/gen/BaseParser.js +55 -44
  44. package/lib/gen/CdlGrammar.checksum +1 -1
  45. package/lib/gen/CdlParser.js +1133 -1150
  46. package/lib/json/from-csn.js +70 -43
  47. package/lib/json/to-csn.js +6 -8
  48. package/lib/language/multiLineStringParser.js +3 -2
  49. package/lib/main.d.ts +58 -24
  50. package/lib/model/csnUtils.js +28 -39
  51. package/lib/model/xprAsTree.js +23 -9
  52. package/lib/modelCompare/compare.js +5 -4
  53. package/lib/optionProcessor.js +21 -17
  54. package/lib/parsers/AstBuildingParser.js +63 -11
  55. package/lib/parsers/XprTree.js +57 -3
  56. package/lib/parsers/identifiers.js +1 -1
  57. package/lib/parsers/index.js +0 -3
  58. package/lib/render/manageConstraints.js +25 -25
  59. package/lib/render/toCdl.js +173 -170
  60. package/lib/render/toHdbcds.js +126 -128
  61. package/lib/render/toRename.js +7 -7
  62. package/lib/render/toSql.js +128 -125
  63. package/lib/render/utils/common.js +47 -22
  64. package/lib/render/utils/delta.js +25 -25
  65. package/lib/render/utils/operators.js +2 -2
  66. package/lib/render/utils/pretty.js +5 -5
  67. package/lib/render/utils/sql.js +13 -13
  68. package/lib/render/utils/standardDatabaseFunctions.js +115 -103
  69. package/lib/render/utils/unique.js +4 -4
  70. package/lib/transform/db/applyTransformations.js +1 -1
  71. package/lib/transform/db/assertUnique.js +2 -2
  72. package/lib/transform/db/associations.js +6 -7
  73. package/lib/transform/db/assocsToQueries/utils.js +4 -5
  74. package/lib/transform/db/backlinks.js +12 -9
  75. package/lib/transform/db/cdsPersistence.js +8 -7
  76. package/lib/transform/db/constraints.js +13 -10
  77. package/lib/transform/db/expansion.js +7 -3
  78. package/lib/transform/db/flattening.js +4 -14
  79. package/lib/transform/db/processSqlServices.js +2 -1
  80. package/lib/transform/db/temporal.js +5 -7
  81. package/lib/transform/db/views.js +2 -4
  82. package/lib/transform/draft/db.js +8 -8
  83. package/lib/transform/draft/odata.js +10 -7
  84. package/lib/transform/forOdata.js +10 -5
  85. package/lib/transform/forRelationalDB.js +5 -75
  86. package/lib/transform/localized.js +1 -1
  87. package/lib/transform/odata/createForeignKeys.js +11 -10
  88. package/lib/transform/odata/flattening.js +8 -4
  89. package/lib/transform/odata/foreignKeyRefsInXprAnnos.js +96 -0
  90. package/lib/transform/odata/typesExposure.js +3 -3
  91. package/lib/transform/transformUtils.js +4 -8
  92. package/lib/transform/translateAssocsToJoins.js +14 -7
  93. package/lib/transform/universalCsn/universalCsnEnricher.js +10 -4
  94. package/lib/utils/objectUtils.js +0 -17
  95. package/package.json +10 -13
  96. package/share/messages/def-upcoming-virtual-change.md +1 -1
  97. package/LICENSE +0 -37
  98. package/bin/cds_remove_invalid_whitespace.js +0 -138
  99. package/doc/CHANGELOG_ARCHIVE.md +0 -3604
  100. package/lib/gen/genericAntlrParser.js +0 -3
  101. package/lib/gen/language.checksum +0 -1
  102. package/lib/gen/language.interp +0 -456
  103. package/lib/gen/language.tokens +0 -180
  104. package/lib/gen/languageLexer.interp +0 -439
  105. package/lib/gen/languageLexer.js +0 -1483
  106. package/lib/gen/languageLexer.tokens +0 -167
  107. package/lib/gen/languageParser.js +0 -24941
  108. package/lib/language/antlrParser.js +0 -205
  109. package/lib/language/errorStrategy.js +0 -646
  110. package/lib/language/genericAntlrParser.js +0 -1572
  111. package/lib/parsers/CdlGrammar.g4 +0 -2070
@@ -14,9 +14,9 @@ function renderUniqueConstraintString( constraint, constraintName, tableName, qu
14
14
  const c = constraint.paths;
15
15
  const refs = c.map(cpath => quoteSqlId(cpath.ref[0])).join(', ');
16
16
  if (options.src === 'hdi')
17
- return `UNIQUE INVERTED INDEX ${constraintName} ON ${tableName} (${refs})`;
17
+ return `UNIQUE INVERTED INDEX ${ constraintName } ON ${ tableName } (${ refs })`;
18
18
 
19
- return `CONSTRAINT ${constraintName} UNIQUE (${refs})`;
19
+ return `CONSTRAINT ${ constraintName } UNIQUE (${ refs })`;
20
20
  }
21
21
  /**
22
22
  * Render the "ALTER TABLE XY DROP CONSTRAINT <Z>;"
@@ -28,7 +28,7 @@ function renderUniqueConstraintString( constraint, constraintName, tableName, qu
28
28
  * @returns {string}
29
29
  */
30
30
  function renderUniqueConstraintDrop( constraint, constraintName, tableName, quoteSqlId ) {
31
- return `ALTER TABLE ${tableName} DROP CONSTRAINT ${quoteSqlId(constraintName)};`;
31
+ return `ALTER TABLE ${ tableName } DROP CONSTRAINT ${ quoteSqlId(constraintName) };`;
32
32
  }
33
33
 
34
34
  /**
@@ -42,7 +42,7 @@ function renderUniqueConstraintDrop( constraint, constraintName, tableName, quot
42
42
  * @returns {string}
43
43
  */
44
44
  function renderUniqueConstraintAdd( constraint, constraintName, tableName, quoteSqlId, options ) {
45
- return `ALTER TABLE ${tableName} ADD ${renderUniqueConstraintString(constraint, constraintName, tableName, quoteSqlId, options)};`;
45
+ return `ALTER TABLE ${ tableName } ADD ${ renderUniqueConstraintString(constraint, constraintName, tableName, quoteSqlId, options) };`;
46
46
  }
47
47
 
48
48
  module.exports = {
@@ -183,7 +183,7 @@ function applyTransformationsInternal( parent, prop, customTransformers, artifac
183
183
  csnPath.push( _prop );
184
184
  for (const name of Object.getOwnPropertyNames( dict )) {
185
185
  if (!isArtifactSkipped( dict, name )) {
186
- artifactTransformers.forEach(fn => fn(dict, name, dict[name]));
186
+ artifactTransformers.forEach(fn => fn(dict, name, dict[name], [ 'definitions' ]));
187
187
  dictEntry( dict, name, dict[name] );
188
188
  }
189
189
  }
@@ -1,6 +1,6 @@
1
1
  'use strict';
2
2
 
3
- const { forEachDefinition, hasAnnotationValue } = require('../../model/csnUtils');
3
+ const { forEachDefinition } = require('../../model/csnUtils');
4
4
  const { getTransformers } = require('../transformUtils');
5
5
  const { pathName } = require('../../compiler/utils');
6
6
 
@@ -32,7 +32,7 @@ function processAssertUnique( csn, options, messageFunctions ) {
32
32
  */
33
33
  function handleAssertUnique( artifact, artifactName ) {
34
34
  // operate only on real entities that are not abstract
35
- if (artifact.abstract || (artifact.kind !== 'entity' || (artifact.query || artifact.projection) && !hasAnnotationValue(artifact, '@cds.persistence.table')))
35
+ if (artifact.abstract || (artifact.kind !== 'entity' || (artifact.query || artifact.projection) && !artifact['@cds.persistence.table']))
36
36
  return;
37
37
  const constraintXrefs = Object.create(null);
38
38
  const constraintDict = Object.create(null);
@@ -46,14 +46,14 @@ function attachOnConditions( csn, csnUtils, pathDelimiter, iterateOptions = {},
46
46
  // of another association - see a few lines lower
47
47
  if (alreadyHandled.has(elem))
48
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 && options.transformation !== 'effective') { // TODO: really kill instead of $ignore?
49
+ if ((!elem.keys || elem.keys.length === 0) && options.transformation !== 'effective') { // TODO: really kill instead of $ignore?
53
50
  elem.$ignore = true;
54
51
  }
55
52
  else {
56
- for (const foreignKey of elem.keys) {
53
+ // Assemble an ON-condition with the foreign keys created in earlier steps
54
+ const onCondParts = [];
55
+ let joinWithAnd = false;
56
+ for (const foreignKey of elem.keys || []) {
57
57
  // Assemble left hand side of 'assoc.key = fkey'
58
58
  const assocKeyArg = {
59
59
  ref: [
@@ -70,9 +70,8 @@ function attachOnConditions( csn, csnUtils, pathDelimiter, iterateOptions = {},
70
70
  ],
71
71
  };
72
72
 
73
- if (joinWithAnd) { // more than one FK
73
+ if (joinWithAnd) // more than one FK
74
74
  onCondParts.push('and');
75
- }
76
75
 
77
76
  onCondParts.push(
78
77
  assocKeyArg
@@ -1,7 +1,7 @@
1
1
  'use strict';
2
2
 
3
3
  const { getRealName } = require('../../../render/utils/common');
4
- const { ModelError } = require('../../../base/error');
4
+ const { ModelError, CompilerAssertion } = require('../../../base/error');
5
5
 
6
6
  /**
7
7
  * Some shared transformation utilities between exists rewriting and general assoc to subselect rewriting
@@ -74,10 +74,10 @@ function getHelpers( csn, inspectRef, error ) {
74
74
  else if (typeof xpr.$env === 'number') {
75
75
  if (xpr.$scope === 'mixin')
76
76
  return '';
77
- return error(null, xpr.$path, '$env with number is not handled yet - report this error!');
77
+ throw new CompilerAssertion(`$env with number is not handled yet - report this error! in ${ JSON.stringify(xpr.$path) }`);
78
78
  }
79
79
 
80
- return error(null, xpr.$path, 'Boolean $env is not handled yet - report this error!');
80
+ throw new CompilerAssertion(`Boolean $env is not handled yet - report this error! in ${ JSON.stringify(xpr.$path) }`);
81
81
  }
82
82
  else if (xpr.ref) {
83
83
  throw new ModelError('Missing $env and missing leading artifact ref - throwing to trigger recompilation!');
@@ -364,8 +364,7 @@ function getHelpers( csn, inspectRef, error ) {
364
364
  }
365
365
  else if (part.$scope === '$self') { // source side - "absolute" scope
366
366
  // Same message as in forRelationalDB/transformDollarSelfComparisonWithUnmanagedAssoc
367
- error(null, part.$path, { name: '$self' },
368
- 'An association that uses $(NAME) in its ON-condition can\'t be compared to "$self"');
367
+ error('type-invalid-self', part.$path, { name: '$self' });
369
368
  }
370
369
  else if (partInspect.art) { // source side - with local scope
371
370
  where.push({ ref: [ target, ...assoc.ref.slice(1, -1), ...part.ref ] });
@@ -5,9 +5,9 @@ const {
5
5
  } = require('../../model/csnUtils');
6
6
 
7
7
  const { setProp } = require('../../base/model');
8
- const { ModelError } = require('../../base/error');
9
8
  const { forEach } = require('../../utils/objectUtils');
10
9
  const { cloneCsnNonDict } = require('../../model/cloneCsn');
10
+ const { ModelError } = require('../../base/error');
11
11
 
12
12
  /**
13
13
  * Get a function that transforms $self backlinks
@@ -188,25 +188,28 @@ function getBacklinkTransformer( csnUtils, messageFunctions, options, pathDelimi
188
188
  // Check: The forward link <assocOp> must not contain '$self' in its own ON-condition
189
189
  if (assoc.on) {
190
190
  const containsDollarSelf = assoc.on.some(isDollarSelfOrProjectionOperand);
191
+ if (containsDollarSelf)
192
+ messageFunctions.error('type-invalid-self', path, { name: '$self' });
193
+ }
191
194
 
192
- if (containsDollarSelf) {
193
- messageFunctions.error(null, path, { name: '$self' },
194
- 'An association that uses $(NAME) in its ON-condition can\'t be compared to $(NAME)');
195
- }
195
+ if (!assoc.keys && !assoc.on) {
196
+ // Interpret no ON-condition/no keys like empty 'keys'.
197
+ if (options.transformation !== 'effective')
198
+ elem.$ignore = true;
199
+ return [];
196
200
  }
197
201
 
198
- // Transform comparison of $self to managed association into AND-combined foreign key comparisons
199
202
  if (assoc.keys) {
200
- if (assoc.keys.length)
203
+ // Transform comparison of $self to managed association into AND-combined foreign key comparisons
204
+ if (assoc.keys.length > 0)
201
205
  return transformDollarSelfComparisonWithManagedAssoc(assocOp, assoc, assocName, elemName, art, path);
202
206
 
203
207
  if (options.transformation !== 'effective')
204
208
  elem.$ignore = true;
205
209
  return [];
206
210
  }
207
-
208
- // Transform comparison of $self to unmanaged association into "reversed" ON-condition
209
211
  else if (assoc.on) {
212
+ // Transform comparison of $self to unmanaged association into "reversed" ON-condition
210
213
  return transformDollarSelfComparisonWithUnmanagedAssoc(assocOp, assoc, assocName, elemName, art, path);
211
214
  }
212
215
 
@@ -1,17 +1,18 @@
1
1
  'use strict';
2
2
 
3
3
  const {
4
- forEachGeneric, forEachMemberRecursively, hasAnnotationValue, isPersistedOnDatabase,
4
+ forEachGeneric,
5
+ forEachMemberRecursively,
6
+ isPersistedOnDatabase,
7
+ hasPersistenceSkipAnnotation,
5
8
  } = require('../../model/csnUtils');
6
9
  const transformUtils = require('../transformUtils');
7
10
 
8
- const exists = '@cds.persistence.exists';
9
-
10
11
  /**
11
12
  * Return a callback function for forEachDefinition that marks artifacts that are abstract or @cds.persistence.exists/skip
12
13
  * with $ignore.
13
14
  *
14
- * @returns {(artifact: CSN.Artifact, artifactName) => void} Callback function for forEachDefinition
15
+ * @returns {(artifact: CSN.Artifact, artifactName: string) => void} Callback function for forEachDefinition
15
16
  */
16
17
  function getAnnoProcessor() {
17
18
  return handleCdsPersistence;
@@ -21,8 +22,8 @@ function getAnnoProcessor() {
21
22
  function handleCdsPersistence( artifact ) {
22
23
  const ignoreArtifact = (artifact.kind === 'entity') &&
23
24
  (artifact.abstract ||
24
- hasAnnotationValue(artifact, '@cds.persistence.skip') ||
25
- hasAnnotationValue(artifact, exists));
25
+ hasPersistenceSkipAnnotation(artifact) ||
26
+ artifact['@cds.persistence.exists']);
26
27
  if (ignoreArtifact)
27
28
  artifact.$ignore = true;
28
29
  }
@@ -116,7 +117,7 @@ function getPersistenceTableProcessor( csn, options, messageFunctions ) {
116
117
  * @param {string} artifactName
117
118
  */
118
119
  function handleQueryish( artifact, artifactName ) {
119
- const stripQueryish = artifact.query && hasAnnotationValue(artifact, '@cds.persistence.table');
120
+ const stripQueryish = artifact.query && artifact['@cds.persistence.table'];
120
121
 
121
122
  if (stripQueryish) {
122
123
  artifact.kind = 'entity';
@@ -1,7 +1,7 @@
1
1
  'use strict';
2
2
 
3
3
  const { forEachDefinition } = require('../../base/model');
4
- const { applyTransformations, hasAnnotationValue, getResultingName } = require('../../model/csnUtils');
4
+ const { applyTransformations, getResultingName, hasPersistenceSkipAnnotation } = require('../../model/csnUtils');
5
5
  const { forEach, forEachKey } = require('../../utils/objectUtils');
6
6
  const { CompilerAssertion } = require('../../base/error');
7
7
 
@@ -292,10 +292,10 @@ function createReferentialConstraints( csn, options ) {
292
292
 
293
293
  // no constraint if either dependent or parent is not persisted
294
294
  if (
295
- hasAnnotationValue(parent, '@cds.persistence.skip') ||
296
- hasAnnotationValue(dependent, '@cds.persistence.skip') ||
297
- hasAnnotationValue(parent, '@cds.persistence.exists') ||
298
- hasAnnotationValue(dependent, '@cds.persistence.exists')
295
+ hasPersistenceSkipAnnotation(parent) ||
296
+ hasPersistenceSkipAnnotation(dependent) ||
297
+ parent['@cds.persistence.exists'] ||
298
+ dependent['@cds.persistence.exists']
299
299
  )
300
300
  return true;
301
301
 
@@ -351,12 +351,12 @@ function createReferentialConstraints( csn, options ) {
351
351
  */
352
352
  function assertForIndividual() {
353
353
  if (isAssertIntegrityAnnotationSetTo(DB) || element[CREATE_FOR_UP]) {
354
- // if this is has a $self comparison, the up_ link should then result in a constraint
354
+ // if this is a $self comparison, the up_ link should then result in a constraint
355
355
  assignPropOnBacklinkIfPossible(CREATE_FOR_UP, true);
356
356
  return false;
357
357
  }
358
358
  if (options.assertIntegrityType === DB && isAssertIntegrityAnnotationSetTo(true)) {
359
- // if this is has a $self comparison, the up_ link should then result in a constraint
359
+ // if this is a $self comparison, the up_ link should then result in a constraint
360
360
  assignPropOnBacklinkIfPossible(CREATE_FOR_UP, true);
361
361
  return false;
362
362
  }
@@ -397,14 +397,17 @@ function createReferentialConstraints( csn, options ) {
397
397
  }
398
398
 
399
399
  /**
400
- * convenience to check if value of element's @assert.integrity annotation
401
- * is the same as a given value
400
+ * Convenience to check if value of element's @assert.integrity annotation
401
+ * is the same as a given value. `@assert.integrity`-value checks do not use the "truthy"-semantics,
402
+ * since string values _and_ booleans are allowed, but are treated differently.
402
403
  *
403
404
  * @param {string|boolean} value
404
405
  * @returns {boolean}
405
406
  */
406
407
  function isAssertIntegrityAnnotationSetTo( value ) {
407
- return hasAnnotationValue(element, '@assert.integrity', value, true);
408
+ if (typeof element['@assert.integrity'] === 'string' && typeof value === 'string')
409
+ return element['@assert.integrity'].toUpperCase() === value.toUpperCase();
410
+ return element['@assert.integrity'] === value;
408
411
  }
409
412
 
410
413
  /**
@@ -1,7 +1,6 @@
1
1
  'use strict';
2
2
 
3
3
  const {
4
- hasAnnotationValue,
5
4
  applyTransformations,
6
5
  setDependencies,
7
6
  walkCsnPath,
@@ -39,7 +38,7 @@ function expandStructureReferences( csn, options, pathDelimiter, messageFunction
39
38
  columns: (parent, name, columns, path) => {
40
39
  const artifact = csn.definitions[path[1]];
41
40
  csnUtils.initDefinition(artifact); // potentially not initialized, yet
42
- if (!hasAnnotationValue(artifact, '@cds.persistence.table')) {
41
+ if (!artifact['@cds.persistence.table']) {
43
42
  const root = csnUtils.get$combined({ SELECT: parent });
44
43
  // TODO: replace with the correct options.transformation?
45
44
  // Do not expand the * in OData for a moment, not to introduce changes
@@ -87,7 +86,7 @@ function expandStructureReferences( csn, options, pathDelimiter, messageFunction
87
86
  // (which is the thing inside SET/SELECT)
88
87
  // We can directly use SELECT here, as only projections and SELECT can have .columns
89
88
  const root = csnUtils.get$combined({ SELECT: parent });
90
- if (!hasAnnotationValue(artifact, '@cds.persistence.table')) {
89
+ if (!artifact['@cds.persistence.table']) {
91
90
  // Make root look like normal .elements - we never cared about conflict afaik anyway
92
91
  Object.keys(root).forEach((key) => {
93
92
  root[key] = root[key][0].element;
@@ -395,6 +394,11 @@ function expandStructureReferences( csn, options, pathDelimiter, messageFunction
395
394
  const stack = [ [ parent, ref ] ];
396
395
  while (stack.length > 0) {
397
396
  const [ current, currentRef ] = stack.pop();
397
+
398
+ // '*' can't be used in function inside expand/inline, as it's not rewritten.
399
+ if (current.func && current.args?.[0] === '*')
400
+ error('query-unsupported-asterisk', current.$path, { code: `${ current.func }(*)` });
401
+
398
402
  if (current.xpr)
399
403
  rewriteSingleExpressionArray(current.xpr, currentRef, stack);
400
404
  if (current.args)
@@ -462,6 +462,9 @@ function handleManagedAssociationsAndCreateForeignKeys( csn, options, messageFun
462
462
  * @param {*} path
463
463
  */
464
464
  function flattenFKs( assoc, assocName, path ) {
465
+ if (!assoc.keys)
466
+ return; // managed to-many assoc
467
+
465
468
  // TODO Depth first search and not iterate mark and sweep approach
466
469
  let finished = false;
467
470
  while (!finished) {
@@ -713,18 +716,7 @@ function createForeignKeys( csnUtils, path, element, prefix, csn, options, pathD
713
716
  return [];
714
717
 
715
718
  if (finalElement.target && !finalElement.on) {
716
- const hasKeys = !!finalElement.keys;
717
- if (!hasKeys) {
718
- const target = csn.definitions[finalElement.target];
719
-
720
- setProp(finalElement, 'keys', [ ] );
721
- if (target && target.elements) {
722
- finalElement.keys = Object.entries(target.elements).filter(([ _n, e ]) => e.key)
723
- . map(([ n, _e ]) => ({ ref: [ n ], as: n }));
724
- }
725
- }
726
- // TODO: has managed assoc keys?
727
- finalElement.keys.forEach((key, keyIndex) => {
719
+ finalElement.keys?.forEach((key, keyIndex) => {
728
720
  const continuePath = getContinuePath([ 'keys', keyIndex ]);
729
721
  const alias = key.as || implicitAs(key.ref);
730
722
  const result = csnUtils.inspectRef(continuePath);
@@ -732,8 +724,6 @@ function createForeignKeys( csnUtils, path, element, prefix, csn, options, pathD
732
724
  ref: key.ref, as: key.as, $path: key.$path, $originalKeyRef: key.$originalKeyRef,
733
725
  } : originalKey));
734
726
  });
735
- if (!hasKeys)
736
- delete finalElement.keys;
737
727
  }
738
728
  // return if the toplevel element is not a managed association
739
729
  else if (lvl === 0) {
@@ -1,6 +1,7 @@
1
1
  'use strict';
2
2
 
3
3
  const { setProp, isBetaEnabled } = require('../../base/model');
4
+ const { hasPersistenceSkipAnnotation } = require('../../model/csnUtils');
4
5
 
5
6
  const sqlServiceAnnotation = '@protocol';
6
7
 
@@ -55,7 +56,7 @@ function isDummyService(artifact, options) {
55
56
  */
56
57
  function isEntityInSqlService(artifact, artifactName, csn, options) {
57
58
  const result = { sqlServiceName: undefined, dummyServiceName: undefined };
58
- if (artifact.kind !== 'entity' || !artifactName.includes('.') || artifact['@cds.persistence.skip'] === true)
59
+ if (artifact.kind !== 'entity' || !artifactName.includes('.') || hasPersistenceSkipAnnotation(artifact))
59
60
  return result;
60
61
 
61
62
  const nameParts = artifactName.split('.');
@@ -1,7 +1,7 @@
1
1
  'use strict';
2
2
 
3
3
  const {
4
- getNormalizedQuery, hasAnnotationValue, forEachMember,
4
+ getNormalizedQuery, forEachMember,
5
5
  } = require('../../model/csnUtils');
6
6
  const { implicitAs } = require('../../model/csnRefs');
7
7
  const { setProp, isBetaEnabled } = require('../../base/model');
@@ -110,10 +110,9 @@ function getViewDecorator( csn, messageFunctions, csnUtils, options ) {
110
110
  if (!Array.isArray(elt))
111
111
  elt = [ elt ];
112
112
  elt.forEach((e) => {
113
- if (hasAnnotationValue(e.element, validFromString))
113
+ if (e.element[validFromString])
114
114
  from.push(e);
115
-
116
- if (hasAnnotationValue(e.element, validToString))
115
+ if (e.element[validToString])
117
116
  to.push(e);
118
117
  });
119
118
  }
@@ -145,9 +144,8 @@ function getViewDecorator( csn, messageFunctions, csnUtils, options ) {
145
144
  }
146
145
  }
147
146
  }
148
- return fromElement && toElement &&
149
- hasAnnotationValue(fromElement, validFromString, false) &&
150
- hasAnnotationValue(toElement, validToString, false);
147
+ return fromElement && !fromElement[validFromString] &&
148
+ toElement && !toElement[validToString];
151
149
  }
152
150
  }
153
151
 
@@ -371,10 +371,8 @@ function getViewTransformer( csn, options, messageFunctions ) {
371
371
  }
372
372
 
373
373
  if (query && !hasNonAssocElements) {
374
- // Complain if there are no elements other than unmanaged associations
375
- // Allow with plain
376
- error(null, [ 'definitions', artName ], { $reviewed: true },
377
- 'Expecting view or projection to have at least one element that is not an unmanaged association');
374
+ // Complain if there are no elements other than unmanaged associations or associations without keys.
375
+ error('def-missing-element', [ 'definitions', artName ], { '#': 'view' });
378
376
  }
379
377
 
380
378
  if (isSelect) {
@@ -1,10 +1,10 @@
1
1
  'use strict';
2
2
 
3
3
  const {
4
- hasAnnotationValue, getServiceNames, forEachDefinition,
4
+ getServiceNames, forEachDefinition,
5
5
  getResultingName, forEachMemberRecursively, applyAnnotationsFromExtensions,
6
6
  } = require('../../model/csnUtils');
7
- const { setProp, isDeprecatedEnabled, isBetaEnabled } = require('../../base/model');
7
+ const { setProp, isBetaEnabled } = require('../../base/model');
8
8
  const { getTransformers } = require('../transformUtils');
9
9
  const { ModelError } = require('../../base/error');
10
10
  const { forEach } = require('../../utils/objectUtils');
@@ -34,7 +34,7 @@ function generateDrafts( csn, options, pathDelimiter, messageFunctions ) {
34
34
 
35
35
  forEachDefinition(csn, generateDraft);
36
36
 
37
- applyAnnotationsFromExtensions(csn, { filter: name => generatedArtifacts[name], applyToElements: false });
37
+ applyAnnotationsFromExtensions(csn, { filter: name => generatedArtifacts[name], applyToElements: false }, error);
38
38
 
39
39
  /**
40
40
  * Generate the draft stuff for a given artifact
@@ -44,7 +44,7 @@ function generateDrafts( csn, options, pathDelimiter, messageFunctions ) {
44
44
  */
45
45
  function generateDraft( artifact, artifactName ) {
46
46
  if ((artifact.kind === 'entity') &&
47
- hasAnnotationValue(artifact, draftAnnotation) &&
47
+ artifact[draftAnnotation] &&
48
48
  isPartOfService(artifactName)) {
49
49
  // Determine the set of target draft nodes belonging to this draft root (the draft root
50
50
  // itself plus all its transitively composition-reachable targets)
@@ -93,13 +93,13 @@ function generateDrafts( csn, options, pathDelimiter, messageFunctions ) {
93
93
  continue;
94
94
  }
95
95
  // Barf if a draft node other than the root has @odata.draft.enabled itself
96
- if (draftNode !== rootArtifact && hasAnnotationValue(draftNode, draftAnnotation)) {
96
+ if (draftNode !== rootArtifact && draftNode[draftAnnotation]) {
97
97
  error('ref-unexpected-draft-enabled', [ 'definitions', artifactName, 'elements', elemName ], { anno: '@odata.draft.enabled' });
98
98
  delete draftNodes[draftNodeName];
99
99
  continue;
100
100
  }
101
- // Recurse unless already known
102
- if (!hasAnnotationValue(draftNode, draftAnnotation, false) && !draftNodes[draftNodeName])
101
+ // Recurse unless already known. Check for explicit `false` on purpose.
102
+ if (draftNode[draftAnnotation] !== false && !draftNodes[draftNodeName])
103
103
  collectDraftNodesInto(draftNode, draftNodeName, rootArtifact, draftNodes);
104
104
  }
105
105
  }
@@ -193,7 +193,7 @@ function generateDrafts( csn, options, pathDelimiter, messageFunctions ) {
193
193
  }
194
194
  else {
195
195
  let elem;
196
- if ((isDeprecatedEnabled(options, '_renderVirtualElements') && origElem.virtual) || !origElem.virtual)
196
+ if (!origElem.virtual)
197
197
  elem = copyAndAddElement(origElem, draftsArtifact, draftsArtifactName, elemName)[elemName];
198
198
  if (elem) {
199
199
  // Remove "virtual" - cap/issues 4956
@@ -36,7 +36,7 @@ function generateDrafts( csn, options, services, messageFunctions ) {
36
36
 
37
37
  const { error, info } = messageFunctions;
38
38
  const {
39
- createAndAddDraftAdminDataProjection, isValidDraftAdminDataMessagesType,
39
+ createAndAddDraftAdminDataProjection, isValidDraftAdminDataMessagesType,
40
40
  createScalarElement, createAssociationElement,
41
41
  createAssociationPathComparison, addElement,
42
42
  createAction, assignAction,
@@ -45,7 +45,6 @@ function generateDrafts( csn, options, services, messageFunctions ) {
45
45
  } = getTransformers(csn, options, messageFunctions);
46
46
  const {
47
47
  getServiceName,
48
- hasAnnotationValue,
49
48
  getFinalTypeInfo,
50
49
  } = csnUtils;
51
50
 
@@ -74,7 +73,7 @@ function generateDrafts( csn, options, services, messageFunctions ) {
74
73
  generateDraftForOdata(def, defName, def);
75
74
  }, { skipArtifact: isExternalServiceMember });
76
75
 
77
- applyAnnotationsFromExtensions(csn, { override: true, filter: name => filterDict[name] });
76
+ applyAnnotationsFromExtensions(csn, { override: true, filter: name => filterDict[name] }, error);
78
77
  rewriteDollarDraft();
79
78
 
80
79
  return csn;
@@ -101,7 +100,7 @@ function generateDrafts( csn, options, services, messageFunctions ) {
101
100
 
102
101
  if(!visitedArtifacts[artifactName])
103
102
  visitedArtifacts[artifactName] = artifact;
104
-
103
+
105
104
  const draftPrepare = createAction('draftPrepare', artifactName, 'SideEffectsQualifier', 'cds.String');
106
105
  assignAction(draftPrepare, artifact);
107
106
  // Generate the actions into the draft-enabled artifact (only draft roots can be activated/edited)
@@ -124,6 +123,8 @@ function generateDrafts( csn, options, services, messageFunctions ) {
124
123
  if (!draftAdminDataProjection) {
125
124
  // @ts-ignore
126
125
  draftAdminDataProjection = createAndAddDraftAdminDataProjection(getServiceOfArtifact(artifactName, services));
126
+ filterDict[draftAdminDataProjectionName] = true;
127
+ filterDict['DRAFT.DraftAdministrativeData'] = true;
127
128
  }
128
129
  // Report an error if it is not an entity or not what we expect
129
130
  if (draftAdminDataProjection.kind !== 'entity' || !draftAdminDataProjection.elements.DraftUUID) {
@@ -217,11 +218,13 @@ function generateDrafts( csn, options, services, messageFunctions ) {
217
218
  // Ignore if that is our own draft root
218
219
  if (draftNode !== rootArtifact) {
219
220
  // Report error when the draft node has @odata.draft.enabled itself
220
- if (hasAnnotationValue(draftNode, '@odata.draft.enabled', true)) {
221
+ const draftEnabled = draftNode['@odata.draft.enabled'];
222
+ if (draftEnabled) {
221
223
  error('ref-unexpected-draft-enabled', [ 'definitions', artifactName, 'elements', elemName ], { anno: '@odata.draft.enabled' });
222
224
  }
223
225
  // Ignore composition if it's target is not part of a service or explicitly draft disabled
224
- else if (!getServiceName(elem.target) || hasAnnotationValue(draftNode, '@odata.draft.enabled', false)) {
226
+ // Only for explicit `false` annotation value, not for `undefined` or `null`.
227
+ else if (!getServiceName(elem.target) || draftEnabled === false) {
225
228
  return;
226
229
  }
227
230
  else {
@@ -247,7 +250,7 @@ function generateDrafts( csn, options, services, messageFunctions ) {
247
250
  * After draft decoration, all visited artifacts are supposed to have the draft state elements
248
251
  * Is/HasActiveEntity, HasDraftEntity. Now, $draft.<postfix> (with postfix defined as magic variable
249
252
  * in the core compiler builtins) needs to be translated into $self.<postfix>.
250
- *
253
+ *
251
254
  * It has to be processed after the late 'applyAnnotationsFromExtensions' which could also merge in
252
255
  * some $draft path expressions.
253
256
  */
@@ -1,6 +1,6 @@
1
1
  'use strict';
2
2
 
3
- const { isDeprecatedEnabled, isBetaEnabled } = require('../base/model');
3
+ const { isBetaEnabled } = require('../base/model');
4
4
  const transformUtils = require('./transformUtils');
5
5
  const { forEachDefinition,
6
6
  forEachMemberRecursively,
@@ -28,6 +28,7 @@ const { addTenantFields } = require('./addTenantFields');
28
28
  const { addLocalizationViews } = require('./localized');
29
29
  const { cloneFullCsn } = require('../model/cloneCsn');
30
30
  const { csnRefs } = require('../model/csnRefs');
31
+ const replaceForeignKeyRefsInExpressionAnnotations = require('./odata/foreignKeyRefsInXprAnnos');
31
32
 
32
33
  // Transformation for ODATA. Expects a CSN 'inputModel', processes it for ODATA.
33
34
  // The result should be suitable for consumption by EDMX processors (annotations and metadata)
@@ -129,11 +130,9 @@ function transform4odataWithCsn(inputModel, options, messageFunctions) {
129
130
  if (options.tenantDiscriminator)
130
131
  addTenantFields(csn, options);
131
132
 
132
- const keepLocalizedViews = isDeprecatedEnabled(options, '_createLocalizedViews');
133
-
134
133
  function acceptLocalizedView(_name, parent) {
135
134
  csn.definitions[parent].$localized = true;
136
- return keepLocalizedViews && !isExternalServiceMember(undefined, parent);
135
+ return false; // don't keep the views
137
136
  }
138
137
 
139
138
  addLocalizationViews(csn, options, { acceptLocalizedView, ignoreUnknownExtensions: true });
@@ -192,6 +191,12 @@ function transform4odataWithCsn(inputModel, options, messageFunctions) {
192
191
 
193
192
  createForeignKeyElements(csn, options, messageFunctions, csnUtils, { skipArtifact: isExternalServiceMember });
194
193
 
194
+ // needs to be performed after creating foreign keys for the entire model,
195
+ // because of multiple managed associations in refs
196
+ replaceForeignKeyRefsInExpressionAnnotations(csn, options, messageFunctions, csnUtils, { skipArtifact: isExternalServiceMember });
197
+
198
+ bindCsnReferenceOnly();
199
+
195
200
  if (!structuredOData) {
196
201
  const resolved = new WeakMap();
197
202
  const { inspectRef, effectiveType } = csnRefs(csn);
@@ -465,7 +470,7 @@ function transform4odataWithCsn(inputModel, options, messageFunctions) {
465
470
  // a cardinality property is set to the association member
466
471
  // with the value { "min": 1 };
467
472
  function setCardinalityToNotNullAssociations(member) {
468
- if (member.target && member.keys && !member.on) {
473
+ if (member.target && !member.on) {
469
474
  if (member.notNull) {
470
475
  if (member.cardinality === undefined)
471
476
  member.cardinality = {};