@sap/cds-compiler 6.9.2 → 7.0.1
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 +86 -2
- package/bin/cdsc.js +4 -33
- package/doc/IncompatibleChanges_v7.md +639 -0
- package/lib/api/main.js +4 -56
- package/lib/api/options.js +6 -15
- package/lib/api/validate.js +1 -0
- package/lib/base/builtins.js +1 -2
- package/lib/base/csnRefs.js +2 -6
- package/lib/base/message-registry.js +82 -76
- package/lib/base/messages.js +23 -4
- package/lib/base/optionProcessor.js +2 -72
- package/lib/base/specialOptions.js +20 -17
- package/lib/checks/defaultValues.js +1 -39
- package/lib/checks/hasPersistedElements.js +19 -3
- package/lib/checks/parameters.js +0 -34
- package/lib/checks/selectItems.js +2 -38
- package/lib/checks/typeParameters.js +162 -0
- package/lib/checks/validator.js +5 -8
- package/lib/compiler/assert-consistency.js +19 -5
- package/lib/compiler/checks.js +47 -43
- package/lib/compiler/define.js +6 -6
- package/lib/compiler/extend.js +102 -111
- package/lib/compiler/generate.js +4 -8
- package/lib/compiler/populate.js +4 -7
- package/lib/compiler/propagator.js +9 -9
- package/lib/compiler/resolve.js +205 -7
- package/lib/compiler/shared.js +76 -82
- package/lib/compiler/tweak-assocs.js +102 -22
- package/lib/compiler/utils.js +57 -12
- package/lib/compiler/xpr-rewrite.js +2 -15
- package/lib/edm/annotations/edmJson.js +14 -10
- package/lib/edm/annotations/genericTranslation.js +3 -1
- package/lib/edm/annotations/preprocessAnnotations.js +9 -26
- package/lib/edm/csn2edm.js +27 -20
- package/lib/edm/edmUtils.js +25 -0
- package/lib/gen/CdlGrammar.checksum +1 -1
- package/lib/gen/CdlParser.js +2237 -2241
- package/lib/gen/Dictionary.json +17 -2
- package/lib/json/from-csn.js +67 -52
- package/lib/json/to-csn.js +28 -25
- package/lib/language/textUtils.js +0 -13
- package/lib/main.d.ts +34 -59
- package/lib/main.js +1 -1
- package/lib/model/csnUtils.js +9 -8
- package/lib/parsers/AstBuildingParser.js +45 -55
- package/lib/parsers/Lexer.js +2 -0
- package/lib/parsers/identifiers.js +0 -9
- package/lib/render/toCdl.js +41 -40
- package/lib/render/toSql.js +8 -1
- package/lib/render/utils/common.js +1 -1
- package/lib/render/utils/sql.js +2 -3
- package/lib/tool-lib/enrichCsn.js +1 -2
- package/lib/transform/db/applyTransformations.js +7 -5
- package/lib/transform/db/assertUnique.js +8 -51
- package/lib/transform/db/associations.js +1 -1
- package/lib/transform/db/cdsPersistence.js +1 -15
- package/lib/transform/db/expansion.js +9 -12
- package/lib/transform/db/flattening.js +1 -1
- package/lib/transform/db/groupByOrderBy.js +0 -16
- package/lib/transform/db/views.js +57 -161
- package/lib/transform/draft/db.js +2 -2
- package/lib/transform/forOdata.js +25 -14
- package/lib/transform/forRelationalDB.js +93 -301
- package/lib/transform/localized.js +33 -102
- package/lib/transform/odata/flattening.js +11 -2
- package/lib/transform/transformUtils.js +25 -3
- package/lib/transform/universalCsn/universalCsnEnricher.js +1 -2
- package/package.json +2 -2
- package/lib/render/toHdbcds.js +0 -1810
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
const { dictLocation, weakLocation, weakRefLocation } = require('../base/location');
|
|
6
6
|
|
|
7
7
|
const {
|
|
8
|
+
pushLink,
|
|
8
9
|
setLink,
|
|
9
10
|
setArtifactLink,
|
|
10
11
|
linkToOrigin,
|
|
@@ -28,7 +29,7 @@ const $inferred = Symbol.for( 'cds.$inferred' );
|
|
|
28
29
|
function tweakAssocs( model ) {
|
|
29
30
|
// Get shared functionality and the message function:
|
|
30
31
|
const {
|
|
31
|
-
info, warning, error,
|
|
32
|
+
info, warning, error, message,
|
|
32
33
|
} = model.$messageFunctions;
|
|
33
34
|
const {
|
|
34
35
|
traverseExpr,
|
|
@@ -42,6 +43,7 @@ function tweakAssocs( model ) {
|
|
|
42
43
|
navigationEnv,
|
|
43
44
|
redirectionChain,
|
|
44
45
|
resolveExprInAnnotations,
|
|
46
|
+
checkBacklinkPartner,
|
|
45
47
|
} = model.$functions;
|
|
46
48
|
|
|
47
49
|
Object.assign(model.$functions, {
|
|
@@ -52,15 +54,18 @@ function tweakAssocs( model ) {
|
|
|
52
54
|
// Phase 5: rewrite associations
|
|
53
55
|
model._entities.forEach( rewriteArtifact ); // _entities contains all definitions, sorted.
|
|
54
56
|
// Think hard whether an on condition rewrite can lead to a new cyclic
|
|
55
|
-
// dependency. If so, we need other messages anyway. TODO: probably
|
|
57
|
+
// dependency. If so, we need other messages anyway. TODO: probably do
|
|
56
58
|
// another cyclic check with testMode.js
|
|
57
59
|
forEachUserArtifact( model, 'definitions', function check( art ) {
|
|
58
|
-
|
|
60
|
+
if (art.on && !art.on.$inferred)
|
|
61
|
+
checkOnCondition( art.on, (art.kind !== 'mixin' ? 'on' : 'mixin-on'), art );
|
|
59
62
|
checkExpr( art.value, (art.$syntax === 'calc' ? 'calc' : 'column'), art );
|
|
60
63
|
|
|
61
64
|
if (art.kind === 'select')
|
|
62
65
|
forEachQueryExpr( art, checkExpr );
|
|
63
66
|
} );
|
|
67
|
+
if (model._associations)
|
|
68
|
+
model._associations.forEach( rewriteBacklink );
|
|
64
69
|
|
|
65
70
|
|
|
66
71
|
// create “super” ANNOTATE statements for annotations on unknown artifacts:
|
|
@@ -281,8 +286,12 @@ function tweakAssocs( model ) {
|
|
|
281
286
|
forEachGeneric( elem, 'elements', rewriteAssociation );
|
|
282
287
|
if (elem.targetAspect?.elements)
|
|
283
288
|
forEachGeneric( elem.targetAspect, 'elements', rewriteAssociation );
|
|
284
|
-
|
|
289
|
+
|
|
290
|
+
if (!originTarget( elem )) {
|
|
291
|
+
if (elem.$backlink?._redirected)
|
|
292
|
+
pushLink( model, '_associations', elem );
|
|
285
293
|
return;
|
|
294
|
+
}
|
|
286
295
|
|
|
287
296
|
// console.log(message( null, elem.location, elem,
|
|
288
297
|
// {art:assoc,target,ftype:JSON.stringify(ftype)}, 'Info','RA').toString())
|
|
@@ -304,13 +313,22 @@ function tweakAssocs( model ) {
|
|
|
304
313
|
if (!elem || elem.builtin) // safety
|
|
305
314
|
return;
|
|
306
315
|
}
|
|
316
|
+
// console.log('PBI:',elem.kind,elem._main?.name.id,elem.name.id,!!elem.$backlink)
|
|
317
|
+
if (elem.$backlink?._redirected)
|
|
318
|
+
pushLink( model, '_associations', elem );
|
|
319
|
+
|
|
307
320
|
chain.reverse();
|
|
308
321
|
for (const art of chain) {
|
|
322
|
+
// console.log('PBC:',art.kind,art._main.name.id,art.name.id,elem.name.id)
|
|
309
323
|
setLink( elem, '_status', null );
|
|
310
|
-
if (elem.on)
|
|
324
|
+
if (elem.on) {
|
|
311
325
|
rewriteCondition( art, elem );
|
|
312
|
-
|
|
326
|
+
if (elem.$backlink?._redirected && art.on)
|
|
327
|
+
propagateBacklink( art, elem );
|
|
328
|
+
}
|
|
329
|
+
else if (elem.foreignKeys) {
|
|
313
330
|
rewriteKeys( art, elem );
|
|
331
|
+
}
|
|
314
332
|
|
|
315
333
|
if (art.on)
|
|
316
334
|
removeManagedPropsFromUnmanaged( art );
|
|
@@ -319,6 +337,52 @@ function tweakAssocs( model ) {
|
|
|
319
337
|
}
|
|
320
338
|
}
|
|
321
339
|
|
|
340
|
+
function propagateBacklink( assoc, _originAssoc ) {
|
|
341
|
+
// const origin = originAssoc.$backlink;
|
|
342
|
+
// if (assoc.target._artifact === originAssoc.target._artifact) {
|
|
343
|
+
// // no redirection → backlink is the same → no need to traverse ON-condition
|
|
344
|
+
// const backlink = { location: weakLocation( origin.location ), target: {} };
|
|
345
|
+
// setLink( backlink, '_origin', origin._origin );
|
|
346
|
+
// setLink( backlink, '_outer', assoc );
|
|
347
|
+
// assoc.$backlink = backlink;
|
|
348
|
+
// // TODO: _redirected
|
|
349
|
+
// }
|
|
350
|
+
// else {
|
|
351
|
+
checkBacklinkPartner( assoc );
|
|
352
|
+
// console.log('PB:',assoc._main.name.id,assoc.name.id,originAssoc.name.id,
|
|
353
|
+
// !!originAssoc.on,!!assoc.on,!!assoc.$backlink)
|
|
354
|
+
// TODO check: there should no reason for a new message (we might need to
|
|
355
|
+
// omit calling checkBacklinkPartner() if rewriting the ON-condition failed)
|
|
356
|
+
model._associations.push( assoc );
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
function rewriteBacklink( elem ) {
|
|
360
|
+
const backlink = elem.$backlink;
|
|
361
|
+
// console.log('RW:',elem._main?.name?.id,elem.name.id,!!backlink)
|
|
362
|
+
if (!backlink)
|
|
363
|
+
return;
|
|
364
|
+
setLink( backlink.target, '_artifact', elem._main ); // TODO: mixin
|
|
365
|
+
// already the fkeys/ON of the backlink association checks that all matches:
|
|
366
|
+
const origin = backlink._origin;
|
|
367
|
+
if (origin.$backlink) {
|
|
368
|
+
message( 'ref-invalid-backlink', [ backlink.location, elem ],
|
|
369
|
+
{ '#': 'backlink', art: origin } );
|
|
370
|
+
setLink( backlink, '_redirected', null ); // no further processing
|
|
371
|
+
return;
|
|
372
|
+
}
|
|
373
|
+
if (backlink._redirected?.length === 0)
|
|
374
|
+
return;
|
|
375
|
+
// all of the following necessary?
|
|
376
|
+
setLink( backlink, '_main', elem._main );
|
|
377
|
+
setLink( backlink, '_parent', elem._parent );
|
|
378
|
+
setLink( backlink, '_effectiveType', backlink );
|
|
379
|
+
backlink.name = origin.name;
|
|
380
|
+
if (origin.on)
|
|
381
|
+
rewriteCondition( backlink, origin );
|
|
382
|
+
else if (origin.foreignKeys)
|
|
383
|
+
rewriteKeys( backlink, origin );
|
|
384
|
+
}
|
|
385
|
+
|
|
322
386
|
/**
|
|
323
387
|
* Remove properties from unmanaged association `elem` that are only valid
|
|
324
388
|
* on managed associations. Only set to `NULL` (special value for propagator),
|
|
@@ -395,12 +459,12 @@ function tweakAssocs( model ) {
|
|
|
395
459
|
elem;
|
|
396
460
|
// TODO: probably better to collect the non-projected foreign keys
|
|
397
461
|
// and have one message for all
|
|
398
|
-
error('rewrite-undefined-key', [ weakLocation( culprit.location ), elem ], {
|
|
399
|
-
'#': 'std',
|
|
462
|
+
error( 'rewrite-undefined-key', [ weakLocation( culprit.location ), elem ], {
|
|
463
|
+
'#': (elem.name ? 'std' : 'backlink'),
|
|
400
464
|
id: targetElement.path.map(p => p.id).join('.'),
|
|
401
465
|
target: alias._main,
|
|
402
|
-
name: elem.name
|
|
403
|
-
});
|
|
466
|
+
name: elem.name?.id,
|
|
467
|
+
} );
|
|
404
468
|
return null;
|
|
405
469
|
}
|
|
406
470
|
}
|
|
@@ -442,6 +506,7 @@ function tweakAssocs( model ) {
|
|
|
442
506
|
// TODO: re-check $self rewrite (with managed composition of aspects),
|
|
443
507
|
// and actually also $self inside anonymous aspect definitions
|
|
444
508
|
// (not entirely urgent as we do not analyse it further, at least sole "$self")
|
|
509
|
+
// `assoc` is the "origin" association whose ON-condition we rewrite
|
|
445
510
|
function rewriteCondition( elem, assoc ) {
|
|
446
511
|
// the ON condition might need to be rewritten even if the target stays the
|
|
447
512
|
// same (TODO later: set status whether rewrite changes anything),
|
|
@@ -464,6 +529,8 @@ function tweakAssocs( model ) {
|
|
|
464
529
|
const nav = (elem._main?.query && elem.value)
|
|
465
530
|
? pathNavigation( elem.value ) // redirected source elem or mixin
|
|
466
531
|
: { navigation: assoc }; // redirected user-provided
|
|
532
|
+
// console.log('RC:',elem._main.name.id,(elem._outer||elem).name.id,
|
|
533
|
+
// assoc.name.id,!!nav.navigation,elem._redirected?.length)
|
|
467
534
|
elem.on = copyExpr( assoc.on,
|
|
468
535
|
// replace location in ON except if from mixin element
|
|
469
536
|
nav.tableAlias && elem.name.location );
|
|
@@ -722,18 +789,19 @@ function tweakAssocs( model ) {
|
|
|
722
789
|
}
|
|
723
790
|
|
|
724
791
|
/**
|
|
725
|
-
*
|
|
726
|
-
*
|
|
727
|
-
*
|
|
728
|
-
*
|
|
792
|
+
* Rewrite reference `expr` in-place (this function does nothing if `expr` is
|
|
793
|
+
* not a reference). `expr` is part of the potentially to-be-rewritten
|
|
794
|
+
* ON-condition of the element `assoc`. `assoc` can be:
|
|
795
|
+
*
|
|
796
|
+
* - a column projecting an association of a query source: then `tableAlias`
|
|
797
|
+
* is the table alias for that query source
|
|
798
|
+
* - a column projecting a mixin definition
|
|
799
|
+
* - an included element (the "copy")
|
|
800
|
+
* - a backlink with an imaginary redirection to the current entity
|
|
801
|
+
*
|
|
802
|
+
* TODO: describe when `navEnv` is different to `tableAlias`.
|
|
729
803
|
*/
|
|
730
804
|
function rewriteExpr( expr, assoc, tableAlias, navEnv = tableAlias ) {
|
|
731
|
-
// Rewrite ON condition (resulting in outside perspective) for association
|
|
732
|
-
// 'assoc' in query or including entity from ON cond of mixin element /
|
|
733
|
-
// element in included structure / element in source ref/d by table alias.
|
|
734
|
-
|
|
735
|
-
// TODO: complain about $self (unclear semantics)
|
|
736
|
-
|
|
737
805
|
if (!expr.path || !expr._artifact)
|
|
738
806
|
return;
|
|
739
807
|
if (!assoc._main)
|
|
@@ -749,6 +817,17 @@ function tweakAssocs( model ) {
|
|
|
749
817
|
|
|
750
818
|
rewritePathForEnv( expr, navEnv, assoc );
|
|
751
819
|
}
|
|
820
|
+
else if (assoc._outer?.$backlink === assoc) { // backlink proxy
|
|
821
|
+
const root = expr.path[0]._navigation || expr.path[0]._artifact;
|
|
822
|
+
if (root.builtin || root.kind !== '$self' && root.kind !== 'element')
|
|
823
|
+
return;
|
|
824
|
+
const item = expr.path[root.kind === '$self' ? 1 : 0];
|
|
825
|
+
if (!item)
|
|
826
|
+
return; // just $self
|
|
827
|
+
// console.log('REB:',assoc.name?.id,expr.path.map(i=>i.id))
|
|
828
|
+
if (item.id === assoc.name.id)
|
|
829
|
+
rewritePath( expr, item, assoc, assoc, null );
|
|
830
|
+
}
|
|
752
831
|
else if (assoc._main.query) { // from ON cond of mixin element in query
|
|
753
832
|
// here also $calc
|
|
754
833
|
const root = expr.path[0]._navigation || expr.path[0]._artifact;
|
|
@@ -963,8 +1042,8 @@ function tweakAssocs( model ) {
|
|
|
963
1042
|
|
|
964
1043
|
/**
|
|
965
1044
|
* Rewrite the reference `ref` with first elem/mixin ref item `item` for user
|
|
966
|
-
* `assoc` (the query element)
|
|
967
|
-
* for item.
|
|
1045
|
+
* `assoc` (the query element). `elem` is usually the first (or preferred)
|
|
1046
|
+
* query element for item.
|
|
968
1047
|
*/
|
|
969
1048
|
function rewritePath( ref, item, assoc, elem, location ) {
|
|
970
1049
|
const { path } = ref;
|
|
@@ -1035,6 +1114,7 @@ function tweakAssocs( model ) {
|
|
|
1035
1114
|
* @param assoc Published association of query.
|
|
1036
1115
|
*/
|
|
1037
1116
|
function rewriteItem( elem, item, assoc, noError ) {
|
|
1117
|
+
// console.log('RPI:',elem._redirected?.map(r=>`${r.name.id}@${r._main?.name?.id||''}`))
|
|
1038
1118
|
if (!elem._redirected)
|
|
1039
1119
|
return true;
|
|
1040
1120
|
let name = item.id;
|
package/lib/compiler/utils.js
CHANGED
|
@@ -111,7 +111,7 @@ function linkToOrigin( origin, name, parent, prop, location, silentDep ) {
|
|
|
111
111
|
return elem;
|
|
112
112
|
}
|
|
113
113
|
|
|
114
|
-
function proxyCopyMembers( art, dictProp, originDict, location, kind
|
|
114
|
+
function proxyCopyMembers( art, dictProp, originDict, location, kind ) {
|
|
115
115
|
art[dictProp] = Object.create( null );
|
|
116
116
|
// TODO: set $inferred ? for dict?
|
|
117
117
|
for (const name in originDict) {
|
|
@@ -121,13 +121,10 @@ function proxyCopyMembers( art, dictProp, originDict, location, kind, tmpDepreca
|
|
|
121
121
|
location || origin.location, true );
|
|
122
122
|
member.$inferred = 'expanded';
|
|
123
123
|
// TODO throughout the compiler: do not set art.‹prop›.$inferred if art.$inferred
|
|
124
|
-
if (kind)
|
|
124
|
+
if (kind) // e.g. for dictionary of '$navElement's
|
|
125
125
|
member.kind = kind;
|
|
126
|
-
else if (origin.key
|
|
127
|
-
// TODO(v6): remove tmpDeprecated once `_noKeyPropagationWithExpansions` is removed
|
|
126
|
+
else if (origin.key)
|
|
128
127
|
member.key = Object.assign( { $inferred: 'expanded' }, origin.key );
|
|
129
|
-
if (kind && origin.masked) // TODO: remove!
|
|
130
|
-
member.masked = Object.assign( { $inferred: 'nav' }, origin.masked );
|
|
131
128
|
}
|
|
132
129
|
}
|
|
133
130
|
}
|
|
@@ -183,7 +180,7 @@ function createAndLinkCalcDepElement( elem ) {
|
|
|
183
180
|
setLink( r, '_outer', elem );
|
|
184
181
|
}
|
|
185
182
|
|
|
186
|
-
function initExprAnnoBlock( art, block ) {
|
|
183
|
+
function initExprAnnoBlock( art, block, error ) {
|
|
187
184
|
// remark: `art` could also be the extension
|
|
188
185
|
for (const prop in art) {
|
|
189
186
|
if (prop.charAt(0) !== '@')
|
|
@@ -192,10 +189,23 @@ function initExprAnnoBlock( art, block ) {
|
|
|
192
189
|
// _block links needed for `cast( … as Type )`, `[ ..., cast( … as Type ) ]`
|
|
193
190
|
if (anno.literal === 'array') {
|
|
194
191
|
anno.kind = '$annotation';
|
|
195
|
-
for (const item of anno.val)
|
|
192
|
+
for (const item of anno.val) {
|
|
196
193
|
setLink( item, '_block', block );
|
|
194
|
+
if (item.literal !== 'token' || item.val !== '...' || item.$errorReported != null)
|
|
195
|
+
continue;
|
|
196
|
+
item.$errorReported = art.kind && art.kind !== 'annotate' && art.kind !== 'extend';
|
|
197
|
+
if (item.$errorReported) {
|
|
198
|
+
error( null, [ item.location, art ], { offending: '...' },
|
|
199
|
+
'Unexpected $(OFFENDING) in the array of an annotation on a definition' );
|
|
200
|
+
item.$errorReported = 'syntax';
|
|
201
|
+
}
|
|
202
|
+
else if (item.upTo) {
|
|
203
|
+
item.$errorReported
|
|
204
|
+
= !checkUpToSpec( item.upTo, art, prop, true, error ) && 'syntax';
|
|
205
|
+
}
|
|
206
|
+
}
|
|
197
207
|
}
|
|
198
|
-
else if (anno.$tokenTexts) {
|
|
208
|
+
else if (anno.$tokenTexts || anno.sym || anno.struct) {
|
|
199
209
|
// remark: it wouldn't hurt to set it always...
|
|
200
210
|
anno.kind = '$annotation';
|
|
201
211
|
setLink( anno, '_block', block );
|
|
@@ -203,6 +213,34 @@ function initExprAnnoBlock( art, block ) {
|
|
|
203
213
|
}
|
|
204
214
|
}
|
|
205
215
|
|
|
216
|
+
function checkUpToSpec( upToSpec, art, annoName, noBoolOrNull, error ) {
|
|
217
|
+
const { literal, path } = upToSpec;
|
|
218
|
+
if (literal === 'struct') {
|
|
219
|
+
return Object.values( upToSpec.struct )
|
|
220
|
+
.reduce( ( ok, v ) => checkUpToSpec( v, art, annoName, false, error ) && ok, true );
|
|
221
|
+
// .reduce instead of .every because we want to see every sub error
|
|
222
|
+
}
|
|
223
|
+
else if (literal
|
|
224
|
+
? literal !== 'array' &&
|
|
225
|
+
(!noBoolOrNull || literal !== 'boolean' && literal !== 'null')
|
|
226
|
+
: path && !upToSpec.scope && !path.some( hasSpecialPathItemProp )) {
|
|
227
|
+
return true;
|
|
228
|
+
}
|
|
229
|
+
error( null, [ upToSpec.location, art ],
|
|
230
|
+
{ anno: annoName, code: '... up to', '#': literal || (path ? 'ref' : 'expr') },
|
|
231
|
+
{
|
|
232
|
+
std: 'Unexpected $(CODE) value type in the assignment of $(ANNO)',
|
|
233
|
+
array: 'Unexpected array as $(CODE) value in the assignment of $(ANNO)',
|
|
234
|
+
// eslint-disable-next-line @stylistic/max-len
|
|
235
|
+
struct: 'Unexpected structure as $(CODE) structure property value in the assignment of $(ANNO)',
|
|
236
|
+
boolean: 'Unexpected boolean as $(CODE) value in the assignment of $(ANNO)',
|
|
237
|
+
null: 'Unexpected null as $(CODE) value in the assignment of $(ANNO)',
|
|
238
|
+
ref: 'Unexpected complex reference as $(CODE) value in the assignment of $(ANNO)',
|
|
239
|
+
expr: 'Unexpected expression as $(CODE) value in the assignment of $(ANNO)',
|
|
240
|
+
} );
|
|
241
|
+
return false;
|
|
242
|
+
}
|
|
243
|
+
|
|
206
244
|
function initDollarSelf( art ) {
|
|
207
245
|
// TODO: use setMemberParent() ?
|
|
208
246
|
// TODO: also on 'namespace's? (test with annotation with checked ref on them)
|
|
@@ -314,6 +352,13 @@ function pathName( path ) {
|
|
|
314
352
|
return (path && !path.broken) ? path.map( id => id.id ).join( '.' ) : '';
|
|
315
353
|
}
|
|
316
354
|
|
|
355
|
+
function hasSpecialPathItemProp( item ) {
|
|
356
|
+
return item.args ||
|
|
357
|
+
item.where || item.groupBy || item.having || item.limit || item.orderBy ||
|
|
358
|
+
item.cardinality || item.$extra || item.$syntax;
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
|
|
317
362
|
/**
|
|
318
363
|
* Generates an XSN path out of the given name. Path segments are delimited by a dot.
|
|
319
364
|
* Each segment will have the given location assigned.
|
|
@@ -459,10 +504,9 @@ function forEachUserArtifact( model, prop, callback ) { // not enums
|
|
|
459
504
|
function forEachUserElementAndFKey( art, callback ) {
|
|
460
505
|
while (art.items)
|
|
461
506
|
art = art.items;
|
|
462
|
-
if (art.target)
|
|
507
|
+
if (art.target)
|
|
463
508
|
forEachUserDict( art, 'foreignKeys', callback );
|
|
464
|
-
|
|
465
|
-
}
|
|
509
|
+
|
|
466
510
|
if (art.targetAspect)
|
|
467
511
|
art = art.targetAspect;
|
|
468
512
|
forEachUserDict( art, 'elements', function element( elem ) {
|
|
@@ -855,6 +899,7 @@ module.exports = {
|
|
|
855
899
|
initBoundSelfParam,
|
|
856
900
|
withAssociation,
|
|
857
901
|
pathName,
|
|
902
|
+
hasSpecialPathItemProp,
|
|
858
903
|
augmentPath,
|
|
859
904
|
splitIntoPath,
|
|
860
905
|
copyExpr,
|
|
@@ -152,7 +152,6 @@ const {
|
|
|
152
152
|
} = require('./utils');
|
|
153
153
|
const { CompilerAssertion } = require('../base/error');
|
|
154
154
|
const { isBetaEnabled } = require('../base/specialOptions');
|
|
155
|
-
const { isSimpleCdlIdentifier } = require('../parsers/identifiers');
|
|
156
155
|
|
|
157
156
|
// Config object passed around all "rewrite" functions.
|
|
158
157
|
class AnnoRewriteConfig {
|
|
@@ -276,7 +275,7 @@ function xprRewriteFns( model ) {
|
|
|
276
275
|
const struct = Object.values(expr.struct);
|
|
277
276
|
return !!struct.find(val => rewriteAnnotationExpr( val, config ));
|
|
278
277
|
}
|
|
279
|
-
else if (expr.$tokenTexts) {
|
|
278
|
+
else if (expr.$tokenTexts) { // no expr.sym check needed here
|
|
280
279
|
// used to set `$tokenText` to true in case of rewritten annotation
|
|
281
280
|
config.tokenExpr = expr;
|
|
282
281
|
return traverseExpr( expr, 'annoRewrite', config.destination,
|
|
@@ -329,18 +328,6 @@ function xprRewriteFns( model ) {
|
|
|
329
328
|
if (hasError)
|
|
330
329
|
return traverseExpr.STOP;
|
|
331
330
|
|
|
332
|
-
if (expr.$tokenTexts === true) {
|
|
333
|
-
// TODO: do not do with Universal-CSN (and gensrc, but that does not matter)
|
|
334
|
-
// We rewrite the string value for backward compatibility with "old-school" tools that
|
|
335
|
-
// only understand the string representation. But we only do so for simple strings, which
|
|
336
|
-
// is the case if this path expression has $tokenTexts. It then is of the form `@a: (path)`.
|
|
337
|
-
const isSimpleStep = step => !step.where && !step.args && isSimpleCdlIdentifier( step.id );
|
|
338
|
-
if (expr.path.every(isSimpleStep)) {
|
|
339
|
-
expr.$tokenTexts = (expr.scope === 'param' ? ':' : '') +
|
|
340
|
-
expr.path.map( step => step.id ).join('.');
|
|
341
|
-
}
|
|
342
|
-
}
|
|
343
|
-
|
|
344
331
|
if (model.options.testMode) {
|
|
345
332
|
// re-resolve the modified path; all paths steps must match what we rewrote
|
|
346
333
|
const ref = { ...expr, path: [ ...expr.path.map(item => ({ ...item })) ] };
|
|
@@ -758,7 +745,7 @@ function isReturnParam( art ) {
|
|
|
758
745
|
|
|
759
746
|
/**
|
|
760
747
|
* Gets the artifact (e.g. element) that was expanded.
|
|
761
|
-
* `destination` is a sub
|
|
748
|
+
* `destination` is a sub artifact of that root and is an expanded element.
|
|
762
749
|
*
|
|
763
750
|
* - expandedRoot: Top-most structure that was expanded.
|
|
764
751
|
* - expandedRootType: The type of expandedRoot.
|
|
@@ -307,7 +307,7 @@ function xpr2edmJson( carrier, anno, location, options, messageFunctions, generi
|
|
|
307
307
|
// LITERALS
|
|
308
308
|
transform.val = (parent, prop, xpr, csnPath, parentParent, parentProp) => {
|
|
309
309
|
if (xpr === null)
|
|
310
|
-
|
|
310
|
+
parentParent[parentProp] = { $Null: true };
|
|
311
311
|
else
|
|
312
312
|
parentParent[parentProp] = xpr;
|
|
313
313
|
};
|
|
@@ -852,16 +852,20 @@ function xpr2edmJson( carrier, anno, location, options, messageFunctions, generi
|
|
|
852
852
|
delete parent[prop];
|
|
853
853
|
};
|
|
854
854
|
|
|
855
|
+
const exprHandler = (parent, _prop, _xpr, csnPath, parentParent, parentProp) => {
|
|
856
|
+
if (isAnnotationExpression(parent)) {
|
|
857
|
+
const edmJson = preTransformXpr(parent);
|
|
858
|
+
// csnPath to this certain expression is passed inside of the 'ctx' variable, in case it is needed for resolving types from the
|
|
859
|
+
// dictionary, for instance in enum symbols resolving, see transform['#']
|
|
860
|
+
parentParent[parentProp] = transformExpression({ $edmJson: edmJson }, undefined, transform, csnPath, { xprCsnPath: csnPath });
|
|
861
|
+
}
|
|
862
|
+
};
|
|
855
863
|
return transformExpression(carrier, anno, {
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
// dictionary, for instance in enum symbols resolving, see transform['#']
|
|
862
|
-
parentParent[parentProp] = transformExpression({ $edmJson: edmJson }, undefined, transform, csnPath, { xprCsnPath: csnPath });
|
|
863
|
-
}
|
|
864
|
-
},
|
|
864
|
+
ref: exprHandler,
|
|
865
|
+
xpr: exprHandler,
|
|
866
|
+
list: exprHandler,
|
|
867
|
+
val: exprHandler,
|
|
868
|
+
func: exprHandler,
|
|
865
869
|
}, location);
|
|
866
870
|
|
|
867
871
|
/**
|
|
@@ -855,7 +855,9 @@ function csn2annotationEdm( reqDefs, reqDefsUtils, csnVocabularies, serviceName,
|
|
|
855
855
|
const qualifier = i >= 0 ? termName.substring(i + 1) : undefined;
|
|
856
856
|
|
|
857
857
|
termNameWithoutQualifiers.split('.').forEach((id) => {
|
|
858
|
-
if (!edmUtils.
|
|
858
|
+
if (isV2() && !edmUtils.isODataV2SimpleIdentifier(id))
|
|
859
|
+
message('odata-invalid-name-v2', msg.location, { id });
|
|
860
|
+
else if (!edmUtils.isODataSimpleIdentifier(id))
|
|
859
861
|
message('odata-invalid-name', msg.location, { id });
|
|
860
862
|
});
|
|
861
863
|
newAnno = new Edm.Annotation(v, termNameWithoutQualifiers);
|
|
@@ -30,24 +30,6 @@ function preprocessAnnotations( csn, serviceName, options, messageFunctions ) {
|
|
|
30
30
|
return options.v && options.v[0];
|
|
31
31
|
}
|
|
32
32
|
|
|
33
|
-
// return value can be null is target has no key
|
|
34
|
-
function getKeyOfTargetOfManagedAssoc( anno, assoc ) {
|
|
35
|
-
// assoc.target can be the name of the target or the object itself
|
|
36
|
-
const targetName = (typeof assoc.target === 'object') ? assoc.target.name : assoc.target;
|
|
37
|
-
const target = (typeof assoc.target === 'object') ? assoc.target : csn.definitions[assoc.target];
|
|
38
|
-
|
|
39
|
-
const keyNames = Object.keys(target.elements).filter(x => target.elements[x].key && !target.elements[x].target);
|
|
40
|
-
if (keyNames.length === 0) {
|
|
41
|
-
keyNames.push('MISSING');
|
|
42
|
-
message('odata-anno-preproc', assoc.$path, { anno, name: targetName, '#': 'nokey' } );
|
|
43
|
-
}
|
|
44
|
-
else if (keyNames.length > 1) {
|
|
45
|
-
message('odata-anno-preproc', assoc.$path, { anno, name: targetName, '#': 'multkeys' });
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
return keyNames[0];
|
|
49
|
-
}
|
|
50
|
-
|
|
51
33
|
// ----------------------------------------------------------------------------------------------
|
|
52
34
|
// main annotation processors
|
|
53
35
|
// ----------------------------------------------------------------------------------------------
|
|
@@ -143,6 +125,11 @@ function preprocessAnnotations( csn, serviceName, options, messageFunctions ) {
|
|
|
143
125
|
return false;
|
|
144
126
|
}
|
|
145
127
|
|
|
128
|
+
// If carrier is an association, do nothing, as the anno only applies to Property, not to NavigationProperty.
|
|
129
|
+
// For a managed association, the anno has also been propagated to the generated FKs, which are processed here, too.
|
|
130
|
+
if (carrier.target)
|
|
131
|
+
return false;
|
|
132
|
+
|
|
146
133
|
// check on "type"? e.g. if present, it must be #fixed ... ?
|
|
147
134
|
|
|
148
135
|
// value list entity
|
|
@@ -200,14 +187,8 @@ function preprocessAnnotations( csn, serviceName, options, messageFunctions ) {
|
|
|
200
187
|
const label = carrier['@Common.ValueList.Label'] ||
|
|
201
188
|
carrier['@Common.Label'] || vlEntity['@Common.Label'] || enameShort;
|
|
202
189
|
|
|
203
|
-
// localDataProp
|
|
204
|
-
// name of the element carrying the value help annotation
|
|
205
|
-
// if this is a managed assoc, use fk field instead (if there is a single one)
|
|
190
|
+
// localDataProp = name of the element carrying the value help annotation
|
|
206
191
|
let localDataProp = carrierName.split('/').pop();
|
|
207
|
-
if (carrier.target && carrier.on === undefined) {
|
|
208
|
-
localDataProp = localDataProp + fkSeparator +
|
|
209
|
-
getKeyOfTargetOfManagedAssoc(anno, carrier);
|
|
210
|
-
}
|
|
211
192
|
|
|
212
193
|
// if this carrier is a generated foreign key field and the association is marked @cds.api.ignore
|
|
213
194
|
// rename the localDataProp to be 'assocName/key'
|
|
@@ -218,9 +199,11 @@ function preprocessAnnotations( csn, serviceName, options, messageFunctions ) {
|
|
|
218
199
|
}
|
|
219
200
|
|
|
220
201
|
// valueListProp: the (single) key field of the value list entity
|
|
202
|
+
// ignore the artificial key "IsActiveEntity" for draft-enabled entities
|
|
221
203
|
// if no key or multiple keys -> message
|
|
222
204
|
let valueListProp = null;
|
|
223
|
-
const keys = Object.keys(vlEntity.elements).filter( x => vlEntity.elements[x].key && !vlEntity.elements[x].target
|
|
205
|
+
const keys = Object.keys(vlEntity.elements).filter( x => vlEntity.elements[x].key && !vlEntity.elements[x].target &&
|
|
206
|
+
!(vlEntity['@odata.draft.enabled'] && x === 'IsActiveEntity')); // ignore added key for draft entities
|
|
224
207
|
if (keys.length === 0) {
|
|
225
208
|
message('odata-anno-preproc', [ ...location, anno ], { anno, name: enameFull, '#': 'vhlnokey' });
|
|
226
209
|
return false;
|
package/lib/edm/csn2edm.js
CHANGED
|
@@ -412,7 +412,9 @@ function csn2edmAll( _csn, _options, serviceNames, messageFunctions ) {
|
|
|
412
412
|
}
|
|
413
413
|
else {
|
|
414
414
|
schema.name.split('.').forEach((id) => {
|
|
415
|
-
if (!edmUtils.
|
|
415
|
+
if (options.isV2() && !edmUtils.isODataV2SimpleIdentifier(id))
|
|
416
|
+
message('odata-invalid-name-v2', loc, { id });
|
|
417
|
+
else if (!edmUtils.isODataSimpleIdentifier(id))
|
|
416
418
|
message('odata-invalid-name', loc, { id });
|
|
417
419
|
});
|
|
418
420
|
}
|
|
@@ -420,6 +422,8 @@ function csn2edmAll( _csn, _options, serviceNames, messageFunctions ) {
|
|
|
420
422
|
/** @type {any} */
|
|
421
423
|
const Schema = new Edm.Schema(v, schema.name, undefined /* unset alias */, schema._csn, /* annotations */ [], schema.container);
|
|
422
424
|
const EntityContainer = Schema._ec || (LeadSchema && LeadSchema._ec);
|
|
425
|
+
if (options.isV2() && EntityContainer && !edmUtils.isODataV2SimpleIdentifier(EntityContainer._edmAttributes.Name))
|
|
426
|
+
message('odata-invalid-name-v2', loc, { id: EntityContainer._edmAttributes.Name });
|
|
423
427
|
// now namespace and alias are used to create the fullQualified(name)
|
|
424
428
|
const schemaNamePrefix = `${ schema.name }.`;
|
|
425
429
|
const schemaAliasPrefix = schemaNamePrefix;
|
|
@@ -526,7 +530,9 @@ function csn2edmAll( _csn, _options, serviceNames, messageFunctions ) {
|
|
|
526
530
|
else if (entityCsn.$hasEntitySet && entityCsn.$edmKeyPaths.length === 0 && !isSingleton)
|
|
527
531
|
message('odata-missing-key', location);
|
|
528
532
|
|
|
529
|
-
if (!edmUtils.
|
|
533
|
+
if (options.isV2() && !edmUtils.isODataV2SimpleIdentifier(EntityTypeName))
|
|
534
|
+
message('odata-invalid-name-v2', location, { id: EntityTypeName });
|
|
535
|
+
else if (!edmUtils.isODataSimpleIdentifier(EntityTypeName))
|
|
530
536
|
message('odata-invalid-name', location, { id: EntityTypeName });
|
|
531
537
|
|
|
532
538
|
properties.forEach((p) => {
|
|
@@ -538,18 +544,10 @@ function csn2edmAll( _csn, _options, serviceNames, messageFunctions ) {
|
|
|
538
544
|
if (options.isV2() && p.$isCollection && !p._csn.target)
|
|
539
545
|
message('odata-unexpected-array', pLoc, { version: '2.0' });
|
|
540
546
|
|
|
541
|
-
if (!edmUtils.
|
|
547
|
+
if (options.isV2() && !edmUtils.isODataV2SimpleIdentifier(p._edmAttributes.Name))
|
|
548
|
+
message('odata-invalid-name-v2', pLoc, { id: p._edmAttributes.Name });
|
|
549
|
+
else if (!edmUtils.isODataSimpleIdentifier(p._edmAttributes.Name))
|
|
542
550
|
message('odata-invalid-name', pLoc, { id: p._edmAttributes.Name });
|
|
543
|
-
}
|
|
544
|
-
else if (options.isV2() && /^(_|\d)/.test(p._edmAttributes.Name)) {
|
|
545
|
-
// FIXME: Rewrite signalIllegalIdentifier function to be more flexible
|
|
546
|
-
message('odata-invalid-name', pLoc, {
|
|
547
|
-
'#': 'v2firstChar',
|
|
548
|
-
prop: p._edmAttributes.Name[0],
|
|
549
|
-
id: p._edmAttributes.Name,
|
|
550
|
-
version: '2.0',
|
|
551
|
-
});
|
|
552
|
-
}
|
|
553
551
|
});
|
|
554
552
|
|
|
555
553
|
// construct EntityType attributes
|
|
@@ -605,7 +603,9 @@ function csn2edmAll( _csn, _options, serviceNames, messageFunctions ) {
|
|
|
605
603
|
const properties = createProperties(elementsCsn, structuredTypeCsn)[0];
|
|
606
604
|
const location = [ 'definitions', structuredTypeCsn.name ];
|
|
607
605
|
|
|
608
|
-
if (!edmUtils.
|
|
606
|
+
if (options.isV2() && !edmUtils.isODataV2SimpleIdentifier(attributes.Name))
|
|
607
|
+
message('odata-invalid-name-v2', location, { id: attributes.Name });
|
|
608
|
+
else if (!edmUtils.isODataSimpleIdentifier(attributes.Name))
|
|
609
609
|
message('odata-invalid-name', location, { id: attributes.Name });
|
|
610
610
|
|
|
611
611
|
properties.forEach((p) => {
|
|
@@ -614,7 +614,9 @@ function csn2edmAll( _csn, _options, serviceNames, messageFunctions ) {
|
|
|
614
614
|
if (p._edmAttributes.Name === complexType._edmAttributes.Name)
|
|
615
615
|
message('odata-invalid-property-name', pLoc, { meta: structuredTypeCsn.kind });
|
|
616
616
|
|
|
617
|
-
if (!edmUtils.
|
|
617
|
+
if (options.isV2() && !edmUtils.isODataV2SimpleIdentifier(p._edmAttributes.Name))
|
|
618
|
+
message('odata-invalid-name-v2', pLoc, { id: p._edmAttributes.Name });
|
|
619
|
+
else if (!edmUtils.isODataSimpleIdentifier(p._edmAttributes.Name))
|
|
618
620
|
message('odata-invalid-name', pLoc, { id: p._edmAttributes.Name });
|
|
619
621
|
|
|
620
622
|
if (options.isV2()) {
|
|
@@ -740,7 +742,6 @@ function csn2edmAll( _csn, _options, serviceNames, messageFunctions ) {
|
|
|
740
742
|
? [ 'definitions', entityCsn.name, 'actions', actionCsn.name ]
|
|
741
743
|
: [ 'definitions', actionCsn.name ];
|
|
742
744
|
|
|
743
|
-
|
|
744
745
|
if (!edmUtils.isODataSimpleIdentifier(attributes.Name))
|
|
745
746
|
message('odata-invalid-name', location, { id: attributes.Name });
|
|
746
747
|
|
|
@@ -893,8 +894,8 @@ function csn2edmAll( _csn, _options, serviceNames, messageFunctions ) {
|
|
|
893
894
|
? [ 'definitions', entityCsn.name, 'actions', actionCsn.name ]
|
|
894
895
|
: [ 'definitions', actionCsn.name ];
|
|
895
896
|
|
|
896
|
-
if (!edmUtils.
|
|
897
|
-
message('odata-invalid-name', location, { id: attributes.Name });
|
|
897
|
+
if (options.isV2() && !edmUtils.isODataV2SimpleIdentifier(attributes.Name))
|
|
898
|
+
message('odata-invalid-name-v2', location, { id: attributes.Name });
|
|
898
899
|
|
|
899
900
|
const rt = actionCsn.returns && ((actionCsn.returns.items && actionCsn.returns.items.type) || actionCsn.returns.type);
|
|
900
901
|
if (rt) { // add EntitySet attribute only if return type is an entity
|
|
@@ -951,8 +952,9 @@ function csn2edmAll( _csn, _options, serviceNames, messageFunctions ) {
|
|
|
951
952
|
const param = new Edm.Parameter(v, { Name: parameterName }, parameterCsn, 'In' );
|
|
952
953
|
collectUsedType(parameterCsn);
|
|
953
954
|
edmTypeCompatibilityCheck(param, pLoc);
|
|
954
|
-
|
|
955
|
-
|
|
955
|
+
|
|
956
|
+
if (options.isV2() && !edmUtils.isODataV2SimpleIdentifier(parameterName))
|
|
957
|
+
message('odata-invalid-name-v2', pLoc, { id: parameterName });
|
|
956
958
|
|
|
957
959
|
// only scalar or structured type in V2 (not entity)
|
|
958
960
|
if (param._type &&
|
|
@@ -1096,6 +1098,11 @@ function csn2edmAll( _csn, _options, serviceNames, messageFunctions ) {
|
|
|
1096
1098
|
if (reuseAssoc)
|
|
1097
1099
|
return;
|
|
1098
1100
|
|
|
1101
|
+
const assocLocation = [ 'definitions', navigationProperty._csn._edmParentCsn.name,
|
|
1102
|
+
'elements', navigationProperty._edmAttributes.Name ];
|
|
1103
|
+
if (!edmUtils.isODataV2SimpleIdentifier(assocName))
|
|
1104
|
+
message('odata-invalid-name-v2', assocLocation, { id: assocName });
|
|
1105
|
+
|
|
1099
1106
|
// Create Association and AssociationSet if this is not a backlink association.
|
|
1100
1107
|
// Store association at navigation property because in case the Ends must be modified
|
|
1101
1108
|
// later by the partner (backlink) association
|