@sap/cds-compiler 6.7.3 → 6.9.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 (116) hide show
  1. package/CHANGELOG.md +70 -0
  2. package/README.md +4 -0
  3. package/bin/cdsc.js +5 -5
  4. package/bin/cdshi.js +1 -0
  5. package/bin/cdsse.js +1 -1
  6. package/lib/api/main.js +17 -9
  7. package/lib/api/options.js +5 -2
  8. package/lib/api/validate.js +1 -1
  9. package/lib/base/builtins.js +13 -9
  10. package/lib/{model → base}/csnRefs.js +8 -10
  11. package/lib/base/error.js +2 -0
  12. package/lib/base/message-registry.js +68 -4
  13. package/lib/base/messages.js +4 -2
  14. package/lib/{optionProcessor.js → base/optionProcessor.js} +5 -3
  15. package/lib/base/{model.js → specialOptions.js} +16 -39
  16. package/lib/checks/arrayOfs.js +1 -1
  17. package/lib/checks/elements.js +1 -1
  18. package/lib/checks/enricher.js +2 -2
  19. package/lib/checks/featureFlags.js +54 -24
  20. package/lib/checks/foreignKeys.js +1 -1
  21. package/lib/checks/invalidTarget.js +1 -1
  22. package/lib/checks/managedInType.js +1 -1
  23. package/lib/checks/onConditions.js +1 -1
  24. package/lib/checks/queryNoDbArtifacts.js +1 -1
  25. package/lib/checks/validator.js +10 -14
  26. package/lib/compiler/assert-consistency.js +11 -9
  27. package/lib/compiler/base.js +5 -1
  28. package/lib/compiler/builtins.js +1 -1
  29. package/lib/compiler/checks.js +3 -3
  30. package/lib/compiler/define.js +6 -3
  31. package/lib/{base → compiler}/dictionaries.js +4 -3
  32. package/lib/compiler/extend.js +121 -21
  33. package/lib/compiler/generate.js +2 -2
  34. package/lib/compiler/index.js +11 -3
  35. package/lib/compiler/kick-start.js +1 -1
  36. package/lib/compiler/lsp-api.js +3 -3
  37. package/lib/compiler/populate.js +6 -7
  38. package/lib/compiler/resolve.js +53 -36
  39. package/lib/compiler/shared.js +68 -18
  40. package/lib/compiler/tweak-assocs.js +2 -2
  41. package/lib/compiler/utils.js +28 -27
  42. package/lib/compiler/xpr-rewrite.js +3 -3
  43. package/lib/edm/EdmPrimitiveTypeDefinitions.js +4 -1
  44. package/lib/edm/annotations/edmJson.js +2 -4
  45. package/lib/edm/annotations/genericTranslation.js +51 -7
  46. package/lib/edm/csn2edm.js +3 -2
  47. package/lib/edm/edmAnnoPreprocessor.js +1 -1
  48. package/lib/edm/edmInboundChecks.js +2 -1
  49. package/lib/edm/edmPreprocessor.js +3 -3
  50. package/lib/edm/edmUtils.js +2 -2
  51. package/lib/gen/BaseParser.js +59 -108
  52. package/lib/gen/CdlGrammar.checksum +1 -1
  53. package/lib/gen/CdlParser.js +2052 -1965
  54. package/lib/gen/Dictionary.json +67 -7
  55. package/lib/json/from-csn.js +14 -14
  56. package/lib/json/to-csn.js +77 -38
  57. package/lib/main.js +3 -3
  58. package/lib/model/csnUtils.js +2 -2
  59. package/lib/modelCompare/compare.js +1 -1
  60. package/lib/modelCompare/utils/filter.js +1 -0
  61. package/lib/parsers/AstBuildingParser.js +83 -33
  62. package/lib/parsers/index.js +1 -1
  63. package/lib/render/manageConstraints.js +1 -1
  64. package/lib/render/toCdl.js +49 -30
  65. package/lib/render/toHdbcds.js +2 -2
  66. package/lib/render/toSql.js +16 -7
  67. package/lib/render/utils/common.js +11 -3
  68. package/lib/render/utils/sql.js +14 -5
  69. package/lib/render/utils/standardDatabaseFunctions.js +108 -99
  70. package/lib/sql-identifier.js +9 -1
  71. package/lib/{model → tool-lib}/enrichCsn.js +3 -2
  72. package/lib/{model → tool-lib}/revealInternalProperties.js +2 -1
  73. package/lib/transform/addTenantFields.js +1 -1
  74. package/lib/transform/db/applyTransformations.js +1 -1
  75. package/lib/transform/db/assertUnique.js +1 -1
  76. package/lib/transform/db/assocsToQueries/transformExists.js +1 -1
  77. package/lib/transform/db/backlinks.js +2 -2
  78. package/lib/transform/db/expansion.js +2 -2
  79. package/lib/transform/db/flattening.js +3 -4
  80. package/lib/transform/db/killAnnotations.js +1 -0
  81. package/lib/transform/db/processSqlServices.js +2 -1
  82. package/lib/transform/db/rewriteCalculatedElements.js +2 -2
  83. package/lib/transform/db/temporal.js +30 -5
  84. package/lib/transform/db/views.js +16 -20
  85. package/lib/transform/draft/db.js +1 -2
  86. package/lib/transform/effective/associations.js +1 -1
  87. package/lib/transform/effective/flattening.js +6 -5
  88. package/lib/transform/effective/main.js +24 -4
  89. package/lib/transform/effective/types.js +1 -1
  90. package/lib/transform/{odata/fioriTreeViews.js → fioriTreeViews.js} +48 -25
  91. package/lib/transform/forOdata.js +25 -7
  92. package/lib/transform/forRelationalDB.js +48 -12
  93. package/lib/transform/localized.js +2 -2
  94. package/lib/transform/odata/createForeignKeys.js +1 -1
  95. package/lib/transform/odata/flattening.js +2 -2
  96. package/lib/transform/odata/toFinalBaseType.js +3 -2
  97. package/lib/transform/odata/typesExposure.js +3 -2
  98. package/lib/transform/transformUtils.js +2 -2
  99. package/lib/transform/translateAssocsToJoins.js +2 -1
  100. package/lib/transform/tupleExpansion.js +44 -4
  101. package/lib/transform/universalCsn/universalCsnEnricher.js +7 -3
  102. package/lib/transform/universalCsn/utils.js +1 -1
  103. package/lib/{base → utils}/lazyload.js +9 -0
  104. package/lib/{base → utils}/node-helpers.js +2 -0
  105. package/lib/utils/objectUtils.js +29 -6
  106. package/lib/{base → utils}/optionProcessorHelper.js +16 -6
  107. package/package.json +3 -40
  108. /package/lib/{model → base}/cloneCsn.js +0 -0
  109. /package/lib/{model/api.js → base/model-api.js} +0 -0
  110. /package/lib/{api → base}/trace.js +0 -0
  111. /package/lib/{model → base}/xprAsTree.js +0 -0
  112. /package/lib/{inspect → tool-lib}/index.js +0 -0
  113. /package/lib/{inspect → tool-lib}/inspectModelStatistics.js +0 -0
  114. /package/lib/{inspect → tool-lib}/inspectPropagation.js +0 -0
  115. /package/lib/{inspect → tool-lib}/inspectUtils.js +0 -0
  116. /package/lib/{base → utils}/shuffle.js +0 -0
