@sap/cds-compiler 2.7.0 → 2.11.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 (87) hide show
  1. package/CHANGELOG.md +167 -0
  2. package/bin/cdsc.js +42 -25
  3. package/bin/cdsse.js +1 -0
  4. package/doc/CHANGELOG_BETA.md +10 -0
  5. package/lib/api/.eslintrc.json +2 -0
  6. package/lib/api/main.js +17 -33
  7. package/lib/api/options.js +25 -13
  8. package/lib/api/validate.js +33 -9
  9. package/lib/backends.js +9 -8
  10. package/lib/base/dictionaries.js +2 -1
  11. package/lib/base/keywords.js +32 -2
  12. package/lib/base/message-registry.js +26 -2
  13. package/lib/base/messages.js +25 -9
  14. package/lib/base/model.js +5 -3
  15. package/lib/base/optionProcessorHelper.js +56 -22
  16. package/lib/checks/onConditions.js +5 -0
  17. package/lib/checks/selectItems.js +4 -0
  18. package/lib/checks/types.js +26 -2
  19. package/lib/checks/unknownMagic.js +41 -0
  20. package/lib/checks/validator.js +7 -2
  21. package/lib/compiler/assert-consistency.js +18 -5
  22. package/lib/compiler/base.js +65 -0
  23. package/lib/compiler/builtins.js +30 -1
  24. package/lib/compiler/checks.js +5 -2
  25. package/lib/compiler/definer.js +145 -120
  26. package/lib/compiler/index.js +16 -4
  27. package/lib/compiler/propagator.js +5 -2
  28. package/lib/compiler/resolver.js +207 -47
  29. package/lib/compiler/shared.js +47 -200
  30. package/lib/compiler/utils.js +173 -0
  31. package/lib/edm/annotations/genericTranslation.js +183 -187
  32. package/lib/edm/csn2edm.js +94 -98
  33. package/lib/edm/edm.js +16 -20
  34. package/lib/edm/edmPreprocessor.js +302 -115
  35. package/lib/edm/edmUtils.js +31 -12
  36. package/lib/gen/language.checksum +1 -1
  37. package/lib/gen/language.interp +28 -1
  38. package/lib/gen/language.tokens +79 -69
  39. package/lib/gen/languageLexer.interp +28 -1
  40. package/lib/gen/languageLexer.js +879 -805
  41. package/lib/gen/languageLexer.tokens +71 -62
  42. package/lib/gen/languageParser.js +5308 -4308
  43. package/lib/json/from-csn.js +59 -30
  44. package/lib/json/to-csn.js +354 -105
  45. package/lib/language/antlrParser.js +11 -0
  46. package/lib/language/errorStrategy.js +1 -0
  47. package/lib/language/genericAntlrParser.js +81 -14
  48. package/lib/language/language.g4 +163 -31
  49. package/lib/main.d.ts +136 -17
  50. package/lib/main.js +7 -1
  51. package/lib/model/api.js +78 -0
  52. package/lib/model/csnRefs.js +115 -32
  53. package/lib/model/csnUtils.js +71 -33
  54. package/lib/model/enrichCsn.js +36 -9
  55. package/lib/model/revealInternalProperties.js +20 -4
  56. package/lib/modelCompare/compare.js +2 -1
  57. package/lib/optionProcessor.js +33 -16
  58. package/lib/render/.eslintrc.json +3 -1
  59. package/lib/render/DuplicateChecker.js +1 -1
  60. package/lib/render/toCdl.js +60 -17
  61. package/lib/render/toHdbcds.js +122 -74
  62. package/lib/render/toSql.js +57 -32
  63. package/lib/render/utils/common.js +6 -10
  64. package/lib/sql-identifier.js +6 -1
  65. package/lib/transform/db/constraints.js +273 -119
  66. package/lib/transform/db/draft.js +9 -6
  67. package/lib/transform/db/expansion.js +19 -7
  68. package/lib/transform/db/flattening.js +31 -7
  69. package/lib/transform/db/transformExists.js +344 -66
  70. package/lib/transform/db/views.js +438 -0
  71. package/lib/transform/forHanaNew.js +65 -436
  72. package/lib/transform/forOdataNew.js +21 -10
  73. package/lib/transform/localized.js +2 -0
  74. package/lib/transform/odata/attachPath.js +19 -4
  75. package/lib/transform/odata/generateForeignKeyElements.js +11 -10
  76. package/lib/transform/odata/referenceFlattener.js +44 -38
  77. package/lib/transform/odata/sortByAssociationDependency.js +2 -2
  78. package/lib/transform/odata/structuralPath.js +72 -0
  79. package/lib/transform/odata/structureFlattener.js +13 -10
  80. package/lib/transform/odata/typesExposure.js +22 -12
  81. package/lib/transform/transformUtilsNew.js +55 -9
  82. package/lib/transform/translateAssocsToJoins.js +11 -17
  83. package/lib/transform/universalCsnEnricher.js +67 -0
  84. package/lib/utils/file.js +5 -3
  85. package/lib/utils/term.js +65 -42
  86. package/lib/utils/timetrace.js +48 -26
  87. package/package.json +1 -1
