@sap/cds-compiler 4.7.6 → 4.9.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 (129) hide show
  1. package/CHANGELOG.md +63 -3
  2. package/bin/cds_remove_invalid_whitespace.js +135 -0
  3. package/bin/cds_update_annotations.js +180 -0
  4. package/bin/cds_update_identifiers.js +3 -4
  5. package/bin/cdsc.js +28 -1
  6. package/bin/cdshi.js +13 -3
  7. package/doc/CHANGELOG_BETA.md +24 -1
  8. package/lib/api/main.js +119 -46
  9. package/lib/api/options.js +51 -0
  10. package/lib/api/validate.js +1 -5
  11. package/lib/base/builtins.js +116 -0
  12. package/lib/base/keywords.js +5 -1
  13. package/lib/base/location.js +91 -14
  14. package/lib/base/message-registry.js +76 -46
  15. package/lib/base/messages.js +121 -35
  16. package/lib/base/model.js +4 -7
  17. package/lib/checks/actionsFunctions.js +3 -3
  18. package/lib/checks/annotationsOData.js +3 -0
  19. package/lib/checks/defaultValues.js +5 -2
  20. package/lib/checks/elements.js +2 -1
  21. package/lib/checks/enricher.js +2 -2
  22. package/lib/checks/queryNoDbArtifacts.js +5 -3
  23. package/lib/checks/utils.js +1 -1
  24. package/lib/checks/validator.js +8 -56
  25. package/lib/compiler/assert-consistency.js +11 -7
  26. package/lib/compiler/builtins.js +0 -74
  27. package/lib/compiler/checks.js +105 -29
  28. package/lib/compiler/define.js +37 -25
  29. package/lib/compiler/extend.js +35 -12
  30. package/lib/compiler/index.js +9 -10
  31. package/lib/compiler/lsp-api.js +5 -0
  32. package/lib/compiler/populate.js +13 -5
  33. package/lib/compiler/propagator.js +24 -18
  34. package/lib/compiler/resolve.js +47 -45
  35. package/lib/compiler/shared.js +61 -21
  36. package/lib/compiler/tweak-assocs.js +15 -90
  37. package/lib/compiler/utils.js +3 -3
  38. package/lib/compiler/xpr-rewrite.js +689 -0
  39. package/lib/compiler/{classes.js → xsn-model.js} +0 -16
  40. package/lib/edm/annotations/edmJson.js +7 -5
  41. package/lib/edm/annotations/genericTranslation.js +149 -71
  42. package/lib/edm/csn2edm.js +25 -9
  43. package/lib/edm/edm.js +7 -7
  44. package/lib/edm/edmInboundChecks.js +57 -5
  45. package/lib/edm/edmPreprocessor.js +54 -25
  46. package/lib/edm/edmUtils.js +3 -16
  47. package/lib/gen/Dictionary.json +138 -14
  48. package/lib/gen/language.checksum +1 -1
  49. package/lib/gen/language.interp +1 -1
  50. package/lib/gen/languageParser.js +2085 -1989
  51. package/lib/json/csnVersion.js +7 -4
  52. package/lib/json/from-csn.js +21 -11
  53. package/lib/json/to-csn.js +8 -4
  54. package/lib/language/antlrParser.js +1 -1
  55. package/lib/language/genericAntlrParser.js +23 -16
  56. package/lib/language/multiLineStringParser.js +2 -2
  57. package/lib/language/textUtils.js +1 -1
  58. package/lib/main.d.ts +90 -14
  59. package/lib/main.js +9 -1
  60. package/lib/model/cloneCsn.js +21 -9
  61. package/lib/model/csnRefs.js +153 -42
  62. package/lib/model/csnUtils.js +14 -11
  63. package/lib/model/enrichCsn.js +4 -2
  64. package/lib/model/revealInternalProperties.js +2 -1
  65. package/lib/model/sortViews.js +14 -6
  66. package/lib/modelCompare/compare.js +135 -122
  67. package/lib/optionProcessor.js +49 -2
  68. package/lib/render/DuplicateChecker.js +6 -6
  69. package/lib/render/manageConstraints.js +1 -0
  70. package/lib/render/toCdl.js +6 -3
  71. package/lib/render/toHdbcds.js +4 -48
  72. package/lib/render/toSql.js +6 -3
  73. package/lib/transform/addTenantFields.js +58 -35
  74. package/lib/transform/db/applyTransformations.js +34 -1
  75. package/lib/transform/db/constraints.js +1 -1
  76. package/lib/transform/db/expansion.js +11 -3
  77. package/lib/transform/db/flattening.js +71 -46
  78. package/lib/transform/db/groupByOrderBy.js +2 -2
  79. package/lib/transform/db/temporal.js +6 -3
  80. package/lib/transform/db/transformExists.js +2 -2
  81. package/lib/transform/db/views.js +1 -4
  82. package/lib/transform/effective/annotations.js +194 -0
  83. package/lib/transform/effective/main.js +11 -10
  84. package/lib/transform/effective/misc.js +45 -14
  85. package/lib/transform/effective/types.js +4 -3
  86. package/lib/transform/forOdata.js +29 -12
  87. package/lib/transform/forRelationalDB.js +104 -113
  88. package/lib/transform/localized.js +7 -6
  89. package/lib/transform/odata/flattening.js +228 -107
  90. package/lib/transform/odata/toFinalBaseType.js +10 -26
  91. package/lib/transform/odata/typesExposure.js +41 -25
  92. package/lib/transform/parseExpr.js +4 -7
  93. package/lib/transform/transformUtils.js +50 -43
  94. package/lib/transform/translateAssocsToJoins.js +48 -48
  95. package/lib/transform/universalCsn/coreComputed.js +2 -1
  96. package/lib/transform/universalCsn/universalCsnEnricher.js +12 -16
  97. package/package.json +2 -2
  98. package/share/messages/README.md +4 -0
  99. package/share/messages/anno-duplicate-unrelated-layer.md +1 -1
  100. package/share/messages/anno-missing-rewrite.md +45 -0
  101. package/share/messages/check-proper-type-of.md +1 -1
  102. package/share/messages/def-duplicate-autoexposed.md +1 -1
  103. package/share/messages/extend-repeated-intralayer.md +3 -16
  104. package/share/messages/extend-unrelated-layer.md +1 -1
  105. package/share/messages/message-explanations.json +2 -0
  106. package/share/messages/redirected-to-ambiguous.md +1 -1
  107. package/share/messages/redirected-to-complex.md +1 -1
  108. package/share/messages/redirected-to-unrelated.md +1 -1
  109. package/share/messages/rewrite-not-supported.md +1 -1
  110. package/share/messages/syntax-expecting-unsigned-int.md +2 -2
  111. package/share/messages/type-missing-enum-value.md +59 -0
  112. package/share/messages/wildcard-excluding-one.md +1 -1
  113. package/bin/.eslintrc.json +0 -17
  114. package/lib/api/.eslintrc.json +0 -37
  115. package/lib/checks/.eslintrc.json +0 -31
  116. package/lib/compiler/.eslintrc.json +0 -8
  117. package/lib/edm/.eslintrc.json +0 -46
  118. package/lib/inspect/.eslintrc.json +0 -4
  119. package/lib/json/.eslintrc.json +0 -4
  120. package/lib/language/.eslintrc.json +0 -4
  121. package/lib/model/.eslintrc.json +0 -13
  122. package/lib/modelCompare/utils/.eslintrc.json +0 -22
  123. package/lib/render/.eslintrc.json +0 -22
  124. package/lib/transform/.eslintrc.json +0 -13
  125. package/lib/transform/db/.eslintrc.json +0 -41
  126. package/lib/transform/draft/.eslintrc.json +0 -4
  127. package/lib/transform/effective/.eslintrc.json +0 -4
  128. package/lib/transform/universalCsn/.eslintrc.json +0 -37
  129. package/lib/utils/.eslintrc.json +0 -7