@@ -4,14 +4,14 @@
4
4
 
5
5
  const { weakRefLocation } = require('../base/location');
6
6
  const { searchName } = require('../base/messages');
7
- const { isDeprecatedEnabled } = require('../base/model');
8
- const { dictAdd, pushToDict, dictForEach } = require('../base/dictionaries');
7
+ const { isDeprecatedEnabled } = require('../base/specialOptions');
8
+ const { dictAdd, pushToDict, dictForEach } = require('./dictionaries');
9
9
  const { kindProperties, dictKinds } = require('./base');
10
10
  const {
11
11
  setLink,
12
12
  setArtifactLink,
13
13
  copyExpr,
14
- setExpandStatusAnnotate,
14
+ setExpandStatus,
15
15
  linkToOrigin,
16
16
  initItemsLinks,
17
17
  setMemberParent,
@@ -84,6 +84,8 @@ function extend( model ) {
84
84
  const includesNonShadowedFirst
85
85
  = isDeprecatedEnabled( model.options, '_includesNonShadowedFirst' );
86
86
 
87
+ const includeCollisions = [];
88
+
87
89
  forEachGeneric( model, 'definitions', tagCompositionTargets );
88
90
  dictForEach( model.$collectedExtensions, e => e._extensions.forEach( tagCompositionTargets ) );
89
91
  // remark: tagging on extensions works _before_ running extendArtifactBefore() on each artifact
@@ -174,7 +176,7 @@ function extend( model ) {
174
176
  // elements etc. TODO: do that more specifically on the dicts (via symbol)
175
177
  // Probably better: we could use the _extensions dict prop directly in to-csn
176
178
  if (art.$inferred)
177
- setExpandStatusAnnotate( art, 'annotate' );
179
+ setExpandStatus( art, 'annotate' );
178
180
  if (Array.isArray( art._extensions )) {
179
181
  checkExtensionsKind( art._extensions, art );
180
182
  transformArtifactExtensions( art );
@@ -258,7 +260,8 @@ function extend( model ) {
258
260
  checkReturnsExtension( ext, art );
259
261
  }
260
262
  // if (art.elements || art.enum || art.kind === 'annotate')
261
- moveDictExtensions( art, extensionsMap, (art.enum ? 'enum' : 'elements'), 'elements' );
263
+ moveDictExtensions( art, extensionsMap,
264
+ (art.enum ? 'enum' : 'elements'), false );
262
265
  }
263
266
  }
264
267
 
@@ -399,7 +402,9 @@ function extend( model ) {
399
402
  pushTo$add( dict, ext );
400
403
  }
401
404
  else if (prop.charAt(0) === '@' || prop === 'doc' || prop === 'columns' ||
402
- prop === 'length' || prop === 'scale' || prop === 'precision' || prop === 'srid') {
405
+ prop === 'groupBy' || prop === 'where' || prop === 'having' ||
406
+ prop === 'orderBy' || prop === 'limit' || prop === 'length' ||
407
+ prop === 'scale' || prop === 'precision' || prop === 'srid') {
403
408
  if (!isAutoItemsOrReturns)
404
409
  pushToDict( dict, prop, ext );
405
410
  }
@@ -577,7 +582,7 @@ function extend( model ) {
577
582
  else if (prop === 'columns') {
578
583
  const { query } = art;
579
584
  for (const col of ext.columns)
580
- col.$extended = true;
585
+ col.$extended = 'columns';
581
586
 
582
587
  if (art.kind === 'annotate' && art.$inferred === '')
583
588
  return; // internal super-annotate for unknown artifacts
@@ -593,6 +598,10 @@ function extend( model ) {
593
598
  query.columns.push( ...ext.columns );
594
599
  ext.columns.forEach( col => changeParentLinks( col, query ) );
595
600
  }
601
+ else if (prop === 'groupBy' || prop === 'where' || prop === 'having' ||
602
+ prop === 'orderBy' || prop === 'limit') {
603
+ applyQueryClause( prop, ext, art );
604
+ }
596
605
  else if (typeParameters.list.includes( prop )) {
597
606
  const typeExts = art.$typeExts || (art.$typeExts = {});
598
607
  typeExts[prop] = ext;
@@ -603,6 +612,39 @@ function extend( model ) {
603
612
  }
604
613
  }
605
614
 
615
+ function applyQueryClause( prop, ext, art ) {
616
+ const { query } = art;
617
+ const clause = ext[prop];
618
+ const isArray = Array.isArray( clause );
619
+
620
+ if (prop !== 'limit') {
621
+ const items = isArray ? clause : [ clause ];
622
+ for (const item of items) {
623
+ item.$extended = prop;
624
+ setLink( item, '_block', ext._block );
625
+ setLink( item, '_outer', query );
626
+ }
627
+ }
628
+ if (!query?.from?.path) {
629
+ const variant = (query?.from || query)?.op?.val || 'std';
630
+ const loc = isArray ? clause[$location] : clause.location;
631
+ error( `extend-${ prop.toLowerCase() }`, [ loc, ext ], { '#': variant, art } );
632
+ return;
633
+ }
634
+ if (isArray) {
635
+ if (!query[prop])
636
+ query[prop] = [];
637
+ query[prop].push( ...clause );
638
+ }
639
+ else {
640
+ if (query[prop]) {
641
+ error( 'ext-unexpected-sql-clause', [ clause.location, ext ], { art, keyword: prop } );
642
+ return;
643
+ }
644
+ query[prop] = clause;
645
+ }
646
+ }
647
+
606
648
  function changeParentLinks( art, queryOrMain ) {
607
649
  // TODO: we might also change the implicit name (if name.id is a number,
608
650
  // adding the previous column lenght - 1) for better error messages
@@ -838,18 +880,18 @@ function extend( model ) {
838
880
  }
839
881
 
840
882
  function moveDictExtensions( art, extensionsMap, artProp, extProp = artProp ) {
841
- // TODO: setExpandStatusAnnotate
842
- const extensions = extensionsMap[extProp];
883
+ // TODO: setExpandStatus
884
+ const extensions = extensionsMap[extProp || 'elements'];
843
885
  if (!extensions)
844
886
  return;
845
887
 
846
888
  for (const ext of extensions) {
847
889
  let dictCheck = (art.kind !== 'annotate'); // no check in super annotate statement
848
- forEachGeneric( ext, extProp, ( elemExt, name ) => {
890
+ forEachGeneric( ext, extProp || (ext.enum ? 'enum' : 'elements'), ( elemExt, name ) => {
849
891
  if (elemExt.kind !== 'annotate' && elemExt.kind !== 'extend') // TODO: specified elems
850
892
  return; // definitions inside extend, already handled
851
893
  dictCheck = dictCheck && checkRemainingMemberExtensions( art, elemExt, artProp, name );
852
- const elem = art[artProp]?.[name] || annotateFor( art, extProp, name );
894
+ const elem = art[artProp]?.[name] || annotateFor( art, extProp || 'elements', name );
853
895
  setLink( elemExt.name, '_artifact', (elem.kind !== 'annotate' ? elem : null ) );
854
896
  // TODO: why null for annotate?
855
897
  ensureArtifactNotProcessed( elem );
@@ -1055,11 +1097,23 @@ function extend( model ) {
1055
1097
 
1056
1098
  function checkRemainingMainExtensions( art, ext ) {
1057
1099
  const refCtx = extensionRefContext( ext );
1058
- if (!resolvePath( ext.name, refCtx, ext )) // error for extend, info for annotate
1059
- return;
1060
-
1061
- if (art?.builtin) { // TODO: do via accept
1062
- info( 'anno-builtin', [ ext.name.location, ext ], {} ); // TODO: better location?
1100
+ if (resolvePath( ext.name, refCtx, ext ) && art?.builtin) {
1101
+ if (ext.kind === 'extend') {
1102
+ // extending built-ins with elements/enums already gives an error
1103
+ warning( 'ext-unexpected-builtin', [ ext.name.location, ext ], {}, // error v8?
1104
+ 'Built-in types should not be extended' ); // keep the text general
1105
+ const typeProp = typeParameters.list.find( p => ext[p] );
1106
+ if (typeProp) {
1107
+ const location = ext.$typeArgs?.[$location] || ext[typeProp].location;
1108
+ message( 'ext-unexpected-type-property', [ location, ext ], {}, // error v7
1109
+ 'Built-in types can\'t be extended with type properties' );
1110
+ // see also 'ext-invalid-type-property'
1111
+ }
1112
+ }
1113
+ else {
1114
+ info( 'anno-builtin', [ ext.name.location, ext ], {} ); // TODO: better location?
1115
+ }
1116
+ // TODO: remove built-ins as CC candidates via accept property of ./shared.js
1063
1117
  }
1064
1118
  }
1065
1119
 
@@ -1142,10 +1196,33 @@ function extend( model ) {
1142
1196
  // TODO: complain if $inferred
1143
1197
  // checkExtensionsKind( extensions, art );
1144
1198
  extendMembers( extensions, art );
1199
+ reportIncludeCollisions( art );
1145
1200
  // TODO: complain about element extensions inside projection
1146
1201
  return true;
1147
1202
  }
1148
1203
 
1204
+ function reportIncludeCollisions( art ) {
1205
+ const grouped = Object.create( null );
1206
+ for (const {
1207
+ prop, name, existing, elem,
1208
+ } of includeCollisions) {
1209
+ const key = `${ prop }:${ name }`;
1210
+ if (!grouped[key])
1211
+ grouped[key] = { prop, name, collisions: new Set( [ existing ] ) };
1212
+ grouped[key].collisions.add( elem );
1213
+ }
1214
+ for (const key in grouped) {
1215
+ const { prop, name, collisions } = grouped[key];
1216
+ const member = art[prop]?.[name];
1217
+ if (member?.$inferred === 'include') {
1218
+ const arts = [ ...collisions ].map( m => m._origin._main );
1219
+ message( 'ext-duplicate-include', [ art.name.location, member ],
1220
+ { '#': prop, name, sorted_arts: arts } );
1221
+ }
1222
+ }
1223
+ includeCollisions.length = 0;
1224
+ }
1225
+
1149
1226
  function extendMembers( extensions, art ) {
1150
1227
  // TODO: do the whole extension stuff lazily if the elements are requested
1151
1228
  const elemExtensions = [];
@@ -1154,7 +1231,11 @@ function extend( model ) {
1154
1231
  // TODO: use same sequence as in chooseAssignment() - better: use common code with that fn
1155
1232
  // console.log('EM:',art.name,extensions,art._extensions)
1156
1233
  for (const ext of extensions) { // those in extMap.includes
1157
- if (art.$inferred) {
1234
+ if (art.$inferred === 'include') {
1235
+ error( 'ref-expected-direct-structure', [ ext.name.location, ext ],
1236
+ { '#': 'elements', art } );
1237
+ }
1238
+ else if (art.$inferred) {
1158
1239
  error( 'extend-for-generated', [ ext.name.location, ext ], { art, keyword: 'extend' },
1159
1240
  'You can\'t use $(KEYWORD) on the generated $(ART)' ); // or with inferred elements
1160
1241
  }
@@ -1298,6 +1379,8 @@ function extend( model ) {
1298
1379
 
1299
1380
  if (obj !== parent && obj.elements && parent.enum) { // applying the extension
1300
1381
  initElementsAsEnum();
1382
+ if (parent.enum[$inferred])
1383
+ setExpandStatus( parent, 'extend' );
1301
1384
  }
1302
1385
  else {
1303
1386
  if (checkDefinitions( construct, parent, 'elements', obj.elements || false ))
@@ -1341,7 +1424,11 @@ function extend( model ) {
1341
1424
  e.kind = 'enum';
1342
1425
  hasElement = true; // warning with CDL input or `name: {}` in CSN input
1343
1426
  }
1344
- if (hasElement) {
1427
+ if (!parent.type || parent.type.$inferred || (parent._main || parent).query) {
1428
+ error( 'extend-type', [ obj.elements[$location], construct ], {},
1429
+ 'Only structures or enum types can be extended with elements/enums' );
1430
+ }
1431
+ else if (hasElement) {
1345
1432
  warning( 'ext-expecting-enum', [ obj.elements[$location], construct ],
1346
1433
  { code: 'extend … with enum' }, 'Use $(CODE) when extending enums' );
1347
1434
  }
@@ -1404,13 +1491,18 @@ function extend( model ) {
1404
1491
 
1405
1492
  if (isQueryExtension && elem.kind === 'element') {
1406
1493
  error( 'extend-query', [ elem.location, construct ], // TODO: searchName ?
1407
- { code: 'extend projection' },
1494
+ { code: 'extend … with columns' },
1408
1495
  'Use $(CODE) to add select items to the query entity' );
1409
1496
  return;
1410
1497
  }
1411
1498
 
1412
1499
  const existing = parent[prop]?.[name];
1413
1500
  const add = construct !== parent && (!existing || elem.$inferred !== 'include');
1501
+ if (!add && existing?.$inferred === 'include' && elem.$inferred === 'include') {
1502
+ includeCollisions.push( {
1503
+ prop, name, existing, elem,
1504
+ } );
1505
+ }
1414
1506
  // don't dump with `entity T {}; extend T with { extend e {}; e {}; e {} };`:
1415
1507
  const { $duplicates } = elem;
1416
1508
  if ($duplicates === true && add)
@@ -1424,6 +1516,9 @@ function extend( model ) {
1424
1516
 
1425
1517
  // for a correct home path, setMemberParent needed to be called
1426
1518
 
1519
+ if (parent[prop]?.[$inferred])
1520
+ setExpandStatus( parent, 'extend' );
1521
+
1427
1522
  if (!elem.value || elem.kind !== 'element')
1428
1523
  return;
1429
1524
  // remark: potential enum elements have already been turned into enums
@@ -1502,10 +1597,14 @@ function extend( model ) {
1502
1597
  error( 'ref-expected-direct-structure', [ location, construct ],
1503
1598
  { '#': variant, art: parent } );
1504
1599
  }
1505
- else {
1600
+ else if (prop !== 'enum' || !parent.enum ||
1601
+ !parent.type || parent.type.$inferred || (parent._main || parent).query) {
1506
1602
  error( 'extend-type', [ location, construct ], {},
1507
1603
  'Only structures or enum types can be extended with elements/enums' );
1508
1604
  }
1605
+ else {
1606
+ return true;
1607
+ }
1509
1608
  }
1510
1609
  else if (prop === 'elements') {
1511
1610
  error( 'def-unexpected-elements', [ location, construct ], {},
@@ -1585,6 +1684,7 @@ function extend( model ) {
1585
1684
  // TODO two kind of messages:
1586
1685
  // Error 'More than one include defines element "A"' (at include ref)
1587
1686
  // Warning 'Overwrites definition from include "I" (at elem def)
1687
+ const propagateKeys = art.kind !== 'type' || !model.options.v7KeyPropagation;
1588
1688
  const parent = ext === art && art;
1589
1689
  const members = ext[prop];
1590
1690
  // if (members)console.log( 'EXT:', prop, art.kind, art.name.id, ...Object.keys(members));
@@ -1624,7 +1724,7 @@ function extend( model ) {
1624
1724
  elem.$inferred = 'include';
1625
1725
  if (origin.masked) // TODO(v6): remove 'masked'
1626
1726
  elem.masked = Object.assign( { $inferred: 'include' }, origin.masked );
1627
- if (origin.key)
1727
+ if (origin.key && propagateKeys)
1628
1728
  elem.key = Object.assign( { $inferred: 'include' }, origin.key );
1629
1729
  if (origin.value && origin.$syntax === 'calc') {
1630
1730
  // TODO: If paths become invalid in the new artifact, should we mark
@@ -4,8 +4,8 @@
4
4
 
5
5
  /* eslint-disable no-nested-ternary */
6
6
 
7
- const { isDeprecatedEnabled } = require('../base/model');
8
- const { dictAdd } = require('../base/dictionaries');
7
+ const { isDeprecatedEnabled } = require('../base/specialOptions');
8
+ const { dictAdd } = require('./dictionaries');
9
9
  const {
10
10
  setLink,
11
11
  setArtifactLink,
@@ -32,8 +32,8 @@ const check = require('./checks');
32
32
 
33
33
  const { Location, emptyWeakLocation } = require('../base/location');
34
34
  const { createMessageFunctions, deduplicateMessages } = require('../base/messages');
35
- const { checkRemovedDeprecatedFlags } = require('../base/model');
36
- const { promiseAllDoNotRejectImmediately } = require('../base/node-helpers');
35
+ const { checkRemovedDeprecatedFlags } = require('../base/specialOptions');
36
+ const { promiseAllDoNotRejectImmediately } = require('../utils/node-helpers');
37
37
  const { cdsFs, fileExtension } = require('../utils/file');
38
38
 
39
39
  const fs = require('fs');
@@ -504,6 +504,8 @@ function compileDoXSync( model ) {
504
504
  // TODO: think about making this work
505
505
 
506
506
  resolve( model );
507
+ if (options.$cdsReplSupport)
508
+ return model;
507
509
  tweakAssocs( model );
508
510
  assertConsistency( model );
509
511
  check( model );
@@ -554,9 +556,15 @@ async function compileDoX( model ) {
554
556
 
555
557
  model.definitions = model.$functions.shuffleDict( model.definitions );
556
558
  // Shuffling extensions is more difficult due to intra-file extensions of same artifact
559
+ // TODO: shuffle _before_ populate()
557
560
  // TODO: think about making this work
558
561
 
559
- for (const phase of [ resolve, tweakAssocs, assertConsistency, check ]) {
562
+ resolve( model );
563
+ if (options.$cdsReplSupport)
564
+ return model;
565
+ await checkAsyncAbortFlag( options.abortSignal );
566
+
567
+ for (const phase of [ tweakAssocs, assertConsistency, check ]) {
560
568
  phase( model );
561
569
  // eslint-disable-next-line no-await-in-loop
562
570
  await checkAsyncAbortFlag( options.abortSignal );
@@ -4,7 +4,7 @@
4
4
 
5
5
  'use strict';
6
6
 
7
- const { isBetaEnabled } = require('../base/model');
7
+ const { isBetaEnabled } = require('../base/specialOptions');
8
8
  const {
9
9
  setLink,
10
10
  annotationVal,
@@ -7,7 +7,7 @@
7
7
  //
8
8
  // This files includes an iterator over "semantic tokens" in an XSN model.
9
9
  // "Semantic tokens" are identifiers, but also the "return" parameter.
10
- // See <../../internalDoc/lsp/IdentifierCrawling.md> for details.
10
+ // See <../../internalDoc/lsp/SemanticTokenCrawling.md> for details.
11
11
 
12
12
  const { CompilerAssertion } = require('../base/error');
13
13
  const $inferred = Symbol.for( 'cds.$inferred' );
@@ -190,7 +190,7 @@ function* artifactTokens( art ) {
190
190
  * @returns {Generator<LspSemanticTokenEvent>}
191
191
  */
192
192
  function* extensionTokens( ext ) {
193
- if (ext.kind !== 'extend' && ext.kind !== 'annotate')
193
+ if (ext.kind !== 'extend' && ext.kind !== 'annotate' || !ext.name)
194
194
  return null;
195
195
 
196
196
  const wasApplied = ext.name._artifact && !ext.name._artifact.$inferred;
@@ -203,7 +203,7 @@ function* extensionTokens( ext ) {
203
203
 
204
204
  // We need to traverse all dictionaries that could themselves contain
205
205
  // extensions. Enum extensions or columns don't need to be traversed,
206
- // for example, because there can't be inner extensions.
206
+ // for example, because there can't be inner extensions. TODO: still?
207
207
  yield* dictOf( extensionTokens )( ext.params );
208
208
  yield* dictOf( extensionTokens )( ext.actions );
209
209
  yield* dictOf( extensionTokens )( ext.elements );
@@ -17,10 +17,10 @@
17
17
 
18
18
  'use strict';
19
19
 
20
- const { isDeprecatedEnabled } = require('../base/model');
20
+ const { isDeprecatedEnabled } = require('../base/specialOptions');
21
21
  const {
22
22
  dictAdd, dictAddArray, dictFirst, dictForEach,
23
- } = require('../base/dictionaries');
23
+ } = require('./dictionaries');
24
24
  const { weakLocation, weakRefLocation } = require('../base/location');
25
25
  const { CompilerAssertion } = require('../base/error');
26
26
 
@@ -36,7 +36,6 @@ const {
36
36
  dependsOn,
37
37
  proxyCopyMembers,
38
38
  setExpandStatus,
39
- setExpandStatusAnnotate,
40
39
  dependsOnSilent,
41
40
  columnRefStartsWithSelf,
42
41
  forEachDefinition,
@@ -218,8 +217,8 @@ function populate( model ) {
218
217
  populateGeneratedEntity( a );
219
218
  if (a.includes)
220
219
  a.includes.forEach( i => resolveInclude( i, a ) );
221
- extendArtifactAdd( a );
222
220
  art = populateArtifact( a, art ) || a;
221
+ extendArtifactAdd( a );
223
222
  setLink( a, '_effectiveType', art );
224
223
  a.$effectiveSeqNo = ++effectiveSeqNo;
225
224
  // console.log('PE:',require('../model/revealInternalProperties').ref(a))
@@ -614,7 +613,7 @@ function populate( model ) {
614
613
  }
615
614
 
616
615
  if (wasAnnotated)
617
- setExpandStatusAnnotate( art, 'annotate' );
616
+ setExpandStatus( art, 'annotate' );
618
617
 
619
618
  // TODO: We don't check enum$, yet! We first need to fix expansion for
620
619
  // `cast(elem as EnumType)` (see #9421)
@@ -668,7 +667,7 @@ function populate( model ) {
668
667
  }
669
668
  }
670
669
  if (wasAnnotated)
671
- setExpandStatusAnnotate( art, 'annotate' );
670
+ setExpandStatus( art, 'annotate' );
672
671
 
673
672
  for (const id in art.foreignKeys$) {
674
673
  const specifiedElement = art.foreignKeys$[id];
@@ -767,7 +766,7 @@ function populate( model ) {
767
766
  const { targetMax } = path[path.length - 1].cardinality ||
768
767
  getInheritedProp( assoc, 'cardinality' ) || {};
769
768
  if (targetMax && (targetMax.val === '*' || targetMax.val > 1)) {
770
- elem.items = { location: elem.expand[$location] };
769
+ elem.items = { location: elem.expand[$location], $inferred: 'query' };
771
770
  setLink( elem.items, '_outer', elem );
772
771
  }
773
772
  return initFromColumns( elem, elem.expand );
@@ -38,8 +38,8 @@
38
38
 
39
39
  'use strict';
40
40
 
41
- const { isDeprecatedEnabled } = require('../base/model');
42
- const { dictAdd } = require('../base/dictionaries');
41
+ const { isDeprecatedEnabled } = require('../base/specialOptions');
42
+ const { dictAdd, pushToDict } = require('./dictionaries');
43
43
  const { weakLocation } = require('../base/location');
44
44
  const { combinedLocation } = require('../base/location');
45
45
  const { typeParameters } = require('./builtins');
@@ -285,9 +285,9 @@ function resolve( model ) {
285
285
  // we don't propagate keys to type projections, see #13575
286
286
  return;
287
287
  }
288
- // Second argument true ensure that `key` is only propagated along simple
288
+ // Second argument controls whether `key` is only propagated along simple
289
289
  // view, i.e. ref or subquery in FROM, not UNION or JOIN.
290
- traverseQueryPost( view.query, true, ( query ) => {
290
+ traverseQueryPost( view.query, !options.v7KeyPropagation, ( query ) => {
291
291
  if (!withExplicitKeys( query ) && inheritKeyProp( query ) &&
292
292
  withKeyPropagation( query )) // now the part with messages
293
293
  inheritKeyProp( query, true );
@@ -331,47 +331,47 @@ function resolve( model ) {
331
331
  return head?.kind === '$tableAlias' && item._artifact?.key;
332
332
  }
333
333
 
334
- function primarySourceNavigation( aliases ) {
335
- for (const name in aliases)
336
- return aliases[name].elements;
337
- return undefined;
338
- }
339
-
340
334
  function withKeyPropagation( query ) {
341
- const { from } = query;
335
+ const { from, $tableAliases } = query;
342
336
  if (!from) // parse error SELECT FROM <EOF>
343
337
  return false;
344
-
345
338
  let propagateKeys = true; // used instead early RETURN to get more messages
346
- const toMany = withAssociation( from, targetMaxNotOne, true );
347
- if (toMany) {
348
- propagateKeys = false;
349
- info( 'query-from-many', [ toMany.location, query ], { art: toMany }, {
350
- std: 'Key properties are not propagated because a to-many association $(ART) is selected',
351
- // eslint-disable-next-line @stylistic/max-len
352
- element: 'Key properties are not propagated because a to-many association $(MEMBER) of $(ART) is selected',
353
- } );
354
- }
339
+
355
340
  // Check that all keys from the source are projected:
356
- const notProjected = []; // we actually push to the array
357
- const navElems = primarySourceNavigation( query.$tableAliases );
341
+ const notProjected = Object.create( null );
342
+ const navElems = query._combined;
358
343
  for (const name in navElems) {
359
344
  const nav = navElems[name];
360
- if (nav.$duplicates)
361
- continue;
362
- const { key } = nav._origin;
363
- if (key?.val && !nav._projections?.length)
364
- notProjected.push( nav.name.id );
345
+ if (Array.isArray( nav ))
346
+ nav.forEach( pushNonProjected );
347
+ else
348
+ pushNonProjected( nav );
365
349
  }
366
- if (notProjected.length) {
350
+ for (const [ alias, names ] of Object.entries( notProjected )) {
367
351
  propagateKeys = false;
368
- info( 'query-missing-keys', [ from.location, query ], { names: notProjected },
352
+ // TODO: mention alias
353
+ const location = $tableAliases[alias].name?.location || from.location;
354
+ info( 'query-missing-keys', [ location, query ], { names },
369
355
  {
370
356
  std: 'Keys $(NAMES) have not been projected - key properties are not propagated',
371
357
  one: 'Key $(NAMES) has not been projected - key properties are not propagated',
372
358
  } );
373
359
  }
374
- // Check that there is no to-many assoc used in select item:
360
+ if (options.v7KeyPropagation)
361
+ return propagateKeys;
362
+
363
+ // Check that there is no to-many assoc in the FROM reference or in the select
364
+ // item reference (references in expressions are not checked,
365
+ // withAssociation() will see no _artifact links anyway)
366
+ const toMany = withAssociation( from, targetMaxNotOne, true );
367
+ if (toMany) {
368
+ propagateKeys = false;
369
+ info( 'query-from-many', [ toMany.location, query ], { art: toMany }, {
370
+ std: 'Key properties are not propagated because a to-many association $(ART) is selected',
371
+ // eslint-disable-next-line @stylistic/max-len
372
+ element: 'Key properties are not propagated because a to-many association $(MEMBER) of $(ART) is selected',
373
+ } );
374
+ }
375
375
  for (const name in query.elements) {
376
376
  const elem = query.elements[name];
377
377
 
@@ -383,6 +383,14 @@ function resolve( model ) {
383
383
  }
384
384
  return propagateKeys;
385
385
 
386
+ function pushNonProjected( nav ) {
387
+ if (nav.$duplicates)
388
+ return;
389
+ const { key } = nav._origin;
390
+ if (key?.val && !nav._projections?.length)
391
+ pushToDict( notProjected, nav._parent.name.id, nav.name.id );
392
+ }
393
+
386
394
  function selectTest( expr, user ) {
387
395
  const art = withAssociation( expr, targetMaxNotOne );
388
396
  if (art) {
@@ -1004,11 +1012,16 @@ function resolve( model ) {
1004
1012
  forEachGeneric( query, 'elements', resolveRefs );
1005
1013
  if (query.from)
1006
1014
  resolveJoinOn( query.from );
1007
- if (query.where)
1008
- resolveExpr( query.where, 'where', query );
1015
+ if (query.where) {
1016
+ const user = query.where._block ? query.where : query;
1017
+ resolveExpr( query.where, 'where', user );
1018
+ }
1009
1019
  if (query.groupBy)
1010
1020
  resolveBy( query.groupBy, 'groupBy', 'groupBy' );
1011
- resolveExpr( query.having, 'having', query );
1021
+ if (query.having) {
1022
+ const user = query.having._block ? query.having : query;
1023
+ resolveExpr( query.having, 'having', user );
1024
+ }
1012
1025
  if (query.$orderBy) // ORDER BY from UNION:
1013
1026
  // TODO clarify: can I access the tab alias of outer queries? If not:
1014
1027
  // 4th arg query._main instead query._parent.
@@ -1051,8 +1064,10 @@ function resolve( model ) {
1051
1064
  */
1052
1065
  function resolveBy( array, refMode, exprMode ) {
1053
1066
  for (const value of array ) {
1054
- if (value)
1055
- resolveExpr( value, (value.path ? refMode : exprMode), query );
1067
+ if (value) {
1068
+ const user = value._block ? value : query;
1069
+ resolveExpr( value, (value.path ? refMode : exprMode), user );
1070
+ }
1056
1071
  }
1057
1072
  }
1058
1073
 
@@ -1566,6 +1581,8 @@ function resolve( model ) {
1566
1581
  // No traversal into query, art.$queries set in define.js
1567
1582
  // No subqueries for type projections, nor in annotation expressions.
1568
1583
  const { query } = expr;
1584
+ if (!query.from && !query.args) // parser error
1585
+ return type; // ...see also TODO in initQueryExpression()
1569
1586
  if (query._main?.kind === 'type' ||
1570
1587
  (!query.kind && !query._leadingQuery)) { // UNION has _leadingQuery
1571
1588
  error( 'expr-no-subquery', [ expr.location, user ], {