@sap/cds-compiler 6.2.2 → 6.3.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 (63) hide show
  1. package/CHANGELOG.md +49 -0
  2. package/bin/cdsc.js +11 -4
  3. package/lib/api/options.js +1 -1
  4. package/lib/base/message-registry.js +36 -7
  5. package/lib/base/messages.js +11 -4
  6. package/lib/base/model.js +0 -1
  7. package/lib/checks/assocOutsideService.js +17 -30
  8. package/lib/checks/checkForTypes.js +0 -18
  9. package/lib/checks/checkPathsInStoredCalcElement.js +2 -1
  10. package/lib/checks/enricher.js +15 -3
  11. package/lib/checks/onConditions.js +2 -2
  12. package/lib/checks/queryNoDbArtifacts.js +16 -15
  13. package/lib/checks/types.js +1 -1
  14. package/lib/checks/utils.js +30 -6
  15. package/lib/checks/validator.js +36 -37
  16. package/lib/compiler/assert-consistency.js +1 -1
  17. package/lib/compiler/checks.js +47 -18
  18. package/lib/compiler/extend.js +1 -1
  19. package/lib/compiler/index.js +88 -6
  20. package/lib/compiler/populate.js +1 -1
  21. package/lib/compiler/resolve.js +7 -7
  22. package/lib/compiler/tweak-assocs.js +48 -25
  23. package/lib/edm/annotations/edmJson.js +19 -19
  24. package/lib/gen/BaseParser.js +1 -1
  25. package/lib/gen/CdlGrammar.checksum +1 -1
  26. package/lib/gen/CdlParser.js +384 -383
  27. package/lib/gen/Dictionary.json +0 -2
  28. package/lib/json/to-csn.js +3 -2
  29. package/lib/model/csnRefs.js +9 -4
  30. package/lib/model/csnUtils.js +67 -2
  31. package/lib/optionProcessor.js +2 -3
  32. package/lib/parsers/AstBuildingParser.js +12 -11
  33. package/lib/render/toCdl.js +10 -4
  34. package/lib/render/utils/common.js +4 -2
  35. package/lib/transform/db/assertUnique.js +2 -1
  36. package/lib/transform/db/associations.js +37 -1
  37. package/lib/transform/db/assocsToQueries/transformExists.js +21 -32
  38. package/lib/transform/db/assocsToQueries/utils.js +1 -1
  39. package/lib/transform/db/cdsPersistence.js +1 -1
  40. package/lib/transform/db/expansion.js +37 -36
  41. package/lib/transform/draft/db.js +20 -20
  42. package/lib/transform/draft/odata.js +38 -40
  43. package/lib/transform/effective/associations.js +1 -1
  44. package/lib/transform/effective/flattening.js +40 -47
  45. package/lib/transform/effective/main.js +6 -4
  46. package/lib/transform/forOdata.js +135 -115
  47. package/lib/transform/forRelationalDB.js +151 -142
  48. package/lib/transform/localized.js +116 -109
  49. package/lib/transform/odata/adaptAnnotationRefs.js +21 -16
  50. package/lib/transform/odata/createForeignKeys.js +73 -70
  51. package/lib/transform/odata/flattening.js +216 -200
  52. package/lib/transform/odata/foreignKeyRefsInXprAnnos.js +47 -45
  53. package/lib/transform/odata/toFinalBaseType.js +40 -39
  54. package/lib/transform/odata/typesExposure.js +151 -133
  55. package/lib/transform/odata/utils.js +7 -6
  56. package/lib/transform/parseExpr.js +165 -162
  57. package/lib/transform/transformUtils.js +184 -551
  58. package/lib/transform/translateAssocsToJoins.js +510 -571
  59. package/lib/transform/tupleExpansion.js +495 -0
  60. package/lib/transform/universalCsn/universalCsnEnricher.js +1 -0
  61. package/package.json +1 -1
  62. package/lib/base/cleanSymbols.js +0 -17
  63. package/lib/checks/nonexpandableStructured.js +0 -39
@@ -42,7 +42,6 @@ const checkForInvalidTarget = require('./invalidTarget');
42
42
  const { validateAssociationsInItems } = require('./arrayOfs');
43
43
  const checkQueryForNoDBArtifacts = require('./queryNoDbArtifacts');
44
44
  const checkExplicitlyNullableKeys = require('./nullableKeys');
45
- const nonexpandableStructuredInExpression = require('./nonexpandableStructured');
46
45
  const existsMustEndInAssoc = require('./existsMustEndInAssoc');
47
46
  const forbidAssocInExists = require('./existsExpressionsOnlyForeignKeys');
