@sap/cds-compiler 6.4.6 → 6.5.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +42 -1156
- package/README.md +1 -10
- package/bin/cdsc.js +1 -1
- package/doc/IncompatibleChanges_v5.md +436 -0
- package/doc/IncompatibleChanges_v6.md +659 -0
- package/doc/Versioning.md +3 -7
- package/lib/api/main.js +1 -0
- package/lib/api/options.js +5 -0
- package/lib/api/validate.js +3 -0
- package/lib/base/message-registry.js +31 -8
- package/lib/base/messages.js +1 -1
- package/lib/base/model.js +3 -2
- package/lib/checks/actionsFunctions.js +6 -3
- package/lib/checks/existsInForbiddenPlaces.js +32 -0
- package/lib/checks/validator.js +2 -0
- package/lib/compiler/assert-consistency.js +3 -5
- package/lib/compiler/checks.js +4 -8
- package/lib/compiler/define.js +330 -558
- package/lib/compiler/extend.js +302 -18
- package/lib/compiler/finalize-parse-cdl.js +2 -10
- package/lib/compiler/generate.js +33 -5
- package/lib/compiler/kick-start.js +8 -7
- package/lib/compiler/populate.js +25 -70
- package/lib/compiler/propagator.js +1 -2
- package/lib/compiler/resolve.js +4 -13
- package/lib/compiler/shared.js +18 -5
- package/lib/compiler/tweak-assocs.js +13 -9
- package/lib/compiler/utils.js +98 -2
- package/lib/compiler/xpr-rewrite.js +2 -1
- package/lib/edm/annotations/edmJson.js +9 -6
- package/lib/edm/annotations/genericTranslation.js +8 -4
- package/lib/edm/csn2edm.js +3 -4
- package/lib/edm/edmInboundChecks.js +1 -2
- package/lib/edm/edmPreprocessor.js +3 -3
- package/lib/gen/CdlGrammar.checksum +1 -1
- package/lib/gen/CdlParser.js +1 -1
- package/lib/gen/Dictionary.json +16 -1
- package/lib/json/from-csn.js +4 -6
- package/lib/json/to-csn.js +3 -3
- package/lib/model/csnRefs.js +19 -5
- package/lib/model/enrichCsn.js +4 -2
- package/lib/optionProcessor.js +8 -4
- package/lib/render/toSql.js +0 -4
- package/lib/render/utils/sql.js +3 -2
- package/lib/transform/db/applyTransformations.js +1 -1
- package/lib/transform/db/assertUnique.js +3 -3
- package/lib/transform/db/assocsToQueries/normalizeFrom.js +33 -0
- package/lib/transform/db/assocsToQueries/transformExists.js +14 -3
- package/lib/transform/db/assocsToQueries/utils.js +1 -1
- package/lib/transform/db/backlinks.js +4 -4
- package/lib/transform/db/cdsPersistence.js +4 -4
- package/lib/transform/db/constraints.js +4 -4
- package/lib/transform/db/expansion.js +5 -5
- package/lib/transform/db/flattening.js +4 -5
- package/lib/transform/db/rewriteCalculatedElements.js +3 -3
- package/lib/transform/db/temporal.js +11 -11
- package/lib/transform/draft/db.js +2 -0
- package/lib/transform/draft/odata.js +5 -7
- package/lib/transform/effective/flattening.js +1 -2
- package/lib/transform/forOdata.js +3 -3
- package/lib/transform/forRelationalDB.js +1 -1
- package/lib/transform/odata/createForeignKeys.js +1 -2
- package/lib/transform/odata/flattening.js +1 -2
- package/lib/transform/odata/toFinalBaseType.js +52 -55
- package/lib/transform/transformUtils.js +3 -4
- package/package.json +1 -1
- package/doc/CHANGELOG_BETA.md +0 -464
- package/doc/CHANGELOG_DEPRECATED.md +0 -235
package/lib/compiler/populate.js
CHANGED
|
@@ -75,7 +75,7 @@ function populate( model ) {
|
|
|
75
75
|
resolvePath,
|
|
76
76
|
nestedElements,
|
|
77
77
|
attachAndEmitValidNames,
|
|
78
|
-
|
|
78
|
+
initMainArtifact,
|
|
79
79
|
extendArtifactBefore,
|
|
80
80
|
extendArtifactAfter,
|
|
81
81
|
} = model.$functions;
|
|
@@ -716,8 +716,7 @@ function populate( model ) {
|
|
|
716
716
|
return initFromColumns( elem, elem.expand );
|
|
717
717
|
}
|
|
718
718
|
|
|
719
|
-
// TODO: make this function shorter
|
|
720
|
-
// parent/name) also be part of definer.js
|
|
719
|
+
// TODO: make this function shorter
|
|
721
720
|
// TODO: query is actually the elemParent, where the new elements are added to
|
|
722
721
|
// top-level: ( query, query.columns )
|
|
723
722
|
// inline: ( queryOrColParent, col.inline, col )
|
|
@@ -730,14 +729,13 @@ function populate( model ) {
|
|
|
730
729
|
query._main.elements = elemsParent.elements;
|
|
731
730
|
}
|
|
732
731
|
|
|
733
|
-
const isExpand = (query.expand === columns);
|
|
734
732
|
if (!columns)
|
|
735
733
|
columns = [ { val: '*' } ];
|
|
736
734
|
|
|
737
735
|
for (let i = 0; i < columns.length; ++i) {
|
|
738
736
|
const col = columns[i];
|
|
739
737
|
if (col.val === '*') {
|
|
740
|
-
const siblings = wildcardSiblings( columns
|
|
738
|
+
const siblings = wildcardSiblings( columns );
|
|
741
739
|
expandWildcard( col, siblings, inlineHead, query );
|
|
742
740
|
}
|
|
743
741
|
// If neither expression (value), expand, new virtual nor new association.
|
|
@@ -748,6 +746,7 @@ function populate( model ) {
|
|
|
748
746
|
q.$inlines.push( col );
|
|
749
747
|
col.kind = '$inline';
|
|
750
748
|
col.name = { id: `.${ q.$inlines.length }`, $inferred: '$internal' };
|
|
749
|
+
// TODO: use a name already set in define.js
|
|
751
750
|
// TODO: really use $inferred: '$internal', not '$inline' ? Re-check.
|
|
752
751
|
// a name for this internal symtab entry (e.g. '.2' to avoid clashes
|
|
753
752
|
// with real elements) is only relevant for `cdsc -R`/debugging
|
|
@@ -755,14 +754,15 @@ function populate( model ) {
|
|
|
755
754
|
// (is also relevant for the semantic location - only use positive)
|
|
756
755
|
dependsOnSilent( q, col );
|
|
757
756
|
// or use userQuery( query ) in the following, too?
|
|
758
|
-
setMemberParent( col, null, query );
|
|
757
|
+
setMemberParent( col, null, query ); // TODO: really set _parent?
|
|
759
758
|
initFromColumns( query, col.inline, col );
|
|
760
759
|
}
|
|
761
760
|
else if (!col.$replacement) {
|
|
762
|
-
const id =
|
|
761
|
+
const { id } = col.name;
|
|
763
762
|
col.kind = 'element';
|
|
764
|
-
dictAdd( elemsParent.elements, id, col, ( name, location ) => {
|
|
765
|
-
|
|
763
|
+
dictAdd( elemsParent.elements, id, col, ( name, location, c ) => {
|
|
764
|
+
if (c.name.$inferred !== '$internal')
|
|
765
|
+
error( 'duplicate-definition', [ location, query ], { name, '#': 'element' } );
|
|
766
766
|
} );
|
|
767
767
|
setMemberParent( col, id, query );
|
|
768
768
|
}
|
|
@@ -771,54 +771,6 @@ function populate( model ) {
|
|
|
771
771
|
return true;
|
|
772
772
|
}
|
|
773
773
|
|
|
774
|
-
/**
|
|
775
|
-
* TODO: probably do this already in definer.js
|
|
776
|
-
*
|
|
777
|
-
* @param col
|
|
778
|
-
* @param {number} colIndex
|
|
779
|
-
* @param query
|
|
780
|
-
* @param {boolean} insideExpand
|
|
781
|
-
* Whether the column is inside 'expand'.
|
|
782
|
-
* Anonymous 'expands' don't have a column parent, hence why we need to know this explicitly.
|
|
783
|
-
*/
|
|
784
|
-
function ensureColumnName( col, colIndex, query, insideExpand ) {
|
|
785
|
-
if (col.name)
|
|
786
|
-
return col.name.id;
|
|
787
|
-
if (col.inline || col.val === '*' || col.val === '**') // '**' = duplicate '*'
|
|
788
|
-
return '';
|
|
789
|
-
const path = col.value &&
|
|
790
|
-
(col.value.path || !col.value.args && col.value.func?.path);
|
|
791
|
-
if (path) {
|
|
792
|
-
const last = path.length && !path.broken && path[path.length - 1];
|
|
793
|
-
if (last) {
|
|
794
|
-
col.name = { id: last.id || '', location: last.location, $inferred: 'as' };
|
|
795
|
-
return col.name.id;
|
|
796
|
-
}
|
|
797
|
-
}
|
|
798
|
-
else if (insideExpand || col.expand ||
|
|
799
|
-
col.value && (col._columnParent || query._parent.kind !== 'select')) {
|
|
800
|
-
// _columnParent => inline/expand with path head; _parent -> only allowed in sub-selects
|
|
801
|
-
error( 'query-req-name', [ col.value?.location || col.location, query ], {},
|
|
802
|
-
'Alias name is required for this select item' );
|
|
803
|
-
}
|
|
804
|
-
else if (col.value) {
|
|
805
|
-
col.name = {
|
|
806
|
-
// NOTE: If the alias is changed, corresponding name-clash tests must be updated as well!
|
|
807
|
-
id: `$_column_${ colIndex + 1 }`,
|
|
808
|
-
location: col.value.location || col.location,
|
|
809
|
-
$inferred: '$internal',
|
|
810
|
-
};
|
|
811
|
-
return col.name.id;
|
|
812
|
-
}
|
|
813
|
-
// invent a name for code completion in expression, see also #10596
|
|
814
|
-
col.name = {
|
|
815
|
-
id: '',
|
|
816
|
-
location: col.value?.location || col.location,
|
|
817
|
-
$inferred: 'none',
|
|
818
|
-
};
|
|
819
|
-
return '';
|
|
820
|
-
}
|
|
821
|
-
|
|
822
774
|
function initElem( elem ) {
|
|
823
775
|
// TODO: we could share code with initMembers/init() in define.js
|
|
824
776
|
if (elem.type && !elem.type.$inferred)
|
|
@@ -844,23 +796,21 @@ function populate( model ) {
|
|
|
844
796
|
|
|
845
797
|
// col ($replacement set before *)
|
|
846
798
|
// false if two cols have same name
|
|
847
|
-
function wildcardSiblings( columns
|
|
799
|
+
function wildcardSiblings( columns ) {
|
|
848
800
|
const siblings = Object.create( null );
|
|
849
801
|
if (!columns)
|
|
850
802
|
return siblings;
|
|
851
803
|
|
|
852
804
|
let seenWildcard = null;
|
|
853
|
-
let colIndex = 0;
|
|
854
805
|
for (const col of columns) {
|
|
855
|
-
const
|
|
856
|
-
if (
|
|
806
|
+
const { name } = col;
|
|
807
|
+
if (name) {
|
|
857
808
|
col.$replacement = !seenWildcard;
|
|
858
|
-
siblings[id] = !(id in siblings) && col;
|
|
809
|
+
siblings[name.id] = !(name.id in siblings) && col;
|
|
859
810
|
}
|
|
860
811
|
else if (col.val === '*') {
|
|
861
812
|
seenWildcard = true;
|
|
862
813
|
}
|
|
863
|
-
++colIndex;
|
|
864
814
|
}
|
|
865
815
|
return siblings;
|
|
866
816
|
}
|
|
@@ -975,7 +925,14 @@ function populate( model ) {
|
|
|
975
925
|
if (!sibling.target || sibling.target.$inferred || // not explicit REDIRECTED TO
|
|
976
926
|
path && path[path.length - 1].id !== sibling.name.id) { // or renamed
|
|
977
927
|
const { id } = sibling.name;
|
|
978
|
-
if (
|
|
928
|
+
if (sibling.name.$inferred === '$internal') {
|
|
929
|
+
error( 'query-req-name',
|
|
930
|
+
// TODO: message function: `query` should work directly
|
|
931
|
+
[ (sibling.value || sibling).location, query ],
|
|
932
|
+
{},
|
|
933
|
+
'Alias name is required for this select item' );
|
|
934
|
+
}
|
|
935
|
+
else if (Array.isArray( navElem )) {
|
|
979
936
|
// ID published! Used in stakeholder project; if renamed, add to oldMessageIds
|
|
980
937
|
info( 'wildcard-excluding-many', [ sibling.name.location, query ],
|
|
981
938
|
{ id, keyword: 'excluding' },
|
|
@@ -1156,9 +1113,7 @@ function populate( model ) {
|
|
|
1156
1113
|
// To avoid repeated messages: if already tried to do autoexposure, return
|
|
1157
1114
|
// auto-exposed entity when successful, or `target` otherwise (no/failed autoexposure)
|
|
1158
1115
|
function minimalExposure( target, service, elemScope ) {
|
|
1159
|
-
const descendants = scopedExposure( target._descendants
|
|
1160
|
-
target._descendants[service.name.id] ||
|
|
1161
|
-
[],
|
|
1116
|
+
const descendants = scopedExposure( target._descendants?.[service.name.id] || [],
|
|
1162
1117
|
elemScope, target );
|
|
1163
1118
|
const preferred = descendants.filter( d => annotationVal( d['@cds.redirection.target'] ) );
|
|
1164
1119
|
const exposed = preferred.length ? preferred : descendants;
|
|
@@ -1215,7 +1170,7 @@ function populate( model ) {
|
|
|
1215
1170
|
kind: 'namespace', name: { id: autoScopeName, location }, location,
|
|
1216
1171
|
};
|
|
1217
1172
|
model.definitions[autoScopeName] = nullScope;
|
|
1218
|
-
|
|
1173
|
+
initMainArtifact( nullScope );
|
|
1219
1174
|
return nullScope;
|
|
1220
1175
|
}
|
|
1221
1176
|
|
|
@@ -1250,7 +1205,6 @@ function populate( model ) {
|
|
|
1250
1205
|
function isDirectProjection( proj, base ) {
|
|
1251
1206
|
return proj.kind === 'entity' && // not event
|
|
1252
1207
|
// direct proj (TODO: or should we add them to another list?)
|
|
1253
|
-
// TODO: delete ENTITY._from - maybe not...
|
|
1254
1208
|
proj.query && proj.query.op && proj.query.op.val === 'SELECT' &&
|
|
1255
1209
|
proj._from && proj._from.length === 1 &&
|
|
1256
1210
|
base === resolvePath( proj._from[0], 'from', proj.query );
|
|
@@ -1259,6 +1213,7 @@ function populate( model ) {
|
|
|
1259
1213
|
// Auto-exposure -----------------------------------------------------------
|
|
1260
1214
|
|
|
1261
1215
|
// TODO: do something in kick-start.js ?
|
|
1216
|
+
// Remark: With includes, the compiler propagates @cds.autoexpose early.
|
|
1262
1217
|
function isAutoExposed( target ) {
|
|
1263
1218
|
if (target.$autoexpose !== undefined)
|
|
1264
1219
|
return target.$autoexpose;
|
|
@@ -1396,7 +1351,7 @@ function populate( model ) {
|
|
|
1396
1351
|
}
|
|
1397
1352
|
setLink( art, '_service', service );
|
|
1398
1353
|
setLink( art, '_block', model.$internal );
|
|
1399
|
-
|
|
1354
|
+
initMainArtifact( art, !!autoexposed );
|
|
1400
1355
|
effectiveType( art );
|
|
1401
1356
|
// TODO: try to set locations of elements locations of orig target elements
|
|
1402
1357
|
newAutoExposed.push( art );
|
|
@@ -12,7 +12,6 @@ const {
|
|
|
12
12
|
forEachDefinition,
|
|
13
13
|
forEachMember,
|
|
14
14
|
forEachGeneric,
|
|
15
|
-
isBetaEnabled,
|
|
16
15
|
} = require( '../base/model');
|
|
17
16
|
const {
|
|
18
17
|
setLink,
|
|
@@ -297,7 +296,7 @@ function propagate( model ) {
|
|
|
297
296
|
else if (destination.kind === 'element' &&
|
|
298
297
|
destination._main?.query && // query element
|
|
299
298
|
!destination.$calc && origin.$calc !== true &&
|
|
300
|
-
|
|
299
|
+
!model.options.noDollarCalc ) {
|
|
301
300
|
destination.$calc
|
|
302
301
|
= Object.assign( copyExpr( origin[prop] ), { $inferred: 'prop' } );
|
|
303
302
|
if (rewriteRefsInExpression( destination, origin, '$calc' ))
|
package/lib/compiler/resolve.js
CHANGED
|
@@ -75,7 +75,6 @@ const $location = Symbol.for( 'cds.$location' );
|
|
|
75
75
|
const $inferred = Symbol.for( 'cds.$inferred' );
|
|
76
76
|
|
|
77
77
|
// TODO: make this part of specExpected in shared.js
|
|
78
|
-
// (standard: not possible on last if !ref.$expected → ref.scope: '$exists')
|
|
79
78
|
const expWithFilter = [ 'from', 'expand', 'inline' ];
|
|
80
79
|
|
|
81
80
|
// Export function of this file. Resolve type references in augmented CSN
|
|
@@ -1557,16 +1556,6 @@ function resolve( model ) {
|
|
|
1557
1556
|
}
|
|
1558
1557
|
|
|
1559
1558
|
function resolveExprPath( expr, expected, user ) {
|
|
1560
|
-
// TODO: re-think this $expected: 'exists' thing
|
|
1561
|
-
if (expr.$expected === 'exists') {
|
|
1562
|
-
if (expected !== 'annotation') { // `exists e[…]` allowed in annotation expressions
|
|
1563
|
-
error( 'expr-unexpected-exists', [ expr.location, user ], {},
|
|
1564
|
-
'An EXISTS predicate is not expected here' );
|
|
1565
|
-
}
|
|
1566
|
-
// We complain about the EXISTS before, as EXISTS subquery is also not supported
|
|
1567
|
-
// TODO: location of EXISTS, TODO: really do this in define.js
|
|
1568
|
-
expr.$expected = 'approved-exists'; // only complain once
|
|
1569
|
-
}
|
|
1570
1559
|
const ref = resolvePath( expr, expected, user );
|
|
1571
1560
|
|
|
1572
1561
|
if (expected === 'annotation') {
|
|
@@ -1588,9 +1577,10 @@ function resolve( model ) {
|
|
|
1588
1577
|
last._navigation?.kind === '$tableAlias') // error already reported
|
|
1589
1578
|
return ref;
|
|
1590
1579
|
|
|
1591
|
-
if (expr.$
|
|
1580
|
+
if (expr.$syntax === 'after-exists') {
|
|
1592
1581
|
if (last.where?.args?.length === 0) {
|
|
1593
1582
|
// at the moment, empty filter is not allowed on last path step
|
|
1583
|
+
// TODO: allow it at some places
|
|
1594
1584
|
reportUnexpectedArgsAndFilter( last, expected, user, null, 'last-empty-filter' );
|
|
1595
1585
|
}
|
|
1596
1586
|
return ref;
|
|
@@ -1679,7 +1669,8 @@ function resolve( model ) {
|
|
|
1679
1669
|
}
|
|
1680
1670
|
const symbols = type && type.enum;
|
|
1681
1671
|
if (!symbols) {
|
|
1682
|
-
if (user.kind !== '$annotation') {
|
|
1672
|
+
if ((user.kind ?? user._outer?.kind) !== '$annotation') {
|
|
1673
|
+
// TODO: better type deduction for annotations
|
|
1683
1674
|
const msg = (user.kind === 'enum') ? 'symbolDef' : type && 'invalidType';
|
|
1684
1675
|
warning( 'ref-unexpected-enum', [ expr.location, user ],
|
|
1685
1676
|
{ '#': msg || 'untyped', enum: sym.id, type: type || '' } );
|
package/lib/compiler/shared.js
CHANGED
|
@@ -64,6 +64,17 @@ function fns( model ) {
|
|
|
64
64
|
notFound: undefinedForAnnotate,
|
|
65
65
|
accept: extendableArtifact,
|
|
66
66
|
},
|
|
67
|
+
'annotate-sec': {
|
|
68
|
+
isMainRef: 'all',
|
|
69
|
+
lexical: userBlock,
|
|
70
|
+
dynamic: modelDefinitions,
|
|
71
|
+
notFound: undefinedDefinition,
|
|
72
|
+
messageMap: {
|
|
73
|
+
'ref-undefined-art': 'ext-undefined-art-sec',
|
|
74
|
+
'ref-undefined-def': 'ext-undefined-def-sec',
|
|
75
|
+
},
|
|
76
|
+
accept: extendableArtifact,
|
|
77
|
+
},
|
|
67
78
|
extend: {
|
|
68
79
|
isMainRef: 'no-generated',
|
|
69
80
|
lexical: userBlock,
|
|
@@ -233,6 +244,7 @@ function fns( model ) {
|
|
|
233
244
|
check: checkAssocOn,
|
|
234
245
|
param: () => '$scopePar', // TODO: check that assocs containing param in ON is not published
|
|
235
246
|
},
|
|
247
|
+
'rewrite-on': {}, // only for traversal when rewriting on condition
|
|
236
248
|
'orderBy-ref': {
|
|
237
249
|
lexical: tableAliasesAndSelf,
|
|
238
250
|
dollar: true,
|
|
@@ -382,7 +394,7 @@ function fns( model ) {
|
|
|
382
394
|
}
|
|
383
395
|
if (expr.args) {
|
|
384
396
|
const args = Array.isArray( expr.args ) ? expr.args : Object.values( expr.args );
|
|
385
|
-
for (const arg of args
|
|
397
|
+
for (const arg of args) {
|
|
386
398
|
if (traverseExpr( arg, exprCtx, user, callback ) === traverseExpr.STOP)
|
|
387
399
|
return traverseExpr.STOP;
|
|
388
400
|
}
|
|
@@ -1283,11 +1295,11 @@ function fns( model ) {
|
|
|
1283
1295
|
|
|
1284
1296
|
// Functions called via semantics.notFound: -----------------------------------
|
|
1285
1297
|
|
|
1286
|
-
function undefinedDefinition( user, item, valid, _dict, prev ) {
|
|
1298
|
+
function undefinedDefinition( user, item, valid, _dict, prev, _path, semantics ) {
|
|
1287
1299
|
// in a CSN source or for `using`, only one env was tested (valid.length 1) :
|
|
1288
1300
|
const art = (!prev) ? item.id : searchName( prev, item.id, 'absolute' );
|
|
1289
1301
|
signalNotFound( (valid.length > 1 ? 'ref-undefined-art' : 'ref-undefined-def'),
|
|
1290
|
-
[ item.location, user ], valid, { art } );
|
|
1302
|
+
[ item.location, user ], valid, { art }, semantics );
|
|
1291
1303
|
// TODO: improve text, use text variant for: "or builtin" or "definitions" or none
|
|
1292
1304
|
}
|
|
1293
1305
|
|
|
@@ -1876,8 +1888,9 @@ function fns( model ) {
|
|
|
1876
1888
|
}
|
|
1877
1889
|
const index = userTargetElementPathIndex( user, path );
|
|
1878
1890
|
checkOnlyForeignKeyNavigation( user, path, index );
|
|
1891
|
+
// TODO: did we check for no filter/args, no `exists` etc?
|
|
1879
1892
|
const last = path[path.length - 1];
|
|
1880
|
-
if (!last.where && ref._artifact?.on) { // filter already complained about
|
|
1893
|
+
if (!last.where && ref._artifact?.on) { // filter already complained about - TODO: where?
|
|
1881
1894
|
const target = index > 0 && index < path.length && ref._artifact?.target;
|
|
1882
1895
|
const msg = (target?._artifact === user._main) ? 'self' : 'unmanaged';
|
|
1883
1896
|
error( 'ref-unexpected-assoc', [ last.location, user ],
|
|
@@ -2016,7 +2029,7 @@ function fns( model ) {
|
|
|
2016
2029
|
}
|
|
2017
2030
|
|
|
2018
2031
|
function checkNoUnmanaged( ref, user, self, messageVariant = 'unmanaged' ) {
|
|
2019
|
-
if (ref._artifact?.on &&
|
|
2032
|
+
if (ref._artifact?.on && ref.$syntax !== 'after-exists') {
|
|
2020
2033
|
const { path } = ref;
|
|
2021
2034
|
const last = path[path.length - 1];
|
|
2022
2035
|
if (self && last.where) // already complained about filter
|
|
@@ -5,7 +5,6 @@
|
|
|
5
5
|
const {
|
|
6
6
|
forEachGeneric,
|
|
7
7
|
forEachInOrder,
|
|
8
|
-
isBetaEnabled,
|
|
9
8
|
} = require('../base/model');
|
|
10
9
|
const { dictLocation, weakLocation, weakRefLocation } = require('../base/location');
|
|
11
10
|
|
|
@@ -138,10 +137,10 @@ function tweakAssocs( model ) {
|
|
|
138
137
|
|
|
139
138
|
function handleQueryElements( column ) {
|
|
140
139
|
rewriteAssociationCheck( column );
|
|
141
|
-
if (
|
|
142
|
-
return;
|
|
140
|
+
if (model.options.noDollarCalc || column._columnParent)
|
|
141
|
+
return; // no $calc supported with inline
|
|
143
142
|
const { value } = column; // `value` = column expression
|
|
144
|
-
if (!value ||
|
|
143
|
+
if (!value || value.path) // not with references
|
|
145
144
|
return;
|
|
146
145
|
// TODO: what about non-simple refs (assocs, even with filter/args)?
|
|
147
146
|
|
|
@@ -946,6 +945,8 @@ function tweakAssocs( model ) {
|
|
|
946
945
|
* for item.
|
|
947
946
|
*/
|
|
948
947
|
function rewriteColumnPath( ref, column ) {
|
|
948
|
+
if (ref.query)
|
|
949
|
+
return traverseExpr.STOP; // sub queries rewrite not supported
|
|
949
950
|
if (!ref._artifact)
|
|
950
951
|
return null;
|
|
951
952
|
const root = ref.path?.[0];
|
|
@@ -1010,9 +1011,12 @@ function tweakAssocs( model ) {
|
|
|
1010
1011
|
state = setArtifactLink( i, elem );
|
|
1011
1012
|
}
|
|
1012
1013
|
else {
|
|
1013
|
-
state = rewriteItem( state, i, assoc );
|
|
1014
|
-
if (
|
|
1015
|
-
|
|
1014
|
+
state = rewriteItem( state, i, assoc, !location );
|
|
1015
|
+
if (state && state !== true)
|
|
1016
|
+
continue;
|
|
1017
|
+
if (!state && !location)
|
|
1018
|
+
return true;
|
|
1019
|
+
break;
|
|
1016
1020
|
}
|
|
1017
1021
|
}
|
|
1018
1022
|
if (state !== true)
|
|
@@ -1032,7 +1036,7 @@ function tweakAssocs( model ) {
|
|
|
1032
1036
|
* @param item Path segment to rewrite.
|
|
1033
1037
|
* @param assoc Published association of query.
|
|
1034
1038
|
*/
|
|
1035
|
-
function rewriteItem( elem, item, assoc ) {
|
|
1039
|
+
function rewriteItem( elem, item, assoc, noError ) {
|
|
1036
1040
|
if (!elem._redirected)
|
|
1037
1041
|
return true;
|
|
1038
1042
|
let name = item.id;
|
|
@@ -1055,7 +1059,7 @@ function tweakAssocs( model ) {
|
|
|
1055
1059
|
if (env?.target)
|
|
1056
1060
|
env = env.target._artifact?._effectiveType;
|
|
1057
1061
|
const found = setArtifactLink( item, env?.elements?.[name] );
|
|
1058
|
-
if (found)
|
|
1062
|
+
if (found || noError)
|
|
1059
1063
|
return found;
|
|
1060
1064
|
|
|
1061
1065
|
const isExplicit = elem.target && !elem.target.$inferred;
|
package/lib/compiler/utils.js
CHANGED
|
@@ -134,6 +134,28 @@ function proxyCopyMembers( art, dictProp, originDict, location, kind, tmpDepreca
|
|
|
134
134
|
}
|
|
135
135
|
}
|
|
136
136
|
|
|
137
|
+
// Initialization (define.js), shared with extend.js: ---------------------------
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Initialize artifact links inside `obj.items` (for nested ones as well).
|
|
141
|
+
* Does nothing, it `obj.items` does not exist.
|
|
142
|
+
*
|
|
143
|
+
* @param {XSN.Artifact} obj
|
|
144
|
+
* @param {object} block
|
|
145
|
+
* @return {XSN.Artifact}
|
|
146
|
+
*/
|
|
147
|
+
function initItemsLinks( obj, block ) {
|
|
148
|
+
let { items } = obj;
|
|
149
|
+
while (items) {
|
|
150
|
+
setLink( items, '_outer', obj );
|
|
151
|
+
setLink( items, '_parent', obj._parent );
|
|
152
|
+
setLink( items, '_block', block );
|
|
153
|
+
obj = items;
|
|
154
|
+
items = obj.items;
|
|
155
|
+
}
|
|
156
|
+
return obj;
|
|
157
|
+
}
|
|
158
|
+
|
|
137
159
|
/**
|
|
138
160
|
* Set the member `elem` to have a _parent link to `parent` and a corresponding
|
|
139
161
|
* _main link. Also set the member's name accordingly, where argument `name`
|
|
@@ -162,6 +184,76 @@ function createAndLinkCalcDepElement( elem ) {
|
|
|
162
184
|
setLink( r, '_outer', elem );
|
|
163
185
|
}
|
|
164
186
|
|
|
187
|
+
function initExprAnnoBlock( art, block ) {
|
|
188
|
+
// remark: `art` could also be the extension
|
|
189
|
+
for (const prop in art) {
|
|
190
|
+
if (prop.charAt(0) !== '@')
|
|
191
|
+
continue;
|
|
192
|
+
const anno = art[prop];
|
|
193
|
+
// _block links needed for `cast( … as Type )`, `[ ..., cast( … as Type ) ]`
|
|
194
|
+
if (anno.literal === 'array') {
|
|
195
|
+
anno.kind = '$annotation';
|
|
196
|
+
for (const item of anno.val)
|
|
197
|
+
setLink( item, '_block', block );
|
|
198
|
+
}
|
|
199
|
+
else if (anno.$tokenTexts) {
|
|
200
|
+
// remark: it wouldn't hurt to set it always...
|
|
201
|
+
anno.kind = '$annotation';
|
|
202
|
+
setLink( anno, '_block', block );
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
function initDollarSelf( art ) {
|
|
208
|
+
// TODO: use setMemberParent() ?
|
|
209
|
+
const self = {
|
|
210
|
+
name: { id: '$self', location: art.location },
|
|
211
|
+
kind: '$self',
|
|
212
|
+
location: art.location,
|
|
213
|
+
};
|
|
214
|
+
setLink( self, '_parent', art );
|
|
215
|
+
setLink( self, '_main', art ); // used on main artifact
|
|
216
|
+
setLink( self, '_origin', art );
|
|
217
|
+
art.$tableAliases = Object.create( null );
|
|
218
|
+
art.$tableAliases.$self = self;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
function initDollarParameters( art ) {
|
|
222
|
+
// TODO: use setMemberParent() ?
|
|
223
|
+
const parameters = {
|
|
224
|
+
name: { id: '$parameters' },
|
|
225
|
+
kind: '$parameters',
|
|
226
|
+
location: art.location,
|
|
227
|
+
deprecated: true, // hide in code completion
|
|
228
|
+
};
|
|
229
|
+
setLink( parameters, '_parent', art );
|
|
230
|
+
setLink( parameters, '_main', art );
|
|
231
|
+
// Search for :const after :param. If there will be a possibility in the
|
|
232
|
+
// future that we can extend <query>.columns, we must be sure to use
|
|
233
|
+
// _block of that new column after :param (or just allow $parameters there).
|
|
234
|
+
setLink( parameters, '_block', art._block );
|
|
235
|
+
if (art.params) {
|
|
236
|
+
parameters.elements = art.params;
|
|
237
|
+
parameters.$tableAliases = art.params; // TODO: find better name - $lexical?
|
|
238
|
+
}
|
|
239
|
+
art.$tableAliases.$parameters = parameters;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
function initBoundSelfParam( params, main ) {
|
|
243
|
+
if (!params)
|
|
244
|
+
return;
|
|
245
|
+
const first = params[Object.keys( params )[0] || ''];
|
|
246
|
+
const type = first?.type || first?.items?.type; // this sequence = no derived type
|
|
247
|
+
const path = type?.path;
|
|
248
|
+
if (path?.length === 1 && path[0]?.id === '$self') { // TODO: no where: ?
|
|
249
|
+
const $self = main.$tableAliases?.$self ||
|
|
250
|
+
main.kind === 'extend' && { name: { id: '$self' } };
|
|
251
|
+
// remark: an 'extend' has no "table alias" `$self` (relevant for parse-cdl)
|
|
252
|
+
setLink( type, '_artifact', $self );
|
|
253
|
+
setLink( path[0], '_artifact', $self );
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
|
|
165
257
|
/**
|
|
166
258
|
* Adds a dependency user -> art with the given location.
|
|
167
259
|
*
|
|
@@ -193,10 +285,9 @@ function dependsOnSilent( user, art ) {
|
|
|
193
285
|
user._deps.push( { art } );
|
|
194
286
|
}
|
|
195
287
|
|
|
196
|
-
function storeExtension( elem, name, prop, parent
|
|
288
|
+
function storeExtension( elem, name, prop, parent ) {
|
|
197
289
|
if (prop === 'enum')
|
|
198
290
|
prop = 'elements';
|
|
199
|
-
setLink( elem, '_block', block );
|
|
200
291
|
const kind = `_${ elem.kind }`; // _extend or _annotate
|
|
201
292
|
if (!parent[kind])
|
|
202
293
|
setLink( parent, kind, {} );
|
|
@@ -702,8 +793,13 @@ module.exports = {
|
|
|
702
793
|
proxyCopyMembers,
|
|
703
794
|
dependsOn,
|
|
704
795
|
dependsOnSilent,
|
|
796
|
+
initItemsLinks,
|
|
705
797
|
setMemberParent,
|
|
706
798
|
createAndLinkCalcDepElement,
|
|
799
|
+
initExprAnnoBlock,
|
|
800
|
+
initDollarSelf,
|
|
801
|
+
initDollarParameters,
|
|
802
|
+
initBoundSelfParam,
|
|
707
803
|
storeExtension,
|
|
708
804
|
withAssociation,
|
|
709
805
|
pathName,
|
|
@@ -395,7 +395,8 @@ function xprRewriteFns( model ) {
|
|
|
395
395
|
}
|
|
396
396
|
}
|
|
397
397
|
|
|
398
|
-
if (isSimpleSelectItem && model.options.testMode
|
|
398
|
+
if (isSimpleSelectItem && model.options.testMode &&
|
|
399
|
+
destination.value?.path[0]._navigation?.kind !== 'mixin')
|
|
399
400
|
throw new CompilerAssertion(`select item has no table alias: ${ JSON.stringify(destination.value.path) }`);
|
|
400
401
|
|
|
401
402
|
if (isAnnoPathAbsolute( expr ))
|
|
@@ -93,8 +93,6 @@ function xpr2edmJson( carrier, anno, location, options, messageFunctions ) {
|
|
|
93
93
|
//----------------------------------
|
|
94
94
|
// operators not supported as dynamic expression
|
|
95
95
|
'.': notADynExpr,
|
|
96
|
-
isNull: (p, o) => notADynExpr(p, o, null, null, null, null, 'is null'),
|
|
97
|
-
isNotNull: (p, o) => notADynExpr(p, o, null, null, null, null, 'is not null'),
|
|
98
96
|
exists: notADynExpr,
|
|
99
97
|
SELECT: notADynExpr,
|
|
100
98
|
SET: (p, o) => notADynExpr(p, o, null, null, null, null, 'UNION'),
|
|
@@ -302,6 +300,8 @@ function xpr2edmJson( carrier, anno, location, options, messageFunctions ) {
|
|
|
302
300
|
transform.$Mul = noOp;
|
|
303
301
|
transform['/'] = op('$DivBy');
|
|
304
302
|
transform.$DivBy = noOp;
|
|
303
|
+
transform.isNull = op('$Eq');
|
|
304
|
+
transform.isNotNull = op('$Ne');
|
|
305
305
|
// $Div, $Mod are functions
|
|
306
306
|
//----------------------------------
|
|
307
307
|
// LITERALS
|
|
@@ -885,10 +885,13 @@ function xpr2edmJson( carrier, anno, location, options, messageFunctions ) {
|
|
|
885
885
|
if (xpr.length === 2 && (xpr[0] === '+' || xpr[0] === '-' || xpr[0] === 'not' || xpr[0] === 'new'))
|
|
886
886
|
return { [xpr[0]]: xpr[1] };
|
|
887
887
|
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
return xpr;
|
|
891
|
-
|
|
888
|
+
// is null
|
|
889
|
+
if (xpr.length === 3 && xpr[1] === 'is' && xpr[2] === 'null')
|
|
890
|
+
return { isNull: [ xpr[0], { val: null } ] };
|
|
891
|
+
|
|
892
|
+
// is not null
|
|
893
|
+
if (xpr.length === 4 && xpr[1] === 'is' && xpr[2] === 'not' && xpr[3] === 'null')
|
|
894
|
+
return { isNotNull: [ xpr[0], { val: null } ] };
|
|
892
895
|
|
|
893
896
|
// binary operators: '=', '<>', 'like', ...
|
|
894
897
|
if (xpr.length === 3 && typeof xpr[1] === 'string')
|
|
@@ -916,8 +916,8 @@ function csn2annotationEdm( reqDefs, reqDefsUtils, csnVocabularies, serviceName,
|
|
|
916
916
|
}
|
|
917
917
|
// expression
|
|
918
918
|
const res = handleExpression(cAnnoValue['='], dTypeName);
|
|
919
|
-
oTarget.setXml(
|
|
920
|
-
oTarget.setJSON(
|
|
919
|
+
oTarget.setXml({ [res.name]: res.value });
|
|
920
|
+
oTarget.setJSON({ [res.name]: res.value });
|
|
921
921
|
}
|
|
922
922
|
else if (cAnnoValue['#'] !== undefined) {
|
|
923
923
|
const enumSymbol = cAnnoValue['#'];
|
|
@@ -968,9 +968,13 @@ function csn2annotationEdm( reqDefs, reqDefsUtils, csnVocabularies, serviceName,
|
|
|
968
968
|
}
|
|
969
969
|
else if (cAnnoValue.$edmJson) {
|
|
970
970
|
// "pseudo-structure" used for embedding a piece of JSON that represents "OData CSDL, JSON Representation"
|
|
971
|
-
|
|
971
|
+
const edmNode = handleEdmJson(cAnnoValue.$edmJson, msg);
|
|
972
|
+
if (edmNode && edmNode._kind === 'Path' && typeof edmNode._value === 'string' && !edmNode._children.length)
|
|
973
|
+
oTarget.setXml({ [edmNode._kind]: edmNode._value });
|
|
974
|
+
else
|
|
975
|
+
oTarget.append(edmNode);
|
|
972
976
|
}
|
|
973
|
-
else if (
|
|
977
|
+
else if (Object.keys(cAnnoValue).filter( x => x[0] !== '@' ).length === 0) {
|
|
974
978
|
// object consists only of properties starting with "@", no $value
|
|
975
979
|
setProp(oTarget, '$isInvalid', true);
|
|
976
980
|
message('odata-anno-value', msg.location,
|
package/lib/edm/csn2edm.js
CHANGED
|
@@ -49,7 +49,6 @@ function csn2edm( _csn, serviceName, _options, messageFunctions ) {
|
|
|
49
49
|
function csn2edmAll( _csn, _options, serviceNames, messageFunctions ) {
|
|
50
50
|
// get us a fresh model copy that we can work with
|
|
51
51
|
const csn = cloneFullCsn(_csn, _options);
|
|
52
|
-
const special$self = !csn?.definitions?.$self && '$self';
|
|
53
52
|
messageFunctions.setModel(csn);
|
|
54
53
|
|
|
55
54
|
const {
|
|
@@ -689,7 +688,7 @@ function csn2edmAll( _csn, _options, serviceNames, messageFunctions ) {
|
|
|
689
688
|
}
|
|
690
689
|
}
|
|
691
690
|
|
|
692
|
-
if (options.isV2()
|
|
691
|
+
if (options.isV2()) {
|
|
693
692
|
if (elementCsn._edmParentCsn.name === `${ elementCsn._edmParentCsn.$mySchemaName }.DraftAdministrativeData`)
|
|
694
693
|
elementCsn['@UI.HiddenFilter'] ??= true;
|
|
695
694
|
}
|
|
@@ -762,7 +761,7 @@ function csn2edmAll( _csn, _options, serviceNames, messageFunctions ) {
|
|
|
762
761
|
const entries = Object.entries(actionCsn.params);
|
|
763
762
|
const firstParam = entries[0][1];
|
|
764
763
|
const type = firstParam?.items?.type || firstParam?.type;
|
|
765
|
-
if (type ===
|
|
764
|
+
if (type === '$self') {
|
|
766
765
|
bpName = entries[0][0];
|
|
767
766
|
setProp(actionCsn, '$bindingParam', firstParam);
|
|
768
767
|
// preserve the original type (as it is the key to reqDefs.defintions)
|
|
@@ -938,7 +937,7 @@ function csn2edmAll( _csn, _options, serviceNames, messageFunctions ) {
|
|
|
938
937
|
if (parameterCsn['@cds.api.ignore'])
|
|
939
938
|
return;
|
|
940
939
|
const type = parameterCsn?.items?.type || parameterCsn?.type;
|
|
941
|
-
if (i === 0 && type ===
|
|
940
|
+
if (i === 0 && type === '$self') {
|
|
942
941
|
// skip and remove the first parameter if it is a $self binding parameter to
|
|
943
942
|
// omit annotation rendering later on
|
|
944
943
|
setProp(actionCsn, '$bindingParam', parameterCsn);
|
|
@@ -130,12 +130,11 @@ function inboundQualificationChecks( csn, options, messageFunctions,
|
|
|
130
130
|
// we need to know if the first path step is the bindind param
|
|
131
131
|
// for the rejection of V2 paths where the BP is ignored
|
|
132
132
|
function markBindingParamPaths( action, loc ) {
|
|
133
|
-
const special$self = !csn?.definitions?.$self && '$self';
|
|
134
133
|
if (action.params) {
|
|
135
134
|
const params = Object.entries(action.params);
|
|
136
135
|
const firstParam = params[0][1];
|
|
137
136
|
const type = firstParam?.items?.type || firstParam?.type;
|
|
138
|
-
if (type ===
|
|
137
|
+
if (type === '$self') {
|
|
139
138
|
const bindingParamName = params[0][0];
|
|
140
139
|
const markBindingParam = {
|
|
141
140
|
ref: (parent, prop, xpr) => {
|