@sap/cds-compiler 6.4.2 → 6.5.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.
- package/CHANGELOG.md +87 -1159
- package/README.md +1 -10
- package/doc/IncompatibleChanges_v5.md +436 -0
- package/doc/IncompatibleChanges_v6.md +659 -0
- package/doc/Versioning.md +3 -7
- package/lib/api/main.js +1 -0
- package/lib/api/options.js +5 -0
- package/lib/api/validate.js +3 -0
- package/lib/base/message-registry.js +25 -2
- package/lib/base/messages.js +1 -1
- package/lib/base/model.js +3 -2
- package/lib/checks/actionsFunctions.js +6 -3
- package/lib/checks/existsExpressionsOnlyForeignKeys.js +16 -10
- package/lib/checks/existsInForbiddenPlaces.js +32 -0
- package/lib/checks/existsMustEndInAssoc.js +1 -1
- package/lib/checks/existsMustNotStartWithDollarSelf.js +31 -0
- package/lib/checks/validator.js +6 -2
- package/lib/compiler/assert-consistency.js +5 -7
- package/lib/compiler/builtins.js +5 -6
- package/lib/compiler/checks.js +4 -8
- package/lib/compiler/define.js +244 -459
- package/lib/compiler/extend.js +297 -11
- package/lib/compiler/finalize-parse-cdl.js +2 -10
- package/lib/compiler/generate.js +29 -1
- package/lib/compiler/populate.js +21 -63
- package/lib/compiler/propagator.js +1 -2
- package/lib/compiler/resolve.js +2 -12
- package/lib/compiler/shared.js +145 -114
- package/lib/compiler/tweak-assocs.js +14 -10
- package/lib/compiler/utils.js +97 -0
- package/lib/compiler/xpr-rewrite.js +113 -140
- package/lib/edm/annotations/edmJson.js +9 -6
- package/lib/edm/annotations/genericTranslation.js +8 -4
- package/lib/edm/csn2edm.js +3 -4
- package/lib/edm/edmInboundChecks.js +1 -2
- package/lib/edm/edmPreprocessor.js +3 -3
- package/lib/gen/CdlGrammar.checksum +1 -1
- package/lib/gen/CdlParser.js +4 -3
- package/lib/gen/Dictionary.json +16 -1
- package/lib/json/from-csn.js +4 -6
- package/lib/json/to-csn.js +3 -3
- package/lib/model/csnRefs.js +13 -4
- package/lib/model/enrichCsn.js +4 -2
- package/lib/optionProcessor.js +8 -4
- package/lib/parsers/AstBuildingParser.js +1 -1
- package/lib/render/utils/sql.js +3 -2
- package/lib/transform/db/applyTransformations.js +1 -1
- package/lib/transform/db/assertUnique.js +3 -3
- package/lib/transform/db/assocsToQueries/normalizeFrom.js +33 -0
- package/lib/transform/db/assocsToQueries/transformExists.js +17 -13
- package/lib/transform/db/assocsToQueries/utils.js +1 -6
- package/lib/transform/db/backlinks.js +4 -4
- package/lib/transform/db/cdsPersistence.js +4 -4
- package/lib/transform/db/constraints.js +4 -4
- package/lib/transform/db/expansion.js +5 -5
- package/lib/transform/db/flattening.js +4 -5
- package/lib/transform/db/rewriteCalculatedElements.js +3 -3
- package/lib/transform/db/temporal.js +11 -11
- package/lib/transform/draft/db.js +2 -0
- package/lib/transform/draft/odata.js +5 -7
- package/lib/transform/effective/flattening.js +1 -2
- package/lib/transform/forOdata.js +3 -3
- package/lib/transform/forRelationalDB.js +1 -1
- package/lib/transform/localized.js +13 -20
- package/lib/transform/odata/createForeignKeys.js +1 -2
- package/lib/transform/odata/flattening.js +1 -2
- package/lib/transform/odata/toFinalBaseType.js +52 -55
- package/lib/transform/transformUtils.js +3 -4
- package/package.json +3 -3
- package/doc/CHANGELOG_BETA.md +0 -464
- package/doc/CHANGELOG_DEPRECATED.md +0 -235
|
@@ -93,8 +93,6 @@ function xpr2edmJson( carrier, anno, location, options, messageFunctions ) {
|
|
|
93
93
|
//----------------------------------
|
|
94
94
|
// operators not supported as dynamic expression
|
|
95
95
|
'.': notADynExpr,
|
|
96
|
-
isNull: (p, o) => notADynExpr(p, o, null, null, null, null, 'is null'),
|
|
97
|
-
isNotNull: (p, o) => notADynExpr(p, o, null, null, null, null, 'is not null'),
|
|
98
96
|
exists: notADynExpr,
|
|
99
97
|
SELECT: notADynExpr,
|
|
100
98
|
SET: (p, o) => notADynExpr(p, o, null, null, null, null, 'UNION'),
|
|
@@ -302,6 +300,8 @@ function xpr2edmJson( carrier, anno, location, options, messageFunctions ) {
|
|
|
302
300
|
transform.$Mul = noOp;
|
|
303
301
|
transform['/'] = op('$DivBy');
|
|
304
302
|
transform.$DivBy = noOp;
|
|
303
|
+
transform.isNull = op('$Eq');
|
|
304
|
+
transform.isNotNull = op('$Ne');
|
|
305
305
|
// $Div, $Mod are functions
|
|
306
306
|
//----------------------------------
|
|
307
307
|
// LITERALS
|
|
@@ -885,10 +885,13 @@ function xpr2edmJson( carrier, anno, location, options, messageFunctions ) {
|
|
|
885
885
|
if (xpr.length === 2 && (xpr[0] === '+' || xpr[0] === '-' || xpr[0] === 'not' || xpr[0] === 'new'))
|
|
886
886
|
return { [xpr[0]]: xpr[1] };
|
|
887
887
|
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
return xpr;
|
|
891
|
-
|
|
888
|
+
// is null
|
|
889
|
+
if (xpr.length === 3 && xpr[1] === 'is' && xpr[2] === 'null')
|
|
890
|
+
return { isNull: [ xpr[0], { val: null } ] };
|
|
891
|
+
|
|
892
|
+
// is not null
|
|
893
|
+
if (xpr.length === 4 && xpr[1] === 'is' && xpr[2] === 'not' && xpr[3] === 'null')
|
|
894
|
+
return { isNotNull: [ xpr[0], { val: null } ] };
|
|
892
895
|
|
|
893
896
|
// binary operators: '=', '<>', 'like', ...
|
|
894
897
|
if (xpr.length === 3 && typeof xpr[1] === 'string')
|
|
@@ -916,8 +916,8 @@ function csn2annotationEdm( reqDefs, reqDefsUtils, csnVocabularies, serviceName,
|
|
|
916
916
|
}
|
|
917
917
|
// expression
|
|
918
918
|
const res = handleExpression(cAnnoValue['='], dTypeName);
|
|
919
|
-
oTarget.setXml(
|
|
920
|
-
oTarget.setJSON(
|
|
919
|
+
oTarget.setXml({ [res.name]: res.value });
|
|
920
|
+
oTarget.setJSON({ [res.name]: res.value });
|
|
921
921
|
}
|
|
922
922
|
else if (cAnnoValue['#'] !== undefined) {
|
|
923
923
|
const enumSymbol = cAnnoValue['#'];
|
|
@@ -968,9 +968,13 @@ function csn2annotationEdm( reqDefs, reqDefsUtils, csnVocabularies, serviceName,
|
|
|
968
968
|
}
|
|
969
969
|
else if (cAnnoValue.$edmJson) {
|
|
970
970
|
// "pseudo-structure" used for embedding a piece of JSON that represents "OData CSDL, JSON Representation"
|
|
971
|
-
|
|
971
|
+
const edmNode = handleEdmJson(cAnnoValue.$edmJson, msg);
|
|
972
|
+
if (edmNode && edmNode._kind === 'Path' && typeof edmNode._value === 'string' && !edmNode._children.length)
|
|
973
|
+
oTarget.setXml({ [edmNode._kind]: edmNode._value });
|
|
974
|
+
else
|
|
975
|
+
oTarget.append(edmNode);
|
|
972
976
|
}
|
|
973
|
-
else if (
|
|
977
|
+
else if (Object.keys(cAnnoValue).filter( x => x[0] !== '@' ).length === 0) {
|
|
974
978
|
// object consists only of properties starting with "@", no $value
|
|
975
979
|
setProp(oTarget, '$isInvalid', true);
|
|
976
980
|
message('odata-anno-value', msg.location,
|
package/lib/edm/csn2edm.js
CHANGED
|
@@ -49,7 +49,6 @@ function csn2edm( _csn, serviceName, _options, messageFunctions ) {
|
|
|
49
49
|
function csn2edmAll( _csn, _options, serviceNames, messageFunctions ) {
|
|
50
50
|
// get us a fresh model copy that we can work with
|
|
51
51
|
const csn = cloneFullCsn(_csn, _options);
|
|
52
|
-
const special$self = !csn?.definitions?.$self && '$self';
|
|
53
52
|
messageFunctions.setModel(csn);
|
|
54
53
|
|
|
55
54
|
const {
|
|
@@ -689,7 +688,7 @@ function csn2edmAll( _csn, _options, serviceNames, messageFunctions ) {
|
|
|
689
688
|
}
|
|
690
689
|
}
|
|
691
690
|
|
|
692
|
-
if (options.isV2()
|
|
691
|
+
if (options.isV2()) {
|
|
693
692
|
if (elementCsn._edmParentCsn.name === `${ elementCsn._edmParentCsn.$mySchemaName }.DraftAdministrativeData`)
|
|
694
693
|
elementCsn['@UI.HiddenFilter'] ??= true;
|
|
695
694
|
}
|
|
@@ -762,7 +761,7 @@ function csn2edmAll( _csn, _options, serviceNames, messageFunctions ) {
|
|
|
762
761
|
const entries = Object.entries(actionCsn.params);
|
|
763
762
|
const firstParam = entries[0][1];
|
|
764
763
|
const type = firstParam?.items?.type || firstParam?.type;
|
|
765
|
-
if (type ===
|
|
764
|
+
if (type === '$self') {
|
|
766
765
|
bpName = entries[0][0];
|
|
767
766
|
setProp(actionCsn, '$bindingParam', firstParam);
|
|
768
767
|
// preserve the original type (as it is the key to reqDefs.defintions)
|
|
@@ -938,7 +937,7 @@ function csn2edmAll( _csn, _options, serviceNames, messageFunctions ) {
|
|
|
938
937
|
if (parameterCsn['@cds.api.ignore'])
|
|
939
938
|
return;
|
|
940
939
|
const type = parameterCsn?.items?.type || parameterCsn?.type;
|
|
941
|
-
if (i === 0 && type ===
|
|
940
|
+
if (i === 0 && type === '$self') {
|
|
942
941
|
// skip and remove the first parameter if it is a $self binding parameter to
|
|
943
942
|
// omit annotation rendering later on
|
|
944
943
|
setProp(actionCsn, '$bindingParam', parameterCsn);
|
|
@@ -130,12 +130,11 @@ function inboundQualificationChecks( csn, options, messageFunctions,
|
|
|
130
130
|
// we need to know if the first path step is the bindind param
|
|
131
131
|
// for the rejection of V2 paths where the BP is ignored
|
|
132
132
|
function markBindingParamPaths( action, loc ) {
|
|
133
|
-
const special$self = !csn?.definitions?.$self && '$self';
|
|
134
133
|
if (action.params) {
|
|
135
134
|
const params = Object.entries(action.params);
|
|
136
135
|
const firstParam = params[0][1];
|
|
137
136
|
const type = firstParam?.items?.type || firstParam?.type;
|
|
138
|
-
if (type ===
|
|
137
|
+
if (type === '$self') {
|
|
139
138
|
const bindingParamName = params[0][0];
|
|
140
139
|
const markBindingParam = {
|
|
141
140
|
ref: (parent, prop, xpr) => {
|
|
@@ -40,7 +40,6 @@ function initializeModel( csn, _options, messageFunctions, requestedServiceNames
|
|
|
40
40
|
const {
|
|
41
41
|
info, warning, error, message,
|
|
42
42
|
} = messageFunctions;
|
|
43
|
-
const special$self = !csn?.definitions?.$self && '$self';
|
|
44
43
|
const csnUtils = getUtils(csn);
|
|
45
44
|
|
|
46
45
|
// proxies are merged into the final model after all proxy elements are collected
|
|
@@ -2077,7 +2076,8 @@ function initializeModel( csn, _options, messageFunctions, requestedServiceNames
|
|
|
2077
2076
|
|
|
2078
2077
|
function iterateParams( action, location ) {
|
|
2079
2078
|
let optPns = [];
|
|
2080
|
-
const isBP = p => (p.items?.type || p.type) ===
|
|
2079
|
+
const isBP = p => (p.items?.type || p.type) === '$self' &&
|
|
2080
|
+
p === Object.values( action.params )[0];
|
|
2081
2081
|
|
|
2082
2082
|
if (action.params) {
|
|
2083
2083
|
Object.entries(action.params).forEach(([ pn, p ]) => {
|
|
@@ -2126,7 +2126,7 @@ function initializeModel( csn, _options, messageFunctions, requestedServiceNames
|
|
|
2126
2126
|
}
|
|
2127
2127
|
else if (action.kind === 'function') {
|
|
2128
2128
|
// this is a mandatory parameter, warn about all previously collected optional parameters
|
|
2129
|
-
if (optPns.
|
|
2129
|
+
if (optPns.length)
|
|
2130
2130
|
error('odata-parameter-order', location.concat(pn));
|
|
2131
2131
|
optPns = [];
|
|
2132
2132
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
|
|
1
|
+
2af41f8c84ca1fa40a50b8c58903dadc
|
package/lib/gen/CdlParser.js
CHANGED
|
@@ -2111,6 +2111,7 @@ default:this.gr([';']);continue
|
|
|
2111
2111
|
}
|
|
2112
2112
|
default:
|
|
2113
2113
|
this.attachLocation( $.art )
|
|
2114
|
+
if (!$.art.name) this.addDef( $.art, $.outer, 'elements', 'element' )
|
|
2114
2115
|
return this.exit_()
|
|
2115
2116
|
}
|
|
2116
2117
|
}
|
|
@@ -3826,7 +3827,7 @@ case 645:this.s=651;{ this.attachLocation( $.expr ); }continue
|
|
|
3826
3827
|
case 646:if(this.valuePath(_={},651)){e=_.expr; this.valuePathAstWithNew( $.expr, e ); }continue
|
|
3827
3828
|
case 647:switch(this.l()){
|
|
3828
3829
|
case'(':if(this.c(648))open=this.lb();continue
|
|
3829
|
-
case'Id':if(this.valuePath(_={},651)){e=_.expr; e = this.valuePathAst( e ); e.$
|
|
3830
|
+
case'Id':if(this.valuePath(_={},651)){e=_.expr; e = this.valuePathAst( e ); e.$syntax = 'after-exists';
|
|
3830
3831
|
$.expr.args.push( e ); this.attachLocation( $.expr ); }continue
|
|
3831
3832
|
case'?':if(this.c(651)){ this.csnParseOnly( 'syntax-unsupported-param', -1, { '#': 'dynamic', code: '?' } );
|
|
3832
3833
|
$.expr.args.push( { param: this.valueWithLocation(), scope: 'param' } ); this.attachLocation( $.expr ); }continue
|
|
@@ -4451,8 +4452,8 @@ case 820:switch(this.l()){
|
|
|
4451
4452
|
case')':this.gc(821,'fail')&&this.c(0);continue
|
|
4452
4453
|
default:this.s=821;continue
|
|
4453
4454
|
}
|
|
4454
|
-
case 821:if(this.condition(_={},822))$.value=_.expr;continue
|
|
4455
|
-
case 822:
|
|
4455
|
+
case 821:if(this.condition(_={},822)){$.value=_.expr; $.value.$tokenTexts = this.ruleTokensText(); }continue
|
|
4456
|
+
case 822:this.m(0,')');continue
|
|
4456
4457
|
default:
|
|
4457
4458
|
this.attachLocation( $.value )
|
|
4458
4459
|
return this.exit_()
|
package/lib/gen/Dictionary.json
CHANGED
|
@@ -596,7 +596,7 @@
|
|
|
596
596
|
"Property",
|
|
597
597
|
"Parameter"
|
|
598
598
|
],
|
|
599
|
-
"Type": "Edm.
|
|
599
|
+
"Type": "Edm.PrimitiveType"
|
|
600
600
|
},
|
|
601
601
|
"Common.FieldControl": {
|
|
602
602
|
"AppliesTo": [
|
|
@@ -910,6 +910,13 @@
|
|
|
910
910
|
"$deprecationText": "Use terms [Aggregation.RecursiveHierarchy](https://github.com/oasis-tcs/odata-vocabularies/blob/main/vocabularies/Org.OData.Aggregation.V1.md#RecursiveHierarchy) and [Hierarchy.RecursiveHierarchy](https://github.com/SAP/odata-vocabularies/blob/main/vocabularies/Hierarchy.md#RecursiveHierarchy) instead",
|
|
911
911
|
"Type": "Common.RecursiveHierarchyType"
|
|
912
912
|
},
|
|
913
|
+
"Common.ReferentialConstraint": {
|
|
914
|
+
"$experimental": true,
|
|
915
|
+
"AppliesTo": [
|
|
916
|
+
"NavigationProperty"
|
|
917
|
+
],
|
|
918
|
+
"Type": "Collection(Common.ReferentialConstraintType)"
|
|
919
|
+
},
|
|
913
920
|
"Common.RelatedRecursiveHierarchy": {
|
|
914
921
|
"AppliesTo": [
|
|
915
922
|
"Property"
|
|
@@ -3345,6 +3352,14 @@
|
|
|
3345
3352
|
"NodeDrillStateProperty": "Edm.PropertyPath"
|
|
3346
3353
|
}
|
|
3347
3354
|
},
|
|
3355
|
+
"Common.ReferentialConstraintType": {
|
|
3356
|
+
"$experimental": true,
|
|
3357
|
+
"$kind": "ComplexType",
|
|
3358
|
+
"Properties": {
|
|
3359
|
+
"Property": "Edm.PropertyPath",
|
|
3360
|
+
"ReferencedProperty": "Edm.PropertyPath"
|
|
3361
|
+
}
|
|
3362
|
+
},
|
|
3348
3363
|
"Common.SAPObjectNodeTypeType": {
|
|
3349
3364
|
"$experimental": true,
|
|
3350
3365
|
"$kind": "ComplexType",
|
package/lib/json/from-csn.js
CHANGED
|
@@ -1682,15 +1682,13 @@ function exprOrString( val, spec ) {
|
|
|
1682
1682
|
: expr( val, spec );
|
|
1683
1683
|
}
|
|
1684
1684
|
|
|
1685
|
-
// mark path argument of 'exists' predicate with $
|
|
1685
|
+
// mark path argument of 'exists' predicate with $syntax:'after-exists'
|
|
1686
1686
|
function exprArgs( cond, spec ) {
|
|
1687
1687
|
const rxsn = arrayOf( exprOrString )( cond, spec );
|
|
1688
|
-
// TODO: do that in definer.js, neither here nor in CDL parser
|
|
1689
1688
|
if (Array.isArray( rxsn )) {
|
|
1690
|
-
for (let i = 0; i < rxsn.length - 1;
|
|
1691
|
-
|
|
1692
|
-
|
|
1693
|
-
rxsn[++i].$expected = 'exists';
|
|
1689
|
+
for (let i = 0; i < rxsn.length - 1;) {
|
|
1690
|
+
if (cond[i++] === 'exists' && rxsn[i].path)
|
|
1691
|
+
rxsn[i].$syntax = 'after-exists';
|
|
1694
1692
|
}
|
|
1695
1693
|
}
|
|
1696
1694
|
return rxsn;
|
package/lib/json/to-csn.js
CHANGED
|
@@ -1172,15 +1172,15 @@ function enumValueOrCalc( v, csn, node, prop ) {
|
|
|
1172
1172
|
if (node.kind === 'enum') {
|
|
1173
1173
|
Object.assign( csn, expression( v ) );
|
|
1174
1174
|
}
|
|
1175
|
-
else if (
|
|
1176
|
-
return true
|
|
1175
|
+
else if (prop === '$calc') {
|
|
1176
|
+
return v === true || expression( v );
|
|
1177
1177
|
}
|
|
1178
1178
|
// In XSN, there are combined elem/column objects: do not represent column
|
|
1179
1179
|
// expression when presented as element in CSN
|
|
1180
1180
|
|
|
1181
1181
|
// node.$syntax set in define.el(!), but not inside an `extend`, a _parent might
|
|
1182
1182
|
// not be set always for parse-only, especially with CSN input
|
|
1183
|
-
else if (node.$syntax === 'calc' ||
|
|
1183
|
+
else if (node.$syntax === 'calc' ||
|
|
1184
1184
|
!node._parent || node._parent.kind === 'extend') {
|
|
1185
1185
|
const stored = v.stored ? { stored: value(v.stored) } : {};
|
|
1186
1186
|
return Object.assign( stored, expression( v ) );
|
package/lib/model/csnRefs.js
CHANGED
|
@@ -519,6 +519,8 @@ function csnRefs( csn, universalReady ) {
|
|
|
519
519
|
* Return the object pointing to by the artifact reference (in 'type',
|
|
520
520
|
* 'includes', 'target'). For `from`, use artifactRefFrom()!
|
|
521
521
|
*
|
|
522
|
+
* Warning: do not use it for the binding parameter if the ref is `$self`.
|
|
523
|
+
*
|
|
522
524
|
* @param {CSN.ArtifactReferencePath|string} ref
|
|
523
525
|
* @param {any} [notFound] Value that is returned in case the artifact reference
|
|
524
526
|
* could not be found.
|
|
@@ -612,9 +614,16 @@ function csnRefs( csn, universalReady ) {
|
|
|
612
614
|
|
|
613
615
|
function getOriginRaw( art ) {
|
|
614
616
|
if (art.type) { // TODO: make robust against "linked" = only direct
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
617
|
+
if (art.type !== '$self')
|
|
618
|
+
return artifactRef( art.type, BUILTIN_TYPE );
|
|
619
|
+
const action = boundActionOrMain( art );
|
|
620
|
+
const binding = action?.params && Object.values( action.params )[0];
|
|
621
|
+
// binding parameter must be typed with `$self` or `many $self`:
|
|
622
|
+
const entity = binding && (art === binding || art === binding.items) &&
|
|
623
|
+
getCache( action, '_parent' );
|
|
624
|
+
// if name-deprecated-$self is a non-config error, this could be simplified to:
|
|
625
|
+
// const entity = action?.params && getCache( action, '_parent' );
|
|
626
|
+
return entity || artifactRef( art.type, BUILTIN_TYPE );
|
|
618
627
|
}
|
|
619
628
|
if (typeof art.$origin === 'object') // null, […], {…}
|
|
620
629
|
return getOriginExplicit( art.$origin );
|
|
@@ -798,7 +807,7 @@ function csnRefs( csn, universalReady ) {
|
|
|
798
807
|
}
|
|
799
808
|
|
|
800
809
|
if (!qcache)
|
|
801
|
-
throw new CompilerAssertion( `
|
|
810
|
+
throw new CompilerAssertion( `For semantics '${ refCtx }', query not in cache at: ${ locationString(query.$location) }` );
|
|
802
811
|
|
|
803
812
|
if (semantics.dynamic === 'query') {
|
|
804
813
|
// TODO: for ON condition in expand, would need to use cached _element
|
package/lib/model/enrichCsn.js
CHANGED
|
@@ -69,7 +69,6 @@ function enrichCsn( csn, options = {} ) {
|
|
|
69
69
|
// options.enrichCsn = 'DEBUG';
|
|
70
70
|
let $$cacheObjectNumber = 0; // for debugging
|
|
71
71
|
const debugLocationInfo = options.enrichCsn === 'DEBUG' && Object.create(null);
|
|
72
|
-
const special$self = !csn.definitions.$self && '$self';
|
|
73
72
|
|
|
74
73
|
setLocations( csn, false, null );
|
|
75
74
|
const {
|
|
@@ -180,7 +179,10 @@ function enrichCsn( csn, options = {} ) {
|
|
|
180
179
|
parent[`_${ prop }`] = ref.map( r => refLocation( artifactRef( r, notFound ) ) );
|
|
181
180
|
}
|
|
182
181
|
else if (typeof ref === 'string') {
|
|
183
|
-
|
|
182
|
+
// Don't use `artifactRef( 'self' )` for binding parameter. Because it is not
|
|
183
|
+
// easily recognizable here, always omit `_type` property for `$self` even if
|
|
184
|
+
// there is a type named `$self` (which would induce a configurable error):
|
|
185
|
+
if (!ref.startsWith( 'cds.') && ref !== '$self')
|
|
184
186
|
parent[`_${ prop }`] = refLocation( artifactRef( ref, notFound ) );
|
|
185
187
|
}
|
|
186
188
|
else if (!ref.elements) {
|
package/lib/optionProcessor.js
CHANGED
|
@@ -2,11 +2,13 @@
|
|
|
2
2
|
|
|
3
3
|
/* eslint @stylistic/max-len: 0 */
|
|
4
4
|
|
|
5
|
-
// Remarks
|
|
6
|
-
// - The specification is client-tool centric (bin/cdsc.js):
|
|
5
|
+
// Remarks (`git blame` on these remarks does not reveal the options designer)
|
|
6
|
+
// - The specification is client-tool centric (../bin/cdsc.js):
|
|
7
7
|
// an option named `fooBar` is “produced” by `.option(' --foo-bar')`.
|
|
8
8
|
// - Also list the option in the `help` text, used with `cdsc -h`.
|
|
9
|
-
// -
|
|
9
|
+
// - All options must also be added to ./api/options.js.
|
|
10
|
+
// - Specify valid values for non-boolean options in ./api/validate.js.
|
|
11
|
+
// - Beta and deprecated options are specified in ./base/model.js.
|
|
10
12
|
|
|
11
13
|
'use strict';
|
|
12
14
|
|
|
@@ -54,7 +56,8 @@ optionProcessor
|
|
|
54
56
|
.option(' --add-texts-language-assoc')
|
|
55
57
|
.option(' --localized-without-coalesce')
|
|
56
58
|
.option(' --tenant-discriminator')
|
|
57
|
-
.option(' --no-composition-includes')
|
|
59
|
+
.option(' --no-composition-includes') // TODO: missed to be removed in #13762
|
|
60
|
+
.option(' --no-dollar-calc') // for grep: option `noDollarCalc`
|
|
58
61
|
.option(' --default-binary-length <length>')
|
|
59
62
|
.option(' --default-string-length <length>')
|
|
60
63
|
.option(' --struct-xpr')
|
|
@@ -157,6 +160,7 @@ optionProcessor
|
|
|
157
160
|
to "sap.common.Languages" if it exists
|
|
158
161
|
--localized-without-coalesce Omit coalesce in localized convenience views
|
|
159
162
|
--no-composition-includes Do NOT add named aspects to 'includes' property of generated composition entity.
|
|
163
|
+
--no-dollar-calc Don't set $calc property in CSN
|
|
160
164
|
--no-recompile Don't recompile in case of internal errors
|
|
161
165
|
--struct-xpr Write structured expressions to the compiler CSN output (possibly then
|
|
162
166
|
used as input for backends)
|
|
@@ -696,7 +696,7 @@ class AstBuildingParser extends BaseParser {
|
|
|
696
696
|
|
|
697
697
|
ruleTokensText() {
|
|
698
698
|
let tokenIdx = this.stack.at(-1).tokenIdx + 1;
|
|
699
|
-
const stop = this.tokenIdx
|
|
699
|
+
const stop = this.tokenIdx;
|
|
700
700
|
|
|
701
701
|
let { text: result, location: prev } = this.tokens[tokenIdx];
|
|
702
702
|
while (++tokenIdx < stop) {
|
package/lib/render/utils/sql.js
CHANGED
|
@@ -198,10 +198,11 @@ function _artifactIsProjectionView( csn, artifact ) {
|
|
|
198
198
|
referencedElements[column.ref.at(-1)] = true;
|
|
199
199
|
}
|
|
200
200
|
|
|
201
|
-
// Full primary key needs to be projected according to HANA SQL spec
|
|
201
|
+
// Full primary key/not null chain needs to be projected according to HANA SQL spec
|
|
202
|
+
// https://help.sap.com/docs/hana-cloud-database/sap-hana-cloud-sap-hana-database-sql-reference-guide/create-projection-view-statement-data-definition
|
|
202
203
|
for (const elementName in source.elements) {
|
|
203
204
|
const element = source.elements[elementName];
|
|
204
|
-
if (element.key && !referencedElements[elementName])
|
|
205
|
+
if ((element.notNull || element.key) && !referencedElements[elementName])
|
|
205
206
|
return false;
|
|
206
207
|
}
|
|
207
208
|
|
|
@@ -211,7 +211,7 @@ function applyTransformationsInternal( parent, prop, customTransformers, artifac
|
|
|
211
211
|
*
|
|
212
212
|
* @param {object | Array} node the thing that has _prop
|
|
213
213
|
* @param {string|number} _prop the name of the current property
|
|
214
|
-
* @param {
|
|
214
|
+
* @param {Array} _path The value of node[_prop]
|
|
215
215
|
*/
|
|
216
216
|
function pathRef( node, _prop, _path ) {
|
|
217
217
|
csnPath.push( _prop );
|
|
@@ -122,7 +122,7 @@ function processAssertUnique( csn, options, messageFunctions ) {
|
|
|
122
122
|
* Check strictly that annotation value is an array
|
|
123
123
|
* and that the individual array entries are references
|
|
124
124
|
*
|
|
125
|
-
* @param {
|
|
125
|
+
* @param {object} val Annotation value
|
|
126
126
|
* @param {string} propName
|
|
127
127
|
* @returns {Array} Array of paths
|
|
128
128
|
*/
|
|
@@ -149,8 +149,8 @@ function processAssertUnique( csn, options, messageFunctions ) {
|
|
|
149
149
|
/**
|
|
150
150
|
* Convert a ref object to a path string
|
|
151
151
|
*
|
|
152
|
-
* @param {
|
|
153
|
-
* @returns {string|string[]|
|
|
152
|
+
* @param {CSN.Ref | object} v
|
|
153
|
+
* @returns {string|string[]|object}
|
|
154
154
|
*/
|
|
155
155
|
function unref( v ) {
|
|
156
156
|
if (Array.isArray(v))
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Normalize a queries `from` clause by moving the infix filters on the last step of a `SELECT.from.ref` path.
|
|
5
|
+
*
|
|
6
|
+
* For the given query/projection, if the terminal ref element is an object
|
|
7
|
+
* containing a `where` (syntactic sugar), its condition is moved into `SELECT.where`.
|
|
8
|
+
* - If `SELECT.where` already exists, both are combined as: { xpr: [ oldWhere ] } AND { xpr: [ leafWhere ] }.
|
|
9
|
+
* - Otherwise the leaf `where` becomes the `SELECT.where`.
|
|
10
|
+
* The ref leaf object is then replaced by its plain `id`, removing the inline filter.
|
|
11
|
+
*
|
|
12
|
+
* Mutates the provided query in place.
|
|
13
|
+
*
|
|
14
|
+
* @param {CSN.Query} query - The query to normalize.
|
|
15
|
+
*/
|
|
16
|
+
function normalizeFromLeaf( query ) {
|
|
17
|
+
const where = query.SELECT?.from.ref?.at(-1).where;
|
|
18
|
+
if (!where || where.length === 0)
|
|
19
|
+
return;
|
|
20
|
+
if (query.SELECT.where)
|
|
21
|
+
query.SELECT.where = [ { xpr: [ ...query.SELECT.where ] }, 'AND', { xpr: [ ...where ] } ];
|
|
22
|
+
else
|
|
23
|
+
query.SELECT.where = where;
|
|
24
|
+
|
|
25
|
+
// preserve `args`
|
|
26
|
+
if (query.SELECT?.from.ref?.at(-1).args)
|
|
27
|
+
delete query.SELECT.from.ref[query.SELECT.from.ref.length - 1].where;
|
|
28
|
+
else
|
|
29
|
+
query.SELECT.from.ref[query.SELECT.from.ref.length - 1] = query.SELECT.from.ref.at(-1).id;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
module.exports = normalizeFromLeaf;
|
|
@@ -1,9 +1,13 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
const {
|
|
3
|
+
const {
|
|
4
|
+
forAllQueries, forEachDefinition, walkCsnPath, transformExpression,
|
|
5
|
+
} = require('../../../model/csnUtils');
|
|
4
6
|
const { setProp } = require('../../../base/model');
|
|
5
7
|
const { getHelpers } = require('./utils');
|
|
6
8
|
|
|
9
|
+
const normalizeFromLeaf = require('./normalizeFrom');
|
|
10
|
+
|
|
7
11
|
/**
|
|
8
12
|
* Turn a `exists assoc[filter = 100]` into a `exists (select 1 as dummy from assoc.target where <assoc on condition> and assoc.target.filter = 100)`.
|
|
9
13
|
*
|
|
@@ -62,6 +66,9 @@ function handleExists( csn, options, messageFunctions, csnUtils ) {
|
|
|
62
66
|
forEachDefinition(csn, (artifact, artifactName) => {
|
|
63
67
|
if (artifact.query || artifact.projection) {
|
|
64
68
|
forAllQueries(artifact.query || { SELECT: artifact.projection }, function handleExistsQuery(query, path) {
|
|
69
|
+
// Normalize infix filters on the last step of a `SELECT.from.ref` path.
|
|
70
|
+
// By doing this before the exists transformation, we enable `SELECT * FROM Author:books[exists genre]`
|
|
71
|
+
normalizeFromLeaf(query);
|
|
65
72
|
if (!generatedExists.has(query)) {
|
|
66
73
|
const toProcess = []; // Collect all expressions we need to process here
|
|
67
74
|
if (query.SELECT?.where?.length > 1)
|
|
@@ -77,8 +84,12 @@ function handleExists( csn, options, messageFunctions, csnUtils ) {
|
|
|
77
84
|
toProcess.push([ path.slice(0, -1), path.concat([ 'from', 'on' ]) ]);
|
|
78
85
|
|
|
79
86
|
for (const [ , exprPath ] of toProcess) {
|
|
80
|
-
const
|
|
81
|
-
|
|
87
|
+
const token = walkCsnPath(csn, exprPath);
|
|
88
|
+
transformExpression(token, null, {
|
|
89
|
+
ref: (container, prop, ref, pathToRef) => {
|
|
90
|
+
nestExists(pathToRef.slice(0, -1));
|
|
91
|
+
},
|
|
92
|
+
}, exprPath);
|
|
82
93
|
}
|
|
83
94
|
|
|
84
95
|
while (toProcess.length > 0) {
|
|
@@ -283,7 +294,7 @@ function handleExists( csn, options, messageFunctions, csnUtils ) {
|
|
|
283
294
|
|
|
284
295
|
newExpr.push('exists');
|
|
285
296
|
if (ref?.where) {
|
|
286
|
-
const remappedWhere = remapExistingWhere(target, ref.where
|
|
297
|
+
const remappedWhere = remapExistingWhere( target, ref.where );
|
|
287
298
|
subselect.SELECT.where.push('and');
|
|
288
299
|
if (remappedWhere.length > 3)
|
|
289
300
|
subselect.SELECT.where.push( { xpr: remappedWhere } );
|
|
@@ -361,21 +372,14 @@ function handleExists( csn, options, messageFunctions, csnUtils ) {
|
|
|
361
372
|
*
|
|
362
373
|
* This function does this by adding the assoc target before all the refs so that the refs are resolvable in the WHERE.
|
|
363
374
|
*
|
|
364
|
-
* This function also rejects $self paths in filter conditions.
|
|
365
|
-
*
|
|
366
375
|
* @param {string} target
|
|
367
376
|
* @param {TokenStream} where
|
|
368
|
-
* @param {CSN.Path} path path to the part, used if error needs to be thrown
|
|
369
|
-
* @param {CSN.Artifact} parent the host of the `where`, used if error needs to be thrown
|
|
370
377
|
*
|
|
371
378
|
* @returns {TokenStream} where The input-where with the refs transformed to absolute ones
|
|
372
379
|
*/
|
|
373
|
-
function remapExistingWhere( target, where
|
|
380
|
+
function remapExistingWhere( target, where ) {
|
|
374
381
|
return where.map((part) => {
|
|
375
|
-
if (part.$scope
|
|
376
|
-
error('ref-unexpected-self', path, { '#': 'exists-filter', elemref: parent, id: part.ref[0] });
|
|
377
|
-
}
|
|
378
|
-
else if (part.ref && part.$scope !== '$magic') {
|
|
382
|
+
if (part.ref && part.$scope !== '$magic') {
|
|
379
383
|
part.ref = [ target, ...part.ref ];
|
|
380
384
|
return part;
|
|
381
385
|
}
|
|
@@ -145,11 +145,6 @@ function getHelpers( csn, inspectRef, error ) {
|
|
|
145
145
|
* @returns {object[]} The stuff to add to the where
|
|
146
146
|
*/
|
|
147
147
|
function translateManagedAssocToWhere( root, target, isPrefixedWithTableAlias, base, current ) {
|
|
148
|
-
if (current.$scope === '$self') {
|
|
149
|
-
error('ref-unexpected-self', current.$path, { '#': 'exists', id: current.ref[0], name: 'exists' });
|
|
150
|
-
return [];
|
|
151
|
-
}
|
|
152
|
-
|
|
153
148
|
const whereExtension = [];
|
|
154
149
|
for (let j = 0; j < root.keys.length; j++) {
|
|
155
150
|
const lop = { ref: [ target, ...root.keys[j].ref ] }; // target side
|
|
@@ -260,7 +255,7 @@ function getHelpers( csn, inspectRef, error ) {
|
|
|
260
255
|
/**
|
|
261
256
|
* Run Object.assign on all of the passed in parameters and delete a .as and .key at the end
|
|
262
257
|
*
|
|
263
|
-
* @param {...
|
|
258
|
+
* @param {...object} args
|
|
264
259
|
* @returns {object} The merged args without an .as and .key property
|
|
265
260
|
*/
|
|
266
261
|
function assignAndDeleteAsAndKey( ...args ) {
|
|
@@ -24,7 +24,7 @@ function getBacklinkTransformer( csnUtils, messageFunctions, options, pathDelimi
|
|
|
24
24
|
/**
|
|
25
25
|
* @param {CSN.Artifact} artifact
|
|
26
26
|
* @param {string} artifactName
|
|
27
|
-
* @param {
|
|
27
|
+
* @param {object} dummy unused Parameter
|
|
28
28
|
* @param {CSN.Path} path
|
|
29
29
|
*/
|
|
30
30
|
function transformSelfInBacklinks( artifact, artifactName, dummy, path ) {
|
|
@@ -164,7 +164,7 @@ function getBacklinkTransformer( csnUtils, messageFunctions, options, pathDelimi
|
|
|
164
164
|
* Return the condition to replace the comparison `<assocOp> = $self` in the ON-condition
|
|
165
165
|
* of element <elem> of artifact 'art'. If there is anything to complain, use location <loc>
|
|
166
166
|
*
|
|
167
|
-
* @param {
|
|
167
|
+
* @param {CSN.Ref} assocOp
|
|
168
168
|
* @param {CSN.Element} assoc
|
|
169
169
|
* @param {string} assocName
|
|
170
170
|
* @param {CSN.Element} elem
|
|
@@ -224,7 +224,7 @@ function getBacklinkTransformer( csnUtils, messageFunctions, options, pathDelimi
|
|
|
224
224
|
* keys in this artifact.
|
|
225
225
|
* For example, `ON elem.ass = $self` becomes `ON elem.ass_key1 = key1 AND elem.ass_key2 = key2`
|
|
226
226
|
* (assuming that `ass` has the foreign keys `key1` and `key2`)
|
|
227
|
-
* @param {
|
|
227
|
+
* @param {CSN.Ref} assocOp
|
|
228
228
|
* @param {CSN.Element} assoc
|
|
229
229
|
* @param {string} originalAssocName
|
|
230
230
|
* @param {string} elemName
|
|
@@ -276,7 +276,7 @@ function getBacklinkTransformer( csnUtils, messageFunctions, options, pathDelimi
|
|
|
276
276
|
* For example, `ON elem.ass = $self` becomes `ON a = elem.x AND b = elem.y`
|
|
277
277
|
* (assuming that `ass` has the ON-condition `ON ass.a = x AND ass.b = y`)
|
|
278
278
|
*
|
|
279
|
-
* @param {
|
|
279
|
+
* @param {CSN.Ref} assocOp
|
|
280
280
|
* @param {CSN.Element} assoc
|
|
281
281
|
* @param {string} originalAssocName
|
|
282
282
|
* @param {string} elemName
|
|
@@ -9,7 +9,7 @@ const {
|
|
|
9
9
|
const { recurseElements } = require('../transformUtils');
|
|
10
10
|
|
|
11
11
|
/**
|
|
12
|
-
* Return a callback function for forEachDefinition that marks artifacts that are abstract or
|
|
12
|
+
* Return a callback function for forEachDefinition that marks artifacts that are abstract or `@cds.persistence.exists/skip`
|
|
13
13
|
* with $ignore.
|
|
14
14
|
*
|
|
15
15
|
* @returns {(artifact: CSN.Artifact, artifactName: string) => void} Callback function for forEachDefinition
|
|
@@ -31,7 +31,7 @@ function getAnnoProcessor() {
|
|
|
31
31
|
|
|
32
32
|
/**
|
|
33
33
|
* Return a callback function for forEachDefinition that marks associations with $ignore
|
|
34
|
-
* if their target does not reach the database, i.e. marked with
|
|
34
|
+
* if their target does not reach the database, i.e. marked with `@cds.persistence.skip` or is abstract
|
|
35
35
|
*
|
|
36
36
|
* @param {CSN.Model} csn
|
|
37
37
|
* @param {CSN.Options} options
|
|
@@ -48,7 +48,7 @@ function getAssocToSkippedIgnorer( csn, options, messageFunctions, csnUtils ) {
|
|
|
48
48
|
|
|
49
49
|
return ignoreAssociationToSkippedTarget;
|
|
50
50
|
/**
|
|
51
|
-
* Associations that target a
|
|
51
|
+
* Associations that target a `@cds.persistence.skip` artifact must be removed
|
|
52
52
|
* from the persistence model
|
|
53
53
|
*
|
|
54
54
|
* @param {CSN.Artifact} artifact
|
|
@@ -94,7 +94,7 @@ function getAssocToSkippedIgnorer( csn, options, messageFunctions, csnUtils ) {
|
|
|
94
94
|
}
|
|
95
95
|
|
|
96
96
|
/**
|
|
97
|
-
* Return a callback function for forEachDefinition that handles artifacts marked with
|
|
97
|
+
* Return a callback function for forEachDefinition that handles artifacts marked with `@cds.persistence.table`.
|
|
98
98
|
* If a .query artifact has this annotation, the .query will be deleted and it will be treated like a table.
|
|
99
99
|
*
|
|
100
100
|
* @param {CSN.Model} csn
|
|
@@ -239,7 +239,7 @@ function createReferentialConstraints( csn, options ) {
|
|
|
239
239
|
* The following decision table reflects the current implementation:
|
|
240
240
|
*
|
|
241
241
|
* +-----------------+--------------------+-------------------+----------+
|
|
242
|
-
* | Global Switch: | Global Check Type: |
|
|
242
|
+
* | Global Switch: | Global Check Type: | `@assert.integrity` | Generate |
|
|
243
243
|
* |"assertIntegrity"| "assertIntegrityType"| | Constraint|
|
|
244
244
|
* +-----------------+--------------------+-------------------+----------+
|
|
245
245
|
* | on | RT | false | no |
|
|
@@ -345,7 +345,7 @@ function createReferentialConstraints( csn, options ) {
|
|
|
345
345
|
|
|
346
346
|
/**
|
|
347
347
|
* if global checks are 'individual' we evaluate every association,
|
|
348
|
-
* we create db constraints if it is annotated with
|
|
348
|
+
* we create db constraints if it is annotated with `@assert.integrity`: 'DB' (or true)
|
|
349
349
|
*
|
|
350
350
|
* @returns {boolean}
|
|
351
351
|
*/
|
|
@@ -388,7 +388,7 @@ function createReferentialConstraints( csn, options ) {
|
|
|
388
388
|
/**
|
|
389
389
|
* if global checks are on and global integrity check type is 'DB'
|
|
390
390
|
* we create db constraints in any case except if annotated
|
|
391
|
-
* with
|
|
391
|
+
* with `@assert.integrity: 'RT'` (or false, but that is rejected earlier)
|
|
392
392
|
*
|
|
393
393
|
* @returns {boolean}
|
|
394
394
|
*/
|
|
@@ -397,7 +397,7 @@ function createReferentialConstraints( csn, options ) {
|
|
|
397
397
|
}
|
|
398
398
|
|
|
399
399
|
/**
|
|
400
|
-
* Convenience to check if value of element's
|
|
400
|
+
* Convenience to check if value of element's `@assert.integrity` annotation
|
|
401
401
|
* is the same as a given value. `@assert.integrity`-value checks do not use the "truthy"-semantics,
|
|
402
402
|
* since string values _and_ booleans are allowed, but are treated differently.
|
|
403
403
|
*
|