48
47
  const checkPathsInStoredCalcElement = require('./checkPathsInStoredCalcElement');
@@ -56,20 +55,20 @@ const featureFlags = require('./featureFlags');
56
55
  const { timetrace } = require('../utils/timetrace');
57
56
 
58
57
  const forRelationalDBMemberValidators
59
- = [
58
+ = [
60
59
  // For HANA CDS specifically, reject any default parameter values, as these are not supported.
61
- rejectParamDefaultsInHanaCds,
62
- checkTypeIsScalar,
63
- checkDecimalScale,
64
- checkExplicitlyNullableKeys,
65
- managedWithoutKeys,
66
- warnAboutDefaultOnAssociationForHanaCds,
67
- // sql.prepend/append
68
- checkSqlAnnotationOnElement,
69
- // no temporal annotations on calc elements
70
- rejectAnnotationsOnCalcElement,
71
- checkElementTypeDefinitionHasType,
72
- ];
60
+ rejectParamDefaultsInHanaCds,
61
+ checkTypeIsScalar,
62
+ checkDecimalScale,
63
+ checkExplicitlyNullableKeys,
64
+ managedWithoutKeys,
65
+ warnAboutDefaultOnAssociationForHanaCds,
66
+ // sql.prepend/append
67
+ checkSqlAnnotationOnElement,
68
+ // no temporal annotations on calc elements
69
+ rejectAnnotationsOnCalcElement,
70
+ checkElementTypeDefinitionHasType,
71
+ ];
73
72
 
