@sap/cds-compiler 3.1.2 → 3.3.2
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 +80 -3
- package/bin/cdsc.js +1 -1
- package/doc/CHANGELOG_BETA.md +18 -0
- package/lib/api/main.js +8 -13
- package/lib/base/error.js +2 -2
- package/lib/base/keywords.js +2 -24
- package/lib/base/message-registry.js +43 -14
- package/lib/base/messages.js +20 -10
- package/lib/base/model.js +1 -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 +48 -0
- package/lib/checks/defaultValues.js +2 -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 +21 -0
- package/lib/checks/selectItems.js +1 -1
- package/lib/checks/types.js +2 -2
- package/lib/checks/utils.js +17 -7
- package/lib/checks/validator.js +26 -14
- package/lib/compiler/assert-consistency.js +13 -6
- package/lib/compiler/builtins.js +8 -0
- package/lib/compiler/checks.js +40 -33
- package/lib/compiler/define.js +50 -44
- package/lib/compiler/extend.js +303 -37
- package/lib/compiler/kick-start.js +2 -35
- package/lib/compiler/populate.js +83 -62
- package/lib/compiler/propagator.js +1 -1
- package/lib/compiler/resolve.js +61 -104
- package/lib/compiler/shared.js +16 -6
- package/lib/compiler/tweak-assocs.js +25 -12
- package/lib/compiler/utils.js +2 -2
- package/lib/edm/annotations/genericTranslation.js +3 -3
- package/lib/edm/csn2edm.js +10 -10
- 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 +4205 -4100
- package/lib/inspect/inspectModelStatistics.js +1 -1
- package/lib/inspect/inspectPropagation.js +23 -9
- package/lib/json/csnVersion.js +1 -1
- package/lib/json/from-csn.js +26 -19
- package/lib/json/to-csn.js +47 -5
- package/lib/language/antlrParser.js +1 -1
- package/lib/language/genericAntlrParser.js +29 -13
- package/lib/language/language.g4 +28 -8
- package/lib/main.d.ts +3 -6
- package/lib/model/.eslintrc.json +13 -0
- package/lib/model/api.js +4 -2
- package/lib/model/csnRefs.js +74 -47
- package/lib/model/csnUtils.js +236 -218
- package/lib/model/enrichCsn.js +41 -31
- package/lib/model/revealInternalProperties.js +61 -57
- package/lib/model/sortViews.js +31 -31
- package/lib/modelCompare/compare.js +6 -6
- package/lib/optionProcessor.js +5 -0
- package/lib/render/manageConstraints.js +2 -2
- package/lib/render/toCdl.js +31 -44
- package/lib/render/toHdbcds.js +7 -5
- package/lib/render/toRename.js +4 -4
- package/lib/render/toSql.js +11 -5
- package/lib/render/utils/common.js +20 -9
- package/lib/render/utils/sql.js +5 -5
- package/lib/transform/db/applyTransformations.js +32 -3
- package/lib/transform/db/expansion.js +81 -37
- package/lib/transform/db/flattening.js +1 -1
- package/lib/transform/db/temporal.js +1 -1
- package/lib/transform/db/transformExists.js +1 -1
- package/lib/transform/forOdataNew.js +10 -7
- package/lib/transform/{forHanaNew.js → forRelationalDB.js} +7 -7
- package/lib/transform/localized.js +28 -19
- package/lib/transform/odata/toFinalBaseType.js +8 -11
- package/lib/transform/odata/typesExposure.js +1 -1
- package/lib/transform/transformUtilsNew.js +101 -39
- package/lib/transform/translateAssocsToJoins.js +5 -4
- package/lib/utils/moduleResolve.js +5 -5
- package/lib/utils/objectUtils.js +3 -3
- 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 +3 -3
- package/share/messages/wildcard-excluding-one.md +37 -0
package/lib/compiler/shared.js
CHANGED
|
@@ -534,16 +534,21 @@ function fns( model ) {
|
|
|
534
534
|
// if the artifact does not exist. Return a "fresh" artifact for
|
|
535
535
|
// non-existing external using references if `unchecked` is truthy.
|
|
536
536
|
function getPathRoot( path, spec, user, env, extDict, msgArt ) {
|
|
537
|
-
|
|
538
|
-
|
|
537
|
+
const head = path[0];
|
|
538
|
+
if (!head || !head.id || !env)
|
|
539
|
+
return undefined; // parse error
|
|
540
|
+
if (!spec.envFn && user._pathHead && head.id.charAt(0) !== '$') {
|
|
541
|
+
if (spec.rootEnv === 'elements') { // ON condition in expand/inline
|
|
542
|
+
let root = user._pathHead;
|
|
543
|
+
while (root.kind === '$inline')
|
|
544
|
+
root = root._parent;
|
|
545
|
+
return root;
|
|
546
|
+
}
|
|
539
547
|
VolatileFns.environment( user._pathHead ); // make sure _origin is set
|
|
540
548
|
return user._pathHead._origin;
|
|
541
549
|
// const { _origin } = user._pathHead;
|
|
542
550
|
// return (_origin && _origin.kind === '$tableAlias') ? _origin._origin : _origin;
|
|
543
551
|
}
|
|
544
|
-
const head = path[0];
|
|
545
|
-
if (!head || !head.id || !env)
|
|
546
|
-
return undefined; // parse error
|
|
547
552
|
// if (head.id === 'k') {console.log(Object.keys(user));throw Error(JSON.stringify(user.name))}
|
|
548
553
|
// if head._artifact is set or is null then it was already computed once
|
|
549
554
|
if ('_artifact' in head)
|
|
@@ -576,7 +581,7 @@ function fns( model ) {
|
|
|
576
581
|
message( 'ref-obsolete-parameters', [ head.location, user ],
|
|
577
582
|
{ code: `$parameters.${ path[1].id }`, newcode: `:${ path[1].id }` },
|
|
578
583
|
'Obsolete $(CODE) - replace by $(NEWCODE)' );
|
|
579
|
-
// TODO: replace it in to-csn correspondingly
|
|
584
|
+
// TODO: replace it in to-csn correspondingly !!!
|
|
580
585
|
return setArtifactLink( head, r );
|
|
581
586
|
}
|
|
582
587
|
}
|
|
@@ -685,6 +690,9 @@ function fns( model ) {
|
|
|
685
690
|
// search environment (for the first path item) is `arg`. For messages about
|
|
686
691
|
// missing artifacts (as opposed to elements), provide the `head` (first
|
|
687
692
|
// element item in the path)
|
|
693
|
+
// TODO - think about setting _navigation for all $navElement – the
|
|
694
|
+
// "ref: ['tabAlias']: inline: […]" handling might be easier
|
|
695
|
+
// (no _pathHead consultation for key prop and renaming support)
|
|
688
696
|
function getPathItem( path, spec, user, artItemsCount, headArt ) {
|
|
689
697
|
// let art = (headArt && headArt.kind === '$tableAlias') ? headArt._origin : headArt;
|
|
690
698
|
let art = headArt;
|
|
@@ -889,6 +897,8 @@ function fns( model ) {
|
|
|
889
897
|
// (TODO: really here?, probably split main artifacts vs returns)
|
|
890
898
|
// see also lateExtensions() where similar messages are reported
|
|
891
899
|
function checkAnnotate( construct, art ) {
|
|
900
|
+
// TODO: Handle extend statements properly: Different message for empty extend?
|
|
901
|
+
|
|
892
902
|
// Namespaces cannot be annotated in CSN but because they exist as XSN artifacts
|
|
893
903
|
// they can still be applied. Namespace annotations are extracted in to-csn.js
|
|
894
904
|
// In parseCdl mode USINGs and other unknown references are generated as
|
|
@@ -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 );
|
|
@@ -473,7 +486,7 @@ function tweakAssocs( model ) {
|
|
|
473
486
|
// TODO: better (extra message), TODO: do it
|
|
474
487
|
error( 'query-undefined-element', [ item.location, assoc ], { id: name || item.id },
|
|
475
488
|
// eslint-disable-next-line max-len
|
|
476
|
-
'Element $(ID) has not been found in the elements of the query; please use REDIRECTED TO with an explicit ON
|
|
489
|
+
'Element $(ID) has not been found in the elements of the query; please use REDIRECTED TO with an explicit ON-condition' );
|
|
477
490
|
return (elem) ? false : null;
|
|
478
491
|
}
|
|
479
492
|
}
|
package/lib/compiler/utils.js
CHANGED
|
@@ -33,7 +33,7 @@ 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
37
|
}
|
|
38
38
|
|
|
39
39
|
/**
|
|
@@ -87,7 +87,7 @@ function linkToOrigin( origin, name, parent, prop, location, silentDep ) {
|
|
|
87
87
|
setMemberParent( elem, name, parent, prop ); // TODO: redef in template
|
|
88
88
|
setLink( elem, '_origin', origin );
|
|
89
89
|
// TODO: should we use silent dependencies also for other things, like
|
|
90
|
-
// included elements? (Currently for $inferred: '
|
|
90
|
+
// included elements? (Currently for $inferred: 'expanded' only)
|
|
91
91
|
if (silentDep)
|
|
92
92
|
dependsOnSilent( elem, origin );
|
|
93
93
|
else
|
|
@@ -699,7 +699,7 @@ function csn2annotationEdm(csn, serviceName, Edm = undefined, options=undefined,
|
|
|
699
699
|
}
|
|
700
700
|
if (innerAnnotation) {
|
|
701
701
|
// A voc annotation has two steps (Namespace+Name),
|
|
702
|
-
// any
|
|
702
|
+
// any further steps need to be rendered separately
|
|
703
703
|
const innerAnnoSteps = innerAnnotation.split('.');
|
|
704
704
|
const tailSteps = innerAnnoSteps.splice(2, innerAnnoSteps.length-2);
|
|
705
705
|
// prepend annotation prefix (path) to tail steps
|
|
@@ -1303,7 +1303,7 @@ function csn2annotationEdm(csn, serviceName, Edm = undefined, options=undefined,
|
|
|
1303
1303
|
function handleEdmJson(obj, context, exprDef=undefined) {
|
|
1304
1304
|
|
|
1305
1305
|
let edmNode = undefined;
|
|
1306
|
-
if(obj === undefined)
|
|
1306
|
+
if(obj === undefined || obj === null)
|
|
1307
1307
|
return edmNode;
|
|
1308
1308
|
|
|
1309
1309
|
const dynExprs = edmUtils.intersect(dynamicExpressionNames, Object.keys(obj));
|
|
@@ -1314,7 +1314,7 @@ function csn2annotationEdm(csn, serviceName, Edm = undefined, options=undefined,
|
|
|
1314
1314
|
}
|
|
1315
1315
|
|
|
1316
1316
|
if (dynExprs.length === 0) {
|
|
1317
|
-
if (typeof obj === 'object' &&
|
|
1317
|
+
if (typeof obj === 'object' && !Array.isArray(obj) && Object.keys(obj).length === 1) {
|
|
1318
1318
|
const k = Object.keys(obj)[0];
|
|
1319
1319
|
const val = obj[k];
|
|
1320
1320
|
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);
|
|
@@ -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
|
+
d054015b5c1bda3f92422cb3f44733bb
|