@sap/cds-compiler 2.5.0 → 2.10.4

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 (92) hide show
  1. package/CHANGELOG.md +191 -9
  2. package/bin/cdsc.js +2 -2
  3. package/doc/CHANGELOG_BETA.md +33 -3
  4. package/lib/api/main.js +29 -101
  5. package/lib/api/options.js +15 -11
  6. package/lib/api/validate.js +12 -8
  7. package/lib/backends.js +0 -81
  8. package/lib/base/keywords.js +32 -2
  9. package/lib/base/message-registry.js +63 -9
  10. package/lib/base/messages.js +63 -21
  11. package/lib/base/model.js +2 -3
  12. package/lib/checks/defaultValues.js +27 -2
  13. package/lib/checks/elements.js +1 -6
  14. package/lib/checks/foreignKeys.js +0 -6
  15. package/lib/checks/managedWithoutKeys.js +17 -0
  16. package/lib/checks/nonexpandableStructured.js +38 -0
  17. package/lib/checks/onConditions.js +9 -45
  18. package/lib/checks/queryNoDbArtifacts.js +25 -7
  19. package/lib/checks/selectItems.js +25 -2
  20. package/lib/checks/types.js +26 -2
  21. package/lib/checks/unknownMagic.js +38 -0
  22. package/lib/checks/utils.js +61 -0
  23. package/lib/checks/validator.js +60 -7
  24. package/lib/compiler/assert-consistency.js +16 -7
  25. package/lib/compiler/builtins.js +2 -0
  26. package/lib/compiler/checks.js +6 -4
  27. package/lib/compiler/definer.js +99 -42
  28. package/lib/compiler/index.js +73 -27
  29. package/lib/compiler/resolver.js +288 -157
  30. package/lib/compiler/shared.js +31 -11
  31. package/lib/edm/annotations/genericTranslation.js +182 -186
  32. package/lib/edm/csn2edm.js +103 -108
  33. package/lib/edm/edm.js +18 -21
  34. package/lib/edm/edmPreprocessor.js +361 -114
  35. package/lib/edm/edmUtils.js +103 -33
  36. package/lib/gen/Dictionary.json +22 -0
  37. package/lib/gen/language.checksum +1 -1
  38. package/lib/gen/language.interp +12 -1
  39. package/lib/gen/language.tokens +57 -53
  40. package/lib/gen/languageLexer.interp +10 -1
  41. package/lib/gen/languageLexer.js +770 -744
  42. package/lib/gen/languageLexer.tokens +49 -46
  43. package/lib/gen/languageParser.js +4713 -4279
  44. package/lib/json/from-csn.js +103 -45
  45. package/lib/json/to-csn.js +296 -117
  46. package/lib/language/antlrParser.js +4 -3
  47. package/lib/language/errorStrategy.js +1 -0
  48. package/lib/language/genericAntlrParser.js +21 -12
  49. package/lib/language/language.g4 +99 -31
  50. package/lib/main.d.ts +81 -3
  51. package/lib/main.js +30 -7
  52. package/lib/model/api.js +78 -0
  53. package/lib/model/csnRefs.js +329 -142
  54. package/lib/model/csnUtils.js +235 -58
  55. package/lib/model/enrichCsn.js +18 -1
  56. package/lib/model/revealInternalProperties.js +2 -1
  57. package/lib/modelCompare/compare.js +37 -20
  58. package/lib/optionProcessor.js +9 -3
  59. package/lib/render/.eslintrc.json +4 -1
  60. package/lib/render/DuplicateChecker.js +8 -5
  61. package/lib/render/toCdl.js +112 -33
  62. package/lib/render/toHdbcds.js +134 -64
  63. package/lib/render/toSql.js +91 -38
  64. package/lib/render/utils/common.js +8 -13
  65. package/lib/render/utils/sql.js +3 -3
  66. package/lib/sql-identifier.js +6 -1
  67. package/lib/transform/db/assertUnique.js +5 -6
  68. package/lib/transform/db/constraints.js +29 -13
  69. package/lib/transform/db/draft.js +8 -6
  70. package/lib/transform/db/expansion.js +582 -0
  71. package/lib/transform/db/flattening.js +325 -0
  72. package/lib/transform/db/groupByOrderBy.js +2 -2
  73. package/lib/transform/db/transformExists.js +284 -63
  74. package/lib/transform/forHanaNew.js +98 -381
  75. package/lib/transform/forOdataNew.js +21 -22
  76. package/lib/transform/localized.js +37 -10
  77. package/lib/transform/odata/attachPath.js +19 -4
  78. package/lib/transform/odata/generateForeignKeyElements.js +11 -10
  79. package/lib/transform/odata/referenceFlattener.js +60 -39
  80. package/lib/transform/odata/sortByAssociationDependency.js +2 -2
  81. package/lib/transform/odata/structuralPath.js +72 -0
  82. package/lib/transform/odata/structureFlattener.js +19 -18
  83. package/lib/transform/odata/typesExposure.js +22 -12
  84. package/lib/transform/transformUtilsNew.js +134 -78
  85. package/lib/transform/translateAssocsToJoins.js +17 -14
  86. package/lib/transform/universalCsnEnricher.js +67 -0
  87. package/lib/utils/file.js +0 -11
  88. package/lib/utils/moduleResolve.js +6 -8
  89. package/package.json +1 -1
  90. package/lib/json/walker.js +0 -26
  91. package/lib/transform/sqlite +0 -0
  92. package/lib/utils/string.js +0 -17
