@sap/cds-compiler 6.8.0 → 6.9.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 +55 -0
- package/README.md +4 -0
- package/bin/cdshi.js +1 -0
- package/lib/api/main.js +8 -1
- package/lib/api/options.js +3 -1
- package/lib/base/builtins.js +13 -9
- package/lib/base/csnRefs.js +8 -10
- package/lib/base/message-registry.js +61 -3
- package/lib/base/messages.js +2 -0
- package/lib/base/optionProcessor.js +2 -0
- package/lib/base/specialOptions.js +1 -1
- package/lib/compiler/assert-consistency.js +11 -9
- package/lib/compiler/base.js +5 -1
- package/lib/compiler/define.js +1 -1
- package/lib/compiler/dictionaries.js +2 -3
- package/lib/compiler/extend.js +137 -27
- package/lib/compiler/lsp-api.js +3 -3
- package/lib/compiler/populate.js +4 -5
- package/lib/compiler/resolve.js +50 -35
- package/lib/compiler/shared.js +33 -14
- package/lib/compiler/tweak-assocs.js +2 -2
- package/lib/compiler/utils.js +26 -23
- package/lib/compiler/xpr-rewrite.js +2 -2
- package/lib/edm/EdmPrimitiveTypeDefinitions.js +4 -1
- package/lib/edm/annotations/genericTranslation.js +49 -6
- package/lib/edm/csn2edm.js +4 -2
- package/lib/edm/edm.js +6 -3
- package/lib/gen/BaseParser.js +59 -97
- package/lib/gen/CdlGrammar.checksum +1 -1
- package/lib/gen/CdlParser.js +2055 -1969
- package/lib/gen/Dictionary.json +67 -7
- package/lib/json/from-csn.js +7 -12
- package/lib/json/to-csn.js +59 -35
- package/lib/parsers/AstBuildingParser.js +43 -30
- package/lib/render/toCdl.js +46 -27
- package/lib/render/toSql.js +9 -0
- package/lib/render/utils/common.js +3 -2
- package/lib/tool-lib/enrichCsn.js +1 -0
- package/lib/transform/effective/flattening.js +6 -5
- package/lib/transform/effective/main.js +5 -0
- package/lib/transform/forOdata.js +20 -2
- package/lib/transform/forRelationalDB.js +8 -4
- package/lib/transform/tupleExpansion.js +40 -0
- package/package.json +3 -40
package/lib/compiler/extend.js
CHANGED
|
@@ -11,7 +11,7 @@ const {
|
|
|
11
11
|
setLink,
|
|
12
12
|
setArtifactLink,
|
|
13
13
|
copyExpr,
|
|
14
|
-
|
|
14
|
+
setExpandStatus,
|
|
15
15
|
linkToOrigin,
|
|
16
16
|
initItemsLinks,
|
|
17
17
|
setMemberParent,
|
|
@@ -84,6 +84,8 @@ function extend( model ) {
|
|
|
84
84
|
const includesNonShadowedFirst
|
|
85
85
|
= isDeprecatedEnabled( model.options, '_includesNonShadowedFirst' );
|
|
86
86
|
|
|
87
|
+
const includeCollisions = [];
|
|
88
|
+
|
|
87
89
|
forEachGeneric( model, 'definitions', tagCompositionTargets );
|
|
88
90
|
dictForEach( model.$collectedExtensions, e => e._extensions.forEach( tagCompositionTargets ) );
|
|
89
91
|
// remark: tagging on extensions works _before_ running extendArtifactBefore() on each artifact
|
|
@@ -174,7 +176,7 @@ function extend( model ) {
|
|
|
174
176
|
// elements etc. TODO: do that more specifically on the dicts (via symbol)
|
|
175
177
|
// Probably better: we could use the _extensions dict prop directly in to-csn
|
|
176
178
|
if (art.$inferred)
|
|
177
|
-
|
|
179
|
+
setExpandStatus( art, 'annotate' );
|
|
178
180
|
if (Array.isArray( art._extensions )) {
|
|
179
181
|
checkExtensionsKind( art._extensions, art );
|
|
180
182
|
transformArtifactExtensions( art );
|
|
@@ -258,7 +260,8 @@ function extend( model ) {
|
|
|
258
260
|
checkReturnsExtension( ext, art );
|
|
259
261
|
}
|
|
260
262
|
// if (art.elements || art.enum || art.kind === 'annotate')
|
|
261
|
-
moveDictExtensions( art, extensionsMap,
|
|
263
|
+
moveDictExtensions( art, extensionsMap,
|
|
264
|
+
(art.enum ? 'enum' : 'elements'), false );
|
|
262
265
|
}
|
|
263
266
|
}
|
|
264
267
|
|
|
@@ -399,7 +402,9 @@ function extend( model ) {
|
|
|
399
402
|
pushTo$add( dict, ext );
|
|
400
403
|
}
|
|
401
404
|
else if (prop.charAt(0) === '@' || prop === 'doc' || prop === 'columns' ||
|
|
402
|
-
|
|
405
|
+
prop === 'groupBy' || prop === 'where' || prop === 'having' ||
|
|
406
|
+
prop === 'orderBy' || prop === 'limit' || prop === 'length' ||
|
|
407
|
+
prop === 'scale' || prop === 'precision' || prop === 'srid') {
|
|
403
408
|
if (!isAutoItemsOrReturns)
|
|
404
409
|
pushToDict( dict, prop, ext );
|
|
405
410
|
}
|
|
@@ -577,7 +582,7 @@ function extend( model ) {
|
|
|
577
582
|
else if (prop === 'columns') {
|
|
578
583
|
const { query } = art;
|
|
579
584
|
for (const col of ext.columns)
|
|
580
|
-
col.$extended =
|
|
585
|
+
col.$extended = 'columns';
|
|
581
586
|
|
|
582
587
|
if (art.kind === 'annotate' && art.$inferred === '')
|
|
583
588
|
return; // internal super-annotate for unknown artifacts
|
|
@@ -593,6 +598,10 @@ function extend( model ) {
|
|
|
593
598
|
query.columns.push( ...ext.columns );
|
|
594
599
|
ext.columns.forEach( col => changeParentLinks( col, query ) );
|
|
595
600
|
}
|
|
601
|
+
else if (prop === 'groupBy' || prop === 'where' || prop === 'having' ||
|
|
602
|
+
prop === 'orderBy' || prop === 'limit') {
|
|
603
|
+
applyQueryClause( prop, ext, art );
|
|
604
|
+
}
|
|
596
605
|
else if (typeParameters.list.includes( prop )) {
|
|
597
606
|
const typeExts = art.$typeExts || (art.$typeExts = {});
|
|
598
607
|
typeExts[prop] = ext;
|
|
@@ -603,6 +612,39 @@ function extend( model ) {
|
|
|
603
612
|
}
|
|
604
613
|
}
|
|
605
614
|
|
|
615
|
+
function applyQueryClause( prop, ext, art ) {
|
|
616
|
+
const { query } = art;
|
|
617
|
+
const clause = ext[prop];
|
|
618
|
+
const isArray = Array.isArray( clause );
|
|
619
|
+
|
|
620
|
+
if (prop !== 'limit') {
|
|
621
|
+
const items = isArray ? clause : [ clause ];
|
|
622
|
+
for (const item of items) {
|
|
623
|
+
item.$extended = prop;
|
|
624
|
+
setLink( item, '_block', ext._block );
|
|
625
|
+
setLink( item, '_outer', query );
|
|
626
|
+
}
|
|
627
|
+
}
|
|
628
|
+
if (!query?.from?.path) {
|
|
629
|
+
const variant = (query?.from || query)?.op?.val || 'std';
|
|
630
|
+
const loc = isArray ? clause[$location] : clause.location;
|
|
631
|
+
error( `extend-${ prop.toLowerCase() }`, [ loc, ext ], { '#': variant, art } );
|
|
632
|
+
return;
|
|
633
|
+
}
|
|
634
|
+
if (isArray) {
|
|
635
|
+
if (!query[prop])
|
|
636
|
+
query[prop] = [];
|
|
637
|
+
query[prop].push( ...clause );
|
|
638
|
+
}
|
|
639
|
+
else {
|
|
640
|
+
if (query[prop]) {
|
|
641
|
+
error( 'ext-unexpected-sql-clause', [ clause.location, ext ], { art, keyword: prop } );
|
|
642
|
+
return;
|
|
643
|
+
}
|
|
644
|
+
query[prop] = clause;
|
|
645
|
+
}
|
|
646
|
+
}
|
|
647
|
+
|
|
606
648
|
function changeParentLinks( art, queryOrMain ) {
|
|
607
649
|
// TODO: we might also change the implicit name (if name.id is a number,
|
|
608
650
|
// adding the previous column lenght - 1) for better error messages
|
|
@@ -838,18 +880,18 @@ function extend( model ) {
|
|
|
838
880
|
}
|
|
839
881
|
|
|
840
882
|
function moveDictExtensions( art, extensionsMap, artProp, extProp = artProp ) {
|
|
841
|
-
// TODO:
|
|
842
|
-
const extensions = extensionsMap[extProp];
|
|
883
|
+
// TODO: setExpandStatus
|
|
884
|
+
const extensions = extensionsMap[extProp || 'elements'];
|
|
843
885
|
if (!extensions)
|
|
844
886
|
return;
|
|
845
887
|
|
|
846
888
|
for (const ext of extensions) {
|
|
847
889
|
let dictCheck = (art.kind !== 'annotate'); // no check in super annotate statement
|
|
848
|
-
forEachGeneric( ext, extProp, ( elemExt, name ) => {
|
|
890
|
+
forEachGeneric( ext, extProp || (ext.enum ? 'enum' : 'elements'), ( elemExt, name ) => {
|
|
849
891
|
if (elemExt.kind !== 'annotate' && elemExt.kind !== 'extend') // TODO: specified elems
|
|
850
892
|
return; // definitions inside extend, already handled
|
|
851
893
|
dictCheck = dictCheck && checkRemainingMemberExtensions( art, elemExt, artProp, name );
|
|
852
|
-
const elem = art[artProp]?.[name] || annotateFor( art, extProp, name );
|
|
894
|
+
const elem = art[artProp]?.[name] || annotateFor( art, extProp || 'elements', name );
|
|
853
895
|
setLink( elemExt.name, '_artifact', (elem.kind !== 'annotate' ? elem : null ) );
|
|
854
896
|
// TODO: why null for annotate?
|
|
855
897
|
ensureArtifactNotProcessed( elem );
|
|
@@ -1055,11 +1097,23 @@ function extend( model ) {
|
|
|
1055
1097
|
|
|
1056
1098
|
function checkRemainingMainExtensions( art, ext ) {
|
|
1057
1099
|
const refCtx = extensionRefContext( ext );
|
|
1058
|
-
if (
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1100
|
+
if (resolvePath( ext.name, refCtx, ext ) && art?.builtin) {
|
|
1101
|
+
if (ext.kind === 'extend') {
|
|
1102
|
+
// extending built-ins with elements/enums already gives an error
|
|
1103
|
+
warning( 'ext-unexpected-builtin', [ ext.name.location, ext ], {}, // error v8?
|
|
1104
|
+
'Built-in types should not be extended' ); // keep the text general
|
|
1105
|
+
const typeProp = typeParameters.list.find( p => ext[p] );
|
|
1106
|
+
if (typeProp) {
|
|
1107
|
+
const location = ext.$typeArgs?.[$location] || ext[typeProp].location;
|
|
1108
|
+
message( 'ext-unexpected-type-property', [ location, ext ], {}, // error v7
|
|
1109
|
+
'Built-in types can\'t be extended with type properties' );
|
|
1110
|
+
// see also 'ext-invalid-type-property'
|
|
1111
|
+
}
|
|
1112
|
+
}
|
|
1113
|
+
else {
|
|
1114
|
+
info( 'anno-builtin', [ ext.name.location, ext ], {} ); // TODO: better location?
|
|
1115
|
+
}
|
|
1116
|
+
// TODO: remove built-ins as CC candidates via accept property of ./shared.js
|
|
1063
1117
|
}
|
|
1064
1118
|
}
|
|
1065
1119
|
|
|
@@ -1119,7 +1173,8 @@ function extend( model ) {
|
|
|
1119
1173
|
}
|
|
1120
1174
|
if (art._extensions?.$add)
|
|
1121
1175
|
extendArtifact( art._extensions.$add, art );
|
|
1122
|
-
|
|
1176
|
+
checkRedefinitionThroughIncludes( art, 'elements' );
|
|
1177
|
+
checkRedefinitionThroughIncludes( art, 'actions' );
|
|
1123
1178
|
}
|
|
1124
1179
|
|
|
1125
1180
|
/**
|
|
@@ -1142,10 +1197,33 @@ function extend( model ) {
|
|
|
1142
1197
|
// TODO: complain if $inferred
|
|
1143
1198
|
// checkExtensionsKind( extensions, art );
|
|
1144
1199
|
extendMembers( extensions, art );
|
|
1200
|
+
reportIncludeCollisions( art );
|
|
1145
1201
|
// TODO: complain about element extensions inside projection
|
|
1146
1202
|
return true;
|
|
1147
1203
|
}
|
|
1148
1204
|
|
|
1205
|
+
function reportIncludeCollisions( art ) {
|
|
1206
|
+
const grouped = Object.create( null );
|
|
1207
|
+
for (const {
|
|
1208
|
+
prop, name, existing, elem,
|
|
1209
|
+
} of includeCollisions) {
|
|
1210
|
+
const key = `${ prop }:${ name }`;
|
|
1211
|
+
if (!grouped[key])
|
|
1212
|
+
grouped[key] = { prop, name, collisions: new Set( [ existing ] ) };
|
|
1213
|
+
grouped[key].collisions.add( elem );
|
|
1214
|
+
}
|
|
1215
|
+
for (const key in grouped) {
|
|
1216
|
+
const { prop, name, collisions } = grouped[key];
|
|
1217
|
+
const member = art[prop]?.[name];
|
|
1218
|
+
if (member?.$inferred === 'include') {
|
|
1219
|
+
const arts = [ ...collisions ].map( m => m._origin._main );
|
|
1220
|
+
message( 'ext-duplicate-include', [ art.name.location, member ],
|
|
1221
|
+
{ '#': prop, name, sorted_arts: arts } );
|
|
1222
|
+
}
|
|
1223
|
+
}
|
|
1224
|
+
includeCollisions.length = 0;
|
|
1225
|
+
}
|
|
1226
|
+
|
|
1149
1227
|
function extendMembers( extensions, art ) {
|
|
1150
1228
|
// TODO: do the whole extension stuff lazily if the elements are requested
|
|
1151
1229
|
const elemExtensions = [];
|
|
@@ -1154,7 +1232,11 @@ function extend( model ) {
|
|
|
1154
1232
|
// TODO: use same sequence as in chooseAssignment() - better: use common code with that fn
|
|
1155
1233
|
// console.log('EM:',art.name,extensions,art._extensions)
|
|
1156
1234
|
for (const ext of extensions) { // those in extMap.includes
|
|
1157
|
-
if (art.$inferred) {
|
|
1235
|
+
if (art.$inferred === 'include') {
|
|
1236
|
+
error( 'ref-expected-direct-structure', [ ext.name.location, ext ],
|
|
1237
|
+
{ '#': 'elements', art } );
|
|
1238
|
+
}
|
|
1239
|
+
else if (art.$inferred) {
|
|
1158
1240
|
error( 'extend-for-generated', [ ext.name.location, ext ], { art, keyword: 'extend' },
|
|
1159
1241
|
'You can\'t use $(KEYWORD) on the generated $(ART)' ); // or with inferred elements
|
|
1160
1242
|
}
|
|
@@ -1298,6 +1380,8 @@ function extend( model ) {
|
|
|
1298
1380
|
|
|
1299
1381
|
if (obj !== parent && obj.elements && parent.enum) { // applying the extension
|
|
1300
1382
|
initElementsAsEnum();
|
|
1383
|
+
if (parent.enum[$inferred])
|
|
1384
|
+
setExpandStatus( parent, 'extend' );
|
|
1301
1385
|
}
|
|
1302
1386
|
else {
|
|
1303
1387
|
if (checkDefinitions( construct, parent, 'elements', obj.elements || false ))
|
|
@@ -1341,7 +1425,11 @@ function extend( model ) {
|
|
|
1341
1425
|
e.kind = 'enum';
|
|
1342
1426
|
hasElement = true; // warning with CDL input or `name: {}` in CSN input
|
|
1343
1427
|
}
|
|
1344
|
-
if (
|
|
1428
|
+
if (!parent.type || parent.type.$inferred || (parent._main || parent).query) {
|
|
1429
|
+
error( 'extend-type', [ obj.elements[$location], construct ], {},
|
|
1430
|
+
'Only structures or enum types can be extended with elements/enums' );
|
|
1431
|
+
}
|
|
1432
|
+
else if (hasElement) {
|
|
1345
1433
|
warning( 'ext-expecting-enum', [ obj.elements[$location], construct ],
|
|
1346
1434
|
{ code: 'extend … with enum' }, 'Use $(CODE) when extending enums' );
|
|
1347
1435
|
}
|
|
@@ -1404,26 +1492,45 @@ function extend( model ) {
|
|
|
1404
1492
|
|
|
1405
1493
|
if (isQueryExtension && elem.kind === 'element') {
|
|
1406
1494
|
error( 'extend-query', [ elem.location, construct ], // TODO: searchName ?
|
|
1407
|
-
{ code: 'extend
|
|
1495
|
+
{ code: 'extend … with columns' },
|
|
1408
1496
|
'Use $(CODE) to add select items to the query entity' );
|
|
1409
1497
|
return;
|
|
1410
1498
|
}
|
|
1411
1499
|
|
|
1412
1500
|
const existing = parent[prop]?.[name];
|
|
1501
|
+
const shadowsIncludeMember = construct !== parent &&
|
|
1502
|
+
elem.$inferred !== 'include' &&
|
|
1503
|
+
existing?.$inferred === 'include';
|
|
1413
1504
|
const add = construct !== parent && (!existing || elem.$inferred !== 'include');
|
|
1505
|
+
if (!add && existing?.$inferred === 'include' && elem.$inferred === 'include') {
|
|
1506
|
+
includeCollisions.push( {
|
|
1507
|
+
prop, name, existing, elem,
|
|
1508
|
+
} );
|
|
1509
|
+
}
|
|
1414
1510
|
// don't dump with `entity T {}; extend T with { extend e {}; e {}; e {} };`:
|
|
1415
1511
|
const { $duplicates } = elem;
|
|
1416
1512
|
if ($duplicates === true && add)
|
|
1417
1513
|
elem.$duplicates = null;
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1514
|
+
if (add && elem.$inferred === 'include' && Array.isArray( $duplicates ))
|
|
1515
|
+
elem.$duplicates = null;
|
|
1516
|
+
if (shadowsIncludeMember) {
|
|
1517
|
+
parent[prop][name] = elem;
|
|
1518
|
+
setMemberParent( elem, name, parent );
|
|
1519
|
+
}
|
|
1520
|
+
else {
|
|
1521
|
+
setMemberParent( elem, name, parent, add && prop );
|
|
1522
|
+
if (!$duplicates) // not already reported
|
|
1523
|
+
checkRedefinition( elem );
|
|
1524
|
+
}
|
|
1421
1525
|
initMembers( elem, elem, elem._block );
|
|
1422
1526
|
if (elem.kind === 'action' || elem.kind === 'function')
|
|
1423
1527
|
initBoundSelfParam( elem.params, elem._main );
|
|
1424
1528
|
|
|
1425
1529
|
// for a correct home path, setMemberParent needed to be called
|
|
1426
1530
|
|
|
1531
|
+
if (parent[prop]?.[$inferred])
|
|
1532
|
+
setExpandStatus( parent, 'extend' );
|
|
1533
|
+
|
|
1427
1534
|
if (!elem.value || elem.kind !== 'element')
|
|
1428
1535
|
return;
|
|
1429
1536
|
// remark: potential enum elements have already been turned into enums
|
|
@@ -1502,10 +1609,14 @@ function extend( model ) {
|
|
|
1502
1609
|
error( 'ref-expected-direct-structure', [ location, construct ],
|
|
1503
1610
|
{ '#': variant, art: parent } );
|
|
1504
1611
|
}
|
|
1505
|
-
else
|
|
1612
|
+
else if (prop !== 'enum' || !parent.enum ||
|
|
1613
|
+
!parent.type || parent.type.$inferred || (parent._main || parent).query) {
|
|
1506
1614
|
error( 'extend-type', [ location, construct ], {},
|
|
1507
1615
|
'Only structures or enum types can be extended with elements/enums' );
|
|
1508
1616
|
}
|
|
1617
|
+
else {
|
|
1618
|
+
return true;
|
|
1619
|
+
}
|
|
1509
1620
|
}
|
|
1510
1621
|
else if (prop === 'elements') {
|
|
1511
1622
|
error( 'def-unexpected-elements', [ location, construct ], {},
|
|
@@ -1585,6 +1696,7 @@ function extend( model ) {
|
|
|
1585
1696
|
// TODO two kind of messages:
|
|
1586
1697
|
// Error 'More than one include defines element "A"' (at include ref)
|
|
1587
1698
|
// Warning 'Overwrites definition from include "I" (at elem def)
|
|
1699
|
+
const propagateKeys = art.kind !== 'type' || !model.options.v7KeyPropagation;
|
|
1588
1700
|
const parent = ext === art && art;
|
|
1589
1701
|
const members = ext[prop];
|
|
1590
1702
|
// if (members)console.log( 'EXT:', prop, art.kind, art.name.id, ...Object.keys(members));
|
|
@@ -1624,7 +1736,7 @@ function extend( model ) {
|
|
|
1624
1736
|
elem.$inferred = 'include';
|
|
1625
1737
|
if (origin.masked) // TODO(v6): remove 'masked'
|
|
1626
1738
|
elem.masked = Object.assign( { $inferred: 'include' }, origin.masked );
|
|
1627
|
-
if (origin.key)
|
|
1739
|
+
if (origin.key && propagateKeys)
|
|
1628
1740
|
elem.key = Object.assign( { $inferred: 'include' }, origin.key );
|
|
1629
1741
|
if (origin.value && origin.$syntax === 'calc') {
|
|
1630
1742
|
// TODO: If paths become invalid in the new artifact, should we mark
|
|
@@ -1640,9 +1752,6 @@ function extend( model ) {
|
|
|
1640
1752
|
} );
|
|
1641
1753
|
}
|
|
1642
1754
|
}
|
|
1643
|
-
|
|
1644
|
-
checkRedefinitionThroughIncludes( parent, prop );
|
|
1645
|
-
|
|
1646
1755
|
if (!hasNewElement && members) {
|
|
1647
1756
|
ext[prop] = members;
|
|
1648
1757
|
}
|
|
@@ -1688,7 +1797,8 @@ function extend( model ) {
|
|
|
1688
1797
|
|
|
1689
1798
|
/**
|
|
1690
1799
|
* Report duplicates in parent[prop] that happen due to multiple includes having the
|
|
1691
|
-
* same member.
|
|
1800
|
+
* same member. Run after includes and extends so shadowing members have already
|
|
1801
|
+
* replaced any include-derived survivors in-place.
|
|
1692
1802
|
*/
|
|
1693
1803
|
function checkRedefinitionThroughIncludes( parent, prop ) {
|
|
1694
1804
|
if (!parent[prop])
|
package/lib/compiler/lsp-api.js
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
//
|
|
8
8
|
// This files includes an iterator over "semantic tokens" in an XSN model.
|
|
9
9
|
// "Semantic tokens" are identifiers, but also the "return" parameter.
|
|
10
|
-
// See <../../internalDoc/lsp/
|
|
10
|
+
// See <../../internalDoc/lsp/SemanticTokenCrawling.md> for details.
|
|
11
11
|
|
|
12
12
|
const { CompilerAssertion } = require('../base/error');
|
|
13
13
|
const $inferred = Symbol.for( 'cds.$inferred' );
|
|
@@ -190,7 +190,7 @@ function* artifactTokens( art ) {
|
|
|
190
190
|
* @returns {Generator<LspSemanticTokenEvent>}
|
|
191
191
|
*/
|
|
192
192
|
function* extensionTokens( ext ) {
|
|
193
|
-
if (ext.kind !== 'extend' && ext.kind !== 'annotate')
|
|
193
|
+
if (ext.kind !== 'extend' && ext.kind !== 'annotate' || !ext.name)
|
|
194
194
|
return null;
|
|
195
195
|
|
|
196
196
|
const wasApplied = ext.name._artifact && !ext.name._artifact.$inferred;
|
|
@@ -203,7 +203,7 @@ function* extensionTokens( ext ) {
|
|
|
203
203
|
|
|
204
204
|
// We need to traverse all dictionaries that could themselves contain
|
|
205
205
|
// extensions. Enum extensions or columns don't need to be traversed,
|
|
206
|
-
// for example, because there can't be inner extensions.
|
|
206
|
+
// for example, because there can't be inner extensions. TODO: still?
|
|
207
207
|
yield* dictOf( extensionTokens )( ext.params );
|
|
208
208
|
yield* dictOf( extensionTokens )( ext.actions );
|
|
209
209
|
yield* dictOf( extensionTokens )( ext.elements );
|
package/lib/compiler/populate.js
CHANGED
|
@@ -36,7 +36,6 @@ const {
|
|
|
36
36
|
dependsOn,
|
|
37
37
|
proxyCopyMembers,
|
|
38
38
|
setExpandStatus,
|
|
39
|
-
setExpandStatusAnnotate,
|
|
40
39
|
dependsOnSilent,
|
|
41
40
|
columnRefStartsWithSelf,
|
|
42
41
|
forEachDefinition,
|
|
@@ -218,8 +217,8 @@ function populate( model ) {
|
|
|
218
217
|
populateGeneratedEntity( a );
|
|
219
218
|
if (a.includes)
|
|
220
219
|
a.includes.forEach( i => resolveInclude( i, a ) );
|
|
221
|
-
extendArtifactAdd( a );
|
|
222
220
|
art = populateArtifact( a, art ) || a;
|
|
221
|
+
extendArtifactAdd( a );
|
|
223
222
|
setLink( a, '_effectiveType', art );
|
|
224
223
|
a.$effectiveSeqNo = ++effectiveSeqNo;
|
|
225
224
|
// console.log('PE:',require('../model/revealInternalProperties').ref(a))
|
|
@@ -614,7 +613,7 @@ function populate( model ) {
|
|
|
614
613
|
}
|
|
615
614
|
|
|
616
615
|
if (wasAnnotated)
|
|
617
|
-
|
|
616
|
+
setExpandStatus( art, 'annotate' );
|
|
618
617
|
|
|
619
618
|
// TODO: We don't check enum$, yet! We first need to fix expansion for
|
|
620
619
|
// `cast(elem as EnumType)` (see #9421)
|
|
@@ -668,7 +667,7 @@ function populate( model ) {
|
|
|
668
667
|
}
|
|
669
668
|
}
|
|
670
669
|
if (wasAnnotated)
|
|
671
|
-
|
|
670
|
+
setExpandStatus( art, 'annotate' );
|
|
672
671
|
|
|
673
672
|
for (const id in art.foreignKeys$) {
|
|
674
673
|
const specifiedElement = art.foreignKeys$[id];
|
|
@@ -767,7 +766,7 @@ function populate( model ) {
|
|
|
767
766
|
const { targetMax } = path[path.length - 1].cardinality ||
|
|
768
767
|
getInheritedProp( assoc, 'cardinality' ) || {};
|
|
769
768
|
if (targetMax && (targetMax.val === '*' || targetMax.val > 1)) {
|
|
770
|
-
elem.items = { location: elem.expand[$location] };
|
|
769
|
+
elem.items = { location: elem.expand[$location], $inferred: 'query' };
|
|
771
770
|
setLink( elem.items, '_outer', elem );
|
|
772
771
|
}
|
|
773
772
|
return initFromColumns( elem, elem.expand );
|
package/lib/compiler/resolve.js
CHANGED
|
@@ -39,7 +39,7 @@
|
|
|
39
39
|
'use strict';
|
|
40
40
|
|
|
41
41
|
const { isDeprecatedEnabled } = require('../base/specialOptions');
|
|
42
|
-
const { dictAdd } = require('./dictionaries');
|
|
42
|
+
const { dictAdd, pushToDict } = require('./dictionaries');
|
|
43
43
|
const { weakLocation } = require('../base/location');
|
|
44
44
|
const { combinedLocation } = require('../base/location');
|
|
45
45
|
const { typeParameters } = require('./builtins');
|
|
@@ -285,9 +285,9 @@ function resolve( model ) {
|
|
|
285
285
|
// we don't propagate keys to type projections, see #13575
|
|
286
286
|
return;
|
|
287
287
|
}
|
|
288
|
-
// Second argument
|
|
288
|
+
// Second argument controls whether `key` is only propagated along simple
|
|
289
289
|
// view, i.e. ref or subquery in FROM, not UNION or JOIN.
|
|
290
|
-
traverseQueryPost( view.query,
|
|
290
|
+
traverseQueryPost( view.query, !options.v7KeyPropagation, ( query ) => {
|
|
291
291
|
if (!withExplicitKeys( query ) && inheritKeyProp( query ) &&
|
|
292
292
|
withKeyPropagation( query )) // now the part with messages
|
|
293
293
|
inheritKeyProp( query, true );
|
|
@@ -331,47 +331,47 @@ function resolve( model ) {
|
|
|
331
331
|
return head?.kind === '$tableAlias' && item._artifact?.key;
|
|
332
332
|
}
|
|
333
333
|
|
|
334
|
-
function primarySourceNavigation( aliases ) {
|
|
335
|
-
for (const name in aliases)
|
|
336
|
-
return aliases[name].elements;
|
|
337
|
-
return undefined;
|
|
338
|
-
}
|
|
339
|
-
|
|
340
334
|
function withKeyPropagation( query ) {
|
|
341
|
-
const { from } = query;
|
|
335
|
+
const { from, $tableAliases } = query;
|
|
342
336
|
if (!from) // parse error SELECT FROM <EOF>
|
|
343
337
|
return false;
|
|
344
|
-
|
|
345
338
|
let propagateKeys = true; // used instead early RETURN to get more messages
|
|
346
|
-
|
|
347
|
-
if (toMany) {
|
|
348
|
-
propagateKeys = false;
|
|
349
|
-
info( 'query-from-many', [ toMany.location, query ], { art: toMany }, {
|
|
350
|
-
std: 'Key properties are not propagated because a to-many association $(ART) is selected',
|
|
351
|
-
// eslint-disable-next-line @stylistic/max-len
|
|
352
|
-
element: 'Key properties are not propagated because a to-many association $(MEMBER) of $(ART) is selected',
|
|
353
|
-
} );
|
|
354
|
-
}
|
|
339
|
+
|
|
355
340
|
// Check that all keys from the source are projected:
|
|
356
|
-
const notProjected =
|
|
357
|
-
const navElems =
|
|
341
|
+
const notProjected = Object.create( null );
|
|
342
|
+
const navElems = query._combined;
|
|
358
343
|
for (const name in navElems) {
|
|
359
344
|
const nav = navElems[name];
|
|
360
|
-
if (nav
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
notProjected.push( nav.name.id );
|
|
345
|
+
if (Array.isArray( nav ))
|
|
346
|
+
nav.forEach( pushNonProjected );
|
|
347
|
+
else
|
|
348
|
+
pushNonProjected( nav );
|
|
365
349
|
}
|
|
366
|
-
|
|
350
|
+
for (const [ alias, names ] of Object.entries( notProjected )) {
|
|
367
351
|
propagateKeys = false;
|
|
368
|
-
|
|
352
|
+
// TODO: mention alias
|
|
353
|
+
const location = $tableAliases[alias].name?.location || from.location;
|
|
354
|
+
info( 'query-missing-keys', [ location, query ], { names },
|
|
369
355
|
{
|
|
370
356
|
std: 'Keys $(NAMES) have not been projected - key properties are not propagated',
|
|
371
357
|
one: 'Key $(NAMES) has not been projected - key properties are not propagated',
|
|
372
358
|
} );
|
|
373
359
|
}
|
|
374
|
-
|
|
360
|
+
if (options.v7KeyPropagation)
|
|
361
|
+
return propagateKeys;
|
|
362
|
+
|
|
363
|
+
// Check that there is no to-many assoc in the FROM reference or in the select
|
|
364
|
+
// item reference (references in expressions are not checked,
|
|
365
|
+
// withAssociation() will see no _artifact links anyway)
|
|
366
|
+
const toMany = withAssociation( from, targetMaxNotOne, true );
|
|
367
|
+
if (toMany) {
|
|
368
|
+
propagateKeys = false;
|
|
369
|
+
info( 'query-from-many', [ toMany.location, query ], { art: toMany }, {
|
|
370
|
+
std: 'Key properties are not propagated because a to-many association $(ART) is selected',
|
|
371
|
+
// eslint-disable-next-line @stylistic/max-len
|
|
372
|
+
element: 'Key properties are not propagated because a to-many association $(MEMBER) of $(ART) is selected',
|
|
373
|
+
} );
|
|
374
|
+
}
|
|
375
375
|
for (const name in query.elements) {
|
|
376
376
|
const elem = query.elements[name];
|
|
377
377
|
|
|
@@ -383,6 +383,14 @@ function resolve( model ) {
|
|
|
383
383
|
}
|
|
384
384
|
return propagateKeys;
|
|
385
385
|
|
|
386
|
+
function pushNonProjected( nav ) {
|
|
387
|
+
if (nav.$duplicates)
|
|
388
|
+
return;
|
|
389
|
+
const { key } = nav._origin;
|
|
390
|
+
if (key?.val && !nav._projections?.length)
|
|
391
|
+
pushToDict( notProjected, nav._parent.name.id, nav.name.id );
|
|
392
|
+
}
|
|
393
|
+
|
|
386
394
|
function selectTest( expr, user ) {
|
|
387
395
|
const art = withAssociation( expr, targetMaxNotOne );
|
|
388
396
|
if (art) {
|
|
@@ -1004,11 +1012,16 @@ function resolve( model ) {
|
|
|
1004
1012
|
forEachGeneric( query, 'elements', resolveRefs );
|
|
1005
1013
|
if (query.from)
|
|
1006
1014
|
resolveJoinOn( query.from );
|
|
1007
|
-
if (query.where)
|
|
1008
|
-
|
|
1015
|
+
if (query.where) {
|
|
1016
|
+
const user = query.where._block ? query.where : query;
|
|
1017
|
+
resolveExpr( query.where, 'where', user );
|
|
1018
|
+
}
|
|
1009
1019
|
if (query.groupBy)
|
|
1010
1020
|
resolveBy( query.groupBy, 'groupBy', 'groupBy' );
|
|
1011
|
-
|
|
1021
|
+
if (query.having) {
|
|
1022
|
+
const user = query.having._block ? query.having : query;
|
|
1023
|
+
resolveExpr( query.having, 'having', user );
|
|
1024
|
+
}
|
|
1012
1025
|
if (query.$orderBy) // ORDER BY from UNION:
|
|
1013
1026
|
// TODO clarify: can I access the tab alias of outer queries? If not:
|
|
1014
1027
|
// 4th arg query._main instead query._parent.
|
|
@@ -1051,8 +1064,10 @@ function resolve( model ) {
|
|
|
1051
1064
|
*/
|
|
1052
1065
|
function resolveBy( array, refMode, exprMode ) {
|
|
1053
1066
|
for (const value of array ) {
|
|
1054
|
-
if (value)
|
|
1055
|
-
|
|
1067
|
+
if (value) {
|
|
1068
|
+
const user = value._block ? value : query;
|
|
1069
|
+
resolveExpr( value, (value.path ? refMode : exprMode), user );
|
|
1070
|
+
}
|
|
1056
1071
|
}
|
|
1057
1072
|
}
|
|
1058
1073
|
|
package/lib/compiler/shared.js
CHANGED
|
@@ -790,6 +790,8 @@ function fns( model ) {
|
|
|
790
790
|
return undefined; // parse error
|
|
791
791
|
if (head._artifact !== undefined)
|
|
792
792
|
return head._artifact;
|
|
793
|
+
if (user.$extended && user._outer && !semantics.isMainRef)
|
|
794
|
+
user = user._outer;
|
|
793
795
|
let ruser = user._user || user; // TODO: nicer name if we keep this
|
|
794
796
|
// TODO: re-think _user link
|
|
795
797
|
if (ruser._outer && !semantics.isMainRef) {
|
|
@@ -982,8 +984,6 @@ function fns( model ) {
|
|
|
982
984
|
art.kind === '$self' && path[0].id === '$projection') {
|
|
983
985
|
// Rewrite $projection to $self
|
|
984
986
|
path[0].id = '$self';
|
|
985
|
-
warning( 'ref-expecting-$self', [ path[0].location, user ],
|
|
986
|
-
{ code: '$projection', newcode: '$self' });
|
|
987
987
|
}
|
|
988
988
|
return art.name?.$inferred !== '$internal'; // not a compiler-generated internal alias
|
|
989
989
|
}
|
|
@@ -1035,13 +1035,23 @@ function fns( model ) {
|
|
|
1035
1035
|
return null;
|
|
1036
1036
|
}
|
|
1037
1037
|
case 'mixin': {
|
|
1038
|
-
// use a source element having that name if in `extend … with columns
|
|
1039
|
-
const
|
|
1040
|
-
|
|
1038
|
+
// use a source element having that name if in `extend … with` (columns or groupBy):
|
|
1039
|
+
const $extended = user._user?.$extended ?? user.$extended;
|
|
1040
|
+
const elem = $extended && art._parent._combined[head.id];
|
|
1041
1041
|
if (elem) {
|
|
1042
1042
|
path.$prefix = elem._parent.name.id; // prepend alias name
|
|
1043
|
-
|
|
1044
|
-
|
|
1043
|
+
if ($extended === 'columns') {
|
|
1044
|
+
warning( 'ref-special-in-extend', [ head.location, user ],
|
|
1045
|
+
{ '#': 'mixin', id: head.id, art: elem._origin._main } );
|
|
1046
|
+
}
|
|
1047
|
+
else {
|
|
1048
|
+
error( 'ref-unexpected-in-extend', [ head.location, user ], {
|
|
1049
|
+
'#': 'mixin',
|
|
1050
|
+
keyword: $extended,
|
|
1051
|
+
id: head.id,
|
|
1052
|
+
art: elem._origin._main,
|
|
1053
|
+
} );
|
|
1054
|
+
}
|
|
1045
1055
|
setLink( head, '_navigation', elem );
|
|
1046
1056
|
return setArtifactLink( head, elem._origin );
|
|
1047
1057
|
}
|
|
@@ -1052,21 +1062,30 @@ function fns( model ) {
|
|
|
1052
1062
|
return setArtifactLink( head, art._origin );
|
|
1053
1063
|
}
|
|
1054
1064
|
case '$tableAlias': {
|
|
1055
|
-
// use a source element having that name if in `extend … with columns
|
|
1056
|
-
const
|
|
1065
|
+
// use a source element having that name if in `extend … with` (columns or groupBy):
|
|
1066
|
+
const $extended = user._user?.$extended ?? user.$extended;
|
|
1057
1067
|
// if query source has duplicates, table alias has no elements
|
|
1058
1068
|
const elem = $extended && art.elements?.[head.id];
|
|
1059
1069
|
if (elem) {
|
|
1060
1070
|
path.$prefix = art.name.id; // prepend alias name
|
|
1061
|
-
|
|
1062
|
-
|
|
1071
|
+
if ($extended === 'columns') {
|
|
1072
|
+
warning( 'ref-special-in-extend', [ head.location, user ],
|
|
1073
|
+
{ '#': 'alias', id: head.id, art: elem._origin._main } );
|
|
1074
|
+
}
|
|
1075
|
+
else {
|
|
1076
|
+
error( 'ref-unexpected-in-extend', [ head.location, user ], {
|
|
1077
|
+
'#': 'alias',
|
|
1078
|
+
keyword: $extended,
|
|
1079
|
+
id: head.id,
|
|
1080
|
+
art: elem._origin._main,
|
|
1081
|
+
} );
|
|
1082
|
+
}
|
|
1063
1083
|
setLink( head, '_navigation', elem );
|
|
1064
1084
|
return setArtifactLink( head, elem._origin );
|
|
1065
1085
|
}
|
|
1066
1086
|
else if ($extended && art.elements) {
|
|
1067
|
-
warning( 'ref-deprecated-in-extend', [ head.location, user ],
|
|
1068
|
-
|
|
1069
|
-
'In an added column, do not use the table alias $(ID) to refer to source elements' );
|
|
1087
|
+
warning( 'ref-deprecated-in-extend', [ head.location, user ],
|
|
1088
|
+
{ '#': $extended, id: head.id } );
|
|
1070
1089
|
}
|
|
1071
1090
|
}
|
|
1072
1091
|
/* FALLTHROUGH */
|
|
@@ -87,7 +87,7 @@ function tweakAssocs( model ) {
|
|
|
87
87
|
forEachGeneric( art, 'elements', complainAboutTargetOutsideService );
|
|
88
88
|
|
|
89
89
|
if (art.query) {
|
|
90
|
-
traverseQueryPost(art.query,
|
|
90
|
+
traverseQueryPost(art.query, null, (query) => {
|
|
91
91
|
forEachGeneric( query, 'elements', handleQueryElements );
|
|
92
92
|
});
|
|
93
93
|
}
|
|
@@ -103,7 +103,7 @@ function tweakAssocs( model ) {
|
|
|
103
103
|
// Check explicit ON / keys with REDIRECTED TO
|
|
104
104
|
// TODO: run on all queries, but this is potentially incompatible
|
|
105
105
|
// function rewriteViewCheck( view ) {
|
|
106
|
-
// traverseQueryPost( view.query,
|
|
106
|
+
// traverseQueryPost( view.query, null, ( query ) => {
|
|
107
107
|
// forEachGeneric( query, 'elements', rewriteAssociationCheck );
|
|
108
108
|
// } );
|
|
109
109
|
// }
|