@sap/cds-compiler 4.2.4 → 4.3.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 +33 -0
- package/bin/cdsc.js +8 -0
- package/bin/cdshi.js +3 -3
- package/doc/CHANGELOG_BETA.md +7 -0
- package/lib/api/main.js +19 -0
- package/lib/base/location.js +16 -0
- package/lib/base/message-registry.js +47 -16
- package/lib/base/messages.js +49 -38
- package/lib/base/model.js +1 -1
- package/lib/checks/checkPathsInStoredCalcElement.js +83 -0
- package/lib/checks/existsExpressionsOnlyForeignKeys.js +71 -0
- package/lib/checks/existsMustEndInAssoc.js +27 -0
- package/lib/checks/onConditions.js +47 -1
- package/lib/checks/validator.js +10 -1
- package/lib/compiler/assert-consistency.js +23 -15
- package/lib/compiler/base.js +31 -14
- package/lib/compiler/builtins.js +21 -20
- package/lib/compiler/checks.js +36 -49
- package/lib/compiler/define.js +71 -91
- package/lib/compiler/extend.js +27 -25
- package/lib/compiler/finalize-parse-cdl.js +1 -1
- package/lib/compiler/generate.js +67 -87
- package/lib/compiler/kick-start.js +9 -5
- package/lib/compiler/populate.js +32 -30
- package/lib/compiler/propagator.js +2 -0
- package/lib/compiler/resolve.js +29 -25
- package/lib/compiler/shared.js +57 -31
- package/lib/compiler/tweak-assocs.js +203 -22
- package/lib/compiler/utils.js +0 -18
- package/lib/gen/Dictionary.json +10 -4
- package/lib/gen/language.checksum +1 -1
- package/lib/gen/languageParser.js +3 -3
- package/lib/inspect/inspectPropagation.js +2 -1
- package/lib/json/from-csn.js +63 -28
- package/lib/json/to-csn.js +23 -13
- package/lib/language/antlrParser.js +1 -1
- package/lib/language/errorStrategy.js +5 -1
- package/lib/language/genericAntlrParser.js +67 -61
- package/lib/main.d.ts +26 -1
- package/lib/main.js +2 -1
- package/lib/model/csnRefs.js +1 -0
- package/lib/model/csnUtils.js +28 -0
- package/lib/model/revealInternalProperties.js +3 -9
- package/lib/optionProcessor.js +17 -1
- package/lib/render/toCdl.js +1 -1
- package/lib/transform/db/associations.js +3 -4
- package/lib/transform/db/backlinks.js +293 -0
- package/lib/transform/db/expansion.js +9 -7
- package/lib/transform/db/flattening.js +3 -2
- package/lib/transform/db/rewriteCalculatedElements.js +1 -67
- package/lib/transform/db/transformExists.js +3 -58
- package/lib/transform/db/views.js +8 -14
- package/lib/transform/effective/.eslintrc.json +4 -0
- package/lib/transform/effective/associations.js +101 -0
- package/lib/transform/effective/main.js +88 -0
- package/lib/transform/effective/misc.js +61 -0
- package/lib/transform/effective/queries.js +42 -0
- package/lib/transform/effective/types.js +121 -0
- package/lib/transform/forRelationalDB.js +12 -235
- package/lib/transform/localized.js +22 -3
- package/lib/transform/parseExpr.js +7 -3
- package/lib/transform/transformUtils.js +5 -22
- package/lib/transform/translateAssocsToJoins.js +44 -39
- package/lib/transform/universalCsn/universalCsnEnricher.js +17 -1
- package/package.json +1 -2
- package/lib/language/language.g4 +0 -3260
|
@@ -60,7 +60,7 @@ function translateAssocsToJoins(model, inputOptions = {})
|
|
|
60
60
|
// create JOINs for foreign key paths
|
|
61
61
|
const noJoinForFK = options.forHana ? !options.joinfk : true;
|
|
62
62
|
|
|
63
|
-
// Note: This is called from the 'forHana' transformations, so it is controlled by its options
|
|
63
|
+
// Note: This is called from the 'forHana' transformations, so it is controlled by its options
|
|
64
64
|
const pathDelimiter = (options.forHana && options.sqlMapping === 'hdbcds') ? '.' : '_';
|
|
65
65
|
|
|
66
66
|
forEachDefinition(model, prepareAssociations);
|
|
@@ -76,7 +76,9 @@ function translateAssocsToJoins(model, inputOptions = {})
|
|
|
76
76
|
prepended to all source side paths of the resulting ON condition
|
|
77
77
|
(cut off name.id from name.element)
|
|
78
78
|
*/
|
|
79
|
-
art.$elementPrefix =
|
|
79
|
+
art.$elementPrefix = '';
|
|
80
|
+
for(let parent = art._parent; parent?.kind === 'element'; parent = parent._parent)
|
|
81
|
+
art.$elementPrefix = parent.name.id + pathDelimiter + art.$elementPrefix;
|
|
80
82
|
|
|
81
83
|
/*
|
|
82
84
|
Create path prefix tree for Foreign Keys, required to substitute
|
|
@@ -207,7 +209,7 @@ function translateAssocsToJoins(model, inputOptions = {})
|
|
|
207
209
|
let alias = taName;
|
|
208
210
|
if (ta.name.$inferred === '$internal') {
|
|
209
211
|
// query has no explicit table alias, i.e. is internal: make it visible and remove `$`
|
|
210
|
-
alias = ta.name.
|
|
212
|
+
alias = ta.name.id.replace(/^[$]/, '_');
|
|
211
213
|
ta.$inferred = undefined;
|
|
212
214
|
ta.name.$inferred = undefined;
|
|
213
215
|
}
|
|
@@ -221,11 +223,11 @@ function translateAssocsToJoins(model, inputOptions = {})
|
|
|
221
223
|
}
|
|
222
224
|
// Only subqueries of the FROM clause have a name (which is the alias)
|
|
223
225
|
// TODO Discuss: a query does not have a name.id anymore
|
|
224
|
-
const queryAlias = query._parent; // parent could also be outer query, or main entity
|
|
225
|
-
if(query.op.val === 'SELECT' && query.name.id && queryAlias && queryAlias.kind === '$tableAlias')
|
|
226
|
-
{
|
|
227
|
-
|
|
228
|
-
}
|
|
226
|
+
// const queryAlias = query._parent; // parent could also be outer query, or main entity
|
|
227
|
+
// if(query.op.val === 'SELECT' && query.name.id && queryAlias && queryAlias.kind === '$tableAlias')
|
|
228
|
+
// {
|
|
229
|
+
// query.name.id = queryAlias._parent.$tableAliases[query.name.id].$QA.name.id;
|
|
230
|
+
// }
|
|
229
231
|
}
|
|
230
232
|
|
|
231
233
|
/*
|
|
@@ -430,7 +432,7 @@ function translateAssocsToJoins(model, inputOptions = {})
|
|
|
430
432
|
if(art.kind === 'entity')
|
|
431
433
|
{
|
|
432
434
|
if(!childQat.$QA)
|
|
433
|
-
childQat.$QA = createQA(env, art, art.name.
|
|
435
|
+
childQat.$QA = createQA(env, art, art.name.id.split('.').pop(), childQat._namedArgs);
|
|
434
436
|
incAliasCount(env, childQat.$QA);
|
|
435
437
|
newAssocLHS = childQat.$QA;
|
|
436
438
|
|
|
@@ -529,7 +531,7 @@ function translateAssocsToJoins(model, inputOptions = {})
|
|
|
529
531
|
const xsnCard = {
|
|
530
532
|
targetMax : { literal: 'number', val: 1 }
|
|
531
533
|
};
|
|
532
|
-
if(assoc.type._artifact._effectiveType.name.
|
|
534
|
+
if(assoc.type._artifact._effectiveType.name.id === 'cds.Composition') {
|
|
533
535
|
xsnCard.sourceMin = { literal: 'number', val: 1 };
|
|
534
536
|
xsnCard.sourceMax = { literal: 'number', val: 1 };
|
|
535
537
|
}
|
|
@@ -658,7 +660,7 @@ function translateAssocsToJoins(model, inputOptions = {})
|
|
|
658
660
|
// clone ON condition with rewritten paths and substituted backlink conditions
|
|
659
661
|
function cloneOnCondition(expr)
|
|
660
662
|
{
|
|
661
|
-
if(expr.op
|
|
663
|
+
if(expr.op?.val === 'xpr')
|
|
662
664
|
return cloneOnCondExprStream(expr);
|
|
663
665
|
else
|
|
664
666
|
return cloneOnCondExprTree(expr);
|
|
@@ -669,7 +671,7 @@ function translateAssocsToJoins(model, inputOptions = {})
|
|
|
669
671
|
const result = { op: { val: expr.op.val }, args: [ ] };
|
|
670
672
|
for(let i = 0; i < args.length; i++)
|
|
671
673
|
{
|
|
672
|
-
if(args[i].op
|
|
674
|
+
if(args[i].op?.val === 'xpr')
|
|
673
675
|
{
|
|
674
676
|
result.args.push(cloneOnCondition(args[i]));
|
|
675
677
|
}
|
|
@@ -696,11 +698,11 @@ function translateAssocsToJoins(model, inputOptions = {})
|
|
|
696
698
|
i += 2; // skip next two tokens and continue with loop
|
|
697
699
|
continue;
|
|
698
700
|
}
|
|
699
|
-
else
|
|
701
|
+
else // it's ensured that it's a path
|
|
700
702
|
result.args.push(rewritePathNode(args[i]));
|
|
701
703
|
}
|
|
702
|
-
else
|
|
703
|
-
result.args.push(
|
|
704
|
+
else // could be `{op:…}`, clone generically
|
|
705
|
+
result.args.push(cloneOnCondition(args[i]));
|
|
704
706
|
}
|
|
705
707
|
return result;
|
|
706
708
|
}
|
|
@@ -731,7 +733,8 @@ function translateAssocsToJoins(model, inputOptions = {})
|
|
|
731
733
|
// this will substitute multiple backlink conditions ($self = ... AND $self = ...AND ...)
|
|
732
734
|
if(expr.op) {
|
|
733
735
|
let x = clone(expr);
|
|
734
|
-
|
|
736
|
+
if(expr.args)
|
|
737
|
+
x.args = expr.args.map(cloneOnCondition);
|
|
735
738
|
return x;
|
|
736
739
|
}
|
|
737
740
|
|
|
@@ -931,8 +934,8 @@ function translateAssocsToJoins(model, inputOptions = {})
|
|
|
931
934
|
|
|
932
935
|
if(elt) {
|
|
933
936
|
if(Array.isArray(elt)) {
|
|
934
|
-
const names = elt.map(e => e._origin.name.
|
|
935
|
-
error(null, [ assocQAT._origin.location, assocQAT._origin ], { elemref: path[0].id, id: assoc.name.id, art: assoc.
|
|
937
|
+
const names = elt.map(e => (e._origin._main || e._origin).name.id);
|
|
938
|
+
error(null, [ assocQAT._origin.location, assocQAT._origin ], { elemref: path[0].id, id: assoc.name.id, art: assoc._main, names },
|
|
936
939
|
'Element $(ELEMREF) referred in association $(ID) of artifact $(ART) is available from multiple query sources $(NAMES)');
|
|
937
940
|
return pathNode.path;
|
|
938
941
|
} else {
|
|
@@ -940,16 +943,16 @@ function translateAssocsToJoins(model, inputOptions = {})
|
|
|
940
943
|
if(elt._origin._main !== path[0]._artifact._origin._main) {
|
|
941
944
|
warning(null, [ assocQAT._origin.location, assocQAT._origin ], {
|
|
942
945
|
elemref: path[0].id,
|
|
943
|
-
id: assoc.name.id, art: assoc.name.
|
|
944
|
-
name: path[0]._artifact._origin._main.name.
|
|
945
|
-
alias: elt._origin._main.name.
|
|
946
|
-
source: elt._main.name.
|
|
946
|
+
id: assoc.name.id, art: assoc._main.name.id,
|
|
947
|
+
name: path[0]._artifact._origin._main.name.id,
|
|
948
|
+
alias: elt._origin._main.name.id,
|
|
949
|
+
source: elt._main.name.id,
|
|
947
950
|
}, 'Element $(ELEMREF) referred in association $(ID) of artifact $(ART) originates from $(NAME) and from $(ALIAS) in $(SOURCE)');
|
|
948
951
|
}
|
|
949
952
|
_navigation = elt._parent;
|
|
950
953
|
}
|
|
951
954
|
} else {
|
|
952
|
-
error(null, [ assocQAT._origin.location, assocQAT._origin ], { elemref: path[0].id, id: assoc.name.id, art: assoc.
|
|
955
|
+
error(null, [ assocQAT._origin.location, assocQAT._origin ], { elemref: path[0].id, id: assoc.name.id, art: (assoc._main || assoc) },
|
|
953
956
|
'Element $(ELEMREF) referred in association $(ID) of artifact $(ART) has not been found');
|
|
954
957
|
return pathNode.path;
|
|
955
958
|
}
|
|
@@ -1020,7 +1023,7 @@ function translateAssocsToJoins(model, inputOptions = {})
|
|
|
1020
1023
|
}
|
|
1021
1024
|
|
|
1022
1025
|
const pathStep = {
|
|
1023
|
-
id: artifact.name.
|
|
1026
|
+
id: (artifact._main || artifact).name.id,
|
|
1024
1027
|
_artifact: artifact,
|
|
1025
1028
|
_navigation : { name: { select: env.queryIndex + 1 } } // ???
|
|
1026
1029
|
};
|
|
@@ -1320,20 +1323,20 @@ function translateAssocsToJoins(model, inputOptions = {})
|
|
|
1320
1323
|
/* checks for all path steps */
|
|
1321
1324
|
if(ps.args) {
|
|
1322
1325
|
error(null, [pathDict.location, lead],
|
|
1323
|
-
{
|
|
1324
|
-
'$(
|
|
1326
|
+
{ art: lead._main || lead, id: ps.id },
|
|
1327
|
+
'$(ART): $(ID) must not have parameters');
|
|
1325
1328
|
pathDict.$check = false;
|
|
1326
1329
|
}
|
|
1327
1330
|
if(ps.where) {
|
|
1328
1331
|
error(null, [pathDict.location, lead],
|
|
1329
|
-
{
|
|
1330
|
-
'$(
|
|
1332
|
+
{ art: lead._main || lead, id: ps.id },
|
|
1333
|
+
'$(ART): $(ID) must not have a filter');
|
|
1331
1334
|
pathDict.$check = false;
|
|
1332
1335
|
}
|
|
1333
1336
|
if(ps._artifact.virtual) {
|
|
1334
1337
|
error(null, [pathDict.location, lead],
|
|
1335
|
-
{
|
|
1336
|
-
'$(
|
|
1338
|
+
{ art: lead._main || lead, id: ps.id },
|
|
1339
|
+
'$(ART): $(ID) must not be virtual');
|
|
1337
1340
|
pathDict.$check = false;
|
|
1338
1341
|
}
|
|
1339
1342
|
// checks for all path steps except the first one (if it is the name of the defining association)
|
|
@@ -1345,15 +1348,15 @@ function translateAssocsToJoins(model, inputOptions = {})
|
|
|
1345
1348
|
if(ps._artifact.on)
|
|
1346
1349
|
{
|
|
1347
1350
|
error(null, [pathDict.location, lead],
|
|
1348
|
-
{
|
|
1349
|
-
'$(
|
|
1351
|
+
{ art: lead._main || lead, id: ps.id, alias: pathAsStr(pathDict.path) },
|
|
1352
|
+
'$(ART): $(ID) in path $(ALIAS) must not be an unmanaged association');
|
|
1350
1353
|
pathDict.$check = false;
|
|
1351
1354
|
}
|
|
1352
1355
|
else if(ps._artifact.$fkPathPrefixTree)// must be managed
|
|
1353
1356
|
{
|
|
1354
1357
|
if(!ps._artifact.$fkPathPrefixTree.children[la1.id]) {
|
|
1355
1358
|
error(null, [pathDict.location, lead],
|
|
1356
|
-
{ art: lead.
|
|
1359
|
+
{ art: lead._main || lead, id: la1.id, name: ps.id, alias: pathAsStr(pathDict.path) },
|
|
1357
1360
|
'$(ART): $(ID) is not foreign key of managed association $(NAME) in path $(ALIAS)' );
|
|
1358
1361
|
pathDict.$check = false;
|
|
1359
1362
|
}
|
|
@@ -1362,7 +1365,7 @@ function translateAssocsToJoins(model, inputOptions = {})
|
|
|
1362
1365
|
else {
|
|
1363
1366
|
// it is the last path step => no association
|
|
1364
1367
|
error(null, [pathDict.location, lead],
|
|
1365
|
-
{ art: lead.
|
|
1368
|
+
{ art: lead._main || lead, id: ps.id, alias: pathAsStr(pathDict.path) },
|
|
1366
1369
|
'$(ART): $(ID) in path $(ALIAS) must not be an association');
|
|
1367
1370
|
pathDict.$check = false;
|
|
1368
1371
|
}
|
|
@@ -1373,7 +1376,7 @@ function translateAssocsToJoins(model, inputOptions = {})
|
|
|
1373
1376
|
const artifact = lastSegment && lastSegment._artifact && lastSegment._artifact.type && lastSegment._artifact.type._artifact && lastSegment._artifact.type._artifact;
|
|
1374
1377
|
if (artifact && artifact.elements) {
|
|
1375
1378
|
error(null, [pathDict.location, lead],
|
|
1376
|
-
{ art: lead.
|
|
1379
|
+
{ art: lead._main || lead, id: lastSegment.id },
|
|
1377
1380
|
'$(ART): $(ID) must have scalar type');
|
|
1378
1381
|
pathDict.$check = false;
|
|
1379
1382
|
}
|
|
@@ -1547,10 +1550,11 @@ function translateAssocsToJoins(model, inputOptions = {})
|
|
|
1547
1550
|
*/
|
|
1548
1551
|
const art = qat._origin;
|
|
1549
1552
|
if(noJoinForFK && qat._origin.target) {
|
|
1550
|
-
if(!pathStep.
|
|
1551
|
-
!pathStep.args && // has no args
|
|
1553
|
+
if(!pathStep.args && // has no args
|
|
1552
1554
|
(
|
|
1553
|
-
(
|
|
1555
|
+
(
|
|
1556
|
+
!pathStep.where && // has no filter
|
|
1557
|
+
// not leaf + next step is foreign key
|
|
1554
1558
|
i < tail.length-1 &&
|
|
1555
1559
|
// path terminates on a scalar type
|
|
1556
1560
|
// _effectiveType.elements can be removed if forRelationalDB can expand fk paths correctly
|
|
@@ -1562,7 +1566,8 @@ function translateAssocsToJoins(model, inputOptions = {})
|
|
|
1562
1566
|
)
|
|
1563
1567
|
||
|
|
1564
1568
|
(
|
|
1565
|
-
// Non
|
|
1569
|
+
// Non-FROM block path terminates on association, e.g. publishing associations
|
|
1570
|
+
// with or without filter.
|
|
1566
1571
|
i === tail.length-1 &&
|
|
1567
1572
|
env.location !== 'from'
|
|
1568
1573
|
)
|
|
@@ -96,7 +96,7 @@ module.exports = (csn, options) => {
|
|
|
96
96
|
val: always,
|
|
97
97
|
type: notWithItemsOrElements,
|
|
98
98
|
target: notWithItemsOrElements,
|
|
99
|
-
keys:
|
|
99
|
+
keys: specialKeysRules,
|
|
100
100
|
cardinality: notWithItemsOrElements,
|
|
101
101
|
};
|
|
102
102
|
|
|
@@ -747,6 +747,22 @@ function specialItemsRules( prop, target, source ) {
|
|
|
747
747
|
target[prop] = source[prop];
|
|
748
748
|
}
|
|
749
749
|
|
|
750
|
+
/**
|
|
751
|
+
* Don't propagate property `keys` if there is an ON-condition. This happens for
|
|
752
|
+
* published managed associations with filters in views, which are transformed into
|
|
753
|
+
* unmanaged associations, i.e. get an ON-condition.
|
|
754
|
+
*
|
|
755
|
+
* Besides that, rules from notWithItemsOrElements() apply.
|
|
756
|
+
*
|
|
757
|
+
* @param {string} prop
|
|
758
|
+
* @param {CSN.Element} target
|
|
759
|
+
* @param {CSN.Element} source
|
|
760
|
+
*/
|
|
761
|
+
function specialKeysRules( prop, target, source ) {
|
|
762
|
+
if (target.on === undefined)
|
|
763
|
+
notWithItemsOrElements( prop, target, source );
|
|
764
|
+
}
|
|
765
|
+
|
|
750
766
|
/**
|
|
751
767
|
* Don't propagate certain properties if the target already has a .items or .elements
|
|
752
768
|
*
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sap/cds-compiler",
|
|
3
|
-
"version": "4.2
|
|
3
|
+
"version": "4.3.2",
|
|
4
4
|
"description": "CDS (Core Data Services) compiler and backends",
|
|
5
5
|
"homepage": "https://cap.cloud.sap/",
|
|
6
6
|
"author": "SAP SE (https://www.sap.com)",
|
|
@@ -38,7 +38,6 @@
|
|
|
38
38
|
"generateOdataAnnoRefs": "cross-env MAKEREFS='true' mocha test/testODataAnnotations.js",
|
|
39
39
|
"generateToSqlRefs": "cross-env MAKEREFS='true' mocha test/testToSql.js",
|
|
40
40
|
"generateToRenameRefs": "cross-env MAKEREFS='true' mocha test/testToRename.js",
|
|
41
|
-
"generateChecksRefs": "cross-env MAKEREFS='true' mocha test/testChecks.js",
|
|
42
41
|
"generateDraftRefs": "cross-env MAKEREFS='true' mocha test/testDraft.js",
|
|
43
42
|
"generateAllRefs": "node scripts/verifyGrammarChecksum.js && cross-env MAKEREFS='true' mocha --reporter-option maxDiffSize=0 test/ test3/"
|
|
44
43
|
},
|