@@ -33,12 +33,15 @@ let projectionAsQuery = false;
33
33
  let withLocations = false;
34
34
  let dictionaryPrototype = null;
35
35
 
36
+ // Properties for dictionaries, set in compileX() and TODO: parseX(), must be
37
+ // stored with symbols as keys, as we do not want to disallow any key name:
38
+ const $inferred = Symbol.for('cds.$inferred');
39
+
36
40
  // IMPORTANT: the order of these properties determine the order of properties
37
41
  // in the resulting CSN !!! Also check const `csnPropertyNames`.
38
42
  const transformers = {
39
43
  // early and modifiers (without null / not null) ---------------------------
40
44
  kind,
41
- _outer: ( _, csn, node ) => addOrigin( csn, node ),
42
45
  id: n => n, // in path item
43
46
  doc: value,
44
47
  '@': value,
@@ -67,7 +70,7 @@ const transformers = {
67
70
  targetAspect,
68
71
  target,
69
72
  foreignKeys,
70
- enum: insertOrderDict,
73
+ enum: enumDict,
71
74
  items,
72
75
  includes: arrayOf( artifactRef ), // also entities
73
76
  // late expressions / query properties -------------------------------------
@@ -80,6 +83,7 @@ const transformers = {
80
83
  where: condition, // also pathItem after 'cardinality' before 'args'
81
84
  having: condition,
82
85
  args, // also pathItem after 'where', before 'on'/'orderBy'
86
+ suffix: node => [].concat( ...node.suffix.map( xprArg ) ),
83
87
  orderBy: arrayOf( orderBy ), // TODO XSN: make `sort` and `nulls` sibling properties
84
88
  sort: value,
85
89
  nulls: value,
@@ -95,7 +99,7 @@ const transformers = {
95
99
  value: enumValue, // do not list for select items as elements
96
100
  query,
97
101
  elements,
98
- actions: nonEmptyDict, // TODO: just normal dictionary
102
+ actions, // TODO: just normal dictionary
99
103
  // special: top-level, cardinality -----------------------------------------
100
104
  sources,
101
105
  definitions: sortedDict,
@@ -191,6 +195,22 @@ const operators = {
191
195
  notLike: ternary( [ 'not', 'like' ], [ 'escape' ] ),
192
196
  when: exprs => [ 'when', ...exprs[0], 'then', ...exprs[1] ],
193
197
  case: exprs => [ 'case' ].concat( ...exprs, [ 'end' ] ),
198
+ over: exprs => [ 'over', { xpr: [].concat( ...exprs ) } ],
199
+ orderBy: exprs => [
200
+ 'order', 'by', ...exprs[0].concat( ...exprs.slice(1).map( e => [ ',', ...e ] ) ),
201
+ ],
202
+ partitionBy: exprs => [
203
+ 'partition', 'by', ...exprs[0].concat( ...exprs.slice(1).map( e => [ ',', ...e ] ) ),
204
+ ],
205
+ rows: exprs => [
206
+ 'rows', ...exprs[0].concat( ...exprs.slice(1).map( e => [ ',', ...e ] ) ),
207
+ ],
208
+ preceding: postfix( [ 'preceding' ] ),
209
+ unboundedPreceding: [ 'unbounded', 'preceding' ],
210
+ currentRow: [ 'current', 'row' ],
211
+ unboundedFollowing: [ 'unbounded', 'following' ],
212
+ following: postfix( [ 'following' ] ),
213
+ frameBetween: exprs => [ 'between', ...exprs[0], 'and', ...exprs[1] ],
194
214
  // xpr: (exprs) => [].concat( ...exprs ), see below - handled extra
195
215
  };
196
216
 
@@ -232,22 +252,36 @@ function sortCsn( csn, cloneOptions = false ) {
232
252
  r[n] = sortCsn(val, cloneOptions);
233
253
  }
234
254
  if (cloneOptions && typeof csn === 'object') {
235
- if (csn.$sources && !r.$sources)
236
- setHidden(r, '$sources', csn.$sources);
237
- if (csn.$location && !r.$location)
238
- setHidden(r, '$location', csn.$location);
239
- if (csn.$path) // used in generic reference flattener
240
- setHidden(r, '$path', csn.$path);
241
- if (csn.$paths) // used in generic reference flattener
242
- setHidden(r, '$paths', csn.$paths);
243
- if (csn.elements && !r.elements) // non-enumerable 'elements'
244
- setHidden(r, 'elements', csnDictionary( csn.elements, false, cloneOptions ) );
245
- if (csn.$tableConstraints && !r.$tableConstraints)
246
- setHidden(r, '$tableConstraints', csn.$tableConstraints);
255
+ if ({}.hasOwnProperty.call( csn, '$sources' ) && !r.$sources)
256
+ setHidden( r, '$sources', csn.$sources );
257
+ if ({}.hasOwnProperty.call( csn, '$location' ) && !r.$location)
258
+ setHidden( r, '$location', csn.$location );
259
+ if ({}.hasOwnProperty.call( csn, '$path' )) // used in generic reference flattener
260
+ setHidden( r, '$path', csn.$path );
261
+ if ({}.hasOwnProperty.call( csn, '$paths' )) // used in generic reference flattener
262
+ setHidden( r, '$paths', csn.$paths );
263
+ if (hasNonEnumerable( csn, 'elements' ) && !r.elements) // non-enumerable 'elements'
264
+ setHidden( r, 'elements', csnDictionary( csn.elements, false, cloneOptions ) );
265
+ if (hasNonEnumerable( csn, '$tableConstraints' ) && !r.$tableConstraints)
266
+ setHidden( r, '$tableConstraints', csn.$tableConstraints );
247
267
  }
248
268
  return r;
249
269
  }
250
270
 
271
+ /**
272
+ * Check wether the given object has non enumerable property.
273
+ * Ensure that we don't take it from the prototype, only "directly" - we accidentally
274
+ * cloned elements with a cds.linked input otherwise.
275
+ *
276
+ * @param {object} object
277
+ * @param {string} property
278
+ * @returns
279
+ */
280
+ function hasNonEnumerable(object, property) {
281
+ return {}.hasOwnProperty.call( object, property ) &&
282
+ !{}.propertyIsEnumerable.call( object, property );
283
+ }
284
+
251
285
  /**
252
286
  * @param {object} csn
253
287
  * @param {boolean} sort
@@ -359,10 +393,12 @@ function usings( srcDict ) {
359
393
  * @param {object} csn
360
394
  * @param {object} model
361
395
  */
396
+
397
+
362
398
  function extensions( node, csn, model ) {
363
399
  if (model.kind && model.kind !== 'source')
364
400
  return undefined;
365
- const exts = node.map( standard );
401
+ const exts = node.map( definition );
366
402
 
367
403
  // builtins are non-enumerable for smaller display
368
404
  for (const name of Object.getOwnPropertyNames( model.definitions || {} ).sort()) {
@@ -377,17 +413,21 @@ function extensions( node, csn, model ) {
377
413
  }
378
414
  else if (gensrcFlavor) {
379
415
  // From definitions (without redefinitions) with potential inferred elements:
380
- if (!Array.isArray(art) && art.elements &&
381
- (art.query || art.includes || art.$inferred)) {
382
- const annos = art.$inferred && annotationsAndDocComment( art, true );
383
- const elems = inferred( art.elements, art.$inferred );
384
- /** @type {object} */
385
- const annotate = Object.assign( { annotate: name }, annos );
386
- if (Object.keys( elems ).length)
387
- annotate.elements = elems;
388
- if (Object.keys( annotate ).length > 1)
389
- exts.push( annotate );
416
+ const annotate = { annotate: name };
417
+ if (art.$inferred)
418
+ Object.assign( annotate, annotationsAndDocComment( art, true ) );
419
+ if (art.$expand === 'annotate') {
420
+ if (art.actions)
421
+ attachAnnotations( annotate, 'actions', art.actions, art.$inferred );
422
+ else if (art.params)
423
+ attachAnnotations( annotate, 'params', art.params, art.$inferred );
424
+ const obj = art.returns || art;
425
+ const elems = (obj.items || obj).elements; // no targetAspect here
426
+ if (elems)
427
+ attachAnnotations( annotate, 'elements', elems, art.$inferred, art.returns );
390
428
  }
429
+ if (Object.keys( annotate ).length > 1)
430
+ exts.push( annotate );
391
431
  }
392
432
  }
393
433
 
@@ -395,6 +435,58 @@ function extensions( node, csn, model ) {
395
435
  (a, b) => (a.annotate || a.extend).localeCompare( b.annotate || b.extend )
396
436
  );
397
437
 
438
+ /*
439
+ function attachElementAnnos( annotate, art ) {
440
+ while (art.items)
441
+ art = art.items;
442
+ if (art.elements) {
443
+ const elems = inferred( art.elements, art.$inferred );
444
+ if (Object.keys( elems ).length)
445
+ annotate.elements = elems;
446
+ }
447
+ }
448
+
449
+ function attachParamAnnos( annotate, art ) {
450
+ const inferredParent = art.$inferred;
451
+ if (art.params) {
452
+ const ext = Object.create( dictionaryPrototype );
453
+ for (const name in art.params) {
454
+ const par = art.params[name];
455
+ if (!inferredParent && !par.$inferred && par.$expand !== 'annotate')
456
+ continue;
457
+ const render = annotationsAndDocComment( par, true );
458
+ const subElems = par.$expand !== 'origin' && (par.items || par).elements;
459
+ if (subElems) {
460
+ const sub = inferred( subElems, par.$inferred );
461
+ if (Object.keys( sub ).length)
462
+ render.elements = sub;
463
+ }
464
+ if (Object.keys(render).length)
465
+ ext[name] = render;
466
+ }
467
+ if (obj.keys( ext ))
468
+ annotate.params = ext;
469
+ }
470
+ if (art.returns) {
471
+ const par = art.returns;
472
+ if (!inferredParent && !par.$inferred && par.$expand !== 'annotate')
473
+ return;
474
+ const render = annotationsAndDocComment( par, true );
475
+ const subElems = par.$expand !== 'origin' && (par.items || par).elements;
476
+ if (subElems) {
477
+ const sub = inferred( subElems, par.$inferred );
478
+ if (Object.keys( sub ).length)
479
+ render.elements = sub;
480
+ }
481
+ if (Object.keys(render).length)
482
+ const sub = inferred( subElems, par.$inferred );
483
+ if (Object.keys( sub ).length)
484
+ render.elements = sub;
485
+ }
486
+ }
487
+ return ext;
488
+ */
489
+
398
490
  // extract namespace/builtin annotations
399
491
  function extractAnnotationsToExtension( art ) {
400
492
  const name = art.name.absolute;
@@ -456,17 +548,29 @@ function sources( srcDict, csn ) {
456
548
  }
457
549
  }
458
550
 
459
- function inferred( elems, inferredParent ) {
460
- const ext = Object.create( dictionaryPrototype );
461
- for (const name in elems) {
462
- const elem = elems[name];
463
- if (Array.isArray(elem) || !inferredParent && !elem.$inferred)
464
- continue;
465
- const csn = annotationsAndDocComment( elem, true );
466
- if (Object.keys(csn).length)
467
- ext[name] = csn;
551
+ function attachAnnotations( annotate, prop, dict, inferred, returns = false ) {
552
+ const annoDict = Object.create( dictionaryPrototype );
553
+ for (const name in dict) {
554
+ const elem = dict[name];
555
+ const inf = inferred || elem.$inferred; // is probably always inferred if parent was
556
+ const sub = (inf) ? annotationsAndDocComment( elem, true ) : {};
557
+ if (elem.$expand === 'annotate') {
558
+ if (elem.params)
559
+ attachAnnotations( sub, 'params', elem.params, inf );
560
+ const obj = elem.returns || elem;
561
+ const elems = (obj.items || obj.targetAspect || obj).elements;
562
+ if (elems)
563
+ attachAnnotations( sub, 'elements', elems, inf, elem.returns );
564
+ }
565
+ if (Object.keys( sub ).length)
566
+ annoDict[name] = sub;
567
+ }
568
+ if (Object.keys( annoDict ).length) {
569
+ if (returns)
570
+ annotate.returns = { elements: annoDict };
571
+ else
572
+ annotate[prop] = annoDict;
468
573
  }
469
- return ext;
470
574
  }
471
575
 
472
576
  function standard( node ) {
@@ -505,10 +609,13 @@ function set( prop, csn, node ) {
505
609
  }
506
610
 
507
611
  function targetAspect( val, csn, node ) {
612
+ const ta = (val.elements)
613
+ ? addLocation( val.location, standard( val ) )
614
+ : artifactRef( val, true );
508
615
  if (!gensrcFlavor || node.target && !node.target.$inferred)
509
- return (val.elements) ? standard( val ) : artifactRef( val, true );
616
+ return ta;
510
617
  // For compatibility, put aspect in 'target' with parse.cdl and csn flavor 'gensrc'
511
- csn.target = (val.elements) ? standard( val ) : artifactRef( val, true );
618
+ csn.target = ta;
512
619
  return undefined;
513
620
  }
514
621
 
@@ -527,11 +634,9 @@ function target( val, _csn, node ) {
527
634
  }
528
635
 
529
636
  function items( obj, csn, node ) {
530
- if (!keepElements( node ))
531
- // no 'elements' with SELECT or inferred elements with gensrc;
532
- // hidden 'elements' will be set in query()
637
+ if (!keepElements( node, obj ))
533
638
  return undefined;
534
- return standard( obj );
639
+ return standard( obj ); // no 'elements' with inferred elements with gensrc
535
640
  }
536
641
 
537
642
  function elements( dict, csn, node ) {
@@ -544,6 +649,18 @@ function elements( dict, csn, node ) {
544
649
  return insertOrderDict( dict );
545
650
  }
546
651
 
652
+ function enumDict( dict, csn, node ) {
653
+ if (gensrcFlavor && dict[$inferred] ||
654
+ !keepElements( node ))
655
+ // no 'elements' with SELECT or inferred elements with gensrc;
656
+ // hidden or visible 'elements' will be set in query()
657
+ return undefined;
658
+ if (universalCsn && node.type && !node.type.$inferred && node.$expand === 'annotate')
659
+ // derived type of enum type with individual annotations: also set $origin
660
+ csn.$origin = originRef( node.type._artifact );
661
+ return insertOrderDict( dict );
662
+ }
663
+
547
664
  function enumerableQueryElements( select ) {
548
665
  if (!universalCsn || select === select._main._leadingQuery)
549
666
  return false;
@@ -553,21 +670,35 @@ function enumerableQueryElements( select ) {
553
670
  return alias.query && (alias.query._leadingQuery || alias.query) === select;
554
671
  }
555
672
 
556
- // We do not optimize away elements which are potentially adapted during their
557
- // way from the original structure type definition to the current usage
558
- function keepElements( node ) {
673
+ // Should we render the elements? (and items?)
674
+ function keepElements( node, line ) {
559
675
  if (universalCsn)
560
- // if there are neither _origin, nor type, $expand won't be set and this
561
- // function returns true automatically
676
+ // $expand = null/undefined: not elements not via expansion
677
+ // $expand = 'target'/'annotate': with redirections / individual annotations
562
678
  return node.$expand !== 'origin';
563
679
  if (!node.type || node.kind === 'type')
564
680
  return true;
681
+ // keep many SimpleType/Entity
682
+ if (line) {
683
+ if (!node.type)
684
+ return true;
685
+ const array = node.type._artifact; // see function items() in propagator.js
686
+ const ltype = line.type && line.type._artifact;
687
+ if (!array || // reference errors
688
+ array._main && !line.elements && !line.enum && !line.items && !line.notNull &&
689
+ (!ltype || !ltype._main)) // many Foo:bar -> not SimpleType
690
+ return true;
691
+ }
692
+ // even if expanded elements have no new target or direct annotation,
693
+ // they might have got one via propagation – any new target/annos during their
694
+ // way from the original structure type definition to the current usage
565
695
  while (node) {
566
696
  if (node.$expand !== 'origin')
567
697
  return true;
568
698
  node = node._origin;
569
699
  }
570
- return false;
700
+ // all in _origin chain only have expanded elements with 'origin':
701
+ return false; // no need to render elements
571
702
  }
572
703
 
573
704
  // for gensrcFlavor and namespace/builtin annotation extraction:
@@ -653,17 +784,17 @@ function sortedDict( dict ) {
653
784
  return dictionary( dict, keys );
654
785
  }
655
786
 
656
- function nonEmptyDict( dict ) {
787
+ function actions( dict ) {
657
788
  const keys = Object.keys( dict );
658
789
  return (keys.length)
659
- ? dictionary( dict, keys )
790
+ ? dictionary( dict, keys, 'actions' )
660
791
  : undefined;
661
792
  }
662
793
 
663
- function dictionary( dict, keys ) {
794
+ function dictionary( dict, keys, prop ) {
664
795
  const csn = Object.create( dictionaryPrototype );
665
796
  for (const name of keys) {
666
- const def = definition( dict[name] );
797
+ const def = definition( dict[name], null, null, prop );
667
798
  if (def !== undefined)
668
799
  csn[name] = def;
669
800
  }
@@ -686,7 +817,7 @@ function foreignKeys( dict, csn, node ) {
686
817
  csn.keys = keys;
687
818
  }
688
819
 
689
- function definition( art ) {
820
+ function definition( art, _csn, _node, prop ) {
690
821
  if (!art || typeof art !== 'object')
691
822
  return undefined; // TODO: complain with strict
692
823
  // Do not include namespace definitions or inferred construct (in gensrc):
@@ -698,44 +829,150 @@ function definition( art ) {
698
829
  addLocation( art.targetElement.location, key );
699
830
  return extra( key, art );
700
831
  }
701
- return standard( art );
832
+ const c = standard( art );
833
+ // The XSN of actions in extensions do not contain a returns yet - TODO?
834
+ const elems = c.elements;
835
+ if (elems && (prop === 'actions' || art.$syntax === 'returns')) {
836
+ delete c.elements;
837
+ c.returns = { elements: elems };
838
+ }
839
+ if (kind && kind !== 'key')
840
+ addOrigin( c, art, art._origin );
841
+ return c;
702
842
  }
703
843
 
704
- function addOrigin( csn, xsn ) {
705
- if (!universalCsn)
706
- return csn;
844
+ // create $origin specification for `includes` of `art`
845
+ function includesOrigin( includes, art ) {
846
+ const $origin = originRef( includes[0]._artifact );
847
+ if (includes.length === 1)
848
+ return $origin;
849
+ const result = { $origin };
850
+ for (const incl of includes.slice(1)) {
851
+ const aspect = incl._artifact;
852
+ for (const prop in aspect) {
853
+ if (prop.charAt(0) === '@' && (!art[prop] || art[prop].$inferred)) {
854
+ const anno = aspect[prop];
855
+ if (anno.val !== null)
856
+ // matererialize non-null annos (whether direct or inherited)
857
+ result[prop] = value( Object.create( anno, { $inferred: { value: null } } ) );
858
+ }
859
+ }
860
+ }
861
+ return (Object.keys( result ).length === 1) ? $origin : result;
862
+ }
863
+
864
+ function addOrigin( csn, xsn, origin ) {
865
+ if (!universalCsn || hasExplicitProp( xsn.type ))
866
+ return;
707
867
  if (xsn._from) {
708
- csn.$origin = originRef( xsn._from[0]._origin );
868
+ const source = xsn._from[0]._origin;
869
+ csn.$origin = originRef( source );
870
+ if (source.params && !xsn.params)
871
+ csn.params = null; // discontinue `params` inheritance
872
+ if (source.actions && !xsn.actions)
873
+ csn.actions = null; // discontinue `actions` inheritance
874
+ return;
709
875
  }
710
- else if (xsn.includes && xsn.includes.length > 1) {
711
- csn.$origin = { $origin: originRef( xsn.includes[0]._artifact ) };
876
+ else if (xsn.includes) {
877
+ csn.$origin = includesOrigin( xsn.includes, xsn );
878
+ return;
712
879
  }
713
- else if (xsn._origin && !hasExplicitProp( xsn.type ) && xsn._origin.kind !== 'builtin') {
714
- let origin = xsn._origin;
715
- while (origin._parent && origin._parent.$expand === 'origin')
716
- origin = origin._origin || origin.type._artifact;
717
- csn.$origin = originRef( origin );
880
+ else if (!xsn._main || xsn.kind === 'select') {
881
+ return;
718
882
  }
719
- return csn;
883
+ const parent = getParent( xsn );
884
+ const parentOrigin = getOrigin( parent );
885
+ if (!xsn._origin || xsn._origin.kind === 'builtin') { // or $dollarVariable
886
+ if (parentOrigin && (!parent.enum || parent.$origin || !parent.type))
887
+ csn.$origin = null;
888
+ return;
889
+ }
890
+ // Skip all proxies which do not make it into the CSN, as there are no
891
+ // individual annotations or redirection targets on it:
892
+ while (origin._parent && origin._parent.$expand === 'origin')
893
+ origin = origin._origin || origin.type._artifact;
894
+ // The while loop is not only for the else case below: when setting implicit
895
+ // prototypes, it is important that we do not have to follow the prototypes of
896
+ // other object; we would need to ensure a right order to avoid issues otherwise.
897
+ if (parentOrigin === getParent( origin )) {
898
+ // implicit prototype or shortened reference
899
+ const { id } = origin.name || {};
900
+ if (id && xsn.name && id !== xsn.name.id)
901
+ csn.$origin = id;
902
+ return;
903
+ }
904
+ if (origin.kind === 'mixin') {
905
+ // currently, target and on are always set - nothing to do here, just set type
906
+ csn.type = 'cds.Association';
907
+ return;
908
+ }
909
+ const ref = originRef( origin, xsn );
910
+ if (ref) {
911
+ csn.$origin = ref;
912
+ return;
913
+ }
914
+ // An element of a query with a query in FROM:
915
+ const anon = definition( origin ); // use $origin: {...} if necessary
916
+ // as there are no implicit $origin prototypes on sub query elements (yet),
917
+ // we do not have to care about $origin not being set
918
+ const { $origin } = anon;
919
+ if ($origin && typeof $origin === 'object' && !Array.isArray( $origin )) {
920
+ // repeated anon: flatten
921
+ csn.$origin = Object.assign( $origin, anon );
922
+ }
923
+ else if (Object.keys( anon )
924
+ // (we can use the properties in `csn`, because addOrigin() is called last)
925
+ .every( p => p in csn || p === '$origin' || p === '$location')) {
926
+ // nothing new in $origin: {...}
927
+ addOrigin( csn, xsn, origin._origin );
928
+ }
929
+ else {
930
+ csn.$origin = anon;
931
+ }
932
+ }
933
+
934
+ function getParent( art ) {
935
+ const parent = art._parent;
936
+ const main = parent._main;
937
+ return (main && parent === main._leadingQuery) ? main : parent;
938
+ }
939
+
940
+ function getOrigin( art ) {
941
+ if (art._origin)
942
+ return art._origin;
943
+ if (hasExplicitProp( art.type ))
944
+ return art.type._artifact;
945
+ if (art.includes)
946
+ return art.includes[0]._artifact;
947
+ if (art._from)
948
+ return art._from[0]._origin;
949
+ return undefined;
720
950
  }
721
951
 
722
952
  function hasExplicitProp( ref ) {
723
953
  return ref && !ref.$inferred;
724
954
  }
725
955
 
726
- function originRef( art ) {
956
+ function originRef( art, user ) {
727
957
  const r = [];
728
958
  // do not use name.element, as we allow `.`s in name
729
- let main = art;
730
- while (main._main && main.kind !== 'select') {
731
- const nkind = normalizedKind[main.kind];
732
- if (main.name.id || !r.length) // { param: "" } only for return, not elements inside
733
- r.push( nkind ? { [nkind]: main.name.id } : main.name.id );
734
- main = main._parent;
959
+ let parent = art;
960
+ while (parent._main && parent.kind !== 'select') {
961
+ const nkind = normalizedKind[parent.kind];
962
+ if (parent.name.id || !r.length)
963
+ // Return parameter is in XSN - kind: 'param', name.id: ''
964
+ // eslint-disable-next-line no-nested-ternary, max-len
965
+ r.push( !nkind ? parent.name.id : parent.name.id ? { [nkind]: parent.name.id } : { return: true } );
966
+ parent = parent._parent;
735
967
  }
736
- if (main._main) // well, an element of an query in FROM
737
- return definition( art ); // use $origin: {}
968
+ if (user && parent._main && parent._main === user._main && parent !== user._main._leadingQuery)
969
+ // well, an element of an query in FROM (TODO: try with sub elem), but not the leading query
970
+ return null; // probably use $origin: {...}
738
971
  // for sub query in FROM in sub query in FROM, we could condense the info
972
+
973
+ // Now the ref, with ["absolute", "action"] instead of ["absolute", {action:"action"}]
974
+ if (r.length === 1 && normalizedKind[art.kind] === 'action')
975
+ return [ art.name.absolute, art.name.id ];
739
976
  r.push( art.name.absolute );
740
977
  r.reverse();
741
978
  return r;
@@ -750,13 +987,11 @@ function kind( k, csn, node ) {
750
987
  else if (k === 'extend')
751
988
  csn.kind = k;
752
989
  }
753
- else {
754
- if (![
755
- 'element', 'key', 'param', 'enum', 'select', '$join',
756
- '$tableAlias', 'annotation', 'mixin',
757
- ].includes(k))
758
- csn.kind = k;
759
- addOrigin( csn, node );
990
+ else if (![
991
+ 'element', 'key', 'param', 'enum', 'select', '$join',
992
+ '$tableAlias', 'annotation', 'mixin',
993
+ ].includes(k)) {
994
+ csn.kind = k;
760
995
  }
761
996
  }
762
997
 
@@ -865,10 +1100,12 @@ function args( node ) {
865
1100
  return dict;
866
1101
  }
867
1102
 
868
- // "Short" value form, e.g. for annotation assignments
869
1103
  function value( node ) {
1104
+ // "Short" value form, e.g. for annotation assignments
870
1105
  if (!node)
871
1106
  return true; // `@aBool` short for `@aBool: true`
1107
+ if (universalCsn && node.$inferred === 'prop') // via propagator.js
1108
+ return undefined;
872
1109
  if (node.$inferred && gensrcFlavor)
873
1110
  return undefined;
874
1111
  if (node.path) {
@@ -892,7 +1129,10 @@ function value( node ) {
892
1129
 
893
1130
  function enumValue( v, csn, node ) {
894
1131
  // Enums can have values but if enums are extended, their kind is 'element',
895
- // so we check whether the node is inside an extension.
1132
+ // so we check whether the node is inside an extension. (TODO: still?)
1133
+ if (universalCsn && v.$inferred)
1134
+ return;
1135
+ // (with gensrc, the symbol itself would not make it into the CSN)
896
1136
  if (node.kind === 'enum' || node._parent && node._parent.kind === 'extend')
897
1137
  Object.assign( csn, expression( v, true ) );
898
1138
  }
@@ -911,7 +1151,7 @@ function onCondition( cond, csn, node ) {
911
1151
  function condition( node ) {
912
1152
  const expr = expression( node );
913
1153
  // we do not set a hidden $parens on array - we could still do it if requested
914
- return !expr.cast && expr.xpr || [ expr ];
1154
+ return !expr.cast && !expr.func && expr.xpr || [ expr ];
915
1155
  }
916
1156
 
917
1157
  function expression( node, dollarExtra ) {
@@ -951,10 +1191,12 @@ function expression( node, dollarExtra ) {
951
1191
  arg0.xpr.unshift( quantifier.val );
952
1192
  }
953
1193
  }
1194
+ if (node.suffix)
1195
+ call.xpr = [].concat( ...node.suffix.map( xprArg ) );
954
1196
  return extra( call, dollarExtraNode );
955
1197
  }
956
1198
  if (node.query)
957
- return query( node.query, null, null, 1 );
1199
+ return query( node.query, null, null, null, 1 );
958
1200
  if (!node.op) // parse error
959
1201
  return { xpr: [] };
960
1202
  else if (node.op.val === 'xpr')
@@ -964,7 +1206,7 @@ function expression( node, dollarExtra ) {
964
1206
  return cast( expression( node.args[0] ), dollarExtraNode );
965
1207
  // from here on: CDL input (no $extra possible - but $parens)
966
1208
  else if (node.op.val !== ',')
967
- return extra( { xpr: xpr( node ) }, dollarExtraNode, 1 );
1209
+ return extra( { xpr: xpr( node ) }, dollarExtraNode, (dollarExtra === 'sub-xpr' ? 1 : 0) );
968
1210
  return (parensAsStrings)
969
1211
  ? { xpr: [ '(', ...xpr( node ), ')' ] }
970
1212
  // the inner parens belong to the tuple construct, i.e. won't count as parens
@@ -974,15 +1216,7 @@ function expression( node, dollarExtra ) {
974
1216
  function xpr( node ) {
975
1217
  // if (!node.op) console.log(node)
976
1218
  const op = operators[node.op.val] || node.op.val.split(' ');
977
- const exprs = node.args.map( ( sub ) => {
978
- const expr = expression( sub );
979
- // return !sub.$parens && !expr.cast && expr.xpr || [ expr ]; if parensAsStrings is gone
980
- if (expr.cast || !expr.xpr || sub.$parens && !parensAsStrings)
981
- return [ expr ];
982
- else if (sub.$parens && sub.op.val !== ',')
983
- return [ '(', ...expr.xpr, ')' ];
984
- return expr.xpr;
985
- } );
1219
+ const exprs = node.args.map( xprArg );
986
1220
  if (op instanceof Function)
987
1221
  return op( exprs );
988
1222
  if (node.quantifier)
@@ -992,6 +1226,27 @@ function xpr( node ) {
992
1226
  return exprs[0].concat( ...exprs.slice(1).map( a => [ ...op, ...a ] ) );
993
1227
  }
994
1228
 
1229
+ function xprArg( sub ) {
1230
+ const realXpr = sub.op && sub.op.val === 'xpr';
1231
+ const expr = expression( sub, 'sub-xpr' );
1232
+ // `sort`/`nulls` will be attached to arguments of orderBy
1233
+ // which might be either `path`s or `xpr`s
1234
+ const sortAndNulls = [];
1235
+ if (sub.sort)
1236
+ sortAndNulls.push( sub.sort.val );
1237
+ if (sub.nulls)
1238
+ sortAndNulls.push( ...[ 'nulls', sub.nulls.val ] );
1239
+ // return !sub.$parens && !expr.cast && !expr.func && expr.xpr || [ expr ];
1240
+ // if parensAsStrings is gone
1241
+ if (realXpr || expr.cast || expr.func || !expr.xpr || sub.$parens && !parensAsStrings)
1242
+ return [ expr, ...sortAndNulls ];
1243
+ else if (sub.$parens && sub.op.val !== ',')
1244
+ return [ '(', ...expr.xpr, ')' ];
1245
+
1246
+ expr.xpr.push( ...sortAndNulls );
1247
+ return expr.xpr;
1248
+ }
1249
+
995
1250
  function ternary( op1, op2 ) {
996
1251
  return function ternaryOp( exprs ) {
997
1252
  return (exprs[2])
@@ -1015,7 +1270,7 @@ function binaryRightParen( op ) {
1015
1270
  };
1016
1271
  }
1017
1272
 
1018
- function query( node, csn, xsn, expectedParens = 0 ) {
1273
+ function query( node, csn, xsn, _prop, expectedParens = 0 ) {
1019
1274
  if (node.op.val === 'SELECT') {
1020
1275
  if (xsn && xsn.query === node && xsn.$syntax === 'projection' &&
1021
1276
  node.from && node.from.path && !projectionAsQuery) {
@@ -1065,7 +1320,7 @@ function columns( xsnColumns, csn, xsn ) {
1065
1320
  addElementAsColumn( col, csnColumns );
1066
1321
  }
1067
1322
  }
1068
- else { // null = use elements
1323
+ else { // null = use elements - TODO: still used by A2J? -> remove
1069
1324
  for (const name in xsn.elements)
1070
1325
  addElementAsColumn( xsn.elements[name], csnColumns );
1071
1326
  }
@@ -1087,7 +1342,7 @@ function from( node ) {
1087
1342
  return extra( join, node );
1088
1343
  }
1089
1344
  else if (node.query) {
1090
- return addExplicitAs( query( node.query, null, null, 1 ), node.name );
1345
+ return addExplicitAs( query( node.query, null, null, null, 1 ), node.name );
1091
1346
  }
1092
1347
  else if (!node._artifact || node._artifact._main) { // CQL or follow assoc
1093
1348
  return extra( addExplicitAs( artifactRef( node, false ), node.name ), node );
@@ -1132,12 +1387,6 @@ function addElementAsColumn( elem, cols ) {
1132
1387
  finally {
1133
1388
  gensrcFlavor = gensrcSaved;
1134
1389
  }
1135
- // FIXME: Currently toHana requires that an '_ignore' property on the elem is
1136
- // also visible on the column. Don't ignore virtual columns, let the
1137
- // renderer decide how to render that column.
1138
- if (!elem.virtual && elem._ignore)
1139
- col._ignore = true;
1140
-
1141
1390
  if (elem.value && !elem.$inferred) {
1142
1391
  const parens = elem.value.$parens;
1143
1392
  if (parens)