@sap/cds-compiler 5.9.4 → 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 +104 -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 +33 -83
  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
@@ -6,8 +6,13 @@
6
6
 
7
7
  const { term } = require('../utils/term');
8
8
  const { Location, locationString } = require('./location');
9
- const { isDeprecatedEnabled, isBetaEnabled } = require('./model');
10
- const { centralMessages, centralMessageTexts, oldMessageIds } = require('./message-registry');
9
+ const { isBetaEnabled } = require('./model');
10
+ const {
11
+ centralMessages,
12
+ configurableForValidValues,
13
+ centralMessageTexts,
14
+ oldMessageIds,
15
+ } = require('./message-registry');
11
16
  const _messageIdsWithExplanation = require('../../share/messages/message-explanations.json').messages;
12
17
  const { analyseCsnPath, traverseQuery } = require('../model/csnRefs');
13
18
  const { CompilerAssertion } = require('./error');
@@ -54,8 +59,10 @@ function hasNonDowngradableErrors( messages, moduleName, options ) {
54
59
 
55
60
  /**
56
61
  * Returns true if the given message id exist in the central message register and is
57
- * downgradable, i.e. an error can be reclassified to a warning or lower.
58
- * Returns false if the messages is an errorFor the given moduleName.
62
+ * downgradable
63
+ * - Non-errors are not downgradable if the moduleName is listed in the
64
+ * `errorFor` property
65
+ * - Errors might be downgradable according to the `configurableFor` property,
59
66
  *
60
67
  * @param {string} messageId
61
68
  * @param {string} moduleName
@@ -70,17 +77,12 @@ function isDowngradable( messageId, moduleName, options ) {
70
77
 
71
78
  // errorFor has the highest priority. If the message is an error for
72
79
  // the module, it is NEVER downgradable.
73
- if (msg.errorFor && msg.errorFor.includes(moduleName))
74
- return false;
75
80
  if (msg.severity !== 'Error')
76
- return true;
77
- // v6 messages are downgradable (except if errorFor also contains the current module).
78
- if (msg.errorFor && msg.errorFor.includes('v6'))
79
- return true;
81
+ return !msg.errorFor?.includes( moduleName );
80
82
  const { configurableFor } = msg;
81
83
  return (Array.isArray( configurableFor ))
82
84
  ? configurableFor.includes( moduleName )
83
- : configurableFor && (configurableFor !== 'deprecated' || isDeprecatedEnabled( options, 'downgradableErrors' ));
85
+ : configurableFor && configurableForValidValues[configurableFor]?.( options );
84
86
  }
85
87
 
86
88
  /**
@@ -90,14 +92,16 @@ function isDowngradable( messageId, moduleName, options ) {
90
92
  * @returns {string}
91
93
  */
92
94
  function severityChangeMarker( msg, config ) {
93
- const severity = msg.severity || 'Error';
94
- if (config.moduleForMarker) {
95
- if (severity === 'Error' &&
96
- isDowngradable( msg.messageId, config.moduleForMarker,
95
+ if (config.moduleForMarker && msg.messageId) {
96
+ if (!msg.severity || msg.severity === 'Error') {
97
+ if (isDowngradable( msg.messageId, config.moduleForMarker,
97
98
  { deprecated: { downgradableErrors: true } }))
98
- return '‹↓›';
99
- if (msg.messageId && centralMessages[msg.messageId]?.errorFor?.includes('v6'))
99
+ // Do not include testMode here, no marker for configurableFor: 'test'
100
+ return '‹↓›';
101
+ }
102
+ else if (centralMessages[msg.messageId]?.errorFor?.includes( 'v7' )) {
100
103
  return '‹↑›';
104
+ }
101
105
  }
102
106
  return '';
103
107
  }
@@ -246,7 +250,7 @@ function reclassifiedSeverity( msg, options, moduleName ) {
246
250
  if (errorFor.includes(moduleName))
247
251
  return 'Error';
248
252
 
249
- if (errorFor.includes('v6') && isBetaEnabled(options, 'v6preview')) {
253
+ if (errorFor.includes('v7') && isBetaEnabled(options, 'v7preview')) {
250
254
  severity = 'Error';
251
255
  if (!isDowngradable(msg.messageId, moduleName, options))
252
256
  return severity;
@@ -755,7 +759,7 @@ const quote = { // could be an option in the future
755
759
  single: p => `‘${ p }’`, // for other things cited from or expected in the model
756
760
  angle: p => `‹${ p }›`, // for tokens like ‹Identifier›, and similar
757
761
  direct: p => p, // e.g. for numbers _not cited from or expected in_ the source
758
- upper: p => p.toUpperCase(), // for keywords reported by ANTLR, use prop.single in v4
762
+ upper: p => p.toUpperCase(),
759
763
  };
760
764
 
761
765
  const paramsTransform = {
@@ -774,6 +778,7 @@ const paramsTransform = {
774
778
  subprop: quote.single,
775
779
  otherprop: quote.single,
776
780
  code: quote.single,
781
+ enum: sym => quote.single( `#${ sym }`),
777
782
  newcode: quote.single,
778
783
  kind: quote.single,
779
784
  meta: quote.angle,
@@ -1899,6 +1904,8 @@ function sanitizeCsnPath( csnPath ) {
1899
1904
  */
1900
1905
  function explainMessage( messageId ) {
1901
1906
  messageId = oldMessageIds[messageId] || messageId;
1907
+ if (Array.isArray(messageId))
1908
+ messageId = messageId[0]; // take first just in case
1902
1909
  const filename = path.join(__dirname, '..', '..', 'share', 'messages', `${ messageId }.md`);
1903
1910
  return fs.readFileSync(filename, 'utf8');
1904
1911
  }
@@ -1912,7 +1919,9 @@ function explainMessage( messageId ) {
1912
1919
  * @returns {boolean}
1913
1920
  */
1914
1921
  function hasMessageExplanation( messageId ) {
1915
- const id = oldMessageIds[messageId] || messageId || false;
1922
+ let id = oldMessageIds[messageId] || messageId || false;
1923
+ if (Array.isArray(id))
1924
+ id = id[0];
1916
1925
  return id && _messageIdsWithExplanation.includes(id);
1917
1926
  }
1918
1927
 
package/lib/base/model.js CHANGED
@@ -25,7 +25,7 @@ const availableBetaFlags = {
25
25
  tenantVariable: true,
26
26
  calcAssoc: true,
27
27
  temporalRawProjection: true,
28
- v6preview: true,
28
+ v7preview: true,
29
29
  draftMessages: true,
30
30
  rewriteAnnotationExpressionsViaType: true,
31
31
  sqlServiceDummies: true,
@@ -36,31 +36,21 @@ const availableBetaFlags = {
36
36
  // Used by isDeprecatedEnabled() to check if any flag ist set.
37
37
  const availableDeprecatedFlags = {
38
38
  // the old ones starting with _, : false
39
+ noPersistenceJournalForGeneratedEntities: true, // since v6
39
40
  downgradableErrors: true,
40
- includesNonShadowedFirst: true,
41
- eagerPersistenceForGeneratedEntities: true,
42
- noKeyPropagationWithExpansions: true,
41
+ noCompositionIncludes: true, // since v6; was an option with inverted meaning in v5
42
+ noQuasiVirtualAssocs: true, // since v6
43
+ _includesNonShadowedFirst: true,
44
+ _eagerPersistenceForGeneratedEntities: true,
45
+ _noKeyPropagationWithExpansions: true,
43
46
  ignoreSpecifiedQueryElements: true,
44
47
  };
45
48
 
46
- const oldDeprecatedFlagsV2 = [
47
- 'createLocalizedViews',
48
- // 'downgradableErrors', // re-added in v4
49
- 'generatedEntityNameWithUnderscore',
50
- 'longAutoexposed',
51
- 'noElementsExpansion',
52
- 'noInheritedAutoexposeViaComposition',
53
- 'noScopedRedirections',
54
- 'oldVirtualNotNullPropagation',
55
- 'parensAsStrings',
56
- 'projectionAsQuery',
57
- 'redirectInSubQueries',
58
- 'renderVirtualElements',
59
- 'shortAutoexposed',
60
- 'unmanagedUpInComponent',
61
- 'v1KeysForTemporal',
62
- // do not add old deprecated flags which should not lead to an error:
63
- // autoCorrectOrderBySourceRefs - just info would be ok
49
+ // Deprecated flags that were removed in v5.
50
+ const oldDeprecatedFlagsV5 = [
51
+ 'includesNonShadowedFirst',
52
+ 'eagerPersistenceForGeneratedEntities',
53
+ 'noKeyPropagationWithExpansions',
64
54
  ];
65
55
 
66
56
  /**
@@ -120,9 +110,9 @@ function checkRemovedDeprecatedFlags( options, { error } ) {
120
110
  return;
121
111
 
122
112
  forEach(options.deprecated, (key, val) => {
123
- if (val && oldDeprecatedFlagsV2.includes(key)) {
113
+ if (val && oldDeprecatedFlagsV5.includes(key)) {
124
114
  error('api-invalid-deprecated', null, { name: key },
125
- 'Deprecated flag $(NAME) has been removed in CDS compiler v3');
115
+ 'Deprecated flag $(NAME) has been removed in CDS compiler v6');
126
116
  }
127
117
  });
128
118
  }
@@ -68,13 +68,8 @@ function checkActionOrFunction( art, artName, prop, path ) {
68
68
  if (!paramType)
69
69
  return; // no type could be resolved
70
70
 
71
- if (param.type && this.csnUtils.isAssocOrComposition(param)) {
72
- this.error(null, currPath, { '#': actKind }, {
73
- std: 'An association is not allowed as this artifact\'s parameter type', // Not used
74
- action: 'An association is not allowed as action\'s parameter type',
75
- function: 'An association is not allowed as function\'s parameter type',
76
- });
77
- }
71
+ if (param.type && this.csnUtils.isAssocOrComposition(param))
72
+ this.error('ref-unexpected-assoc-type', currPath, { '#': `${ actKind }-param` });
78
73
 
79
74
  if (paramType.items?.type)
80
75
  checkActionOrFunctionParameter.call(this, paramType.items, currPath.concat('items'), actKind);
@@ -95,14 +90,8 @@ function checkActionOrFunction( art, artName, prop, path ) {
95
90
  if (!finalReturnType)
96
91
  return; // no type, e.g. `type of V:calculated`; already an error in `checkTypeOfHasProperType()`
97
92
 
98
- if (this.csnUtils.isAssocOrComposition(returns)) {
99
- this.error(null, currPath, { '#': actKind },
100
- {
101
- std: 'An association is not allowed as this artifact\'s return type', // Not used
102
- action: 'An association is not allowed as action\'s return type',
103
- function: 'An association is not allowed as function\'s return type',
104
- });
105
- }
93
+ if (this.csnUtils.isAssocOrComposition(returns))
94
+ this.error('ref-unexpected-assoc-type', currPath, { '#': `${ actKind }-returns` });
106
95
 
107
96
  if (finalReturnType.items) // check array return type
108
97
  checkReturns.call(this, finalReturnType.items, currPath.concat('items'), actKind);
@@ -118,14 +107,15 @@ function checkActionOrFunction( art, artName, prop, path ) {
118
107
  * @param {CSN.Path} currPath The current path
119
108
  */
120
109
  function checkUserDefinedType( type, typeName, currPath ) {
121
- // TODO: isBuiltinType does not resolve any type-chains.
122
- if (!isBuiltinType(type.type) && type.kind && type.kind !== 'type') {
110
+ if (type.kind && type.kind !== 'type' && type.type && !isBuiltinType(this.csnUtils.getFinalTypeInfo(type.type)?.type)) {
123
111
  const serviceOfType = this.csnUtils.getServiceName(typeName);
124
112
  if (serviceName && serviceName !== serviceOfType) {
125
113
  // if (!(isMultiSchema && serviceOfType)) {
126
- this.error(null, currPath,
127
- { type: typeName, kind: type.kind, service: serviceName },
128
- 'Referenced $(KIND) $(TYPE) can\'t be used in service $(SERVICE) because it is not defined in $(SERVICE)');
114
+ this.error('ref-expected-service-type', currPath, { type: typeName, kind: type.kind, service: serviceName }, {
115
+ std: 'Referenced $(KIND) $(TYPE) can\'t be used in service $(SERVICE) because it is defined outside the service',
116
+ entity: 'Referenced entity $(TYPE) can\'t be used in service $(SERVICE) because it is defined outside the service',
117
+ type: 'Referenced type $(TYPE) can\'t be used in service $(SERVICE) because it is defined outside the service',
118
+ });
129
119
  // }
130
120
  }
131
121
  }
@@ -108,7 +108,7 @@ function checkTemporalAnnotationsAssignment( artifact, artifactName ) {
108
108
  * @param {CSN.Path} path
109
109
  */
110
110
  function checkForAnnoAssignmentAndApplicability( annoIdentifier, member, path ) {
111
- if (this.csnUtils.hasAnnotationValue(member, `@cds.valid.${ annoIdentifier }`)) {
111
+ if (member[`@cds.valid.${ annoIdentifier }`]) {
112
112
  valid[annoIdentifier].push(path);
113
113
  // check whether annotation is not assigned to not allowed element type, these are: association, structured elements, leaf element of a structure
114
114
  if (this.csnUtils.isAssocOrComposition(member) || this.csnUtils.isStructured(member) || path.length > 5)
@@ -1,7 +1,10 @@
1
1
  'use strict';
2
2
 
3
3
  const {
4
- forEachMember, forEachMemberRecursively, cardinality2str,
4
+ forEachMember,
5
+ forEachMemberRecursively,
6
+ cardinality2str,
7
+ hasPersistenceSkipAnnotation,
5
8
  } = require('../model/csnUtils');
6
9
  const { isBuiltinType } = require('../base/builtins');
7
10
  const { isGeoTypeName } = require('../compiler/builtins');
@@ -41,9 +44,8 @@ function checkPrimaryKey( art ) {
41
44
  if (member.key || parentIsKey) {
42
45
  const finalBaseType = this.csnUtils.getFinalTypeInfo(member.type);
43
46
  if (isGeoTypeName(finalBaseType?.type)) {
44
- this.error(null, parentPath || member.$path,
45
- { type: finalBaseType.type, name: elemFqName },
46
- 'Type $(TYPE) can\'t be used as primary key in element $(NAME)');
47
+ this.error('ref-unsupported-type', parentPath || member.$path,
48
+ { '#': 'key', type: finalBaseType.type, name: elemFqName });
47
49
  }
48
50
  else if (finalBaseType && this.csnUtils.isStructured(finalBaseType) && !finalBaseType.$visited) {
49
51
  setProp(finalBaseType, '$visited', true);
@@ -105,7 +107,7 @@ function checkPrimaryKey( art ) {
105
107
  function checkVirtualElement( member ) {
106
108
  if (member.virtual) {
107
109
  if (this.csnUtils.isAssocOrComposition(member))
108
- this.error(null, member.$path, {}, 'Element can\'t be virtual and an association or composition');
110
+ this.error('def-unexpected-virtual', member.$path, { keyword: 'virtual' });
109
111
  }
110
112
  }
111
113
 
@@ -119,12 +121,31 @@ function checkVirtualElement( member ) {
119
121
  function checkManagedAssoc( member ) {
120
122
  if (!member.target || isManagedComposition.bind(this)(member))
121
123
  return;
124
+
125
+ const targetMax = member.cardinality?.max ?? 1;
126
+ if (targetMax === 1 || member.on)
127
+ return;
128
+
129
+ const isPersisted = !hasPersistenceSkipAnnotation(this.artifact) && !this.artifact['@cds.persistence.exists'];
130
+ if (isPersisted && !member.keys && (targetMax === '*' || Number(targetMax) > 1) && this.options.transformation === 'sql') {
131
+ // Since cds-compiler v6, managed to-many no longer get 'keys'.
132
+ // As this would lead to DROP COLUMNs, emit an error instead.
133
+ this.error('type-missing-on-condition', member.cardinality?.$path || member.$path, {
134
+ value: cardinality2str(member, false),
135
+ '#': this.csnUtils.isComposition(member) ? 'comp' : 'std',
136
+ }, {
137
+ // same as 'to-many-no-on', but as error
138
+ std: 'Expected association with target cardinality $(VALUE) to have an ON-condition',
139
+ comp: 'Expected composition with target cardinality $(VALUE) to have an ON-condition',
140
+ });
141
+ }
142
+
122
143
  // Implementation note: Imported services (i.e. external ones) may contain to-many associations
123
144
  // with an empty foreign key list. If the user (in this case importer) explicitly sets an empty
124
145
  // foreign key array, we won't emit a warning to avoid spamming the user.
125
- const max = member.cardinality?.max ?? 1;
126
- if (max === 1 || member.on || !member.keys || member.keys.length === 0)
146
+ if (!member.keys || member.keys.length === 0)
127
147
  return;
148
+
128
149
  // We use the fact that `key` is only supported top-level (warning otherwise).
129
150
  // And if an element of a structured _type_ is "key", we get a warning for the key.
130
151
  // However, we may get false negatives for our warning, which is acceptable, e.g. for
@@ -135,8 +156,7 @@ function checkManagedAssoc( member ) {
135
156
  if (!coversAllTargetKeys.call(this, foreignKeys, targetKeys))
136
157
  return; // foreign key does not cover at least one target key -> can be to-many
137
158
 
138
- const isNoDb = this.artifact['@cds.persistence.skip'] || this.artifact['@cds.persistence.exists'];
139
- this.warning(isNoDb ? 'to-many-no-on-noDB' : 'to-many-no-on', member.cardinality?.$path || member.$path, {
159
+ this.warning(!isPersisted ? 'to-many-no-on-noDB' : 'to-many-no-on', member.cardinality?.$path || member.$path, {
140
160
  value: cardinality2str(member, false),
141
161
  '#': this.csnUtils.isComposition(member) ? 'comp' : 'std',
142
162
  }, {
@@ -256,7 +276,7 @@ function checkRecursiveTypeUsage( art ) {
256
276
  if (!isDeref && this.options.odataVersion === 'v4' && this.options.odataFormat === 'structured')
257
277
  return;
258
278
  if (!def.$recErr) {
259
- this.error(null, loc, {}, 'Unexpected recursive type definition');
279
+ this.error('ref-cyclic', loc, { '#': 'type', type: type ?? prevType });
260
280
  setProp(def, '$recErr', true);
261
281
  }
262
282
  }
@@ -0,0 +1,31 @@
1
+ 'use strict';
2
+
3
+ const { ModelError } = require('../base/error');
4
+
5
+ /**
6
+ * Removes the `enum` property - the compiler already resolved them.
7
+ *
8
+ * @param {Object} parent
9
+ */
10
+ function removeEnum( parent ) {
11
+ delete parent.enum;
12
+ }
13
+
14
+ /**
15
+ * Check if '#' has a `val` property. If val is undefined, an error is logged as this means that the compiler could not resolve the reference.
16
+ *
17
+ * If the reference was resolved, remove # and keep the .val.
18
+ *
19
+ * @param {Object} parent
20
+ */
21
+ function checkAndRemoveHash( parent ) {
22
+ if (parent.val === undefined)
23
+ throw new ModelError('Expected value to be resolved by the compiler - throwing exception to trigger recompilation.');
24
+
25
+ delete parent['#'];
26
+ }
27
+
28
+ module.exports = {
29
+ enum: removeEnum,
30
+ '#': checkAndRemoveHash,
31
+ };
@@ -42,7 +42,7 @@ function validateForeignKeys( member, memberName ) {
42
42
  // Declared as arrow-function to keep scope the same (this value)
43
43
  const checkForItemsOrMissingType = (mem, memName) => {
44
44
  if (mem.items) {
45
- this.error(null, member.$path, {}, 'Array-like properties must not be foreign keys');
45
+ this.error('type-invalid-foreign-key', member.$path, { '#': 'items' });
46
46
  }
47
47
  else if (mem.keys) {
48
48
  handleAssociation(mem);
@@ -52,7 +52,7 @@ function validateForeignKeys( member, memberName ) {
52
52
  }
53
53
  else if (mem.type) {
54
54
  if (mem.type === 'cds.Map') {
55
- this.error(null, member.$path, { type: mem.type }, 'Unexpected type $(TYPE) in foreign key');
55
+ this.error('type-invalid-foreign-key', member.$path, { '#': 'std', type: mem.type } );
56
56
  }
57
57
  else {
58
58
  const type = mem.type.ref
@@ -35,6 +35,11 @@ function hasRealElements( elements ) {
35
35
  if (hasRealElements(element.elements))
36
36
  return true;
37
37
  }
38
+ else if (element.target) {
39
+ if (element.keys?.length > 0)
40
+ return true;
41
+ // else: either unmanaged or no keys
42
+ }
38
43
  else {
39
44
  return true;
40
45
  }
@@ -24,7 +24,7 @@ function invalidTarget( member ) {
24
24
  const checkForInvalidTarget = (mem) => {
25
25
  if (mem.target) {
26
26
  const target = this.csn.definitions[mem.target];
27
- if (!target)
27
+ if (!target) // `[object Object]` → anonymous target aspect
28
28
  throw new ModelError(`Expected target ${ mem.target }`);
29
29
  if (target.kind !== 'entity') {
30
30
  const isAssoc = this.csnUtils.getFinalTypeInfo(member.type)?.type !== 'cds.Composition';
@@ -1,18 +1,19 @@
1
1
  'use strict';
2
2
 
3
-
4
3
  const { ModelError } = require('../base/error');
5
4
 
6
5
  /**
7
- * Trigger a recompilation in case of an association without .keys and without .on
6
+ * Trigger a recompilation in case of an to-one association without .keys and without .on
8
7
  *
9
8
  * @param {CSN.Element} member the element to be checked
10
9
  * @param {string} memberName the elements name
11
10
  * @param {string} prop which kind of member are we looking at -> only prop "elements"
12
11
  */
13
12
  function managedWithoutKeys( member, memberName, prop ) {
14
- if (prop === 'elements' && member.target && !member.keys && !member.on) { // trigger recompilation
15
- throw new ModelError('Expected association to have either an on-condition or foreign keys.');
13
+ if (prop === 'elements' && member.target && !member.keys && !member.on) {
14
+ const targetMax = member.cardinality?.max;
15
+ if (!targetMax || targetMax === 1)
16
+ throw new ModelError('Expected association to have either an on-condition or foreign keys.');
16
17
  }
17
18
  }
18
19
 
@@ -1,6 +1,6 @@
1
1
  'use strict';
2
2
 
3
- const { hasAnnotationValue, isPersistedOnDatabase } = require('../model/csnUtils');
3
+ const { isPersistedOnDatabase, hasPersistenceSkipAnnotation } = require('../model/csnUtils');
4
4
  const { isBuiltinType } = require('../base/builtins');
5
5
  const { requireForeignKeyAccess } = require('./onConditions');
6
6
  const { pathId } = require('../model/csnRefs');
@@ -22,7 +22,7 @@ const generalQueryProperties = [ 'from', 'columns', 'where', 'groupBy', 'orderBy
22
22
  * @param {CSN.Query} query Query to check
23
23
  */
24
24
  function checkQueryForNoDBArtifacts( query ) {
25
- if (isPersistedOnDatabase(this.artifact) && !hasAnnotationValue(this.artifact, '@cds.persistence.table')) {
25
+ if (isPersistedOnDatabase(this.artifact) && !this.artifact['@cds.persistence.table']) {
26
26
  for (const prop of generalQueryProperties) {
27
27
  const queryPart = (query.SELECT || query.SET)[prop];
28
28
  if (Array.isArray(queryPart)) {
@@ -164,7 +164,7 @@ function _checkRef( ref, _links, $path, inColumns ) {
164
164
  }
165
165
 
166
166
  if (isJoinRelevant) {
167
- const cdsPersistenceSkipped = hasAnnotationValue(targetArt, '@cds.persistence.skip');
167
+ const cdsPersistenceSkipped = hasPersistenceSkipAnnotation(targetArt);
168
168
  this.error( null, $path, {
169
169
  '#': cdsPersistenceSkipped ? 'std' : 'abstract',
170
170
  anno: '@cds.persistence.skip',
@@ -180,11 +180,7 @@ function _checkRef( ref, _links, $path, inColumns ) {
180
180
  }
181
181
 
182
182
  // check managed association to have foreign keys array filled
183
- if (art.keys && !hasForeignKeys.call(this, art)) {
184
- this.error('expr-missing-foreign-key', $path, { id: pathStep, elemref: { ref } } );
185
- break; // only one error per path
186
- }
187
- else if (art.on) {
183
+ if (art.target && art.on) {
188
184
  for (let j = 0; j < art.on.length - 2; j++) {
189
185
  if (art.on[j].ref && art.on[j + 1] === '=' && art.on[j + 2].ref) {
190
186
  const [ fwdAssoc, fwdPath ] = getForwardAssociation(pathStep, art.on[j], art.on[j + 2]);
@@ -196,6 +192,12 @@ function _checkRef( ref, _links, $path, inColumns ) {
196
192
  }
197
193
  }
198
194
  }
195
+ else if (art.target && !hasForeignKeys.call(this, art)) {
196
+ // Either no 'keys' array or an empty one. Since v6, to-many associations
197
+ // may have neither ON-condition nor foreign keys.
198
+ this.error('expr-missing-foreign-key', $path, { id: pathStep, elemref: { ref } } );
199
+ break; // only one error per path
200
+ }
199
201
  }
200
202
  }
201
203
 
@@ -1,9 +1,9 @@
1
1
  'use strict';
2
2
 
3
- const { hasAnnotationValue } = require('../model/csnUtils');
4
-
5
3
  // Only to be used with validator.js - a correct this value needs to be provided!
6
4
 
5
+ const { hasPersistenceSkipAnnotation } = require('../model/csnUtils');
6
+
7
7
  /**
8
8
  * Scale must not be 'variable' or 'floating'
9
9
  *
@@ -15,9 +15,9 @@ const { hasAnnotationValue } = require('../model/csnUtils');
15
15
  * @param {CSN.Path} path the path to the member
16
16
  */
17
17
  function checkDecimalScale( member, memberName, prop, path ) {
18
- if (hasAnnotationValue(this.artifact, '@cds.persistence.exists') ||
19
- // skip is already filtered in validator, here for completeness
20
- hasAnnotationValue(this.artifact, '@cds.persistence.skip'))
18
+ if (this.artifact['@cds.persistence.exists'] ||
19
+ // skip is already filtered in validator, here for completeness
20
+ hasPersistenceSkipAnnotation(this.artifact))
21
21
  return;
22
22
  if (member.scale && (member.scale === 'variable' || member.scale === 'floating'))
23
23
  this.error(null, path, { name: member.scale }, 'Unexpected scale $(NAME)');
@@ -2,8 +2,8 @@
2
2
 
3
3
  const {
4
4
  forEachDefinition, forEachMemberRecursively, forAllQueries,
5
- forEachMember, getNormalizedQuery, hasAnnotationValue,
6
- applyTransformations, functionList, mergeTransformers,
5
+ forEachMember, getNormalizedQuery,
6
+ applyTransformations, functionList, mergeTransformers, hasPersistenceSkipAnnotation,
7
7
  } = require('../model/csnUtils');
8
8
  const enrichCsn = require('./enricher');
9
9
 
@@ -17,6 +17,7 @@ const validateHasPersistedElements = require('./hasPersistedElements');
17
17
  const checkForHanaTypes = require('./checkForTypes');
18
18
  const { checkAnnotationExpression } = require('./structuredAnnoExpressions');
19
19
  const checkForParams = require('./parameters');
20
+ const checkAndRemoveEnums = require('./enums');
20
21
  // forOdata
21
22
  const { validateDefaultValues } = require('./defaultValues');
22
23
  const { checkActionOrFunction } = require('./actionsFunctions');
@@ -90,6 +91,7 @@ const forRelationalDBCsnValidators = [
90
91
  navigationIntoMany,
91
92
  checkPathsInStoredCalcElement,
92
93
  featureFlags,
94
+ checkAndRemoveEnums,
93
95
  ];
94
96
  /**
95
97
  * @type {Array<(query: CSN.Query, path: CSN.Path) => void>}
@@ -239,8 +241,8 @@ function forRelationalDB( csn, that ) {
239
241
  forRelationalDBQueryValidators.concat(commonQueryValidators),
240
242
  {
241
243
  skipArtifact: artifact => artifact.abstract ||
242
- hasAnnotationValue(artifact, '@cds.persistence.skip') ||
243
- hasAnnotationValue(artifact, '@cds.persistence.exists') ||
244
+ hasPersistenceSkipAnnotation(artifact) ||
245
+ artifact['@cds.persistence.exists'] ||
244
246
  [ 'action', 'function', 'event' ].includes(artifact.kind),
245
247
  });
246
248
  }
@@ -261,7 +261,12 @@ function assertConsistency( model, stage ) {
261
261
  $contains: { kind: true, test: TODO },
262
262
  actions: { kind: true, inherits: 'definitions' },
263
263
  enum: { kind: true, inherits: 'definitions' },
264
- foreignKeys: { kind: true, inherits: 'definitions', instanceOf: 'ignore' },
264
+ foreignKeys: {
265
+ kind: true,
266
+ inherits: 'definitions',
267
+ instanceOf: 'ignore',
268
+ also: [ false ],
269
+ },
265
270
  $keysNavigation: { kind: true, test: TODO },
266
271
  $filtered: { kind: true, inherits: 'value' }, // for assoc+filter
267
272
  $enclosed: { kind: true, inherits: 'value' }, // for comp+filter
@@ -334,7 +339,7 @@ function assertConsistency( model, stage ) {
334
339
  test: isArray( column ),
335
340
  instanceOf: XsnArtifact,
336
341
  optional: thoseWithKind,
337
- enum: [ '*' ],
342
+ enum: [ '*', '**' ], // '**' = duplicate wildcard
338
343
  requires: [ 'location' ],
339
344
  // schema: { kind: { isRequired: () => {} } } // kind not required
340
345
  },
@@ -433,9 +438,6 @@ function assertConsistency( model, stage ) {
433
438
  optional: [
434
439
  'location', '$inferred', 'sort', 'nulls',
435
440
  'param', 'scope', // for dynamic parameter '?'
436
- // before recompilation for A2J, named types are replaced by the base
437
- // definition (TODO v6 remove, we can probably remove elements+items now)
438
- 'elements', 'items', 'enum',
439
441
  'args', 'op', 'func', 'suffix',
440
442
  // calculated elements on-write - TODO: outside `value`
441
443
  'stored',
@@ -472,7 +474,6 @@ function assertConsistency( model, stage ) {
472
474
  '$parens',
473
475
  '_artifact', // _artifact with "localized data"s 'coalesce'
474
476
  'sort', 'nulls', // if used in GROUP BY
475
- 'enum', // for CAST in A2J recompilation (TODO v6: remove)
476
477
  // `elements` of type is not put into cast, many will be cds.LargeString
477
478
  ...typeProperties, // for CAST
478
479
  ],
@@ -877,9 +878,11 @@ function assertConsistency( model, stage ) {
877
878
  if (node !== null || noSyntaxErrors()) {
878
879
  isObject( node, parent, prop, spec, idx );
879
880
 
880
- // eslint-disable-next-line no-nested-ternary
881
- const choice = (node.path) ? 'ref' : (node.join) ? 'join'
882
- : (node.query) ? 'query' : 'none';
881
+ const choice = (!noSyntaxErrors()) ? 'none'
882
+ : (node.path) && 'ref' ||
883
+ (node.join) && 'join' ||
884
+ (node.query) && 'query' ||
885
+ 'none';
883
886
  if (spec[choice])
884
887
  assertProp( node, parent, prop, spec[choice], choice );
885
888
  else