@sap/cds-compiler 3.9.2 → 4.0.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 +98 -0
- package/README.md +0 -1
- package/bin/cdsc.js +11 -23
- package/bin/cdsse.js +3 -3
- package/doc/API.md +5 -0
- package/doc/CHANGELOG_ARCHIVE.md +1 -1
- package/doc/CHANGELOG_BETA.md +17 -1
- package/doc/CHANGELOG_DEPRECATED.md +28 -0
- package/lib/api/.eslintrc.json +1 -1
- package/lib/api/main.js +26 -8
- package/lib/api/options.js +2 -0
- package/lib/base/error.js +2 -0
- package/lib/base/message-registry.js +144 -65
- package/lib/base/messages.js +213 -107
- package/lib/base/model.js +11 -11
- package/lib/checks/.eslintrc.json +1 -1
- package/lib/checks/annotationsOData.js +2 -2
- package/lib/checks/elements.js +1 -1
- package/lib/checks/enricher.js +26 -3
- package/lib/checks/onConditions.js +67 -12
- package/lib/checks/queryNoDbArtifacts.js +106 -105
- package/lib/checks/sql-snippets.js +2 -0
- package/lib/checks/types.js +12 -6
- package/lib/checks/validator.js +2 -2
- package/lib/compiler/assert-consistency.js +10 -8
- package/lib/compiler/builtins.js +8 -2
- package/lib/compiler/checks.js +52 -35
- package/lib/compiler/define.js +31 -26
- package/lib/compiler/extend.js +120 -65
- package/lib/compiler/finalize-parse-cdl.js +12 -43
- package/lib/compiler/generate.js +16 -5
- package/lib/compiler/index.js +8 -5
- package/lib/compiler/kick-start.js +4 -3
- package/lib/compiler/populate.js +96 -95
- package/lib/compiler/propagator.js +7 -8
- package/lib/compiler/resolve.js +377 -103
- package/lib/compiler/shared.js +794 -517
- package/lib/compiler/tweak-assocs.js +8 -6
- package/lib/compiler/utils.js +44 -0
- package/lib/edm/annotations/genericTranslation.js +24 -6
- package/lib/edm/csn2edm.js +47 -45
- package/lib/edm/edm.js +34 -31
- package/lib/edm/edmAnnoPreprocessor.js +0 -23
- package/lib/edm/edmInboundChecks.js +7 -2
- package/lib/edm/edmPreprocessor.js +18 -17
- package/lib/edm/edmUtils.js +8 -4
- package/lib/gen/Dictionary.json +18 -0
- package/lib/gen/language.checksum +1 -1
- package/lib/gen/language.interp +4 -2
- package/lib/gen/languageParser.js +5006 -4582
- package/lib/json/from-csn.js +159 -114
- package/lib/json/to-csn.js +60 -89
- package/lib/language/antlrParser.js +17 -13
- package/lib/language/docCommentParser.js +11 -1
- package/lib/language/genericAntlrParser.js +13 -10
- package/lib/language/language.g4 +168 -97
- package/lib/main.d.ts +128 -36
- package/lib/main.js +1 -1
- package/lib/model/csnRefs.js +24 -5
- package/lib/model/csnUtils.js +9 -8
- package/lib/model/revealInternalProperties.js +7 -12
- package/lib/modelCompare/compare.js +1 -1
- package/lib/modelCompare/utils/filter.js +40 -2
- package/lib/optionProcessor.js +0 -3
- package/lib/render/toCdl.js +247 -214
- package/lib/render/toHdbcds.js +197 -181
- package/lib/render/toSql.js +325 -289
- package/lib/render/utils/common.js +42 -4
- package/lib/render/utils/delta.js +1 -1
- package/lib/render/utils/sql.js +3 -3
- package/lib/transform/braceExpression.js +2 -2
- package/lib/transform/db/.eslintrc.json +1 -1
- package/lib/transform/db/applyTransformations.js +3 -3
- package/lib/transform/db/associations.js +24 -12
- package/lib/transform/db/expansion.js +17 -18
- package/lib/transform/db/flattening.js +17 -21
- package/lib/transform/db/rewriteCalculatedElements.js +171 -64
- package/lib/transform/db/views.js +3 -4
- package/lib/transform/draft/db.js +21 -12
- package/lib/transform/draft/odata.js +4 -0
- package/lib/transform/forOdataNew.js +11 -10
- package/lib/transform/forRelationalDB.js +12 -7
- package/lib/transform/localized.js +5 -3
- package/lib/transform/odata/toFinalBaseType.js +5 -5
- package/lib/transform/odata/typesExposure.js +3 -3
- package/lib/transform/parseExpr.js +3 -0
- package/lib/transform/transformUtilsNew.js +43 -23
- package/lib/transform/translateAssocsToJoins.js +7 -6
- package/lib/transform/universalCsn/.eslintrc.json +1 -1
- package/lib/transform/universalCsn/coreComputed.js +7 -5
- package/lib/transform/universalCsn/universalCsnEnricher.js +12 -12
- package/lib/utils/file.js +3 -3
- package/lib/utils/moduleResolve.js +1 -1
- package/package.json +2 -2
- package/share/messages/{duplicate-autoexposed.md → def-duplicate-autoexposed.md} +5 -1
- package/share/messages/message-explanations.json +1 -1
|
@@ -17,6 +17,7 @@ const {
|
|
|
17
17
|
traverseQueryPost,
|
|
18
18
|
traverseQueryExtra,
|
|
19
19
|
setExpandStatus,
|
|
20
|
+
traverseExpr,
|
|
20
21
|
} = require('./utils');
|
|
21
22
|
|
|
22
23
|
const $location = Symbol.for('cds.$location');
|
|
@@ -31,13 +32,12 @@ function tweakAssocs( model ) {
|
|
|
31
32
|
const {
|
|
32
33
|
effectiveType,
|
|
33
34
|
getOrigin,
|
|
34
|
-
|
|
35
|
+
navigationEnv,
|
|
35
36
|
} = model.$functions;
|
|
36
|
-
const { environment } = model.$volatileFunctions;
|
|
37
37
|
|
|
38
38
|
// Phase 5: rewrite associations
|
|
39
39
|
model._entities.forEach( rewriteArtifact );
|
|
40
|
-
// Think hard whether an on condition rewrite can lead to a new
|
|
40
|
+
// Think hard whether an on condition rewrite can lead to a new cyclic
|
|
41
41
|
// dependency. If so, we need other messages anyway. TODO: probably do
|
|
42
42
|
// another cyclic check with testMode.js
|
|
43
43
|
return;
|
|
@@ -313,7 +313,8 @@ function tweakAssocs( model ) {
|
|
|
313
313
|
return;
|
|
314
314
|
}
|
|
315
315
|
if (!nav.tableAlias || nav.tableAlias.path) {
|
|
316
|
-
|
|
316
|
+
traverseExpr( cond, 'rewrite-on', elem,
|
|
317
|
+
expr => rewriteExpr( expr, elem, nav.tableAlias ) );
|
|
317
318
|
}
|
|
318
319
|
else {
|
|
319
320
|
// TODO: support that, now that the ON condition is rewritten in the right order
|
|
@@ -452,6 +453,7 @@ function tweakAssocs( model ) {
|
|
|
452
453
|
function rewriteItem( elem, item, name, assoc, forKeys ) {
|
|
453
454
|
// TODO: for rewriting ON conditions of explicitly provided model targets,
|
|
454
455
|
// we need to only rewrite the current element, not all sibling elements
|
|
456
|
+
// TODO: this will be different in v4
|
|
455
457
|
if (!elem._redirected)
|
|
456
458
|
return true;
|
|
457
459
|
for (const alias of elem._redirected) {
|
|
@@ -480,8 +482,8 @@ function tweakAssocs( model ) {
|
|
|
480
482
|
item.id = name;
|
|
481
483
|
}
|
|
482
484
|
}
|
|
483
|
-
const env = name &&
|
|
484
|
-
elem = setArtifactLink( item, env
|
|
485
|
+
const env = name && navigationEnv(elem);
|
|
486
|
+
elem = setArtifactLink( item, env?.elements?.[name] );
|
|
485
487
|
if (elem && !Array.isArray(elem))
|
|
486
488
|
return elem;
|
|
487
489
|
// TODO: better (extra message), TODO: do it
|
package/lib/compiler/utils.js
CHANGED
|
@@ -442,6 +442,47 @@ function isDirectComposition( art ) {
|
|
|
442
442
|
return type && type[0] && type[0].id === 'cds.Composition';
|
|
443
443
|
}
|
|
444
444
|
|
|
445
|
+
function traverseExpr( expr, exprCtx, user, callback ) {
|
|
446
|
+
if (!expr || typeof expr === 'string') // parse error or keywords in {xpr:...}
|
|
447
|
+
return;
|
|
448
|
+
|
|
449
|
+
if (expr.path) {
|
|
450
|
+
callback( expr, exprCtx, user );
|
|
451
|
+
// TODO: move arguments and filter traversal to here
|
|
452
|
+
return;
|
|
453
|
+
}
|
|
454
|
+
else if (expr.type || expr.query) {
|
|
455
|
+
callback( expr, exprCtx, user );
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
if (expr.args) {
|
|
459
|
+
const args = Array.isArray(expr.args) ? expr.args : Object.values( expr.args );
|
|
460
|
+
// TODO: re-think $expected
|
|
461
|
+
args.forEach( e => traverseExpr( e, exprCtx, user, callback ) );
|
|
462
|
+
}
|
|
463
|
+
if (expr.suffix) // fn( … ) OVER …
|
|
464
|
+
expr.suffix.forEach( e => traverseExpr( e, exprCtx, user, callback ) );
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
function userQuery( user ) {
|
|
468
|
+
// TODO: we need _query links set by the definer
|
|
469
|
+
while (user._main) {
|
|
470
|
+
if (user.kind === 'select' || user.kind === '$join')
|
|
471
|
+
return user;
|
|
472
|
+
user = user._parent;
|
|
473
|
+
}
|
|
474
|
+
return null;
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
function definedViaCdl( art ) {
|
|
478
|
+
// return !!art._block?.artifacts;
|
|
479
|
+
// TODO: the above code would work when _block links are correctly set on
|
|
480
|
+
// members of duplicate extensions, see test3/Extensions/DuplicateExtend/. The
|
|
481
|
+
// following is a workaround to make at least ref to builtins work:
|
|
482
|
+
const { $frontend } = art._block || art;
|
|
483
|
+
return $frontend !== 'json' && $frontend !== '$internal';
|
|
484
|
+
}
|
|
485
|
+
|
|
445
486
|
module.exports = {
|
|
446
487
|
pushLink,
|
|
447
488
|
annotationVal,
|
|
@@ -470,4 +511,7 @@ module.exports = {
|
|
|
470
511
|
setExpandStatus,
|
|
471
512
|
setExpandStatusAnnotate,
|
|
472
513
|
isDirectComposition,
|
|
514
|
+
traverseExpr,
|
|
515
|
+
userQuery,
|
|
516
|
+
definedViaCdl,
|
|
473
517
|
};
|
|
@@ -412,13 +412,29 @@ function csn2annotationEdm(reqDefs, csnVocabularies, serviceName,
|
|
|
412
412
|
handleAnnotations(edmTargetName, p, [ ...location, 'params', n ]);
|
|
413
413
|
});
|
|
414
414
|
}
|
|
415
|
+
if(cAction.returns) {
|
|
416
|
+
const edmTargetName = actionName + '/$ReturnType';
|
|
417
|
+
setProp(cAction.returns, '$appliesToReturnType', true);
|
|
418
|
+
handleAnnotations(edmTargetName, cAction.returns, [ ...location, 'returns' ]);
|
|
419
|
+
delete cAction.returns['$appliesToReturnType'];
|
|
420
|
+
}
|
|
415
421
|
|
|
416
422
|
function relParList() {
|
|
417
423
|
// we rely on the order of params in the csn being the correct one
|
|
418
424
|
const params = [];
|
|
419
|
-
if (entityNameIfBound
|
|
420
|
-
|
|
425
|
+
if (entityNameIfBound) {
|
|
426
|
+
// If this is an action and has an explicit binding parameter add it here
|
|
427
|
+
if(cAction.$bindingParam && cAction.kind === 'action') {
|
|
428
|
+
params.push(cAction.$bindingParam.items ? 'Collection(' + entityNameIfBound + ')' : entityNameIfBound);
|
|
429
|
+
}
|
|
430
|
+
// If action/function has no explicit binding parameter add it here
|
|
431
|
+
else if (!cAction.$bindingParam) {
|
|
432
|
+
params.push(cAction['@cds.odata.bindingparameter.collection'] ? 'Collection(' + entityNameIfBound + ')' : entityNameIfBound);
|
|
433
|
+
}
|
|
421
434
|
}
|
|
435
|
+
// In case this is a function the explicit binding parameter is part of
|
|
436
|
+
// the functions params dictionary. Only for functions all parameters must
|
|
437
|
+
// be listed in the annotation target
|
|
422
438
|
if (cAction.kind === 'function') {
|
|
423
439
|
if(cAction.params) {
|
|
424
440
|
cAction.params && Object.values(cAction.params).forEach(p => {
|
|
@@ -454,7 +470,7 @@ function csn2annotationEdm(reqDefs, csnVocabularies, serviceName,
|
|
|
454
470
|
// keep only those annotations that - start with a known vocabulary name
|
|
455
471
|
// - have a value other than null
|
|
456
472
|
|
|
457
|
-
// if the
|
|
473
|
+
// if the carrier is an element that is not rendered or
|
|
458
474
|
// if the carrier is a derived type of a primitive type which is not rendered in V2
|
|
459
475
|
// if the carrier is a media stream element in V2
|
|
460
476
|
// do nothing
|
|
@@ -646,8 +662,10 @@ function csn2annotationEdm(reqDefs, csnVocabularies, serviceName,
|
|
|
646
662
|
carrier.cardinality && carrier.cardinality.max === '*' && x.includes('Collection')
|
|
647
663
|
: true);
|
|
648
664
|
}
|
|
665
|
+
else if(carrier.$appliesToReturnType)
|
|
666
|
+
testToStandardEdmTarget = (x => x ? x.includes('ReturnType') : true);
|
|
649
667
|
else {
|
|
650
|
-
|
|
668
|
+
// this might be more precise if handleAnnotation would know more about the carrier
|
|
651
669
|
testToStandardEdmTarget = (x => x
|
|
652
670
|
? ['Parameter', 'Property'].some(y => x.includes(y) ||
|
|
653
671
|
carrier._isCollection && x.includes('Collection'))
|
|
@@ -1106,11 +1124,11 @@ function csn2annotationEdm(reqDefs, csnVocabularies, serviceName,
|
|
|
1106
1124
|
|
|
1107
1125
|
// found a simple value "val"
|
|
1108
1126
|
// expected type is dTypeName
|
|
1109
|
-
//
|
|
1127
|
+
// mapping rule for values:
|
|
1110
1128
|
// if expected type is ... the expression to be generated is ...
|
|
1111
1129
|
// floating point type except Edm.Decimal -> Float
|
|
1112
1130
|
// Edm.Decimal -> Decimal
|
|
1113
|
-
// integer
|
|
1131
|
+
// integer type -> Int
|
|
1114
1132
|
function handleSimpleValue(value, dTypeName, msg) {
|
|
1115
1133
|
// these types must be represented as "String" values in XML:
|
|
1116
1134
|
const castToXmlString = [ 'Edm.PrimitiveType', 'Edm.Stream', 'Edm.Untyped' ];
|
package/lib/edm/csn2edm.js
CHANGED
|
@@ -51,7 +51,7 @@ function csn2edmAll(_csn, _options, serviceNames=undefined) {
|
|
|
51
51
|
whatsMyServiceRootName,
|
|
52
52
|
fallBackSchemaName,
|
|
53
53
|
options ] = initializeModel(csn, _options, messageFunctions, serviceNames);
|
|
54
|
-
|
|
54
|
+
|
|
55
55
|
const mergedVocabularies = translate.mergeOdataVocabularies(options, message);
|
|
56
56
|
|
|
57
57
|
const Edm = getEdm(options, messageFunctions);
|
|
@@ -404,7 +404,7 @@ function csn2edmAll(_csn, _options, serviceNames=undefined) {
|
|
|
404
404
|
createTerm);
|
|
405
405
|
}
|
|
406
406
|
|
|
407
|
-
// fetch all
|
|
407
|
+
// fetch all existing children names in a map
|
|
408
408
|
const NamesInSchemaXRef = Schema._children.reduce((acc, cur) => {
|
|
409
409
|
const name = cur._edmAttributes.Name;
|
|
410
410
|
if(acc[name] === undefined) {
|
|
@@ -436,7 +436,7 @@ function csn2edmAll(_csn, _options, serviceNames=undefined) {
|
|
|
436
436
|
|
|
437
437
|
The first sentence in chapter 13 is:
|
|
438
438
|
Each metadata document used to describe an OData service MUST define exactly one entity container.
|
|
439
|
-
|
|
439
|
+
|
|
440
440
|
This sentence expresses that an OData SERVICE must contain an entity container, but an EDMX is not required to have a container.
|
|
441
441
|
Therefore it is absolutely legal and necessary to remove an empty container from the IR!
|
|
442
442
|
*/
|
|
@@ -683,18 +683,19 @@ function csn2edmAll(_csn, _options, serviceNames=undefined) {
|
|
|
683
683
|
const bpType = entityCsn ? fullQualified(entityCsn.name) : undefined;
|
|
684
684
|
/*
|
|
685
685
|
Check for binding $self parameter. If available, use this parameter
|
|
686
|
-
instead of
|
|
686
|
+
instead of artificially created binding parameter (hasBindingParameter).
|
|
687
687
|
The binding parameter remains in the CSN and is rendered as any other
|
|
688
688
|
parameter (including default value/not null/ etc) and acts as annotation carrier.
|
|
689
689
|
*/
|
|
690
|
-
|
|
690
|
+
|
|
691
|
+
let bpName = 'in';
|
|
691
692
|
if(actionCsn.params) {
|
|
692
693
|
const entries = Object.entries(actionCsn.params);
|
|
693
694
|
const firstParam = entries[0][1];
|
|
694
695
|
const type = firstParam?.items?.type || firstParam?.type;
|
|
695
696
|
if(type === special$self) {
|
|
696
|
-
|
|
697
|
-
|
|
697
|
+
bpName = entries[0][0];
|
|
698
|
+
setProp(actionCsn, '$bindingParam', firstParam);
|
|
698
699
|
if(bpType) {
|
|
699
700
|
if(firstParam.items?.type)
|
|
700
701
|
firstParam.items.type = bpType;
|
|
@@ -707,16 +708,15 @@ function csn2edmAll(_csn, _options, serviceNames=undefined) {
|
|
|
707
708
|
}
|
|
708
709
|
|
|
709
710
|
// bpName is eventually used later for EntitySetPath
|
|
710
|
-
const bpNameAnno = actionCsn['@cds.odata.bindingparameter.name'];
|
|
711
|
-
let bpName = 'in';
|
|
712
|
-
if(bpNameAnno != null) {
|
|
713
|
-
if(typeof bpNameAnno === 'string')
|
|
714
|
-
bpName = bpNameAnno;
|
|
715
|
-
if(typeof bpNameAnno === 'object' && bpNameAnno['='])
|
|
716
|
-
bpName = bpNameAnno['='];
|
|
717
|
-
}
|
|
718
711
|
// No explicit binding parameter, check (user defined) annotation value)
|
|
719
|
-
if(!actionCsn.$
|
|
712
|
+
if(!actionCsn.$bindingParam) {
|
|
713
|
+
const bpNameAnno = actionCsn['@cds.odata.bindingparameter.name'];
|
|
714
|
+
if(bpNameAnno != null) {
|
|
715
|
+
if(typeof bpNameAnno === 'string')
|
|
716
|
+
bpName = bpNameAnno;
|
|
717
|
+
if(typeof bpNameAnno === 'object' && bpNameAnno['='])
|
|
718
|
+
bpName = bpNameAnno['='];
|
|
719
|
+
}
|
|
720
720
|
if(!edmUtils.isODataSimpleIdentifier(bpName))
|
|
721
721
|
message('odata-spec-violation-id', [...loc, '@cds.odata.bindingparameter.name'], { id: bpName });
|
|
722
722
|
if(actionCsn.params && actionCsn.params[bpName]) {
|
|
@@ -726,7 +726,7 @@ function csn2edmAll(_csn, _options, serviceNames=undefined) {
|
|
|
726
726
|
if(entityCsn != undefined)
|
|
727
727
|
{
|
|
728
728
|
actionNode.setEdmAttribute('IsBound', true);
|
|
729
|
-
if(!actionCsn.$
|
|
729
|
+
if(!actionCsn.$bindingParam) {
|
|
730
730
|
// Binding Parameter: 'in' at first position in sequence, this is decisive!
|
|
731
731
|
if(actionCsn['@cds.odata.bindingparameter.collection'])
|
|
732
732
|
actionNode.append(new Edm.Parameter(v, { Name: bpName, Type: bpType, Collection:true/*, Nullable: false*/ } ));
|
|
@@ -805,8 +805,8 @@ function csn2edmAll(_csn, _options, serviceNames=undefined) {
|
|
|
805
805
|
const rt = actionCsn.returns && ((actionCsn.returns.items && actionCsn.returns.items.type) || actionCsn.returns.type);
|
|
806
806
|
if(rt) // add EntitySet attribute only if return type is an entity
|
|
807
807
|
{
|
|
808
|
-
const
|
|
809
|
-
if(
|
|
808
|
+
const definition = schemaCsn.definitions[rt];
|
|
809
|
+
if(definition && definition.kind === 'entity')
|
|
810
810
|
{
|
|
811
811
|
functionImport.setEdmAttribute('EntitySet', rt.replace(schemaNamePrefix, ''));
|
|
812
812
|
}
|
|
@@ -883,31 +883,34 @@ function csn2edmAll(_csn, _options, serviceNames=undefined) {
|
|
|
883
883
|
// it is safe to assume that either type or items.type are set
|
|
884
884
|
const returnsLoc = [ ...loc, 'returns'];
|
|
885
885
|
const returns = action.returns.items || action.returns;
|
|
886
|
-
let type = returns.
|
|
887
|
-
if
|
|
888
|
-
|
|
889
|
-
if (
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
if(type) {
|
|
895
|
-
|
|
896
|
-
if(
|
|
897
|
-
|
|
886
|
+
let type = returns['@odata.Type'];
|
|
887
|
+
if(!type) {
|
|
888
|
+
type = returns.type;
|
|
889
|
+
if (type) {
|
|
890
|
+
collectUsedType(action.returns);
|
|
891
|
+
if (!isBuiltinType(type) && csn.definitions[type].kind !== 'entity' && csn.definitions[type].kind !== 'type') {
|
|
892
|
+
message('odata-spec-violation-returns', returnsLoc, { kind: action.kind, version: '2.0' });
|
|
893
|
+
}
|
|
894
|
+
else if(isBuiltinType(type)) {
|
|
895
|
+
type = edmUtils.mapCdsToEdmType(returns, messageFunctions, true);
|
|
896
|
+
if(type) {
|
|
897
|
+
const td = EdmPrimitiveTypeMap[type];
|
|
898
|
+
if(td && !td.v2) {
|
|
899
|
+
message('odata-spec-violation-type', returnsLoc,
|
|
898
900
|
{ type, version: '2.0', '#': 'incompatible' });
|
|
901
|
+
}
|
|
902
|
+
}
|
|
903
|
+
else {
|
|
904
|
+
message('odata-spec-violation-type-unknown', returnsLoc, { type });
|
|
899
905
|
}
|
|
900
906
|
}
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
907
|
+
if(action.returns._isCollection)
|
|
908
|
+
type = `Collection(${type})`
|
|
909
|
+
}
|
|
910
|
+
else {
|
|
911
|
+
// type is missing
|
|
912
|
+
message('odata-spec-violation-type', returnsLoc);
|
|
904
913
|
}
|
|
905
|
-
if(action.returns._isCollection)
|
|
906
|
-
type = `Collection(${type})`
|
|
907
|
-
}
|
|
908
|
-
else {
|
|
909
|
-
// type is missing
|
|
910
|
-
message('odata-spec-violation-type', returnsLoc);
|
|
911
914
|
}
|
|
912
915
|
return type;
|
|
913
916
|
}
|
|
@@ -916,7 +919,7 @@ function csn2edmAll(_csn, _options, serviceNames=undefined) {
|
|
|
916
919
|
/*
|
|
917
920
|
addAssociation() constructs a V2 association.
|
|
918
921
|
In V4 all this has been simplified very much, the only thing actually left over is
|
|
919
|
-
<
|
|
922
|
+
<ReferentialConstraint> that is then a sub element to <NavigationProperty>.
|
|
920
923
|
However, referential constraints are substantially different to its V2 counterpart,
|
|
921
924
|
so it is better to reimplement proper V4 construction of<NavigationProperty> in a separate
|
|
922
925
|
function.
|
|
@@ -1086,7 +1089,7 @@ function csn2edmAll(_csn, _options, serviceNames=undefined) {
|
|
|
1086
1089
|
// Only the unknown type warning may be triggered by an unknown @odata.Type override.
|
|
1087
1090
|
if(td.v2 !== p.v2 && td.v4 !== p.v4)
|
|
1088
1091
|
message('odata-spec-violation-type', pLoc,
|
|
1089
|
-
{ type:edmType, version: (p.v4 ? '4.0' : '2.0'), '#': 'incompatible' });
|
|
1092
|
+
{ type: edmType, version: (p.v4 ? '4.0' : '2.0'), '#': 'incompatible' });
|
|
1090
1093
|
EdmTypeFacetNames.forEach(name => {
|
|
1091
1094
|
const facet = EdmTypeFacetMap[name];
|
|
1092
1095
|
const optional =
|
|
@@ -1101,13 +1104,12 @@ function csn2edmAll(_csn, _options, serviceNames=undefined) {
|
|
|
1101
1104
|
// node and facet version match
|
|
1102
1105
|
if(!p._edmAttributes[name] && td[name] && !optional && (p.v2 === facet.v2 || p.v4 === facet.v4)) {
|
|
1103
1106
|
message('odata-spec-violation-type', pLoc,
|
|
1104
|
-
{ type:edmType, name, version: (p.v4 ? '4.0' : '2.0'), '#': 'facet' });
|
|
1107
|
+
{ type: edmType, name, version: (p.v4 ? '4.0' : '2.0'), '#': 'facet' });
|
|
1105
1108
|
}
|
|
1106
1109
|
});
|
|
1107
1110
|
}
|
|
1108
1111
|
else {
|
|
1109
|
-
message('odata-spec-violation-type-unknown', pLoc,
|
|
1110
|
-
{ type:edmType });
|
|
1112
|
+
message('odata-spec-violation-type-unknown', pLoc, { type: edmType });
|
|
1111
1113
|
}
|
|
1112
1114
|
}
|
|
1113
1115
|
}
|
package/lib/edm/edm.js
CHANGED
|
@@ -683,41 +683,44 @@ function getEdm(options, messageFunctions) {
|
|
|
683
683
|
if(!(this._edmAttributes[typeName] === 'Edm.Stream' &&
|
|
684
684
|
!( /*scalarType.type === 'cds.String' ||*/ this._scalarType.type === 'cds.Binary')))
|
|
685
685
|
edmUtils.addTypeFacets(this, this._scalarType);
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
686
|
+
}
|
|
687
|
+
else {
|
|
688
|
+
this._edmAttributes[typeName] = typecsn.type;
|
|
689
|
+
}
|
|
690
|
+
}
|
|
691
|
+
// CDXCORE-245:
|
|
692
|
+
// map type to @odata.Type
|
|
693
|
+
// optionally add @odata { MaxLength, Precision, Scale, SRID }
|
|
694
|
+
// but only in combination with @odata.Type
|
|
695
|
+
// Allow to override type only on scalar and undefined types
|
|
696
|
+
if((this._scalarType || typecsn.type == null) && !csn.elements) {
|
|
697
|
+
const odataType = csn['@odata.Type'];
|
|
698
|
+
if(odataType) {
|
|
699
|
+
const td = EdmPrimitiveTypeMap[odataType];
|
|
700
|
+
// If type is known, it must be available in the current version
|
|
701
|
+
// Reason: EDMX Importer may set `@odata.Type: 'Edm.DateTime'` on imported V2 services
|
|
702
|
+
// Not filtering out this incompatible type here in case of a V4 rendering would
|
|
703
|
+
// produce an unrecoverable error.
|
|
704
|
+
if(td && (td.v2 === this.v2 || td.v4 === this.v4)) {
|
|
705
|
+
this.setEdmAttribute(typeName, odataType);
|
|
706
|
+
EdmTypeFacetNames.forEach(facetName => {
|
|
707
|
+
const facet = EdmTypeFacetMap[facetName];
|
|
708
|
+
if(facet.remove) {
|
|
709
|
+
this.removeEdmAttribute(facetName);
|
|
710
|
+
this.removeEdmAttribute(facet.extra);
|
|
711
|
+
}
|
|
712
|
+
if(td[facetName] !== undefined &&
|
|
706
713
|
(facet.v2 === this.v2 ||
|
|
707
714
|
facet.v4 === this.v4))
|
|
708
715
|
{
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
716
|
+
if(this.v2 && facetName === 'Scale' && csn['@odata.'+facetName] === 'variable')
|
|
717
|
+
this.setXml({ [facet.extra]: true });
|
|
718
|
+
else
|
|
712
719
|
this.setEdmAttribute(facetName, csn['@odata.'+facetName]);
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
}
|
|
720
|
+
}
|
|
721
|
+
});
|
|
716
722
|
}
|
|
717
723
|
}
|
|
718
|
-
else {
|
|
719
|
-
this._edmAttributes[typeName] = typecsn.type;
|
|
720
|
-
}
|
|
721
724
|
}
|
|
722
725
|
}
|
|
723
726
|
|
|
@@ -734,7 +737,7 @@ function getEdm(options, messageFunctions) {
|
|
|
734
737
|
// store undecorated type for JSON
|
|
735
738
|
this._type = this._edmAttributes[typeName];
|
|
736
739
|
// decorate for XML (not for Complex/EntityType)
|
|
737
|
-
if(this._isCollection)
|
|
740
|
+
if(this._isCollection && this._edmAttributes[typeName])
|
|
738
741
|
this._edmAttributes[typeName] = `Collection(${this._edmAttributes[typeName]})`
|
|
739
742
|
}
|
|
740
743
|
|
|
@@ -1127,7 +1130,7 @@ function getEdm(options, messageFunctions) {
|
|
|
1127
1130
|
and if the csn.containsTarget for this NavigationProperty is true,
|
|
1128
1131
|
then this is the generated 'Results' association to the underlying entityType.
|
|
1129
1132
|
Only this special association may have an explicit ContainsTarget attribute.
|
|
1130
|
-
See csn2edm.
|
|
1133
|
+
See csn2edm.createEntityTypeAndSet() for details
|
|
1131
1134
|
2) ContainsTarget stems from the @odata.contained annotation
|
|
1132
1135
|
*/
|
|
1133
1136
|
// eslint-disable-next-line sonarjs/no-redundant-boolean
|
|
@@ -59,7 +59,6 @@ function applyAppSpecificLateCsnTransformationOnElement(options, element, struct
|
|
|
59
59
|
{
|
|
60
60
|
mapAnnotationAssignment(element, struct, AnalyticalAnnotations());
|
|
61
61
|
}
|
|
62
|
-
mapAnnotationAssignment(element, struct, PDMSemantics());
|
|
63
62
|
}
|
|
64
63
|
|
|
65
64
|
// etag requires Core.OptimisticConcurrency to be set in V4 (cap/issues#2641)
|
|
@@ -80,28 +79,6 @@ function applyAppSpecificLateCsnTransformationOnElement(options, element, struct
|
|
|
80
79
|
}
|
|
81
80
|
}
|
|
82
81
|
|
|
83
|
-
// nested functions begin
|
|
84
|
-
function PDMSemantics()
|
|
85
|
-
{
|
|
86
|
-
/*
|
|
87
|
-
let dict = Object.create(null);
|
|
88
|
-
|
|
89
|
-
dict['@PDM.xxx1'] = [ '@sap.pdm-semantics' ];
|
|
90
|
-
dict['@PDM.xxx2'] = [ '@sap.pdm-propery' ];
|
|
91
|
-
dict['@PDM.xxx3'] = [ '@sap.pdm-display-sq-no' ];
|
|
92
|
-
dict['@PDM.xxx4'] = [ '@sap.pdm-record-identifier' ];
|
|
93
|
-
dict['@PDM.xxx5'] = [ '@sap.pdm-field-group' ];
|
|
94
|
-
dict['@PDM.xxx6'] = [ '@sap.pdm-mask-find-pattern' ];
|
|
95
|
-
dict['@PDM.xxx7'] = [ '@sap.pdm-mask-replacement-pattern' ];
|
|
96
|
-
dict['@PDM.xxx8'] = [ '@sap.deletable' ];
|
|
97
|
-
dict['@PDM.xxx8'] = [ '@sap.updatable' ];
|
|
98
|
-
|
|
99
|
-
// respect flattened annotation $value
|
|
100
|
-
Object.keys(dict).forEach(k => dict[k+'.$value'] = dict[k]);
|
|
101
|
-
*/
|
|
102
|
-
return Object.create(null);
|
|
103
|
-
}
|
|
104
|
-
|
|
105
82
|
function AnalyticalAnnotations()
|
|
106
83
|
{
|
|
107
84
|
function mapCommonAttributes(element, struct, prop)
|
|
@@ -24,7 +24,7 @@ function inboundQualificationChecks(csn, options, messageFunctions,
|
|
|
24
24
|
serviceRootNames, requestedServiceNames, isMyServiceRequested, whatsMyServiceRootName, csnUtils) {
|
|
25
25
|
const { message, throwWithError } = messageFunctions;
|
|
26
26
|
|
|
27
|
-
forEachDefinition(csn, [ attach$path,
|
|
27
|
+
forEachDefinition(csn, [ attach$path, checkProperArrayUsage ]);
|
|
28
28
|
checkNestedContextsAndServices();
|
|
29
29
|
throwWithError();
|
|
30
30
|
|
|
@@ -37,7 +37,7 @@ function inboundQualificationChecks(csn, options, messageFunctions,
|
|
|
37
37
|
}, [ 'definitions', defName ]);
|
|
38
38
|
}
|
|
39
39
|
|
|
40
|
-
function
|
|
40
|
+
function checkProperArrayUsage(def, defName) {
|
|
41
41
|
if (!isMyServiceRequested(defName))
|
|
42
42
|
return;
|
|
43
43
|
let currPath = ['definitions', defName];
|
|
@@ -47,6 +47,11 @@ function inboundQualificationChecks(csn, options, messageFunctions,
|
|
|
47
47
|
function checkIfItemsOfItems(construct, _constructName, _prop, path) {
|
|
48
48
|
const constructType = csnUtils.effectiveType(construct);
|
|
49
49
|
if (constructType.items) {
|
|
50
|
+
if(constructType.items.target) {
|
|
51
|
+
const isComp = constructType.items.type === 'cds.Composition';
|
|
52
|
+
message('type-invalid-items', path, { '#': isComp ? 'comp' : 'assoc', prop: 'items' });
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
50
55
|
if (constructType.items.items) {
|
|
51
56
|
message('chained-array-of', path);
|
|
52
57
|
return;
|