@sap/cds-compiler 4.9.6 → 5.1.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 (95) hide show
  1. package/CHANGELOG.md +92 -0
  2. package/bin/cds_remove_invalid_whitespace.js +2 -1
  3. package/bin/cdsc.js +49 -19
  4. package/bin/cdshi.js +3 -1
  5. package/doc/CHANGELOG_BETA.md +7 -0
  6. package/lib/api/main.js +16 -19
  7. package/lib/api/options.js +5 -14
  8. package/lib/api/trace.js +0 -1
  9. package/lib/base/builtins.js +1 -0
  10. package/lib/base/location.js +4 -1
  11. package/lib/base/message-registry.js +43 -29
  12. package/lib/base/messages.js +23 -26
  13. package/lib/base/meta.js +10 -0
  14. package/lib/base/model.js +0 -2
  15. package/lib/base/node-helpers.js +0 -1
  16. package/lib/base/optionProcessorHelper.js +11 -0
  17. package/lib/checks/dbFeatureFlags.js +5 -0
  18. package/lib/checks/enricher.js +1 -5
  19. package/lib/checks/structuredAnnoExpressions.js +30 -0
  20. package/lib/checks/validator.js +8 -0
  21. package/lib/compiler/assert-consistency.js +5 -1
  22. package/lib/compiler/base.js +1 -1
  23. package/lib/compiler/builtins.js +18 -2
  24. package/lib/compiler/checks.js +2 -5
  25. package/lib/compiler/define.js +8 -8
  26. package/lib/compiler/extend.js +108 -37
  27. package/lib/compiler/generate.js +1 -1
  28. package/lib/compiler/index.js +27 -10
  29. package/lib/compiler/lsp-api.js +501 -2
  30. package/lib/compiler/populate.js +60 -13
  31. package/lib/compiler/propagator.js +10 -8
  32. package/lib/compiler/resolve.js +117 -94
  33. package/lib/compiler/shared.js +114 -32
  34. package/lib/compiler/tweak-assocs.js +31 -21
  35. package/lib/compiler/utils.js +2 -1
  36. package/lib/compiler/xsn-model.js +4 -0
  37. package/lib/edm/annotations/genericTranslation.js +69 -35
  38. package/lib/edm/csn2edm.js +16 -4
  39. package/lib/edm/edm.js +10 -3
  40. package/lib/edm/edmAnnoPreprocessor.js +1 -2
  41. package/lib/edm/edmPreprocessor.js +8 -10
  42. package/lib/gen/Dictionary.json +66 -2
  43. package/lib/gen/language.checksum +1 -1
  44. package/lib/gen/language.interp +2 -1
  45. package/lib/gen/languageParser.js +4995 -4817
  46. package/lib/json/csnVersion.js +1 -1
  47. package/lib/json/from-csn.js +4 -7
  48. package/lib/json/to-csn.js +25 -12
  49. package/lib/language/antlrParser.js +2 -2
  50. package/lib/language/errorStrategy.js +0 -1
  51. package/lib/language/genericAntlrParser.js +35 -12
  52. package/lib/language/multiLineStringParser.js +3 -2
  53. package/lib/language/textUtils.js +1 -0
  54. package/lib/main.d.ts +28 -9
  55. package/lib/main.js +9 -9
  56. package/lib/model/cloneCsn.js +1 -0
  57. package/lib/model/csnRefs.js +22 -5
  58. package/lib/model/csnUtils.js +0 -14
  59. package/lib/model/revealInternalProperties.js +1 -2
  60. package/lib/modelCompare/compare.js +13 -11
  61. package/lib/optionProcessor.js +30 -9
  62. package/lib/render/manageConstraints.js +1 -1
  63. package/lib/render/toCdl.js +44 -14
  64. package/lib/render/toHdbcds.js +1 -2
  65. package/lib/render/toSql.js +45 -8
  66. package/lib/render/utils/common.js +12 -9
  67. package/lib/render/utils/stringEscapes.js +1 -0
  68. package/lib/transform/db/applyTransformations.js +13 -8
  69. package/lib/transform/db/associations.js +62 -54
  70. package/lib/transform/db/backlinks.js +20 -5
  71. package/lib/transform/db/expansion.js +1 -6
  72. package/lib/transform/db/flattening.js +86 -109
  73. package/lib/transform/db/killAnnotations.js +3 -0
  74. package/lib/transform/db/processSqlServices.js +63 -0
  75. package/lib/transform/db/temporal.js +3 -4
  76. package/lib/transform/db/views.js +0 -1
  77. package/lib/transform/draft/odata.js +56 -3
  78. package/lib/transform/effective/annotations.js +3 -2
  79. package/lib/transform/effective/flattening.js +135 -0
  80. package/lib/transform/effective/main.js +6 -4
  81. package/lib/transform/effective/types.js +13 -9
  82. package/lib/transform/forOdata.js +0 -2
  83. package/lib/transform/forRelationalDB.js +9 -19
  84. package/lib/transform/localized.js +7 -8
  85. package/lib/transform/odata/flattening.js +39 -31
  86. package/lib/transform/odata/typesExposure.js +5 -17
  87. package/lib/transform/transformUtils.js +1 -1
  88. package/lib/transform/translateAssocsToJoins.js +0 -1
  89. package/lib/utils/file.js +87 -8
  90. package/lib/utils/moduleResolve.js +59 -8
  91. package/lib/utils/term.js +3 -2
  92. package/package.json +7 -3
  93. package/share/messages/message-explanations.json +2 -0
  94. package/share/messages/type-unexpected-foreign-keys.md +52 -0
  95. package/share/messages/type-unexpected-on-condition.md +52 -0
