@sap/cds-compiler 3.1.0 → 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 +90 -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 +15 -5
- package/lib/edm/csn2edm.js +10 -10
- package/lib/edm/edm.js +17 -9
- package/lib/edm/edmPreprocessor.js +82 -42
- package/lib/edm/edmUtils.js +18 -16
- 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
|
|
@@ -401,8 +401,6 @@ function csn2annotationEdm(csn, serviceName, Edm = undefined, options=undefined,
|
|
|
401
401
|
params.push(action['@cds.odata.bindingparameter.collection'] ? 'Collection(' + bindingParam + ')' : bindingParam);
|
|
402
402
|
}
|
|
403
403
|
if (action.kind === 'function') {
|
|
404
|
-
let mapType = (p) => (isBuiltinType(p.type)) ?
|
|
405
|
-
edmUtils.mapCdsToEdmType(p, messageFunctions, false /*is only called for v4*/) : p.type;
|
|
406
404
|
if(action.params) {
|
|
407
405
|
action.params && Object.values(action.params).forEach(p => {
|
|
408
406
|
let isArrayType = !p.type && p.items && p.items.type;
|
|
@@ -411,6 +409,18 @@ function csn2annotationEdm(csn, serviceName, Edm = undefined, options=undefined,
|
|
|
411
409
|
}
|
|
412
410
|
}
|
|
413
411
|
return '(' + params.join(',') + ')';
|
|
412
|
+
|
|
413
|
+
function mapType(p) {
|
|
414
|
+
if(isBuiltinType(p.type))
|
|
415
|
+
return edmUtils.mapCdsToEdmType(p, messageFunctions, false /*is only called for v4*/)
|
|
416
|
+
else if(options.whatsMySchemaName) {
|
|
417
|
+
const schemaName = options.whatsMySchemaName(p.type);
|
|
418
|
+
// strip the service namespace of from a parameter type
|
|
419
|
+
if(schemaName && schemaName !== options.serviceName)
|
|
420
|
+
return p.type.replace(options.serviceName + '.', '');
|
|
421
|
+
}
|
|
422
|
+
return p.type;
|
|
423
|
+
}
|
|
414
424
|
}
|
|
415
425
|
|
|
416
426
|
|
|
@@ -689,7 +699,7 @@ function csn2annotationEdm(csn, serviceName, Edm = undefined, options=undefined,
|
|
|
689
699
|
}
|
|
690
700
|
if (innerAnnotation) {
|
|
691
701
|
// A voc annotation has two steps (Namespace+Name),
|
|
692
|
-
// any
|
|
702
|
+
// any further steps need to be rendered separately
|
|
693
703
|
const innerAnnoSteps = innerAnnotation.split('.');
|
|
694
704
|
const tailSteps = innerAnnoSteps.splice(2, innerAnnoSteps.length-2);
|
|
695
705
|
// prepend annotation prefix (path) to tail steps
|
|
@@ -1293,7 +1303,7 @@ function csn2annotationEdm(csn, serviceName, Edm = undefined, options=undefined,
|
|
|
1293
1303
|
function handleEdmJson(obj, context, exprDef=undefined) {
|
|
1294
1304
|
|
|
1295
1305
|
let edmNode = undefined;
|
|
1296
|
-
if(obj === undefined)
|
|
1306
|
+
if(obj === undefined || obj === null)
|
|
1297
1307
|
return edmNode;
|
|
1298
1308
|
|
|
1299
1309
|
const dynExprs = edmUtils.intersect(dynamicExpressionNames, Object.keys(obj));
|
|
@@ -1304,7 +1314,7 @@ function csn2annotationEdm(csn, serviceName, Edm = undefined, options=undefined,
|
|
|
1304
1314
|
}
|
|
1305
1315
|
|
|
1306
1316
|
if (dynExprs.length === 0) {
|
|
1307
|
-
if (typeof obj === 'object' &&
|
|
1317
|
+
if (typeof obj === 'object' && !Array.isArray(obj) && Object.keys(obj).length === 1) {
|
|
1308
1318
|
const k = Object.keys(obj)[0];
|
|
1309
1319
|
const val = obj[k];
|
|
1310
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,
|
|
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,22 +1858,49 @@ 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
|
-
|
|
1836
|
-
|
|
1861
|
+
cloneAnnotationValue(container[NavResAnno]) : []
|
|
1862
|
+
|
|
1863
|
+
// prefix the existing navigation property restritictions on the container
|
|
1864
|
+
if(prefix.length) {
|
|
1865
|
+
localRestrictions.forEach(npe => {
|
|
1866
|
+
if(npe.NavigationProperty &&
|
|
1867
|
+
npe.NavigationProperty['='] &&
|
|
1868
|
+
typeof npe.NavigationProperty['='] === 'string') {
|
|
1869
|
+
applyTransformations({ definitions: { npe }}, {
|
|
1870
|
+
"=": (parent, prop, value) => {
|
|
1871
|
+
parent[prop] = prefix.concat(value).join('.');
|
|
1872
|
+
}
|
|
1873
|
+
});
|
|
1874
|
+
}
|
|
1875
|
+
});
|
|
1876
|
+
}
|
|
1877
|
+
|
|
1837
1878
|
setProp(container, '$visited', true);
|
|
1879
|
+
// collect capabilities from containees
|
|
1838
1880
|
container.$containeeAssociations.forEach(entry => {
|
|
1839
1881
|
const { assoc, path } = entry;
|
|
1840
1882
|
const containee = assoc._target;
|
|
1841
1883
|
|
|
1842
1884
|
if(isMyServiceRequested(containee.name) || containee.$proxy) {
|
|
1843
1885
|
const localAssocPath = path.join('.');
|
|
1886
|
+
let navPropEntry;
|
|
1887
|
+
const hasEntry = !!(navPropEntry = localRestrictions.find(p =>
|
|
1888
|
+
p.NavigationProperty && p.NavigationProperty['='] === prefix.concat(localAssocPath).join('.')));
|
|
1889
|
+
|
|
1890
|
+
if(!hasEntry) {
|
|
1891
|
+
navPropEntry = { NavigationProperty: { '=': prefix.concat(localAssocPath).join('.') } };
|
|
1892
|
+
}
|
|
1893
|
+
|
|
1844
1894
|
const props = Object.entries(containee);
|
|
1845
|
-
|
|
1846
|
-
|
|
1847
|
-
|
|
1848
|
-
|
|
1849
|
-
|
|
1895
|
+
let newEntry = false;
|
|
1896
|
+
capabilities.forEach(c => {
|
|
1897
|
+
if(edmUtils.mergeIntoNavPropEntry(c, navPropEntry, prefix.concat(path), props))
|
|
1898
|
+
newEntry = true;
|
|
1899
|
+
});
|
|
1850
1900
|
|
|
1901
|
+
if(newEntry && !hasEntry) {
|
|
1902
|
+
localRestrictions.push(navPropEntry);
|
|
1903
|
+
}
|
|
1851
1904
|
|
|
1852
1905
|
if(!containee.$visited) {
|
|
1853
1906
|
addContainmentAnnotationsRecursively(prefix.concat(path), containee);
|
|
@@ -1855,19 +1908,6 @@ function initializeModel(csn, _options, messageFunctions, requestedServiceNames=
|
|
|
1855
1908
|
}
|
|
1856
1909
|
});
|
|
1857
1910
|
|
|
1858
|
-
// prefix all paths in the navPropEntry with the NavigationPropertyPath
|
|
1859
|
-
localRestrictions.forEach((p, i) => {
|
|
1860
|
-
if(p.NavigationProperty && p.NavigationProperty['='] && typeof p.NavigationProperty['='] === 'string') {
|
|
1861
|
-
const lp = [ ...prefix, p.NavigationProperty['=']].join('.');
|
|
1862
|
-
applyTransformationsOnNonDictionary(localRestrictions, i, {
|
|
1863
|
-
"=": (parent, prop, value) => {
|
|
1864
|
-
parent[prop] = [lp, value].join('.');
|
|
1865
|
-
}
|
|
1866
|
-
});
|
|
1867
|
-
// reset NavigationPropertyPath
|
|
1868
|
-
p.NavigationProperty['='] = lp;
|
|
1869
|
-
}
|
|
1870
|
-
});
|
|
1871
1911
|
rootRestrictions.unshift(...localRestrictions);
|
|
1872
1912
|
delete container.$visited;
|
|
1873
1913
|
}
|
|
@@ -1978,12 +2018,12 @@ function initializeModel(csn, _options, messageFunctions, requestedServiceNames=
|
|
|
1978
2018
|
if (obj.type && isBuiltinType(obj.type) && !obj.target && !obj.targetAspect) {
|
|
1979
2019
|
let edmType = edmUtils.mapCdsToEdmType(obj, messageFunctions, _options.odataVersion === 'v2', obj['@Core.MediaType']);
|
|
1980
2020
|
edmUtils.assignProp(obj, '_edmType', edmType);
|
|
1981
|
-
} else if (obj._isCollection && (obj.items && isBuiltinType(csnUtils.
|
|
2021
|
+
} else if (obj._isCollection && (obj.items && isBuiltinType(csnUtils.getFinalBaseTypeWithProps(obj.items.type)?.type))) {
|
|
1982
2022
|
let edmType = edmUtils.mapCdsToEdmType(obj.items, messageFunctions, _options.odataVersion === 'v2', obj['@Core.MediaType'], obj.$path);
|
|
1983
2023
|
edmUtils.assignProp(obj, '_edmType', edmType);
|
|
1984
2024
|
}
|
|
1985
2025
|
// This is the special case when we have array of array, but will not be supported in the future
|
|
1986
|
-
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)) {
|
|
1987
2027
|
let edmType = edmUtils.mapCdsToEdmType(obj.items.items, messageFunctions, _options.odataVersion === 'v2', obj['@Core.MediaType']);
|
|
1988
2028
|
edmUtils.assignProp(obj, '_edmType', edmType);
|
|
1989
2029
|
}
|