@sap/cds-compiler 4.4.4 → 4.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +52 -0
- package/bin/cdsc.js +5 -0
- package/bin/cdsv2m.js +7 -5
- package/doc/CHANGELOG_BETA.md +16 -0
- package/lib/api/main.js +68 -47
- package/lib/api/options.js +10 -6
- package/lib/api/validate.js +1 -1
- package/lib/base/message-registry.js +28 -6
- package/lib/base/messages.js +18 -13
- package/lib/base/model.js +3 -0
- package/lib/checks/annotationsOData.js +49 -0
- package/lib/checks/validator.js +6 -4
- package/lib/compiler/assert-consistency.js +38 -16
- package/lib/compiler/builtins.js +10 -49
- package/lib/compiler/checks.js +16 -8
- package/lib/compiler/cycle-detector.js +1 -4
- package/lib/compiler/define.js +4 -1
- package/lib/compiler/extend.js +21 -7
- package/lib/compiler/generate.js +3 -0
- package/lib/compiler/populate.js +5 -1
- package/lib/compiler/propagator.js +46 -9
- package/lib/compiler/resolve.js +68 -14
- package/lib/compiler/shared.js +44 -27
- package/lib/compiler/tweak-assocs.js +158 -37
- package/lib/compiler/utils.js +9 -0
- package/lib/edm/annotations/edmJson.js +35 -61
- package/lib/edm/annotations/genericTranslation.js +13 -5
- package/lib/edm/annotations/preprocessAnnotations.js +2 -3
- package/lib/edm/csn2edm.js +4 -1
- package/lib/edm/edmInboundChecks.js +59 -15
- package/lib/edm/edmPreprocessor.js +1 -7
- package/lib/gen/Dictionary.json +8 -0
- package/lib/gen/language.checksum +1 -1
- package/lib/gen/language.interp +12 -2
- package/lib/gen/languageParser.js +6095 -5195
- package/lib/json/from-csn.js +4 -5
- package/lib/json/to-csn.js +22 -3
- package/lib/language/errorStrategy.js +7 -3
- package/lib/language/genericAntlrParser.js +120 -24
- package/lib/language/textUtils.js +16 -0
- package/lib/model/csnUtils.js +9 -8
- package/lib/model/revealInternalProperties.js +5 -2
- package/lib/optionProcessor.js +2 -3
- package/lib/render/toCdl.js +31 -13
- package/lib/render/toHdbcds.js +20 -30
- package/lib/render/toSql.js +33 -54
- package/lib/render/utils/common.js +24 -6
- package/lib/transform/db/applyTransformations.js +59 -2
- package/lib/transform/db/backlinks.js +13 -1
- package/lib/transform/db/expansion.js +24 -3
- package/lib/transform/db/flattening.js +2 -2
- package/lib/transform/db/killAnnotations.js +37 -0
- package/lib/transform/db/rewriteCalculatedElements.js +46 -6
- package/lib/transform/forOdata.js +13 -46
- package/lib/transform/forRelationalDB.js +2 -1
- package/lib/transform/translateAssocsToJoins.js +13 -4
- package/lib/transform/universalCsn/coreComputed.js +1 -1
- package/lib/transform/universalCsn/universalCsnEnricher.js +4 -4
- package/package.json +7 -6
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
const {
|
|
6
6
|
forEachGeneric,
|
|
7
|
+
forEachMember,
|
|
7
8
|
forEachInOrder,
|
|
8
9
|
} = require('../base/model');
|
|
9
10
|
const { dictLocation, weakLocation, weakRefLocation } = require('../base/location');
|
|
@@ -32,6 +33,7 @@ function tweakAssocs( model ) {
|
|
|
32
33
|
info, warning, error,
|
|
33
34
|
} = model.$messageFunctions;
|
|
34
35
|
const {
|
|
36
|
+
resolvePath,
|
|
35
37
|
traverseExpr,
|
|
36
38
|
checkExpr,
|
|
37
39
|
checkOnCondition,
|
|
@@ -62,8 +64,6 @@ function tweakAssocs( model ) {
|
|
|
62
64
|
function rewriteArtifact( art ) {
|
|
63
65
|
// return;
|
|
64
66
|
if (!art.query) {
|
|
65
|
-
// console.log(message( null, art.location, art, {target:art._target},
|
|
66
|
-
// 'Info','RAS').toString())
|
|
67
67
|
rewriteAssociation( art );
|
|
68
68
|
forEachGeneric( art, 'elements', rewriteAssociation );
|
|
69
69
|
}
|
|
@@ -78,6 +78,8 @@ function tweakAssocs( model ) {
|
|
|
78
78
|
traverseQueryPost( art.query, false, ( query ) => {
|
|
79
79
|
forEachGeneric( query, 'elements', rewriteAssociationCheck );
|
|
80
80
|
} );
|
|
81
|
+
|
|
82
|
+
checkForAnnotationRefs( art );
|
|
81
83
|
}
|
|
82
84
|
|
|
83
85
|
// function rewriteView( view ) {
|
|
@@ -122,6 +124,79 @@ function tweakAssocs( model ) {
|
|
|
122
124
|
}
|
|
123
125
|
}
|
|
124
126
|
|
|
127
|
+
function checkForAnnotationRefs( art ) {
|
|
128
|
+
// TODO: no check for type inheritance yet
|
|
129
|
+
const origin = art._origin ||
|
|
130
|
+
art.includes && art.includes[art.includes.length - 1]._artifact;
|
|
131
|
+
// Make sure not to waste time if no inherited annotation has checked refs:
|
|
132
|
+
if (!origin?.$contains?.$annotation)
|
|
133
|
+
return;
|
|
134
|
+
for (const prop in origin) {
|
|
135
|
+
const anno = prop.charAt(0) === '@' && !art[prop] && origin[prop];
|
|
136
|
+
// Remark: to be on the academic safe side, we should consider the
|
|
137
|
+
// annotations which are not propagated, but they never have references as
|
|
138
|
+
// value. So “no” for the moment. We also do not perform these checks in
|
|
139
|
+
// propagator.js, as it should go away in compiler v6.
|
|
140
|
+
if (anno.kind) { // i.e. with values refs
|
|
141
|
+
art[prop] = { ...anno, $inferred: 'prop' };
|
|
142
|
+
setLink( art[prop], '_outer', art );
|
|
143
|
+
const errorRef = checkAnnotationForRefs( art[prop], art );
|
|
144
|
+
if (errorRef) {
|
|
145
|
+
const valid = errorRef.path[errorRef.path.length - 1]._artifact;
|
|
146
|
+
error( 'anno-missing-rewrite', [ weakLocation( art.location ), art ], {
|
|
147
|
+
'#': (valid ? 'unrelated' : 'std'),
|
|
148
|
+
anno: prop,
|
|
149
|
+
art: origin,
|
|
150
|
+
elemref: errorRef,
|
|
151
|
+
} );
|
|
152
|
+
}
|
|
153
|
+
art.$contains ??= {};
|
|
154
|
+
art.$contains.$annotation = true;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
forEachMember( art, checkForAnnotationRefs );
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
function checkAnnotationForRefs( expr, user ) {
|
|
161
|
+
if (expr.$tokenTexts)
|
|
162
|
+
return traverseExpr( expr, 'annoRewrite', user, checkAnnotationRef );
|
|
163
|
+
if (expr.literal === 'array')
|
|
164
|
+
return expr.val.find( val => checkAnnotationForRefs( val, user ) );
|
|
165
|
+
if (expr.literal !== 'struct')
|
|
166
|
+
return null;
|
|
167
|
+
const struct = Object.values( expr.struct );
|
|
168
|
+
return struct.find( val => checkAnnotationForRefs( val, user ) );
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
function checkAnnotationRef( ref, refCtx, user ) {
|
|
172
|
+
const origPath = ref.path;
|
|
173
|
+
if (!origPath[origPath.length - 1]._artifact) // already wrong in original
|
|
174
|
+
return null;
|
|
175
|
+
ref = { ...ref, path: [ ...origPath.map( item => ({ ...item }) ) ] };
|
|
176
|
+
if (!resolvePath( ref, refCtx, user ))
|
|
177
|
+
return ref;
|
|
178
|
+
return ref.path.some( isUnrelated ) && ref;
|
|
179
|
+
|
|
180
|
+
function isUnrelated( item, idx ) {
|
|
181
|
+
let elem = item._artifact;
|
|
182
|
+
const orig = origPath[idx]._artifact;
|
|
183
|
+
// With includes, we allow shadowing: an included element might seem to be
|
|
184
|
+
// unrelated.
|
|
185
|
+
if (elem._main?.includes && elem._main === (user._main || user))
|
|
186
|
+
return false;
|
|
187
|
+
if (!elem._effectiveType) // safety
|
|
188
|
+
return false;
|
|
189
|
+
// With redirections, the originally referred object might not be the same
|
|
190
|
+
// or even the direct _origin
|
|
191
|
+
do {
|
|
192
|
+
if (elem === orig)
|
|
193
|
+
return false;
|
|
194
|
+
elem = elem._origin;
|
|
195
|
+
} while (elem);
|
|
196
|
+
return true;
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
125
200
|
function rewriteAssociationCheck( element ) {
|
|
126
201
|
const elem = element.items || element; // TODO v5: nested items
|
|
127
202
|
if (elem.elements)
|
|
@@ -287,7 +362,7 @@ function tweakAssocs( model ) {
|
|
|
287
362
|
elem[prop] = { $inferred: 'NULL', val: undefined, location };
|
|
288
363
|
break;
|
|
289
364
|
}
|
|
290
|
-
origin = origin
|
|
365
|
+
origin = getOrigin( origin );
|
|
291
366
|
}
|
|
292
367
|
}
|
|
293
368
|
}
|
|
@@ -340,9 +415,9 @@ function tweakAssocs( model ) {
|
|
|
340
415
|
setExpandStatus( elem, 'target' );
|
|
341
416
|
if (elem._parent?.kind === 'element') {
|
|
342
417
|
// managed association as sub element not supported yet
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
418
|
+
// TODO: Only report once for multi-include chains, see
|
|
419
|
+
// Associations/SubElements/UnmanagedInSubElement.err.cds
|
|
420
|
+
error( 'type-unsupported-rewrite', [ elem.location, elem ], { '#': 'sub-element' } );
|
|
346
421
|
return;
|
|
347
422
|
}
|
|
348
423
|
const nav = (elem._main?.query && elem.value)
|
|
@@ -388,23 +463,28 @@ function tweakAssocs( model ) {
|
|
|
388
463
|
* The added condition (filter) is already rewritten relative to `elem`.
|
|
389
464
|
*/
|
|
390
465
|
function addConditionFromAssocPublishing( elem, assoc ) {
|
|
391
|
-
const publishAssoc = (elem._main?.query
|
|
466
|
+
const publishAssoc = (elem._main?.query || elem.$syntax === 'calc') &&
|
|
467
|
+
elem.value?.path?.length > 0;
|
|
392
468
|
if (!publishAssoc)
|
|
393
469
|
return;
|
|
394
470
|
|
|
395
471
|
const { location } = elem.name;
|
|
472
|
+
let isRestricted = false;
|
|
396
473
|
const lastStep = elem.value.path[elem.value.path.length - 1];
|
|
474
|
+
if (!lastStep)
|
|
475
|
+
return;
|
|
397
476
|
|
|
398
|
-
if (lastStep
|
|
477
|
+
if (lastStep.cardinality) {
|
|
478
|
+
isRestricted = true;
|
|
399
479
|
if (!elem.cardinality)
|
|
400
|
-
elem.cardinality = assoc.cardinality || { location };
|
|
480
|
+
elem.cardinality = { ...assoc.cardinality } || { location };
|
|
401
481
|
for (const card of [ 'sourceMin', 'targetMin', 'targetMax' ]) {
|
|
402
482
|
if (lastStep.cardinality[card])
|
|
403
483
|
elem.cardinality[card] = copyExpr( lastStep.cardinality[card], location );
|
|
404
484
|
}
|
|
405
485
|
}
|
|
406
486
|
|
|
407
|
-
if (lastStep
|
|
487
|
+
if (lastStep.where) {
|
|
408
488
|
// If there are foreign keys, transform them into an ON-condition first.
|
|
409
489
|
if (assoc.foreignKeys) {
|
|
410
490
|
const cond = foreignKeysToOnCondition( elem );
|
|
@@ -415,6 +495,7 @@ function tweakAssocs( model ) {
|
|
|
415
495
|
}
|
|
416
496
|
|
|
417
497
|
if (elem.on) {
|
|
498
|
+
isRestricted = true;
|
|
418
499
|
elem.on = {
|
|
419
500
|
op: { val: 'and', location },
|
|
420
501
|
args: [
|
|
@@ -426,6 +507,14 @@ function tweakAssocs( model ) {
|
|
|
426
507
|
$inferred: 'copy',
|
|
427
508
|
};
|
|
428
509
|
}
|
|
510
|
+
if (isRestricted) {
|
|
511
|
+
elem.$filtered = {
|
|
512
|
+
val: true,
|
|
513
|
+
literal: 'boolean',
|
|
514
|
+
location,
|
|
515
|
+
$inferred: '$generated',
|
|
516
|
+
};
|
|
517
|
+
}
|
|
429
518
|
}
|
|
430
519
|
}
|
|
431
520
|
|
|
@@ -468,11 +557,13 @@ function tweakAssocs( model ) {
|
|
|
468
557
|
|
|
469
558
|
// Caller must ensure ON-condition correctness via rewriteExpr()!
|
|
470
559
|
function foreignKeysToOnCondition( elem ) {
|
|
471
|
-
const nav =
|
|
472
|
-
|
|
473
|
-
|
|
560
|
+
const nav = elem.$syntax === 'calc'
|
|
561
|
+
? calcPathNavigation( elem.value )
|
|
562
|
+
: pathNavigation( elem.value );
|
|
563
|
+
if (model.options.testMode && !nav.tableAlias && !elem._pathHead && elem.$syntax !== 'calc')
|
|
564
|
+
throw new CompilerAssertion('rewriting keys to cond: no tableAlias but not inline/calc');
|
|
474
565
|
|
|
475
|
-
if (!nav.tableAlias || elem._parent?.kind === 'element' ||
|
|
566
|
+
if ((!nav.tableAlias && elem.$syntax !== 'calc') || elem._parent?.kind === 'element' ||
|
|
476
567
|
(nav && nav.item !== elem.value.path[elem.value.path.length - 1])) {
|
|
477
568
|
// - no nav.tableAlias for mixins or inside inline; mixins can't have managed assocs, though.
|
|
478
569
|
// - _parent is element for expand
|
|
@@ -501,21 +592,27 @@ function tweakAssocs( model ) {
|
|
|
501
592
|
if (elem.name.id.charAt(0) === '$')
|
|
502
593
|
prependSelfToPath( lhs.path, elem );
|
|
503
594
|
|
|
595
|
+
const elemOrigin = getOrigin( elem );
|
|
504
596
|
const rhs = {
|
|
505
597
|
path: [
|
|
506
598
|
// use origin's name; elem could have alias
|
|
507
|
-
{ id:
|
|
599
|
+
{ id: elemOrigin.name.id, location: elemOrigin.name.location },
|
|
508
600
|
...copyExpr( fKey.targetElement.path ),
|
|
509
601
|
],
|
|
510
602
|
location: elem.name.location,
|
|
511
603
|
};
|
|
512
|
-
setLink( rhs.path[0], '_artifact',
|
|
604
|
+
setLink( rhs.path[0], '_artifact', elemOrigin ); // different to lhs!
|
|
513
605
|
setLink( rhs, '_artifact', rhs.path[rhs.path.length - 1] );
|
|
514
606
|
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
607
|
+
if (elem.$syntax !== 'calc') {
|
|
608
|
+
// Can't use rewriteExpr as that would use `assoc[…]` itself as well.
|
|
609
|
+
const projectedFk = firstProjectionForPath( rhs.path, nav.tableAlias, elem );
|
|
610
|
+
rewritePath( rhs, projectedFk.item, elem, projectedFk.elem, elem.value.location );
|
|
611
|
+
}
|
|
612
|
+
else {
|
|
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
|
+
}
|
|
519
616
|
const fkCond = {
|
|
520
617
|
op: { val: 'ixpr', location: elem.name.location },
|
|
521
618
|
args: [
|
|
@@ -593,31 +690,39 @@ function tweakAssocs( model ) {
|
|
|
593
690
|
nav.item ? nav.item.location : expr.path[0].location );
|
|
594
691
|
}
|
|
595
692
|
}
|
|
596
|
-
else {
|
|
693
|
+
else { // from ON cond of element that was included (i.e. from included structure)
|
|
597
694
|
const root = expr.path[0]._navigation || expr.path[0]._artifact;
|
|
598
695
|
if (root.builtin || root.kind !== '$self' && root.kind !== 'element')
|
|
599
696
|
return;
|
|
600
697
|
const item = expr.path[root.kind === '$self' ? 1 : 0];
|
|
601
698
|
if (!item)
|
|
602
|
-
return;
|
|
603
|
-
// corresponding elem in including structure
|
|
604
|
-
|
|
699
|
+
return; // just $self
|
|
700
|
+
// corresponding elem in including structure or…
|
|
701
|
+
let elem = (assoc._main.items || assoc._main).elements[item.id];
|
|
702
|
+
if (elem !== assoc && assoc.$syntax === 'calc') {
|
|
703
|
+
// … special case for calc elements where "elem" points to the referenced
|
|
704
|
+
// (possibly included) association. For _included_ calc elements of the form,
|
|
705
|
+
// `calc = assoc[…]` it holds: `elem === assoc`
|
|
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;
|
|
711
|
+
}
|
|
605
712
|
if (!elem)
|
|
606
713
|
return; // See #11755
|
|
607
|
-
if (!(
|
|
608
|
-
elem === item._artifact || // redirection for explicit def
|
|
714
|
+
if (!(elem === item._artifact || // redirection for explicit def
|
|
609
715
|
elem._origin === item._artifact)) {
|
|
610
716
|
const art = assoc._origin;
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
} );
|
|
717
|
+
// eslint-disable-next-line max-len
|
|
718
|
+
warning( 'rewrite-shadowed', [ elem.name.location, elem ], { art: art && effectiveType( art ) }, {
|
|
719
|
+
// eslint-disable-next-line max-len
|
|
720
|
+
std: 'This element is not originally referred to in the ON-condition of association $(ART)',
|
|
721
|
+
// eslint-disable-next-line max-len
|
|
722
|
+
element: 'This element is not originally referred to in the ON-condition of association $(MEMBER) of $(ART)',
|
|
723
|
+
} );
|
|
619
724
|
}
|
|
620
|
-
rewritePath( expr, item, assoc,
|
|
725
|
+
rewritePath( expr, item, assoc, elem, null );
|
|
621
726
|
}
|
|
622
727
|
}
|
|
623
728
|
|
|
@@ -719,12 +824,12 @@ function tweakAssocs( model ) {
|
|
|
719
824
|
env = env.target._artifact?._effectiveType;
|
|
720
825
|
elem = setArtifactLink( item, env?.elements?.[name] );
|
|
721
826
|
|
|
722
|
-
if (elem
|
|
827
|
+
if (elem)
|
|
723
828
|
return elem;
|
|
724
829
|
// TODO: better (extra message), TODO: do it
|
|
725
830
|
error( 'query-undefined-element', [ item.location, assoc ],
|
|
726
831
|
{ id: name || item.id, '#': 'redirected' } );
|
|
727
|
-
return
|
|
832
|
+
return null;
|
|
728
833
|
}
|
|
729
834
|
}
|
|
730
835
|
|
|
@@ -826,4 +931,20 @@ function pathNavigation( ref ) {
|
|
|
826
931
|
return { navigation: root.elements[item.id], item, tableAlias: root };
|
|
827
932
|
}
|
|
828
933
|
|
|
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
|
+
|
|
829
950
|
module.exports = tweakAssocs;
|
package/lib/compiler/utils.js
CHANGED
|
@@ -90,6 +90,7 @@ function setArtifactLink( obj, value ) {
|
|
|
90
90
|
}
|
|
91
91
|
|
|
92
92
|
function linkToOrigin( origin, name, parent, prop, location, silentDep ) {
|
|
93
|
+
// TODO: should `key` propagation be part of this?
|
|
93
94
|
location ||= weakLocation( origin.name.location ); // not ??=
|
|
94
95
|
const elem = {
|
|
95
96
|
name: { location, id: origin.name.id },
|
|
@@ -153,12 +154,19 @@ function setMemberParent( elem, name, parent, prop ) {
|
|
|
153
154
|
setLink( elem, '_main', parent._main || parent );
|
|
154
155
|
}
|
|
155
156
|
|
|
157
|
+
function createAndLinkCalcDepElement( elem ) {
|
|
158
|
+
const r = { kind: '$calculation' }; // no name, like /items
|
|
159
|
+
elem.$calcDepElement = r;
|
|
160
|
+
setLink( r, '_outer', elem );
|
|
161
|
+
}
|
|
162
|
+
|
|
156
163
|
/**
|
|
157
164
|
* Adds a dependency user -> art with the given location.
|
|
158
165
|
*
|
|
159
166
|
* @param {XSN.Artifact} user
|
|
160
167
|
* @param {XSN.Artifact} art
|
|
161
168
|
* @param {XSN.Location} location
|
|
169
|
+
* @param {XSN.Artifact} [semanticLoc]
|
|
162
170
|
*/
|
|
163
171
|
function dependsOn( user, art, location, semanticLoc = undefined ) {
|
|
164
172
|
while (user._outer && !user.kind)
|
|
@@ -683,6 +691,7 @@ module.exports = {
|
|
|
683
691
|
dependsOn,
|
|
684
692
|
dependsOnSilent,
|
|
685
693
|
setMemberParent,
|
|
694
|
+
createAndLinkCalcDepElement,
|
|
686
695
|
storeExtension,
|
|
687
696
|
withAssociation,
|
|
688
697
|
pathName,
|
|
@@ -7,8 +7,9 @@ const {
|
|
|
7
7
|
EdmTypeFacetNames,
|
|
8
8
|
EdmPrimitiveTypeMap,
|
|
9
9
|
} = require('../EdmPrimitiveTypeDefinitions.js');
|
|
10
|
-
const { mapCdsToEdmType } = require('../edmUtils.js');
|
|
11
10
|
const { isBuiltinType } = require('../../model/csnUtils');
|
|
11
|
+
const { transformExpression } = require('../../transform/db/applyTransformations.js');
|
|
12
|
+
const { xprInAnnoProperties } = require('../../compiler/builtins');
|
|
12
13
|
|
|
13
14
|
/**
|
|
14
15
|
* Translate a given token stream expression into an edmJson representation
|
|
@@ -77,7 +78,7 @@ function xpr2edmJson( carrier, anno, location, options, messageFunctions ) {
|
|
|
77
78
|
};
|
|
78
79
|
//----------------------------------
|
|
79
80
|
// Error transformer
|
|
80
|
-
const notADynExpr = (parent, op, xpr, parentparent, parentprop, txt) => {
|
|
81
|
+
const notADynExpr = (parent, op, xpr, csnPath, parentparent, parentprop, txt) => {
|
|
81
82
|
error('odata-anno-xpr', location, {
|
|
82
83
|
anno, op: txt ??= op, '#': 'notadynexpr',
|
|
83
84
|
});
|
|
@@ -93,27 +94,27 @@ function xpr2edmJson( carrier, anno, location, options, messageFunctions ) {
|
|
|
93
94
|
//----------------------------------
|
|
94
95
|
// operators not supported as dynamic expression
|
|
95
96
|
'.': notADynExpr,
|
|
96
|
-
isNull: (p, o) => notADynExpr(p, o, null, null, null, 'is null'),
|
|
97
|
-
isNotNull: (p, o) => notADynExpr(p, o, null, null, null, 'is not null'),
|
|
97
|
+
isNull: (p, o) => notADynExpr(p, o, null, null, null, null, 'is null'),
|
|
98
|
+
isNotNull: (p, o) => notADynExpr(p, o, null, null, null, null, 'is not null'),
|
|
98
99
|
exists: notADynExpr,
|
|
99
100
|
'#': notADynExpr,
|
|
100
101
|
SELECT: notADynExpr,
|
|
101
|
-
SET: (p, o) => notADynExpr(p, o, null, null, null, 'UNION'),
|
|
102
|
+
SET: (p, o) => notADynExpr(p, o, null, null, null, null, 'UNION'),
|
|
102
103
|
like: notADynExpr,
|
|
103
104
|
new: notADynExpr,
|
|
104
105
|
};
|
|
105
106
|
|
|
106
107
|
//----------------------------------
|
|
107
108
|
// list is a $Collection => []
|
|
108
|
-
transform.list = (parent, prop, xpr, parentparent, parentprop) => {
|
|
109
|
+
transform.list = (parent, prop, xpr, csnPath, parentparent, parentprop) => {
|
|
109
110
|
parentparent[parentprop] = xpr.filter(a => a);
|
|
110
|
-
|
|
111
|
+
transformExpression(parentparent, transform);
|
|
111
112
|
};
|
|
112
113
|
// XPR
|
|
113
|
-
transform.xpr = (parent, prop, xpr, parentparent, parentprop) => {
|
|
114
|
+
transform.xpr = (parent, prop, xpr, csnPath, parentparent, parentprop) => {
|
|
114
115
|
// eliminate 'xpr' node by pulling up xpr node to its parent
|
|
115
116
|
parentparent[parentprop] = xpr;
|
|
116
|
-
|
|
117
|
+
transformExpression(parentparent, transform);
|
|
117
118
|
};
|
|
118
119
|
//----------------------------------
|
|
119
120
|
// CASE
|
|
@@ -144,12 +145,12 @@ function xpr2edmJson( carrier, anno, location, options, messageFunctions ) {
|
|
|
144
145
|
curIf.$If.push(caseExpr[i]);
|
|
145
146
|
parent.$If = edmIf.$If;
|
|
146
147
|
delete parent.case;
|
|
147
|
-
|
|
148
|
+
transformExpression(parent, transform);
|
|
148
149
|
};
|
|
149
150
|
transform.$If = noOp;
|
|
150
151
|
//----------------------------------
|
|
151
152
|
// Cast => $Cast
|
|
152
|
-
transform.cast = (parent, prop, castExpr, parentparent, parentprop) => {
|
|
153
|
+
transform.cast = (parent, prop, castExpr, csnPath, parentparent, parentprop) => {
|
|
153
154
|
const csnType = castExpr[0];
|
|
154
155
|
// try to resolve to final scalar base type and use that instead of derived type
|
|
155
156
|
if (!isBuiltinType(csnType.type)) {
|
|
@@ -161,7 +162,7 @@ function xpr2edmJson( carrier, anno, location, options, messageFunctions ) {
|
|
|
161
162
|
});
|
|
162
163
|
}
|
|
163
164
|
}
|
|
164
|
-
const edmTypeName = mapCdsToEdmType(csnType, messageFunctions, options.isV2(), false, location);
|
|
165
|
+
const edmTypeName = edmUtils.mapCdsToEdmType(csnType, messageFunctions, options.isV2(), false, location);
|
|
165
166
|
const typeFunc = { func: 'Type', args: [ { val: edmTypeName } ] };
|
|
166
167
|
const castFunc = { func: '$Cast', args: [ typeFunc, castExpr[1] ] };
|
|
167
168
|
|
|
@@ -184,7 +185,7 @@ function xpr2edmJson( carrier, anno, location, options, messageFunctions ) {
|
|
|
184
185
|
|
|
185
186
|
|
|
186
187
|
parentparent[parentprop] = castFunc;
|
|
187
|
-
|
|
188
|
+
transformExpression(parentparent, transform);
|
|
188
189
|
};
|
|
189
190
|
//----------------------------------
|
|
190
191
|
const evalArgs = (argDef, args, propName) => {
|
|
@@ -220,7 +221,7 @@ function xpr2edmJson( carrier, anno, location, options, messageFunctions ) {
|
|
|
220
221
|
evalArgs({ exact }, xpr, prop);
|
|
221
222
|
parent[opStr] = xpr;
|
|
222
223
|
delete parent[prop];
|
|
223
|
-
|
|
224
|
+
transformExpression(parent, transform);
|
|
224
225
|
};
|
|
225
226
|
//----------------------------------
|
|
226
227
|
// LOGICAL
|
|
@@ -249,12 +250,12 @@ function xpr2edmJson( carrier, anno, location, options, messageFunctions ) {
|
|
|
249
250
|
evalArgs({ min: 1 }, xpr[1].list, prop);
|
|
250
251
|
parent.$In = [ xpr[0], ...xpr[1].list ];
|
|
251
252
|
delete parent[prop];
|
|
252
|
-
|
|
253
|
+
transformExpression(parent, transform);
|
|
253
254
|
};
|
|
254
255
|
transform.$In = noOp;
|
|
255
|
-
transform.between = (parent, prop, xpr, parentparent, parentprop) => {
|
|
256
|
+
transform.between = (parent, prop, xpr, csnPath, parentparent, parentprop) => {
|
|
256
257
|
evalArgs({ exact: 2 }, xpr.slice(1), prop);
|
|
257
|
-
|
|
258
|
+
transformExpression(xpr, transform);
|
|
258
259
|
delete parent[prop];
|
|
259
260
|
parentparent[parentprop]
|
|
260
261
|
= {
|
|
@@ -264,22 +265,22 @@ function xpr2edmJson( carrier, anno, location, options, messageFunctions ) {
|
|
|
264
265
|
],
|
|
265
266
|
};
|
|
266
267
|
};
|
|
267
|
-
transform['||'] = (parent, prop, xpr, parentparent, parentprop) => {
|
|
268
|
+
transform['||'] = (parent, prop, xpr, csnPath, parentparent, parentprop) => {
|
|
268
269
|
evalArgs({ exact: 2 }, xpr, prop);
|
|
269
|
-
|
|
270
|
+
transformExpression(xpr, transform);
|
|
270
271
|
delete parent[prop];
|
|
271
272
|
parentparent[parentprop].$Apply = [ { $Function: 'odata.concat' }, ...xpr ];
|
|
272
273
|
};
|
|
273
274
|
//----------------------------------
|
|
274
275
|
// ARITHMETICAL AND UNARY
|
|
275
|
-
transform['+'] = (parent, prop, xpr, parentparent, parentprop) => {
|
|
276
|
+
transform['+'] = (parent, prop, xpr, csnPath, parentparent, parentprop) => {
|
|
276
277
|
if (Array.isArray(xpr)) {
|
|
277
278
|
op('$Add')(parent, prop, xpr);
|
|
278
279
|
}
|
|
279
280
|
else {
|
|
280
281
|
delete parent[prop];
|
|
281
282
|
parentparent[parentprop] = xpr;
|
|
282
|
-
|
|
283
|
+
transformExpression(parentparent, transform);
|
|
283
284
|
}
|
|
284
285
|
};
|
|
285
286
|
transform.$Add = noOp;
|
|
@@ -295,13 +296,13 @@ function xpr2edmJson( carrier, anno, location, options, messageFunctions ) {
|
|
|
295
296
|
// $DivBy, $Mod are functions
|
|
296
297
|
//----------------------------------
|
|
297
298
|
// LITERALS
|
|
298
|
-
transform.val = (parent, prop, xpr, parentparent, parentprop) => {
|
|
299
|
+
transform.val = (parent, prop, xpr, csnPath, parentparent, parentprop) => {
|
|
299
300
|
if (xpr === null)
|
|
300
301
|
parent.$Null = true;
|
|
301
302
|
else
|
|
302
303
|
parentparent[parentprop] = xpr;
|
|
303
304
|
};
|
|
304
|
-
transform.ref = (parent, prop, xpr, parentparent, parentprop) => {
|
|
305
|
+
transform.ref = (parent, prop, xpr, csnPath, parentparent, parentprop) => {
|
|
305
306
|
if (xpr.some(ps => ps.args || ps.where)) {
|
|
306
307
|
error('odata-anno-xpr-args', location, {
|
|
307
308
|
anno, elemref: parent, '#': 'wrongref',
|
|
@@ -311,7 +312,7 @@ function xpr2edmJson( carrier, anno, location, options, messageFunctions ) {
|
|
|
311
312
|
};
|
|
312
313
|
//----------------------------------
|
|
313
314
|
// Functions
|
|
314
|
-
transform.func = (parent, prop, xpr, parentparent, parentprop) => {
|
|
315
|
+
transform.func = (parent, prop, xpr, csnPath, parentparent, parentprop) => {
|
|
315
316
|
const rewriteArgs = (argDefs, evalVal = true) => {
|
|
316
317
|
Object.entries(argDefs).forEach(([ argName, argDef ]) => {
|
|
317
318
|
const [ foundProps, newArgs ]
|
|
@@ -694,7 +695,7 @@ function xpr2edmJson( carrier, anno, location, options, messageFunctions ) {
|
|
|
694
695
|
// $Record ???
|
|
695
696
|
$Collection: () => {
|
|
696
697
|
standard(parentparent, parentprop);
|
|
697
|
-
|
|
698
|
+
transformExpression(parentparent, transform);
|
|
698
699
|
},
|
|
699
700
|
$Path: () => {
|
|
700
701
|
oneArg(parent, xpr);
|
|
@@ -704,7 +705,7 @@ function xpr2edmJson( carrier, anno, location, options, messageFunctions ) {
|
|
|
704
705
|
anno, op: `${xpr}(…)`, meta: 'string', '#': 'wrongval_meta',
|
|
705
706
|
});
|
|
706
707
|
}
|
|
707
|
-
|
|
708
|
+
transformExpression(parentparent, transform);
|
|
708
709
|
},
|
|
709
710
|
$Null: () => {
|
|
710
711
|
parent[xpr] = true;
|
|
@@ -724,7 +725,7 @@ function xpr2edmJson( carrier, anno, location, options, messageFunctions ) {
|
|
|
724
725
|
funcDef.forEach(f => f());
|
|
725
726
|
else
|
|
726
727
|
funcDef();
|
|
727
|
-
|
|
728
|
+
transformExpression(parent, transform);
|
|
728
729
|
}
|
|
729
730
|
else {
|
|
730
731
|
const funcName = xpr.startsWith('odata.') ? xpr : `odata.${xpr}`;
|
|
@@ -740,7 +741,7 @@ function xpr2edmJson( carrier, anno, location, options, messageFunctions ) {
|
|
|
740
741
|
parentparent[parentprop].$Apply = [ { $Function: funcName }, ...(parent.args || []) ];
|
|
741
742
|
delete parentparent[parentprop].func;
|
|
742
743
|
delete parentparent[parentprop].args;
|
|
743
|
-
|
|
744
|
+
transformExpression(parentparent, transform);
|
|
744
745
|
}
|
|
745
746
|
}
|
|
746
747
|
else {
|
|
@@ -752,41 +753,14 @@ function xpr2edmJson( carrier, anno, location, options, messageFunctions ) {
|
|
|
752
753
|
delete parent[prop];
|
|
753
754
|
};
|
|
754
755
|
|
|
755
|
-
return
|
|
756
|
-
'=': (parent, prop, xpr, parentparent, parentprop) => {
|
|
757
|
-
|
|
758
|
-
|
|
756
|
+
return transformExpression({ annoVal }, {
|
|
757
|
+
'=': (parent, prop, xpr, csnPath, parentparent, parentprop) => {
|
|
758
|
+
if (parent?.['='] !== undefined && xprInAnnoProperties.some(xProp => parent[xProp] !== undefined)) {
|
|
759
|
+
delete parent['='];
|
|
760
|
+
parentparent[parentprop] = transformExpression({ $edmJson: parseExpr( parent, { array: false }) }, transform);
|
|
761
|
+
}
|
|
759
762
|
},
|
|
760
763
|
}).annoVal;
|
|
761
|
-
|
|
762
|
-
function applyTransformations( parent, customTransformers ) {
|
|
763
|
-
function standard( _parent, _prop, node ) {
|
|
764
|
-
if (!node || typeof node !== 'object' ||
|
|
765
|
-
!{}.propertyIsEnumerable.call( _parent, _prop ) ||
|
|
766
|
-
(typeof _prop === 'string' && _prop.startsWith('@')))
|
|
767
|
-
return;
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
if (Array.isArray(node)) {
|
|
771
|
-
node.forEach( (n, i) => standard( node, i, n ) );
|
|
772
|
-
}
|
|
773
|
-
else {
|
|
774
|
-
for (const name of Object.getOwnPropertyNames( node )) {
|
|
775
|
-
const ct = customTransformers[name];
|
|
776
|
-
if (ct) {
|
|
777
|
-
if (Array.isArray(ct))
|
|
778
|
-
ct.forEach(cti => cti(node, name, node[name], _parent, _prop));
|
|
779
|
-
else
|
|
780
|
-
ct(node, name, node[name], _parent, _prop);
|
|
781
|
-
}
|
|
782
|
-
standard(node, name, node[name]);
|
|
783
|
-
}
|
|
784
|
-
}
|
|
785
|
-
}
|
|
786
|
-
for (const name of Object.getOwnPropertyNames( parent ))
|
|
787
|
-
standard( parent, name, parent[name] );
|
|
788
|
-
return parent;
|
|
789
|
-
}
|
|
790
764
|
}
|
|
791
765
|
|
|
792
766
|
// Not everything that can occur in OData annotations can be expressed with
|
|
@@ -302,7 +302,7 @@ function csn2annotationEdm( reqDefs, csnVocabularies, serviceName,
|
|
|
302
302
|
if (knownAnnos.length === 0)
|
|
303
303
|
return;
|
|
304
304
|
}
|
|
305
|
-
if (isBetaEnabled(options, '
|
|
305
|
+
if (isBetaEnabled(options, 'odataAnnotationExpressions')) {
|
|
306
306
|
knownAnnos.forEach((knownAnno) => {
|
|
307
307
|
if (knownAnno.search(/\.\$edmJson\./g) < 0)
|
|
308
308
|
carrier[knownAnno] = xpr2edmJson(carrier, knownAnno, location, options, messageFunctions);
|
|
@@ -841,8 +841,9 @@ function csn2annotationEdm( reqDefs, csnVocabularies, serviceName,
|
|
|
841
841
|
else {
|
|
842
842
|
// regular record
|
|
843
843
|
if (dTypeIsACollection) {
|
|
844
|
-
message('odata-anno-value', msg.location,
|
|
845
|
-
|
|
844
|
+
message('odata-anno-value', msg.location, {
|
|
845
|
+
anno: msg.anno(), str: 'structured', type: dTypeName, '#': 'incompval',
|
|
846
|
+
});
|
|
846
847
|
}
|
|
847
848
|
oTarget.append(generateRecord(cAnnoValue, oTermName, dTypeName, dTypeIsACollection, msg));
|
|
848
849
|
}
|
|
@@ -1100,8 +1101,15 @@ function csn2annotationEdm( reqDefs, csnVocabularies, serviceName,
|
|
|
1100
1101
|
}
|
|
1101
1102
|
}
|
|
1102
1103
|
|
|
1103
|
-
if ( [
|
|
1104
|
-
resolvedType
|
|
1104
|
+
if ( EdmPathTypeMap[resolvedType] ) {
|
|
1105
|
+
if (resolvedType === 'Edm.AnyPropertyPath') {
|
|
1106
|
+
resolvedType = 'PropertyPath';
|
|
1107
|
+
typeName = resolvedType;
|
|
1108
|
+
}
|
|
1109
|
+
else {
|
|
1110
|
+
resolvedType = resolvedType.split('.')[1];
|
|
1111
|
+
}
|
|
1112
|
+
}
|
|
1105
1113
|
|
|
1106
1114
|
return {
|
|
1107
1115
|
name: typeName,
|
|
@@ -40,11 +40,10 @@ function preprocessAnnotations( csn, serviceName, options ) {
|
|
|
40
40
|
const keyNames = Object.keys(target.elements).filter(x => target.elements[x].key && !target.elements[x].target);
|
|
41
41
|
if (keyNames.length === 0) {
|
|
42
42
|
keyNames.push('MISSING');
|
|
43
|
-
message('odata-anno-preproc',
|
|
44
|
-
'target $(NAME) has no key');
|
|
43
|
+
message('odata-anno-preproc', assoc.$path, { anno, name: targetName, '#': 'nokey' } );
|
|
45
44
|
}
|
|
46
45
|
else if (keyNames.length > 1) {
|
|
47
|
-
message('odata-anno-preproc',
|
|
46
|
+
message('odata-anno-preproc', assoc.$path, { anno, name: targetName, '#': 'multkeys' });
|
|
48
47
|
}
|
|
49
48
|
|
|
50
49
|
return keyNames[0];
|