74
73
  const forRelationalDBArtifactValidators = [
75
74
  checkPrimaryKey,
@@ -87,7 +86,6 @@ const forRelationalDBCsnValidators = [
87
86
  checkCdsMap,
88
87
  existsMustEndInAssoc,
89
88
  forbidAssocInExists,
90
- nonexpandableStructuredInExpression,
91
89
  navigationIntoMany,
92
90
  checkPathsInStoredCalcElement,
93
91
  featureFlags,
@@ -103,35 +101,35 @@ const forRelationalDBQueryValidators = [
103
101
  ];
104
102
 
105
103
  const forOdataMemberValidators
106
- = [
104
+ = [
107
105
  // OData allows only simple values, no expressions or functions
108
- validateDefaultValues,
109
- managedWithoutKeys,
110
- ];
106
+ validateDefaultValues,
107
+ managedWithoutKeys,
108
+ ];
111
109
 
112
110
  const forOdataArtifactValidators
113
- = [
111
+ = [
114
112
  // actions and functions are not of interest for the database
115
- checkActionOrFunction,
116
- // arrays are just CLOBs/LargeString for the database,
117
- // no inner for the array structure is of interest for the database
118
- // NOTE: moved to the renderer for a while
119
- // TODO: Re-enable this code and remove the duplicated code from the renderer.
120
- // Not possible at the moment, because running this at the beginning of
121
- // the renderer does not work because the enricher can't handle certain
122
- // OData specifics.
123
- // checkChainedArray,
124
- checkReadOnlyAndInsertOnly,
125
- ];
113
+ checkActionOrFunction,
114
+ // arrays are just CLOBs/LargeString for the database,
115
+ // no inner for the array structure is of interest for the database
116
+ // NOTE: moved to the renderer for a while
117
+ // TODO: Re-enable this code and remove the duplicated code from the renderer.
118
+ // Not possible at the moment, because running this at the beginning of
119
+ // the renderer does not work because the enricher can't handle certain
120
+ // OData specifics.
121
+ // checkChainedArray,
122
+ checkReadOnlyAndInsertOnly,
123
+ ];
126
124
 
127
- const forOdataCsnValidators = [ checkCdsMap, nonexpandableStructuredInExpression ];
125
+ const forOdataCsnValidators = [ checkCdsMap ];
128
126
 
129
127
  const forOdataQueryValidators = [];
130
128
 
131
129
  const commonMemberValidators
132
- = [ validateOnCondition, validateForeignKeys,
133
- validateAssociationsInItems, checkForInvalidTarget,
134
- checkVirtualElement, checkManagedAssoc ];
130
+ = [ validateOnCondition, validateForeignKeys,
131
+ validateAssociationsInItems, checkForInvalidTarget,
132
+ checkVirtualElement, checkManagedAssoc ];
135
133
 
136
134
  // TODO: checkManagedAssoc is a forEachMemberRecursively!
137
135
  const commonArtifactValidators = [
@@ -202,8 +200,6 @@ function getDBCsnValidators( options ) {
202
200
  validations.push(checkForParams.csnValidator);
203
201
  if (options.sqlDialect === 'h2' || options.sqlDialect === 'postgres')
204
202
  validations.push(checkForHanaTypes);
205
- if (options.transformation === 'effective' && options.effectiveServiceName)
206
- validations.push(assertNoAssocUsageOutsideOfService);
207
203
 
208
204
  return validations;
209
205
  }
@@ -232,6 +228,9 @@ function forRelationalDB( csn, that ) {
232
228
  },
233
229
  (artifact, artifactName) => {
234
230
  if (that.options.transformation === 'effective') {
231
+ if (that.options.effectiveServiceName)
232
+ assertNoAssocUsageOutsideOfService.bind(that)(artifact, artifactName);
233
+
235
234
  forEachMemberRecursively(artifact, checkAnnotationExpression.bind(that), [ 'definitions', artifactName ], false, {
236
235
  skipArtifact: a => a.returns || (a.params && !a.query),
237
236
  });
@@ -602,7 +602,7 @@ function assertConsistency( model, stage ) {
602
602
  cardinality: {
603
603
  kind: true,
604
604
  requires: [ 'location' ],
605
- optional: [ 'sourceMin', 'sourceMax', 'targetMin', 'targetMax' ],
605
+ optional: [ 'sourceMin', 'sourceMax', 'targetMin', 'targetMax', '$inferred' ],
606
606
  },
607
607
  sourceMin: { test: isNumberVal },
608
608
  sourceMax: { test: isNumberVal, also: [ '*' ] },
@@ -116,13 +116,21 @@ function check( model ) {
116
116
  return;
117
117
 
118
118
  const isVirtual = parentProps.virtual?.val || elem.virtual?.val;
119
+ const typeName = elem._effectiveType?.name?.id;
119
120
  if (isVirtual) {
120
121
  error( 'def-unexpected-key', [ (parentProps.key || elem.key).location, elem ],
121
- { '#': 'virtual', prop: 'key' } );
122
+ { '#': 'virtual', keyword: 'key' } );
122
123
  }
123
- else if (elem._effectiveType?.name?.id === 'cds.Map') {
124
+ else if (typeName === 'cds.Map') {
124
125
  error( 'def-unexpected-key', [ elem.type?.location || elem.location, elem ],
125
- { '#': 'invalidType', prop: 'key', type: 'cds.Map' } );
126
+ { '#': 'invalidType', keyword: 'key', type: typeName } );
127
+ }
128
+ else if (typeName === 'cds.LargeString' ||
129
+ typeName === 'cds.Vector' ||
130
+ typeName === 'cds.hana.CLOB' ||
131
+ typeName === 'cds.LargeBinary') {
132
+ warning( 'def-unsupported-key', [ elem.type?.location || elem.location, elem ],
133
+ { '#': 'type', keyword: 'key', type: typeName } );
126
134
  }
127
135
  }
128
136
 
@@ -704,12 +712,15 @@ function check( model ) {
704
712
  *
705
713
  * @param {any} xpr The expression to check
706
714
  * @param {XSN.Artifact} user User for semantic location
715
+ * @param {any} _parentExpr
707
716
  * @param {string} [context] where the expression is used, e.g. 'anno'
708
717
  */
709
- function checkGenericExpression( xpr, user, context ) {
718
+ function checkGenericExpression( xpr, user, _parentExpr, context ) {
710
719
  if (context !== 'anno')
711
720
  checkExpressionNotVirtual( xpr, user );
712
- checkExpressionAssociationUsage( xpr, user, false );
721
+ checkExpressionAssociationUsage( xpr, user, {
722
+ context, rejectManaged: context === 'anno', rejectUnmanaged: true,
723
+ } );
713
724
  if (xpr.op?.val === 'cast') {
714
725
  requireExplicitTypeInSqlCast( xpr, user );
715
726
  checkTypeCast( xpr, user );
@@ -730,7 +741,7 @@ function check( model ) {
730
741
 
731
742
  visitExpression( elem.on, elem, (xpr, user) => {
732
743
  checkExpressionNotVirtual( xpr, user );
733
- checkExpressionAssociationUsage( xpr, user, true );
744
+ checkExpressionAssociationUsage( xpr, user, null );
734
745
 
735
746
  if (xpr._artifact?._effectiveType?.name.id === 'cds.Map') {
736
747
  error( 'ref-unexpected-map', [ xpr.location, user ], { '#': 'onCond', type: 'cds.Map' } );
@@ -744,7 +755,9 @@ function check( model ) {
744
755
  }
745
756
 
746
757
  function checkSelectItemValue( elem ) {
747
- checkExpressionAssociationUsage( elem.value, elem, false );
758
+ checkExpressionAssociationUsage( elem.value, elem, {
759
+ context: 'query', rejectManaged: false, rejectUnmanaged: true,
760
+ } );
748
761
  checkVirtualSelectItemChangeForV6( elem );
749
762
  // To avoid duplicate messages, only run this check if the type wasn't inferred from
750
763
  // the cast, as otherwise we will check it twice (once here, once via element).
@@ -753,8 +766,8 @@ function check( model ) {
753
766
  checkTypeCast( elem.value, elem );
754
767
  checkTypeArguments( elem.value, elem );
755
768
  }
756
- visitSubExpression( elem.value, elem, (xpr) => {
757
- checkGenericExpression( xpr, elem );
769
+ visitSubExpression( elem.value, elem, (xpr, user, parentExpr) => {
770
+ checkGenericExpression( xpr, elem, parentExpr, 'query' );
758
771
  } );
759
772
  }
760
773
 
@@ -811,7 +824,8 @@ function check( model ) {
811
824
  // For inferred (e.g. included) calc elements, this error is already emitted at the origin.
812
825
  // And users can't change structured to non-structured elements.
813
826
  if (!elem.$inferred && xpr._artifact._effectiveType?.elements) {
814
- error( 'ref-unexpected-structured', [ sourceLoc, elem ], { '#': 'expr' } );
827
+ error( 'ref-unexpected-structured', [ sourceLoc, elem ],
828
+ { '#': 'struct-expr', elemref: xpr } );
815
829
  }
816
830
  else if (xpr._artifact.target !== undefined && (!lastStep.where || isStored)) {
817
831
  // Allow using an association _with filter_, but only for on-read calculated elements.
@@ -884,10 +898,11 @@ function check( model ) {
884
898
  *
885
899
  * @param {any} xpr The expression to check
886
900
  * @param {XSN.Artifact} user
887
- * @param {boolean} allowAssocTail
901
+ * @param {{context: string, rejectUnmanaged, rejectManaged}|null} [rejectAssocTail]
902
+ * Context where association tails are not allowed.
888
903
  * @returns {void}
889
904
  */
890
- function checkExpressionAssociationUsage( xpr, user, allowAssocTail ) {
905
+ function checkExpressionAssociationUsage( xpr, user, rejectAssocTail = null ) {
891
906
  if (!xpr.args)
892
907
  return;
893
908
 
@@ -902,12 +917,18 @@ function check( model ) {
902
917
  const op = getBinaryOp( xpr );
903
918
  for (const arg of args) {
904
919
  if (arg && !(op?.val !== '=' && isDollarSelfOrProjectionOperand( arg )))
905
- checkExpressionIsNotAssocOrSelf( arg, user, allowAssocTail );
920
+ checkExpressionIsNotAssocOrSelf( arg, user, rejectAssocTail );
906
921
  }
907
922
  }
908
923
  }
909
924
 
910
- function checkExpressionIsNotAssocOrSelf( arg, user, allowAssocTail ) {
925
+ /**
926
+ * @param arg
927
+ * @param {XSN.Artifact} user
928
+ * @param {{context: string, rejectUnmanaged, rejectManaged}|null} [rejectAssocTail]
929
+ * Context where association tails are not allowed.
930
+ */
931
+ function checkExpressionIsNotAssocOrSelf( arg, user, rejectAssocTail ) {
911
932
  // Arg must not be an association and not $self
912
933
  // Only if path is not approved exists path (that is non-query position)
913
934
  if (arg.path && arg.$expected !== undefined) { // not 'approved-exists'
@@ -916,9 +937,17 @@ function check( model ) {
916
937
  error( 'ref-unexpected-assoc', [ arg.location, user ], { '#': variant } );
917
938
  }
918
939
  }
919
- else if (!allowAssocTail && isAssociationOperand( arg )) {
920
- const variant = isComposition( model, arg._artifact ) ? 'expr-comp' : 'expr';
921
- error( 'ref-unexpected-assoc', [ arg.location, user ], { '#': variant } );
940
+ else if (rejectAssocTail && isAssociationOperand( arg )) {
941
+ if (rejectAssocTail.rejectManaged && rejectAssocTail.rejectUnmanaged ||
942
+ rejectAssocTail.rejectManaged && arg._artifact.keys ||
943
+ rejectAssocTail.rejectUnmanaged && arg._artifact.on) {
944
+ // only a few contexts have special message
945
+ const context = rejectAssocTail.context === 'query' && 'query-' ||
946
+ rejectAssocTail.context === 'anno' && 'anno-' ||
947
+ '';
948
+ const variant = isComposition( model, arg._artifact ) ? 'expr-comp' : 'expr';
949
+ error( 'ref-unexpected-assoc', [ arg.location, user ], { '#': `${ context }${ variant }` } );
950
+ }
922
951
  }
923
952
  }
924
953
 
@@ -1105,7 +1134,7 @@ function check( model ) {
1105
1134
  */
1106
1135
  function checkAnnotationExpressions( anno, art ) {
1107
1136
  if (anno.$tokenTexts) {
1108
- checkGenericExpression( anno, art, 'anno' );
1137
+ checkGenericExpression( anno, art, null, 'anno' );
1109
1138
  }
1110
1139
  else if (anno.literal === 'array') {
1111
1140
  anno.val.forEach( val => checkAnnotationExpressions( val, art ) );
@@ -75,7 +75,7 @@ function extend( model ) {
75
75
  } );
76
76
 
77
77
  const includesNonShadowedFirst
78
- = isDeprecatedEnabled( model.options, '_includesNonShadowedFirst' );
78
+ = isDeprecatedEnabled( model.options, '_includesNonShadowedFirst' );
79
79
 
80
80
  sortModelSources();
81
81
  const extensionsDict = Object.create( null ); // TODO TMP
@@ -183,7 +183,7 @@ function compileX( filenames, dir = '', options = {}, fileCache = Object.create(
183
183
  return all.then( () => {
184
184
  options.abortSignal?.throwIfAborted();
185
185
  moduleLayers.setLayers( input.sources );
186
- return compileDoX( model );
186
+ return compileDoX( model ); // also async
187
187
  } );
188
188
 
189
189
  // Read file `filename` and parse its content, return messages
@@ -303,7 +303,7 @@ function compileSyncX( filenames, dir = '', options = {}, fileCache = Object.cre
303
303
  }
304
304
 
305
305
  moduleLayers.setLayers( a.sources );
306
- return compileDoX( model );
306
+ return compileDoXSync( model );
307
307
 
308
308
  // Read file `filename` and parse its content, return messages
309
309
  function readAndParseSync( filename, cb ) {
@@ -423,7 +423,7 @@ function compileSourcesX( sourcesDict, options = {} ) {
423
423
  }
424
424
  moduleLayers.setLayers( sources );
425
425
 
426
- return compileDoX( model );
426
+ return compileDoXSync( model );
427
427
  }
428
428
 
429
429
  /**
@@ -457,8 +457,8 @@ function recompileX( csn, options ) {
457
457
 
458
458
  sources[file] = parseCsn.augment( csn, file, options, model.$messageFunctions );
459
459
  moduleLayers.setLayers( sources );
460
- const compiled = compileDoX( model ); // calls throwWithError()
461
- if (options.messages) // does not help with exception in compileDoX()
460
+ const compiled = compileDoXSync( model ); // calls throwWithError()
461
+ if (options.messages) // does not help with exception in compileDoXSync()
462
462
  deduplicateMessages( options.messages ); // TODO: do better
463
463
  return compiled;
464
464
  }
@@ -467,10 +467,12 @@ function recompileX( csn, options ) {
467
467
  * On the given model (AST like CSN) run the definer, resolver as well as semantic checks.
468
468
  * Creates an augmented CSN (XSN) and returns it.
469
469
  *
470
+ * This is the non-interruptible version of `compileDoX()` and can be used in non-`async` functions.
471
+ *
470
472
  * @param {object} model AST like CSN generated e.g. by `parsers.parseCdl()`
471
473
  * @returns {XSN.Model} Augmented CSN (XSN)
472
474
  */
473
- function compileDoX( model ) {
475
+ function compileDoXSync( model ) {
474
476
  const { options } = model;
475
477
  const { throwWithError } = model.$messageFunctions;
476
478
  if (!options.testMode)
@@ -512,6 +514,61 @@ function compileDoX( model ) {
512
514
  return propagator.propagate( model );
513
515
  }
514
516
 
517
+ /**
518
+ * On the given model (AST like CSN) run the definer, resolver as well as semantic checks.
519
+ * Creates an augmented CSN (XSN) and returns it.
520
+ *
521
+ * @param {object} model AST like CSN generated e.g. by `parsers.parseCdl()`
522
+ * @returns {XSN.Model} Augmented CSN (XSN)
523
+ */
524
+ async function compileDoX( model ) {
525
+ const { options } = model;
526
+ const { throwWithError } = model.$messageFunctions;
527
+ if (!options.testMode)
528
+ model.meta = {}; // provide initial central meta object
529
+
530
+ checkRemovedDeprecatedFlags( options, model.$messageFunctions );
531
+
532
+ if (options.parseOnly) {
533
+ throwWithError();
534
+ return model;
535
+ }
536
+ model.$functions = {};
537
+ fns( model ); // attach (mostly) paths functions
538
+ define( model );
539
+ await checkAsyncAbortFlag( options.abortSignal );
540
+
541
+ // do not run the resolver in parse-cdl mode or we get duplicate annotations, etc.
542
+ // TODO: do not use this function for parseCdl anyway…
543
+ if (options.parseCdl) {
544
+ finalizeParseCdl( model );
545
+ throwWithError();
546
+ return model;
547
+ }
548
+
549
+ for (const phase of [ extend, generate, kickStart, populate ]) {
550
+ phase( model );
551
+ // eslint-disable-next-line no-await-in-loop
552
+ await checkAsyncAbortFlag( options.abortSignal );
553
+ }
554
+
555
+ model.definitions = model.$functions.shuffleDict( model.definitions );
556
+ // Shuffling extensions is more difficult due to intra-file extensions of same artifact
557
+ // TODO: think about making this work
558
+
559
+ for (const phase of [ resolve, tweakAssocs, assertConsistency, check ]) {
560
+ phase( model );
561
+ // eslint-disable-next-line no-await-in-loop
562
+ await checkAsyncAbortFlag( options.abortSignal );
563
+ }
564
+
565
+ throwWithError();
566
+ if (options.lintMode)
567
+ return model;
568
+
569
+ return propagator.propagate( model );
570
+ }
571
+
515
572
  /**
516
573
  * Process an array of `filenames`. Returns an object with properties:
517
574
  * - `sources`: dictionary which has a filename as key (value is irrelevant)
@@ -595,6 +652,31 @@ function createSourcesDict( filenames, filenameMap, dir ) {
595
652
  return { sources, files, repeated };
596
653
  }
597
654
 
655
+ /**
656
+ * An `await`able function to fake a real asynchronous event.
657
+ *
658
+ * @returns {Promise<unknown>}
659
+ */
660
+ async function waitForNextEventLoopIteration() {
661
+ return new Promise( ( r ) => {
662
+ setTimeout( r, 0 );
663
+ });
664
+ }
665
+
666
+ /**
667
+ * An actual async function that uses `setTimeout()` to allow the Node event loop to
668
+ * start its next iteration, but only if the `abortSignal` is defined.
669
+ *
670
+ * @param {AbortSignal?} abortSignal
671
+ * @returns {Promise<void>}
672
+ */
673
+ async function checkAsyncAbortFlag( abortSignal ) {
674
+ if (!abortSignal)
675
+ return;
676
+ await waitForNextEventLoopIteration();
677
+ abortSignal.throwIfAborted();
678
+ }
679
+
598
680
  module.exports = {
599
681
  parseX,
600
682
  compileX,
@@ -92,7 +92,7 @@ function populate( model ) {
92
92
  let newAutoExposed = [];
93
93
 
94
94
  const ignoreSpecifiedElements
95
- = isDeprecatedEnabled( model.options, 'ignoreSpecifiedQueryElements' );
95
+ = isDeprecatedEnabled( model.options, 'ignoreSpecifiedQueryElements' );
96
96
 
97
97
  forEachDefinition( model, traverseElementEnvironments );
98
98
  while (newAutoExposed.length) {
@@ -430,10 +430,7 @@ function resolve( model ) {
430
430
  }
431
431
  else if (!allowedInMain || !isTopLevelElement) {
432
432
  warning( 'def-unsupported-key', [ art.key.location, art ],
433
- { '#': allowedInMain ? 'sub' : 'std', keyword: 'key' }, {
434
- std: '$(KEYWORD) is only supported for elements in an entity or an aspect',
435
- sub: '$(KEYWORD) is only supported for top-level elements',
436
- } );
433
+ { '#': allowedInMain ? 'sub' : 'kind', keyword: 'key' } );
437
434
  }
438
435
  }
439
436
 
@@ -905,10 +902,13 @@ function resolve( model ) {
905
902
  }
906
903
  else if (effectiveType( art )?.elements && !art.$inferred) {
907
904
  // For inferred (e.g. included) calc elements, this error is already emitted at the origin.
908
- if (art.type)
905
+ if (art.type) {
909
906
  error( 'type-unexpected-structure', [ art.type.location, art ], { '#': 'calc' } );
910
- else
911
- error( 'ref-unexpected-structured', [ art.value.location, art ], { '#': 'expr' } );
907
+ }
908
+ else {
909
+ error( 'ref-unexpected-structured', [ art.value.location, art ],
910
+ { '#': 'struct-expr', elemref: art.value } );
911
+ }
912
912
  }
913
913
  else if (effectiveType( art )?.items && !art.$inferred) {
914
914
  // For inferred (e.g. included) calc elements, this error is already emitted at the origin.
@@ -349,42 +349,46 @@ function tweakAssocs( model ) {
349
349
  setLink( fk, '_effectiveType', fk );
350
350
  fk.targetElement = copyExpr( orig.targetElement, location );
351
351
  if (elem._redirected)
352
- rewriteKey( elem, fk.targetElement );
352
+ rewriteKey( elem, fk );
353
353
  } );
354
354
  if (elem.foreignKeys) // Possibly no fk was set
355
355
  elem.foreignKeys[$inferred] = 'rewrite';
356
356
  }
357
357
 
358
- function rewriteKey( elem, targetElement ) {
358
+ function rewriteKey( elem, fk ) {
359
+ const { targetElement } = fk;
359
360
  let projectedKey = null;
360
361
  // rewrite along redirection chain
361
362
  for (const alias of elem._redirected) {
362
- if (alias.kind !== '$tableAlias')
363
- continue;
364
-
365
- projectedKey = firstProjectionForPath( targetElement.path, 0, alias, null );
366
- if (projectedKey.elem) {
367
- const item = targetElement.path[projectedKey.index];
368
- item.id = projectedKey.elem.name.id;
369
- if (projectedKey.index > 0)
370
- targetElement.path.splice(0, projectedKey.index);
371
- }
372
- else {
373
- setArtifactLink( targetElement.path[0], null );
374
- setArtifactLink( targetElement, null );
363
+ if (alias.kind === '$tableAlias') {
364
+ projectedKey = firstProjectionForPath( targetElement.path, 0, alias, null );
365
+ if (projectedKey.elem) {
366
+ const item = targetElement.path[projectedKey.index];
367
+ item.id = projectedKey.elem.name.id;
368
+ if (projectedKey.index > 0)
369
+ targetElement.path.splice(0, projectedKey.index);
370
+ }
371
+ else {
372
+ setArtifactLink( targetElement.path[0], null );
373
+ setArtifactLink( targetElement, null );
375
374
 
376
- const culprit = !elem.target.$inferred && elem.target ||
375
+ const culprit = !elem.target.$inferred && elem.target ||
377
376
  elem.value?.path?.[elem.value.path.length - 1] ||
378
377
  elem;
379
- // TODO: probably better to collect the non-projected foreign keys
380
- // and have one message for all
381
- error('rewrite-undefined-key', [ weakLocation( culprit.location ), elem ], {
382
- '#': 'std',
383
- id: targetElement.path.map(p => p.id).join('.'),
384
- target: alias._main,
385
- name: elem.name.id,
386
- });
387
- return null;
378
+ // TODO: probably better to collect the non-projected foreign keys
379
+ // and have one message for all
380
+ error('rewrite-undefined-key', [ weakLocation( culprit.location ), elem ], {
381
+ '#': 'std',
382
+ id: targetElement.path.map(p => p.id).join('.'),
383
+ target: alias._main,
384
+ name: elem.name.id,
385
+ });
386
+ return null;
387
+ }
388
+ }
389
+ else {
390
+ // e.g. redirection target is entity that includes original target
391
+ projectedKey = { elem: findTargetElement( alias, targetElement ) };
388
392
  }
389
393
  }
390
394
 
@@ -397,6 +401,24 @@ function tweakAssocs( model ) {
397
401
  return null;
398
402
  }
399
403
 
404
+ /**
405
+ * Find the target element in the given redirection target.
406
+ * Used to find the target element in entities that include the original
407
+ * target entity.
408
+ *
409
+ * @param redirected
410
+ * @param targetElement
411
+ * @returns {*|null}
412
+ */
413
+ function findTargetElement( redirected, targetElement ) {
414
+ for (const step of targetElement.path) {
415
+ redirected = redirected.elements?.[step.id];
416
+ if (!redirected)
417
+ return null;
418
+ }
419
+ return redirected;
420
+ }
421
+
400
422
  // TODO: there is no need to rewrite the on condition of non-leading queries,
401
423
  // i.e. we could just have on = {…}
402
424
  // TODO: re-check $self rewrite (with managed composition of aspects),
@@ -505,6 +527,7 @@ function tweakAssocs( model ) {
505
527
  if (lastStep.cardinality) {
506
528
  elem.cardinality ??= { ...assoc.cardinality };
507
529
  elem.cardinality.location = location;
530
+ elem.cardinality.$inferred = 'rewrite';
508
531
  for (const card of [ 'sourceMin', 'targetMin', 'targetMax' ]) {
509
532
  if (lastStep.cardinality[card])
510
533
  elem.cardinality[card] = copyExpr( lastStep.cardinality[card], location );
@@ -266,7 +266,7 @@ function xpr2edmJson( carrier, anno, location, options, messageFunctions ) {
266
266
  transformExpression(xpr, undefined, transform);
267
267
  delete parent[prop];
268
268
  parentparent[parentprop]
269
- = {
269
+ = {
270
270
  $And: [
271
271
  { $Le: [ xpr[1], xpr[0] ] },
272
272
  { $Le: [ xpr[0], xpr[2] ] },
@@ -370,12 +370,12 @@ function xpr2edmJson( carrier, anno, location, options, messageFunctions ) {
370
370
 
371
371
  // Map Edm primitive type funcs to $Type funcs
372
372
  let [ foundTypeProps, newArgs ]
373
- = parent.args
374
- ? parent.args.reduce((acc, arg) => {
375
- (arg.func === '$Collection' || arg.func === 'Collection' ? acc[0] : acc[1]).push(arg);
376
- return acc;
377
- }, [ [], [] ] )
378
- : [ [], [] ];
373
+ = parent.args
374
+ ? parent.args.reduce((acc, arg) => {
375
+ (arg.func === '$Collection' || arg.func === 'Collection' ? acc[0] : acc[1]).push(arg);
376
+ return acc;
377
+ }, [ [], [] ] )
378
+ : [ [], [] ];
379
379
 
380
380
  if (foundTypeProps.length === 1) {
381
381
  const type = foundTypeProps[0];
@@ -395,7 +395,7 @@ function xpr2edmJson( carrier, anno, location, options, messageFunctions ) {
395
395
 
396
396
  let typePropName = isDollarFunc ? '$Type' : 'Type';
397
397
  [ foundTypeProps, newArgs ]
398
- = parent.args
398
+ = parent.args
399
399
  ? parent.args.reduce((acc, arg) => {
400
400
  (EdmPrimitiveTypeMap[`Edm.${ arg.func }`] ? acc[0] : acc[1]).push(arg);
401
401
  return acc;
@@ -429,7 +429,7 @@ function xpr2edmJson( carrier, anno, location, options, messageFunctions ) {
429
429
  }
430
430
 
431
431
  [ foundTypeProps, newArgs ]
432
- = parent.args
432
+ = parent.args
433
433
  ? parent.args.reduce((acc, arg) => {
434
434
  ((arg.func === '$Type' || arg.func === 'Type') ? acc[0] : acc[1]).push(arg);
435
435
  return acc;
@@ -448,19 +448,19 @@ function xpr2edmJson( carrier, anno, location, options, messageFunctions ) {
448
448
  typePropName = typeProp.func;
449
449
 
450
450
  const [ collTypes, newTypeArgs ]
451
- = typeProp.args
452
- ? typeProp.args.reduce((acc, arg) => {
453
- ((arg.func === '$Collection' || arg.func === 'Collection') ? acc[0] : acc[1]).push(arg);
454
- return acc;
455
- }, [ [], [] ] )
456
- : [ [], [] ];
451
+ = typeProp.args
452
+ ? typeProp.args.reduce((acc, arg) => {
453
+ ((arg.func === '$Collection' || arg.func === 'Collection') ? acc[0] : acc[1]).push(arg);
454
+ return acc;
455
+ }, [ [], [] ] )
456
+ : [ [], [] ];
457
457
  typeProp.args = newTypeArgs;
458
458
 
459
459
  const [ scalarTypes, typeFacets ]
460
- = typeProp.args.reduce((acc, arg) => {
461
- ((/* arg.ref || */ arg.val) ? acc[0] : acc[1]).push(arg);
462
- return acc;
463
- }, [ [], [] ] );
460
+ = typeProp.args.reduce((acc, arg) => {
461
+ ((/* arg.ref || */ arg.val) ? acc[0] : acc[1]).push(arg);
462
+ return acc;
463
+ }, [ [], [] ] );
464
464
 
465
465
  let typeOpStr = collTypes.length
466
466
  ? `${ typePropName }(${ isDollarFunc ? '$Collection' : 'Collection' }(…))`
@@ -1,4 +1,4 @@
1
- // Base class for generated parser, for redepage v0.2.7
1
+ // Base class for generated parser, for redepage v0.3.0
2
2
 
3
3
  'use strict';
4
4
 
@@ -1 +1 @@
1
- 7489417512fa82b33fb6799686ce2917
1
+ bffacbf5acb61179807cb657191c5114