@sap/cds-compiler 2.5.2 → 2.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (102) hide show
  1. package/CHANGELOG.md +235 -9
  2. package/bin/cdsc.js +44 -27
  3. package/bin/cdsse.js +1 -0
  4. package/doc/CHANGELOG_BETA.md +37 -3
  5. package/lib/api/.eslintrc.json +2 -0
  6. package/lib/api/main.js +37 -123
  7. package/lib/api/options.js +27 -15
  8. package/lib/api/validate.js +34 -9
  9. package/lib/backends.js +9 -89
  10. package/lib/base/dictionaries.js +2 -1
  11. package/lib/base/keywords.js +32 -2
  12. package/lib/base/message-registry.js +73 -11
  13. package/lib/base/messages.js +86 -30
  14. package/lib/base/model.js +6 -6
  15. package/lib/base/optionProcessorHelper.js +56 -22
  16. package/lib/checks/defaultValues.js +27 -2
  17. package/lib/checks/elements.js +1 -6
  18. package/lib/checks/foreignKeys.js +0 -6
  19. package/lib/checks/managedWithoutKeys.js +17 -0
  20. package/lib/checks/nonexpandableStructured.js +38 -0
  21. package/lib/checks/onConditions.js +9 -45
  22. package/lib/checks/queryNoDbArtifacts.js +25 -7
  23. package/lib/checks/selectItems.js +29 -2
  24. package/lib/checks/types.js +26 -2
  25. package/lib/checks/unknownMagic.js +41 -0
  26. package/lib/checks/utils.js +61 -0
  27. package/lib/checks/validator.js +60 -7
  28. package/lib/compiler/assert-consistency.js +23 -7
  29. package/lib/compiler/base.js +65 -0
  30. package/lib/compiler/builtins.js +30 -1
  31. package/lib/compiler/checks.js +8 -5
  32. package/lib/compiler/definer.js +157 -133
  33. package/lib/compiler/index.js +89 -31
  34. package/lib/compiler/propagator.js +5 -2
  35. package/lib/compiler/resolver.js +375 -185
  36. package/lib/compiler/shared.js +49 -202
  37. package/lib/compiler/utils.js +173 -0
  38. package/lib/edm/annotations/genericTranslation.js +183 -187
  39. package/lib/edm/csn2edm.js +104 -108
  40. package/lib/edm/edm.js +18 -21
  41. package/lib/edm/edmPreprocessor.js +388 -146
  42. package/lib/edm/edmUtils.js +104 -34
  43. package/lib/gen/Dictionary.json +22 -0
  44. package/lib/gen/language.checksum +1 -1
  45. package/lib/gen/language.interp +28 -1
  46. package/lib/gen/language.tokens +79 -69
  47. package/lib/gen/languageLexer.interp +28 -1
  48. package/lib/gen/languageLexer.js +879 -805
  49. package/lib/gen/languageLexer.tokens +71 -62
  50. package/lib/gen/languageParser.js +5330 -4300
  51. package/lib/json/from-csn.js +110 -52
  52. package/lib/json/to-csn.js +434 -120
  53. package/lib/language/antlrParser.js +15 -3
  54. package/lib/language/errorStrategy.js +1 -0
  55. package/lib/language/genericAntlrParser.js +93 -26
  56. package/lib/language/language.g4 +172 -31
  57. package/lib/main.d.ts +216 -19
  58. package/lib/main.js +32 -7
  59. package/lib/model/api.js +78 -0
  60. package/lib/model/csnRefs.js +413 -149
  61. package/lib/model/csnUtils.js +286 -75
  62. package/lib/model/enrichCsn.js +50 -6
  63. package/lib/model/revealInternalProperties.js +22 -5
  64. package/lib/modelCompare/compare.js +39 -21
  65. package/lib/optionProcessor.js +35 -18
  66. package/lib/render/.eslintrc.json +4 -1
  67. package/lib/render/DuplicateChecker.js +9 -6
  68. package/lib/render/toCdl.js +121 -36
  69. package/lib/render/toHdbcds.js +148 -98
  70. package/lib/render/toSql.js +114 -43
  71. package/lib/render/utils/common.js +8 -13
  72. package/lib/render/utils/sql.js +3 -3
  73. package/lib/sql-identifier.js +6 -1
  74. package/lib/transform/db/assertUnique.js +5 -6
  75. package/lib/transform/db/constraints.js +281 -106
  76. package/lib/transform/db/draft.js +11 -8
  77. package/lib/transform/db/expansion.js +584 -0
  78. package/lib/transform/db/flattening.js +341 -0
  79. package/lib/transform/db/groupByOrderBy.js +2 -2
  80. package/lib/transform/db/transformExists.js +345 -65
  81. package/lib/transform/db/views.js +438 -0
  82. package/lib/transform/forHanaNew.js +131 -793
  83. package/lib/transform/forOdataNew.js +30 -24
  84. package/lib/transform/localized.js +39 -10
  85. package/lib/transform/odata/attachPath.js +19 -4
  86. package/lib/transform/odata/generateForeignKeyElements.js +11 -10
  87. package/lib/transform/odata/referenceFlattener.js +60 -39
  88. package/lib/transform/odata/sortByAssociationDependency.js +2 -2
  89. package/lib/transform/odata/structuralPath.js +72 -0
  90. package/lib/transform/odata/structureFlattener.js +19 -18
  91. package/lib/transform/odata/typesExposure.js +22 -12
  92. package/lib/transform/transformUtilsNew.js +144 -78
  93. package/lib/transform/translateAssocsToJoins.js +22 -27
  94. package/lib/transform/universalCsnEnricher.js +67 -0
  95. package/lib/utils/file.js +5 -14
  96. package/lib/utils/moduleResolve.js +6 -8
  97. package/lib/utils/term.js +65 -42
  98. package/lib/utils/timetrace.js +48 -26
  99. package/package.json +1 -1
  100. package/lib/json/walker.js +0 -26
  101. package/lib/transform/sqlite +0 -0
  102. package/lib/utils/string.js +0 -17
@@ -45,29 +45,30 @@ const {
45
45
  dictAdd, dictAddArray,
46
46
  } = require('../base/dictionaries');
