@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.
- package/CHANGELOG.md +49 -0
- package/bin/cdsc.js +11 -4
- package/lib/api/options.js +1 -1
- package/lib/base/message-registry.js +36 -7
- package/lib/base/messages.js +11 -4
- package/lib/base/model.js +0 -1
- package/lib/checks/assocOutsideService.js +17 -30
- package/lib/checks/checkForTypes.js +0 -18
- package/lib/checks/checkPathsInStoredCalcElement.js +2 -1
- package/lib/checks/enricher.js +15 -3
- package/lib/checks/onConditions.js +2 -2
- package/lib/checks/queryNoDbArtifacts.js +16 -15
- package/lib/checks/types.js +1 -1
- package/lib/checks/utils.js +30 -6
- package/lib/checks/validator.js +36 -37
- package/lib/compiler/assert-consistency.js +1 -1
- package/lib/compiler/checks.js +47 -18
- package/lib/compiler/extend.js +1 -1
- package/lib/compiler/index.js +88 -6
- package/lib/compiler/populate.js +1 -1
- package/lib/compiler/resolve.js +7 -7
- package/lib/compiler/tweak-assocs.js +48 -25
- package/lib/edm/annotations/edmJson.js +19 -19
- package/lib/gen/BaseParser.js +1 -1
- package/lib/gen/CdlGrammar.checksum +1 -1
- package/lib/gen/CdlParser.js +384 -383
- package/lib/gen/Dictionary.json +0 -2
- package/lib/json/to-csn.js +3 -2
- package/lib/model/csnRefs.js +9 -4
- package/lib/model/csnUtils.js +67 -2
- package/lib/optionProcessor.js +2 -3
- package/lib/parsers/AstBuildingParser.js +12 -11
- package/lib/render/toCdl.js +10 -4
- package/lib/render/utils/common.js +4 -2
- package/lib/transform/db/assertUnique.js +2 -1
- package/lib/transform/db/associations.js +37 -1
- package/lib/transform/db/assocsToQueries/transformExists.js +21 -32
- package/lib/transform/db/assocsToQueries/utils.js +1 -1
- package/lib/transform/db/cdsPersistence.js +1 -1
- package/lib/transform/db/expansion.js +37 -36
- package/lib/transform/draft/db.js +20 -20
- package/lib/transform/draft/odata.js +38 -40
- package/lib/transform/effective/associations.js +1 -1
- package/lib/transform/effective/flattening.js +40 -47
- package/lib/transform/effective/main.js +6 -4
- package/lib/transform/forOdata.js +135 -115
- package/lib/transform/forRelationalDB.js +151 -142
- package/lib/transform/localized.js +116 -109
- package/lib/transform/odata/adaptAnnotationRefs.js +21 -16
- package/lib/transform/odata/createForeignKeys.js +73 -70
- package/lib/transform/odata/flattening.js +216 -200
- package/lib/transform/odata/foreignKeyRefsInXprAnnos.js +47 -45
- package/lib/transform/odata/toFinalBaseType.js +40 -39
- package/lib/transform/odata/typesExposure.js +151 -133
- package/lib/transform/odata/utils.js +7 -6
- package/lib/transform/parseExpr.js +165 -162
- package/lib/transform/transformUtils.js +184 -551
- package/lib/transform/translateAssocsToJoins.js +510 -571
- package/lib/transform/tupleExpansion.js +495 -0
- package/lib/transform/universalCsn/universalCsnEnricher.js +1 -0
- package/package.json +1 -1
- package/lib/base/cleanSymbols.js +0 -17
- package/lib/checks/nonexpandableStructured.js +0 -39
package/lib/checks/validator.js
CHANGED
|
@@ -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
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
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
|
-
|
|
109
|
-
|
|
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
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
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
|
|
125
|
+
const forOdataCsnValidators = [ checkCdsMap ];
|
|
128
126
|
|
|
129
127
|
const forOdataQueryValidators = [];
|
|
130
128
|
|
|
131
129
|
const commonMemberValidators
|
|
132
|
-
= [ validateOnCondition, validateForeignKeys,
|
|
133
|
-
|
|
134
|
-
|
|
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: [ '*' ] },
|
package/lib/compiler/checks.js
CHANGED
|
@@ -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',
|
|
122
|
+
{ '#': 'virtual', keyword: 'key' } );
|
|
122
123
|
}
|
|
123
|
-
else if (
|
|
124
|
+
else if (typeName === 'cds.Map') {
|
|
124
125
|
error( 'def-unexpected-key', [ elem.type?.location || elem.location, elem ],
|
|
125
|
-
{ '#': 'invalidType',
|
|
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,
|
|
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,
|
|
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,
|
|
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 ],
|
|
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 {
|
|
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,
|
|
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,
|
|
920
|
+
checkExpressionIsNotAssocOrSelf( arg, user, rejectAssocTail );
|
|
906
921
|
}
|
|
907
922
|
}
|
|
908
923
|
}
|
|
909
924
|
|
|
910
|
-
|
|
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 (
|
|
920
|
-
|
|
921
|
-
|
|
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 ) );
|
package/lib/compiler/extend.js
CHANGED
|
@@ -75,7 +75,7 @@ function extend( model ) {
|
|
|
75
75
|
} );
|
|
76
76
|
|
|
77
77
|
const includesNonShadowedFirst
|
|
78
|
-
|
|
78
|
+
= isDeprecatedEnabled( model.options, '_includesNonShadowedFirst' );
|
|
79
79
|
|
|
80
80
|
sortModelSources();
|
|
81
81
|
const extensionsDict = Object.create( null ); // TODO TMP
|
package/lib/compiler/index.js
CHANGED
|
@@ -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
|
|
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
|
|
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 =
|
|
461
|
-
if (options.messages) // does not help with exception in
|
|
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
|
|
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,
|
package/lib/compiler/populate.js
CHANGED
|
@@ -92,7 +92,7 @@ function populate( model ) {
|
|
|
92
92
|
let newAutoExposed = [];
|
|
93
93
|
|
|
94
94
|
const ignoreSpecifiedElements
|
|
95
|
-
|
|
95
|
+
= isDeprecatedEnabled( model.options, 'ignoreSpecifiedQueryElements' );
|
|
96
96
|
|
|
97
97
|
forEachDefinition( model, traverseElementEnvironments );
|
|
98
98
|
while (newAutoExposed.length) {
|
package/lib/compiler/resolve.js
CHANGED
|
@@ -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' : '
|
|
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
|
-
|
|
911
|
-
|
|
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
|
|
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,
|
|
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
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
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
|
-
|
|
375
|
+
const culprit = !elem.target.$inferred && elem.target ||
|
|
377
376
|
elem.value?.path?.[elem.value.path.length - 1] ||
|
|
378
377
|
elem;
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
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
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
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
|
-
|
|
461
|
-
|
|
462
|
-
|
|
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' }(…))`
|
package/lib/gen/BaseParser.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
|
|
1
|
+
bffacbf5acb61179807cb657191c5114
|