@sap/cds-compiler 4.5.0 → 4.6.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 +50 -7
- package/bin/cdsc.js +13 -11
- package/doc/CHANGELOG_BETA.md +6 -0
- package/lib/api/main.js +256 -115
- package/lib/api/options.js +8 -0
- package/lib/base/message-registry.js +17 -4
- package/lib/base/messages.js +15 -3
- package/lib/base/model.js +1 -0
- package/lib/base/optionProcessorHelper.js +45 -176
- package/lib/checks/elements.js +32 -34
- package/lib/checks/enricher.js +39 -3
- package/lib/checks/validator.js +2 -3
- package/lib/compiler/assert-consistency.js +2 -1
- package/lib/compiler/builtins.js +20 -4
- package/lib/compiler/checks.js +30 -6
- package/lib/compiler/define.js +31 -9
- package/lib/compiler/populate.js +5 -1
- package/lib/compiler/resolve.js +26 -21
- package/lib/compiler/shared.js +19 -9
- package/lib/compiler/tweak-assocs.js +82 -107
- package/lib/compiler/utils.js +2 -1
- package/lib/edm/annotations/edmJson.js +23 -22
- package/lib/edm/annotations/genericTranslation.js +14 -4
- package/lib/edm/csn2edm.js +24 -10
- package/lib/edm/edmInboundChecks.js +1 -2
- package/lib/edm/edmPreprocessor.js +11 -9
- package/lib/edm/edmUtils.js +5 -2
- package/lib/gen/Dictionary.json +3 -1
- package/lib/gen/language.checksum +1 -1
- package/lib/gen/language.interp +4 -1
- package/lib/gen/language.tokens +1 -0
- package/lib/gen/languageParser.js +5253 -5214
- package/lib/json/to-csn.js +7 -1
- package/lib/language/antlrParser.js +19 -1
- package/lib/language/errorStrategy.js +21 -4
- package/lib/language/genericAntlrParser.js +9 -11
- package/lib/main.d.ts +28 -3
- package/lib/main.js +3 -0
- package/lib/model/csnRefs.js +4 -1
- package/lib/model/csnUtils.js +12 -7
- package/lib/optionProcessor.js +21 -19
- package/lib/render/manageConstraints.js +13 -29
- package/lib/render/toCdl.js +18 -15
- package/lib/render/toHdbcds.js +59 -28
- package/lib/render/toRename.js +6 -10
- package/lib/render/toSql.js +57 -82
- package/lib/render/utils/common.js +17 -0
- package/lib/transform/.eslintrc.json +9 -1
- package/lib/transform/addTenantFields.js +228 -0
- package/lib/transform/db/applyTransformations.js +27 -31
- package/lib/transform/db/assertUnique.js +4 -4
- package/lib/transform/db/cdsPersistence.js +1 -1
- package/lib/transform/db/flattening.js +68 -69
- package/lib/transform/db/temporal.js +1 -1
- package/lib/transform/draft/db.js +2 -16
- package/lib/transform/draft/odata.js +3 -3
- package/lib/transform/effective/associations.js +3 -5
- package/lib/transform/effective/main.js +6 -9
- package/lib/transform/forOdata.js +13 -9
- package/lib/transform/forRelationalDB.js +36 -17
- package/lib/transform/odata/toFinalBaseType.js +3 -3
- package/lib/transform/odata/typesExposure.js +14 -5
- package/lib/transform/transformUtils.js +47 -34
- package/lib/transform/translateAssocsToJoins.js +33 -8
- package/package.json +2 -2
|
@@ -378,6 +378,10 @@ function tweakAssocs( model ) {
|
|
|
378
378
|
}
|
|
379
379
|
|
|
380
380
|
function rewriteKeys( elem, assoc ) {
|
|
381
|
+
addConditionFromAssocPublishing( elem, assoc, null );
|
|
382
|
+
if (elem.on)
|
|
383
|
+
return; // foreign keys were transformed into ON-condition
|
|
384
|
+
|
|
381
385
|
// TODO: split this function: create foreign keys without `targetElement`
|
|
382
386
|
// already in Phase 2: redirectImplicitly()
|
|
383
387
|
// console.log(message( null, elem.location, elem, {art:assoc,target:assoc.target},
|
|
@@ -399,8 +403,6 @@ function tweakAssocs( model ) {
|
|
|
399
403
|
} );
|
|
400
404
|
if (elem.foreignKeys) // Possibly no fk was set
|
|
401
405
|
elem.foreignKeys[$inferred] = 'rewrite';
|
|
402
|
-
|
|
403
|
-
addConditionFromAssocPublishing( elem, assoc );
|
|
404
406
|
}
|
|
405
407
|
|
|
406
408
|
// TODO: there is no need to rewrite the on condition of non-leading queries,
|
|
@@ -423,27 +425,24 @@ function tweakAssocs( model ) {
|
|
|
423
425
|
const nav = (elem._main?.query && elem.value)
|
|
424
426
|
? pathNavigation( elem.value ) // redirected source elem or mixin
|
|
425
427
|
: { navigation: assoc }; // redirected user-provided
|
|
426
|
-
|
|
428
|
+
elem.on = copyExpr( assoc.on,
|
|
427
429
|
// replace location in ON except if from mixin element
|
|
428
|
-
|
|
429
|
-
elem.on = cond;
|
|
430
|
-
addConditionFromAssocPublishing( elem, assoc );
|
|
431
|
-
// `cond` still points to the original condition; does not include possible assoc filter
|
|
430
|
+
nav.tableAlias && elem.name.location );
|
|
432
431
|
elem.on.$inferred = 'copy';
|
|
433
432
|
|
|
434
433
|
const { navigation } = nav;
|
|
435
434
|
if (!navigation) // TODO: what about $projection.assoc as myAssoc ?
|
|
436
435
|
return; // should not happen: $projection, $magic, or ref to const
|
|
436
|
+
|
|
437
437
|
// Currently, having an unmanaged association inside a struct is not
|
|
438
438
|
// supported by this function:
|
|
439
439
|
if (navigation !== assoc && navigation._origin !== assoc) { // TODO: re-check
|
|
440
440
|
// For "assoc1.assoc2" and "struct.elem1.assoc2"
|
|
441
441
|
if (elem._redirected !== null) // null = already reported
|
|
442
442
|
error( 'rewrite-not-supported', [ elem.target.location, elem ] );
|
|
443
|
-
return;
|
|
444
443
|
}
|
|
445
|
-
if (!nav.tableAlias || nav.tableAlias.path) {
|
|
446
|
-
traverseExpr(
|
|
444
|
+
else if (!nav.tableAlias || nav.tableAlias.path) {
|
|
445
|
+
traverseExpr( elem.on, 'rewrite-on', elem,
|
|
447
446
|
expr => rewriteExpr( expr, elem, nav.tableAlias ) );
|
|
448
447
|
}
|
|
449
448
|
else {
|
|
@@ -451,6 +450,12 @@ function tweakAssocs( model ) {
|
|
|
451
450
|
error( null, [ elem.value.location, elem ], {},
|
|
452
451
|
'Selecting unmanaged associations from a sub query is not supported' );
|
|
453
452
|
}
|
|
453
|
+
|
|
454
|
+
// filter was copied in original element already
|
|
455
|
+
// TODO: not correct for e.g. composition-of-inline-aspect (#12223)
|
|
456
|
+
if (elem.$inferred !== 'include')
|
|
457
|
+
addConditionFromAssocPublishing( elem, assoc, nav );
|
|
458
|
+
|
|
454
459
|
elem.on.$inferred = 'rewrite';
|
|
455
460
|
}
|
|
456
461
|
|
|
@@ -462,70 +467,67 @@ function tweakAssocs( model ) {
|
|
|
462
467
|
*
|
|
463
468
|
* The added condition (filter) is already rewritten relative to `elem`.
|
|
464
469
|
*/
|
|
465
|
-
function addConditionFromAssocPublishing( elem, assoc ) {
|
|
470
|
+
function addConditionFromAssocPublishing( elem, assoc, nav ) {
|
|
466
471
|
const publishAssoc = (elem._main?.query || elem.$syntax === 'calc') &&
|
|
467
472
|
elem.value?.path?.length > 0;
|
|
468
473
|
if (!publishAssoc)
|
|
469
474
|
return;
|
|
470
475
|
|
|
476
|
+
nav ??= (elem._main?.query && elem.value)
|
|
477
|
+
? pathNavigation( elem.value ) // redirected source elem or mixin
|
|
478
|
+
: { navigation: assoc }; // redirected user-provided
|
|
479
|
+
|
|
471
480
|
const { location } = elem.name;
|
|
472
|
-
let isRestricted = false;
|
|
473
481
|
const lastStep = elem.value.path[elem.value.path.length - 1];
|
|
474
|
-
if (!lastStep)
|
|
482
|
+
if (!lastStep || !lastStep.where)
|
|
475
483
|
return;
|
|
476
484
|
|
|
477
485
|
if (lastStep.cardinality) {
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
elem.cardinality = { ...assoc.cardinality } || { location };
|
|
486
|
+
elem.cardinality ??= { ...assoc.cardinality };
|
|
487
|
+
elem.cardinality.location = location;
|
|
481
488
|
for (const card of [ 'sourceMin', 'targetMin', 'targetMax' ]) {
|
|
482
489
|
if (lastStep.cardinality[card])
|
|
483
490
|
elem.cardinality[card] = copyExpr( lastStep.cardinality[card], location );
|
|
484
491
|
}
|
|
485
492
|
}
|
|
486
493
|
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
elem.foreignKeys = undefined;
|
|
494
|
-
}
|
|
495
|
-
}
|
|
496
|
-
|
|
497
|
-
if (elem.on) {
|
|
498
|
-
isRestricted = true;
|
|
499
|
-
elem.on = {
|
|
500
|
-
op: { val: 'and', location },
|
|
501
|
-
args: [
|
|
502
|
-
// TODO: Get rid of $parens
|
|
503
|
-
{ ...elem.on, $parens: [ assoc.location ] },
|
|
504
|
-
filterToCondition( lastStep, elem ),
|
|
505
|
-
],
|
|
506
|
-
location,
|
|
507
|
-
$inferred: 'copy',
|
|
508
|
-
};
|
|
509
|
-
}
|
|
510
|
-
if (isRestricted) {
|
|
511
|
-
elem.$filtered = {
|
|
512
|
-
val: true,
|
|
513
|
-
literal: 'boolean',
|
|
514
|
-
location,
|
|
515
|
-
$inferred: '$generated',
|
|
516
|
-
};
|
|
494
|
+
// If there are foreign keys, transform them into an ON-condition first.
|
|
495
|
+
if (assoc.foreignKeys) {
|
|
496
|
+
const cond = foreignKeysToOnCondition( elem, assoc, nav );
|
|
497
|
+
if (cond) {
|
|
498
|
+
elem.on = cond;
|
|
499
|
+
elem.foreignKeys = undefined;
|
|
517
500
|
}
|
|
518
501
|
}
|
|
502
|
+
|
|
503
|
+
elem.on = {
|
|
504
|
+
op: { val: 'and', location },
|
|
505
|
+
args: [
|
|
506
|
+
// TODO: Get rid of $parens
|
|
507
|
+
{ ...elem.on, $parens: [ assoc.location ] },
|
|
508
|
+
filterToCondition( lastStep, elem, nav ),
|
|
509
|
+
],
|
|
510
|
+
location,
|
|
511
|
+
$inferred: 'copy',
|
|
512
|
+
};
|
|
513
|
+
|
|
514
|
+
elem.$filtered = {
|
|
515
|
+
val: true,
|
|
516
|
+
literal: 'boolean',
|
|
517
|
+
location,
|
|
518
|
+
$inferred: '$generated',
|
|
519
|
+
};
|
|
519
520
|
}
|
|
520
521
|
|
|
521
522
|
/**
|
|
522
|
-
* Transform a filter on `
|
|
523
|
-
* Paths inside the filter are rewritten relative to `
|
|
523
|
+
* Transform a filter on `assocPathStep` into an ON-condition.
|
|
524
|
+
* Paths inside the filter are rewritten relative to `assoc`, so they can be redirected
|
|
525
|
+
* using `rewriteExpr()` later on. `$self` paths remain unchanged.
|
|
524
526
|
*/
|
|
525
|
-
function filterToCondition(
|
|
526
|
-
const cond = copyExpr(
|
|
527
|
+
function filterToCondition( assocPathStep, elem, nav ) {
|
|
528
|
+
const cond = copyExpr( assocPathStep.where );
|
|
527
529
|
// TODO: Get rid of $parens
|
|
528
|
-
cond.$parens = [
|
|
530
|
+
cond.$parens = [ assocPathStep.location ];
|
|
529
531
|
traverseExpr( cond, 'rewrite-filter', elem, (expr) => {
|
|
530
532
|
if (!expr.path || expr.path.length === 0)
|
|
531
533
|
return;
|
|
@@ -539,15 +541,17 @@ function tweakAssocs( model ) {
|
|
|
539
541
|
}
|
|
540
542
|
else if (!root.builtin && root.kind !== 'builtin') {
|
|
541
543
|
expr.path.unshift({
|
|
542
|
-
id:
|
|
544
|
+
id: assocPathStep.id,
|
|
543
545
|
location: elem.name.location,
|
|
544
546
|
});
|
|
545
|
-
setLink( expr.path[0], '_artifact',
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
547
|
+
setLink( expr.path[0], '_artifact', assocPathStep._artifact );
|
|
548
|
+
if (assocPathStep._navigation?.kind === 'mixin') {
|
|
549
|
+
// _navigation link necessary because condition is rewritten
|
|
550
|
+
// inside the same view (needed for mixins).
|
|
551
|
+
setLink( expr.path[0], '_navigation', assocPathStep._navigation );
|
|
552
|
+
}
|
|
553
|
+
// up to here, filter is relative to original association
|
|
554
|
+
rewriteExpr( expr, elem, nav?.tableAlias );
|
|
551
555
|
}
|
|
552
556
|
} );
|
|
553
557
|
|
|
@@ -556,15 +560,12 @@ function tweakAssocs( model ) {
|
|
|
556
560
|
}
|
|
557
561
|
|
|
558
562
|
// Caller must ensure ON-condition correctness via rewriteExpr()!
|
|
559
|
-
function foreignKeysToOnCondition( elem ) {
|
|
560
|
-
const nav = elem.$syntax === 'calc'
|
|
561
|
-
? calcPathNavigation( elem.value )
|
|
562
|
-
: pathNavigation( elem.value );
|
|
563
|
+
function foreignKeysToOnCondition( elem, assoc, nav ) {
|
|
563
564
|
if (model.options.testMode && !nav.tableAlias && !elem._pathHead && elem.$syntax !== 'calc')
|
|
564
565
|
throw new CompilerAssertion('rewriting keys to cond: no tableAlias but not inline/calc');
|
|
565
566
|
|
|
566
567
|
if ((!nav.tableAlias && elem.$syntax !== 'calc') || elem._parent?.kind === 'element' ||
|
|
567
|
-
(nav && nav.item !== elem.value.path[elem.value.path.length - 1])) {
|
|
568
|
+
(nav && nav.item && nav.item !== elem.value.path[elem.value.path.length - 1])) {
|
|
568
569
|
// - no nav.tableAlias for mixins or inside inline; mixins can't have managed assocs, though.
|
|
569
570
|
// - _parent is element for expand
|
|
570
571
|
// - nav.item is different for multi-path steps e.g. `sub.assoc`, which is not supported, yet
|
|
@@ -574,45 +575,40 @@ function tweakAssocs( model ) {
|
|
|
574
575
|
}
|
|
575
576
|
|
|
576
577
|
let cond = [];
|
|
577
|
-
forEachInOrder(
|
|
578
|
+
forEachInOrder( assoc, 'foreignKeys', function keyToCond( fKey ) {
|
|
578
579
|
// Format: lhs = rhs
|
|
579
580
|
// assoc.id = assoc_id
|
|
580
|
-
// lhs and rhs look the same
|
|
581
|
-
// rhs is rewritten
|
|
581
|
+
// lhs and rhs look the same but are rewritten differently. We must ensure that
|
|
582
|
+
// the rhs is rewritten to a projected element (or it must remain the assoc's
|
|
583
|
+
// foreign key in case of calc elements).
|
|
582
584
|
const lhs = {
|
|
583
585
|
path: [
|
|
584
|
-
{ id:
|
|
586
|
+
{ id: assoc.name.id, location: elem.name.location },
|
|
585
587
|
...copyExpr( fKey.targetElement.path ),
|
|
586
588
|
],
|
|
587
589
|
location: elem.name.location,
|
|
588
590
|
};
|
|
589
|
-
setLink( lhs.path[0], '_artifact',
|
|
590
|
-
setLink( lhs, '_artifact', lhs.path[lhs.path.length - 1] );
|
|
591
|
+
setLink( lhs.path[0], '_artifact', assoc );
|
|
592
|
+
setLink( lhs, '_artifact', lhs.path[lhs.path.length - 1]._artifact );
|
|
591
593
|
|
|
592
|
-
|
|
593
|
-
prependSelfToPath( lhs.path, elem );
|
|
594
|
+
rewritePath( lhs, lhs.path[0], assoc, elem, elem.value.location ); // different to rhs!
|
|
594
595
|
|
|
595
|
-
const elemOrigin = getOrigin( elem );
|
|
596
596
|
const rhs = {
|
|
597
597
|
path: [
|
|
598
598
|
// use origin's name; elem could have alias
|
|
599
|
-
{ id:
|
|
599
|
+
{ id: assoc.name.id, location: elem.name.location },
|
|
600
600
|
...copyExpr( fKey.targetElement.path ),
|
|
601
601
|
],
|
|
602
602
|
location: elem.name.location,
|
|
603
603
|
};
|
|
604
|
-
setLink( rhs.path[0], '_artifact',
|
|
605
|
-
setLink( rhs, '_artifact', rhs.path[rhs.path.length - 1] );
|
|
604
|
+
setLink( rhs.path[0], '_artifact', assoc );
|
|
605
|
+
setLink( rhs, '_artifact', rhs.path[rhs.path.length - 1]._artifact );
|
|
606
606
|
|
|
607
|
-
if (elem.$syntax !== 'calc') {
|
|
608
|
-
// Can't use rewriteExpr as that would use `assoc[…]` itself as well.
|
|
607
|
+
if (elem.$syntax !== 'calc') { // different to lhs!
|
|
609
608
|
const projectedFk = firstProjectionForPath( rhs.path, nav.tableAlias, elem );
|
|
610
609
|
rewritePath( rhs, projectedFk.item, elem, projectedFk.elem, elem.value.location );
|
|
611
610
|
}
|
|
612
|
-
|
|
613
|
-
// TODO: calc elements: Do we need to rewrite? Shouldn't all elements exist, since
|
|
614
|
-
// the calc element is right next to all others?
|
|
615
|
-
}
|
|
611
|
+
|
|
616
612
|
const fkCond = {
|
|
617
613
|
op: { val: 'ixpr', location: elem.name.location },
|
|
618
614
|
args: [
|
|
@@ -699,15 +695,10 @@ function tweakAssocs( model ) {
|
|
|
699
695
|
return; // just $self
|
|
700
696
|
// corresponding elem in including structure or…
|
|
701
697
|
let elem = (assoc._main.items || assoc._main).elements[item.id];
|
|
702
|
-
if (
|
|
703
|
-
// …
|
|
704
|
-
//
|
|
705
|
-
|
|
706
|
-
// TODO: expr could be the transformed fkey->ON-condition, where this statement is not true
|
|
707
|
-
// And for the reference to the _foreign key_, we must _not_ rewrite it.
|
|
708
|
-
const calcRoot = assoc.value.path[0]?._navigation;
|
|
709
|
-
if (assoc.value.path[calcRoot?.kind === '$self' ? 1 : 0]?._artifact === elem)
|
|
710
|
-
elem = assoc;
|
|
698
|
+
if (assoc.$syntax === 'calc' && assoc._origin === elem) {
|
|
699
|
+
// … calc element where "elem" points to the referenced (possibly included)
|
|
700
|
+
// sibling element (association).
|
|
701
|
+
elem = assoc;
|
|
711
702
|
}
|
|
712
703
|
if (!elem)
|
|
713
704
|
return; // See #11755
|
|
@@ -785,9 +776,9 @@ function tweakAssocs( model ) {
|
|
|
785
776
|
|
|
786
777
|
function prependSelfToPath( path, elem ) {
|
|
787
778
|
const root = { id: '$self', location: path[0].location };
|
|
788
|
-
path.unshift( root );
|
|
789
779
|
setLink( root, '_navigation', elem._parent.$tableAliases.$self );
|
|
790
780
|
setArtifactLink( root, elem._parent );
|
|
781
|
+
path.unshift( root );
|
|
791
782
|
}
|
|
792
783
|
|
|
793
784
|
function rewriteItem( elem, item, name, assoc, forKeys ) {
|
|
@@ -931,20 +922,4 @@ function pathNavigation( ref ) {
|
|
|
931
922
|
return { navigation: root.elements[item.id], item, tableAlias: root };
|
|
932
923
|
}
|
|
933
924
|
|
|
934
|
-
// TODO: Maybe unify with pathNavigation?
|
|
935
|
-
function calcPathNavigation( ref ) {
|
|
936
|
-
if (!ref._artifact)
|
|
937
|
-
return {};
|
|
938
|
-
let item = ref.path && ref.path[0];
|
|
939
|
-
const root = item && item._artifact;
|
|
940
|
-
if (!root)
|
|
941
|
-
return {};
|
|
942
|
-
if (root.kind === 'element')
|
|
943
|
-
return { navigation: root, item, tableAlias: root._parent };
|
|
944
|
-
item = ref.path[1];
|
|
945
|
-
if (root.kind === '$self')
|
|
946
|
-
return { item, tableAlias: root };
|
|
947
|
-
return { navigation: root.elements[item.id], item, tableAlias: root };
|
|
948
|
-
}
|
|
949
|
-
|
|
950
925
|
module.exports = tweakAssocs;
|
package/lib/compiler/utils.js
CHANGED
|
@@ -29,7 +29,8 @@ function pushLink( obj, prop, value ) {
|
|
|
29
29
|
// for annotations:
|
|
30
30
|
|
|
31
31
|
function annotationVal( anno ) {
|
|
32
|
-
|
|
32
|
+
// XSN TODO: set val but no location for anno short form
|
|
33
|
+
return anno && (anno.val === undefined || anno.val);
|
|
33
34
|
}
|
|
34
35
|
function annotationIsFalse( anno ) { // falsy, but not null (unset)
|
|
35
36
|
return anno && (anno.val === false || anno.val === 0 || anno.val === '');
|
|
@@ -7,9 +7,8 @@ const {
|
|
|
7
7
|
EdmTypeFacetNames,
|
|
8
8
|
EdmPrimitiveTypeMap,
|
|
9
9
|
} = require('../EdmPrimitiveTypeDefinitions.js');
|
|
10
|
-
const { isBuiltinType } = require('../../model/csnUtils');
|
|
10
|
+
const { isBuiltinType, isAnnotationExpression } = require('../../model/csnUtils');
|
|
11
11
|
const { transformExpression } = require('../../transform/db/applyTransformations.js');
|
|
12
|
-
const { xprInAnnoProperties } = require('../../compiler/builtins');
|
|
13
12
|
|
|
14
13
|
/**
|
|
15
14
|
* Translate a given token stream expression into an edmJson representation
|
|
@@ -80,7 +79,7 @@ function xpr2edmJson( carrier, anno, location, options, messageFunctions ) {
|
|
|
80
79
|
// Error transformer
|
|
81
80
|
const notADynExpr = (parent, op, xpr, csnPath, parentparent, parentprop, txt) => {
|
|
82
81
|
error('odata-anno-xpr', location, {
|
|
83
|
-
anno, op: txt
|
|
82
|
+
anno, op: txt ?? op, '#': 'notadynexpr',
|
|
84
83
|
});
|
|
85
84
|
delete parent[op];
|
|
86
85
|
};
|
|
@@ -108,13 +107,13 @@ function xpr2edmJson( carrier, anno, location, options, messageFunctions ) {
|
|
|
108
107
|
// list is a $Collection => []
|
|
109
108
|
transform.list = (parent, prop, xpr, csnPath, parentparent, parentprop) => {
|
|
110
109
|
parentparent[parentprop] = xpr.filter(a => a);
|
|
111
|
-
transformExpression(parentparent, transform);
|
|
110
|
+
transformExpression(parentparent, parentprop, transform);
|
|
112
111
|
};
|
|
113
112
|
// XPR
|
|
114
113
|
transform.xpr = (parent, prop, xpr, csnPath, parentparent, parentprop) => {
|
|
115
114
|
// eliminate 'xpr' node by pulling up xpr node to its parent
|
|
116
115
|
parentparent[parentprop] = xpr;
|
|
117
|
-
transformExpression(parentparent, transform);
|
|
116
|
+
transformExpression(parentparent, parentprop, transform);
|
|
118
117
|
};
|
|
119
118
|
//----------------------------------
|
|
120
119
|
// CASE
|
|
@@ -145,9 +144,11 @@ function xpr2edmJson( carrier, anno, location, options, messageFunctions ) {
|
|
|
145
144
|
curIf.$If.push(caseExpr[i]);
|
|
146
145
|
parent.$If = edmIf.$If;
|
|
147
146
|
delete parent.case;
|
|
148
|
-
transformExpression(parent, transform);
|
|
147
|
+
transformExpression(parent, undefined, transform);
|
|
148
|
+
};
|
|
149
|
+
transform.$If = (_parent, _prop, expr) => {
|
|
150
|
+
transformExpression(expr, undefined, transform);
|
|
149
151
|
};
|
|
150
|
-
transform.$If = noOp;
|
|
151
152
|
//----------------------------------
|
|
152
153
|
// Cast => $Cast
|
|
153
154
|
transform.cast = (parent, prop, castExpr, csnPath, parentparent, parentprop) => {
|
|
@@ -185,7 +186,7 @@ function xpr2edmJson( carrier, anno, location, options, messageFunctions ) {
|
|
|
185
186
|
|
|
186
187
|
|
|
187
188
|
parentparent[parentprop] = castFunc;
|
|
188
|
-
transformExpression(parentparent, transform);
|
|
189
|
+
transformExpression(parentparent, parentprop, transform);
|
|
189
190
|
};
|
|
190
191
|
//----------------------------------
|
|
191
192
|
const evalArgs = (argDef, args, propName) => {
|
|
@@ -221,7 +222,7 @@ function xpr2edmJson( carrier, anno, location, options, messageFunctions ) {
|
|
|
221
222
|
evalArgs({ exact }, xpr, prop);
|
|
222
223
|
parent[opStr] = xpr;
|
|
223
224
|
delete parent[prop];
|
|
224
|
-
transformExpression(parent, transform);
|
|
225
|
+
transformExpression(parent, undefined, transform);
|
|
225
226
|
};
|
|
226
227
|
//----------------------------------
|
|
227
228
|
// LOGICAL
|
|
@@ -250,12 +251,12 @@ function xpr2edmJson( carrier, anno, location, options, messageFunctions ) {
|
|
|
250
251
|
evalArgs({ min: 1 }, xpr[1].list, prop);
|
|
251
252
|
parent.$In = [ xpr[0], ...xpr[1].list ];
|
|
252
253
|
delete parent[prop];
|
|
253
|
-
transformExpression(parent, transform);
|
|
254
|
+
transformExpression(parent, undefined, transform);
|
|
254
255
|
};
|
|
255
256
|
transform.$In = noOp;
|
|
256
257
|
transform.between = (parent, prop, xpr, csnPath, parentparent, parentprop) => {
|
|
257
258
|
evalArgs({ exact: 2 }, xpr.slice(1), prop);
|
|
258
|
-
transformExpression(xpr, transform);
|
|
259
|
+
transformExpression(xpr, undefined, transform);
|
|
259
260
|
delete parent[prop];
|
|
260
261
|
parentparent[parentprop]
|
|
261
262
|
= {
|
|
@@ -267,7 +268,7 @@ function xpr2edmJson( carrier, anno, location, options, messageFunctions ) {
|
|
|
267
268
|
};
|
|
268
269
|
transform['||'] = (parent, prop, xpr, csnPath, parentparent, parentprop) => {
|
|
269
270
|
evalArgs({ exact: 2 }, xpr, prop);
|
|
270
|
-
transformExpression(xpr, transform);
|
|
271
|
+
transformExpression(xpr, undefined, transform);
|
|
271
272
|
delete parent[prop];
|
|
272
273
|
parentparent[parentprop].$Apply = [ { $Function: 'odata.concat' }, ...xpr ];
|
|
273
274
|
};
|
|
@@ -280,7 +281,7 @@ function xpr2edmJson( carrier, anno, location, options, messageFunctions ) {
|
|
|
280
281
|
else {
|
|
281
282
|
delete parent[prop];
|
|
282
283
|
parentparent[parentprop] = xpr;
|
|
283
|
-
transformExpression(parentparent, transform);
|
|
284
|
+
transformExpression(parentparent, parentprop, transform);
|
|
284
285
|
}
|
|
285
286
|
};
|
|
286
287
|
transform.$Add = noOp;
|
|
@@ -591,7 +592,7 @@ function xpr2edmJson( carrier, anno, location, options, messageFunctions ) {
|
|
|
591
592
|
delete parent.args;
|
|
592
593
|
};
|
|
593
594
|
|
|
594
|
-
const exactArgs = (tgt = parent, x = xpr, count) => {
|
|
595
|
+
const exactArgs = (tgt = parent, x = xpr, count = undefined) => {
|
|
595
596
|
standard(tgt, x);
|
|
596
597
|
evalArgs({ exact: count }, tgt[x], xpr);
|
|
597
598
|
};
|
|
@@ -695,7 +696,7 @@ function xpr2edmJson( carrier, anno, location, options, messageFunctions ) {
|
|
|
695
696
|
// $Record ???
|
|
696
697
|
$Collection: () => {
|
|
697
698
|
standard(parentparent, parentprop);
|
|
698
|
-
transformExpression(parentparent, transform);
|
|
699
|
+
transformExpression(parentparent, parentprop, transform);
|
|
699
700
|
},
|
|
700
701
|
$Path: () => {
|
|
701
702
|
oneArg(parent, xpr);
|
|
@@ -705,7 +706,7 @@ function xpr2edmJson( carrier, anno, location, options, messageFunctions ) {
|
|
|
705
706
|
anno, op: `${xpr}(…)`, meta: 'string', '#': 'wrongval_meta',
|
|
706
707
|
});
|
|
707
708
|
}
|
|
708
|
-
transformExpression(parentparent, transform);
|
|
709
|
+
transformExpression(parentparent, parentprop, transform);
|
|
709
710
|
},
|
|
710
711
|
$Null: () => {
|
|
711
712
|
parent[xpr] = true;
|
|
@@ -725,7 +726,7 @@ function xpr2edmJson( carrier, anno, location, options, messageFunctions ) {
|
|
|
725
726
|
funcDef.forEach(f => f());
|
|
726
727
|
else
|
|
727
728
|
funcDef();
|
|
728
|
-
transformExpression(parent, transform);
|
|
729
|
+
transformExpression(parent, undefined, transform);
|
|
729
730
|
}
|
|
730
731
|
else {
|
|
731
732
|
const funcName = xpr.startsWith('odata.') ? xpr : `odata.${xpr}`;
|
|
@@ -741,7 +742,7 @@ function xpr2edmJson( carrier, anno, location, options, messageFunctions ) {
|
|
|
741
742
|
parentparent[parentprop].$Apply = [ { $Function: funcName }, ...(parent.args || []) ];
|
|
742
743
|
delete parentparent[parentprop].func;
|
|
743
744
|
delete parentparent[parentprop].args;
|
|
744
|
-
transformExpression(parentparent, transform);
|
|
745
|
+
transformExpression(parentparent, parentprop, transform);
|
|
745
746
|
}
|
|
746
747
|
}
|
|
747
748
|
else {
|
|
@@ -753,14 +754,14 @@ function xpr2edmJson( carrier, anno, location, options, messageFunctions ) {
|
|
|
753
754
|
delete parent[prop];
|
|
754
755
|
};
|
|
755
756
|
|
|
756
|
-
return transformExpression(
|
|
757
|
+
return transformExpression(carrier, anno, {
|
|
757
758
|
'=': (parent, prop, xpr, csnPath, parentparent, parentprop) => {
|
|
758
|
-
if (
|
|
759
|
+
if (isAnnotationExpression(parent)) {
|
|
759
760
|
delete parent['='];
|
|
760
|
-
parentparent[parentprop] = transformExpression({ $edmJson: parseExpr( parent, { array: false }) }, transform);
|
|
761
|
+
parentparent[parentprop] = transformExpression({ $edmJson: parseExpr( parent, { array: false }) }, undefined, transform);
|
|
761
762
|
}
|
|
762
763
|
},
|
|
763
|
-
})
|
|
764
|
+
});
|
|
764
765
|
}
|
|
765
766
|
|
|
766
767
|
// Not everything that can occur in OData annotations can be expressed with
|
|
@@ -305,7 +305,7 @@ function csn2annotationEdm( reqDefs, csnVocabularies, serviceName,
|
|
|
305
305
|
if (isBetaEnabled(options, 'odataAnnotationExpressions')) {
|
|
306
306
|
knownAnnos.forEach((knownAnno) => {
|
|
307
307
|
if (knownAnno.search(/\.\$edmJson\./g) < 0)
|
|
308
|
-
|
|
308
|
+
xpr2edmJson(carrier, knownAnno, location, options, messageFunctions);
|
|
309
309
|
});
|
|
310
310
|
}
|
|
311
311
|
|
|
@@ -676,7 +676,7 @@ function csn2annotationEdm( reqDefs, csnVocabularies, serviceName,
|
|
|
676
676
|
|
|
677
677
|
// anno is the full <Annotation Term=...>
|
|
678
678
|
const anno = handleTerm(fullTermName, prefixTree[voc][term], msg);
|
|
679
|
-
if (anno
|
|
679
|
+
if (!anno?.$isInvalid) {
|
|
680
680
|
// addAnnotationFunc needs AppliesTo message from dictionary to decide where to put the anno
|
|
681
681
|
const termName = fullTermName.replace(/#(\w+)$/g, ''); // remove qualifier
|
|
682
682
|
const dictTerm = getDictTerm(termName, msg); // message for unknown term was already issued in handleTerm
|
|
@@ -835,6 +835,7 @@ function csn2annotationEdm( reqDefs, csnVocabularies, serviceName,
|
|
|
835
835
|
}
|
|
836
836
|
else if ( Object.keys(cAnnoValue).filter( x => x[0] !== '@' ).length === 0) {
|
|
837
837
|
// object consists only of properties starting with "@", no $value
|
|
838
|
+
setProp(oTarget, '$isInvalid', true);
|
|
838
839
|
message('odata-anno-value', msg.location,
|
|
839
840
|
{ anno: msg.anno(), str: 'base', '#': 'nested' } );
|
|
840
841
|
}
|
|
@@ -1147,8 +1148,17 @@ function csn2annotationEdm( reqDefs, csnVocabularies, serviceName,
|
|
|
1147
1148
|
actualTypeName = obj.$Type;
|
|
1148
1149
|
if (!getDictType(actualTypeName)) {
|
|
1149
1150
|
// this type doesn't exist
|
|
1150
|
-
|
|
1151
|
-
|
|
1151
|
+
if (typeof actualTypeName !== 'string') {
|
|
1152
|
+
actualTypeName = JSON.stringify(obj.$Type);
|
|
1153
|
+
message('odata-anno-type', msg.location,
|
|
1154
|
+
{
|
|
1155
|
+
anno: msg.anno(), code: '$Type', rawvalue: actualTypeName, '#': 'literal',
|
|
1156
|
+
});
|
|
1157
|
+
}
|
|
1158
|
+
else {
|
|
1159
|
+
message('odata-anno-type', msg.location,
|
|
1160
|
+
{ anno: msg.anno(), type: actualTypeName, '#': 'unknown' });
|
|
1161
|
+
}
|
|
1152
1162
|
// explicitly mentioned type, render in XML and JSON
|
|
1153
1163
|
newRecord.setXml({ Type: actualTypeName });
|
|
1154
1164
|
// unknown dictionary type: can't fully qualify it
|
package/lib/edm/csn2edm.js
CHANGED
|
@@ -14,7 +14,6 @@ const {
|
|
|
14
14
|
cloneCsnNonDict, isEdmPropertyRendered, isBuiltinType, getUtils,
|
|
15
15
|
} = require('../model/csnUtils');
|
|
16
16
|
const { checkCSNVersion } = require('../json/csnVersion');
|
|
17
|
-
const { makeMessageFunction } = require('../base/messages');
|
|
18
17
|
const {
|
|
19
18
|
EdmTypeFacetMap,
|
|
20
19
|
EdmTypeFacetNames,
|
|
@@ -23,21 +22,34 @@ const {
|
|
|
23
22
|
const { getEdm } = require('./edm.js');
|
|
24
23
|
|
|
25
24
|
/*
|
|
26
|
-
OData V2 spec 06/01/2017 PDF version is available
|
|
25
|
+
OData V2 spec 06/01/2017 PDF version is available here:
|
|
27
26
|
https://msdn.microsoft.com/en-us/library/dd541474.aspx
|
|
28
27
|
*/
|
|
29
28
|
|
|
30
|
-
|
|
31
|
-
|
|
29
|
+
/**
|
|
30
|
+
* @param {CSN.Model} _csn
|
|
31
|
+
* @param {string} serviceName
|
|
32
|
+
* @param {CSN.Options} _options
|
|
33
|
+
* @param {object} messageFunctions Message functions such as `error()`, `info()`, …
|
|
34
|
+
* @return {any}
|
|
35
|
+
*/
|
|
36
|
+
function csn2edm( _csn, serviceName, _options, messageFunctions ) {
|
|
37
|
+
return csn2edmAll(_csn, _options, [ serviceName ], messageFunctions)[serviceName];
|
|
32
38
|
}
|
|
33
39
|
|
|
34
|
-
|
|
40
|
+
/**
|
|
41
|
+
* @param {CSN.Model} _csn
|
|
42
|
+
* @param {CSN.Options} _options
|
|
43
|
+
* @param {string[]|undefined} serviceNames
|
|
44
|
+
* @param {object} messageFunctions Message functions such as `error()`, `info()`, …
|
|
45
|
+
* @return {any}
|
|
46
|
+
*/
|
|
47
|
+
function csn2edmAll( _csn, _options, serviceNames, messageFunctions ) {
|
|
35
48
|
// get us a fresh model copy that we can work with
|
|
36
49
|
const csn = cloneCsnNonDict(_csn, _options);
|
|
37
50
|
const special$self = !csn?.definitions?.$self && '$self';
|
|
51
|
+
messageFunctions.setModel(csn);
|
|
38
52
|
|
|
39
|
-
// use original options for messages; cloned CSN for semantic location
|
|
40
|
-
const messageFunctions = makeMessageFunction(csn, _options, 'to.edmx');
|
|
41
53
|
const {
|
|
42
54
|
info, warning, error, message, throwWithError,
|
|
43
55
|
} = messageFunctions;
|
|
@@ -630,15 +642,17 @@ function csn2edmAll( _csn, _options, serviceNames = undefined ) {
|
|
|
630
642
|
else if (isEdmPropertyRendered(elementCsn, options)) {
|
|
631
643
|
// CDXCORE-CDXCORE-173
|
|
632
644
|
// V2: filter @Core.MediaType
|
|
633
|
-
if (
|
|
645
|
+
if (options.isV2() && elementCsn['@Core.MediaType']) {
|
|
634
646
|
hasStream = elementCsn['@Core.MediaType'];
|
|
635
|
-
|
|
647
|
+
elementCsn['@cds.api.ignore'] = true;
|
|
636
648
|
// CDXCORE-CDXCORE-177:
|
|
637
649
|
// V2: don't render element but add attribute 'm:HasStream="true' to EntityType
|
|
638
|
-
// V4: render property type 'Edm.Stream'
|
|
639
650
|
streamProps.push(elementName);
|
|
640
651
|
}
|
|
641
652
|
else {
|
|
653
|
+
// V4: render property type 'Edm.Stream' but don't add '@Core.IsURL'
|
|
654
|
+
if ( elementCsn['@Core.MediaType'])
|
|
655
|
+
delete elementCsn['@Core.IsURL'];
|
|
642
656
|
collectUsedType(elementCsn);
|
|
643
657
|
props.push(new Edm.Property(v, { Name: elementName }, elementCsn));
|
|
644
658
|
}
|
|
@@ -71,10 +71,9 @@ function inboundQualificationChecks( csn, options, messageFunctions,
|
|
|
71
71
|
type = elt;
|
|
72
72
|
}
|
|
73
73
|
|
|
74
|
-
if (type.type === 'cds.UUID') {
|
|
74
|
+
if (type.type === 'cds.UUID' && elt['@odata.foreignKey4'] == null) {
|
|
75
75
|
if (path[1] === defName)
|
|
76
76
|
assignAnnotation(elt, anno, true);
|
|
77
|
-
|
|
78
77
|
else if (elt[anno] == null)
|
|
79
78
|
warning('odata-key-uuid-default-anno', path, { type: type.type, anno, id: `${defName}:${eltPath.join('.')}` });
|
|
80
79
|
}
|