47
47
  const { dictLocation } = require('../base/location');
48
- const {
49
- makeMessageFunction, searchName, weakLocation,
50
- } = require('../base/messages');
48
+ const { searchName, weakLocation } = require('../base/messages');
51
49
  const { combinedLocation } = require('../base/location');
52
- const { pushLink } = require('./utils');
50
+
51
+ const { kindProperties } = require('./base');
53
52
  const {
54
- getDefinerFunctions,
53
+ pushLink,
54
+ setLink,
55
55
  augmentPath,
56
56
  splitIntoPath,
57
- } = require('./definer');
57
+ linkToOrigin,
58
+ setMemberParent,
59
+ withAssociation,
60
+ storeExtension,
61
+ dependsOn,
62
+ dependsOnSilent,
63
+ } = require('./utils');
58
64
 
59
65
  const detectCycles = require('./cycle-detector');
60
66
  const layers = require('./moduleLayers');
61
67
 
62
- const {
63
- kindProperties, fns, setLink, linkToOrigin, setMemberParent, withAssociation, storeExtension,
64
- dependsOn, dependsOnSilent,
65
- } = require('./shared');
66
-
67
68
  const annotationPriorities = {
68
69
  define: 1, extend: 2, annotate: 2, edmx: 3,
69
70
  };
70
-
71
+ const $inferred = Symbol.for('cds.$inferred');
71
72
 
72
73
  // Export function of this file. Resolve type references in augmented CSN
73
74
  // `model`. If the model has a property argument `messages`, do not throw