@@ -12,14 +12,21 @@
12
12
  'use strict';
13
13
 
14
14
  const { locationString } = require('../base/messages');
15
- const { isDeprecatedEnabled } = require('../base/model');
15
+ const { isBetaEnabled, isDeprecatedEnabled } = require('../base/model');
16
16
 
17
17
  const compilerVersion = require('../../package.json').version;
18
18
  const creator = `CDS Compiler v${ compilerVersion }`;
19
19
  const csnVersion = '2.0';
20
20
 
21
+ const normalizedKind = {
22
+ param: 'param',
23
+ action: 'action',
24
+ function: 'action',
25
+ };
26
+
21
27
  /** @type {boolean|string} */
22
28
  let gensrcFlavor = true; // good enough here...
29
+ let universalCsn = false;
23
30
  let strictMode = false; // whether to dump with unknown properties (in standard)
24
31
  let parensAsStrings = false;
25
32
  let projectionAsQuery = false;
@@ -31,6 +38,7 @@ let dictionaryPrototype = null;
31
38
  const transformers = {
32
39
  // early and modifiers (without null / not null) ---------------------------
33
40
  kind,
41
+ _outer: ( _, csn, node ) => addOrigin( csn, node ),
34
42
  id: n => n, // in path item
35
43
  doc: value,
36
44
  '@': value,
@@ -50,7 +58,7 @@ const transformers = {
50
58
  all: ignore, // XSN TODO use quantifier
51
59
  // type properties (without 'elements') ------------------------------------
52
60
  localized: value,
53
- type: t => artifactRef( t, !t.$extra ),
61
+ type,
54
62
  length: value,
55
63
  precision: value,
56
64
  scale: value,
@@ -72,6 +80,7 @@ const transformers = {
72
80
  where: condition, // also pathItem after 'cardinality' before 'args'
73
81
  having: condition,
74
82
  args, // also pathItem after 'where', before 'on'/'orderBy'
83
+ suffix: node => [].concat( ...node.suffix.map( xprArg ) ),
75
84
  orderBy: arrayOf( orderBy ), // TODO XSN: make `sort` and `nulls` sibling properties
76
85
  sort: value,
77
86
  nulls: value,
@@ -80,14 +89,14 @@ const transformers = {
80
89
  offset: expression,
81
90
  on: onCondition,
82
91
  // definitions, extensions, members ----------------------------------------
83
- returns: standard, // storing the return type of actions
92
+ returns: definition, // storing the return type of actions
84
93
  notNull: value,
85
94
  default: expression,
86
95
  // targetElement: ignore, // special display of foreign key, renameTo: select
87
96
  value: enumValue, // do not list for select items as elements
88
97
  query,
89
98
  elements,
90
- actions: nonEmptyDict, // TODO: just normal dictionary
99
+ actions, // TODO: just normal dictionary
91
100
  // special: top-level, cardinality -----------------------------------------
92
101
  sources,
93
102
  definitions: sortedDict,
@@ -128,7 +137,7 @@ const transformers = {
128
137
  // which should appear at that place in order.
129
138
  const csnPropertyNames = {
130
139
  virtual: [ 'abstract' ], // abstract is compiler v1 CSN property
131
- kind: [ 'annotate', 'extend' ],
140
+ kind: [ 'annotate', 'extend', '$origin' ],
132
141
  op: [ 'join', 'func', 'xpr' ], // TODO: 'func','xpr' into 'quantifier'? TODO: 'global'(scope)?
133
142
  quantifier: [
134
143
  'some', 'any', 'distinct', // 'all' explicitly listed
@@ -148,7 +157,7 @@ const csnPropertyNames = {
148
157
  name: [ 'as', 'cast' ],
149
158
  location: [ '$env', '$location' ], // --enrich-csn
150
159
  expectedKind: [
151
- '_type', '_targetAspect', '_target', '_includes', '_links', '_art', '_scope',
160
+ '_origin', '_type', '_targetAspect', '_target', '_includes', '_links', '_art', '_scope',
152
161
  ], // --enrich-csn
153
162
  };
154
163
 
@@ -183,6 +192,13 @@ const operators = {
183
192
  notLike: ternary( [ 'not', 'like' ], [ 'escape' ] ),
184
193
  when: exprs => [ 'when', ...exprs[0], 'then', ...exprs[1] ],
185
194
  case: exprs => [ 'case' ].concat( ...exprs, [ 'end' ] ),
195
+ over: exprs => [ 'over', { xpr: [].concat( ...exprs ) } ],
196
+ orderBy: exprs => [
197
+ 'order', 'by', ...exprs[0].concat( ...exprs.slice(1).map( e => [ ',', ...e ] ) ),
198
+ ],
199
+ partitionBy: exprs => [
200
+ 'partition', 'by', ...exprs[0].concat( ...exprs.slice(1).map( e => [ ',', ...e ] ) ),
201
+ ],
186
202
  // xpr: (exprs) => [].concat( ...exprs ), see below - handled extra
187
203
  };
188
204
 
@@ -194,7 +210,7 @@ const csnDirectValues = [ 'val' ]; // + all starting with '@' - TODO: still rele
194
210
  /**
195
211
  * Sort property names of CSN according to sequence which is also used by the compactModel function
196
212
  * Only returns enumerable properties, except for certain hidden properties
197
- * if requested (cloneOptions != false): $location, $env, elements.
213
+ * if requested (cloneOptions != false): $location, elements.
198
214
  *
199
215
  * If cloneOptions is false or if either cloneOptions.testMode or cloneOptions.testSortCsn
200
216
  * are set, definitions are also sorted.
@@ -224,24 +240,36 @@ function sortCsn( csn, cloneOptions = false ) {
224
240
  r[n] = sortCsn(val, cloneOptions);
225
241
  }
226
242
  if (cloneOptions && typeof csn === 'object') {
227
- if (csn.$sources && !r.$sources)
228
- setHidden(r, '$sources', csn.$sources);
229
- if (csn.$location && !r.$location)
230
- setHidden(r, '$location', csn.$location);
231
- if (csn.$env)
232
- setHidden(r, '$env', csn.$env);
233
- if (csn.$path) // used in generic reference flattener
234
- setHidden(r, '$path', csn.$path);
235
- if (csn.$paths) // used in generic reference flattener
236
- setHidden(r, '$paths', csn.$paths);
237
- if (csn.elements && !r.elements) // non-enumerable 'elements'
238
- setHidden(r, 'elements', csnDictionary( csn.elements, false, cloneOptions ) );
239
- if (csn.$tableConstraints && !r.$tableConstraints)
240
- setHidden(r, '$tableConstraints', csn.$tableConstraints);
243
+ if ({}.hasOwnProperty.call( csn, '$sources' ) && !r.$sources)
244
+ setHidden( r, '$sources', csn.$sources );
245
+ if ({}.hasOwnProperty.call( csn, '$location' ) && !r.$location)
246
+ setHidden( r, '$location', csn.$location );
247
+ if ({}.hasOwnProperty.call( csn, '$path' )) // used in generic reference flattener
248
+ setHidden( r, '$path', csn.$path );
249
+ if ({}.hasOwnProperty.call( csn, '$paths' )) // used in generic reference flattener
250
+ setHidden( r, '$paths', csn.$paths );
251
+ if (hasNonEnumerable( csn, 'elements' ) && !r.elements) // non-enumerable 'elements'
252
+ setHidden( r, 'elements', csnDictionary( csn.elements, false, cloneOptions ) );
253
+ if (hasNonEnumerable( csn, '$tableConstraints' ) && !r.$tableConstraints)
254
+ setHidden( r, '$tableConstraints', csn.$tableConstraints );
241
255
  }
242
256
  return r;
243
257
  }
244
258
 
259
+ /**
260
+ * Check wether the given object has non enumerable property.
261
+ * Ensure that we don't take it from the prototype, only "directly" - we accidentally
262
+ * cloned elements with a cds.linked input otherwise.
263
+ *
264
+ * @param {object} object
265
+ * @param {string} property
266
+ * @returns
267
+ */
268
+ function hasNonEnumerable(object, property) {
269
+ return {}.hasOwnProperty.call( object, property ) &&
270
+ !{}.propertyIsEnumerable.call( object, property );
271
+ }
272
+
245
273
  /**
246
274
  * @param {object} csn
247
275
  * @param {boolean} sort
@@ -353,10 +381,12 @@ function usings( srcDict ) {
353
381
  * @param {object} csn
354
382
  * @param {object} model
355
383
  */
384
+
385
+
356
386
  function extensions( node, csn, model ) {
357
387
  if (model.kind && model.kind !== 'source')
358
388
  return undefined;
359
- const exts = node.map( standard );
389
+ const exts = node.map( definition );
360
390
 
361
391
  // builtins are non-enumerable for smaller display
362
392
  for (const name of Object.getOwnPropertyNames( model.definitions || {} ).sort()) {
@@ -371,17 +401,21 @@ function extensions( node, csn, model ) {
371
401
  }
372
402
  else if (gensrcFlavor) {
373
403
  // From definitions (without redefinitions) with potential inferred elements:
374
- if (!Array.isArray(art) && art.elements &&
375
- (art.query || art.includes || art.$inferred)) {
376
- const annos = art.$inferred && annotationsAndDocComment( art, true );
377
- const elems = inferred( art.elements, art.$inferred );
378
- /** @type {object} */
379
- const annotate = Object.assign( { annotate: name }, annos );
380
- if (Object.keys( elems ).length)
381
- annotate.elements = elems;
382
- if (Object.keys( annotate ).length > 1)
383
- exts.push( annotate );
404
+ const annotate = { annotate: name };
405
+ if (art.$inferred)
406
+ Object.assign( annotate, annotationsAndDocComment( art, true ) );
407
+ if (art.$expand === 'annotate') {
408
+ if (art.actions)
409
+ attachAnnotations( annotate, 'actions', art.actions, art.$inferred );
410
+ else if (art.params)
411
+ attachAnnotations( annotate, 'params', art.params, art.$inferred );
412
+ const obj = art.returns || art;
413
+ const elems = (obj.items || obj).elements; // no targetAspect here
414
+ if (elems)
415
+ attachAnnotations( annotate, 'elements', elems, art.$inferred, art.returns );
384
416
  }
417
+ if (Object.keys( annotate ).length > 1)
418
+ exts.push( annotate );
385
419
  }
386
420
  }
387
421
 
@@ -389,6 +423,58 @@ function extensions( node, csn, model ) {
389
423
  (a, b) => (a.annotate || a.extend).localeCompare( b.annotate || b.extend )
390
424
  );
391
425
 
426
+ /*
427
+ function attachElementAnnos( annotate, art ) {
428
+ while (art.items)
429
+ art = art.items;
430
+ if (art.elements) {
431
+ const elems = inferred( art.elements, art.$inferred );
432
+ if (Object.keys( elems ).length)
433
+ annotate.elements = elems;
434
+ }
435
+ }
436
+
437
+ function attachParamAnnos( annotate, art ) {
438
+ const inferredParent = art.$inferred;
439
+ if (art.params) {
440
+ const ext = Object.create( dictionaryPrototype );
441
+ for (const name in art.params) {
442
+ const par = art.params[name];
443
+ if (!inferredParent && !par.$inferred && par.$expand !== 'annotate')
444
+ continue;
445
+ const render = annotationsAndDocComment( par, true );
446
+ const subElems = par.$expand !== 'origin' && (par.items || par).elements;
447
+ if (subElems) {
448
+ const sub = inferred( subElems, par.$inferred );
449
+ if (Object.keys( sub ).length)
450
+ render.elements = sub;
451
+ }
452
+ if (Object.keys(render).length)
453
+ ext[name] = render;
454
+ }
455
+ if (obj.keys( ext ))
456
+ annotate.params = ext;
457
+ }
458
+ if (art.returns) {
459
+ const par = art.returns;
460
+ if (!inferredParent && !par.$inferred && par.$expand !== 'annotate')
461
+ return;
462
+ const render = annotationsAndDocComment( par, true );
463
+ const subElems = par.$expand !== 'origin' && (par.items || par).elements;
464
+ if (subElems) {
465
+ const sub = inferred( subElems, par.$inferred );
466
+ if (Object.keys( sub ).length)
467
+ render.elements = sub;
468
+ }
469
+ if (Object.keys(render).length)
470
+ const sub = inferred( subElems, par.$inferred );
471
+ if (Object.keys( sub ).length)
472
+ render.elements = sub;
473
+ }
474
+ }
475
+ return ext;
476
+ */
477
+
392
478
  // extract namespace/builtin annotations
393
479
  function extractAnnotationsToExtension( art ) {
394
480
  const name = art.name.absolute;
@@ -450,17 +536,29 @@ function sources( srcDict, csn ) {
450
536
  }
451
537
  }
452
538
 
453
- function inferred( elems, inferredParent ) {
454
- const ext = Object.create( dictionaryPrototype );
455
- for (const name in elems) {
456
- const elem = elems[name];
457
- if (Array.isArray(elem) || !inferredParent && !elem.$inferred)
458
- continue;
459
- const csn = annotationsAndDocComment( elem, true );
460
- if (Object.keys(csn).length)
461
- ext[name] = csn;
539
+ function attachAnnotations( annotate, prop, dict, inferred, returns = false ) {
540
+ const annoDict = Object.create( dictionaryPrototype );
541
+ for (const name in dict) {
542
+ const elem = dict[name];
543
+ const inf = inferred || elem.$inferred; // is probably always inferred if parent was
544
+ const sub = (inf) ? annotationsAndDocComment( elem, true ) : {};
545
+ if (elem.$expand === 'annotate') {
546
+ if (elem.params)
547
+ attachAnnotations( sub, 'params', elem.params, inf );
548
+ const obj = elem.returns || elem;
549
+ const elems = (obj.items || obj.targetAspect || obj).elements;
550
+ if (elems)
551
+ attachAnnotations( sub, 'elements', elems, inf, elem.returns );
552
+ }
553
+ if (Object.keys( sub ).length)
554
+ annoDict[name] = sub;
555
+ }
556
+ if (Object.keys( annoDict ).length) {
557
+ if (returns)
558
+ annotate.returns = { elements: annoDict };
559
+ else
560
+ annotate[prop] = annoDict;
462
561
  }
463
- return ext;
464
562
  }
465
563
 
466
564
  function standard( node ) {
@@ -499,48 +597,73 @@ function set( prop, csn, node ) {
499
597
  }
500
598
 
501
599
  function targetAspect( val, csn, node ) {
600
+ const ta = (val.elements)
601
+ ? addLocation( val.location, standard( val ) )
602
+ : artifactRef( val, true );
502
603
  if (!gensrcFlavor || node.target && !node.target.$inferred)
503
- return (val.elements) ? standard( val ) : artifactRef( val, true );
604
+ return ta;
504
605
  // For compatibility, put aspect in 'target' with parse.cdl and csn flavor 'gensrc'
505
- csn.target = (val.elements) ? standard( val ) : artifactRef( val, true );
606
+ csn.target = ta;
506
607
  return undefined;
507
608
  }
508
609
 
509
610
  function target( val, _csn, node ) {
510
611
  if (gensrcFlavor && node._origin && node._origin.$inferred === 'REDIRECTED')
511
612
  val = node._origin.target;
512
- if (!val.elements)
613
+ if (val.elements)
614
+ return standard( val ); // elements in target (parse-cdl)
615
+ if (!universalCsn || node.on)
513
616
  return artifactRef( val, true );
514
- return standard( val ); // elements in target (parse-cdl)
617
+ const tref = artifactRef( val, true );
618
+ const proto = node.type && !node.type.$inferred ? node.type._artifact : node._origin;
619
+ return (proto && proto.target && artifactRef( proto.target, true ) === tref)
620
+ ? undefined
621
+ : tref;
515
622
  }
516
623
 
517
624
  function items( obj, csn, node ) {
518
- if (!keepElements( node ) && node.type && node.kind !== 'type')
519
- // no 'elements' with SELECT or inferred elements with gensrc;
520
- // hidden 'elements' will be set in query()
625
+ if (!keepElements( node ))
521
626
  return undefined;
522
- return standard( obj );
627
+ return standard( obj ); // no 'elements' with inferred elements with gensrc
523
628
  }
524
629
 
525
630
  function elements( dict, csn, node ) {
526
- if (csn.from ||
631
+ if (node.from || // do not directly show query elements here
527
632
  gensrcFlavor && (node.query || node.type) ||
528
- node.type && node.kind !== 'type' && !keepElements( node ))
633
+ !keepElements( node ))
529
634
  // no 'elements' with SELECT or inferred elements with gensrc;
530
- // hidden 'elements' will be set in query()
635
+ // hidden or visible 'elements' will be set in query()
531
636
  return undefined;
532
637
  return insertOrderDict( dict );
533
638
  }
534
639
 
535
- // We do not optimize away elements which are potentially adapted during their
536
- // way from the original structure type definition to the current usage
640
+ function enumerableQueryElements( select ) {
641
+ if (!universalCsn || select === select._main._leadingQuery)
642
+ return false;
643
+ if (select.orderBy || select.$orderBy)
644
+ return true;
645
+ const alias = select._parent;
646
+ return alias.query && (alias.query._leadingQuery || alias.query) === select;
647
+ }
648
+
649
+ // Should we render the elements? (and items?)
537
650
  function keepElements( node ) {
651
+ if (universalCsn)
652
+ // $expand = null/undefined: not elements not via expansion
653
+ // $expand = 'target'/'annotate': with redirections / individual annotations
654
+ return node.$expand !== 'origin';
655
+ if (!node.type || node.kind === 'type')
656
+ return true;
657
+ // even if expanded elements have no new target or direct annotation,
658
+ // they might have got one via propagation – any new target/annos during their
659
+ // way from the original structure type definition to the current usage
538
660
  while (node) {
539
661
  if (node.$expand !== 'origin')
540
662
  return true;
541
663
  node = node._origin;
542
664
  }
543
- return false;
665
+ // all in _origin chain only have expanded elements with 'origin':
666
+ return false; // no need to render elements
544
667
  }
545
668
 
546
669
  // for gensrcFlavor and namespace/builtin annotation extraction:
@@ -626,17 +749,17 @@ function sortedDict( dict ) {
626
749
  return dictionary( dict, keys );
627
750
  }
628
751
 
629
- function nonEmptyDict( dict ) {
752
+ function actions( dict ) {
630
753
  const keys = Object.keys( dict );
631
754
  return (keys.length)
632
- ? dictionary( dict, keys )
755
+ ? dictionary( dict, keys, 'actions' )
633
756
  : undefined;
634
757
  }
635
758
 
636
- function dictionary( dict, keys ) {
759
+ function dictionary( dict, keys, prop ) {
637
760
  const csn = Object.create( dictionaryPrototype );
638
761
  for (const name of keys) {
639
- const def = definition( dict[name] );
762
+ const def = definition( dict[name], null, null, prop );
640
763
  if (def !== undefined)
641
764
  csn[name] = def;
642
765
  }
@@ -644,6 +767,8 @@ function dictionary( dict, keys ) {
644
767
  }
645
768
 
646
769
  function foreignKeys( dict, csn, node ) {
770
+ if (universalCsn && !target( node.target, csn, node ))
771
+ return;
647
772
  if (gensrcFlavor && node._origin && node._origin.$inferred === 'REDIRECTED')
648
773
  dict = node._origin.foreignKeys;
649
774
  const keys = [];
@@ -657,7 +782,7 @@ function foreignKeys( dict, csn, node ) {
657
782
  csn.keys = keys;
658
783
  }
659
784
 
660
- function definition( art ) {
785
+ function definition( art, _csn, _node, prop ) {
661
786
  if (!art || typeof art !== 'object')
662
787
  return undefined; // TODO: complain with strict
663
788
  // Do not include namespace definitions or inferred construct (in gensrc):
@@ -669,22 +794,79 @@ function definition( art ) {
669
794
  addLocation( art.targetElement.location, key );
670
795
  return extra( key, art );
671
796
  }
672
- return standard( art );
797
+ const c = standard( art );
798
+ // The XSN of actions in extensions do not contain a returns yet - TODO?
799
+ const elems = c.elements;
800
+ if (elems && (prop === 'actions' || art.$syntax === 'returns')) {
801
+ delete c.elements;
802
+ c.returns = { elements: elems };
803
+ }
804
+ return c;
805
+ }
806
+
807
+ function addOrigin( csn, xsn ) {
808
+ if (!universalCsn)
809
+ return csn;
810
+ if (xsn._from) {
811
+ csn.$origin = originRef( xsn._from[0]._origin );
812
+ }
813
+ else if (xsn.includes && xsn.includes.length > 1) {
814
+ csn.$origin = { $origin: originRef( xsn.includes[0]._artifact ) };
815
+ }
816
+ else if (xsn._origin && !hasExplicitProp( xsn.type ) && xsn._origin.kind !== 'builtin') {
817
+ let origin = xsn._origin;
818
+ while (origin._parent && origin._parent.$expand === 'origin')
819
+ origin = origin._origin || origin.type._artifact;
820
+ csn.$origin = originRef( origin );
821
+ }
822
+ return csn;
823
+ }
824
+
825
+ function hasExplicitProp( ref ) {
826
+ return ref && !ref.$inferred;
827
+ }
828
+
829
+ function originRef( art ) {
830
+ const r = [];
831
+ // do not use name.element, as we allow `.`s in name
832
+ let main = art;
833
+ while (main._main && main.kind !== 'select') {
834
+ const nkind = normalizedKind[main.kind];
835
+ if (main.name.id || !r.length) // { param: "" } only for return, not elements inside
836
+ r.push( nkind ? { [nkind]: main.name.id } : main.name.id );
837
+ main = main._parent;
838
+ }
839
+ if (main._main) // well, an element of an query in FROM
840
+ return definition( art ); // use $origin: {}
841
+ // for sub query in FROM in sub query in FROM, we could condense the info
842
+ r.push( art.name.absolute );
843
+ r.reverse();
844
+ return r;
673
845
  }
674
846
 
675
847
  function kind( k, csn, node ) {
676
- if (!node._main && [ 'annotate', 'extend' ].includes( k )) {
848
+ if (k === 'annotate' || k === 'extend') {
677
849
  // We just use `name.absolute` because it is very likely a "constructed"
678
850
  // extensions. The CSN parser must produce name.path like for other refs.
679
- csn[k] = node.name.absolute || artifactRef( node.name, true );
680
- return undefined;
851
+ if (!node._main)
852
+ csn[k] = node.name.absolute || artifactRef( node.name, true );
853
+ else if (k === 'extend')
854
+ csn.kind = k;
855
+ }
856
+ else {
857
+ if (![
858
+ 'element', 'key', 'param', 'enum', 'select', '$join',
859
+ '$tableAlias', 'annotation', 'mixin',
860
+ ].includes(k))
861
+ csn.kind = k;
862
+ addOrigin( csn, node );
681
863
  }
682
- if ([
683
- 'element', 'key', 'param', 'enum', 'annotate', 'select', '$join',
684
- '$tableAlias', 'annotation', 'mixin',
685
- ].includes(k))
864
+ }
865
+
866
+ function type( node, csn, xsn ) {
867
+ if (universalCsn && node.$inferred && xsn._origin)
686
868
  return undefined;
687
- return k;
869
+ return artifactRef( node, !node.$extra );
688
870
  }
689
871
 
690
872
  function artifactRef( node, terse ) {
@@ -786,10 +968,12 @@ function args( node ) {
786
968
  return dict;
787
969
  }
788
970
 
789
- // "Short" value form, e.g. for annotation assignments
790
971
  function value( node ) {
972
+ // "Short" value form, e.g. for annotation assignments
791
973
  if (!node)
792
974
  return true; // `@aBool` short for `@aBool: true`
975
+ if (universalCsn && node.$inferred === 'prop') // via propagator.js
976
+ return undefined;
793
977
  if (node.$inferred && gensrcFlavor)
794
978
  return undefined;
795
979
  if (node.path) {
@@ -832,23 +1016,7 @@ function onCondition( cond, csn, node ) {
832
1016
  function condition( node ) {
833
1017
  const expr = expression( node );
834
1018
  // we do not set a hidden $parens on array - we could still do it if requested
835
- return !expr.cast && expr.xpr || [ expr ];
836
- }
837
-
838
- // TODO: quoted magic names like $now should be complained about in the compiler
839
-
840
- function pathRef( path ) {
841
- const ref = { ref: path.map( pathItem ) };
842
- const nav = path[0]._navigation;
843
- if (nav && nav.kind !== '$self' && nav.kind !== 'element' && nav.name.select != null) {
844
- setHidden( ref, '$env', (nav.kind === '$navElement')
845
- ? nav.name.alias
846
- : nav.name.select );
847
- }
848
- else if ( path[0]._artifact && path[0]._artifact.query ) {
849
- setHidden( ref, '$env', true );
850
- }
851
- return ref;
1019
+ return !expr.cast && !expr.func && expr.xpr || [ expr ];
852
1020
  }
853
1021
 
854
1022
  function expression( node, dollarExtra ) {
@@ -863,8 +1031,8 @@ function expression( node, dollarExtra ) {
863
1031
  return { ref: [ node.param.val ], param: true }; // CDL rule for runtimes
864
1032
  }
865
1033
  if (node.path) {
866
- // TODO: global
867
- return extra( pathRef( node.path ), dollarExtraNode );
1034
+ // we would need to consider node.global here if we introduce that
1035
+ return extra( { ref: node.path.map( pathItem ) }, dollarExtraNode );
868
1036
  }
869
1037
  if (node.literal) {
870
1038
  if (typeof node.val === node.literal || node.val === null)
@@ -888,10 +1056,12 @@ function expression( node, dollarExtra ) {
888
1056
  arg0.xpr.unshift( quantifier.val );
889
1057
  }
890
1058
  }
1059
+ if (node.suffix)
1060
+ call.xpr = [].concat( ...node.suffix.map( xprArg ) );
891
1061
  return extra( call, dollarExtraNode );
892
1062
  }
893
1063
  if (node.query)
894
- return query( node.query, null, null, 1 );
1064
+ return query( node.query, null, null, null, 1 );
895
1065
  if (!node.op) // parse error
896
1066
  return { xpr: [] };
897
1067
  else if (node.op.val === 'xpr')
@@ -901,7 +1071,7 @@ function expression( node, dollarExtra ) {
901
1071
  return cast( expression( node.args[0] ), dollarExtraNode );
902
1072
  // from here on: CDL input (no $extra possible - but $parens)
903
1073
  else if (node.op.val !== ',')
904
- return extra( { xpr: xpr( node ) }, dollarExtraNode, 1 );
1074
+ return extra( { xpr: xpr( node ) }, dollarExtraNode, (dollarExtra === 'sub-xpr' ? 1 : 0) );
905
1075
  return (parensAsStrings)
906
1076
  ? { xpr: [ '(', ...xpr( node ), ')' ] }
907
1077
  // the inner parens belong to the tuple construct, i.e. won't count as parens
@@ -911,15 +1081,7 @@ function expression( node, dollarExtra ) {
911
1081
  function xpr( node ) {
912
1082
  // if (!node.op) console.log(node)
913
1083
  const op = operators[node.op.val] || node.op.val.split(' ');
914
- const exprs = node.args.map( ( sub ) => {
915
- const expr = expression( sub );
916
- // return !sub.$parens && !expr.cast && expr.xpr || [ expr ]; if parensAsStrings is gone
917
- if (expr.cast || !expr.xpr || sub.$parens && !parensAsStrings)
918
- return [ expr ];
919
- else if (sub.$parens && sub.op.val !== ',')
920
- return [ '(', ...expr.xpr, ')' ];
921
- return expr.xpr;
922
- } );
1084
+ const exprs = node.args.map( xprArg );
923
1085
  if (op instanceof Function)
924
1086
  return op( exprs );
925
1087
  if (node.quantifier)
@@ -929,6 +1091,27 @@ function xpr( node ) {
929
1091
  return exprs[0].concat( ...exprs.slice(1).map( a => [ ...op, ...a ] ) );
930
1092
  }
931
1093
 
1094
+ function xprArg( sub ) {
1095
+ const realXpr = sub.op && sub.op.val === 'xpr';
1096
+ const expr = expression( sub, 'sub-xpr' );
1097
+ // `sort`/`nulls` will be attached to arguments of orderBy
1098
+ // which might be either `path`s or `xpr`s
1099
+ const sortAndNulls = [];
1100
+ if (sub.sort)
1101
+ sortAndNulls.push( sub.sort.val );
1102
+ if (sub.nulls)
1103
+ sortAndNulls.push( ...[ 'nulls', sub.nulls.val ] );
1104
+ // return !sub.$parens && !expr.cast && !expr.func && expr.xpr || [ expr ];
1105
+ // if parensAsStrings is gone
1106
+ if (realXpr || expr.cast || expr.func || !expr.xpr || sub.$parens && !parensAsStrings)
1107
+ return [ expr, ...sortAndNulls ];
1108
+ else if (sub.$parens && sub.op.val !== ',')
1109
+ return [ '(', ...expr.xpr, ')' ];
1110
+
1111
+ expr.xpr.push( ...sortAndNulls );
1112
+ return expr.xpr;
1113
+ }
1114
+
932
1115
  function ternary( op1, op2 ) {
933
1116
  return function ternaryOp( exprs ) {
934
1117
  return (exprs[2])
@@ -952,7 +1135,7 @@ function binaryRightParen( op ) {
952
1135
  };
953
1136
  }
954
1137
 
955
- function query( node, csn, xsn, expectedParens = 0 ) {
1138
+ function query( node, csn, xsn, _prop, expectedParens = 0 ) {
956
1139
  if (node.op.val === 'SELECT') {
957
1140
  if (xsn && xsn.query === node && xsn.$syntax === 'projection' &&
958
1141
  node.from && node.from.path && !projectionAsQuery) {
@@ -969,7 +1152,10 @@ function query( node, csn, xsn, expectedParens = 0 ) {
969
1152
  const gensrcSaved = gensrcFlavor;
970
1153
  try {
971
1154
  gensrcFlavor = false;
972
- setHidden( select.SELECT, 'elements', insertOrderDict( elems ) );
1155
+ if (enumerableQueryElements( node ))
1156
+ select.SELECT.elements = insertOrderDict( elems );
1157
+ else
1158
+ setHidden( select.SELECT, 'elements', insertOrderDict( elems ) );
973
1159
  }
974
1160
  finally {
975
1161
  gensrcFlavor = gensrcSaved;
@@ -1021,7 +1207,7 @@ function from( node ) {
1021
1207
  return extra( join, node );
1022
1208
  }
1023
1209
  else if (node.query) {
1024
- return addExplicitAs( query( node.query, null, null, 1 ), node.name );
1210
+ return addExplicitAs( query( node.query, null, null, null, 1 ), node.name );
1025
1211
  }
1026
1212
  else if (!node._artifact || node._artifact._main) { // CQL or follow assoc
1027
1213
  return extra( addExplicitAs( artifactRef( node, false ), node.name ), node );
@@ -1057,25 +1243,15 @@ function addElementAsColumn( elem, cols ) {
1057
1243
  col.excluding = Object.keys( elem.excludingDict );
1058
1244
  // yes, the AS comes after the EXPAND
1059
1245
  addExplicitAs( col, elem.name, neqPath( elem.value ) );
1060
- // $env and elements (sub queries) in expr are hidden (not set via Object.assign):
1061
- if (!expr.cast) {
1062
- if (expr.$env)
1063
- setHidden( col, '$env', expr.$env );
1064
- if (expr.elements)
1065
- setHidden( col, 'elements', expr.elements );
1066
- }
1246
+ // elements of sub queries (in expr) are hidden (not set via Object.assign):
1247
+ if (!expr.cast && expr.elements)
1248
+ setHidden( col, 'elements', expr.elements );
1067
1249
  if (elem.type && !elem.type.$inferred || elem.target && !elem.target.$inferred)
1068
1250
  cast( col, elem );
1069
1251
  }
1070
1252
  finally {
1071
1253
  gensrcFlavor = gensrcSaved;
1072
1254
  }
1073
- // FIXME: Currently toHana requires that an '_ignore' property on the elem is
1074
- // also visible on the column. Don't ignore virtual columns, let the
1075
- // renderer decide how to render that column.
1076
- if (!elem.virtual && elem._ignore)
1077
- col._ignore = true;
1078
-
1079
1255
  if (elem.value && !elem.$inferred) {
1080
1256
  const parens = elem.value.$parens;
1081
1257
  if (parens)
@@ -1162,6 +1338,9 @@ function compactExpr( e ) { // TODO: options
1162
1338
  function initModuleVars( options = { csnFlavor: 'gensrc' } ) {
1163
1339
  gensrcFlavor = options.parseCdl || options.csnFlavor === 'gensrc' ||
1164
1340
  options.toCsn && options.toCsn.flavor === 'gensrc';
1341
+ universalCsn = (options.csnFlavor === 'universal' ||
1342
+ options.toCsn && options.toCsn.flavor === 'universal' ) &&
1343
+ isBetaEnabled( options, 'enableUniversalCsn' ) && !options.parseCdl;
1165
1344
  strictMode = options.testMode;
1166
1345
  const proto = options.dictionaryPrototype;
1167
1346
  // eslint-disable-next-line no-nested-ternary