@sap/cds-compiler 5.5.0 → 5.6.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 +17 -1
- package/bin/cdsse.js +3 -0
- package/lib/base/message-registry.js +2 -0
- package/lib/checks/cdsMap.js +27 -0
- package/lib/checks/validator.js +3 -1
- package/lib/compiler/builtins.js +2 -0
- package/lib/compiler/define.js +1 -0
- package/lib/edm/annotations/vocabularyDefinitions.js +6 -0
- package/lib/gen/BaseParser.js +115 -76
- package/lib/gen/CdlParser.js +1106 -1104
- package/lib/gen/Dictionary.json +185 -6
- package/lib/model/cloneCsn.js +1 -5
- package/lib/parsers/AstBuildingParser.js +94 -50
- package/lib/parsers/CdlGrammar.g4 +55 -29
- package/lib/transform/db/expansion.js +3 -0
- package/lib/transform/forOdata.js +1 -1
- package/lib/transform/odata/createForeignKeys.js +92 -9
- package/lib/utils/objectUtils.js +13 -0
- package/package.json +1 -1
|
@@ -208,7 +208,7 @@ aspectDef[ art, outer ]
|
|
|
208
208
|
@finally{ this.attachLocation( $art ); }
|
|
209
209
|
:
|
|
210
210
|
( ASPECT
|
|
211
|
-
| <hide> ABSTRACT { this.warning( 'syntax-deprecated-abstract', this.lb().
|
|
211
|
+
| <hide> ABSTRACT { this.warning( 'syntax-deprecated-abstract', this.combineLocation( this.lb(), this.la() ) ); }
|
|
212
212
|
ENTITY
|
|
213
213
|
)
|
|
214
214
|
name=namePath[ 'Type' ] // TODO: Type?
|
|
@@ -418,7 +418,7 @@ elementDef[ outer, art = undefined ]
|
|
|
418
418
|
( KEY { $art.key = this.valueWithLocation( true ); } )?
|
|
419
419
|
( <hide> MASKED { $art.masked = this.valueWithLocation( true ); }
|
|
420
420
|
{ this.message( 'syntax-unsupported-masked', this.lb(), { keyword: 'masked' } ); } )?
|
|
421
|
-
( ELEMENT { $art.$syntax = 'element'; } )?
|
|
421
|
+
( <hide> ELEMENT { $art.$syntax = 'element'; } )?
|
|
422
422
|
Id['Element'] <prepare=elementRestriction, arg=elem>
|
|
423
423
|
{ this.addDef( $art, $outer, 'elements', 'element', this.identAst() ); }
|
|
424
424
|
{ this.docComment( $art ); } annoAssignMid[ $art ]*
|
|
@@ -507,7 +507,8 @@ mixinElementDef[ outer ] locals[ art = new XsnArtifact() ]
|
|
|
507
507
|
( assoc=ASSOCIATION cardinality[ $art ]? TO
|
|
508
508
|
| assoc=COMPOSITION cardinality[ $art ]? OF
|
|
509
509
|
)
|
|
510
|
-
card=ONE/MANY?
|
|
510
|
+
( <cond=noRepeatedCardinality> card=ONE/MANY )?
|
|
511
|
+
target=simplePath
|
|
511
512
|
{ this.setAssocAndComposition( $art, $assoc, $card, $target ); }
|
|
512
513
|
ON expr=condition { $art.on = $expr; }
|
|
513
514
|
;
|
|
@@ -515,7 +516,7 @@ mixinElementDef[ outer ] locals[ art = new XsnArtifact() ]
|
|
|
515
516
|
// Annotate and Extend: main definitions ----------------------------------------
|
|
516
517
|
|
|
517
518
|
annotateArtifact[ art, outer ]
|
|
518
|
-
@finally{ this.
|
|
519
|
+
@finally{ this.attachLocation( $art ); }
|
|
519
520
|
:
|
|
520
521
|
name=namePath[ 'Ext' ]
|
|
521
522
|
( // direct element annotation:
|
|
@@ -538,10 +539,11 @@ annotateArtifact[ art, outer ]
|
|
|
538
539
|
annotateActionsBlock[ $art ]?
|
|
539
540
|
)
|
|
540
541
|
)
|
|
542
|
+
{ this.checkWith( $keyword ); }
|
|
541
543
|
;
|
|
542
544
|
|
|
543
545
|
extendArtifact[ art, outer ]
|
|
544
|
-
@finally{ this.
|
|
546
|
+
@finally{ this.attachLocation( $art ); }
|
|
545
547
|
:
|
|
546
548
|
name=namePath[ 'Ext' ]
|
|
547
549
|
( // direct element annotation:
|
|
@@ -586,6 +588,7 @@ extendArtifact[ art, outer ]
|
|
|
586
588
|
DEFINITIONS artifactsBlock[ $art, this.lb() ]
|
|
587
589
|
)?
|
|
588
590
|
)
|
|
591
|
+
{ this.checkWith( $keyword ); }
|
|
589
592
|
;
|
|
590
593
|
|
|
591
594
|
extendService[ art, outer ]
|
|
@@ -864,11 +867,13 @@ typeExpression[ art ]
|
|
|
864
867
|
typeProperties[ $art ]?
|
|
865
868
|
|
|
|
866
869
|
assoc=ASSOCIATION <prepare=elementRestriction, arg=calc>
|
|
867
|
-
cardinality[ $art ]? TO
|
|
870
|
+
cardinality[ $art ]? TO
|
|
871
|
+
( <cond=noRepeatedCardinality> card=ONE/MANY )?
|
|
868
872
|
typeAssocProperties[ $art, $assoc, $card ]
|
|
869
873
|
|
|
|
870
874
|
assoc=COMPOSITION <prepare=elementRestriction, arg=calc>
|
|
871
|
-
cardinality[ $art ]? OF
|
|
875
|
+
cardinality[ $art ]? OF
|
|
876
|
+
( <cond=noRepeatedCardinality> card=ONE/MANY )?
|
|
872
877
|
( typeAssocProperties[ $art, $assoc, $card ]
|
|
873
878
|
| elementsBlock[ this.setAssocAndComposition( $art, $assoc, $card ) ]
|
|
874
879
|
{ $art.target.location = $art.target.elements[Symbol.for('cds.$location')]; }
|
|
@@ -987,6 +992,7 @@ typeNamedArgsList[ art ]
|
|
|
987
992
|
|
|
988
993
|
typeNamedArg[ art ]
|
|
989
994
|
:
|
|
995
|
+
// TODO: or keywords with guards for better code completion?
|
|
990
996
|
name=Id['typeparamname']
|
|
991
997
|
':'
|
|
992
998
|
( Number
|
|
@@ -1053,12 +1059,11 @@ projectionSpec returns[ default query = {} ]
|
|
|
1053
1059
|
@finally{ this.attachLocation($query); }
|
|
1054
1060
|
:
|
|
1055
1061
|
// TODO, currently just with simple ref
|
|
1056
|
-
PROJECTION
|
|
1062
|
+
PROJECTION <prepare=afterBrace> // v6: remove <prepare>
|
|
1063
|
+
{ $query = { op: this.valueWithLocation( 'SELECT' ) }; }
|
|
1057
1064
|
ON
|
|
1058
|
-
tab=fromRefWithOptAlias
|
|
1065
|
+
tab=fromRefWithOptAlias <prepare=afterBrace>
|
|
1059
1066
|
// TODO: this <prepare=afterBrace> is extremely strange... v6 forbid.
|
|
1060
|
-
// Deliberately set this via action (→ interpreter will not accept this)
|
|
1061
|
-
{ this.afterBrace(); }<always>
|
|
1062
1067
|
{ $query.from = tab; }
|
|
1063
1068
|
selectItemsList[ $query ]?
|
|
1064
1069
|
excludingClause[ $query ]?
|
|
@@ -1115,6 +1120,8 @@ selectQuery returns[ default query = {} ]
|
|
|
1115
1120
|
excludingClause[ $query ]?
|
|
1116
1121
|
|
|
|
1117
1122
|
( ALL/DISTINCT { $query.quantifier = this.valueWithLocation(); } )?
|
|
1123
|
+
// TODO TOOL: move <prepare> to all branches if "simple", or with special <…,attach>
|
|
1124
|
+
{;} <prepare=inSelectItem, arg=sqlStyle>
|
|
1118
1125
|
( '*' { $query.columns = [ this.valueWithLocation() ]; }
|
|
1119
1126
|
| selectItemDef[ ($query.columns = []) ]
|
|
1120
1127
|
)
|
|
@@ -1221,8 +1228,7 @@ fromRefWithOptAlias returns[ default expr = {} ]
|
|
|
1221
1228
|
(
|
|
1222
1229
|
AS Id['FromAlias'] { $expr.name = this.identAst(); }
|
|
1223
1230
|
|
|
|
1224
|
-
<cond=tableWithoutAs>
|
|
1225
|
-
// TODO: probably not necessary, TOOL already uses `default: this.giR()`
|
|
1231
|
+
// <cond=tableWithoutAs> not necessary, tool uses `default: this.giR()`
|
|
1226
1232
|
Id_restricted['FromAlias']
|
|
1227
1233
|
{ $expr.name = this.fragileAlias(); }
|
|
1228
1234
|
|
|
|
@@ -1237,6 +1243,7 @@ fromPath[ table, category ] locals[ pathItem ]
|
|
|
1237
1243
|
Id[ $category ] { $table.path.push( $pathItem = this.identAst() ); }
|
|
1238
1244
|
( fromArgumentsAndFilter[ $pathItem ] { $pathItem = null; } )?
|
|
1239
1245
|
(
|
|
1246
|
+
<cond=notAfterEntityArgOrFilter> // TODO TOOL: allow <hide=method>
|
|
1240
1247
|
'.' { if (!$pathItem && !$table.scope) {
|
|
1241
1248
|
$table.scope = $table.path.length; $category = 'ref';
|
|
1242
1249
|
this.warning( 'syntax-invalid-path-separator', this.lb(),
|
|
@@ -1352,24 +1359,24 @@ selectItemDef[ columns ] locals[ art = new XsnArtifact(), alias ]
|
|
|
1352
1359
|
{ $columns.push( $art ); } // TODO: probably too early
|
|
1353
1360
|
{ this.docComment( $art ); } annoAssignCol[ $art ]*
|
|
1354
1361
|
( <cond=modifierRestriction> VIRTUAL
|
|
1355
|
-
{ $art.virtual = this.valueWithLocation( true ); }
|
|
1362
|
+
{ $art.virtual = this.valueWithLocation( true ); }
|
|
1363
|
+
)?
|
|
1364
|
+
{;} <prepare=columnExpr, arg=key> // TOOL TODO: disappears without {;}
|
|
1356
1365
|
( <cond=modifierRestriction> KEY
|
|
1357
|
-
{ $art.key = this.valueWithLocation( true ); }
|
|
1366
|
+
{ $art.key = this.valueWithLocation( true ); }
|
|
1367
|
+
)?
|
|
1358
1368
|
(
|
|
1359
1369
|
expr=expression { $art.value = $expr; }
|
|
1360
1370
|
( as=AS Id['ItemAlias'] { $art.name = this.identAst(); }
|
|
1361
1371
|
| Id_restricted['ItemAlias'] { $art.name = this.fragileAlias( true ); }
|
|
1362
1372
|
| { $alias = this.classifyImplicitName( 'ItemImplicit', $expr ); }
|
|
1363
|
-
)
|
|
1364
|
-
// TODO: <cond> instead `reportExpandInline` for "expand/inline only w/ ref"
|
|
1365
|
-
(
|
|
1366
|
-
{ this.reportExpandInline( $art, false ); }
|
|
1367
|
-
nestedSelectItemsList[ $art, 'expand' ]
|
|
1368
|
-
excludingClause[ $art ]?
|
|
1369
1373
|
|
|
|
1374
|
+
// TODO: guard for ref-only expression can probably replace reportExpandInline
|
|
1370
1375
|
'.'
|
|
1371
1376
|
{ this.reportUnexpectedSpace( this.lb(), this.la().location, true ); } // TODO: no ERR
|
|
1372
1377
|
{ this.reportExpandInline( $art, $as || true ); }
|
|
1378
|
+
// no extra 'syntax-unexpected-alias' anymore,
|
|
1379
|
+
// 'syntax-unexpected-anno' reported in define.js
|
|
1373
1380
|
{ if ($alias) $alias.token.parsedAs = $alias.parsedAs; }
|
|
1374
1381
|
(
|
|
1375
1382
|
nestedSelectItemsList[ $art, 'inline' ]
|
|
@@ -1377,13 +1384,21 @@ selectItemDef[ columns ] locals[ art = new XsnArtifact(), alias ]
|
|
|
1377
1384
|
|
|
|
1378
1385
|
'*' { $art.inline = [ this.valueWithLocation() ]; }
|
|
1379
1386
|
)
|
|
1387
|
+
<exitRule>
|
|
1388
|
+
)
|
|
1389
|
+
// TODO: <cond> instead `reportExpandInline` for "expand/inline only w/ ref"
|
|
1390
|
+
(
|
|
1391
|
+
// TODO: guard for ref-only expression can probably replace:
|
|
1392
|
+
{ this.reportExpandInline( $art, false ); }
|
|
1393
|
+
nestedSelectItemsList[ $art, 'expand' ]
|
|
1394
|
+
excludingClause[ $art ]?
|
|
1380
1395
|
)?
|
|
1381
1396
|
|
|
|
1382
1397
|
nestedSelectItemsList[ $art, 'expand' ]
|
|
1383
1398
|
excludingClause[ $art ]?
|
|
1384
1399
|
AS Id['ItemAlias'] { $art.name = this.identAst(); }
|
|
1385
1400
|
)
|
|
1386
|
-
{ this.docComment( $art ); } annoAssignMid[ $art ]*
|
|
1401
|
+
{ this.docComment( $art ); } <prepare=columnExpr> annoAssignMid[ $art ]*
|
|
1387
1402
|
(
|
|
1388
1403
|
':'
|
|
1389
1404
|
(
|
|
@@ -1392,18 +1407,21 @@ selectItemDef[ columns ] locals[ art = new XsnArtifact(), alias ]
|
|
|
1392
1407
|
typeRefOptArgs[ $art ]
|
|
1393
1408
|
)
|
|
1394
1409
|
|
|
|
1410
|
+
// TODO: guard for ref-only expression ?
|
|
1395
1411
|
REDIRECTED TO target=simplePath { $art.target = $target; }
|
|
1396
1412
|
( ON cond=condition { $art.on = $cond; }
|
|
1397
1413
|
| foreignKeysBlock[ $art ]
|
|
1398
1414
|
)?
|
|
1399
1415
|
|
|
|
1400
|
-
//
|
|
1401
|
-
|
|
1416
|
+
( <cond=columnExpr> // arg=singleId
|
|
1417
|
+
assoc=ASSOCIATION { this.associationInSelectItem( $art ); }
|
|
1402
1418
|
cardinality[ $art ]? TO
|
|
1403
|
-
|
|
|
1419
|
+
| <cond=columnExpr> // arg=singleId
|
|
1420
|
+
assoc=COMPOSITION { this.associationInSelectItem( $art ); }
|
|
1404
1421
|
cardinality[ $art ]? OF
|
|
1405
1422
|
)
|
|
1406
|
-
card=ONE/MANY?
|
|
1423
|
+
( <cond=noRepeatedCardinality> card=ONE/MANY )?
|
|
1424
|
+
target=simplePath
|
|
1407
1425
|
{ this.setAssocAndComposition( $art, $assoc, $card, $target ); }
|
|
1408
1426
|
ON expr=condition { $art.on = $expr; }
|
|
1409
1427
|
)
|
|
@@ -1692,7 +1710,7 @@ options{ minTokensMatched = 1 }
|
|
|
1692
1710
|
)*
|
|
1693
1711
|
)
|
|
1694
1712
|
)
|
|
1695
|
-
')'
|
|
1713
|
+
')' { this.finalizeDictOrArray( $pathStep.args ); }
|
|
1696
1714
|
)?
|
|
1697
1715
|
// TODO: not with function!
|
|
1698
1716
|
cardinalityAndFilter[ ...$ ]?
|
|
@@ -1972,11 +1990,19 @@ annoValue returns[ default value = {} ]
|
|
|
1972
1990
|
( sub=annoValue { $value.val.push( $sub ) }
|
|
1973
1991
|
|
|
|
1974
1992
|
<cond=arrayAnno, arg=ellipsis> ellipsis='...'
|
|
1975
|
-
( UP TO upTo=annoValue
|
|
1976
|
-
|
|
1993
|
+
( UP TO upTo=annoValue
|
|
1994
|
+
{ $value.val.push( { literal: 'token', val: '...', location: $ellipsis.location, upTo: $upTo } ); }
|
|
1995
|
+
| { $value.val.push( { literal: 'token', val: '...', location: $ellipsis.location } ); }
|
|
1996
|
+
)
|
|
1997
|
+
// TODO TOOL: if at last good state the command is ['g'],resume after the
|
|
1998
|
+
// gotos, do not execute its actions - ?
|
|
1999
|
+
// ( UP TO upTo=annoValue | { $upTo = undefined; } )
|
|
2000
|
+
// { $value.val.push( { literal: 'token', val: '...', location: $ellipsis.location, upTo: $upTo } ); }
|
|
1977
2001
|
)
|
|
1978
2002
|
( ',' | <exitLoop> )
|
|
1979
2003
|
)*
|
|
2004
|
+
// TODO TOOL: allow ( <cond=arrayAnno, arg=bracket> ']' )
|
|
2005
|
+
{ this.ec( 'arrayAnno', 'bracket' ); }<always>
|
|
1980
2006
|
']'
|
|
1981
2007
|
|
|
|
1982
2008
|
'(' $value=condition ')' { $value.$tokenTexts = this.ruleTokensText(); }
|
|
@@ -652,6 +652,9 @@ function expandStructureReferences( csn, options, pathDelimiter, messageFunction
|
|
|
652
652
|
if (typeof root.$env === 'string' && (isComplexOrNestedQuery || options.transformation !== 'odata' || root.$env === pathId(obj.ref[0])))
|
|
653
653
|
obj.ref = [ root.$env, ...obj.ref ];
|
|
654
654
|
|
|
655
|
+
if (iterateOptions.keepKeysOrigin)
|
|
656
|
+
setProp(obj, '$originalKeyRef', { ref: root.ref, as: root.as });
|
|
657
|
+
|
|
655
658
|
return obj;
|
|
656
659
|
});
|
|
657
660
|
}
|
|
@@ -188,7 +188,7 @@ function transform4odataWithCsn(inputModel, options, messageFunctions) {
|
|
|
188
188
|
if (!structuredOData) {
|
|
189
189
|
expansion.expandStructureReferences(csn, options, '_',
|
|
190
190
|
{ error, info, throwWithAnyError }, csnUtils,
|
|
191
|
-
{ skipArtifact: isExternalServiceMember });
|
|
191
|
+
{ skipArtifact: isExternalServiceMember, keepKeysOrigin: true });
|
|
192
192
|
}
|
|
193
193
|
|
|
194
194
|
createForeignKeyElements(csn, options, messageFunctions, csnUtils, { skipArtifact: isExternalServiceMember });
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
const { isBuiltinType } = require('../../base/builtins');
|
|
4
4
|
const { setProp } = require('../../base/model');
|
|
5
|
-
const { applyTransformations, implicitAs, findAnnotationExpression, copyAnnotations, isDeepEqual } = require('../../model/csnUtils');
|
|
5
|
+
const { transformAnnotationExpression, applyTransformations, implicitAs, findAnnotationExpression, copyAnnotations, isDeepEqual } = require('../../model/csnUtils');
|
|
6
6
|
const { EdmTypeFacetNames } = require('../../edm/EdmPrimitiveTypeDefinitions');
|
|
7
7
|
|
|
8
8
|
function createForeignKeyElements(csn, options, messageFunctions, csnUtils, iterateOptions = {}) {
|
|
@@ -60,6 +60,8 @@ function createForeignKeyElements(csn, options, messageFunctions, csnUtils, iter
|
|
|
60
60
|
if (options.transformation === 'effective')
|
|
61
61
|
delete element.default;
|
|
62
62
|
}
|
|
63
|
+
|
|
64
|
+
adaptAnnotationsRefs(generatedForeignKeys, csnUtils);
|
|
63
65
|
setProp(element, '$generatedForeignKeys', generatedForeignKeys.map(gfk => gfk[0]));
|
|
64
66
|
orderedElements.push(...generatedForeignKeys);
|
|
65
67
|
|
|
@@ -69,9 +71,9 @@ function createForeignKeyElements(csn, options, messageFunctions, csnUtils, iter
|
|
|
69
71
|
elementsAccumulator[name] = element;
|
|
70
72
|
return elementsAccumulator;
|
|
71
73
|
}, Object.create(null));
|
|
72
|
-
}
|
|
74
|
+
}
|
|
73
75
|
|
|
74
|
-
function createForeignKeysForElement(csnUtils, path, element, prefix, csn, options, pathDelimiter, lvl = 0 ) {
|
|
76
|
+
function createForeignKeysForElement(csnUtils, path, element, prefix, csn, options, pathDelimiter, lvl = 0, originalkey = {} ) {
|
|
75
77
|
const special$self = !csn?.definitions?.$self && '$self';
|
|
76
78
|
const isInspectRefResult = !Array.isArray(path);
|
|
77
79
|
|
|
@@ -109,7 +111,16 @@ function createForeignKeysForElement(csnUtils, path, element, prefix, csn, optio
|
|
|
109
111
|
const continuePath = getContinuePath([ 'keys', keyIndex ]);
|
|
110
112
|
const alias = key.as || implicitAs(key.ref);
|
|
111
113
|
const result = csnUtils.inspectRef(continuePath);
|
|
112
|
-
|
|
114
|
+
let gfks = createForeignKeysForElement(csnUtils, result, result.art, alias, csn, options, pathDelimiter, lvl + 1,
|
|
115
|
+
lvl === 0 ? { ref: key.ref, as: key.as, $path: key.$path, $originalKeyRef: key.$originalKeyRef } : originalkey);
|
|
116
|
+
if (lvl === 0) {
|
|
117
|
+
gfks.forEach(gfk => copyAnnotations(key, gfk[1]));
|
|
118
|
+
// once applied -> remove the annotations from the keys array, to keep the OData CSN size as small as possible
|
|
119
|
+
Object.keys(key).forEach( prop => {
|
|
120
|
+
if (prop[0] === '@') delete key[prop]
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
fks = fks.concat(gfks);
|
|
113
124
|
});
|
|
114
125
|
}
|
|
115
126
|
// the element is a structure
|
|
@@ -118,7 +129,7 @@ function createForeignKeysForElement(csnUtils, path, element, prefix, csn, optio
|
|
|
118
129
|
// Skip already produced foreign keys
|
|
119
130
|
if (!elem['@odata.foreignKey4']) {
|
|
120
131
|
const continuePath = getContinuePath([ 'elements', elemName ]);
|
|
121
|
-
fks = fks.concat(createForeignKeysForElement(csnUtils, continuePath, elem, elemName, csn, options, pathDelimiter, lvl + 1));
|
|
132
|
+
fks = fks.concat(createForeignKeysForElement(csnUtils, continuePath, elem, elemName, csn, options, pathDelimiter, lvl + 1, originalkey));
|
|
122
133
|
}
|
|
123
134
|
});
|
|
124
135
|
}
|
|
@@ -130,7 +141,7 @@ function createForeignKeysForElement(csnUtils, path, element, prefix, csn, optio
|
|
|
130
141
|
if (element[prop] !== undefined)
|
|
131
142
|
newFk[prop] = element[prop];
|
|
132
143
|
});
|
|
133
|
-
return [ [ prefix, newFk ] ];
|
|
144
|
+
return [ [ prefix, newFk, originalkey ] ];
|
|
134
145
|
}
|
|
135
146
|
|
|
136
147
|
fks.forEach((fk) => {
|
|
@@ -140,9 +151,13 @@ function createForeignKeysForElement(csnUtils, path, element, prefix, csn, optio
|
|
|
140
151
|
if (lvl === 0) {
|
|
141
152
|
if (options.transformation !== 'effective')
|
|
142
153
|
fk[1]['@odata.foreignKey4'] = prefix;
|
|
143
|
-
|
|
144
|
-
const
|
|
145
|
-
|
|
154
|
+
|
|
155
|
+
const allowedOverwriteAnnotationNames = ['@odata.Type', ...EdmTypeFacetNames.map(f => `@odata.${f}`)];
|
|
156
|
+
const validAnnoNames = Object.keys(element).filter(pn => pn[0] === '@' && !allowedOverwriteAnnotationNames.includes(pn) && !findAnnotationExpression(element, pn));
|
|
157
|
+
copyAnnotations(element, fk[1], false, {}, validAnnoNames);
|
|
158
|
+
const overwriteAnnoNames = Object.keys(element).filter(pn => allowedOverwriteAnnotationNames.includes(pn) && !findAnnotationExpression(element, pn));
|
|
159
|
+
copyAnnotations(element, fk[1], true, {}, overwriteAnnoNames);
|
|
160
|
+
|
|
146
161
|
// propagate not null to final foreign key
|
|
147
162
|
for (const prop of [ 'notNull', 'key' ]) {
|
|
148
163
|
if (element[prop] !== undefined)
|
|
@@ -175,6 +190,74 @@ function createForeignKeysForElement(csnUtils, path, element, prefix, csn, optio
|
|
|
175
190
|
return [ ...path, ...additions ];
|
|
176
191
|
}
|
|
177
192
|
}
|
|
193
|
+
|
|
194
|
+
function adaptAnnotationsRefs(generatedForeignKeys, csnUtils) {
|
|
195
|
+
let reportedErrorsForAnnoPath = {};
|
|
196
|
+
generatedForeignKeys.forEach(gfk => {
|
|
197
|
+
Object.entries(gfk[1]).forEach(([key, value]) => {
|
|
198
|
+
if (key[0] !== '@') return;
|
|
199
|
+
|
|
200
|
+
transformAnnotationExpression(gfk[1], key, {
|
|
201
|
+
ref: (_parent, _prop, ref, path, _p, _ppn, ctx) => {
|
|
202
|
+
if (ref[0] !== '$self') {
|
|
203
|
+
|
|
204
|
+
const { art } = csnUtils.inspectRef(getOriginatingKeyPath(gfk, path));
|
|
205
|
+
if (csnUtils.isManagedAssociation(art)) {
|
|
206
|
+
if (!reportedErrorsForAnnoPath[path]) {
|
|
207
|
+
error('odata-anno-xpr-ref', path, { elemref: { ref }, anno: key, '#': 'fk_substitution' });
|
|
208
|
+
reportedErrorsForAnnoPath[path] = true;
|
|
209
|
+
}
|
|
210
|
+
} else {
|
|
211
|
+
const gfkForRef = findGeneratedForeignKeyForKeyRef(generatedForeignKeys, ref);
|
|
212
|
+
if (gfkForRef.length === 1) {
|
|
213
|
+
ref[0] = gfkForRef[0][0];
|
|
214
|
+
|
|
215
|
+
if (ctx?.annoExpr?.['=']) {
|
|
216
|
+
ctx.annoExpr['='] = true;
|
|
217
|
+
}
|
|
218
|
+
} else {
|
|
219
|
+
// check if the annotation reference points to a structure that has been expanded,
|
|
220
|
+
// if so -> report an error
|
|
221
|
+
const foundInOriginalRef = findOriginalRef(generatedForeignKeys.filter(gfk => gfk[2].$originalKeyRef), ref);
|
|
222
|
+
// references to expanded structures in flat mode will be found in the $originalKeyRef
|
|
223
|
+
// and in strucred mode more than one match will be found in the generated foreign keys
|
|
224
|
+
if ((foundInOriginalRef.length || gfkForRef.length > 1) && !reportedErrorsForAnnoPath[path]) {
|
|
225
|
+
error('odata-anno-xpr-ref', path, { elemref: { ref }, anno: key, '#': 'fk_substitution' });
|
|
226
|
+
reportedErrorsForAnnoPath[path] = true;
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
}, value?.$path?.slice(0, value.$path.length - 1));
|
|
233
|
+
});
|
|
234
|
+
});
|
|
235
|
+
|
|
236
|
+
// During tuple expansion, the key ref object looses the $path, therefore
|
|
237
|
+
// it needs to be extracted from the anno path
|
|
238
|
+
function getOriginatingKeyPath(gfk, path) {
|
|
239
|
+
return gfk[2].$path || path.slice(0, path.findIndex(ps => ps[0] === '@'));
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
// Loops through the generated foreign keys for this entity
|
|
243
|
+
// and filters the ones, which were created for this specific
|
|
244
|
+
// key ref. In case there are more than one foreign keys found,
|
|
245
|
+
// that means the key ref points to a structured element/managed assoc
|
|
246
|
+
function findGeneratedForeignKeyForKeyRef(generatedForeignKeys, ref) {
|
|
247
|
+
return generatedForeignKeys.filter(gfk => {
|
|
248
|
+
return (ref.join() === (gfk[2].as || implicitAs(gfk[2].ref)));
|
|
249
|
+
});
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
// Tuple expansion is performed before the generation of the foreign keys and the original(unexpanded) key ref
|
|
253
|
+
// is stored in the property $originalKeyRef. Here we try to evaluate whether the reference in the annotation
|
|
254
|
+
// points to a structure that has been expanded.
|
|
255
|
+
function findOriginalRef(generatedForeignKeys, ref) {
|
|
256
|
+
return generatedForeignKeys.filter(gfk => {
|
|
257
|
+
return (ref.join() === (gfk[2].$originalKeyRef.as || implicitAs(gfk[2].$originalKeyRef.ref)));
|
|
258
|
+
});
|
|
259
|
+
}
|
|
260
|
+
}
|
|
178
261
|
}
|
|
179
262
|
|
|
180
263
|
module.exports = createForeignKeyElements;
|
package/lib/utils/objectUtils.js
CHANGED
|
@@ -83,6 +83,18 @@ function setHidden( obj, prop, val ) {
|
|
|
83
83
|
} );
|
|
84
84
|
}
|
|
85
85
|
|
|
86
|
+
/**
|
|
87
|
+
* Check if the given object has the property as non-enumerable
|
|
88
|
+
*
|
|
89
|
+
* @param {Object} object
|
|
90
|
+
* @param {string} propertyName
|
|
91
|
+
* @returns {boolean}
|
|
92
|
+
*/
|
|
93
|
+
function hasNonEnumerable( object, propertyName ) {
|
|
94
|
+
return Object.prototype.hasOwnProperty.call( object, propertyName ) &&
|
|
95
|
+
!Object.prototype.propertyIsEnumerable.call( object, propertyName );
|
|
96
|
+
}
|
|
97
|
+
|
|
86
98
|
module.exports = {
|
|
87
99
|
copyPropIfExist,
|
|
88
100
|
createDict,
|
|
@@ -90,4 +102,5 @@ module.exports = {
|
|
|
90
102
|
forEachValue,
|
|
91
103
|
forEachKey,
|
|
92
104
|
setHidden,
|
|
105
|
+
hasNonEnumerable,
|
|
93
106
|
};
|