@@ -75,26 +76,28 @@ const annotationPriorities = {
75
76
  // that property (should be a vector).
76
77
  function resolve( model ) {
77
78
  const { options } = model;
78
- // Get shared "resolve" functionality and the message function:
79
+ // Get shared functionality and the message function:
80
+ const {
81
+ info, warning, error, message,
82
+ } = model.$messageFunctions;
79
83
  const {
80
84
  resolvePath,
81
85
  resolveTypeArguments,
82
86
  defineAnnotations,
83
87
  attachAndEmitValidNames,
84
- } = fns( model, environment );
85
- const {
86
- info, warning, error, message,
87
- } = makeMessageFunction( model, model.options, 'compile' );
88
- const {
89
88
  initArtifact,
90
89
  lateExtensions,
91
90
  projectionAncestor,
92
- } = getDefinerFunctions(model);
91
+ } = model.$functions;
92
+ model.$volatileFunctions.environment = environment;
93
+
93
94
  /** @type {any} may also be a boolean */
94
95
  let newAutoExposed = [];
95
96
 
96
97
  // behavior depending on option `deprecated`:
97
98
  const enableExpandElements = !isDeprecatedEnabled( options, 'noElementsExpansion' );
99
+ // TODO: we should get rid of noElementsExpansion soon; both
100
+ // beta.nestedProjections and beta.universalCsn do not work with it.
98
101
  const scopedRedirections
99
102
  = enableExpandElements &&
100
103
  !isDeprecatedEnabled( options, 'generatedEntityNameWithUnderscore' ) &&
@@ -242,6 +245,7 @@ function resolve( model ) {
242
245
  const chain = [];
243
246
  while (art && !('_effectiveType' in art) &&
244
247
  (art.type || art._origin || art.value && art.value.path) &&
248
+ // TODO: really stop at art.enum?
245
249
  !art.target && !art.enum && !art.elements && !art.items) {
246
250
  chain.push( art );
247
251
  setProp( art, '_effectiveType', 0 ); // initial setting in case of cycles
@@ -273,13 +277,17 @@ function resolve( model ) {
273
277
  // collect the "latest" cardinality (calculate lazyly if necessary)
274
278
  let cardinality = art.cardinality ||
275
279
  art._effectiveType && (() => getCardinality( art._effectiveType ));
280
+ let prev = art;
276
281
  for (const a of chain) {
277
282
  if (a.cardinality)
278
283
  cardinality = a.cardinality;
279
284
  if (a.expand && expandFromColumns( a, art, cardinality ) ||
280
285
  art.target && redirectImplicitly( a, art ) ||
281
- art.elements && expandElements( a, art, eType ))
286
+ art.elements && expandElements( a, art, eType ) ||
287
+ art.items && expandItems( a, art, eType ))
282
288
  art = a;
289
+ else if (art.enum && expandEnum( a, prev ))
290
+ prev = a; // do not set art - effective type is base
283
291
  setProp( a, '_effectiveType', art );
284
292
  }
285
293
  }
@@ -319,6 +327,7 @@ function resolve( model ) {
319
327
  return null;
320
328
  }
321
329
 
330
+ // TODO: test it in combination with top-level CAST function
322
331
  function directType( art ) {
323
332
  // Be careful when using it with art.target or art.enum or art.elements
324
333
  if (art._origin || art.builtin)
@@ -326,14 +335,14 @@ function resolve( model ) {
326
335
  if (art.type)
327
336
  return resolveType( art.type, art );
328
337
  // console.log( 'EXPR-IN', art.kind, refString(art.name) )
329
- if (!art._main)
338
+ if (!art._main || !art.value || !art.value.path)
330
339
  return undefined;
331
340
  if (art._pathHead && art.value) {
332
341
  setProp( art, '_origin', resolvePath( art.value, 'expr', art, null ) );
333
342
  return art._origin;
334
343
  }
335
344
  const query = userQuery( art ) || art._parent;
336
- if (query.kind !== 'select' || !art.value.path)
345
+ if (query.kind !== 'select')
337
346
  return undefined;
338
347
  // Reached an element in a query which is a simple ref -> return referred artifact
339
348
  // TODO: remember that we still have to resolve path arguments and filters
@@ -404,7 +413,7 @@ function resolve( model ) {
404
413
  for (const view of resolveChain.reverse()) {
405
414
  if (view._status !== '_query' ) { // not already resolved
406
415
  setProp( view, '_status', '_query' );
407
- traverseQueryPost( view.query, false, populateQuery );
416
+ traverseQueryPost( view.query, null, populateQuery );
408
417
  if (view.elements$) // specified elements
409
418
  mergeSpecifiedElements( view );
410
419
  if (!view.$entity) {
@@ -424,7 +433,7 @@ function resolve( model ) {
424
433
  info( 'query-missing-element', [ ielem.name.location, view ], { id },
425
434
  'Element $(ID) is missing in specified elements' );
426
435
  }
427
- else if (!Array.isArray( ielem ) && !Array.isArray( selem )) {
436
+ else {
428
437
  for (const prop in selem) {
429
438
  // just annotation assignments and doc comments for the moment
430
439
  if (prop.charAt(0) === '@' || prop === 'doc')
@@ -435,7 +444,7 @@ function resolve( model ) {
435
444
  }
436
445
  for (const id in view.elements$) {
437
446
  const selem = view.elements$[id]; // specified element
438
- if (!Array.isArray( selem ) && !selem.$replacement) {
447
+ if (!selem.$replacement) {
439
448
  error( 'query-unspecified-element', [ selem.name.location, selem ], { id },
440
449
  'Element $(ID) does not result from the query' );
441
450
  }
@@ -445,8 +454,7 @@ function resolve( model ) {
445
454
  function traverseElementEnvironments( art ) {
446
455
  populateView( art );
447
456
  environment( art );
448
- // TODO: but we have no proxy copy of sub elements yet - no redirection in ValueHelpList.cds
449
- forEachGeneric( art, 'elements', traverseElementEnvironments );
457
+ forEachMember( art, traverseElementEnvironments );
450
458
  }
451
459
 
452
460
  function populateQuery( query ) {
@@ -454,6 +462,7 @@ function resolve( model ) {
454
462
  // already done or $join query or parse error
455
463
  return;
456
464
  setProp( query, '_combined', Object.create(null) );
465
+ query.$inlines = [];
457
466
  forEachGeneric( query, '$tableAliases', resolveTabRef );
458
467
 
459
468
  initFromColumns( query, query.columns );
@@ -493,7 +502,8 @@ function resolve( model ) {
493
502
  });
494
503
  }
495
504
  forEachGeneric( { elements: alias.elements }, 'elements', ( elem, name ) => {
496
- dictAddArray( query._combined, name, elem, null ); // not dictAdd()
505
+ if (elem.$duplicates !== true)
506
+ dictAddArray( query._combined, name, elem, null ); // not dictAdd()
497
507
  });
498
508
  }
499
509
  }
@@ -534,13 +544,31 @@ function resolve( model ) {
534
544
  setMemberParent( key, name, elem ); // TODO: set _block here if not present?
535
545
  }
536
546
 
547
+ function expandItems( art, origin, eType ) {
548
+ if (!enableExpandElements || art.items)
549
+ return false;
550
+ if (isInParents( art, eType )) {
551
+ art.items = 0; // circular
552
+ return true;
553
+ }
554
+ const ref = art.type || art.value || art.name;
555
+ const location = ref && ref.location || art.location;
556
+ art.items = { $inferred: 'expand-element', location };
557
+ setProp( art.items, '_outer', art );
558
+ setProp( art.items, '_origin', origin.items );
559
+ if (!art.$expand)
560
+ art.$expand = 'origin'; // if value stays, elements won't appear in CSN
561
+ return true;
562
+ }
563
+
537
564
  function expandElements( art, struct, eType ) {
538
565
  if (!enableExpandElements)
539
566
  return false;
540
567
  if (art.elements || art.kind === '$tableAlias' ||
541
568
  // no element expansions for "non-proper" types like
542
569
  // entities (as parameter types) etc:
543
- struct.kind !== 'type' && struct.kind !== 'element' && !struct._outer)
570
+ struct.kind !== 'type' && struct.kind !== 'element' && struct.kind !== 'param' &&
571
+ !struct._outer)
544
572
  return false;
545
573
  if (struct.elements === 0 || isInParents( art, eType )) {
546
574
  art.elements = 0; // circular
@@ -559,8 +587,33 @@ function resolve( model ) {
559
587
  // or should we use orig.location? - TODO: try to find test to see message
560
588
  .$inferred = 'expand-element';
561
589
  }
562
- art.$expand = 'origin'; // if value stays, elements won't appear in CSN
563
- // TODO: should we also set 'virtual' here?
590
+ // Set elements expansion status (the if condition is always true, as no
591
+ // elements expansion will take place on artifact with existing other
592
+ // member property):
593
+ if (!art.$expand)
594
+ art.$expand = 'origin'; // if value stays, elements won't appear in CSN
595
+ // TODO: have some art.elements[SYM.$inferred] = 'expand-element';
596
+ return true;
597
+ }
598
+
599
+ function expandEnum( art, origin ) {
600
+ if (!enableExpandElements || art.enum)
601
+ return false;
602
+ const ref = art.type || art.value || art.name;
603
+ const location = weakLocation( ref && ref.location || art.location );
604
+ art.enum = Object.create(null);
605
+ for (const name in origin.enum) {
606
+ const orig = origin.enum[name];
607
+ linkToOrigin( orig, name, art, 'enum', location, true )
608
+ // or should we use orig.location? - TODO: try to find test to see message
609
+ .$inferred = 'expand-element';
610
+ }
611
+ // Set elements expansion status (the if condition is always true, as no
612
+ // elements expansion will take place on artifact with existing other
613
+ // member property):
614
+ if (!art.$expand)
615
+ art.$expand = 'origin'; // if value stays, elements won't appear in CSN
616
+ art.enum[$inferred] = 'expand-element';
564
617
  return true;
565
618
  }
566
619
 
@@ -586,14 +639,45 @@ function resolve( model ) {
586
639
  return false;
587
640
  }
588
641
 
589
- function setExpandStatus( elem ) {
642
+ // About Helper property $expand for faster the XSN-to-CSN transformation
643
+ // - null/undefined: artifact, member, items does not contain expanded members
644
+ // - 'origin': all expanded (sub) elements have no new target/on and no new annotations
645
+ // that value is only on elements, types, and params -> no other members
646
+ // when set, only on elem/art with expanded elements
647
+ // - 'target': all expanded (sub) elements might only have new target/on, but
648
+ // no indivual annotations on any (sub) member
649
+ // when set, traverse all parents where the value has been 'origin' before
650
+ // - 'annotate': at least one inferred (sub) member has an individual annotation,
651
+ // not counting propagated ones; set up to the definition (main artifact)
652
+ // (only set with anno on $inferred elem)
653
+ // Usage according to CSN flavor:
654
+ // - gensrc: do not render inferred elements (including expanded elements),
655
+ // collect annotate statements with value 'annotate'
656
+ // - client: do not render expanded sub elements if artifact/member is no type, has a type,
657
+ // has $expand = 'origin', and all its _origin also have $expand = 'origin'
658
+ // (might sometimes render the elements unnecessarily, which is not wrong)
659
+ // - universal: do not render expanded sub elements if $expand = 'origin'
660
+ function setExpandStatus( elem, status ) {
661
+ // set on element
590
662
  while (elem._main) {
591
663
  elem = elem._parent;
592
- if (!elem.$expand)
664
+ if (elem.$expand !== 'origin')
593
665
  return;
594
- elem.$expand = 'target'; // meaning: expanded, containing assocs
666
+ elem.$expand = status; // meaning: expanded, containing assocs
595
667
  for (let line = elem.items; line; line = line.items)
596
- line.$expand = 'target'; // to-csn just uses the innermost $expand
668
+ line.$expand = status; // to-csn just uses the innermost $expand
669
+ }
670
+ }
671
+ function setExpandStatusAnnotate( elem, status ) {
672
+ for (;;) {
673
+ if (elem.$expand === status)
674
+ return; // already set
675
+ elem.$expand = status; // meaning: expanded, containing annos
676
+ for (let line = elem.items; line; line = line.items)
677
+ line.$expand = status; // to-csn just uses the innermost $expand
678
+ if (!elem._main)
679
+ return;
680
+ elem = elem._parent;
597
681
  }
598
682
  }
599
683
 
@@ -605,7 +689,7 @@ function resolve( model ) {
605
689
  // PRE: elem has no target, assoc has target prop
606
690
  if (elem.kind === '$tableAlias')
607
691
  return false;
608
- setExpandStatus( elem );
692
+ setExpandStatus( elem, 'target' );
609
693
  let target = resolvePath( assoc.target, 'target', assoc );
610
694
  // console.log( info( null, [ elem.location, elem ], {target,art:assoc,name:''+assoc.target},
611
695
  // 'RED').toString())
@@ -718,6 +802,7 @@ function resolve( model ) {
718
802
  const nullScope = {
719
803
  kind: 'namespace', name: { absolute: autoScopeName, location }, location,
720
804
  };
805
+ model.definitions[autoScopeName] = nullScope;
721
806
  initArtifact( nullScope );
722
807
  return nullScope;
723
808
  }
@@ -926,26 +1011,53 @@ function resolve( model ) {
926
1011
  return art;
927
1012
  }
928
1013
 
1014
+ // TODO: probably do this already in definer.js
1015
+ function ensureColumnName( col, query ) {
1016
+ if (col.name)
1017
+ return col.name.id;
1018
+ if (col.inline || col.val === '*')
1019
+ return '';
1020
+ const path = col.value &&
1021
+ (col.value.path || !col.value.args && col.value.func && col.value.func.path);
1022
+ if (path) {
1023
+ const last = !path.broken && path.length && path[path.length - 1];
1024
+ if (last) {
1025
+ col.name = { id: last.id, location: last.location, $inferred: 'as' };
1026
+ return col.name.id;
1027
+ }
1028
+ }
1029
+ else if (col.value || col.expand) {
1030
+ error( 'query-req-name', [ col.value && col.value.location || col.location, query ], {},
1031
+ 'Alias name is required for this select item' );
1032
+ }
1033
+ // invent a name for code completion in expression
1034
+ col.name = {
1035
+ id: '',
1036
+ location: col.value && col.value.location || col.location,
1037
+ $inferred: 'none',
1038
+ };
1039
+ return '';
1040
+ }
1041
+
929
1042
  // TODO: make this function shorter - make part of this (e.g. setting
930
1043
  // parent/name) also be part of definer.js
931
1044
  // TODO: query is actually the elemParent, where the new elements are added to
932
- function initFromColumns( query, columns, elements = undefined, colParent = undefined ) {
1045
+ // top-level: just query, columns
1046
+ // inline: + elements (TODO: remove), colParent
1047
+ // expand: just query (which is a column/element), columns=array of expand
1048
+ function initFromColumns( query, columns, inlineHead = undefined ) {
933
1049
  const elemsParent = query.items || query;
934
- if (!elements) {
935
- elements = Object.create(null); // explicitly prov
1050
+ if (!inlineHead) {
936
1051
  elemsParent.elements = Object.create(null);
937
1052
  if (query._main._leadingQuery === query) // never the case for 'expand'
938
1053
  query._main.elements = elemsParent.elements;
939
1054
  }
940
- let wildcard = false;
941
- let inline = 0;
942
1055
 
943
1056
  for (const col of columns || [ { val: '*' } ]) {
944
1057
  if (col.val === '*') {
945
- wildcard = col;
946
- continue;
1058
+ const siblings = wildcardSiblings( columns, query );
1059
+ expandWildcard( col, siblings, inlineHead, query );
947
1060
  }
948
- col.kind = 'element';
949
1061
  if ((col.expand || col.inline) && !isBetaEnabled( options, 'nestedProjections' )) {
950
1062
  error( null, [ col.location, query ], { prop: (col.expand ? 'expand' : 'inline') },
951
1063
  'Unsupported nested $(PROP)' );
@@ -957,137 +1069,135 @@ function resolve( model ) {
957
1069
  col.name = {};
958
1070
  // a name for this internal symtab entry (e.g. '.2' to avoid clashes
959
1071
  // with real elements) is only relevant for for `cdsc -R`/debugging
960
- // TODO: user better scheme with _outer (for messages /column:…/inline:2),
961
- // use depth-first numbering
962
- setMemberParent( col, `.${ ++inline }`, query );
963
- initFromColumns( query, col.inline, elements, col );
1072
+ const q = userQuery( query );
1073
+ q.$inlines.push( col );
1074
+ // or use userQuery( query ) in the following, too?
1075
+ setMemberParent( col, `.${ q.$inlines.length }`, query );
1076
+ initFromColumns( query, col.inline, col );
964
1077
  continue;
965
1078
  }
966
- if (!col.name) {
967
- const path = col.value &&
968
- (col.value.path || !col.value.args && col.value.func && col.value.func.path);
969
- if (!path) {
970
- error( 'query-req-name', [ col.value && col.value.location || col.location, query ], {},
971
- 'Alias name is required for this select item' );
972
- }
973
- else if (path.length && !path.broken) {
974
- const last = path[path.length - 1];
975
- if (last)
976
- col.name = { id: last.id, location: last.location, $inferred: 'as' };
977
- }
978
- if (!col.name) {
979
- // invent a name for code completion in expression
980
- col.name = {
981
- id: '',
982
- location: col.value && col.value.location || col.location,
983
- $inferred: 'none',
984
- };
985
- }
986
- }
987
- const { id } = col.name;
988
- dictAdd( elements, id, col, ( name, location ) => {
989
- error( 'duplicate-definition', [ location, query ], { name, '#': 'element' } );
990
- });
991
- setMemberParent( col, id, query );
992
- if (!wildcard) {
993
- if (col.$duplicates !== true)
994
- dictAdd( elemsParent.elements, id, col );
995
- col.$replacement = true;
1079
+ else if (!col.$replacement) {
1080
+ const id = ensureColumnName( col, query );
1081
+ col.kind = 'element';
1082
+ dictAdd( elemsParent.elements, id, col, ( name, location ) => {
1083
+ error( 'duplicate-definition', [ location, query ], { name, '#': 'element' } );
1084
+ });
1085
+ setMemberParent( col, id, query );
996
1086
  }
997
1087
  }
998
- if (wildcard)
999
- expandWildcard( elements, wildcard );
1000
1088
  forEachGeneric( query, 'elements', e => initElem( e, query ) );
1001
1089
  return true;
1090
+ }
1002
1091
 
1003
- // TODO: make this function shorter, probably outside initFromColumns
1004
- // `elements` are the element explicitly provided via `columns`
1005
- // TODO: make struct.* are to be added at place, not sub-wildcards first,
1006
- // see test3/Queries/ExpandInlineCreate/Excluding.cds
1007
- // TODO: disallow $self.elem.* and $self.*, toSelf.* (circular dependency)
1008
- // eslint-disable-next-line no-shadow
1009
- function expandWildcard( elements, wildcard ) {
1010
- let location = wildcard.location || query.from && query.from.location || query.location;
1011
- const inferred = query._main.$inferred;
1012
- const excludingDict = (colParent || query).excludingDict || Object.create(null);
1013
-
1014
- const envParent = wildcard._pathHead; // TODO: rename _pathHead to _pathEnv
1015
- // console.log('S1:',location.line,location.col,
1016
- // envParent&&!!envParent._origin&&envParent._origin.name)
1017
- const env = columnEnv( envParent, query );
1018
- // console.log('S2:',location.line,location.col,
1019
- // envParent&&!!envParent._origin&&envParent._origin.name,
1020
- // Object.keys(env),Object.keys(elements))
1021
- for (const name in env) {
1022
- const navElem = env[name];
1023
- // TODO: if it is an array, filter out those with masked
1024
- if (excludingDict[name] || navElem.masked && navElem.masked.val)
1025
- continue;
1026
- const selElem = elements[name];
1027
- if (selElem) { // is explicitly provided
1028
- if (!selElem.name) // no name with parse error or repeated def
1029
- continue;
1030
- const path = selElem.value && selElem.value.path;
1031
- // TODO: to bring the message below for ParentElem.Assoc, we should move this
1032
- // check to resolveElem for elems with $replacement, by comparing the
1033
- // name with the name.element of the _origin.
1034
- // We cannot check path.length === 1, as we want to allow Alias.Elem.
1035
-
1036
- // TODO: bring this less often (only if shadowed elem does not appear
1037
- // in expr and if not projected as other name)
1038
- // TODO: in general - re-think and implement
1039
- if (!inferred && (
1040
- selElem.$replacement !== 'silent' && !envParent &&
1041
- (!selElem.target || selElem.target.$inferred ) ||
1042
- path && path[path.length - 1].id !== selElem.name.id)) {
1043
- if (Array.isArray(navElem)) {
1044
- info( 'wildcard-excluding-many', [ selElem.name.location, query ], { id: name },
1045
- 'This select item replaces $(ID) from two or more sources' );
1046
- }
1047
- else {
1048
- info( 'wildcard-excluding-one', [ selElem.name.location, query ],
1049
- { id: name, alias: navElem._parent.name.id },
1050
- 'This select item replaces $(ID) from table alias $(ALIAS)' );
1051
- }
1052
- }
1053
- if (!selElem.$replacement || selElem.$replacement === 'silent') {
1054
- selElem.$replacement = true;
1055
- dictAdd( elemsParent.elements, name, selElem );
1056
- }
1057
- else {
1058
- selElem.$inferred = 'query';
1059
- }
1060
- }
1061
- else if (Array.isArray(navElem)) {
1062
- const names = navElem.filter( e => !e.$duplicates)
1063
- .map( e => `${ e.name.alias }.${ e.name.element }` );
1064
- if (names.length) {
1065
- error( 'wildcard-ambiguous', [ location, query ], { id: name, names },
1066
- 'Ambiguous wildcard, select $(ID) explicitly with $(NAMES)' );
1067
- }
1092
+ // col ($replacement set before *)
1093
+ // false if two cols have same name
1094
+ function wildcardSiblings( columns, query ) {
1095
+ const siblings = Object.create(null);
1096
+ if (!columns)
1097
+ return siblings;
1098
+
1099
+ let seenWildcard = null;
1100
+ for (const col of columns) {
1101
+ const id = ensureColumnName( col, query );
1102
+ if (id) {
1103
+ col.$replacement = !seenWildcard;
1104
+ siblings[id] = !(id in siblings) && col;
1105
+ }
1106
+ else if (col.val === '*') {
1107
+ seenWildcard = true;
1108
+ }
1109
+ }
1110
+ return siblings;
1111
+ }
1112
+
1113
+ // TODO: make struct.* are to be added at place, not sub-wildcards first,
1114
+ // see test3/Queries/ExpandInlineCreate/Excluding.cds
1115
+ // TODO: disallow $self.elem.* and $self.*, toSelf.* (circular dependency)
1116
+ function expandWildcard( wildcard, siblingElements, colParent, query ) {
1117
+ const { elements } = query.items || query;
1118
+ let location = wildcard.location || query.from && query.from.location || query.location;
1119
+ const inferred = query._main.$inferred;
1120
+ const excludingDict = (colParent || query).excludingDict || Object.create(null);
1121
+
1122
+ const envParent = wildcard._pathHead; // TODO: rename _pathHead to _pathEnv
1123
+ // console.log('S1:',location.line,location.col,
1124
+ // envParent&&!!envParent._origin&&envParent._origin.name)
1125
+ const env = columnEnv( envParent, query );
1126
+ // console.log('S2:',location.line,location.col,
1127
+ // envParent&&!!envParent._origin&&envParent._origin.name,
1128
+ // Object.keys(env),Object.keys(elements))
1129
+ for (const name in env) {
1130
+ const navElem = env[name];
1131
+ // TODO: if it is an array, filter out those with masked
1132
+ if (excludingDict[name] || navElem.masked && navElem.masked.val)
1133
+ continue;
1134
+ const sibling = siblingElements[name];
1135
+ if (sibling) { // is explicitly provided (without duplicate)
1136
+ if (!inferred && !envParent) // not yet for expand/inline
1137
+ reportReplacement( sibling, navElem, query );
1138
+ if (!sibling.$replacement) {
1139
+ sibling.$replacement = true;
1140
+ sibling.kind = 'element';
1141
+ dictAdd( elements, name, sibling, ( _name, loc ) => {
1142
+ // there can be a definition from a previous inline with the same name:
1143
+ error( 'duplicate-definition', [ loc, query ], { name, '#': 'element' } );
1144
+ });
1145
+ setMemberParent( sibling, name, query );
1068
1146
  }
1069
- else {
1070
- location = weakLocation( location );
1071
- const origin = envParent ? navElem : navElem._origin;
1072
- const elem = linkToOrigin( origin, name, query, 'elements', location );
1073
- elem.$inferred = '*';
1074
- elem.name.$inferred = '*';
1075
- if (envParent)
1076
- setWildcardExpandInline( elem, envParent, origin, name, location );
1077
- else
1078
- setElementOrigin( elem, navElem, name, location );
1147
+ // else {
1148
+ // sibling.$inferred = 'query';
1149
+ // }
1150
+ }
1151
+ else if (Array.isArray(navElem)) {
1152
+ const names = navElem.filter( e => !e.$duplicates)
1153
+ .map( e => `${ e.name.alias }.${ e.name.element }` );
1154
+ if (names.length) {
1155
+ error( 'wildcard-ambiguous', [ location, query ], { id: name, names },
1156
+ 'Ambiguous wildcard, select $(ID) explicitly with $(NAMES)' );
1079
1157
  }
1080
1158
  }
1081
- forEachInOrder( { elements }, 'elements', (elem, name) => {
1082
- if (!elem.$replacement)
1083
- dictAdd( elemsParent.elements, name, elem );
1084
- });
1085
- if (envParent || query.kind !== 'select') {
1086
- // already done in populateQuery (TODO: change that and check whether
1087
- // `*` is allowed at all in definer)
1088
- const user = colParent || query;
1089
- for (const name in user.excludingDict)
1090
- resolveExcluding( name, env, excludingDict, query );
1159
+ else {
1160
+ location = weakLocation( location );
1161
+ const origin = envParent ? navElem : navElem._origin;
1162
+ const elem = linkToOrigin( origin, name, query, null, location );
1163
+ // TODO: check assocToMany { * }
1164
+ dictAdd( elements, name, elem, ( _name, loc ) => {
1165
+ // there can be a definition from a previous inline with the same name:
1166
+ error( 'duplicate-definition', [ loc, query ], { name, '#': 'element' } );
1167
+ });
1168
+ elem.$inferred = '*';
1169
+ elem.name.$inferred = '*';
1170
+ if (envParent)
1171
+ setWildcardExpandInline( elem, envParent, origin, name, location );
1172
+ else
1173
+ setElementOrigin( elem, navElem, name, location );
1174
+ }
1175
+ }
1176
+ if (envParent || query.kind !== 'select') {
1177
+ // already done in populateQuery (TODO: change that and check whether
1178
+ // `*` is allowed at all in definer)
1179
+ const user = colParent || query;
1180
+ for (const name in user.excludingDict)
1181
+ resolveExcluding( name, env, excludingDict, query );
1182
+ }
1183
+ }
1184
+
1185
+ function reportReplacement( sibling, navElem, query ) {
1186
+ // TODO: bring this much less often = only if shadowed elem does not appear
1187
+ // in expr and if not projected as other name.
1188
+ // Probably needs to be reported at a later phase
1189
+ const path = sibling.value && sibling.value.path;
1190
+ if (!sibling.target || sibling.target.$inferred || // not explicit REDIRECTED TO
1191
+ path && path[path.length - 1].id !== sibling.name.id) { // or renamed
1192
+ const { id } = sibling.name;
1193
+ if (Array.isArray(navElem)) {
1194
+ info( 'wildcard-excluding-many', [ sibling.name.location, query ], { id },
1195
+ 'This select item replaces $(ID) from two or more sources' );
1196
+ }
1197
+ else {
1198
+ info( 'wildcard-excluding-one', [ sibling.name.location, query ],
1199
+ { id, alias: navElem._parent.name.id },
1200
+ 'This select item replaces $(ID) from table alias $(ALIAS)' );
1091
1201
  }
1092
1202
  }
1093
1203
  }
@@ -1378,7 +1488,9 @@ function resolve( model ) {
1378
1488
  }
1379
1489
  // Resolve projections/views
1380
1490
  // if (art.query)console.log( info( null, [art.query.location,art.query], 'VQ:' ).toString() );
1381
- traverseQueryPost( art.query, false, resolveQuery );
1491
+
1492
+ if (art.$queries)
1493
+ art.$queries.forEach( resolveQuery );
1382
1494
 
1383
1495
  if (obj.type || obj._origin || obj.value && obj.value.path || obj.elements) // typed artifacts
1384
1496
  effectiveType(obj); // set _effectiveType if appropriate, (future?): copy elems if extended
@@ -1523,14 +1635,28 @@ function resolve( model ) {
1523
1635
  }
1524
1636
  }
1525
1637
  if (art && art._annotate) {
1526
- if (art.$expand && art._annotate.elements)
1527
- art.$expand = 'annotate'; // do not omit expanded elements when they are annotated
1528
- // TODO: returns
1529
- let obj = art.returns || art; // why the extra `returns` for actions?
1530
- obj = obj.items || obj;
1638
+ if (art.kind === 'action' || art.kind === 'function') {
1639
+ expandParameters( art );
1640
+ if (art.returns)
1641
+ effectiveType( art.returns );
1642
+ }
1643
+ const aor = art.returns || art;
1644
+ const obj = aor.items || aor.targetAspect || aor;
1645
+ // Currently(?), effectiveType() does not calculate the effective type of
1646
+ // its line item:
1647
+ effectiveType( obj );
1648
+ if (art._annotate.elements)
1649
+ setExpandStatusAnnotate( aor, 'annotate' );
1531
1650
  annotate( obj, 'element', 'elements', 'enum', art );
1532
1651
  annotate( art, 'action', 'actions' );
1533
1652
  annotate( art, 'param', 'params' );
1653
+ // const { returns } = art._annotate;
1654
+ // if (returns) {
1655
+ // const dict = returns.elements;
1656
+ // const env = obj.returns && obj.returns.elements || null;
1657
+ // for (const n in dict)
1658
+ // annotateMembers( env && env[n], dict[n], 'elements', n, parent, 'element' );
1659
+ // }
1534
1660
  }
1535
1661
  return;
1536
1662
 
@@ -1548,6 +1674,44 @@ function resolve( model ) {
1548
1674
  annotateMembers( env && env[n], dict[n], prop, n, parent, kind );
1549
1675
  }
1550
1676
  }
1677
+ function expandParameters( action ) {
1678
+ // see also expandElements()
1679
+ if (!enableExpandElements || !effectiveType( action ))
1680
+ return;
1681
+ const chain = [];
1682
+ // Should we be able to consider params and returns separately?
1683
+ // Probably not, let to-csn omit unchanged params/returns.
1684
+ while (action._origin && !action.params) {
1685
+ chain.push( action );
1686
+ action = action._origin;
1687
+ }
1688
+ chain.reverse();
1689
+ for (const art of chain) {
1690
+ const origin = art._origin;
1691
+ if (!art.params && origin.params) {
1692
+ for (const name in origin.params) {
1693
+ // TODO: we could check _annotate here to decide whether we really
1694
+ // not to create proxies
1695
+ const orig = origin.params[name];
1696
+ linkToOrigin( orig, name, art, 'params', weakLocation( orig.location ), true )
1697
+ .$inferred = 'expand-param';
1698
+ }
1699
+ }
1700
+ if (!art.returns && origin.returns) {
1701
+ // TODO: make linkToOrigin() work for returns, kind/name?
1702
+ const location = weakLocation( origin.returns.location );
1703
+ art.returns = {
1704
+ name: Object.assign( {}, art.name, { id: '', param: '', location } ),
1705
+ kind: 'param',
1706
+ location,
1707
+ $inferred: 'expand-param',
1708
+ };
1709
+ setProp( art.returns, '_parent', art );
1710
+ setProp( art.returns, '_main', art._main || art );
1711
+ setProp( art.returns, '_origin', origin.returns );
1712
+ }
1713
+ }
1714
+ }
1551
1715
 
1552
1716
  function extensionFor( art ) {
1553
1717
  if (art.kind === 'annotate')
@@ -1586,6 +1750,8 @@ function resolve( model ) {
1586
1750
  ext.kind = 'annotate'; // after setMemberParent()!
1587
1751
  setProp( art, '_extension', ext );
1588
1752
  setProp( ext.name, '_artifact', art );
1753
+ if (art.returns)
1754
+ ext.$syntax = 'returns';
1589
1755
  return ext;
1590
1756
  }
1591
1757
 
@@ -1752,7 +1918,7 @@ function resolve( model ) {
1752
1918
  function resolveQuery( query ) {
1753
1919
  if (!query._main) // parse error
1754
1920
  return;
1755
- populateQuery( query );
1921
+ traverseQueryPost( query, null, populateQuery );
1756
1922
  forEachGeneric( query, '$tableAliases', ( alias ) => {
1757
1923
  // console.log( info( null, [alias.location,alias], 'SQA:' ).toString() );
1758
1924
  if (alias.kind === 'mixin')
@@ -1761,8 +1927,11 @@ function resolve( model ) {
1761
1927
  // pure path has been resolved, resolve args and filter now:
1762
1928
  resolveExpr( alias, 'from', query._parent );
1763
1929
  } );
1930
+ for (const col of query.$inlines)
1931
+ resolveExpr( col.value, 'expr', col, undefined, true );
1932
+ // for (const col of query.$inlines)
1933
+ // if (!col.value.path) throw Error(col.name.element)
1764
1934
  if (query !== query._main._leadingQuery) // will be done later
1765
- // TODO: rethink elements(view) === elements(view._leadingQuery)
1766
1935
  forEachGeneric( query, 'elements', resolveRefs );
1767
1936
  if (query.from)
1768
1937
  resolveJoinOn( query.from );
@@ -1786,7 +1955,7 @@ function resolve( model ) {
1786
1955
  return;
1787
1956
 
1788
1957
  function resolveJoinOn( join ) {
1789
- if (join.args) { // JOIN
1958
+ if (join && join.args) { // JOIN
1790
1959
  for (const j of join.args)
1791
1960
  resolveJoinOn( j );
1792
1961
  if (join.on)
@@ -1823,7 +1992,10 @@ function resolve( model ) {
1823
1992
  }
1824
1993
  const target = resolvePath( obj.target, 'target', art );
1825
1994
  if (obj.on) {
1826
- if (!art._main || !art._parent.elements && !art._parent.items) {
1995
+ if (!art._main || !art._parent.elements && !art._parent.items && !art._parent.targetAspect) {
1996
+ // TODO: test of .items a bit unclear - we should somehow restrict the
1997
+ // use of unmanaged assocs in MANY, at least with $self
1998
+ // TODO: $self usage in anonymous aspects to be corrected in Core Compiler
1827
1999
  const isComposition = obj.type && obj.type.path && obj.type.path[0] &&
1828
2000
  obj.type.path[0].id === 'cds.Composition';
1829
2001
  message( 'assoc-as-type', [ obj.on.location, art ],
@@ -2264,6 +2436,9 @@ function resolve( model ) {
2264
2436
 
2265
2437
  // TODO: there is no need to rewrite the on condition of non-leading queries,
2266
2438
  // i.e. we could just have on = {…}
2439
+ // TODO: re-check $self rewrite (with managed composition of aspects),
2440
+ // and actually also $self inside anonymous aspect definitions
2441
+ // (not entirely urgent as we do not analyse it further, at least sole "$self")
2267
2442
  function rewriteCondition( elem, assoc ) {
2268
2443
  if (enableExpandElements && elem._parent && elem._parent.kind === 'element') {
2269
2444
  // managed association as sub element not supported yet
@@ -2468,6 +2643,8 @@ function resolve( model ) {
2468
2643
  }
2469
2644
 
2470
2645
  function resolveExpr( expr, expected, user, extDict, expandOrInline) {
2646
+ // TODO: when we have rewritten the resolvePath functions,
2647
+ // define a traverseExpr() in ./utils.js
2471
2648
  // TODO: extra "expected" 'expand'/'inline' instead o param `expandOrInline`
2472
2649
  if (!expr || typeof expr === 'string') // parse error or keywords in {xpr:...}
2473
2650
  return;
@@ -2504,7 +2681,7 @@ function resolve( model ) {
2504
2681
  else if (expr.query) {
2505
2682
  const { query } = expr;
2506
2683
  if (query.kind || query._leadingQuery) { // UNION has _leadingQuery
2507
- traverseQueryPost( query, false, resolveQuery );
2684
+ // traverseQueryPost( query, false, resolveQuery );
2508
2685
  }
2509
2686
  else {
2510
2687
  error( 'expr-no-subquery', [ expr.location, user ], {},
@@ -2515,12 +2692,19 @@ function resolve( model ) {
2515
2692
  const args = Array.isArray(expr.args) ? expr.args : Object.values( expr.args );
2516
2693
  args.forEach( e => e && resolveExpr( e, e.$expected || expected, user, extDict ) );
2517
2694
  }
2695
+ if (expr.suffix && isDeprecatedEnabled( options )) {
2696
+ const { location } = expr.suffix[0] || expr;
2697
+ error( null, [ location, user ], { prop: 'deprecated' },
2698
+ 'Window functions are not supported if $(PROP) options are set' );
2699
+ }
2700
+ if (expr.suffix)
2701
+ expr.suffix.forEach( s => s && resolveExpr( s, expected, user, extDict ) );
2518
2702
  }
2519
2703
 
2520
2704
  function resolveParamsAndWhere( step, expected, user, extDict, isLast ) {
2521
2705
  const alias = step._navigation && step._navigation.kind === '$tableAlias' && step._navigation;
2522
2706
  const type = alias || effectiveType( step._artifact );
2523
- const art = type && type.target && type.target._artifact || type;
2707
+ const art = (type && type.target) ? type.target._artifact : type;
2524
2708
  if (!art)
2525
2709
  return;
2526
2710
  const entity = (art.kind === 'entity') &&
@@ -2531,7 +2715,7 @@ function resolve( model ) {
2531
2715
  if (step.where)
2532
2716
  resolveExpr( step.where, 'filter', user, environment( type ) );
2533
2717
  }
2534
- else if (step.where || step.cardinality ) {
2718
+ else if (step.where && step.where.location || step.cardinality ) {
2535
2719
  const location = combinedLocation( step.where, step.cardinality );
2536
2720
  // XSN TODO: filter$location including […]
2537
2721
  message( 'expr-no-filter', [ location, user ], { '#': expected },
@@ -2699,8 +2883,6 @@ function navProjection( navigation, preferred ) {
2699
2883
  // Query tree post-order traversal - called for everything which makes a query
2700
2884
  // except "real ones": operands of UNION etc, JOIN with ON, and sub queries in FROM
2701
2885
  function traverseQueryPost( query, simpleOnly, callback ) {
2702
- while (Array.isArray(query)) // query in parentheses, TODO: remove
2703
- query = query[0];
2704
2886
  if (!query) // parser error
2705
2887
  return;
2706
2888
  if (!query.op) { // in FROM (not JOIN)
@@ -2720,11 +2902,19 @@ function traverseQueryPost( query, simpleOnly, callback ) {
2720
2902
  // console.log('FE:')
2721
2903
  }
2722
2904
  else if (query.args) { // JOIN, UNION, INTERSECT
2723
- for (const q of query.args)
2724
- traverseQueryPost( q, simpleOnly, callback );
2725
- // The ON condition has to be traversed extra, because it must be evaluated
2726
- // after the complete FROM has been traversed. It is also not necessary to
2727
- // evaluate it in populateQuery().
2905
+ if (!query.join && simpleOnly == null) {
2906
+ // enough for elements: traverse only first args for UNION/INTERSECT
2907
+ // TODO: we might use this also when we do not rewrite associations
2908
+ // in non-referred sub queries
2909
+ traverseQueryPost( query.args[0], simpleOnly, callback );
2910
+ }
2911
+ else {
2912
+ for (const q of query.args)
2913
+ traverseQueryPost( q, simpleOnly, callback );
2914
+ // The ON condition has to be traversed extra, because it must be evaluated
2915
+ // after the complete FROM has been traversed. It is also not necessary to
2916
+ // evaluate it in populateQuery().
2917
+ }
2728
2918
  }
2729
2919
  // else: with parse error (`select from <EOF>`, `select distinct from;`)
2730
2920
  }