@@ -193,7 +193,7 @@ function getBacklinkTransformer( csnUtils, messageFunctions, options, pathDelimi
193
193
  // Transform comparison of $self to managed association into AND-combined foreign key comparisons
194
194
  if (assoc.keys) {
195
195
  if (assoc.keys.length)
196
- return transformDollarSelfComparisonWithManagedAssoc(assocOp, assoc, assocName, elemName);
196
+ return transformDollarSelfComparisonWithManagedAssoc(assocOp, assoc, assocName, elemName, art, path);
197
197
 
198
198
  if (options.transformation !== 'effective')
199
199
  elem.$ignore = true;
@@ -202,7 +202,7 @@ function getBacklinkTransformer( csnUtils, messageFunctions, options, pathDelimi
202
202
 
203
203
  // Transform comparison of $self to unmanaged association into "reversed" ON-condition
204
204
  else if (assoc.on) {
205
- return transformDollarSelfComparisonWithUnmanagedAssoc(assocOp, assoc, assocName, elemName);
205
+ return transformDollarSelfComparisonWithUnmanagedAssoc(assocOp, assoc, assocName, elemName, art, path);
206
206
  }
207
207
 
208
208
  throw new ModelError(`Expected either managed or unmanaged association in $self-comparison: ${JSON.stringify(elem.on)}`);
@@ -220,9 +220,11 @@ function getBacklinkTransformer( csnUtils, messageFunctions, options, pathDelimi
220
220
  * @param {CSN.Element} assoc
221
221
  * @param {string} originalAssocName
222
222
  * @param {string} elemName
223
+ * @param {CSN.Artifact} art
224
+ * @param {CSN.Path} path
223
225
  * @returns {Array} New on-condition
224
226
  */
225
- function transformDollarSelfComparisonWithManagedAssoc( assocOp, assoc, originalAssocName, elemName ) {
227
+ function transformDollarSelfComparisonWithManagedAssoc( assocOp, assoc, originalAssocName, elemName, art, path) {
226
228
  const conditions = [];
227
229
  // if the element was structured then it was flattened => change of the delimiter from '.' to '_'
228
230
  // this is done in the flattening, but as we do not alter the onCond itself there should be done here as well
@@ -245,7 +247,9 @@ function getBacklinkTransformer( csnUtils, messageFunctions, options, pathDelimi
245
247
  if (prepend$self)
246
248
  a[1].ref = [ '$self', ...a[1].ref ];
247
249
 
248
-
250
+ // Not without a2j so we can rely on a certain model state
251
+ if (doA2J && prepend$self && art.elements[k.ref[1]] || !prepend$self && !art.elements[k.ref[0]])
252
+ messageFunctions.message('ref-missing-self-counterpart', path, { prop: k.ref[0], name: assocName });
249
253
  conditions.push([ a[0], '=', a[1] ]);
250
254
  });
251
255
 
@@ -268,9 +272,11 @@ function getBacklinkTransformer( csnUtils, messageFunctions, options, pathDelimi
268
272
  * @param {CSN.Element} assoc
269
273
  * @param {string} originalAssocName
270
274
  * @param {string} elemName
275
+ * @param {CSN.Artifact} art
276
+ * @param {CSN.Path} path
271
277
  * @returns {Array} New on-condition
272
278
  */
273
- function transformDollarSelfComparisonWithUnmanagedAssoc( assocOp, assoc, originalAssocName, elemName ) {
279
+ function transformDollarSelfComparisonWithUnmanagedAssoc( assocOp, assoc, originalAssocName, elemName, art, path ) {
274
280
  // if the element was structured then it may have been flattened => change of the delimiter from '.' to '_'
275
281
  // this is done in the flattening, but as we do not alter the onCond itself there should be done here as well
276
282
  elemName = elemName.replace(/\./g, pathDelimiter);
@@ -279,11 +285,14 @@ function getBacklinkTransformer( csnUtils, messageFunctions, options, pathDelimi
279
285
  const newOnCond = cloneCsnNonDict(assoc.on, options);
280
286
  applyTransformationsOnNonDictionary({ on: newOnCond }, 'on', {
281
287
  ref: (parent, prop, ref) => {
288
+ let sourceSide = false;
282
289
  // we are in the "path" from the forwarding assoc => need to remove the first part of the path
283
290
  if (ref[0] === assocName) {
284
291
  ref.shift();
285
292
  if (prepend$self)
286
293
  ref.unshift('$self');
294
+
295
+ sourceSide = true;
287
296
  }
288
297
  else if (ref.length > 1 && ref[0] === '$self' && ref[1] === assocName) {
289
298
  // We could also have a $self in front of the assoc name - so we would need to shift twice
@@ -291,6 +300,8 @@ function getBacklinkTransformer( csnUtils, messageFunctions, options, pathDelimi
291
300
  ref.shift();
292
301
  if (prepend$self)
293
302
  ref.unshift('$self');
303
+
304
+ sourceSide = true;
294
305
  }
295
306
  else { // we are in the backlink assoc "path" => need to push at the beginning the association's id
296
307
  ref.unshift(elemName);
@@ -299,6 +310,10 @@ function getBacklinkTransformer( csnUtils, messageFunctions, options, pathDelimi
299
310
  if (ref[1] === '$self')
300
311
  ref.splice(1, 1);
301
312
  }
313
+
314
+ // Not without a2j so we can rely on a certain model state
315
+ if (doA2J && sourceSide && (prepend$self && !art.elements[ref[1]] || !prepend$self && !art.elements[ref[0]]))
316
+ messageFunctions.message('ref-missing-self-counterpart', path, { '#': 'unmanaged', prop: ref[0], name: assocName });
302
317
  },
303
318
  });
304
319
  return newOnCond;
@@ -47,7 +47,7 @@ function expandStructureReferences( csn, options, pathDelimiter, messageFunction
47
47
  const isComplexQuery = parent.from.join !== undefined;
48
48
  if (!options.toOdata)
49
49
  parent.columns = replaceStar(root, columns, parent.excluding, isComplexQuery);
50
- // FIXME(v5): Remove argument "isComplexOrNestedQuery"; we use path.length > 4 to check
50
+ // FIXME(v6): Remove argument "isComplexOrNestedQuery"; we use path.length > 4 to check
51
51
  // if we're inside the outermost "columns". If so, always prepend a table alias. See #11662
52
52
  parent.columns = expand(parent.columns, path.concat('columns'), true, isComplexQuery || path.length > 4);
53
53
  }
@@ -622,11 +622,6 @@ function expandStructureReferences( csn, options, pathDelimiter, messageFunction
622
622
  return _expandStructCol(art, columnAlias(root), root.ref, ( currentRef, currentAlias) => {
623
623
  const obj = { ...root, ref: currentRef };
624
624
  if (withAlias) {
625
- // TODO: Remove this line in case foreign key annotations should
626
- // be addressed via full path into target instead of using alias
627
- // names. See flattening.js::flattenAllStructStepsInRefs()
628
- // apply transformations on `ref` counterpart comment.
629
- setProp(obj, '$structRef', currentAlias);
630
625
  obj.as = currentAlias.join(pathDelimiter);
631
626
  // alias was implicit - to later distinguish expanded s -> s.a from explicitly written s.a
632
627
  if (root.as === undefined)
@@ -12,7 +12,7 @@ const {
12
12
  const { isBuiltinType, isMagicVariable } = require('../../base/builtins');
13
13
  const transformUtils = require('../transformUtils');
14
14
  const { csnRefs } = require('../../model/csnRefs');
15
- const { setProp, isBetaEnabled } = require('../../base/model');
15
+ const { setProp } = require('../../base/model');
16
16
  const { forEach } = require('../../utils/objectUtils');
17
17
  const { transformExpression } = require('./applyTransformations');
18
18
  const { cloneCsnNonDict } = require('../../model/cloneCsn');
@@ -207,9 +207,24 @@ function resolveTypeReferences( csn, options, messageFunctions, resolved, pathDe
207
207
  * @param {object} iterateOptions
208
208
  */
209
209
  function flattenAllStructStepsInRefs( csn, options, messageFunctions, resolved, pathDelimiter, iterateOptions = {} ) {
210
+ const adaptRefs = [];
211
+
212
+ applyTransformations(csn, getStructStepsFlattener(csn, options, messageFunctions, resolved, pathDelimiter, adaptRefs), [], iterateOptions);
213
+
214
+ adaptRefs.forEach(fn => fn());
215
+ }
216
+ /**
217
+ * @param {CSN.Model} csn
218
+ * @param {CSN.Options} options
219
+ * @param {object} messageFunctions Message functions such as `error()`, `info()`, …
220
+ * @param {WeakMap} resolved Cache for resolved refs
221
+ * @param {string} pathDelimiter
222
+ * @param {Function[]} adaptRefs
223
+ * @returns {object} applyTransformations transformer
224
+ */
225
+ function getStructStepsFlattener( csn, options, messageFunctions, resolved, pathDelimiter, adaptRefs ) {
210
226
  const { inspectRef, effectiveType } = csnRefs(csn);
211
227
  const { flattenStructStepsInRef } = transformUtils.getTransformers(csn, options, messageFunctions, pathDelimiter);
212
- const adaptRefs = [];
213
228
 
214
229
  /**
215
230
  * For each step of the links, check if there is a type reference.
@@ -230,41 +245,57 @@ function flattenAllStructStepsInRefs( csn, options, messageFunctions, resolved,
230
245
  return resolvedLinkTypes;
231
246
  }
232
247
 
233
- applyTransformations(csn, {
248
+ const transformer = {
234
249
  // @ts-ignore
235
- ref: (parent, prop, ref, path) => {
250
+ ref: (parent, prop, ref, path, _parent, _prop, context) => {
236
251
  const { links, art, scope } = inspectRef(path);
237
252
  const resolvedLinkTypes = resolveLinkTypes(links);
238
253
  setProp(parent, '$path', [ ...path ]);
239
254
  const lastRef = ref[ref.length - 1];
240
- const fn = () => {
241
- const scopedPath = [ ...parent.$path ];
242
- // TODO: If foreign key annotations should be assigned via
243
- // full path into target, uncomment this line and
244
- // comment/remove setProp in expansion.js
245
- // setProp(parent, '$structRef', parent.ref);
246
- [ parent.ref ] = flattenStructStepsInRef(ref, scopedPath, links, scope, resolvedLinkTypes);
247
- resolved.set(parent, { links, art, scope });
248
- // Explicitly set implicit alias for things that are now flattened - but only in columns
249
- // TODO: Can this be done elegantly during expand phase already?
250
- if (parent.$implicitAlias) { // an expanded s -> s.a is marked with this - do not add implicit alias "a" there, we want s_a
251
- if (parent.ref[parent.ref.length - 1] === parent.as) // for a simple s that was expanded - for s.substructure this would not apply
252
- delete parent.as;
253
- delete parent.$implicitAlias;
254
- }
255
- // To handle explicitly written s.a - add implicit alias a, since after flattening it would otherwise be s_a
256
- else if (parent.ref[parent.ref.length - 1] !== lastRef &&
257
- (insideColumns(scopedPath) || insideKeys(scopedPath)) &&
258
- !parent.as) {
259
- parent.as = lastRef;
255
+ const fn = (suspend = false, suspendPos = 0,
256
+ refFilter = _parent => true) => {
257
+ let refChanged = false;
258
+ if (refFilter(parent)) {
259
+ const scopedPath = [ ...parent.$path ];
260
+ // TODO: If foreign key annotations should be assigned via
261
+ // full path into target, uncomment this line and
262
+ // comment/remove setProp in expansion.js
263
+ // setProp(parent, '$structRef', parent.ref);
264
+ [ parent.ref, refChanged ] = flattenStructStepsInRef(ref, scopedPath, links, scope, resolvedLinkTypes, suspend, suspendPos, parent.$bparam);
265
+ resolved.set(parent, { links, art, scope });
266
+ // Explicitly set implicit alias for things that are now flattened - but only in columns
267
+ // TODO: Can this be done elegantly during expand phase already?
268
+ if (parent.$implicitAlias) { // an expanded s -> s.a is marked with this - do not add implicit alias "a" there, we want s_a
269
+ if (parent.ref[parent.ref.length - 1] === parent.as) // for a simple s that was expanded - for s.substructure this would not apply
270
+ delete parent.as;
271
+ delete parent.$implicitAlias;
272
+ }
273
+ // To handle explicitly written s.a - add implicit alias a, since after flattening it would otherwise be s_a
274
+ else if (parent.ref[parent.ref.length - 1] !== lastRef &&
275
+ (insideColumns(scopedPath) || insideKeys(scopedPath)) &&
276
+ !parent.as) {
277
+ parent.as = lastRef;
278
+ }
260
279
  }
280
+
281
+ return refChanged;
261
282
  };
262
- // adapt queries later
263
- adaptRefs.push(fn);
283
+
284
+ if (context?.$annotation) {
285
+ const annotation = context.$annotation.value;
286
+ adaptRefs.push((...args) => {
287
+ const refChanged = fn(...args);
288
+ if (refChanged && annotation['='])
289
+ annotation['='] = true;
290
+ });
291
+ }
292
+ else {
293
+ // adapt queries later
294
+ adaptRefs.push(fn);
295
+ }
264
296
  },
265
- }, [], iterateOptions);
297
+ };
266
298
 
267
- adaptRefs.forEach(fn => fn());
268
299
 
269
300
  /**
270
301
  * Return true if the path points inside columns
@@ -284,6 +315,8 @@ function flattenAllStructStepsInRefs( csn, options, messageFunctions, resolved,
284
315
  function insideKeys( path ) {
285
316
  return path.length >= 3 && path[path.length - 2] === 'keys' && typeof path[path.length - 1] === 'number';
286
317
  }
318
+
319
+ return transformer;
287
320
  }
288
321
 
289
322
  /**
@@ -344,25 +377,28 @@ function flattenElements( csn, options, messageFunctions, pathDelimiter, iterate
344
377
  if (flatElement.type && isAssocOrComposition(flatElement) && flatElement.on) {
345
378
  // unmanaged relations can't be primary key
346
379
  delete flatElement.key;
347
- transformExpression(flatElement, 'on', {
348
- ref: (_parent, _prop, xpr) => {
349
- const prefix = flatElement._flatElementNameWithDots.split('.').slice(0, -1).join(pathDelimiter);
380
+ if (options.transformation !== 'effective') {
381
+ const process = endIndex => function processRef(_parent, _prop, xpr) {
382
+ const prefix = flatElement._flatElementNameWithDots.split('.').slice(0, endIndex).join(pathDelimiter);
350
383
  const possibleFlatName = prefix + pathDelimiter + xpr[0];
351
384
  /*
352
- when element is defined in the current name resolution scope, like
353
- entity E {
354
- key x: Integer;
355
- s : {
356
- y : Integer;
357
- a3 : association to E on a3.x = y;
358
- }
359
- }
360
- We need to replace y with s_y and a3 with s_a3 - we must take care to not escape our local scope
361
- */
385
+ when element is defined in the current name resolution scope, like
386
+ entity E {
387
+ key x: Integer;
388
+ s : {
389
+ y : Integer;
390
+ a3 : association to E on a3.x = y;
391
+ }
392
+ }
393
+ We need to replace y with s_y and a3 with s_a3 - we must take care to not escape our local scope
394
+ */
362
395
  if (flatElems[possibleFlatName])
363
396
  xpr[0] = possibleFlatName;
364
- },
365
- });
397
+ };
398
+ transformExpression(flatElement, 'on', {
399
+ ref: process(-1),
400
+ });
401
+ }
366
402
  }
367
403
  parent[prop].$orderedElements.push([ flatElemName, flatElement ]);
368
404
  // Still add them - otherwise we might not detect collisions between generated elements.
@@ -428,36 +464,6 @@ function getBranches( element, elementName, effectiveType, pathDelimiter ) {
428
464
  return branches;
429
465
  }
430
466
 
431
- /**
432
- * Link annotate extensions to managed associations as a preparational step
433
- * for later annotation assignment on the final foreignkeys
434
- * This function must be applied on an unmodified, structured CSN in order to
435
- * traverse both the extensions and dictionary trees in corresponding order.
436
- *
437
- * @param {CSN.Model} csn
438
- * @param {object} options
439
- */
440
- function linkForeignKeyAnnotationExtensionsToAssociation( csn, options ) {
441
- if (isBetaEnabled(options, 'annotateForeignKeys')) {
442
- csn.extensions?.forEach(( ext ) => {
443
- const defName = ext.annotate;
444
-
445
- const traverseExtensions = (env, enode) => {
446
- if (env?.target && env?.keys) {
447
- setProp(env, '$fkExtensions', enode);
448
- }
449
- else {
450
- const elements = env?.items?.elements || env?.elements;
451
- if (enode?.elements && elements)
452
- Object.keys(enode.elements).forEach(en => traverseExtensions(elements[en], enode.elements[en]));
453
- }
454
- };
455
- if (ext.annotate)
456
- traverseExtensions(csn.definitions[defName], ext);
457
- });
458
- }
459
- }
460
-
461
467
  /**
462
468
  * @param {CSN.Model} csn
463
469
  * @param {CSN.Options} options
@@ -588,7 +594,7 @@ function handleManagedAssociationsAndCreateForeignKeys( csn, options, messageFun
588
594
  * @returns {object} The clone of base
589
595
  */
590
596
  function cloneAndExtendRef( key, base, ref ) {
591
- const clone = cloneCsnNonDict(base, { ...options, hiddenPropertiesToClone: [ '$structRef', '$fkExtensions' ] } );
597
+ const clone = cloneCsnNonDict(base, options );
592
598
  if (key.ref) {
593
599
  // We build a ref that contains the aliased fk - that element will be created later on, so this ref is not resolvable yet
594
600
  // Therefore we keep it as $ref - ref is the non-aliased, resolvable "clone"
@@ -604,8 +610,6 @@ function handleManagedAssociationsAndCreateForeignKeys( csn, options, messageFun
604
610
  }
605
611
  setProp(clone, '$ref', $ref);
606
612
  clone.ref = clone.ref.concat(key.ref);
607
- if (clone.$structRef && key.$structRef)
608
- clone.$structRef = clone.$structRef.concat(key.$structRef);
609
613
  }
610
614
 
611
615
  if (!clone.as && clone.ref && clone.ref.length > 0) {
@@ -705,34 +709,9 @@ function handleManagedAssociationsAndCreateForeignKeys( csn, options, messageFun
705
709
  'Expected target cardinality $(VALUE) and $(CODE) to match');
706
710
  }
707
711
  }
708
- }
709
- // assign annotations from fkExtension tree to foreign keys
710
- if (isBetaEnabled(options, 'annotateForeignKeys')) {
711
- const extCollector = {};
712
- fks.forEach(([ _fkn, fk ]) => {
713
- let ext = element.$fkExtensions;
714
- let extKey = elementName;
715
- for (const step of fk.$extensionPath) {
716
- extKey += `.${step}`;
717
- ext = ext?.elements?.[step];
718
- if (!ext)
719
- break;
720
- // collect annotations, lowest wins
721
- // eslint-disable-next-line no-loop-func
722
- Object.entries(ext).forEach(([ k, v ]) => {
723
- if (k[0] === '@') {
724
- fk[k] = v;
725
- extCollector[extKey] = ext;
726
- }
727
- });
728
- }
729
- });
730
712
 
731
- // remove consumed annotations after applying the annotation hierarchy to each fk!
732
- Object.values(extCollector).forEach(ext => Object.keys(ext).forEach((k) => {
733
- if (k[0] === '@')
734
- delete ext[k];
735
- }));
713
+ if (options.transformation === 'effective')
714
+ delete element.default;
736
715
  }
737
716
  orderedElements.push(...fks);
738
717
  });
@@ -757,11 +736,10 @@ function handleManagedAssociationsAndCreateForeignKeys( csn, options, messageFun
757
736
  * @param {CSN.Model} csn
758
737
  * @param {object} options
759
738
  * @param {string} pathDelimiter
760
- * @param {object} extensionPath
761
739
  * @param {number} lvl
762
740
  * @returns {Array[]} First element of every sub-array is the foreign key name, second is the foreign key definition
763
741
  */
764
- function createForeignKeys( csnUtils, path, element, prefix, csn, options, pathDelimiter, extensionPath = [], lvl = 0 ) {
742
+ function createForeignKeys( csnUtils, path, element, prefix, csn, options, pathDelimiter, lvl = 0 ) {
765
743
  const special$self = !csn?.definitions?.$self && '$self';
766
744
  const isInspectRefResult = !Array.isArray(path);
767
745
 
@@ -807,7 +785,7 @@ function createForeignKeys( csnUtils, path, element, prefix, csn, options, pathD
807
785
  const continuePath = getContinuePath([ 'keys', keyIndex ]);
808
786
  const alias = key.as || implicitAs(key.ref);
809
787
  const result = csnUtils.inspectRef(continuePath);
810
- fks = fks.concat(createForeignKeys(csnUtils, result, result.art, alias, csn, options, pathDelimiter, extensionPath.concat(key.$structRef), lvl + 1));
788
+ fks = fks.concat(createForeignKeys(csnUtils, result, result.art, alias, csn, options, pathDelimiter, lvl + 1));
811
789
  });
812
790
  if (!hasKeys)
813
791
  delete finalElement.keys;
@@ -821,14 +799,13 @@ function createForeignKeys( csnUtils, path, element, prefix, csn, options, pathD
821
799
  // Skip already produced foreign keys
822
800
  if (!elem['@odata.foreignKey4']) {
823
801
  const continuePath = getContinuePath([ 'elements', elemName ]);
824
- fks = fks.concat(createForeignKeys(csnUtils, continuePath, elem, elemName, csn, options, pathDelimiter, extensionPath.concat(elemName), lvl + 1));
802
+ fks = fks.concat(createForeignKeys(csnUtils, continuePath, elem, elemName, csn, options, pathDelimiter, lvl + 1));
825
803
  }
826
804
  });
827
805
  }
828
806
  // we have reached a leaf element, create a foreign key
829
807
  else if (finalElement.type == null || isBuiltinType(finalElement.type)) {
830
808
  const newFk = Object.create(null);
831
- setProp(newFk, '$extensionPath', extensionPath);
832
809
  [ 'type', 'length', 'scale', 'precision', 'srid', 'default', '@odata.Type', ...EdmTypeFacetNames.map(f => `@odata.${f}`) ].forEach((prop) => {
833
810
  // copy props from original element to preserve derived types!
834
811
  if (element[prop] !== undefined)
@@ -887,7 +864,7 @@ module.exports = {
887
864
  flattenAllStructStepsInRefs,
888
865
  flattenElements,
889
866
  removeLeadingSelf,
890
- linkForeignKeyAnnotationExtensionsToAssociation,
891
867
  handleManagedAssociationsAndCreateForeignKeys,
892
868
  getBranches,
869
+ getStructStepsFlattener,
893
870
  };
@@ -1,5 +1,7 @@
1
1
  'use strict';
2
2
 
3
+ const { sqlServiceAnnotation } = require('./processSqlServices');
4
+
3
5
  const requiredAnnos = {
4
6
  '@cds.persistence.skip': true,
5
7
  '@cds.persistence.exists': true,
@@ -22,6 +24,7 @@ const requiredAnnos = {
22
24
  '@cds.autoexposed': true,
23
25
  '@cds.redirection.target': true,
24
26
  '@Core.Computed': true,
27
+ [sqlServiceAnnotation]: true,
25
28
  };
26
29
 
27
30
  /**
@@ -0,0 +1,63 @@
1
+ 'use strict';
2
+
3
+ const { setProp } = require('../../base/model');
4
+
5
+ const sqlServiceAnnotation = '@protocol';
6
+ // Problem: How can we clone a Symbol when sorting?
7
+ // const sqlServiceEntities = Symbol.for('SQL Service enabled entities');
8
+ /**
9
+ * Find all entities in SQL services and mark them with an annotation and
10
+ * remember them in a symbol property for easier processing in toSql-rendering.
11
+ *
12
+ * @param {CSN.Model} csn
13
+ * @returns {Function}
14
+ */
15
+ function processSqlServices(csn) {
16
+ setProp(csn, '$sqlServiceEntities', Object.create(null));
17
+ return function findAndMarkSqlServiceArtifacts(artifact, artifactName) {
18
+ const sqlServiceName = isEntityInSqlService(artifact, artifactName, csn);
19
+ if (sqlServiceName?.length > 0)
20
+ setProp(artifact, '$sqlService', sqlServiceName);
21
+ };
22
+ }
23
+
24
+ /**
25
+ *
26
+ * @param {CSN.Artifact} artifact
27
+ * @returns {boolean}
28
+ */
29
+ function isSqlService(artifact) {
30
+ return artifact.kind === 'service' && artifact[sqlServiceAnnotation] === 'sql';
31
+ }
32
+ /**
33
+ *
34
+ * @param {CSN.Artifact} artifact
35
+ * @param {string} artifactName
36
+ * @param {CSN.Model} csn
37
+ * @returns {string|null}
38
+ */
39
+ function isEntityInSqlService(artifact, artifactName, csn) {
40
+ if (artifact.kind !== 'entity' || !artifactName.includes('.'))
41
+ return null;
42
+
43
+ const nameParts = artifactName.split('.');
44
+ for (let i = nameParts.length; i >= 0; i--) {
45
+ const possibleServiceName = nameParts.slice(0, i).join('.');
46
+ if (!csn.definitions[possibleServiceName])
47
+ continue;
48
+
49
+ const definition = csn.definitions[possibleServiceName];
50
+ if (isSqlService(definition))
51
+ return possibleServiceName;
52
+
53
+ // We don't allow nested services/contexts - if we find one, we don't need to keep searching
54
+ if (definition.kind === 'service' || definition.kind === 'context')
55
+ return null;
56
+ }
57
+
58
+ return null;
59
+ }
60
+
61
+ module.exports = {
62
+ processSqlServices, isSqlService, sqlServiceAnnotation,
63
+ };
@@ -70,11 +70,10 @@ function getViewDecorator( csn, messageFunctions, csnUtils, options ) {
70
70
  ],
71
71
  };
72
72
 
73
- // Clarify: What about $valid?
74
- const atFrom = { ref: [ '$at', 'from' ] };
75
- const atTo = { ref: [ '$at', 'to' ] };
73
+ const validFrom = { ref: [ '$valid', 'from' ] };
74
+ const validTo = { ref: [ '$valid', 'to' ] };
76
75
 
77
- const cond = [ '(', fromPath, '<', atTo, 'and', toPath, '>', atFrom, ')' ];
76
+ const cond = [ '(', fromPath, '<', validTo, 'and', toPath, '>', validFrom, ')' ];
78
77
 
79
78
  if (normalizedQuery.query.SELECT.where) { // if there is an existing where-clause, extend it by adding 'and (temporal clause)'
80
79
  normalizedQuery.query.SELECT.where = [ '(', ...normalizedQuery.query.SELECT.where, ')', 'and', ...cond ];
@@ -324,7 +324,6 @@ function getViewTransformer( csn, options, messageFunctions ) {
324
324
  * @param {string} artName
325
325
  * @param {CSN.Path} path
326
326
  */
327
- // eslint-disable-next-line complexity
328
327
  function transformViewOrEntity( query, artifact, artName, path ) {
329
328
  const ignoreAssociations = options.sqlDialect === 'hana' && options.withHanaAssociations === false;
330
329
  csnUtils.initDefinition(artifact);
@@ -1,6 +1,8 @@
1
1
  'use strict';
2
2
 
3
- const { forEachDefinition, getServiceNames, applyAnnotationsFromExtensions } = require('../../model/csnUtils');
3
+ const { forEachDefinition, forEachMemberRecursively,
4
+ getServiceNames, applyAnnotationsFromExtensions,
5
+ transformExpression } = require('../../model/csnUtils');
4
6
  const { forEach } = require('../../utils/objectUtils');
5
7
  const { isArtifactInSomeService, getServiceOfArtifact } = require('../odata/utils');
6
8
  const { getTransformers } = require('../transformUtils');
@@ -58,12 +60,13 @@ function generateDrafts( csn, options, services, messageFunctions ) {
58
60
  // Generate artificial draft fields for entities/views if requested, ignore if not part of a service
59
61
  if (def.kind === 'entity' && def['@odata.draft.enabled'] && isArtifactInSomeService(defName, services))
60
62
  generateDraftForOdata(def, defName, def);
61
-
62
- visitedArtifacts[defName] = true;
63
63
  }, { skipArtifact: isExternalServiceMember });
64
64
 
65
65
  applyAnnotationsFromExtensions(csn, { override: true, filter: name => filterDict[name] });
66
+ rewriteDollarDraft();
67
+
66
68
  return csn;
69
+
67
70
  /**
68
71
  * Generate all that is required in ODATA for draft enablement of 'artifact' into the artifact,
69
72
  * into its transitively reachable composition targets, and into the model.
@@ -84,6 +87,9 @@ function generateDrafts( csn, options, services, messageFunctions ) {
84
87
  artifact.actions && artifact.actions.draftPrepare)
85
88
  return;
86
89
 
90
+ if(!visitedArtifacts[artifactName])
91
+ visitedArtifacts[artifactName] = artifact;
92
+
87
93
  const draftPrepare = createAction('draftPrepare', artifactName, 'SideEffectsQualifier', 'cds.String');
88
94
  assignAction(draftPrepare, artifact);
89
95
  // Generate the actions into the draft-enabled artifact (only draft roots can be activated/edited)
@@ -211,6 +217,53 @@ function generateDrafts( csn, options, services, messageFunctions ) {
211
217
  }
212
218
  }
213
219
  }
220
+
221
+ /*
222
+ * After draft decoration, all visited artifacts are supposed to have the draft state elements
223
+ * Is/HasActiveEntity, HasDraftEntity. Now, $draft.<postfix> (with postfix defined as magic variable
224
+ * in the core compiler builtins) needs to be translated into $self.<postfix>.
225
+ *
226
+ * It has to be processed after the late 'applyAnnotationsFromExtensions' which could also merge in
227
+ * some $draft path expressions.
228
+ */
229
+ function rewriteDollarDraft() {
230
+
231
+ function $draft2$self(member) {
232
+ Object.keys(member).forEach(pn => {
233
+ if(pn[0] === '@') {
234
+ let refChanged = false;
235
+ transformExpression(member, pn,{
236
+ ref: (_parent, _prop, xpr, _path) => {
237
+ if(xpr[0] === '$draft') {
238
+ xpr[0] = '$self';
239
+ refChanged = true;
240
+ }
241
+ }
242
+ });
243
+ if (refChanged)
244
+ member[pn]['='] = true;
245
+ }
246
+ });
247
+ }
248
+
249
+ // entity parameters are not substituted as the EDM param entity is not draft enabled
250
+ Object.entries(visitedArtifacts).forEach(([artName, art]) => {
251
+ $draft2$self(art);
252
+ forEachMemberRecursively(art, $draft2$self,
253
+ [ 'definitions', artName ],
254
+ true, { elementsOnly: true }
255
+ );
256
+ if(art.actions) {
257
+ Object.entries(art.actions).forEach(([actionName, action]) => {
258
+ $draft2$self(action);
259
+ forEachMemberRecursively(action, $draft2$self,
260
+ [ 'definitions', artName, 'actions', actionName ]);
261
+ if(action.returns)
262
+ $draft2$self(action.returns);
263
+ })
264
+ }
265
+ })
266
+ }
214
267
  }
215
268
 
216
269
  module.exports = generateDrafts;
@@ -179,9 +179,10 @@ function remapODataAnnotations( csn ) {
179
179
  }
180
180
 
181
181
  return {
182
- elements: (parent, prop, elements, path) => {
182
+ elements: (parent, prop, elements, path, _parentParent, _dummy, context) => {
183
183
  const artifact = csn.definitions[path[1]];
184
- if (artifact?.kind === 'entity') {
184
+ // Don't process bound actions, as they are still structured
185
+ if (artifact?.kind === 'entity' && !context.$in_actions) {
185
186
  for (const elementName in elements)
186
187
  remapAnnotationsOnElement(artifact, elements[elementName]);
187
188
  }