@sap/cds-compiler 2.4.4 → 2.10.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 (106) hide show
  1. package/CHANGELOG.md +241 -1
  2. package/bin/.eslintrc.json +17 -0
  3. package/bin/cds_update_identifiers.js +8 -7
  4. package/bin/cdsc.js +180 -132
  5. package/bin/cdshi.js +18 -11
  6. package/bin/cdsse.js +38 -32
  7. package/bin/cdsv2m.js +8 -7
  8. package/doc/CHANGELOG_BETA.md +36 -1
  9. package/lib/api/main.js +81 -100
  10. package/lib/api/options.js +17 -11
  11. package/lib/api/validate.js +12 -8
  12. package/lib/backends.js +0 -81
  13. package/lib/base/keywords.js +32 -2
  14. package/lib/base/location.js +2 -2
  15. package/lib/base/message-registry.js +66 -4
  16. package/lib/base/messages.js +84 -27
  17. package/lib/base/model.js +2 -61
  18. package/lib/checks/arrayOfs.js +0 -1
  19. package/lib/checks/defaultValues.js +27 -2
  20. package/lib/checks/elements.js +1 -6
  21. package/lib/checks/enricher.js +8 -2
  22. package/lib/checks/foreignKeys.js +0 -6
  23. package/lib/checks/managedWithoutKeys.js +17 -0
  24. package/lib/checks/nonexpandableStructured.js +38 -0
  25. package/lib/checks/onConditions.js +9 -45
  26. package/lib/checks/queryNoDbArtifacts.js +27 -9
  27. package/lib/checks/selectItems.js +25 -2
  28. package/lib/checks/types.js +26 -2
  29. package/lib/checks/unknownMagic.js +38 -0
  30. package/lib/checks/utils.js +61 -0
  31. package/lib/checks/validator.js +66 -13
  32. package/lib/compiler/assert-consistency.js +24 -12
  33. package/lib/compiler/builtins.js +2 -0
  34. package/lib/compiler/checks.js +6 -4
  35. package/lib/compiler/definer.js +101 -39
  36. package/lib/compiler/index.js +88 -59
  37. package/lib/compiler/resolver.js +455 -209
  38. package/lib/compiler/shared.js +57 -33
  39. package/lib/edm/annotations/genericTranslation.js +183 -187
  40. package/lib/edm/csn2edm.js +128 -99
  41. package/lib/edm/edm.js +18 -21
  42. package/lib/edm/edmPreprocessor.js +361 -127
  43. package/lib/edm/edmUtils.js +103 -33
  44. package/lib/gen/Dictionary.json +74 -28
  45. package/lib/gen/language.checksum +1 -1
  46. package/lib/gen/language.interp +18 -4
  47. package/lib/gen/language.tokens +124 -118
  48. package/lib/gen/languageLexer.interp +13 -1
  49. package/lib/gen/languageLexer.js +870 -839
  50. package/lib/gen/languageLexer.tokens +116 -111
  51. package/lib/gen/languageParser.js +5894 -5614
  52. package/lib/json/from-csn.js +152 -67
  53. package/lib/json/to-csn.js +334 -135
  54. package/lib/language/antlrParser.js +4 -3
  55. package/lib/language/errorStrategy.js +1 -0
  56. package/lib/language/genericAntlrParser.js +24 -14
  57. package/lib/language/language.g4 +188 -128
  58. package/lib/main.d.ts +435 -0
  59. package/lib/main.js +31 -7
  60. package/lib/model/api.js +78 -0
  61. package/lib/model/csnRefs.js +463 -187
  62. package/lib/model/csnUtils.js +280 -136
  63. package/lib/model/enrichCsn.js +75 -4
  64. package/lib/model/revealInternalProperties.js +2 -1
  65. package/lib/modelCompare/compare.js +70 -25
  66. package/lib/optionProcessor.js +13 -10
  67. package/lib/render/.eslintrc.json +4 -1
  68. package/lib/render/DuplicateChecker.js +8 -5
  69. package/lib/render/toCdl.js +123 -40
  70. package/lib/render/toHdbcds.js +156 -65
  71. package/lib/render/toSql.js +87 -11
  72. package/lib/render/utils/common.js +55 -9
  73. package/lib/render/utils/sql.js +3 -3
  74. package/lib/sql-identifier.js +6 -1
  75. package/lib/transform/{sql → db}/.eslintrc.json +0 -0
  76. package/lib/transform/{sql → db}/assertUnique.js +7 -8
  77. package/lib/transform/{sql → db}/constraints.js +35 -20
  78. package/lib/transform/db/draft.js +353 -0
  79. package/lib/transform/db/expansion.js +582 -0
  80. package/lib/transform/db/flattening.js +325 -0
  81. package/lib/transform/{sql → db}/groupByOrderBy.js +8 -16
  82. package/lib/transform/{sql → db}/helpers.js +0 -0
  83. package/lib/transform/{sql → db}/transformExists.js +256 -60
  84. package/lib/transform/forHanaNew.js +216 -765
  85. package/lib/transform/forOdataNew.js +60 -56
  86. package/lib/transform/localized.js +48 -26
  87. package/lib/transform/odata/attachPath.js +19 -4
  88. package/lib/transform/odata/expandStructKeysInAssociations.js +2 -2
  89. package/lib/transform/odata/generateForeignKeyElements.js +13 -12
  90. package/lib/transform/odata/referenceFlattener.js +60 -36
  91. package/lib/transform/odata/sortByAssociationDependency.js +4 -4
  92. package/lib/transform/odata/structuralPath.js +76 -0
  93. package/lib/transform/odata/structureFlattener.js +21 -22
  94. package/lib/transform/odata/toFinalBaseType.js +5 -5
  95. package/lib/transform/odata/typesExposure.js +27 -17
  96. package/lib/transform/odata/utils.js +2 -2
  97. package/lib/transform/transformUtilsNew.js +141 -77
  98. package/lib/transform/translateAssocsToJoins.js +17 -14
  99. package/lib/transform/universalCsnEnricher.js +67 -0
  100. package/lib/utils/file.js +0 -11
  101. package/lib/utils/moduleResolve.js +6 -8
  102. package/lib/utils/timetrace.js +6 -1
  103. package/package.json +2 -1
  104. package/lib/base/deepCopy.js +0 -66
  105. package/lib/json/walker.js +0 -26
  106. 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,
