@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
@@ -45,9 +45,7 @@ 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
50
  const { pushLink } = require('./utils');
53
51
  const {
@@ -84,7 +82,7 @@ function resolve( model ) {
84
82
  } = fns( model, environment );
85
83
  const {
86
84
  info, warning, error, message,
87
- } = makeMessageFunction( model, model.options, 'compile' );
85
+ } = model.$messageFunctions;
88
86
  const {
89
87
  initArtifact,
90
88
  lateExtensions,
@@ -95,6 +93,8 @@ function resolve( model ) {
95
93
 
96
94
  // behavior depending on option `deprecated`:
97
95
  const enableExpandElements = !isDeprecatedEnabled( options, 'noElementsExpansion' );
96
+ // TODO: we should get rid of noElementsExpansion soon; both
97
+ // beta.nestedProjections and beta.universalCsn do not work with it.
98
98
  const scopedRedirections
99
99
  = enableExpandElements &&
100
100
  !isDeprecatedEnabled( options, 'generatedEntityNameWithUnderscore' ) &&
@@ -319,6 +319,7 @@ function resolve( model ) {
319
319
  return null;
320
320
  }
321
321
 
322
+ // TODO: test it in combination with top-level CAST function
322
323
  function directType( art ) {
323
324
  // Be careful when using it with art.target or art.enum or art.elements
324
325
  if (art._origin || art.builtin)
@@ -326,14 +327,14 @@ function resolve( model ) {
326
327
  if (art.type)
327
328
  return resolveType( art.type, art );
328
329
  // console.log( 'EXPR-IN', art.kind, refString(art.name) )
329
- if (!art._main)
330
+ if (!art._main || !art.value || !art.value.path)
330
331
  return undefined;
331
332
  if (art._pathHead && art.value) {
332
333
  setProp( art, '_origin', resolvePath( art.value, 'expr', art, null ) );
333
334
  return art._origin;
334
335
  }
335
336
  const query = userQuery( art ) || art._parent;
336
- if (query.kind !== 'select' || !art.value.path)
337
+ if (query.kind !== 'select')
337
338
  return undefined;
338
339
  // Reached an element in a query which is a simple ref -> return referred artifact
339
340
  // TODO: remember that we still have to resolve path arguments and filters
@@ -424,7 +425,7 @@ function resolve( model ) {
424
425
  info( 'query-missing-element', [ ielem.name.location, view ], { id },
425
426
  'Element $(ID) is missing in specified elements' );
426
427
  }
427
- else if (!Array.isArray( ielem ) && !Array.isArray( selem )) {
428
+ else {
428
429
  for (const prop in selem) {
429
430
  // just annotation assignments and doc comments for the moment
430
431
  if (prop.charAt(0) === '@' || prop === 'doc')
@@ -435,7 +436,7 @@ function resolve( model ) {
435
436
  }
436
437
  for (const id in view.elements$) {
437
438
  const selem = view.elements$[id]; // specified element
438
- if (!Array.isArray( selem ) && !selem.$replacement) {
439
+ if (!selem.$replacement) {
439
440
  error( 'query-unspecified-element', [ selem.name.location, selem ], { id },
440
441
  'Element $(ID) does not result from the query' );
441
442
  }
@@ -445,8 +446,7 @@ function resolve( model ) {
445
446
  function traverseElementEnvironments( art ) {
446
447
  populateView( art );
447
448
  environment( art );
448
- // TODO: but we have no proxy copy of sub elements yet - no redirection in ValueHelpList.cds
449
- forEachGeneric( art, 'elements', traverseElementEnvironments );
449
+ forEachMember( art, traverseElementEnvironments );
450
450
  }
451
451
 
452
452
  function populateQuery( query ) {
@@ -454,6 +454,7 @@ function resolve( model ) {
454
454
  // already done or $join query or parse error
455
455
  return;
456
456
  setProp( query, '_combined', Object.create(null) );
457
+ query.$inlines = [];
457
458
  forEachGeneric( query, '$tableAliases', resolveTabRef );
458
459
 
459
460
  initFromColumns( query, query.columns );
@@ -540,7 +541,8 @@ function resolve( model ) {
540
541
  if (art.elements || art.kind === '$tableAlias' ||
541
542
  // no element expansions for "non-proper" types like
542
543
  // entities (as parameter types) etc:
543
- struct.kind !== 'type' && struct.kind !== 'element' && !struct._outer)
544
+ struct.kind !== 'type' && struct.kind !== 'element' && struct.kind !== 'param' &&
545
+ !struct._outer)
544
546
  return false;
545
547
  if (struct.elements === 0 || isInParents( art, eType )) {
546
548
  art.elements = 0; // circular
@@ -559,8 +561,12 @@ function resolve( model ) {
559
561
  // or should we use orig.location? - TODO: try to find test to see message
560
562
  .$inferred = 'expand-element';
561
563
  }
562
- art.$expand = 'origin'; // if value stays, elements won't appear in CSN
563
- // TODO: should we also set 'virtual' here?
564
+ // Set elements expansion status (the if condition is always true, as no
565
+ // elements expansion will take place on artifact with existing other
566
+ // member property):
567
+ if (!art.$expand)
568
+ art.$expand = 'origin'; // if value stays, elements won't appear in CSN
569
+ // TODO: have some art.elements[SYM.$inferred] = 'expand-elements';
564
570
  return true;
565
571
  }
566
572
 
@@ -586,14 +592,45 @@ function resolve( model ) {
586
592
  return false;
587
593
  }
588
594
 
589
- function setExpandStatus( elem ) {
595
+ // About Helper property $expand for faster the XSN-to-CSN transformation
596
+ // - null/undefined: artifact, member, items does not contain expanded members
597
+ // - 'origin': all expanded (sub) elements have no new target/on and no new annotations
598
+ // that value is only on elements, types, and params -> no other members
599
+ // when set, only on elem/art with expanded elements
600
+ // - 'target': all expanded (sub) elements might only have new target/on, but
601
+ // no indivual annotations on any (sub) member
602
+ // when set, traverse all parents where the value has been 'origin' before
603
+ // - 'annotate': at least one inferred (sub) member has an individual annotation,
604
+ // not counting propagated ones; set up to the definition (main artifact)
605
+ // (only set with anno on $inferred elem)
606
+ // Usage according to CSN flavor:
607
+ // - gensrc: do not render enferred elements (including expanded elements),
608
+ // collect annotate statements with value 'annotate'
609
+ // - client: do not render expanded sub elements if artifact/member is no type, has a type,
610
+ // has $expand = 'origin', and all its _origin also have $expand = 'origin'
611
+ // (might sometimes render the elements unnecessarily, which is not wrong)
612
+ // - universal: do not render expanded sub elements if $expand = 'origin'
613
+ function setExpandStatus( elem, status ) {
614
+ // set on element
590
615
  while (elem._main) {
591
616
  elem = elem._parent;
592
- if (!elem.$expand)
617
+ if (elem.$expand !== 'origin')
593
618
  return;
594
- elem.$expand = 'target'; // meaning: expanded, containing assocs
619
+ elem.$expand = status; // meaning: expanded, containing assocs
620
+ for (let line = elem.items; line; line = line.items)
621
+ line.$expand = status; // to-csn just uses the innermost $expand
622
+ }
623
+ }
624
+ function setExpandStatusAnnotate( elem, status ) {
625
+ for (;;) {
626
+ if (elem.$expand === status)
627
+ return; // already set
628
+ elem.$expand = status; // meaning: expanded, containing annos
595
629
  for (let line = elem.items; line; line = line.items)
596
- line.$expand = 'target'; // to-csn just uses the innermost $expand
630
+ line.$expand = status; // to-csn just uses the innermost $expand
631
+ if (!elem._main)
632
+ return;
633
+ elem = elem._parent;
597
634
  }
598
635
  }
599
636
 
@@ -605,7 +642,7 @@ function resolve( model ) {
605
642
  // PRE: elem has no target, assoc has target prop
606
643
  if (elem.kind === '$tableAlias')
607
644
  return false;
608
- setExpandStatus( elem );
645
+ setExpandStatus( elem, 'target' );
609
646
  let target = resolvePath( assoc.target, 'target', assoc );
610
647
  // console.log( info( null, [ elem.location, elem ], {target,art:assoc,name:''+assoc.target},
611
648
  // 'RED').toString())
@@ -718,6 +755,7 @@ function resolve( model ) {
718
755
  const nullScope = {
719
756
  kind: 'namespace', name: { absolute: autoScopeName, location }, location,
720
757
  };
758
+ model.definitions[autoScopeName] = nullScope;
721
759
  initArtifact( nullScope );
722
760
  return nullScope;
723
761
  }
@@ -926,26 +964,53 @@ function resolve( model ) {
926
964
  return art;
927
965
  }
928
966
 
967
+ // TODO: probably do this already in definer.js
968
+ function ensureColumnName( col, query ) {
969
+ if (col.name)
970
+ return col.name.id;
971
+ if (col.inline || col.val === '*')
972
+ return '';
973
+ const path = col.value &&
974
+ (col.value.path || !col.value.args && col.value.func && col.value.func.path);
975
+ if (path) {
976
+ const last = !path.broken && path.length && path[path.length - 1];
977
+ if (last) {
978
+ col.name = { id: last.id, location: last.location, $inferred: 'as' };
979
+ return col.name.id;
980
+ }
981
+ }
982
+ else if (col.value || col.expand) {
983
+ error( 'query-req-name', [ col.value && col.value.location || col.location, query ], {},
984
+ 'Alias name is required for this select item' );
985
+ }
986
+ // invent a name for code completion in expression
987
+ col.name = {
988
+ id: '',
989
+ location: col.value && col.value.location || col.location,
990
+ $inferred: 'none',
991
+ };
992
+ return '';
993
+ }
994
+
929
995
  // TODO: make this function shorter - make part of this (e.g. setting
930
996
  // parent/name) also be part of definer.js
931
997
  // TODO: query is actually the elemParent, where the new elements are added to
932
- function initFromColumns( query, columns, elements = undefined, colParent = undefined ) {
998
+ // top-level: just query, columns
999
+ // inline: + elements (TODO: remove), colParent
1000
+ // expand: just query (which is a column/element), columns=array of expand
1001
+ function initFromColumns( query, columns, inlineHead = undefined ) {
933
1002
  const elemsParent = query.items || query;
934
- if (!elements) {
935
- elements = Object.create(null); // explicitly prov
1003
+ if (!inlineHead) {
936
1004
  elemsParent.elements = Object.create(null);
937
1005
  if (query._main._leadingQuery === query) // never the case for 'expand'
938
1006
  query._main.elements = elemsParent.elements;
939
1007
  }
940
- let wildcard = false;
941
- let inline = 0;
942
1008
 
943
1009
  for (const col of columns || [ { val: '*' } ]) {
944
1010
  if (col.val === '*') {
945
- wildcard = col;
946
- continue;
1011
+ const siblings = wildcardSiblings( columns, query );
1012
+ expandWildcard( col, siblings, inlineHead, query );
947
1013
  }
948
- col.kind = 'element';
949
1014
  if ((col.expand || col.inline) && !isBetaEnabled( options, 'nestedProjections' )) {
950
1015
  error( null, [ col.location, query ], { prop: (col.expand ? 'expand' : 'inline') },
951
1016
  'Unsupported nested $(PROP)' );
@@ -957,137 +1022,135 @@ function resolve( model ) {
957
1022
  col.name = {};
958
1023
  // a name for this internal symtab entry (e.g. '.2' to avoid clashes
959
1024
  // 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 );
1025
+ const q = userQuery( query );
1026
+ q.$inlines.push( col );
1027
+ // or use userQuery( query ) in the following, too?
1028
+ setMemberParent( col, `.${ q.$inlines.length }`, query );
1029
+ initFromColumns( query, col.inline, col );
964
1030
  continue;
965
1031
  }
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;
1032
+ else if (!col.$replacement) {
1033
+ const id = ensureColumnName( col, query );
1034
+ col.kind = 'element';
1035
+ dictAdd( elemsParent.elements, id, col, ( name, location ) => {
1036
+ error( 'duplicate-definition', [ location, query ], { name, '#': 'element' } );
1037
+ });
1038
+ setMemberParent( col, id, query );
996
1039
  }
997
1040
  }
998
- if (wildcard)
999
- expandWildcard( elements, wildcard );
1000
1041
  forEachGeneric( query, 'elements', e => initElem( e, query ) );
1001
1042
  return true;
1043
+ }
1002
1044
 
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
- }
1045
+ // col ($replacement set before *)
1046
+ // false if two cols have same name
1047
+ function wildcardSiblings( columns, query ) {
1048
+ const siblings = Object.create(null);
1049
+ if (!columns)
1050
+ return siblings;
1051
+
1052
+ let seenWildcard = null;
1053
+ for (const col of columns) {
1054
+ const id = ensureColumnName( col, query );
1055
+ if (id) {
1056
+ col.$replacement = !seenWildcard;
1057
+ siblings[id] = !(id in siblings) && col;
1058
+ }
1059
+ else if (col.val === '*') {
1060
+ seenWildcard = true;
1061
+ }
1062
+ }
1063
+ return siblings;
1064
+ }
1065
+
1066
+ // TODO: make struct.* are to be added at place, not sub-wildcards first,
1067
+ // see test3/Queries/ExpandInlineCreate/Excluding.cds
1068
+ // TODO: disallow $self.elem.* and $self.*, toSelf.* (circular dependency)
1069
+ function expandWildcard( wildcard, siblingElements, colParent, query ) {
1070
+ const { elements } = query.items || query;
1071
+ let location = wildcard.location || query.from && query.from.location || query.location;
1072
+ const inferred = query._main.$inferred;
1073
+ const excludingDict = (colParent || query).excludingDict || Object.create(null);
1074
+
1075
+ const envParent = wildcard._pathHead; // TODO: rename _pathHead to _pathEnv
1076
+ // console.log('S1:',location.line,location.col,
1077
+ // envParent&&!!envParent._origin&&envParent._origin.name)
1078
+ const env = columnEnv( envParent, query );
1079
+ // console.log('S2:',location.line,location.col,
1080
+ // envParent&&!!envParent._origin&&envParent._origin.name,
1081
+ // Object.keys(env),Object.keys(elements))
1082
+ for (const name in env) {
1083
+ const navElem = env[name];
1084
+ // TODO: if it is an array, filter out those with masked
1085
+ if (excludingDict[name] || navElem.masked && navElem.masked.val)
1086
+ continue;
1087
+ const sibling = siblingElements[name];
1088
+ if (sibling) { // is explicitly provided (without duplicate)
1089
+ if (!inferred && !envParent) // not yet for expand/inline
1090
+ reportReplacement( sibling, navElem, query );
1091
+ if (!sibling.$replacement) {
1092
+ sibling.$replacement = true;
1093
+ sibling.kind = 'element';
1094
+ dictAdd( elements, name, sibling, ( _name, loc ) => {
1095
+ // there can be a definition from a previous inline with the same name:
1096
+ error( 'duplicate-definition', [ loc, query ], { name, '#': 'element' } );
1097
+ });
1098
+ setMemberParent( sibling, name, query );
1068
1099
  }
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 );
1100
+ // else {
1101
+ // sibling.$inferred = 'query';
1102
+ // }
1103
+ }
1104
+ else if (Array.isArray(navElem)) {
1105
+ const names = navElem.filter( e => !e.$duplicates)
1106
+ .map( e => `${ e.name.alias }.${ e.name.element }` );
1107
+ if (names.length) {
1108
+ error( 'wildcard-ambiguous', [ location, query ], { id: name, names },
1109
+ 'Ambiguous wildcard, select $(ID) explicitly with $(NAMES)' );
1079
1110
  }
1080
1111
  }
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 );
1112
+ else {
1113
+ location = weakLocation( location );
1114
+ const origin = envParent ? navElem : navElem._origin;
1115
+ const elem = linkToOrigin( origin, name, query, null, location );
1116
+ // TODO: check assocToMany { * }
1117
+ dictAdd( elements, name, elem, ( _name, loc ) => {
1118
+ // there can be a definition from a previous inline with the same name:
1119
+ error( 'duplicate-definition', [ loc, query ], { name, '#': 'element' } );
1120
+ });
1121
+ elem.$inferred = '*';
1122
+ elem.name.$inferred = '*';
1123
+ if (envParent)
1124
+ setWildcardExpandInline( elem, envParent, origin, name, location );
1125
+ else
1126
+ setElementOrigin( elem, navElem, name, location );
1127
+ }
1128
+ }
1129
+ if (envParent || query.kind !== 'select') {
1130
+ // already done in populateQuery (TODO: change that and check whether
1131
+ // `*` is allowed at all in definer)
1132
+ const user = colParent || query;
1133
+ for (const name in user.excludingDict)
1134
+ resolveExcluding( name, env, excludingDict, query );
1135
+ }
1136
+ }
1137
+
1138
+ function reportReplacement( sibling, navElem, query ) {
1139
+ // TODO: bring this much less often = only if shadowed elem does not appear
1140
+ // in expr and if not projected as other name.
1141
+ // Probably needs to be reported at a later phase
1142
+ const path = sibling.value && sibling.value.path;
1143
+ if (!sibling.target || sibling.target.$inferred || // not explicit REDIRECTED TO
1144
+ path && path[path.length - 1].id !== sibling.name.id) { // or renamed
1145
+ const { id } = sibling.name;
1146
+ if (Array.isArray(navElem)) {
1147
+ info( 'wildcard-excluding-many', [ sibling.name.location, query ], { id },
1148
+ 'This select item replaces $(ID) from two or more sources' );
1149
+ }
1150
+ else {
1151
+ info( 'wildcard-excluding-one', [ sibling.name.location, query ],
1152
+ { id, alias: navElem._parent.name.id },
1153
+ 'This select item replaces $(ID) from table alias $(ALIAS)' );
1091
1154
  }
1092
1155
  }
1093
1156
  }
@@ -1378,6 +1441,7 @@ function resolve( model ) {
1378
1441
  }
1379
1442
  // Resolve projections/views
1380
1443
  // if (art.query)console.log( info( null, [art.query.location,art.query], 'VQ:' ).toString() );
1444
+ // TODO: here, any order should be ok, i.e. just loop over $queries
1381
1445
  traverseQueryPost( art.query, false, resolveQuery );
1382
1446
 
1383
1447
  if (obj.type || obj._origin || obj.value && obj.value.path || obj.elements) // typed artifacts
@@ -1523,14 +1587,28 @@ function resolve( model ) {
1523
1587
  }
1524
1588
  }
1525
1589
  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;
1590
+ if (art.kind === 'action' || art.kind === 'function') {
1591
+ expandParameters( art );
1592
+ if (art.returns)
1593
+ effectiveType( art.returns );
1594
+ }
1595
+ const aor = art.returns || art;
1596
+ const obj = aor.items || aor.targetAspect || aor;
1597
+ // Currently(?), effectiveType() does not calculate the effective type of
1598
+ // its line item:
1599
+ effectiveType( obj );
1600
+ if (art._annotate.elements)
1601
+ setExpandStatusAnnotate( aor, 'annotate' );
1531
1602
  annotate( obj, 'element', 'elements', 'enum', art );
1532
1603
  annotate( art, 'action', 'actions' );
1533
1604
  annotate( art, 'param', 'params' );
1605
+ // const { returns } = art._annotate;
1606
+ // if (returns) {
1607
+ // const dict = returns.elements;
1608
+ // const env = obj.returns && obj.returns.elements || null;
1609
+ // for (const n in dict)
1610
+ // annotateMembers( env && env[n], dict[n], 'elements', n, parent, 'element' );
1611
+ // }
1534
1612
  }
1535
1613
  return;
1536
1614
 
@@ -1548,6 +1626,42 @@ function resolve( model ) {
1548
1626
  annotateMembers( env && env[n], dict[n], prop, n, parent, kind );
1549
1627
  }
1550
1628
  }
1629
+ function expandParameters( action ) {
1630
+ // see also expandElements()
1631
+ if (!enableExpandElements || !effectiveType( action ))
1632
+ return;
1633
+ const chain = [];
1634
+ // Should we be able to consider params and returns separately?
1635
+ // Probably not, let to-csn omit unchanged params/returns.
1636
+ while (action._origin && !action.params) {
1637
+ chain.push( action );
1638
+ action = action._origin;
1639
+ }
1640
+ chain.reverse();
1641
+ for (const art of chain) {
1642
+ const origin = art._origin;
1643
+ if (!art.params && origin.params) {
1644
+ for (const name in origin.params) {
1645
+ // TODO: we could check _annotate here to decide whether we really
1646
+ // not to create proxies
1647
+ const orig = origin.params[name];
1648
+ linkToOrigin( orig, name, art, 'params', weakLocation( orig.location ), true )
1649
+ .$inferred = 'expand-param';
1650
+ }
1651
+ }
1652
+ if (!art.returns && origin.returns) {
1653
+ // TODO: make linkToOrigin() work for returns, kind/name?
1654
+ const location = weakLocation( origin.returns.location );
1655
+ art.returns = {
1656
+ name: Object.assign( {}, art.name, { id: '', param: '', location } ),
1657
+ kind: 'param',
1658
+ location,
1659
+ $inferred: 'expand-param',
1660
+ };
1661
+ setProp( art.returns, '_origin', origin.returns );
1662
+ }
1663
+ }
1664
+ }
1551
1665
 
1552
1666
  function extensionFor( art ) {
1553
1667
  if (art.kind === 'annotate')
@@ -1586,6 +1700,8 @@ function resolve( model ) {
1586
1700
  ext.kind = 'annotate'; // after setMemberParent()!
1587
1701
  setProp( art, '_extension', ext );
1588
1702
  setProp( ext.name, '_artifact', art );
1703
+ if (art.returns)
1704
+ ext.$syntax = 'returns';
1589
1705
  return ext;
1590
1706
  }
1591
1707
 
@@ -1761,8 +1877,11 @@ function resolve( model ) {
1761
1877
  // pure path has been resolved, resolve args and filter now:
1762
1878
  resolveExpr( alias, 'from', query._parent );
1763
1879
  } );
1880
+ for (const col of query.$inlines)
1881
+ resolveExpr( col.value, 'expr', col, undefined, true );
1882
+ // for (const col of query.$inlines)
1883
+ // if (!col.value.path) throw Error(col.name.element)
1764
1884
  if (query !== query._main._leadingQuery) // will be done later
1765
- // TODO: rethink elements(view) === elements(view._leadingQuery)
1766
1885
  forEachGeneric( query, 'elements', resolveRefs );
1767
1886
  if (query.from)
1768
1887
  resolveJoinOn( query.from );
@@ -1786,7 +1905,7 @@ function resolve( model ) {
1786
1905
  return;
1787
1906
 
1788
1907
  function resolveJoinOn( join ) {
1789
- if (join.args) { // JOIN
1908
+ if (join && join.args) { // JOIN
1790
1909
  for (const j of join.args)
1791
1910
  resolveJoinOn( j );
1792
1911
  if (join.on)
@@ -1823,7 +1942,10 @@ function resolve( model ) {
1823
1942
  }
1824
1943
  const target = resolvePath( obj.target, 'target', art );
1825
1944
  if (obj.on) {
1826
- if (!art._main || !art._parent.elements && !art._parent.items) {
1945
+ if (!art._main || !art._parent.elements && !art._parent.items && !art._parent.targetAspect) {
1946
+ // TODO: test of .items a bit unclear - we should somehow restrict the
1947
+ // use of unmanaged assocs in MANY, at least with $self
1948
+ // TODO: $self usage in anonymous aspects to be corrected in Core Compiler
1827
1949
  const isComposition = obj.type && obj.type.path && obj.type.path[0] &&
1828
1950
  obj.type.path[0].id === 'cds.Composition';
1829
1951
  message( 'assoc-as-type', [ obj.on.location, art ],
@@ -2264,6 +2386,9 @@ function resolve( model ) {
2264
2386
 
2265
2387
  // TODO: there is no need to rewrite the on condition of non-leading queries,
2266
2388
  // i.e. we could just have on = {…}
2389
+ // TODO: re-check $self rewrite (with managed composition of aspects),
2390
+ // and actually also $self inside anonymous aspect definitions
2391
+ // (not entirely urgent as we do not analyse it further, at least sole "$self")
2267
2392
  function rewriteCondition( elem, assoc ) {
2268
2393
  if (enableExpandElements && elem._parent && elem._parent.kind === 'element') {
2269
2394
  // managed association as sub element not supported yet
@@ -2515,12 +2640,18 @@ function resolve( model ) {
2515
2640
  const args = Array.isArray(expr.args) ? expr.args : Object.values( expr.args );
2516
2641
  args.forEach( e => e && resolveExpr( e, e.$expected || expected, user, extDict ) );
2517
2642
  }
2643
+ if (expr.suffix && !isBetaEnabled( options, 'windowFunctions' )) {
2644
+ const { location } = expr.suffix[0] || expr;
2645
+ error( null, [ location, user ], 'Window functions are not supported' );
2646
+ }
2647
+ if (expr.suffix)
2648
+ expr.suffix.forEach( s => s && resolveExpr( s, expected, user, extDict ) );
2518
2649
  }
2519
2650
 
2520
2651
  function resolveParamsAndWhere( step, expected, user, extDict, isLast ) {
2521
2652
  const alias = step._navigation && step._navigation.kind === '$tableAlias' && step._navigation;
2522
2653
  const type = alias || effectiveType( step._artifact );
2523
- const art = type && type.target && type.target._artifact || type;
2654
+ const art = (type && type.target) ? type.target._artifact : type;
2524
2655
  if (!art)
2525
2656
  return;
2526
2657
  const entity = (art.kind === 'entity') &&
@@ -2531,7 +2662,7 @@ function resolve( model ) {
2531
2662
  if (step.where)
2532
2663
  resolveExpr( step.where, 'filter', user, environment( type ) );
2533
2664
  }
2534
- else if (step.where || step.cardinality ) {
2665
+ else if (step.where && step.where.location || step.cardinality ) {
2535
2666
  const location = combinedLocation( step.where, step.cardinality );
2536
2667
  // XSN TODO: filter$location including […]
2537
2668
  message( 'expr-no-filter', [ location, user ], { '#': expected },