@@ -2,10 +2,11 @@
2
2
 
3
3
  const {
4
4
  getLastPartOf, getLastPartOfRef,
5
- hasValidSkipOrExists, isBuiltinType, generatedByCompilerVersion, getNormalizedQuery,
5
+ hasValidSkipOrExists, generatedByCompilerVersion, getNormalizedQuery,
6
6
  getRootArtifactName, getResultingName, getNamespace, forEachMember, getVariableReplacement, hasAnnotationValue,
7
- pathName, isMagicVariable,
7
+ pathName,
8
8
  } = require('../model/csnUtils');
9
+ const { isBuiltinType, isMagicVariable } = require('../base/builtins');
9
10
  const keywords = require('../base/keywords');
10
11
  const {
11
12
  renderFunc, createExpressionRenderer, getRealName, addContextMarkers, addIntermediateContexts,
@@ -315,49 +316,6 @@ function toHdbcdsSource( csn, options, messageFunctions ) {
315
316
  return false;
316
317
  }
317
318
 
318
- /* FIXME: Not yet required
319
- // Returns the artifact or element that constitutes the final type of
320
- // construct 'node', i.e. the object in which we would find type properties for
321
- // 'node'. Note that this may well be 'node' itself.
322
- function getFinalTypeOf(node) {
323
- if (node && node.type) {
324
- if (isBuiltinType(node.type)) {
325
- return node;
326
- }
327
- return getFinalTypeOf(node.type);
328
- }
329
- return node;
330
- }
331
-
332
- // Resolve path array 'ref' against artifact 'base' (or against 'csn.definitions'
333
- // if no 'base' given).
334
- // Return the resulting artifact or element (or 'undefined' if not found).
335
- function resolveRef(ref, base) {
336
- let result = base;
337
- for (let i = 0; i < ref.length; i++) {
338
- let pathStep = ref[i].id || ref[i];
339
- // Only first path step may be looked up in 'definitions'
340
- if (i === 0 && !base) {
341
- result = csn.definitions[pathStep];
342
- continue;
343
- }
344
- // Structured type
345
- else if (result && result.elements) {
346
- result = getFinalTypeOf(result.elements[pathStep]);
347
- }
348
- // Association
349
- else if (result && result.target) {
350
- result = resolveRef([pathStep], csn.definitions[result.target]);
351
- }
352
- // Not resolvable
353
- else {
354
- return undefined;
355
- }
356
- }
357
- return result;
358
- }
359
- */
360
-
361
319
  /**
362
320
  * Render a context or service. Return the resulting source string.
363
321
  *
@@ -1155,9 +1113,7 @@ function toHdbcdsSource( csn, options, messageFunctions ) {
1155
1113
  */
1156
1114
  function renderAssociationType( elm, env ) {
1157
1115
  // Type, cardinality and target
1158
- let result = 'association';
1159
-
1160
- result += `${renderCardinality(elm.cardinality)} to `;
1116
+ let result = `association${renderCardinality(elm.cardinality)} to `;
1161
1117
 
1162
1118
  // normal target or named aspect
1163
1119
  if (elm.target || elm.targetAspect && typeof elm.targetAspect === 'string') {
@@ -3,10 +3,11 @@
3
3
 
4
4
  const {
5
5
  getLastPartOf, getLastPartOfRef,
6
- hasValidSkipOrExists, isBuiltinType, generatedByCompilerVersion, getNormalizedQuery,
6
+ hasValidSkipOrExists, generatedByCompilerVersion, getNormalizedQuery,
7
7
  forEachDefinition, getResultingName,
8
- getVariableReplacement, isMagicVariable, pathName,
8
+ getVariableReplacement, pathName,
9
9
  } = require('../model/csnUtils');
10
+ const { isBuiltinType, isMagicVariable } = require('../base/builtins');
10
11
  const { forEach, forEachValue, forEachKey } = require('../utils/objectUtils');
11
12
  const {
12
13
  renderFunc, cdsToSqlTypes, getHanaComment, hasHanaComment,
@@ -740,7 +741,9 @@ function toSqlDdl( csn, options, messageFunctions ) {
740
741
  let result = `${env.indent + quotedElementName}${isPostgresAlterColumn ? ' TYPE' : ''} ${renderTypeReference(elm, env)
741
742
  }${renderNullability(elm, true, env.alterMode)}`;
742
743
  // calculated elements (on write) can't have a default; ignore it
743
- if (elm.default && !elm.value)
744
+ if (elm.$default && env.alterMode && !elm.value && options.sqlDialect !== 'postgres')
745
+ result += ` DEFAULT ${renderExpr(elm.$default, env.withSubPath([ '$default' ]))}`;
746
+ else if (elm.default && !elm.value)
744
747
  result += ` DEFAULT ${renderExpr(elm.default, env.withSubPath([ 'default' ]))}`;
745
748
 
746
749
  // Only SAP HANA has fuzzy indices
@@ -29,8 +29,9 @@ const tenantDef = {
29
29
  '@cds.api.ignore': true, // and/or $generated: 'tenant' for the full Universal CSN?
30
30
  };
31
31
 
32
- function addTenantFields( csn, options ) {
33
- const { error, throwWithError } = createMessageFunctions( options, 'tenant', csn );
32
+ function addTenantFields( csn, options, messageFunctions ) {
33
+ const { error, throwWithError }
34
+ = messageFunctions ?? createMessageFunctions( options, 'tenant', csn );
34
35
  const { tenantDiscriminator } = options;
35
36
  const tenantName = tenantDiscriminator === true ? 'tenant' : tenantDiscriminator;
36
37
  if (tenantName !== 'tenant') {
@@ -47,17 +48,21 @@ function addTenantFields( csn, options ) {
47
48
  const { definitions } = csn;
48
49
  if (!definitions)
49
50
  return csn;
50
- const { initDefinition, artifactRef, effectiveType } = csnRefs( csn );
51
+ const {
52
+ initDefinition,
53
+ artifactRef,
54
+ effectiveType,
55
+ msgLocations,
56
+ } = csnRefs( csn, true );
51
57
 
52
58
  const typeCache = new WeakMap();
53
- const csnPath = [ 'definitions', '' ];
59
+ const csnPath = [ null ];
54
60
  let independent;
55
61
  let projection;
56
62
 
57
63
  for (const name in definitions) {
58
- const art = definitions[name];
59
- initDefinition( art );
60
- csnPath[1] = name;
64
+ const art = initDefinition( name );
65
+ csnPath[0] = art;
61
66
  independent = art[annoTenantIndep];
62
67
  projection = art.query || art.projection && art;
63
68
 
@@ -68,9 +73,11 @@ function addTenantFields( csn, options ) {
68
73
  handleElements( art );
69
74
  if (projection)
70
75
  traverseQuery( projection, null, null, handleQuery );
76
+ // Note: handleQuery sets csnPath[0]; store if needed afterwards
71
77
  }
72
78
  else if (!independent && independent != null) {
73
- error( 'tenant-invalid-anno-value', csnPath, { anno: annoTenantIndep, value: independent },
79
+ error( 'tenant-invalid-anno-value', msgLocations( csnPath ),
80
+ { anno: annoTenantIndep, value: independent },
74
81
  // eslint-disable-next-line max-len
75
82
  'Can\'t add $(ANNO) with value $(VALUE) to a non-entity, which is always tenant-independent' );
76
83
  }
@@ -88,16 +95,15 @@ function addTenantFields( csn, options ) {
88
95
  // the cache of csnRefs):
89
96
  for (const name in definitions) {
90
97
  const art = definitions[name];
91
- if (isTenantDepEntity( art ))
92
- art.elements = { [tenantName]: { ...tenantDef }, ...art.elements };
93
- // consider non-enumerable `elements` of subqueries if that is supported
98
+ addTenantFieldToArt(art, options);
94
99
  }
95
100
 
96
101
  (csn.extensions || []).forEach( ( ext, idx ) => {
97
102
  const tenant = ext.elements?.[tenantName];
98
103
  const name = ext.annotate || ext.extend; // extend should not happen
99
104
  if (tenant && isTenantDepEntity( definitions[name] )) {
100
- error( 'tenant-unexpected-ext', [ 'extensions', idx, 'elements', 'tenant' ],
105
+ error( 'tenant-unexpected-ext',
106
+ msgLocations( [ 'extensions', idx, 'elements', 'tenant' ] ),
101
107
  { name: tenantName },
102
108
  'Can\'t annotate element $(NAME) of a tenant-dependent entity' );
103
109
  }
@@ -112,7 +118,7 @@ function addTenantFields( csn, options ) {
112
118
  const names = art.includes
113
119
  .filter( name => isTenantDepEntity( csn.definitions[name] ) );
114
120
  if (names.length) {
115
- error( 'tenant-invalid-include', csnPath, { names }, {
121
+ error( 'tenant-invalid-include', msgLocations( csnPath ), { names }, {
116
122
  // eslint-disable-next-line max-len
117
123
  std: 'Can\'t include the tenant-dependent entities $(NAMES) into a tenant-independent definition',
118
124
  // eslint-disable-next-line max-len
@@ -125,12 +131,13 @@ function addTenantFields( csn, options ) {
125
131
  function handleElements( art ) {
126
132
  const { elements } = art;
127
133
  if (elements[tenantName]) {
128
- error( 'tenant-unexpected-element', [ ...csnPath, 'elements', tenantName ],
134
+ error( 'tenant-unexpected-element',
135
+ msgLocations( [ ...csnPath, 'elements', tenantName ] ),
129
136
  { name: tenantName, option: 'tenantDiscriminator' },
130
137
  'Can\'t have entity with element $(NAME) when using option $(OPTION)' );
131
138
  }
132
139
  else if (!independent && !Object.values( elements ).some( e => e.key )) {
133
- error( 'tenant-expecting-key', csnPath, {},
140
+ error( 'tenant-expecting-key', msgLocations( csnPath ), {},
134
141
  'There must be a key in a tenant-dependent entity' );
135
142
  }
136
143
  else {
@@ -147,18 +154,16 @@ function addTenantFields( csn, options ) {
147
154
  return;
148
155
 
149
156
  if (query !== projection && !independent) {
150
- error( 'tenant-unsupported-query', csnPath, {},
157
+ error( 'tenant-unsupported-query', msgLocations( csnPath ), {},
151
158
  'Can\'t yet have tenant-dependent non-simple query entities' );
152
159
  projection = null;
153
160
  return;
154
161
  }
155
162
 
156
- if (query.projection)
157
- csnPath.push( 'projection' );
158
- else if (query.SELECT)
159
- csnPath.push( 'query', 'SELECT' );
160
- else
163
+ if (!query.projection && !query.SELECT)
161
164
  return; // query.SET or query.join
165
+ csnPath[0] = query;
166
+ csnPath.push( query.SELECT ? 'SELECT' : 'projection' );
162
167
 
163
168
  const select = query.SELECT || query.projection;
164
169
  if (select.mixin)
@@ -177,7 +182,7 @@ function addTenantFields( csn, options ) {
177
182
  else if (query !== projection && select.columns) {
178
183
  checkColumnCasts( select.columns );
179
184
  }
180
- csnPath.length = 2;
185
+ csnPath.length = 1;
181
186
  }
182
187
 
183
188
  function handleQuerySource( query ) {
@@ -185,7 +190,7 @@ function addTenantFields( csn, options ) {
185
190
  const art = query.ref[0]; // yes, the base
186
191
  if (csn.definitions[art][annoTenantIndep])
187
192
  return true;
188
- error( 'tenant-invalid-query-source', csnPath, { art, '#': independent }, {
193
+ error( 'tenant-invalid-query-source', msgLocations( csnPath ), { art, '#': independent }, {
189
194
  std: 'Can\'t use a tenant-dependent query source $(ART) in a tenant-independent entity',
190
195
  event: 'Can\'t use a tenant-dependent query source $(ART) in an event',
191
196
  } );
@@ -194,12 +199,12 @@ function addTenantFields( csn, options ) {
194
199
  if (query !== (projection.SELECT || projection.projection)?.from) // with `join`
195
200
  return false;
196
201
  if ((query.as || implicitAs( query.ref )) === tenantName) {
197
- error( 'tenant-invalid-alias-name', csnPath,
202
+ error( 'tenant-invalid-alias-name', msgLocations( csnPath ),
198
203
  { name: tenantName, '#': (query.as ? 'std' : 'implicit') } );
199
204
  }
200
205
  const art = artifactRef.from( query );
201
206
  if (art[annoTenantIndep]) {
202
- error( 'tenant-expecting-tenant-source', csnPath, { art: query },
207
+ error( 'tenant-expecting-tenant-source', msgLocations( csnPath ), { art: query },
203
208
  // TODO: better the final entity name of assoc navigation in FROM
204
209
  // eslint-disable-next-line max-len
205
210
  'Expecting the query source $(ART) to be tenant-dependent for a tenant-dependent query entity' );
@@ -212,7 +217,7 @@ function addTenantFields( csn, options ) {
212
217
  for (const name in mixin) {
213
218
  csnPath[csnPath.length - 1] = name;
214
219
  if (name === tenantName && !independent)
215
- error( 'tenant-invalid-alias-name', csnPath, { name, '#': 'mixin' } );
220
+ error( 'tenant-invalid-alias-name', msgLocations( csnPath ), { name, '#': 'mixin' } );
216
221
  handleAssociations( mixin[name], null );
217
222
  }
218
223
  csnPath.length -= 2;
@@ -220,7 +225,7 @@ function addTenantFields( csn, options ) {
220
225
 
221
226
  function checkExcluding( excludeList ) {
222
227
  if (excludeList.includes( tenantName )) {
223
- error( 'tenant-invalid-excluding', csnPath, { name: tenantName },
228
+ error( 'tenant-invalid-excluding', msgLocations( csnPath ), { name: tenantName },
224
229
  'Can\'t exclude $(NAME) from the query source of a tenant-dependent entity' );
225
230
  }
226
231
  }
@@ -241,7 +246,7 @@ function addTenantFields( csn, options ) {
241
246
  for (const col of columns) {
242
247
  ++csnPath[csnPath.length - 1];
243
248
  if (col.expand || col.inline) {
244
- error( 'tenant-unsupported-expand-inline', csnPath, {},
249
+ error( 'tenant-unsupported-expand-inline', msgLocations( csnPath ), {},
245
250
  'Can\'t use expand/inline in a tenant-dependent entity' );
246
251
  }
247
252
  if (col.key != null) // yes, also with key: false
@@ -275,12 +280,20 @@ function addTenantFields( csn, options ) {
275
280
  return null;
276
281
 
277
282
  if (elem.target) {
278
- if (!csn.definitions[elem.target][annoTenantIndep]) {
279
- if (independent)
280
- error( 'tenant-invalid-target', csnPath, { target: elem.target } );
283
+ const { target } = elem;
284
+ if (csn.definitions[target][annoTenantIndep]) {
285
+ if (!independent && isComposition( elem ))
286
+ error( 'tenant-invalid-composition', msgLocations( csnPath ), { target } );
281
287
  }
282
- else if (!independent && isComposition( elem )) {
283
- error( 'tenant-invalid-composition', csnPath, { target: elem.target } );
288
+ else if (independent) {
289
+ if (target.endsWith( '.DraftAdministrativeData' ) && csnPath.length === 3 &&
290
+ csnPath[1] === 'elements' && csnPath[2] === 'DraftAdministrativeData') {
291
+ error( 'tenant-invalid-draft', msgLocations( csnPath ), {},
292
+ 'A tenant-independent entity can\'t be draft-enabled' );
293
+ }
294
+ else {
295
+ error( 'tenant-invalid-target', msgLocations( csnPath ), { target } );
296
+ }
284
297
  }
285
298
  }
286
299
  else if (elem.type && (independent || !elem.elements && !elem.items)) {
@@ -290,10 +303,11 @@ function addTenantFields( csn, options ) {
290
303
  if (independent) {
291
304
  if (!dep || dep === 'Composition')
292
305
  return true; // check elements (assocs could be redirected)
293
- error( 'tenant-invalid-target', csnPath, { type: elem.type, '#': 'type' } );
306
+ error( 'tenant-invalid-target', msgLocations( csnPath ), { type: elem.type, '#': 'type' } );
294
307
  }
295
308
  else if (dep && dep !== 'dependent') {
296
- error( 'tenant-invalid-composition', csnPath, { type: elem.type, '#': 'type' } );
309
+ error( 'tenant-invalid-composition', msgLocations( csnPath ),
310
+ { type: elem.type, '#': 'type' } );
297
311
  }
298
312
  }
299
313
  else {
@@ -398,6 +412,15 @@ function isTenantDepEntity( art ) {
398
412
  return art?.kind === 'entity' && !art[annoTenantIndep];
399
413
  }
400
414
 
415
+ function addTenantFieldToArt( art, options ) {
416
+ const tenantName = options.tenantDiscriminator === true ? 'tenant' : options.tenantDiscriminator;
417
+
418
+ if (isTenantDepEntity( art ))
419
+ art.elements = { [tenantName]: { ...tenantDef }, ...art.elements };
420
+ // consider non-enumerable `elements` of subqueries if that is supported
421
+ }
422
+
401
423
  module.exports = {
402
424
  addTenantFields,
425
+ addTenantFieldToArt,
403
426
  };
@@ -13,7 +13,7 @@
13
13
 
14
14
 
15
15
  const { setProp } = require('../../base/model');
16
- const { isAnnotationExpression } = require('../../compiler/builtins');
16
+ const { isAnnotationExpression } = require('../../base/builtins');
17
17
 
18
18
 
19
19
  /**
@@ -344,7 +344,40 @@ function transformExpression( parent, propName, transformers, path = [] ) {
344
344
  return parent;
345
345
  }
346
346
 
347
+ /**
348
+ * Merge an array of transformer-objects into a single one, set the this-value of every subfunction to "that"
349
+ *
350
+ * @param {object[]} transformers transformers
351
+ * @param {object} that Value for this
352
+ * @returns {object} Remapped transformers.
353
+ */
354
+ function mergeTransformers( transformers, that ) {
355
+ const remapped = {};
356
+ for (const transformer of transformers) {
357
+ for (const [ n, fns ] of Object.entries(transformer)) {
358
+ if (!remapped[n])
359
+ remapped[n] = [];
360
+
361
+ if (Array.isArray(fns)) {
362
+ remapped[n].push((parent, name, prop, path, parentParent) => fns.forEach(
363
+ fn => fn.bind(that)(parent, name, prop, path, parentParent)
364
+ ));
365
+ }
366
+ else {
367
+ remapped[n].push((parent, name, prop, path, parentParent) => fns.bind(that)(parent, name, prop, path, parentParent));
368
+ }
369
+ }
370
+ }
371
+
372
+ for (const [ n, fns ] of Object.entries(remapped))
373
+ remapped[n] = (parent, name, prop, path, parentParent) => fns.forEach(fn => fn.bind(that)(parent, name, prop, path, parentParent));
374
+
375
+
376
+ return remapped;
377
+ }
378
+
347
379
  module.exports = {
380
+ mergeTransformers,
348
381
  transformExpression,
349
382
  applyTransformations,
350
383
  applyTransformationsOnNonDictionary,
@@ -116,7 +116,7 @@ function createReferentialConstraints( csn, options ) {
116
116
  foreignKeyConstraintForAssociation(up_, dependent, [ 'definitions', composition.target, 'elements', upLinkName ], path[path.length - 1] );
117
117
  }
118
118
  else if (!onCondition && composition.keys.length > 0) {
119
- throw new CompilerAssertion('Please debug me, an on-condition was expected here, but only found keys');
119
+ throw new CompilerAssertion('Debug me, an on-condition was expected here, but only found keys');
120
120
  }
121
121
  }
122
122
 
@@ -58,6 +58,9 @@ function expandStructureReferences( csn, options, pathDelimiter, messageFunction
58
58
  orderBy: (parent, name, orderBy, path) => {
59
59
  parent.orderBy = expand(orderBy, path.concat('orderBy'));
60
60
  },
61
+ list: (parent, name, list, path) => {
62
+ parent.list = expand(list, path.concat('list'));
63
+ },
61
64
  };
62
65
 
63
66
  // To not have a whole model loop for such a "small" thing, we kill all non-sql-backend relevant annotations here
@@ -95,7 +98,9 @@ function expandStructureReferences( csn, options, pathDelimiter, messageFunction
95
98
  */
96
99
  if (rewritten.toMany.length > 0 && !options.toOdata) {
97
100
  markAsToDummify(artifact, path[1]);
98
- error( null, [ 'definitions', path[1] ], { name: path[1] }, 'Unexpected .expand with to-many association in entity $(NAME)');
101
+ rewritten.toMany.forEach(({ art }) => {
102
+ error( null, art.$path || [ 'definitions', path[1] ], { name: `${art.$env || path[1]}:${art.ref.map(r => r.id || r)}` }, 'Unexpected .expand with to-many association $(NAME)');
103
+ });
99
104
  }
100
105
  else {
101
106
  parent.columns = rewritten.columns;
@@ -172,8 +177,11 @@ function expandStructureReferences( csn, options, pathDelimiter, messageFunction
172
177
  const target = art.target ? art.target : pathStep;
173
178
  if (toDummify.indexOf(target) !== -1) {
174
179
  error( null, obj.$path, {
175
- id: pathStep, elemref: obj, name,
176
- }, 'Unexpected “@cds.persistence.skip” annotation on Association target $(NAME) of $(ID) in path $(ELEMREF) was skipped because of .expand in conjunction with to-many');
180
+ id: pathStep,
181
+ elemref: obj,
182
+ name,
183
+ anno: '@cds.persistence.skip',
184
+ }, 'Unexpected $(ANNO) annotation on Association target $(NAME) of $(ID) in path $(ELEMREF) was skipped because of .expand in conjunction with to-many');
177
185
  }
178
186
  }
179
187
 
@@ -1,38 +1,42 @@
1
1
  'use strict';
2
2
 
3
3
  const {
4
- applyTransformations, applyTransformationsOnNonDictionary,
5
- isBuiltinType, cardinality2str,
6
- copyAnnotations, implicitAs, isDeepEqual, findAnnotationExpression,
4
+ applyTransformations,
5
+ applyTransformationsOnNonDictionary,
6
+ cardinality2str,
7
+ copyAnnotations,
8
+ implicitAs,
9
+ isDeepEqual,
10
+ findAnnotationExpression,
7
11
  } = require('../../model/csnUtils');
12
+ const { isBuiltinType, isMagicVariable } = require('../../base/builtins');
8
13
  const transformUtils = require('../transformUtils');
9
14
  const { csnRefs } = require('../../model/csnRefs');
10
15
  const { setProp, isBetaEnabled } = require('../../base/model');
11
16
  const { forEach } = require('../../utils/objectUtils');
12
17
  const { transformExpression } = require('./applyTransformations');
13
18
  const { cloneCsnNonDict } = require('../../model/cloneCsn');
19
+
14
20
  /**
15
- * Strip off leading $self from refs where applicable
21
+ * Strip off leading $self from refs where applicable.
22
+ * Only relevant for HDBCDS, because handling of `$self` is not implemented there.
16
23
  *
17
- * @param {CSN.Model} csn
24
+ * @param {object} parent
25
+ * @param {string} prop
26
+ * @param {CSN.Elements} elements
18
27
  */
19
- function removeLeadingSelf( csn ) {
20
- const magicVars = [ '$now', '$self', '$projection', '$user', '$tenant', '$session', '$at' ];
21
- applyTransformations(csn, {
22
- elements: (parent, prop, elements) => {
23
- for (const [ elementName, element ] of Object.entries(elements)) {
24
- if (element.on) {
25
- applyTransformationsOnNonDictionary(elements, elementName, {
26
- ref: (root, name, ref) => {
27
- // Renderers seem to expect it to not be there...
28
- if (ref[0] === '$self' && ref.length > 1 && !magicVars.includes(ref[1]))
29
- root.ref = ref.slice(1);
30
- },
31
- });
32
- }
33
- }
34
- }, /* only for kind entity and view */ /* do not go into .actions */
35
- }, [], { skipIgnore: false, allowArtifact: artifact => (artifact.kind === 'entity'), skipDict: { actions: true } });
28
+ function removeLeadingSelf( parent, prop, elements ) {
29
+ for (const [ elementName, element ] of Object.entries(elements)) {
30
+ if (element.on) {
31
+ applyTransformationsOnNonDictionary(elements, elementName, {
32
+ ref: (root, name, ref) => {
33
+ // HDBCDS renderers seem to expect it to not be there...
34
+ if (ref[0] === '$self' && ref.length > 1 && !isMagicVariable(ref[1]) && ref[1] !== '$projection' && ref[1] !== '$self')
35
+ root.ref.shift();
36
+ },
37
+ });
38
+ }
39
+ }
36
40
  }
37
41
 
38
42
  /**
@@ -79,34 +83,49 @@ function resolveTypeReferences( csn, options, messageFunctions, resolved, pathDe
79
83
 
80
84
  const ignoreOdataKinds = { aspect: 1, event: 1, type: 1 };
81
85
  const replaceWithDummyKinds = { action: 1, function: 1, event: 1 };
86
+ const stripItems = options.transformation === 'hdbcds' || options.transformation === 'sql';
87
+ const removeItems = new Set();
82
88
  applyTransformations(csn, {
83
89
  type: (node, prop, type, path, parent, parentProp) => {
84
90
  if (options.toOdata && node.kind && node.kind in ignoreOdataKinds)
85
91
  return;
86
92
  if (parentProp === 'cast') {
87
93
  const e = csnUtils.getFinalTypeInfo(type, t => resolved.get(t)?.art || csnUtils.artifactRef(t));
94
+ if (e.items && stripItems)
95
+ removeItems.add(node);
88
96
  if (!e || e.items || e.elements)
89
97
  return;
90
98
  }
91
99
  if (!isBuiltinType(type) && (!options.toOdata || options.toOdata && !isODataV4BuiltinFromService(type, path) && !isODataItems(type))) {
92
100
  toFinalBaseType(node, resolved, true);
93
101
 
94
- if (node.items) // items could have unresolved types
95
- toFinalBaseType(node.items, resolved, true);
96
-
97
- // structured types might not have the child-types replaced.
98
- // Drill down to ensure this.
99
- const nextElements = node.elements || node.items?.elements;
100
- const stack = nextElements ? [ nextElements ] : [];
101
- while (stack.length > 0) {
102
- const elements = stack.pop();
103
- for (const e of Object.values(elements)) {
104
- toFinalBaseType(e, resolved, true);
105
- if (!options.toOdata && e.items) // items could have unresolved types
106
- toFinalBaseType(e.items, resolved, true);
107
- const next = e.elements || e.items?.elements;
108
- if (next)
109
- stack.push(next);
102
+
103
+ if (node.items && stripItems) {
104
+ removeItems.add(node);
105
+ }
106
+ else {
107
+ if (node.items) // items could have unresolved types
108
+ toFinalBaseType(node.items, resolved, true);
109
+
110
+ // structured types might not have the child-types replaced.
111
+ // Drill down to ensure this.
112
+ const nextElements = node.elements || node.items?.elements;
113
+ const stack = nextElements ? [ nextElements ] : [];
114
+ while (stack.length > 0) {
115
+ const elements = stack.pop();
116
+ for (const e of Object.values(elements)) {
117
+ toFinalBaseType(e, resolved, true);
118
+ if (stripItems && e.items) {
119
+ removeItems.add(e);
120
+ }
121
+ else {
122
+ if (!options.toOdata && e.items) // items could have unresolved types
123
+ toFinalBaseType(e.items, resolved, true);
124
+ const next = e.elements || e.items?.elements;
125
+ if (next)
126
+ stack.push(next);
127
+ }
128
+ }
110
129
  }
111
130
  }
112
131
 
@@ -115,6 +134,7 @@ function resolveTypeReferences( csn, options, messageFunctions, resolved, pathDe
115
134
  removeLocalized(node);
116
135
  }
117
136
  },
137
+ items: node => removeItems.add(node),
118
138
  }, [ (definitions, artifactName, artifact) => {
119
139
  // Replace events, actions and functions with simple dummies - they don't have effect on forRelationalDB stuff
120
140
  // and that way they contain no references and don't hurt.
@@ -123,6 +143,8 @@ function resolveTypeReferences( csn, options, messageFunctions, resolved, pathDe
123
143
  // TODO:factor out somewhere else
124
144
  if (!options.toOdata && artifact.kind in replaceWithDummyKinds) {
125
145
  const dummy = { kind: artifact.kind };
146
+ if (artifact.kind === 'event')
147
+ dummy.elements = {}; // events must be structured for recompilation
126
148
  if (artifact.$location)
127
149
  setProp(dummy, '$location', artifact.$location);
128
150
 
@@ -131,6 +153,13 @@ function resolveTypeReferences( csn, options, messageFunctions, resolved, pathDe
131
153
  // TODO: skipDict options as default function arguments not via Object.assign
132
154
  } ], iterateOptions);
133
155
 
156
+ // no support for array-of - turn into CLOB/Text
157
+ for (const node of removeItems) {
158
+ node.type = 'cds.LargeString';
159
+ delete node.items;
160
+ }
161
+ removeItems.clear();
162
+
134
163
 
135
164
  /**
136
165
  * OData V4 only:
@@ -439,17 +468,13 @@ function linkForeignKeyAnnotationExtensionsToAssociation( csn, options ) {
439
468
  */
440
469
  function handleManagedAssociationsAndCreateForeignKeys( csn, options, messageFunctions, pathDelimiter, flattenKeyRefs, csnUtils, iterateOptions = {} ) {
441
470
  const { error, warning } = messageFunctions;
442
- const { isManagedAssociation, inspectRef, isStructured } = csnUtils;
471
+ const { inspectRef, isStructured } = csnUtils;
443
472
  const { flattenStructStepsInRef, flattenStructuredElement } = transformUtils.getTransformers(csn, options, messageFunctions, pathDelimiter);
444
473
  if (flattenKeyRefs) {
445
474
  applyTransformations(csn, {
446
- elements: (parent, prop, elements, path) => {
447
- Object.entries(elements).forEach(([ elementName, element ]) => {
448
- if (isManagedAssociation(element)) {
449
- // replace foreign keys that are managed associations by their respective foreign keys
450
- flattenFKs(element, elementName, [ ...path, 'elements', elementName ]);
451
- }
452
- });
475
+ keys: (element, prop, keys, path) => {
476
+ // replace foreign keys that are managed associations by their respective foreign keys
477
+ flattenFKs(element, path.at(-1), path);
453
478
  },
454
479
  }, [], Object.assign({
455
480
  skipIgnore: false,
@@ -28,8 +28,8 @@ function replaceAssociationsInGroupByOrderBy( inputQuery, options, inspectRef, e
28
28
  // (230 c) If we keep associations as they are (hdbcds naming convention), we can't have associations in GROUP BY
29
29
  if (options.transformation === 'hdbcds' && options.sqlMapping === 'hdbcds') {
30
30
  error(null, groupByPath,
31
- { $reviewed: true },
32
- 'Unexpected managed association in GROUP BY for naming mode “hdbcds”');
31
+ { $reviewed: true, keyword: 'GROUP BY', value: 'hdbcds' },
32
+ 'Unexpected managed association in $(KEYWORD) for naming mode $(VALUE)');
33
33
  continue;
34
34
  }
35
35
  const pathPrefix = query.groupBy[i].ref.slice(0, -1);
@@ -4,7 +4,7 @@ const {
4
4
  getNormalizedQuery, hasAnnotationValue, forEachMember,
5
5
  } = require('../../model/csnUtils');
6
6
  const { implicitAs } = require('../../model/csnRefs');
7
- const { setProp } = require('../../base/model');
7
+ const { setProp, isBetaEnabled } = require('../../base/model');
8
8
  const { getTransformers } = require('../transformUtils');
9
9
 
10
10
  const validToString = '@cds.valid.to';
@@ -21,9 +21,10 @@ const validFromString = '@cds.valid.from';
21
21
  * @param {object} messageFunctions
22
22
  * @param {Function} messageFunctions.info
23
23
  * @param {object} csnUtils
24
+ * @param {object} options
24
25
  * @returns {(artifact: CSN.Artifact, artifactName: string) => void} Callback for forEachDefinition applying the where-condition to views.
25
26
  */
26
- function getViewDecorator( csn, messageFunctions, csnUtils ) {
27
+ function getViewDecorator( csn, messageFunctions, csnUtils, options ) {
27
28
  const { info } = messageFunctions;
28
29
  const { get$combined } = csnUtils;
29
30
  return addTemporalWhereConditionToView;
@@ -52,7 +53,9 @@ function getViewDecorator( csn, messageFunctions, csnUtils ) {
52
53
  if (from.length === 1 && to.length === 1) {
53
54
  // and both are from the same origin
54
55
  if (from[0].source === to[0].source && from[0].parent === to[0].parent) {
55
- if (!hasFalsyTemporalAnnotations(normalizedQuery.query.SELECT, artifact.elements, from[0], to[0])) {
56
+ const omitWhereClause = isBetaEnabled(options, 'temporalRawProjection') &&
57
+ hasFalsyTemporalAnnotations(normalizedQuery.query.SELECT, artifact.elements, from[0], to[0]);
58
+ if (!omitWhereClause) {
56
59
  const fromPath = {
57
60
  ref: [
58
61
  from[0].parent,