@@ -67,11 +75,12 @@ const transformers = {
67
75
  columns,
68
76
  expand: ignore, // do not list for select items as elements
69
77
  inline: ignore, // do not list for select items as elements
70
- excludingDict: renameTo( 'excluding', Object.keys ),
78
+ excludingDict,
71
79
  groupBy: arrayOf( expression ),
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
@@ -283,6 +311,7 @@ function compactModel( model, options = model.options || {} ) {
283
311
  csn.requires = using;
284
312
  }
285
313
  // 'namespace' for complete model is 'namespace' of first source
314
+ // (not a really useful property at all, avoids XSN inspection by Umbrella)
286
315
  for (const first in srcDict) {
287
316
  const { namespace } = srcDict[first];
288
317
  if (namespace && namespace.path)
@@ -352,10 +381,12 @@ function usings( srcDict ) {
352
381
  * @param {object} csn
353
382
  * @param {object} model
354
383
  */
384
+
385
+
355
386
  function extensions( node, csn, model ) {
356
387
  if (model.kind && model.kind !== 'source')
357
388
  return undefined;
358
- const exts = node.map( standard );
389
+ const exts = node.map( definition );
359
390
 
360
391
  // builtins are non-enumerable for smaller display
361
392
  for (const name of Object.getOwnPropertyNames( model.definitions || {} ).sort()) {
@@ -370,17 +401,21 @@ function extensions( node, csn, model ) {
370
401
  }
371
402
  else if (gensrcFlavor) {
372
403
  // From definitions (without redefinitions) with potential inferred elements:
373
- if (!Array.isArray(art) && art.elements &&
374
- (art.query || art.includes || art.$inferred)) {
375
- const annos = art.$inferred && annotations( art, true );
376
- const elems = inferred( art.elements, art.$inferred );
377
- /** @type {object} */
378
- const annotate = Object.assign( { annotate: name }, annos );
379
- if (Object.keys( elems ).length)
380
- annotate.elements = elems;
381
- if (Object.keys( annotate ).length > 1)
382
- 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 );
383
416
  }
417
+ if (Object.keys( annotate ).length > 1)
418
+ exts.push( annotate );
384
419
  }
385
420
  }
386
421
 
@@ -388,12 +423,64 @@ function extensions( node, csn, model ) {
388
423
  (a, b) => (a.annotate || a.extend).localeCompare( b.annotate || b.extend )
389
424
  );
390
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
+
391
478
  // extract namespace/builtin annotations
392
479
  function extractAnnotationsToExtension( art ) {
393
480
  const name = art.name.absolute;
394
481
  // 'true' because annotations on namespaces and builtins can only
395
482
  // happen through extensions.
396
- const annos = annotations( art, true );
483
+ const annos = annotationsAndDocComment( art, true );
397
484
  const annotate = Object.assign( { annotate: name }, annos );
398
485
  if (Object.keys( annotate ).length > 1) {
399
486
  const loc = locationForAnnotationExtension();
@@ -433,8 +520,13 @@ function i18n( i18nNode ) {
433
520
  }
434
521
 
435
522
  function sources( srcDict, csn ) {
523
+ const names = Object.keys( srcDict );
524
+ const $sources = names.length && srcDict[names[0]].$sources;
525
+ if ($sources) {
526
+ setHidden( csn, '$sources', $sources );
527
+ return undefined;
528
+ }
436
529
  // TODO: sort according to some layering order, see #6368
437
- const names = Object.keys( srcDict);
438
530
  setHidden( csn, '$sources', (!strictMode) ? names : names.map( relativeName ) );
439
531
  return undefined;
440
532
 
@@ -444,17 +536,29 @@ function sources( srcDict, csn ) {
444
536
  }
445
537
  }
446
538
 
447
- function inferred( elems, inferredParent ) {
448
- const ext = Object.create( dictionaryPrototype );
449
- for (const name in elems) {
450
- const elem = elems[name];
451
- if (Array.isArray(elem) || !inferredParent && !elem.$inferred)
452
- continue;
453
- const csn = annotations( elem, true );
454
- if (Object.keys(csn).length)
455
- 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;
456
561
  }
457
- return ext;
458
562
  }
459
563
 
460
564
  function standard( node ) {
@@ -493,54 +597,79 @@ function set( prop, csn, node ) {
493
597
  }
494
598
 
495
599
  function targetAspect( val, csn, node ) {
600
+ const ta = (val.elements)
601
+ ? addLocation( val.location, standard( val ) )
602
+ : artifactRef( val, true );
496
603
  if (!gensrcFlavor || node.target && !node.target.$inferred)
497
- return (val.elements) ? standard( val ) : artifactRef( val, true );
604
+ return ta;
498
605
  // For compatibility, put aspect in 'target' with parse.cdl and csn flavor 'gensrc'
499
- csn.target = (val.elements) ? standard( val ) : artifactRef( val, true );
606
+ csn.target = ta;
500
607
  return undefined;
501
608
  }
502
609
 
503
610
  function target( val, _csn, node ) {
504
611
  if (gensrcFlavor && node._origin && node._origin.$inferred === 'REDIRECTED')
505
612
  val = node._origin.target;
506
- if (!val.elements)
613
+ if (val.elements)
614
+ return standard( val ); // elements in target (parse-cdl)
615
+ if (!universalCsn || node.on)
507
616
  return artifactRef( val, true );
508
- 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;
509
622
  }
510
623
 
511
624
  function items( obj, csn, node ) {
512
- if (!keepElements( node ) && node.type && node.kind !== 'type')
513
- // no 'elements' with SELECT or inferred elements with gensrc;
514
- // hidden 'elements' will be set in query()
625
+ if (!keepElements( node ))
515
626
  return undefined;
516
- return standard( obj );
627
+ return standard( obj ); // no 'elements' with inferred elements with gensrc
517
628
  }
518
629
 
519
630
  function elements( dict, csn, node ) {
520
- if (csn.from ||
631
+ if (node.from || // do not directly show query elements here
521
632
  gensrcFlavor && (node.query || node.type) ||
522
- node.type && node.kind !== 'type' && !keepElements( node ))
633
+ !keepElements( node ))
523
634
  // no 'elements' with SELECT or inferred elements with gensrc;
524
- // hidden 'elements' will be set in query()
635
+ // hidden or visible 'elements' will be set in query()
525
636
  return undefined;
526
637
  return insertOrderDict( dict );
527
638
  }
528
639
 
529
- // We do not optimize away elements which are potentially adapted during their
530
- // 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?)
531
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
532
660
  while (node) {
533
661
  if (node.$expand !== 'origin')
534
662
  return true;
535
663
  node = node._origin;
536
664
  }
537
- return false;
665
+ // all in _origin chain only have expanded elements with 'origin':
666
+ return false; // no need to render elements
538
667
  }
539
668
 
540
669
  // for gensrcFlavor and namespace/builtin annotation extraction:
541
670
  // return annotations from definition (annotated==false)
542
671
  // or annotations (annotated==true)
543
- function annotations( node, annotated ) {
672
+ function annotationsAndDocComment( node, annotated ) {
544
673
  const csn = {};
545
674
  const transformer = transformers['@'];
546
675
  const keys = Object.keys( node ).filter( a => a.charAt(0) === '@' ).sort();
@@ -557,6 +686,8 @@ function annotations( node, annotated ) {
557
686
  csn[prop] = sub;
558
687
  }
559
688
  }
689
+ if (node.doc)
690
+ csn.doc = transformers.doc(node.doc);
560
691
  return csn;
561
692
  }
562
693
 
@@ -618,17 +749,17 @@ function sortedDict( dict ) {
618
749
  return dictionary( dict, keys );
619
750
  }
620
751
 
621
- function nonEmptyDict( dict ) {
752
+ function actions( dict ) {
622
753
  const keys = Object.keys( dict );
623
754
  return (keys.length)
624
- ? dictionary( dict, keys )
755
+ ? dictionary( dict, keys, 'actions' )
625
756
  : undefined;
626
757
  }
627
758
 
628
- function dictionary( dict, keys ) {
759
+ function dictionary( dict, keys, prop ) {
629
760
  const csn = Object.create( dictionaryPrototype );
630
761
  for (const name of keys) {
631
- const def = definition( dict[name] );
762
+ const def = definition( dict[name], null, null, prop );
632
763
  if (def !== undefined)
633
764
  csn[name] = def;
634
765
  }
@@ -636,6 +767,8 @@ function dictionary( dict, keys ) {
636
767
  }
637
768
 
638
769
  function foreignKeys( dict, csn, node ) {
770
+ if (universalCsn && !target( node.target, csn, node ))
771
+ return;
639
772
  if (gensrcFlavor && node._origin && node._origin.$inferred === 'REDIRECTED')
640
773
  dict = node._origin.foreignKeys;
641
774
  const keys = [];
@@ -649,7 +782,7 @@ function foreignKeys( dict, csn, node ) {
649
782
  csn.keys = keys;
650
783
  }
651
784
 
652
- function definition( art ) {
785
+ function definition( art, _csn, _node, prop ) {
653
786
  if (!art || typeof art !== 'object')
654
787
  return undefined; // TODO: complain with strict
655
788
  // Do not include namespace definitions or inferred construct (in gensrc):
@@ -661,22 +794,79 @@ function definition( art ) {
661
794
  addLocation( art.targetElement.location, key );
662
795
  return extra( key, art );
663
796
  }
664
- 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;
665
845
  }
666
846
 
667
847
  function kind( k, csn, node ) {
668
- if (!node._main && [ 'annotate', 'extend' ].includes( k )) {
848
+ if (k === 'annotate' || k === 'extend') {
669
849
  // We just use `name.absolute` because it is very likely a "constructed"
670
850
  // extensions. The CSN parser must produce name.path like for other refs.
671
- csn[k] = node.name.absolute || artifactRef( node.name, true );
672
- 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;
673
855
  }
674
- if ([
675
- 'element', 'key', 'param', 'enum', 'annotate', 'select', '$join',
676
- '$tableAlias', 'annotation', 'mixin',
677
- ].includes(k))
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 );
863
+ }
864
+ }
865
+
866
+ function type( node, csn, xsn ) {
867
+ if (universalCsn && node.$inferred && xsn._origin)
678
868
  return undefined;
679
- return k;
869
+ return artifactRef( node, !node.$extra );
680
870
  }
681
871
 
682
872
  function artifactRef( node, terse ) {
@@ -778,10 +968,12 @@ function args( node ) {
778
968
  return dict;
779
969
  }
780
970
 
781
- // "Short" value form, e.g. for annotation assignments
782
971
  function value( node ) {
972
+ // "Short" value form, e.g. for annotation assignments
783
973
  if (!node)
784
974
  return true; // `@aBool` short for `@aBool: true`
975
+ if (universalCsn && node.$inferred === 'prop') // via propagator.js
976
+ return undefined;
785
977
  if (node.$inferred && gensrcFlavor)
786
978
  return undefined;
787
979
  if (node.path) {
@@ -792,6 +984,8 @@ function value( node ) {
792
984
  return extra( { '#': node.sym.id }, node );
793
985
  if (node.literal === 'array')
794
986
  return node.val.map( value );
987
+ if (node.literal === 'token' && node.val === '...')
988
+ return extra( { '...': true } );
795
989
  if (node.literal !== 'struct')
796
990
  // no val (undefined) as true only for annotation values (and struct elem values)
797
991
  return node.name && !('val' in node) || node.val;
@@ -821,23 +1015,8 @@ function onCondition( cond, csn, node ) {
821
1015
 
822
1016
  function condition( node ) {
823
1017
  const expr = expression( node );
824
- return !expr.cast && expr.xpr || [ expr ];
825
- }
826
-
827
- // TODO: quoted magic names like $now should be complained about in the compiler
828
-
829
- function pathRef( path ) {
830
- const ref = { ref: path.map( pathItem ) };
831
- const nav = path[0]._navigation;
832
- if (nav && nav.kind !== '$self' && nav.kind !== 'element' && nav.name.select != null) {
833
- setHidden( ref, '$env', (nav.kind === '$navElement')
834
- ? nav.name.alias
835
- : nav.name.select );
836
- }
837
- else if ( path[0]._artifact && path[0]._artifact.query ) {
838
- setHidden( ref, '$env', true );
839
- }
840
- return ref;
1018
+ // we do not set a hidden $parens on array - we could still do it if requested
1019
+ return !expr.cast && !expr.func && expr.xpr || [ expr ];
841
1020
  }
842
1021
 
843
1022
  function expression( node, dollarExtra ) {
@@ -852,8 +1031,8 @@ function expression( node, dollarExtra ) {
852
1031
  return { ref: [ node.param.val ], param: true }; // CDL rule for runtimes
853
1032
  }
854
1033
  if (node.path) {
855
- // TODO: global
856
- 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 );
857
1036
  }
858
1037
  if (node.literal) {
859
1038
  if (typeof node.val === node.literal || node.val === null)
@@ -877,37 +1056,32 @@ function expression( node, dollarExtra ) {
877
1056
  arg0.xpr.unshift( quantifier.val );
878
1057
  }
879
1058
  }
1059
+ if (node.suffix)
1060
+ call.xpr = [].concat( ...node.suffix.map( xprArg ) );
880
1061
  return extra( call, dollarExtraNode );
881
1062
  }
882
1063
  if (node.query)
883
- return query( node.query );
1064
+ return query( node.query, null, null, null, 1 );
884
1065
  if (!node.op) // parse error
885
1066
  return { xpr: [] };
886
1067
  else if (node.op.val === 'xpr')
887
1068
  // do not use xpr() for xpr, as it would flatten inner xpr's
888
- return extra({ xpr: node.args.map( expression ) }, dollarExtraNode );
1069
+ return extra({ xpr: node.args.map( expression ) }, dollarExtraNode, 1 );
889
1070
  else if (node.op.val === 'cast')
890
1071
  return cast( expression( node.args[0] ), dollarExtraNode );
891
1072
  // from here on: CDL input (no $extra possible - but $parens)
892
1073
  else if (node.op.val !== ',')
893
- return extra( { xpr: xpr( node ) }, dollarExtraNode );
1074
+ return extra( { xpr: xpr( node ) }, dollarExtraNode, (dollarExtra === 'sub-xpr' ? 1 : 0) );
894
1075
  return (parensAsStrings)
895
1076
  ? { xpr: [ '(', ...xpr( node ), ')' ] }
896
- : extra( { list: node.args.map( expression ) } );
1077
+ // the inner parens belong to the tuple construct, i.e. won't count as parens
1078
+ : extra( { list: node.args.map( expression ) }, dollarExtraNode, 0 );
897
1079
  }
898
1080
 
899
1081
  function xpr( node ) {
900
1082
  // if (!node.op) console.log(node)
901
1083
  const op = operators[node.op.val] || node.op.val.split(' ');
902
- const exprs = node.args.map( ( sub ) => {
903
- const expr = expression( sub );
904
- // return !sub.$parens && !expr.cast && expr.xpr || [ expr ]; if parensAsStrings is gone
905
- if (expr.cast || !expr.xpr || sub.$parens && !parensAsStrings)
906
- return [ expr ];
907
- else if (sub.$parens && sub.op.val !== ',')
908
- return [ '(', ...expr.xpr, ')' ];
909
- return expr.xpr;
910
- } );
1084
+ const exprs = node.args.map( xprArg );
911
1085
  if (op instanceof Function)
912
1086
  return op( exprs );
913
1087
  if (node.quantifier)
@@ -917,6 +1091,27 @@ function xpr( node ) {
917
1091
  return exprs[0].concat( ...exprs.slice(1).map( a => [ ...op, ...a ] ) );
918
1092
  }
919
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
+
920
1115
  function ternary( op1, op2 ) {
921
1116
  return function ternaryOp( exprs ) {
922
1117
  return (exprs[2])
@@ -940,16 +1135,15 @@ function binaryRightParen( op ) {
940
1135
  };
941
1136
  }
942
1137
 
943
- function query( node, csn, xsn ) {
944
- while (Array.isArray(node)) // in parentheses -> remove
945
- node = node[0];
1138
+ function query( node, csn, xsn, _prop, expectedParens = 0 ) {
946
1139
  if (node.op.val === 'SELECT') {
947
1140
  if (xsn && xsn.query === node && xsn.$syntax === 'projection' &&
948
1141
  node.from && node.from.path && !projectionAsQuery) {
949
1142
  csn.projection = standard( node );
950
1143
  return undefined;
951
1144
  }
952
- const select = { SELECT: standard( node ) };
1145
+ const select = { SELECT: extra( standard( node ), node, expectedParens ) };
1146
+ // one paren pair is not put into XSN - TODO: change that?
953
1147
  const elems = node.elements;
954
1148
  if (elems && node._main && node !== node._main._leadingQuery && gensrcFlavor !== true) {
955
1149
  // Set hidden 'elements' for csnRefs.js. In select-item subqueries,
@@ -958,7 +1152,10 @@ function query( node, csn, xsn ) {
958
1152
  const gensrcSaved = gensrcFlavor;
959
1153
  try {
960
1154
  gensrcFlavor = false;
961
- 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 ) );
962
1159
  }
963
1160
  finally {
964
1161
  gensrcFlavor = gensrcSaved;
@@ -995,11 +1192,13 @@ function columns( xsnColumns, csn, xsn ) {
995
1192
  return csnColumns;
996
1193
  }
997
1194
 
1195
+ function excludingDict( xsnDict, csn, xsn ) {
1196
+ if (xsn.kind !== 'element')
1197
+ csn.excluding = Object.keys( xsnDict );
1198
+ }
1199
+
998
1200
  function from( node ) {
999
- while (Array.isArray(node)) // TODO: old-style parentheses - keep tmp for A2J
1000
- node = node[0];
1001
- // TODO: CSN: FROM ((SELECT...)) as -> also add 'subquery' op? - Together
1002
- // with []-elimination in FROM... -> normal standard()
1201
+ // TODO: can we use the normal standard(), at least with JOIN?
1003
1202
  if (node.join) {
1004
1203
  const join = { join: node.join.val };
1005
1204
  set( 'cardinality', join, node );
@@ -1008,7 +1207,7 @@ function from( node ) {
1008
1207
  return extra( join, node );
1009
1208
  }
1010
1209
  else if (node.query) {
1011
- return addExplicitAs( query( node.query ), node.name ); // $extra inside SELECT/SET
1210
+ return addExplicitAs( query( node.query, null, null, null, 1 ), node.name );
1012
1211
  }
1013
1212
  else if (!node._artifact || node._artifact._main) { // CQL or follow assoc
1014
1213
  return extra( addExplicitAs( artifactRef( node, false ), node.name ), node );
@@ -1024,7 +1223,7 @@ function addElementAsColumn( elem, cols ) {
1024
1223
  if (elem.$inferred === '*')
1025
1224
  return;
1026
1225
  // only list annotations here which are provided directly with definition
1027
- const col = (gensrcFlavor) ? annotations( elem, false ) : {};
1226
+ const col = (gensrcFlavor) ? annotationsAndDocComment( elem, false ) : {};
1028
1227
  // with `client` flavor, assignments are available at the element
1029
1228
  const gensrcSaved = gensrcFlavor;
1030
1229
 
@@ -1034,33 +1233,29 @@ function addElementAsColumn( elem, cols ) {
1034
1233
  set( 'key', col, elem );
1035
1234
  const expr = expression( elem.value, true );
1036
1235
  Object.assign( col, (expr.cast ? { xpr: [ expr ] } : expr) );
1236
+ gensrcFlavor = gensrcSaved; // for not having annotations in inline etc
1037
1237
  if (elem.expand)
1038
1238
  col.expand = columns( elem.expand );
1039
1239
  if (elem.inline)
1040
1240
  col.inline = columns( elem.inline );
1241
+ gensrcFlavor = gensrcFlavor || 'column';
1242
+ if (elem.excludingDict)
1243
+ col.excluding = Object.keys( elem.excludingDict );
1041
1244
  // yes, the AS comes after the EXPAND
1042
1245
  addExplicitAs( col, elem.name, neqPath( elem.value ) );
1043
- // $env and elements (sub queries) in expr are hidden (not set via Object.assign):
1044
- if (!expr.cast) {
1045
- if (expr.$env)
1046
- setHidden( col, '$env', expr.$env );
1047
- if (expr.elements)
1048
- setHidden( col, 'elements', expr.elements );
1049
- }
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 );
1050
1249
  if (elem.type && !elem.type.$inferred || elem.target && !elem.target.$inferred)
1051
1250
  cast( col, elem );
1052
1251
  }
1053
1252
  finally {
1054
1253
  gensrcFlavor = gensrcSaved;
1055
1254
  }
1056
- // FIXME: Currently toHana requires that an '_ignore' property on the elem is
1057
- // also visible on the column. Don't ignore virtual columns, let the
1058
- // renderer decide how to render that column.
1059
- if (!elem.virtual && elem._ignore)
1060
- col._ignore = true;
1061
-
1062
1255
  if (elem.value && !elem.$inferred) {
1063
1256
  const parens = elem.value.$parens;
1257
+ if (parens)
1258
+ setHidden( col, '$parens', parens.length );
1064
1259
  addLocation( (parens ? parens[parens.length - 1] : elem.value.location), col );
1065
1260
  }
1066
1261
  cols.push( extra( col, elem ) );
@@ -1075,12 +1270,13 @@ function orderBy( node ) {
1075
1270
  return extra( expr, node ); // extra properties after sort/nulls
1076
1271
  }
1077
1272
 
1078
- function extra( csn, node ) {
1273
+ function extra( csn, node, expectedParens = 0 ) {
1079
1274
  if (node) {
1080
1275
  if (node.$extra)
1081
1276
  Object.assign( csn, node.$extra );
1082
- if (node.$parens)
1083
- setHidden( csn, '$parens', node.$parens.length );
1277
+ const parens = (node.$parens ? node.$parens.length : 0);
1278
+ if (parens !== expectedParens)
1279
+ setHidden( csn, '$parens', parens );
1084
1280
  }
1085
1281
  return csn;
1086
1282
  }
@@ -1142,6 +1338,9 @@ function compactExpr( e ) { // TODO: options
1142
1338
  function initModuleVars( options = { csnFlavor: 'gensrc' } ) {
1143
1339
  gensrcFlavor = options.parseCdl || options.csnFlavor === 'gensrc' ||
1144
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;
1145
1344
  strictMode = options.testMode;
1146
1345
  const proto = options.dictionaryPrototype;
1147
1346
  // eslint-disable-next-line no-nested-ternary
@@ -1154,7 +1353,7 @@ function initModuleVars( options = { csnFlavor: 'gensrc' } ) {
1154
1353
  }
1155
1354
 
1156
1355
  module.exports = {
1157
- cloneCsnDictionary: csnDictionary,
1356
+ cloneCsnDictionary: (csn, options) => csnDictionary(csn, false, options),
1158
1357
  compactModel,
1159
1358
  compactQuery,
1160
1359
  compactExpr,