@sap/cds-compiler 4.0.0 → 4.1.2

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 (85) hide show
  1. package/CHANGELOG.md +115 -5
  2. package/bin/cdsc.js +12 -12
  3. package/doc/CHANGELOG_BETA.md +11 -0
  4. package/lib/api/main.js +60 -12
  5. package/lib/api/validate.js +1 -1
  6. package/lib/base/location.js +6 -7
  7. package/lib/base/message-registry.js +84 -38
  8. package/lib/base/messages.js +11 -10
  9. package/lib/base/model.js +6 -2
  10. package/lib/checks/defaultValues.js +6 -6
  11. package/lib/checks/foreignKeys.js +0 -5
  12. package/lib/checks/onConditions.js +17 -12
  13. package/lib/checks/queryNoDbArtifacts.js +132 -72
  14. package/lib/checks/sql-snippets.js +15 -4
  15. package/lib/checks/types.js +3 -3
  16. package/lib/checks/utils.js +1 -1
  17. package/lib/compiler/assert-consistency.js +44 -16
  18. package/lib/compiler/base.js +1 -0
  19. package/lib/compiler/builtins.js +7 -8
  20. package/lib/compiler/checks.js +274 -197
  21. package/lib/compiler/classes.js +62 -0
  22. package/lib/compiler/cycle-detector.js +3 -3
  23. package/lib/compiler/define.js +63 -50
  24. package/lib/compiler/extend.js +38 -20
  25. package/lib/compiler/finalize-parse-cdl.js +2 -1
  26. package/lib/compiler/generate.js +0 -8
  27. package/lib/compiler/index.js +9 -7
  28. package/lib/compiler/kick-start.js +2 -0
  29. package/lib/compiler/populate.js +139 -110
  30. package/lib/compiler/propagator.js +4 -3
  31. package/lib/compiler/resolve.js +157 -126
  32. package/lib/compiler/shared.js +706 -404
  33. package/lib/compiler/tweak-assocs.js +21 -10
  34. package/lib/compiler/utils.js +228 -36
  35. package/lib/edm/annotations/genericTranslation.js +30 -2
  36. package/lib/edm/edm.js +4 -1
  37. package/lib/edm/edmPreprocessor.js +12 -5
  38. package/lib/edm/edmUtils.js +2 -4
  39. package/lib/gen/Dictionary.json +34 -10
  40. package/lib/gen/language.checksum +1 -1
  41. package/lib/gen/language.interp +1 -1
  42. package/lib/gen/languageParser.js +3987 -3963
  43. package/lib/json/from-csn.js +43 -47
  44. package/lib/json/to-csn.js +11 -11
  45. package/lib/language/antlrParser.js +2 -1
  46. package/lib/language/genericAntlrParser.js +52 -43
  47. package/lib/language/language.g4 +59 -59
  48. package/lib/language/multiLineStringParser.js +2 -0
  49. package/lib/main.d.ts +5 -0
  50. package/lib/model/csnRefs.js +37 -19
  51. package/lib/model/csnUtils.js +20 -16
  52. package/lib/model/revealInternalProperties.js +29 -21
  53. package/lib/model/sortViews.js +4 -2
  54. package/lib/modelCompare/compare.js +112 -39
  55. package/lib/modelCompare/utils/filter.js +54 -24
  56. package/lib/optionProcessor.js +6 -6
  57. package/lib/render/manageConstraints.js +20 -17
  58. package/lib/render/toCdl.js +34 -20
  59. package/lib/render/toHdbcds.js +2 -2
  60. package/lib/render/toRename.js +4 -9
  61. package/lib/render/toSql.js +77 -26
  62. package/lib/render/utils/common.js +3 -3
  63. package/lib/render/utils/unique.js +52 -0
  64. package/lib/transform/db/applyTransformations.js +61 -20
  65. package/lib/transform/db/assertUnique.js +7 -8
  66. package/lib/transform/db/associations.js +2 -2
  67. package/lib/transform/db/cdsPersistence.js +8 -8
  68. package/lib/transform/db/expansion.js +17 -21
  69. package/lib/transform/db/flattening.js +23 -23
  70. package/lib/transform/db/rewriteCalculatedElements.js +20 -14
  71. package/lib/transform/db/temporal.js +1 -1
  72. package/lib/transform/db/transformExists.js +8 -7
  73. package/lib/transform/db/views.js +73 -33
  74. package/lib/transform/draft/db.js +11 -9
  75. package/lib/transform/draft/odata.js +1 -1
  76. package/lib/transform/{forOdataNew.js → forOdata.js} +56 -42
  77. package/lib/transform/forRelationalDB.js +69 -75
  78. package/lib/transform/localized.js +6 -5
  79. package/lib/transform/odata/toFinalBaseType.js +3 -3
  80. package/lib/transform/{transformUtilsNew.js → transformUtils.js} +4 -101
  81. package/lib/transform/translateAssocsToJoins.js +14 -28
  82. package/package.json +1 -1
  83. package/share/messages/check-proper-type-of.md +1 -1
  84. package/share/messages/{check-proper-type.md → def-missing-type.md} +3 -5
  85. package/share/messages/message-explanations.json +1 -1
