@sap/cds-compiler 3.1.2 → 3.4.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 +101 -3
- package/bin/cdsc.js +4 -2
- package/doc/CHANGELOG_BETA.md +35 -0
- package/lib/api/main.js +153 -29
- package/lib/api/validate.js +8 -3
- package/lib/base/dictionaries.js +6 -6
- package/lib/base/error.js +2 -2
- package/lib/base/keywords.js +106 -24
- package/lib/base/message-registry.js +177 -79
- package/lib/base/messages.js +78 -57
- package/lib/base/model.js +2 -1
- package/lib/checks/actionsFunctions.js +1 -1
- package/lib/checks/annotationsOData.js +2 -2
- package/lib/checks/arrayOfs.js +15 -7
- package/lib/checks/cdsPersistence.js +1 -1
- package/lib/checks/checkForTypes.js +53 -0
- package/lib/checks/defaultValues.js +4 -2
- package/lib/checks/elements.js +81 -6
- package/lib/checks/foreignKeys.js +12 -13
- package/lib/checks/invalidTarget.js +10 -11
- package/lib/checks/managedInType.js +21 -15
- package/lib/checks/nullableKeys.js +1 -1
- package/lib/checks/onConditions.js +9 -9
- package/lib/checks/parameters.js +23 -0
- package/lib/checks/queryNoDbArtifacts.js +1 -1
- package/lib/checks/selectItems.js +1 -1
- package/lib/checks/sql-snippets.js +12 -10
- package/lib/checks/types.js +2 -2
- package/lib/checks/utils.js +17 -7
- package/lib/checks/validator.js +36 -14
- package/lib/compiler/assert-consistency.js +21 -13
- package/lib/compiler/builtins.js +8 -0
- package/lib/compiler/checks.js +57 -40
- package/lib/compiler/define.js +139 -69
- package/lib/compiler/extend.js +319 -50
- package/lib/compiler/finalize-parse-cdl.js +14 -9
- package/lib/compiler/kick-start.js +2 -35
- package/lib/compiler/populate.js +111 -68
- package/lib/compiler/propagator.js +5 -3
- package/lib/compiler/resolve.js +71 -108
- package/lib/compiler/shared.js +82 -54
- package/lib/compiler/tweak-assocs.js +26 -14
- package/lib/compiler/utils.js +13 -2
- package/lib/edm/annotations/genericTranslation.js +10 -7
- package/lib/edm/csn2edm.js +11 -11
- package/lib/edm/edm.js +17 -9
- package/lib/edm/edmPreprocessor.js +53 -30
- package/lib/edm/edmUtils.js +7 -2
- package/lib/gen/Dictionary.json +14 -0
- package/lib/gen/language.checksum +1 -1
- package/lib/gen/language.interp +3 -2
- package/lib/gen/languageParser.js +4312 -4186
- package/lib/inspect/inspectModelStatistics.js +1 -1
- package/lib/inspect/inspectPropagation.js +23 -9
- package/lib/json/csnVersion.js +13 -13
- package/lib/json/from-csn.js +161 -172
- package/lib/json/to-csn.js +70 -10
- package/lib/language/.eslintrc.json +4 -0
- package/lib/language/antlrParser.js +8 -11
- package/lib/language/docCommentParser.js +1 -2
- package/lib/language/errorStrategy.js +54 -27
- package/lib/language/genericAntlrParser.js +140 -93
- package/lib/language/language.g4 +57 -33
- package/lib/language/multiLineStringParser.js +75 -63
- package/lib/main.d.ts +3 -6
- package/lib/main.js +1 -0
- package/lib/model/.eslintrc.json +13 -0
- package/lib/model/api.js +4 -2
- package/lib/model/csnRefs.js +78 -50
- package/lib/model/csnUtils.js +272 -222
- package/lib/model/enrichCsn.js +41 -31
- package/lib/model/revealInternalProperties.js +61 -57
- package/lib/model/sortViews.js +35 -31
- package/lib/modelCompare/compare.js +52 -18
- package/lib/modelCompare/filter.js +83 -0
- package/lib/optionProcessor.js +10 -1
- package/lib/render/manageConstraints.js +11 -7
- package/lib/render/toCdl.js +151 -106
- package/lib/render/toHdbcds.js +8 -6
- package/lib/render/toRename.js +4 -4
- package/lib/render/toSql.js +17 -7
- package/lib/render/utils/common.js +27 -9
- package/lib/render/utils/sql.js +5 -5
- package/lib/sql-identifier.js +7 -0
- package/lib/transform/db/applyTransformations.js +32 -3
- package/lib/transform/db/assertUnique.js +27 -38
- package/lib/transform/db/expansion.js +92 -41
- package/lib/transform/db/flattening.js +1 -1
- package/lib/transform/db/temporal.js +3 -1
- package/lib/transform/db/transformExists.js +8 -2
- package/lib/transform/db/views.js +42 -13
- package/lib/transform/draft/db.js +2 -2
- package/lib/transform/forOdataNew.js +10 -7
- package/lib/transform/{forHanaNew.js → forRelationalDB.js} +18 -12
- package/lib/transform/localized.js +29 -20
- package/lib/transform/odata/toFinalBaseType.js +8 -11
- package/lib/transform/odata/typesExposure.js +2 -1
- package/lib/transform/parseExpr.js +245 -0
- package/lib/transform/transformUtilsNew.js +122 -51
- package/lib/transform/translateAssocsToJoins.js +17 -16
- package/lib/utils/moduleResolve.js +5 -5
- package/lib/utils/objectUtils.js +3 -3
- package/lib/utils/term.js +5 -5
- package/package.json +2 -2
- package/share/messages/anno-duplicate-unrelated-layer.md +6 -6
- package/share/messages/check-proper-type-of.md +4 -4
- package/share/messages/check-proper-type.md +2 -2
- package/share/messages/duplicate-autoexposed.md +4 -4
- package/share/messages/extend-repeated-intralayer.md +4 -5
- package/share/messages/extend-unrelated-layer.md +4 -4
- package/share/messages/message-explanations.json +3 -1
- package/share/messages/redirected-to-ambiguous.md +7 -6
- package/share/messages/redirected-to-complex.md +63 -0
- package/share/messages/redirected-to-unrelated.md +6 -5
- package/share/messages/rewrite-not-supported.md +4 -4
- package/share/messages/{syntax-expected-integer.md → syntax-expecting-integer.md} +4 -4
- package/share/messages/wildcard-excluding-one.md +37 -0
|
@@ -98,6 +98,7 @@ function tweakAssocs( model ) {
|
|
|
98
98
|
} );
|
|
99
99
|
}
|
|
100
100
|
else {
|
|
101
|
+
// ID published! Used in stakeholder project; if renamed, add to oldMessageIds
|
|
101
102
|
info( 'assoc-outside-service', [ elem.target.location, elem ],
|
|
102
103
|
{ target },
|
|
103
104
|
'Association target $(TARGET) is outside any service' );
|
|
@@ -283,11 +284,12 @@ function tweakAssocs( model ) {
|
|
|
283
284
|
// managed association as sub element not supported yet
|
|
284
285
|
error( null, [ elem.location, elem ], {},
|
|
285
286
|
// eslint-disable-next-line max-len
|
|
286
|
-
'Rewriting the ON
|
|
287
|
+
'Rewriting the ON-condition of unmanaged association in sub element is not supported' );
|
|
287
288
|
return;
|
|
288
289
|
}
|
|
289
|
-
const nav = (elem._main && elem._main.query
|
|
290
|
-
|
|
290
|
+
const nav = (elem._main && elem._main.query && elem.value)
|
|
291
|
+
? pathNavigation( elem.value ) // redirected source elem or mixin
|
|
292
|
+
: { navigation: assoc }; // redirected user-provided
|
|
291
293
|
const cond = copyExpr( assoc.on,
|
|
292
294
|
// replace location in ON except if from mixin element
|
|
293
295
|
nav.tableAlias && elem.name.location );
|
|
@@ -305,7 +307,7 @@ function tweakAssocs( model ) {
|
|
|
305
307
|
// For "assoc1.assoc2" and "structelem1.assoc2"
|
|
306
308
|
if (elem._redirected !== null) { // null = already reported
|
|
307
309
|
error( 'rewrite-not-supported', [ elem.target.location, elem ], {},
|
|
308
|
-
'The ON
|
|
310
|
+
'The ON-condition is not rewritten here - provide an explicit ON-condition' );
|
|
309
311
|
}
|
|
310
312
|
return;
|
|
311
313
|
}
|
|
@@ -314,7 +316,7 @@ function tweakAssocs( model ) {
|
|
|
314
316
|
}
|
|
315
317
|
else {
|
|
316
318
|
// TODO: support that, now that the ON condition is rewritten in the right order
|
|
317
|
-
error( null, [ elem.value.location, elem ],
|
|
319
|
+
error( null, [ elem.value.location, elem ], {},
|
|
318
320
|
'Selecting unmanaged associations from a sub query is not supported' );
|
|
319
321
|
}
|
|
320
322
|
cond.$inferred = 'rewrite';
|
|
@@ -325,7 +327,6 @@ function tweakAssocs( model ) {
|
|
|
325
327
|
// 'assoc' in query or including entity from ON cond of mixin element /
|
|
326
328
|
// element in included structure / element in source ref/d by table alias.
|
|
327
329
|
|
|
328
|
-
// TODO: re-check args in references, forbid parameter use for the moment
|
|
329
330
|
// TODO: complain about $self (unclear semantics)
|
|
330
331
|
// console.log( info(null, [assoc.name.location, assoc],
|
|
331
332
|
// { art: expr._artifact, names: expr.path.map(i=>i.id) }, 'A').toString(), expr.path)
|
|
@@ -341,13 +342,25 @@ function tweakAssocs( model ) {
|
|
|
341
342
|
// { names: expr.path.map(i=>i.id), art: root }, 'TA').toString())
|
|
342
343
|
if (!root || root._main !== source)
|
|
343
344
|
return; // not $self or source element
|
|
345
|
+
if (expr.scope === 'param' || root.kind === '$parameters')
|
|
346
|
+
return; // are not allowed anyway - there was an error before
|
|
344
347
|
const item = expr.path[root.kind === '$self' ? 1 : 0];
|
|
345
348
|
// console.log('YE', assoc.name, item, root.name, expr.path)
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
assoc.value.location );
|
|
349
|
+
const elem = navProjection( item && tableAlias.elements[item.id], assoc );
|
|
350
|
+
rewritePath( expr, item, assoc, elem, assoc.value.location );
|
|
349
351
|
}
|
|
350
352
|
else if (assoc._main.query) { // from ON cond of mixin element in query
|
|
353
|
+
const root = expr.path[0]._navigation || expr.path[0]._artifact;
|
|
354
|
+
if (expr.scope === 'param' || root?.kind === '$parameters') {
|
|
355
|
+
if (assoc.$errorReported !== 'assoc-unexpected-scope') {
|
|
356
|
+
error( 'assoc-unexpected-scope', [ assoc.value.location, assoc ],
|
|
357
|
+
{ id: assoc.value._artifact.name.id },
|
|
358
|
+
// eslint-disable-next-line max-len
|
|
359
|
+
'Association $(ID) can\'t be projected because its ON-condition refers to a parameter' );
|
|
360
|
+
assoc.$errorReported = 'assoc-unexpected-scope';
|
|
361
|
+
}
|
|
362
|
+
return;
|
|
363
|
+
}
|
|
351
364
|
const nav = pathNavigation( expr );
|
|
352
365
|
if (nav.navigation || nav.tableAlias) { // rewrite src elem, mixin, $self[.elem]
|
|
353
366
|
rewritePath( expr, nav.item, assoc,
|
|
@@ -372,9 +385,9 @@ function tweakAssocs( model ) {
|
|
|
372
385
|
{ art: art && effectiveType( art ) },
|
|
373
386
|
{
|
|
374
387
|
// eslint-disable-next-line max-len
|
|
375
|
-
std: 'This element is not originally referred to in the ON
|
|
388
|
+
std: 'This element is not originally referred to in the ON-condition of association $(ART)',
|
|
376
389
|
// eslint-disable-next-line max-len
|
|
377
|
-
element: 'This element is not originally referred to in the ON
|
|
390
|
+
element: 'This element is not originally referred to in the ON-condition of association $(MEMBER) of $(ART)',
|
|
378
391
|
} );
|
|
379
392
|
}
|
|
380
393
|
rewritePath( expr, item, assoc, (Array.isArray(elem) ? false : elem), null );
|
|
@@ -471,9 +484,8 @@ function tweakAssocs( model ) {
|
|
|
471
484
|
if (elem && !Array.isArray(elem))
|
|
472
485
|
return elem;
|
|
473
486
|
// TODO: better (extra message), TODO: do it
|
|
474
|
-
error( 'query-undefined-element', [ item.location, assoc ],
|
|
475
|
-
|
|
476
|
-
'Element $(ID) has not been found in the elements of the query; please use REDIRECTED TO with an explicit ON condition' );
|
|
487
|
+
error( 'query-undefined-element', [ item.location, assoc ],
|
|
488
|
+
{ id: name || item.id, '#': 'redirected' } );
|
|
477
489
|
return (elem) ? false : null;
|
|
478
490
|
}
|
|
479
491
|
}
|
package/lib/compiler/utils.js
CHANGED
|
@@ -33,7 +33,17 @@ function annotationIsFalse( anno ) { // falsy, but not null (u
|
|
|
33
33
|
}
|
|
34
34
|
function annotationHasEllipsis( anno ) {
|
|
35
35
|
const { val } = anno || {};
|
|
36
|
-
return Array.isArray( val ) && val.
|
|
36
|
+
return Array.isArray( val ) && val.find( v => v.literal === 'token' && v.val === '...' );
|
|
37
|
+
}
|
|
38
|
+
function annotationLocation( anno ) {
|
|
39
|
+
const { name, location } = anno;
|
|
40
|
+
return {
|
|
41
|
+
file: name.location.file,
|
|
42
|
+
line: name.location.line,
|
|
43
|
+
col: name.location.col,
|
|
44
|
+
endLine: location.endLine,
|
|
45
|
+
endCol: location.endCol,
|
|
46
|
+
};
|
|
37
47
|
}
|
|
38
48
|
|
|
39
49
|
/**
|
|
@@ -87,7 +97,7 @@ function linkToOrigin( origin, name, parent, prop, location, silentDep ) {
|
|
|
87
97
|
setMemberParent( elem, name, parent, prop ); // TODO: redef in template
|
|
88
98
|
setLink( elem, '_origin', origin );
|
|
89
99
|
// TODO: should we use silent dependencies also for other things, like
|
|
90
|
-
// included elements? (Currently for $inferred: '
|
|
100
|
+
// included elements? (Currently for $inferred: 'expanded' only)
|
|
91
101
|
if (silentDep)
|
|
92
102
|
dependsOnSilent( elem, origin );
|
|
93
103
|
else
|
|
@@ -395,6 +405,7 @@ module.exports = {
|
|
|
395
405
|
annotationVal,
|
|
396
406
|
annotationIsFalse,
|
|
397
407
|
annotationHasEllipsis,
|
|
408
|
+
annotationLocation,
|
|
398
409
|
annotateWith,
|
|
399
410
|
setLink,
|
|
400
411
|
setArtifactLink,
|
|
@@ -6,7 +6,6 @@ const oDataDictionary = require('../../gen/Dictionary.json');
|
|
|
6
6
|
const { forEachDefinition } = require('../../model/csnUtils');
|
|
7
7
|
const { forEach } = require("../../utils/objectUtils");
|
|
8
8
|
|
|
9
|
-
|
|
10
9
|
/*
|
|
11
10
|
OASIS: https://github.com/oasis-tcs/odata-vocabularies/tree/master/vocabularies
|
|
12
11
|
Aggregation (published)
|
|
@@ -562,6 +561,9 @@ function csn2annotationEdm(csn, serviceName, Edm = undefined, options=undefined,
|
|
|
562
561
|
alternativeEdmTargetName = serviceName + '.EntityContainer/' + alternativeEdmTargetName.substr(lastDotIndex + 1);
|
|
563
562
|
hasAlternativeCarrier = carrier.$hasEntitySet;
|
|
564
563
|
}
|
|
564
|
+
else if(carrier.kind === 'type') {
|
|
565
|
+
testToStandardEdmTarget = (x => x ? x.includes(carrier.elements ? 'ComplexType' : 'TypeDefinition') : true);
|
|
566
|
+
}
|
|
565
567
|
else if (carrier.kind === 'service') {
|
|
566
568
|
// if annotated object is a service, annotation goes to EntityContainer,
|
|
567
569
|
// except if AppliesTo contains Schema but not EntityContainer, then annotation goes to Schema
|
|
@@ -653,14 +655,15 @@ function csn2annotationEdm(csn, serviceName, Edm = undefined, options=undefined,
|
|
|
653
655
|
Insert $value into $edmJson with inner annotation as well.
|
|
654
656
|
*/
|
|
655
657
|
if(innerAnnotation) {
|
|
656
|
-
|
|
658
|
+
// != null => also != undefined
|
|
659
|
+
if(carrier[prefix] != null) {
|
|
657
660
|
const valPrefix = prefix + '.$value';
|
|
658
661
|
carrier[valPrefix] = carrier[prefix];
|
|
659
662
|
delete carrier[prefix];
|
|
660
663
|
rc = true;
|
|
661
664
|
}
|
|
662
665
|
const edmJsonPrefix = prefix + '.$edmJson';
|
|
663
|
-
if(carrier[edmJsonPrefix]) {
|
|
666
|
+
if(carrier[edmJsonPrefix] != null) {
|
|
664
667
|
const valPrefix = prefix + '.$value.$edmJson';
|
|
665
668
|
carrier[valPrefix] = carrier[edmJsonPrefix];
|
|
666
669
|
delete carrier[edmJsonPrefix];
|
|
@@ -699,7 +702,7 @@ function csn2annotationEdm(csn, serviceName, Edm = undefined, options=undefined,
|
|
|
699
702
|
}
|
|
700
703
|
if (innerAnnotation) {
|
|
701
704
|
// A voc annotation has two steps (Namespace+Name),
|
|
702
|
-
// any
|
|
705
|
+
// any further steps need to be rendered separately
|
|
703
706
|
const innerAnnoSteps = innerAnnotation.split('.');
|
|
704
707
|
const tailSteps = innerAnnoSteps.splice(2, innerAnnoSteps.length-2);
|
|
705
708
|
// prepend annotation prefix (path) to tail steps
|
|
@@ -908,7 +911,7 @@ function csn2annotationEdm(csn, serviceName, Edm = undefined, options=undefined,
|
|
|
908
911
|
oTarget.append(handleEdmJson(cAnnoValue['$edmJson'], context));
|
|
909
912
|
}
|
|
910
913
|
else if ( Object.keys(cAnnoValue).filter( x => x[0] !== '@' ).length === 0) {
|
|
911
|
-
// object consists only of properties starting with "@"
|
|
914
|
+
// object consists only of properties starting with "@", no $value
|
|
912
915
|
message(warning, context, 'nested annotations without corresponding base annotation');
|
|
913
916
|
}
|
|
914
917
|
else {
|
|
@@ -1303,7 +1306,7 @@ function csn2annotationEdm(csn, serviceName, Edm = undefined, options=undefined,
|
|
|
1303
1306
|
function handleEdmJson(obj, context, exprDef=undefined) {
|
|
1304
1307
|
|
|
1305
1308
|
let edmNode = undefined;
|
|
1306
|
-
if(obj === undefined)
|
|
1309
|
+
if(obj === undefined || obj === null)
|
|
1307
1310
|
return edmNode;
|
|
1308
1311
|
|
|
1309
1312
|
const dynExprs = edmUtils.intersect(dynamicExpressionNames, Object.keys(obj));
|
|
@@ -1314,7 +1317,7 @@ function csn2annotationEdm(csn, serviceName, Edm = undefined, options=undefined,
|
|
|
1314
1317
|
}
|
|
1315
1318
|
|
|
1316
1319
|
if (dynExprs.length === 0) {
|
|
1317
|
-
if (typeof obj === 'object' &&
|
|
1320
|
+
if (typeof obj === 'object' && !Array.isArray(obj) && Object.keys(obj).length === 1) {
|
|
1318
1321
|
const k = Object.keys(obj)[0];
|
|
1319
1322
|
const val = obj[k];
|
|
1320
1323
|
edmNode = new Edm.ValueThing(v, k[0] === '$' ? k.slice(1) : k, val );
|
package/lib/edm/csn2edm.js
CHANGED
|
@@ -305,9 +305,9 @@ function csn2edmAll(_csn, _options, serviceNames=undefined) {
|
|
|
305
305
|
const reservedNames = ['Edm', 'odata', 'System', 'Transient'];
|
|
306
306
|
const loc = ['definitions', schema.name];
|
|
307
307
|
if(reservedNames.includes(schema.name))
|
|
308
|
-
|
|
308
|
+
message('odata-spec-violation-namespace', loc, { names: reservedNames });
|
|
309
309
|
if (schema.name.length > 511)
|
|
310
|
-
|
|
310
|
+
message('odata-spec-violation-namespace', loc, { '#': 'length' });
|
|
311
311
|
else {
|
|
312
312
|
schema.name.split('.').forEach(id => {
|
|
313
313
|
if (!edmUtils.isODataSimpleIdentifier(id))
|
|
@@ -413,10 +413,10 @@ function csn2edmAll(_csn, _options, serviceNames=undefined) {
|
|
|
413
413
|
const pLoc = [...loc, 'elements', p._edmAttributes.Name];
|
|
414
414
|
edmTypeCompatibilityCheck(p, pLoc);
|
|
415
415
|
if(p._edmAttributes.Name === EntityTypeName)
|
|
416
|
-
|
|
416
|
+
message('odata-spec-violation-property-name', pLoc, { kind: entityCsn.kind });
|
|
417
417
|
|
|
418
418
|
if(options.isV2() && p._isCollection && !p._csn.target)
|
|
419
|
-
|
|
419
|
+
message('odata-spec-violation-array', pLoc, { version: '2.0' });
|
|
420
420
|
|
|
421
421
|
if(!edmUtils.isODataSimpleIdentifier(p._edmAttributes.Name))
|
|
422
422
|
message('odata-spec-violation-id', pLoc, { id: p._edmAttributes.Name });
|
|
@@ -626,10 +626,10 @@ function csn2edmAll(_csn, _options, serviceNames=undefined) {
|
|
|
626
626
|
!param._type.startsWith('Edm.') &&
|
|
627
627
|
csn.definitions[param._type] &&
|
|
628
628
|
!edmUtils.isStructuredType(csn.definitions[param._type]))
|
|
629
|
-
|
|
629
|
+
message('odata-spec-violation-param', pLoc, { version: '2.0' });
|
|
630
630
|
|
|
631
631
|
if(param._isCollection)
|
|
632
|
-
|
|
632
|
+
message('odata-spec-violation-array', pLoc, { version: '2.0' });
|
|
633
633
|
|
|
634
634
|
functionImport.append(param);
|
|
635
635
|
});
|
|
@@ -645,7 +645,7 @@ function csn2edmAll(_csn, _options, serviceNames=undefined) {
|
|
|
645
645
|
let type = returns.type;
|
|
646
646
|
if (type) {
|
|
647
647
|
if (!isBuiltinType(type) && csn.definitions[type].kind !== 'entity' && csn.definitions[type].kind !== 'type') {
|
|
648
|
-
|
|
648
|
+
message('odata-spec-violation-returns', returnsLoc, { kind: action.kind, version: '2.0' });
|
|
649
649
|
}
|
|
650
650
|
else if(isBuiltinType(type)) {
|
|
651
651
|
type = edmUtils.mapCdsToEdmType(returns, messageFunctions, true);
|
|
@@ -731,7 +731,7 @@ function csn2edmAll(_csn, _options, serviceNames=undefined) {
|
|
|
731
731
|
|
|
732
732
|
});
|
|
733
733
|
if(options.isV2()) {
|
|
734
|
-
if(streamProps.length > 1) {
|
|
734
|
+
if(streamProps.length > 1) { // TODO: why not mention 2.0 in text?
|
|
735
735
|
error(null, ['definitions', elementsCsn.name], { names: streamProps, version: '2.0', anno: '@Core.MediaType' },
|
|
736
736
|
`Expected only one element to be annotated with $(ANNO) for OData $(VERSION) but found $(NAMES)`);
|
|
737
737
|
}
|
|
@@ -764,17 +764,17 @@ function csn2edmAll(_csn, _options, serviceNames=undefined) {
|
|
|
764
764
|
const pLoc = [ ...loc, ...(structuredTypeCsn.items ? ['items', 'elements'] : [ 'elements' ]), p._edmAttributes.Name ];
|
|
765
765
|
edmTypeCompatibilityCheck(p, pLoc);
|
|
766
766
|
if(p._edmAttributes.Name === complexType._edmAttributes.Name)
|
|
767
|
-
|
|
767
|
+
message('odata-spec-violation-property-name', pLoc, { kind: structuredTypeCsn.kind });
|
|
768
768
|
|
|
769
769
|
if(!edmUtils.isODataSimpleIdentifier(p._edmAttributes.Name))
|
|
770
770
|
message('odata-spec-violation-id', pLoc, { id: p._edmAttributes.Name });
|
|
771
771
|
|
|
772
772
|
if(options.isV2()) {
|
|
773
773
|
if(p._isCollection && !p._csn.target)
|
|
774
|
-
|
|
774
|
+
message('odata-spec-violation-array', pLoc, { version: '2.0' });
|
|
775
775
|
|
|
776
776
|
if(p._csn.target)
|
|
777
|
-
|
|
777
|
+
message('odata-spec-violation-assoc', pLoc, { version: '2.0' });
|
|
778
778
|
}
|
|
779
779
|
});
|
|
780
780
|
|
package/lib/edm/edm.js
CHANGED
|
@@ -10,7 +10,7 @@ const { isBetaEnabled } = require('../base/model.js');
|
|
|
10
10
|
const EdmTypeFacetMap = {
|
|
11
11
|
'MaxLength': { v2: true, v4: true, remove: true, optional: true },
|
|
12
12
|
'Precision': { v2: true, v4: true, remove: true, optional: true },
|
|
13
|
-
'Scale': { v2: true, v4: true, remove: true, optional: true },
|
|
13
|
+
'Scale': { v2: true, v4: true, remove: true, optional: true, extra: 'sap:variable-scale' },
|
|
14
14
|
'SRID': { v4: true, remove: true, optional: true },
|
|
15
15
|
//'FixedLength': { v2: true },
|
|
16
16
|
//'Collation': { v2: true },
|
|
@@ -116,6 +116,8 @@ function getEdm(options, messageFunctions) {
|
|
|
116
116
|
removeEdmAttribute(name) {
|
|
117
117
|
if (name in this._edmAttributes)
|
|
118
118
|
delete this._edmAttributes[name];
|
|
119
|
+
if (name in this._xmlOnlyAttributes)
|
|
120
|
+
delete this._xmlOnlyAttributes[name];
|
|
119
121
|
}
|
|
120
122
|
|
|
121
123
|
/**
|
|
@@ -695,14 +697,20 @@ function getEdm(options, messageFunctions) {
|
|
|
695
697
|
// produce an unrecoverable error.
|
|
696
698
|
if(td && (td.v2 === this.v2 || td.v4 === this.v4)) {
|
|
697
699
|
this.setEdmAttribute(typeName, odataType);
|
|
698
|
-
EdmTypeFacetNames.forEach(
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
700
|
+
EdmTypeFacetNames.forEach(facetName => {
|
|
701
|
+
const facet = EdmTypeFacetMap[facetName];
|
|
702
|
+
if(facet.remove) {
|
|
703
|
+
this.removeEdmAttribute(facetName);
|
|
704
|
+
this.removeEdmAttribute(facet.extra);
|
|
705
|
+
}
|
|
706
|
+
if(td[facetName] !== undefined &&
|
|
707
|
+
(facet.v2 === this.v2 ||
|
|
708
|
+
facet.v4 === this.v4))
|
|
709
|
+
{
|
|
710
|
+
if(this.v2 && facetName === 'Scale' && csn['@odata.'+facetName] === 'variable')
|
|
711
|
+
this.setXml({ [facet.extra]: true });
|
|
712
|
+
else
|
|
713
|
+
this.setEdmAttribute(facetName, csn['@odata.'+facetName]);
|
|
706
714
|
}
|
|
707
715
|
});
|
|
708
716
|
}
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
/* eslint max-statements-per-line:off */
|
|
3
3
|
const { setProp, isDeprecatedEnabled, isBetaEnabled } = require('../base/model');
|
|
4
|
-
const {
|
|
4
|
+
const {
|
|
5
5
|
forEachDefinition, forEachGeneric, forEachMemberRecursively,
|
|
6
|
-
isEdmPropertyRendered, getUtils, cloneCsnNonDict,
|
|
7
|
-
isBuiltinType, applyTransformations
|
|
6
|
+
isEdmPropertyRendered, getUtils, cloneCsnNonDict,
|
|
7
|
+
isBuiltinType, applyTransformations, cloneAnnotationValue
|
|
8
8
|
} = require('../model/csnUtils');
|
|
9
9
|
const edmUtils = require('./edmUtils.js');
|
|
10
10
|
const edmAnnoPreproc = require('./edmAnnoPreprocessor.js');
|
|
@@ -14,6 +14,12 @@ const expandCSNToFinalBaseType = require('../transform/odata/toFinalBaseType');
|
|
|
14
14
|
|
|
15
15
|
const NavResAnno = '@Capabilities.NavigationRestrictions.RestrictedProperties';
|
|
16
16
|
|
|
17
|
+
// Capabilities that can be pulled up to NavigationRestrictions
|
|
18
|
+
const capabilities = Object.keys(require('../gen/Dictionary.json').
|
|
19
|
+
types['Capabilities.NavigationPropertyRestriction'].Properties).
|
|
20
|
+
filter(c => !['NavigationProperty', 'Navigability'].includes(c)).
|
|
21
|
+
map(c => `@Capabilities.`+c);
|
|
22
|
+
|
|
17
23
|
/**
|
|
18
24
|
* edmPreprocessor warms up the model so that it can be converted into an EDM document and
|
|
19
25
|
* contains all late & application specific model transformations
|
|
@@ -203,7 +209,8 @@ function initializeModel(csn, _options, messageFunctions, requestedServiceNames=
|
|
|
203
209
|
// TODO: work on service basis (this requires post exposure renaming)
|
|
204
210
|
let fallBackSchemaName = 'root';
|
|
205
211
|
let i = 1;
|
|
206
|
-
|
|
212
|
+
const defNames = Object.keys(defs);
|
|
213
|
+
while (defNames.some(artName => {
|
|
207
214
|
const p = artName.split('.');
|
|
208
215
|
return p.length === 2 && p[0] === fallBackSchemaName;
|
|
209
216
|
})) {
|
|
@@ -417,9 +424,11 @@ function initializeModel(csn, _options, messageFunctions, requestedServiceNames=
|
|
|
417
424
|
while(elt && !isBuiltinType(elt.type) && !elt.elements) {
|
|
418
425
|
elt = csn.definitions[elt.type];
|
|
419
426
|
}
|
|
420
|
-
if(elt && elt.elements) {
|
|
427
|
+
if(elt && elt.elements && !elt.$visited) {
|
|
428
|
+
setProp(elt, '$visited', true);
|
|
421
429
|
forEachMemberRecursively(elt, initContainments,
|
|
422
430
|
path, true, { pathWithoutProp: true, elementsOnly: true });
|
|
431
|
+
delete elt.$visited;
|
|
423
432
|
}
|
|
424
433
|
}
|
|
425
434
|
}
|
|
@@ -437,9 +446,11 @@ function initializeModel(csn, _options, messageFunctions, requestedServiceNames=
|
|
|
437
446
|
while(elt && !(isBuiltinType(elt.type) || elt.elements)) {
|
|
438
447
|
elt = csn.definitions[elt.type];
|
|
439
448
|
}
|
|
440
|
-
if(elt && elt.elements) {
|
|
449
|
+
if(elt && elt.elements && !elt.$visited) {
|
|
450
|
+
setProp(elt, '$visited', true);
|
|
441
451
|
forEachMemberRecursively(elt, markToContainer,
|
|
442
452
|
[], true, { elementsOnly: true });
|
|
453
|
+
delete elt.$visited;
|
|
443
454
|
}
|
|
444
455
|
}
|
|
445
456
|
}
|
|
@@ -1492,7 +1503,7 @@ function initializeModel(csn, _options, messageFunctions, requestedServiceNames=
|
|
|
1492
1503
|
and do not render associations (this will include the foreign keys of
|
|
1493
1504
|
the _isToContainer association).
|
|
1494
1505
|
*/
|
|
1495
|
-
function initEdmKeyRefPaths(def) {
|
|
1506
|
+
function initEdmKeyRefPaths(def, defName) {
|
|
1496
1507
|
if(def.$keys) {
|
|
1497
1508
|
setProp(def, '$edmKeyPaths', []);
|
|
1498
1509
|
// for all key elements that shouldn't be ignored produce the paths
|
|
@@ -1503,7 +1514,7 @@ function initializeModel(csn, _options, messageFunctions, requestedServiceNames=
|
|
|
1503
1514
|
// This is structured OData ONLY
|
|
1504
1515
|
// if the foreign keys are explicitly requested, ignore associations and use the flat foreign keys instead
|
|
1505
1516
|
if(!options.renderForeignKeys || (options.renderForeignKeys && !k.target))
|
|
1506
|
-
def.$edmKeyPaths.push(...produceKeyRefPaths(k, kn));
|
|
1517
|
+
def.$edmKeyPaths.push(...produceKeyRefPaths(k, kn, [ 'definitions', defName, 'elements', kn ]));
|
|
1507
1518
|
}
|
|
1508
1519
|
// In v2/v4 flat, associations are never rendered
|
|
1509
1520
|
else if(!k.target) {
|
|
@@ -1525,7 +1536,7 @@ function initializeModel(csn, _options, messageFunctions, requestedServiceNames=
|
|
|
1525
1536
|
navprops to be key ref.
|
|
1526
1537
|
If element is of scalar type, return it as an array.
|
|
1527
1538
|
*/
|
|
1528
|
-
function produceKeyRefPaths(eltCsn, prefix) {
|
|
1539
|
+
function produceKeyRefPaths(eltCsn, prefix, path) {
|
|
1529
1540
|
const keyPaths = [];
|
|
1530
1541
|
// we want to point to the element in the entity which is the first path step
|
|
1531
1542
|
const location = def.$path.concat(['elements']).concat(prefix.split('/')[0]);
|
|
@@ -1537,18 +1548,25 @@ function initializeModel(csn, _options, messageFunctions, requestedServiceNames=
|
|
|
1537
1548
|
}
|
|
1538
1549
|
// OData requires all elements along the path to be nullable: false (that is either key or notNull)
|
|
1539
1550
|
|
|
1540
|
-
const finalType = csnUtils.getFinalTypeDef(eltCsn.items
|
|
1541
|
-
const elements = eltCsn.elements || eltCsn.items
|
|
1542
|
-
|
|
1551
|
+
const finalType = csnUtils.getFinalTypeDef(eltCsn.items?.type || eltCsn.type);
|
|
1552
|
+
const elements = eltCsn.elements || eltCsn.items?.elements ||
|
|
1553
|
+
finalType?.elements || finalType?.items?.elements;
|
|
1543
1554
|
if(elements) {
|
|
1544
1555
|
Object.entries(elements).forEach(([eltName, elt]) => {
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
|
|
1556
|
+
if(!elt.$visited) {
|
|
1557
|
+
setProp(elt, '$visited', true);
|
|
1558
|
+
const newRefs = produceKeyRefPaths(elt, prefix + options.pathDelimiter + eltName, path);
|
|
1559
|
+
if(newRefs.length) {
|
|
1560
|
+
keyPaths.push(...newRefs);
|
|
1548
1561
|
// check path step key for spec violations
|
|
1549
|
-
|
|
1550
|
-
|
|
1562
|
+
const pathSegment = `${prefix}/${eltName}`;
|
|
1563
|
+
checkKeySpecViolations(elt, location, pathSegment);
|
|
1564
|
+
}
|
|
1565
|
+
delete elt.$visited;
|
|
1566
|
+
} else {
|
|
1567
|
+
error('odata-key-recursive', path, { name: prefix });
|
|
1551
1568
|
}
|
|
1569
|
+
|
|
1552
1570
|
});
|
|
1553
1571
|
}
|
|
1554
1572
|
/* If element is a managed association (can't be anything else),
|
|
@@ -1572,7 +1590,7 @@ function initializeModel(csn, _options, messageFunctions, requestedServiceNames=
|
|
|
1572
1590
|
art = art._type || csnUtils.getCsnDef(art.type);
|
|
1573
1591
|
}
|
|
1574
1592
|
}
|
|
1575
|
-
keyPaths.push(...produceKeyRefPaths(art, prefix + options.pathDelimiter + k.ref.join(options.pathDelimiter)));
|
|
1593
|
+
keyPaths.push(...produceKeyRefPaths(art, prefix + options.pathDelimiter + k.ref.join(options.pathDelimiter), path));
|
|
1576
1594
|
});
|
|
1577
1595
|
}
|
|
1578
1596
|
else {
|
|
@@ -1677,7 +1695,11 @@ function initializeModel(csn, _options, messageFunctions, requestedServiceNames=
|
|
|
1677
1695
|
while(elt && !(isBuiltinType(elt.type) || elt.elements)) {
|
|
1678
1696
|
elt = csn.definitions[elt.type];
|
|
1679
1697
|
}
|
|
1680
|
-
elt && elt.elements &&
|
|
1698
|
+
if(elt && elt.elements && !elt.$visited) {
|
|
1699
|
+
setProp(elt, '$visited', true);
|
|
1700
|
+
Object.values(elt.elements).forEach(e => produceTargetPath(newPrefix, e, curDef));
|
|
1701
|
+
delete elt.$visited;
|
|
1702
|
+
}
|
|
1681
1703
|
}
|
|
1682
1704
|
}
|
|
1683
1705
|
}
|
|
@@ -1750,7 +1772,11 @@ function initializeModel(csn, _options, messageFunctions, requestedServiceNames=
|
|
|
1750
1772
|
while(elt && !(isBuiltinType(elt.type) || elt.elements)) {
|
|
1751
1773
|
elt = csn.definitions[elt.type];
|
|
1752
1774
|
}
|
|
1753
|
-
elt && elt.elements &&
|
|
1775
|
+
if(elt && elt.elements && !elt.$visited) {
|
|
1776
|
+
setProp(elt, '$visited', true);
|
|
1777
|
+
Object.values(elt.elements).forEach(e => npbs = npbs.concat(produceNavigationPath(e, curDef)));
|
|
1778
|
+
delete elt.$visited;
|
|
1779
|
+
}
|
|
1754
1780
|
}
|
|
1755
1781
|
}
|
|
1756
1782
|
npbs.forEach(p => p.Path = prefix + '/' + p.Path );
|
|
@@ -1832,7 +1858,7 @@ function initializeModel(csn, _options, messageFunctions, requestedServiceNames=
|
|
|
1832
1858
|
if(container.$containeeAssociations) {
|
|
1833
1859
|
// copy or create container restrictions, don't modify original
|
|
1834
1860
|
const localRestrictions = container[NavResAnno] ?
|
|
1835
|
-
|
|
1861
|
+
cloneAnnotationValue(container[NavResAnno]) : []
|
|
1836
1862
|
|
|
1837
1863
|
// prefix the existing navigation property restritictions on the container
|
|
1838
1864
|
if(prefix.length) {
|
|
@@ -1867,13 +1893,10 @@ function initializeModel(csn, _options, messageFunctions, requestedServiceNames=
|
|
|
1867
1893
|
|
|
1868
1894
|
const props = Object.entries(containee);
|
|
1869
1895
|
let newEntry = false;
|
|
1870
|
-
|
|
1871
|
-
|
|
1872
|
-
|
|
1873
|
-
|
|
1874
|
-
if(edmUtils.mergeIntoNavPropEntry(c, navPropEntry, prefix.concat(path), props))
|
|
1875
|
-
newEntry = true;
|
|
1876
|
-
});
|
|
1896
|
+
capabilities.forEach(c => {
|
|
1897
|
+
if(edmUtils.mergeIntoNavPropEntry(c, navPropEntry, prefix.concat(path), props))
|
|
1898
|
+
newEntry = true;
|
|
1899
|
+
});
|
|
1877
1900
|
|
|
1878
1901
|
if(newEntry && !hasEntry) {
|
|
1879
1902
|
localRestrictions.push(navPropEntry);
|
|
@@ -1995,12 +2018,12 @@ function initializeModel(csn, _options, messageFunctions, requestedServiceNames=
|
|
|
1995
2018
|
if (obj.type && isBuiltinType(obj.type) && !obj.target && !obj.targetAspect) {
|
|
1996
2019
|
let edmType = edmUtils.mapCdsToEdmType(obj, messageFunctions, _options.odataVersion === 'v2', obj['@Core.MediaType']);
|
|
1997
2020
|
edmUtils.assignProp(obj, '_edmType', edmType);
|
|
1998
|
-
} else if (obj._isCollection && (obj.items && isBuiltinType(csnUtils.
|
|
2021
|
+
} else if (obj._isCollection && (obj.items && isBuiltinType(csnUtils.getFinalBaseTypeWithProps(obj.items.type)?.type))) {
|
|
1999
2022
|
let edmType = edmUtils.mapCdsToEdmType(obj.items, messageFunctions, _options.odataVersion === 'v2', obj['@Core.MediaType'], obj.$path);
|
|
2000
2023
|
edmUtils.assignProp(obj, '_edmType', edmType);
|
|
2001
2024
|
}
|
|
2002
2025
|
// This is the special case when we have array of array, but will not be supported in the future
|
|
2003
|
-
else if (obj._isCollection && obj.items && obj.items.type && obj.items.items && isBuiltinType(csnUtils.
|
|
2026
|
+
else if (obj._isCollection && obj.items && obj.items.type && obj.items.items && isBuiltinType(csnUtils.getFinalBaseTypeWithProps(obj.items.items.type)?.type)) {
|
|
2004
2027
|
let edmType = edmUtils.mapCdsToEdmType(obj.items.items, messageFunctions, _options.odataVersion === 'v2', obj['@Core.MediaType']);
|
|
2005
2028
|
edmUtils.assignProp(obj, '_edmType', edmType);
|
|
2006
2029
|
}
|
package/lib/edm/edmUtils.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
const { setProp } = require('../base/model');
|
|
3
|
-
const { isBuiltinType, isEdmPropertyRendered, applyTransformations } = require('../model/csnUtils');
|
|
3
|
+
const { isBuiltinType, isEdmPropertyRendered, applyTransformations, cloneAnnotationValue } = require('../model/csnUtils');
|
|
4
4
|
const { escapeString, hasControlCharacters, hasUnpairedUnicodeSurrogate } = require("../render/utils/stringEscapes");
|
|
5
5
|
|
|
6
6
|
/* eslint max-statements-per-line:off */
|
|
@@ -508,6 +508,10 @@ function mapCdsToEdmType(csn, messageFunctions, isV2=false, isMediaType=false, l
|
|
|
508
508
|
'cds.hana.SMALLDECIMAL': 'Edm.Decimal', // V4: Scale="floating" Precision="16"
|
|
509
509
|
'cds.Integer64': 'Edm.Int64',
|
|
510
510
|
'cds.Integer': 'Edm.Int32',
|
|
511
|
+
'cds.Int64': 'Edm.Int64',
|
|
512
|
+
'cds.Int32': 'Edm.Int32',
|
|
513
|
+
'cds.Int16': 'Edm.Int16',
|
|
514
|
+
'cds.UInt8': 'Edm.Byte',
|
|
511
515
|
'cds.hana.SMALLINT': 'Edm.Int16',
|
|
512
516
|
'cds.hana.TINYINT': 'Edm.Byte',
|
|
513
517
|
'cds.Double': 'Edm.Double',
|
|
@@ -758,7 +762,8 @@ function mergeIntoNavPropEntry(annoPrefix, navPropEntry, prefix, props) {
|
|
|
758
762
|
|
|
759
763
|
// Filter properties with prefix and reduce them into a new dictionary
|
|
760
764
|
const o = props.filter(p => p[0].startsWith(annoPrefix+'.')).reduce((a,c) => {
|
|
761
|
-
|
|
765
|
+
// clone the annotation value to avoid side effects with rewritten paths
|
|
766
|
+
a[c[0].replace(annoPrefix+'.', '')] = cloneAnnotationValue(c[1]);
|
|
762
767
|
return a;
|
|
763
768
|
}, { });
|
|
764
769
|
|
package/lib/gen/Dictionary.json
CHANGED
|
@@ -1808,6 +1808,13 @@
|
|
|
1808
1808
|
"Parameter"
|
|
1809
1809
|
]
|
|
1810
1810
|
},
|
|
1811
|
+
"UI.IsCopyAction": {
|
|
1812
|
+
"Type": "Core.Tag",
|
|
1813
|
+
"AppliesTo": [
|
|
1814
|
+
"Record"
|
|
1815
|
+
],
|
|
1816
|
+
"$experimental": true
|
|
1817
|
+
},
|
|
1811
1818
|
"UI.CreateHidden": {
|
|
1812
1819
|
"Type": "Core.Tag",
|
|
1813
1820
|
"AppliesTo": [
|
|
@@ -1897,6 +1904,13 @@
|
|
|
1897
1904
|
],
|
|
1898
1905
|
"$experimental": true
|
|
1899
1906
|
},
|
|
1907
|
+
"UI.LeadingEntitySet": {
|
|
1908
|
+
"Type": "Edm.String",
|
|
1909
|
+
"AppliesTo": [
|
|
1910
|
+
"EntityContainer"
|
|
1911
|
+
],
|
|
1912
|
+
"$experimental": true
|
|
1913
|
+
},
|
|
1900
1914
|
"Validation.Pattern": {
|
|
1901
1915
|
"Type": "Edm.String",
|
|
1902
1916
|
"AppliesTo": [
|
|
@@ -1 +1 @@
|
|
|
1
|
-
|
|
1
|
+
5a5c0432924af9c8833ad5aa6b94724f
|