@sap/cds-compiler 4.2.2 → 4.3.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 +32 -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 +2 -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 +7 -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 +18 -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 +42 -38
- 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
|
}
|
|
@@ -931,8 +933,8 @@ function translateAssocsToJoins(model, inputOptions = {})
|
|
|
931
933
|
|
|
932
934
|
if(elt) {
|
|
933
935
|
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.
|
|
936
|
+
const names = elt.map(e => (e._origin._main || e._origin).name.id);
|
|
937
|
+
error(null, [ assocQAT._origin.location, assocQAT._origin ], { elemref: path[0].id, id: assoc.name.id, art: assoc._main, names },
|
|
936
938
|
'Element $(ELEMREF) referred in association $(ID) of artifact $(ART) is available from multiple query sources $(NAMES)');
|
|
937
939
|
return pathNode.path;
|
|
938
940
|
} else {
|
|
@@ -940,16 +942,16 @@ function translateAssocsToJoins(model, inputOptions = {})
|
|
|
940
942
|
if(elt._origin._main !== path[0]._artifact._origin._main) {
|
|
941
943
|
warning(null, [ assocQAT._origin.location, assocQAT._origin ], {
|
|
942
944
|
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.
|
|
945
|
+
id: assoc.name.id, art: assoc._main.name.id,
|
|
946
|
+
name: path[0]._artifact._origin._main.name.id,
|
|
947
|
+
alias: elt._origin._main.name.id,
|
|
948
|
+
source: elt._main.name.id,
|
|
947
949
|
}, 'Element $(ELEMREF) referred in association $(ID) of artifact $(ART) originates from $(NAME) and from $(ALIAS) in $(SOURCE)');
|
|
948
950
|
}
|
|
949
951
|
_navigation = elt._parent;
|
|
950
952
|
}
|
|
951
953
|
} else {
|
|
952
|
-
error(null, [ assocQAT._origin.location, assocQAT._origin ], { elemref: path[0].id, id: assoc.name.id, art: assoc.
|
|
954
|
+
error(null, [ assocQAT._origin.location, assocQAT._origin ], { elemref: path[0].id, id: assoc.name.id, art: (assoc._main || assoc) },
|
|
953
955
|
'Element $(ELEMREF) referred in association $(ID) of artifact $(ART) has not been found');
|
|
954
956
|
return pathNode.path;
|
|
955
957
|
}
|
|
@@ -1020,7 +1022,7 @@ function translateAssocsToJoins(model, inputOptions = {})
|
|
|
1020
1022
|
}
|
|
1021
1023
|
|
|
1022
1024
|
const pathStep = {
|
|
1023
|
-
id: artifact.name.
|
|
1025
|
+
id: (artifact._main || artifact).name.id,
|
|
1024
1026
|
_artifact: artifact,
|
|
1025
1027
|
_navigation : { name: { select: env.queryIndex + 1 } } // ???
|
|
1026
1028
|
};
|
|
@@ -1320,20 +1322,20 @@ function translateAssocsToJoins(model, inputOptions = {})
|
|
|
1320
1322
|
/* checks for all path steps */
|
|
1321
1323
|
if(ps.args) {
|
|
1322
1324
|
error(null, [pathDict.location, lead],
|
|
1323
|
-
{
|
|
1324
|
-
'$(
|
|
1325
|
+
{ art: lead._main || lead, id: ps.id },
|
|
1326
|
+
'$(ART): $(ID) must not have parameters');
|
|
1325
1327
|
pathDict.$check = false;
|
|
1326
1328
|
}
|
|
1327
1329
|
if(ps.where) {
|
|
1328
1330
|
error(null, [pathDict.location, lead],
|
|
1329
|
-
{
|
|
1330
|
-
'$(
|
|
1331
|
+
{ art: lead._main || lead, id: ps.id },
|
|
1332
|
+
'$(ART): $(ID) must not have a filter');
|
|
1331
1333
|
pathDict.$check = false;
|
|
1332
1334
|
}
|
|
1333
1335
|
if(ps._artifact.virtual) {
|
|
1334
1336
|
error(null, [pathDict.location, lead],
|
|
1335
|
-
{
|
|
1336
|
-
'$(
|
|
1337
|
+
{ art: lead._main || lead, id: ps.id },
|
|
1338
|
+
'$(ART): $(ID) must not be virtual');
|
|
1337
1339
|
pathDict.$check = false;
|
|
1338
1340
|
}
|
|
1339
1341
|
// checks for all path steps except the first one (if it is the name of the defining association)
|
|
@@ -1345,15 +1347,15 @@ function translateAssocsToJoins(model, inputOptions = {})
|
|
|
1345
1347
|
if(ps._artifact.on)
|
|
1346
1348
|
{
|
|
1347
1349
|
error(null, [pathDict.location, lead],
|
|
1348
|
-
{
|
|
1349
|
-
'$(
|
|
1350
|
+
{ art: lead._main || lead, id: ps.id, alias: pathAsStr(pathDict.path) },
|
|
1351
|
+
'$(ART): $(ID) in path $(ALIAS) must not be an unmanaged association');
|
|
1350
1352
|
pathDict.$check = false;
|
|
1351
1353
|
}
|
|
1352
1354
|
else if(ps._artifact.$fkPathPrefixTree)// must be managed
|
|
1353
1355
|
{
|
|
1354
1356
|
if(!ps._artifact.$fkPathPrefixTree.children[la1.id]) {
|
|
1355
1357
|
error(null, [pathDict.location, lead],
|
|
1356
|
-
{ art: lead.
|
|
1358
|
+
{ art: lead._main || lead, id: la1.id, name: ps.id, alias: pathAsStr(pathDict.path) },
|
|
1357
1359
|
'$(ART): $(ID) is not foreign key of managed association $(NAME) in path $(ALIAS)' );
|
|
1358
1360
|
pathDict.$check = false;
|
|
1359
1361
|
}
|
|
@@ -1362,7 +1364,7 @@ function translateAssocsToJoins(model, inputOptions = {})
|
|
|
1362
1364
|
else {
|
|
1363
1365
|
// it is the last path step => no association
|
|
1364
1366
|
error(null, [pathDict.location, lead],
|
|
1365
|
-
{ art: lead.
|
|
1367
|
+
{ art: lead._main || lead, id: ps.id, alias: pathAsStr(pathDict.path) },
|
|
1366
1368
|
'$(ART): $(ID) in path $(ALIAS) must not be an association');
|
|
1367
1369
|
pathDict.$check = false;
|
|
1368
1370
|
}
|
|
@@ -1373,7 +1375,7 @@ function translateAssocsToJoins(model, inputOptions = {})
|
|
|
1373
1375
|
const artifact = lastSegment && lastSegment._artifact && lastSegment._artifact.type && lastSegment._artifact.type._artifact && lastSegment._artifact.type._artifact;
|
|
1374
1376
|
if (artifact && artifact.elements) {
|
|
1375
1377
|
error(null, [pathDict.location, lead],
|
|
1376
|
-
{ art: lead.
|
|
1378
|
+
{ art: lead._main || lead, id: lastSegment.id },
|
|
1377
1379
|
'$(ART): $(ID) must have scalar type');
|
|
1378
1380
|
pathDict.$check = false;
|
|
1379
1381
|
}
|
|
@@ -1547,10 +1549,11 @@ function translateAssocsToJoins(model, inputOptions = {})
|
|
|
1547
1549
|
*/
|
|
1548
1550
|
const art = qat._origin;
|
|
1549
1551
|
if(noJoinForFK && qat._origin.target) {
|
|
1550
|
-
if(!pathStep.
|
|
1551
|
-
!pathStep.args && // has no args
|
|
1552
|
+
if(!pathStep.args && // has no args
|
|
1552
1553
|
(
|
|
1553
|
-
(
|
|
1554
|
+
(
|
|
1555
|
+
!pathStep.where && // has no filter
|
|
1556
|
+
// not leaf + next step is foreign key
|
|
1554
1557
|
i < tail.length-1 &&
|
|
1555
1558
|
// path terminates on a scalar type
|
|
1556
1559
|
// _effectiveType.elements can be removed if forRelationalDB can expand fk paths correctly
|
|
@@ -1562,7 +1565,8 @@ function translateAssocsToJoins(model, inputOptions = {})
|
|
|
1562
1565
|
)
|
|
1563
1566
|
||
|
|
1564
1567
|
(
|
|
1565
|
-
// Non
|
|
1568
|
+
// Non-FROM block path terminates on association, e.g. publishing associations
|
|
1569
|
+
// with or without filter.
|
|
1566
1570
|
i === tail.length-1 &&
|
|
1567
1571
|
env.location !== 'from'
|
|
1568
1572
|
)
|
|
@@ -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.
|
|
3
|
+
"version": "4.3.0",
|
|
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
|
},
|