@@ -14,11 +14,13 @@ const {
14
14
  setArtifactLink,
15
15
  linkToOrigin,
16
16
  copyExpr,
17
+ forEachUserArtifact,
18
+ forEachQueryExpr,
17
19
  traverseQueryPost,
18
20
  traverseQueryExtra,
19
21
  setExpandStatus,
20
- traverseExpr,
21
22
  } = require('./utils');
23
+ const { CsnLocation } = require('./classes');
22
24
 
23
25
  const $location = Symbol.for('cds.$location');
24
26
  const $inferred = Symbol.for('cds.$inferred');
@@ -30,9 +32,11 @@ function tweakAssocs( model ) {
30
32
  info, warning, error,
31
33
  } = model.$messageFunctions;
32
34
  const {
35
+ traverseExpr,
36
+ checkExpr,
37
+ checkOnCondition,
33
38
  effectiveType,
34
39
  getOrigin,
35
- navigationEnv,
36
40
  } = model.$functions;
37
41
 
38
42
  // Phase 5: rewrite associations
@@ -40,6 +44,13 @@ function tweakAssocs( model ) {
40
44
  // Think hard whether an on condition rewrite can lead to a new cyclic
41
45
  // dependency. If so, we need other messages anyway. TODO: probably do
42
46
  // another cyclic check with testMode.js
47
+ forEachUserArtifact( model, 'definitions', function check( art ) {
48
+ checkOnCondition( art.on, (art.kind !== 'mixin' ? 'on' : 'mixin-on'), art );
49
+ checkExpr( art.value, (art.$syntax === 'calc' ? 'calc' : 'column'), art );
50
+
51
+ if (art.kind === 'select')
52
+ forEachQueryExpr( art, checkExpr );
53
+ } );
43
54
  return;
44
55
 
45
56
 
@@ -166,13 +177,9 @@ function tweakAssocs( model ) {
166
177
  }
167
178
  if (names.length) {
168
179
  const loc = otherAssoc.foreignKeys[$location] || dictLocation( otherAssoc.foreignKeys );
169
- const location = loc && (!loc.endCol ? loc : {
170
- file: loc.file,
171
- line: loc.endLine,
172
- col: loc.endCol - 1,
173
- endLine: loc.endLine,
174
- endCol: loc.endCol,
175
- } );
180
+ const location = loc && (!loc.endCol
181
+ ? loc
182
+ : new CsnLocation( loc.file, loc.endLine, loc.endCol - 1, loc.endLine, loc.endCol ));
176
183
  const baseAssoc = assocWithExplicitSpec( thisAssoc );
177
184
  if (inferredForeignKeys( baseAssoc.foreignKeys )) { // still inferred = via target keys
178
185
  error( 'rewrite-key-not-covered-implicit', [ location, otherAssoc ],
@@ -482,8 +489,12 @@ function tweakAssocs( model ) {
482
489
  item.id = name;
483
490
  }
484
491
  }
485
- const env = name && navigationEnv(elem);
492
+ let env = name && elem._effectiveType; // should have been computed
493
+ // refs in ON cannot navigate along `items`, no need to consider `items` here
494
+ if (env?.target)
495
+ env = env.target._artifact?._effectiveType;
486
496
  elem = setArtifactLink( item, env?.elements?.[name] );
497
+
487
498
  if (elem && !Array.isArray(elem))
488
499
  return elem;
489
500
  // TODO: better (extra message), TODO: do it
@@ -6,12 +6,15 @@
6
6
  // Please do not add functions “for completeness”, this is not an API file for
7
7
  // others but only by the core compiler.
8
8
 
9
- // TODO: probably split this file into utils/….js
9
+ // TODO: split this file into utils/….js, add some functions from lib/base/model.js
10
10
 
11
11
  'use strict';
12
12
 
13
13
  const { dictAdd, pushToDict, dictFirst } = require('../base/dictionaries');
14
14
  const { kindProperties } = require('./base');
15
+ const { XsnName, XsnArtifact, CsnLocation } = require('./classes');
16
+
17
+ const $inferred = Symbol.for('cds.$inferred');
15
18
 
16
19
  // for links, i.e., properties starting with an underscore '_':
17
20
 
@@ -108,15 +111,18 @@ function linkToOrigin( origin, name, parent, prop, location, silentDep ) {
108
111
 
109
112
  function proxyCopyMembers( art, dictProp, originDict, location, kind ) {
110
113
  art[dictProp] = Object.create( null );
114
+ // TODO: set $inferred ? for dict?
111
115
  for (const name in originDict) {
112
116
  const origin = originDict[name];
113
- const member = linkToOrigin( origin, name, art, dictProp,
114
- location || origin.location, true );
115
- member.$inferred = 'expanded';
116
- if (kind)
117
- member.kind = kind;
118
- if (kind && origin.masked) // TODO: remove!
119
- member.masked = Object.assign( { $inferred: 'nav' }, origin.masked );
117
+ if (origin !== undefined) {
118
+ const member = linkToOrigin( origin, name, art, dictProp,
119
+ location || origin.location, true );
120
+ member.$inferred = 'expanded';
121
+ if (kind)
122
+ member.kind = kind;
123
+ if (kind && origin.masked) // TODO: remove!
124
+ member.masked = Object.assign( { $inferred: 'nav' }, origin.masked );
125
+ }
120
126
  }
121
127
  }
122
128
 
@@ -132,7 +138,7 @@ function setMemberParent( elem, name, parent, prop ) {
132
138
  if (prop) { // extension or structure include
133
139
  // TODO: consider nested ARRAY OF and RETURNS, COMPOSITION OF type
134
140
  const p = parent.items || parent.targetAspect || parent;
135
- if (!(prop in p))
141
+ if (p[prop] === undefined)
136
142
  p[prop] = Object.create(null);
137
143
  dictAdd( p[prop], name, elem );
138
144
  }
@@ -166,10 +172,12 @@ function setMemberParent( elem, name, parent, prop ) {
166
172
  * @param {XSN.Artifact} art
167
173
  * @param {XSN.Location} location
168
174
  */
169
- function dependsOn( user, art, location ) {
175
+ function dependsOn( user, art, location, semanticLoc = undefined ) {
176
+ while (user._outer && !user.kind)
177
+ user = user._outer;
170
178
  if (!user._deps)
171
179
  setLink( user, '_deps', [] );
172
- user._deps.push( { art, location } );
180
+ user._deps.push( { art, location, semanticLoc } );
173
181
  }
174
182
 
175
183
  /**
@@ -180,6 +188,8 @@ function dependsOn( user, art, location ) {
180
188
  * @param {XSN.Artifact} art
181
189
  */
182
190
  function dependsOnSilent( user, art ) {
191
+ while (user._outer && !user.kind)
192
+ user = user._outer;
183
193
  if (!user._deps)
184
194
  setLink( user, '_deps', [] );
185
195
  user._deps.push( { art } );
@@ -253,7 +263,9 @@ function copyExpr( expr, location, skipUnderscored, rewritePath ) {
253
263
  return expr.map( e => copyExpr( e, location, skipUnderscored, rewritePath ) );
254
264
 
255
265
  const proto = Object.getPrototypeOf( expr );
256
- if (proto && proto !== Object.prototype) // do not copy object from special classes
266
+ if (proto && proto !== Object.prototype && proto !== XsnName.prototype &&
267
+ // do not copy object from special classes outside the compiler domain&&
268
+ proto !== XsnArtifact.prototype && proto !== CsnLocation.prototype)
257
269
  return expr;
258
270
  const r = Object.create( proto );
259
271
  for (const prop of Object.getOwnPropertyNames( expr )) {
@@ -299,7 +311,7 @@ function testExpr( expr, pathTest, queryTest, user ) {
299
311
  return expr.args.some( e => testExpr( e, pathTest, queryTest, user ) );
300
312
  // named args => dictionary
301
313
  for (const namedArg of Object.keys(expr.args)) {
302
- if (testExpr(expr.args[namedArg], pathTest, queryTest, user))
314
+ if (testExpr( expr.args[namedArg], pathTest, queryTest, user ))
303
315
  return true;
304
316
  }
305
317
  }
@@ -314,6 +326,129 @@ function targetMaxNotOne( assoc, item ) {
314
326
  return cardinality && cardinality.targetMax && cardinality.targetMax.val !== 1;
315
327
  }
316
328
 
329
+ /**
330
+ * Call function `callback(art)` for each user-defined main artifact and member
331
+ * `art` of the model reachable from the dictionary `model[prop]`. User-defined
332
+ * artifacts are those with no or a falsy `art.$inferred` value, i.e. this
333
+ * function is useful for checks.
334
+ *
335
+ * The callback function is not called on the following artifacts:
336
+ * - `enum` symbol definitions (use forEachUserDict() yourself if needed)
337
+ * - the anonymous aspect in the `target`/`targetAspect` property (but the
338
+ * callback function is called on its elements).
339
+ * - table aliases
340
+ *
341
+ * The callback function is also called on duplicates. For example, if there are
342
+ * two entities named `E`, the callback function is called on both.
343
+ * It is also called on columns with `inline`.
344
+ *
345
+ * See also function forEachDefinition(), currently in lib/base/model.js.
346
+ */
347
+ function forEachUserArtifact( model, prop, callback ) { // not enums
348
+ forEachUserDict( model, prop, function main( art ) {
349
+ callback( art );
350
+ forEachUserDict( art, 'params', function param( par ) {
351
+ callback( par );
352
+ forEachUserElementAndFKey( par, callback );
353
+ } );
354
+ if (art.$queries) {
355
+ for (const query of art.$queries) {
356
+ callback( query );
357
+ forEachUserDict( query, 'mixin', callback );
358
+ forEachUserElementAndFKey( query, callback );
359
+ if (query.$inlines) // e.g. not with `entity V as projection on V;`
360
+ query.$inlines.forEach( callback );
361
+ }
362
+ }
363
+ else if (art.returns) {
364
+ callback( art.returns );
365
+ forEachUserElementAndFKey( art.returns, callback );
366
+ }
367
+ else {
368
+ forEachUserElementAndFKey( art, callback );
369
+ }
370
+ forEachUserArtifact( art, 'actions', callback );
371
+ } );
372
+ }
373
+
374
+ /**
375
+ * Call function `callback(art)` for each user-defined element and foreign key
376
+ * reachable from artifact `art`. Do not (again) call the callback function on
377
+ * `art` itself, even if it is an element.
378
+ *
379
+ * Consider that we have (nested) `array of`/`many` types, but do not call the
380
+ * callback function on the array item itself (only on elements inside).
381
+ */
382
+ function forEachUserElementAndFKey( art, callback ) {
383
+ while (art.items)
384
+ art = art.items;
385
+ if (art.target) {
386
+ forEachUserDict( art, 'foreignKeys', callback );
387
+ return;
388
+ }
389
+ if (art.targetAspect)
390
+ art = art.targetAspect;
391
+ forEachUserDict( art, 'elements', function element( elem ) {
392
+ callback( elem );
393
+ forEachUserElementAndFKey( elem, callback );
394
+ } );
395
+ }
396
+
397
+ function forEachUserDict( art, prop, callback ) {
398
+ const dict = art[prop];
399
+ if (!dict || dict[$inferred])
400
+ return;
401
+ for (const name in dict) {
402
+ const obj = dict[name];
403
+ if (obj.$inferred)
404
+ continue;
405
+ callback( obj, name, prop );
406
+ if (Array.isArray(obj.$duplicates)) // redefinitions
407
+ obj.$duplicates.forEach( o => callback( o, name, prop ) );
408
+ }
409
+ }
410
+
411
+ /**
412
+ * Call `callback( expr, exprCtx, query )` on all direct expressions `expr` of
413
+ * `query`, where `exprCtx` is the expression context used as key for the
414
+ * `referenceSemantics` in shared.js.
415
+ *
416
+ * Indirect expressions are not called, these are:
417
+ * - the `from` reference (expression of the table alias)
418
+ * - the ON-condition of mixins (expression of the mixin)
419
+ * - the expressions in columns (expression of the column/element)
420
+ */
421
+ function forEachQueryExpr( query, callback ) { // see resolveQuery()
422
+ forEachJoinOn( query, query.from, callback );
423
+ // TODO: run over $inlines ?
424
+ if (query.where)
425
+ callback( query.where, 'where', query );
426
+ if (query.groupBy)
427
+ forEachExprArray( query, query.groupBy, 'groupBy', 'groupBy', callback );
428
+ if (query.having)
429
+ callback( query.having, 'having', query );
430
+ if (query.$orderBy)
431
+ forEachExprArray( query, query.$orderBy, 'orderBy-set-ref', 'orderBy-set-expr', callback );
432
+ if (query.orderBy)
433
+ forEachExprArray( query, query.orderBy, 'orderBy-ref', 'orderBy-expr', callback );
434
+ }
435
+
436
+ function forEachJoinOn( query, from, callback ) {
437
+ if (!from?.join)
438
+ return; // TODO: run over from.path here?
439
+ for (const tab of from.args)
440
+ forEachJoinOn( query, tab, callback );
441
+ if (from.on)
442
+ callback( from.on, 'join-on', query );
443
+ }
444
+
445
+ function forEachExprArray( query, array, refContext, exprContext, callback ) {
446
+ for (const expr of array ) {
447
+ if (expr)
448
+ callback( expr, (expr.path ? refContext : exprContext), query );
449
+ }
450
+ }
451
+
317
452
  // Query tree post-order traversal - called for everything which contributes to the query
318
453
  // i.e. is necessary to calculate the elements of the query
319
454
  // except "real ones": operands of UNION etc, JOIN with ON, and sub queries in FROM
@@ -442,28 +577,6 @@ function isDirectComposition( art ) {
442
577
  return type && type[0] && type[0].id === 'cds.Composition';
443
578
  }
444
579
 
445
- function traverseExpr( expr, exprCtx, user, callback ) {
446
- if (!expr || typeof expr === 'string') // parse error or keywords in {xpr:...}
447
- return;
448
-
449
- if (expr.path) {
450
- callback( expr, exprCtx, user );
451
- // TODO: move arguments and filter traversal to here
452
- return;
453
- }
454
- else if (expr.type || expr.query) {
455
- callback( expr, exprCtx, user );
456
- }
457
-
458
- if (expr.args) {
459
- const args = Array.isArray(expr.args) ? expr.args : Object.values( expr.args );
460
- // TODO: re-think $expected
461
- args.forEach( e => traverseExpr( e, exprCtx, user, callback ) );
462
- }
463
- if (expr.suffix) // fn( … ) OVER …
464
- expr.suffix.forEach( e => traverseExpr( e, exprCtx, user, callback ) );
465
- }
466
-
467
580
  function userQuery( user ) {
468
581
  // TODO: we need _query links set by the definer
469
582
  while (user._main) {
@@ -474,6 +587,63 @@ function userQuery( user ) {
474
587
  return null;
475
588
  }
476
589
 
590
+ function pathStartsWithSelf( ref ) {
591
+ const head = ref && !ref.scope && ref.path?.[0];
592
+ if (head?._navigation?.kind === '$self')
593
+ return true;
594
+ if (head?._artifact?.kind === 'builtin') // CDS variable
595
+ return false;
596
+ return undefined;
597
+ }
598
+
599
+ function columnRefStartsWithSelf( col ) {
600
+ for (; col; col = col._pathHead) {
601
+ const ref = col.value;
602
+ const head = ref && !ref.scope && ref.path?.[0];
603
+ if (head?._navigation?.kind === '$self')
604
+ return true;
605
+ if (head?._artifact?.kind === 'builtin') // CDS variable
606
+ return false;
607
+ }
608
+ return false;
609
+ }
610
+
611
+ // Remark: this function is based on an early check that no target element is
612
+ // covered more than once by a foreign key: then…
613
+ // we only need to check that all foreign key references are primary keys and
614
+ // that the number of foreign and primary keys are the same.
615
+ function isAssocToPrimaryKeys( assoc ) {
616
+ let fkeys = 0;
617
+ const { foreignKeys } = assoc;
618
+ if (!foreignKeys)
619
+ return undefined;
620
+ for (const name in foreignKeys) {
621
+ const fk = foreignKeys[name];
622
+ const elem = fk.targetElement._artifact;
623
+ if (!elem || fk.$duplicates)
624
+ return undefined;
625
+ if (!elem.key?.val)
626
+ return false;
627
+ ++fkeys;
628
+ }
629
+
630
+ const elements = assoc.target._artifact?.elements;
631
+ if (!elements)
632
+ return undefined;
633
+ for (const name in elements) {
634
+ if (elements[name].key?.val)
635
+ --fkeys;
636
+ }
637
+ return fkeys === 0;
638
+ }
639
+
640
+ // only if _effectiveType has been computed:
641
+ function getUnderlyingBuiltinType( art ) {
642
+ while (art?._effectiveType && !art.builtin)
643
+ art = art._origin || art.type?._artifact;
644
+ return art;
645
+ }
646
+
477
647
  function definedViaCdl( art ) {
478
648
  // return !!art._block?.artifacts;
479
649
  // TODO: the above code would work when _block links are correctly set on
@@ -483,6 +653,21 @@ function definedViaCdl( art ) {
483
653
  return $frontend !== 'json' && $frontend !== '$internal';
484
654
  }
485
655
 
656
+ // For error messages: ----------------------------------------------------------
657
+
658
+ // (To be) used for the location in error messages
659
+ function artifactRefLocation( ref ) {
660
+ return (ref._artifact?._main)
661
+ ? ref.path[ref.path.length - 1].location
662
+ : ref.location;
663
+ }
664
+
665
+ function compositionTextVariant( art, composition, association = 'std' ) {
666
+ return (getUnderlyingBuiltinType( art )?.name.absolute === 'cds.Composition')
667
+ ? composition
668
+ : association;
669
+ }
670
+
486
671
  module.exports = {
487
672
  pushLink,
488
673
  annotationVal,
@@ -505,13 +690,20 @@ module.exports = {
505
690
  copyExpr,
506
691
  testExpr,
507
692
  targetMaxNotOne,
693
+ forEachUserArtifact,
694
+ forEachQueryExpr,
508
695
  traverseQueryPost,
509
696
  traverseQueryExtra,
510
697
  viewFromPrimary,
511
698
  setExpandStatus,
512
699
  setExpandStatusAnnotate,
513
700
  isDirectComposition,
514
- traverseExpr,
515
701
  userQuery,
702
+ pathStartsWithSelf,
703
+ columnRefStartsWithSelf,
704
+ isAssocToPrimaryKeys,
705
+ getUnderlyingBuiltinType,
516
706
  definedViaCdl,
707
+ artifactRefLocation,
708
+ compositionTextVariant,
517
709
  };
@@ -267,6 +267,34 @@ function csn2annotationEdm(reqDefs, csnVocabularies, serviceName,
267
267
 
268
268
  const v = options.v;
269
269
 
270
+ // Copy annotations from origin to parameter entity if it's
271
+ // qualified with #$parameters or if its applicable to an EntitySet or Singleton
272
+ forEachDefinition(reqDefs, (object) => {
273
+ if(object.$isParamEntity && object._origin) {
274
+ for(const attr in object._origin) {
275
+ if (attr[0] === '@') {
276
+ const [ prefix, innerAnnotation ] = attr.split('.@');
277
+ const ns = whatsMyTermNamespace(prefix);
278
+ if(ns) {
279
+ const steps = prefix.replace('@' + ns + '.', '').split('.');
280
+ const paramAnnoParts = steps[0].split('#$parameters');
281
+ const dictTerm = getDictTerm(ns + '.' + paramAnnoParts[0], options);
282
+ if(paramAnnoParts.length > 1 || ['Singleton', 'EntitySet'].some(y => dictTerm?.AppliesTo?.includes(y))) {
283
+ steps[0] = '@' + ns + '.' + paramAnnoParts.join('');
284
+ let newAnno = steps.join('.');
285
+ if(innerAnnotation)
286
+ newAnno += '.@' + innerAnnotation;
287
+ edmUtils.assignAnnotation(object, newAnno, object._origin[attr]);
288
+ // delete original annotation only if it was qualified with $parameters
289
+ if(paramAnnoParts.length > 1)
290
+ delete object._origin[attr];
291
+ }
292
+ }
293
+ }
294
+ }
295
+ }
296
+ });
297
+
270
298
  // Crawl over the csn and trigger the annotation translation for all kinds
271
299
  // of annotated things.
272
300
  // Note: only works for single service
@@ -771,7 +799,7 @@ function csn2annotationEdm(reqDefs, csnVocabularies, serviceName,
771
799
  const [ prefix, innerAnnotation ] = a.split('.@');
772
800
  const ns = whatsMyTermNamespace(prefix);
773
801
  const steps = prefix.replace('@' + ns + '.', '').split('.');
774
- steps.splice(0,0, ns);
802
+ steps.splice(0, 0, ns);
775
803
  let i = steps.lastIndexOf('$edmJson');
776
804
  if(i > -1) {
777
805
  i = steps.findIndex(s => s.includes('@'), i+1);
@@ -1460,7 +1488,7 @@ function csn2annotationEdm(reqDefs, csnVocabularies, serviceName,
1460
1488
  function handleEdmJson(obj, msg, exprDef=undefined) {
1461
1489
 
1462
1490
  let edmNode = undefined;
1463
- if(obj === undefined || obj === null)
1491
+ if(obj == null)
1464
1492
  return edmNode;
1465
1493
 
1466
1494
  const dynExprs = edmUtils.intersect(dynamicExpressionNames, Object.keys(obj));
package/lib/edm/edm.js CHANGED
@@ -1220,13 +1220,16 @@ function getEdm(options, messageFunctions) {
1220
1220
  // V4 referential constraints!
1221
1221
  addReferentialConstraintNodes()
1222
1222
  {
1223
+ // flip the constrains if this is a $self partner
1223
1224
  let _constraints = this._csn._constraints;
1225
+ let [i,j] = [0,1];
1224
1226
  if(this._csn._constraints._partnerCsn) {
1225
1227
  _constraints = this._csn._constraints._partnerCsn._constraints;
1228
+ [i,j] = [1,0];
1226
1229
  }
1227
1230
  _constraints.constraints && Object.values(_constraints.constraints).forEach(c =>
1228
1231
  this.append(new ReferentialConstraint(this._v,
1229
- { Property: c[0].join(options.pathDelimiter), ReferencedProperty: c[1].join(options.pathDelimiter) } ) )
1232
+ { Property: c[i].join(options.pathDelimiter), ReferencedProperty: c[j].join(options.pathDelimiter) } ) )
1230
1233
  );
1231
1234
  }
1232
1235
  }
@@ -94,7 +94,7 @@ function initializeModel(csn, _options, messageFunctions, requestedServiceNames=
94
94
  as a step in the OData transformer with the goal to have a protocol agnostic OData CSN.
95
95
  */
96
96
  if (csn.meta && csn.meta.options && csn.meta.options.odataVersion === 'v4' && options.isV2()) {
97
- const { toFinalBaseType }= require('../transform/transformUtilsNew').getTransformers(csn, options);
97
+ const { toFinalBaseType }= require('../transform/transformUtils').getTransformers(csn, options);
98
98
  expandCSNToFinalBaseType(csn, { toFinalBaseType }, csnUtils, serviceRootNames, options);
99
99
  }
100
100
 
@@ -495,7 +495,7 @@ function initializeModel(csn, _options, messageFunctions, requestedServiceNames=
495
495
 
496
496
  // create the Parameter Definition
497
497
  const parameterCsn = createParameterEntity(entityCsn, entityName, false);
498
-
498
+ setProp(parameterCsn, '_origin', entityCsn);
499
499
  // create the Type Definition
500
500
  // modify the original parameter entity with backlink and new name
501
501
  if(csn.definitions[typeEntityName])
@@ -609,6 +609,12 @@ function initializeModel(csn, _options, messageFunctions, requestedServiceNames=
609
609
  [ 'definitions', parameterEntityName, 'elements', parameterToTypeAssocName ] );
610
610
  }
611
611
 
612
+ [ '@odata.singleton', '@odata.singleton.nullable' ].forEach(a => {
613
+ if(entityCsn[a] != null)
614
+ parameterCsn[a] = entityCsn[a];
615
+ delete entityCsn[a];
616
+ });
617
+
612
618
  // initialize containment
613
619
  // propagate containment information, if containment is recursive, use parameterCsn.name as $containerNames
614
620
  if(entityCsn.$containerNames) {
@@ -969,8 +975,8 @@ function initializeModel(csn, _options, messageFunctions, requestedServiceNames=
969
975
  edmUtils.foreach(struct.elements,
970
976
  e =>
971
977
  e['@odata.foreignKey4'] === element.name,
972
- e => e._ignore = true);
973
- element._ignore = true;
978
+ e => e.$ignore = true);
979
+ element.$ignore = true;
974
980
  info(null, ['definitions', struct.name, 'elements', element.name]
975
981
  `${element.type.replace('cds.', '')} "${element.name}" excluded,
976
982
  target "${element._target.name}" is annotated '@cds.autoexpose: ${element._target['@cds.autoexpose']}'`
@@ -1582,7 +1588,8 @@ function initializeModel(csn, _options, messageFunctions, requestedServiceNames=
1582
1588
  setProp(elt, '$visited', true);
1583
1589
  let newRefs = [];
1584
1590
  // if the foreign keys are explicitly requested, ignore associations and use the flat foreign key instead
1585
- if(!options.renderForeignKeys || (options.renderForeignKeys && !elt.target))
1591
+ // ignore nested unmanaged associations
1592
+ if((!options.renderForeignKeys || (options.renderForeignKeys && !elt.target)) && !(elt.target && elt.on))
1586
1593
  newRefs = produceKeyRefPaths(elt, prefix + options.pathDelimiter + eltName, path);
1587
1594
  if(newRefs.length) {
1588
1595
  keyPaths.push(...newRefs);
@@ -839,10 +839,8 @@ function mergeIntoNavPropEntry(annoPrefix, navPropEntry, prefix, props) {
839
839
  // Assign but not overwrite annotation
840
840
  function assignAnnotation(node, name, value) {
841
841
  if(value !== undefined &&
842
- name !== undefined && name[0] === '@' &&
843
- (node[name] === undefined || node[name] === null)) {
844
- node[name] = value;
845
- }
842
+ name !== undefined && name[0] === '@')
843
+ node[name] ??= value;
846
844
  }
847
845
 
848
846
  // Set non enumerable property if it doesn't exist yet
@@ -787,8 +787,7 @@
787
787
  ]
788
788
  },
789
789
  "Common.ValueListRelevantQualifiers": {
790
- "Type": "Collection(Core.SimpleIdentifier)",
791
- "$experimental": true
790
+ "Type": "Collection(Core.SimpleIdentifier)"
792
791
  },
793
792
  "Common.ValueListWithFixedValues": {
794
793
  "Type": "Core.Tag",
@@ -802,8 +801,7 @@
802
801
  "AppliesTo": [
803
802
  "Property",
804
803
  "Parameter"
805
- ],
806
- "$experimental": true
804
+ ]
807
805
  },
808
806
  "Common.ValueListReferences": {
809
807
  "Type": "Collection(Edm.String)",
@@ -1461,6 +1459,10 @@
1461
1459
  "Record"
1462
1460
  ]
1463
1461
  },
1462
+ "HTML5.LinkTarget": {
1463
+ "Type": "HTML5.LinkTargetType",
1464
+ "$experimental": true
1465
+ },
1464
1466
  "JSON.Schema": {
1465
1467
  "Type": "JSON.JSON",
1466
1468
  "AppliesTo": [
@@ -1561,15 +1563,13 @@
1561
1563
  "Type": "Edm.String",
1562
1564
  "AppliesTo": [
1563
1565
  "EntitySet"
1564
- ],
1565
- "$experimental": true
1566
+ ]
1566
1567
  },
1567
1568
  "PersonalData.DataSubjectRoleDescription": {
1568
1569
  "Type": "Edm.String",
1569
1570
  "AppliesTo": [
1570
1571
  "EntitySet"
1571
- ],
1572
- "$experimental": true
1572
+ ]
1573
1573
  },
1574
1574
  "PersonalData.FieldSemantics": {
1575
1575
  "Type": "PersonalData.FieldSemanticsType",
@@ -1834,8 +1834,7 @@
1834
1834
  "Type": "Edm.String",
1835
1835
  "AppliesTo": [
1836
1836
  "Property"
1837
- ],
1838
- "$experimental": true
1837
+ ]
1839
1838
  },
1840
1839
  "UI.TextArrangement": {
1841
1840
  "Type": "UI.TextArrangementType",
@@ -3435,6 +3434,31 @@
3435
3434
  "width": "Edm.String"
3436
3435
  }
3437
3436
  },
3437
+ "HTML5.LinkTargetType": {
3438
+ "$kind": "TypeDefinition",
3439
+ "UnderlyingType": "Edm.String",
3440
+ "$Allowed": {
3441
+ "Values": {
3442
+ "_self": {
3443
+ "Value": "_self",
3444
+ "Type": "String"
3445
+ },
3446
+ "_blank": {
3447
+ "Value": "_blank",
3448
+ "Type": "String"
3449
+ },
3450
+ "_parent": {
3451
+ "Value": "_parent",
3452
+ "Type": "String"
3453
+ },
3454
+ "_top": {
3455
+ "Value": "_top",
3456
+ "Type": "String"
3457
+ }
3458
+ },
3459
+ "Symbols": {}
3460
+ }
3461
+ },
3438
3462
  "JSON.JSON": {
3439
3463
  "$kind": "TypeDefinition",
3440
3464
  "UnderlyingType": "Edm.Stream"
@@ -1 +1 @@
1
- 8c23b0ea94048e8e7a54d2ed4a4e4707
1
+ 2c5e5aa4e688aa88877916a4d40340bf