@sap/cds-compiler 2.10.2 → 2.10.4
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 +11 -5
- package/doc/CHANGELOG_BETA.md +6 -0
- package/lib/compiler/resolver.js +43 -1
- package/lib/edm/edmPreprocessor.js +3 -2
- package/lib/model/csnUtils.js +3 -0
- package/lib/render/toCdl.js +6 -5
- package/lib/render/toHdbcds.js +8 -6
- package/lib/render/toSql.js +22 -23
- package/lib/render/utils/common.js +1 -1
- package/lib/transform/db/expansion.js +1 -1
- package/lib/transform/db/flattening.js +1 -1
- package/lib/transform/db/transformExists.js +32 -7
- package/lib/transform/odata/structuralPath.js +1 -5
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,16 @@
|
|
|
7
7
|
Note: `beta` fixes, changes and features are usually not listed in this ChangeLog but [here](doc/CHANGELOG_BETA.md).
|
|
8
8
|
The compiler behavior concerning `beta` features can change at any time without notice.
|
|
9
9
|
|
|
10
|
+
## Version 2.10.4 - 2021-11-05
|
|
11
|
+
|
|
12
|
+
### Fixed
|
|
13
|
+
|
|
14
|
+
- to.sql/hdi/hdbcds:
|
|
15
|
+
+ Correctly complain about `exists` in conjunction with non-associations/compositions
|
|
16
|
+
+ Don't resolve types in action returns, as this causes issues with $self-resolution
|
|
17
|
+
|
|
18
|
+
- to.edm(x): Be robust against transitively untyped keys in stacked view hierarchies
|
|
19
|
+
|
|
10
20
|
## Version 2.10.2 - 2021-10-29
|
|
11
21
|
|
|
12
22
|
### Fixed
|
|
@@ -38,13 +48,9 @@ The compiler behavior concerning `beta` features can change at any time without
|
|
|
38
48
|
+ `exists` can now be followed by more than one association step.
|
|
39
49
|
`exists assoc.anotherassoc.moreassoc` is semantically equivalent to `exists assoc[exists anotherassoc[exists moreassoc]]`
|
|
40
50
|
|
|
41
|
-
### Removed
|
|
42
|
-
|
|
43
51
|
### Changed
|
|
44
52
|
|
|
45
|
-
- to.odata: Inform when overwriting draft action annotations like
|
|
46
|
-
|
|
47
|
-
### Fixed
|
|
53
|
+
- to.odata: Inform when overwriting draft action annotations like `@Common.DraftRoot.ActivationAction`.
|
|
48
54
|
|
|
49
55
|
## Version 2.9.0 - 2021-10-15
|
|
50
56
|
|
package/doc/CHANGELOG_BETA.md
CHANGED
|
@@ -8,6 +8,12 @@ Note: `beta` fixes, changes and features are listed in this ChangeLog just for i
|
|
|
8
8
|
The compiler behavior concerning `beta` features can change at any time without notice.
|
|
9
9
|
**Don't use `beta` fixes, changes and features in productive mode.**
|
|
10
10
|
|
|
11
|
+
## Version 2.10.4
|
|
12
|
+
|
|
13
|
+
### Fixed `nestedProjections`
|
|
14
|
+
|
|
15
|
+
- to.sql/hdi/hdbcds: Correctly handle a `*` at the not-first place in the query
|
|
16
|
+
|
|
11
17
|
## Version 2.6.0
|
|
12
18
|
|
|
13
19
|
### Removed `pretransformedCSN`
|
package/lib/compiler/resolver.js
CHANGED
|
@@ -541,7 +541,8 @@ function resolve( model ) {
|
|
|
541
541
|
if (art.elements || art.kind === '$tableAlias' ||
|
|
542
542
|
// no element expansions for "non-proper" types like
|
|
543
543
|
// entities (as parameter types) etc:
|
|
544
|
-
struct.kind !== 'type' && struct.kind !== 'element' &&
|
|
544
|
+
struct.kind !== 'type' && struct.kind !== 'element' && struct.kind !== 'param' &&
|
|
545
|
+
!struct._outer)
|
|
545
546
|
return false;
|
|
546
547
|
if (struct.elements === 0 || isInParents( art, eType )) {
|
|
547
548
|
art.elements = 0; // circular
|
|
@@ -1586,6 +1587,11 @@ function resolve( model ) {
|
|
|
1586
1587
|
}
|
|
1587
1588
|
}
|
|
1588
1589
|
if (art && art._annotate) {
|
|
1590
|
+
if (art.kind === 'action' || art.kind === 'function') {
|
|
1591
|
+
expandParameters( art );
|
|
1592
|
+
if (art.returns)
|
|
1593
|
+
effectiveType( art.returns );
|
|
1594
|
+
}
|
|
1589
1595
|
const aor = art.returns || art;
|
|
1590
1596
|
const obj = aor.items || aor.targetAspect || aor;
|
|
1591
1597
|
// Currently(?), effectiveType() does not calculate the effective type of
|
|
@@ -1620,6 +1626,42 @@ function resolve( model ) {
|
|
|
1620
1626
|
annotateMembers( env && env[n], dict[n], prop, n, parent, kind );
|
|
1621
1627
|
}
|
|
1622
1628
|
}
|
|
1629
|
+
function expandParameters( action ) {
|
|
1630
|
+
// see also expandElements()
|
|
1631
|
+
if (!enableExpandElements || !effectiveType( action ))
|
|
1632
|
+
return;
|
|
1633
|
+
const chain = [];
|
|
1634
|
+
// Should we be able to consider params and returns separately?
|
|
1635
|
+
// Probably not, let to-csn omit unchanged params/returns.
|
|
1636
|
+
while (action._origin && !action.params) {
|
|
1637
|
+
chain.push( action );
|
|
1638
|
+
action = action._origin;
|
|
1639
|
+
}
|
|
1640
|
+
chain.reverse();
|
|
1641
|
+
for (const art of chain) {
|
|
1642
|
+
const origin = art._origin;
|
|
1643
|
+
if (!art.params && origin.params) {
|
|
1644
|
+
for (const name in origin.params) {
|
|
1645
|
+
// TODO: we could check _annotate here to decide whether we really
|
|
1646
|
+
// not to create proxies
|
|
1647
|
+
const orig = origin.params[name];
|
|
1648
|
+
linkToOrigin( orig, name, art, 'params', weakLocation( orig.location ), true )
|
|
1649
|
+
.$inferred = 'expand-param';
|
|
1650
|
+
}
|
|
1651
|
+
}
|
|
1652
|
+
if (!art.returns && origin.returns) {
|
|
1653
|
+
// TODO: make linkToOrigin() work for returns, kind/name?
|
|
1654
|
+
const location = weakLocation( origin.returns.location );
|
|
1655
|
+
art.returns = {
|
|
1656
|
+
name: Object.assign( {}, art.name, { id: '', param: '', location } ),
|
|
1657
|
+
kind: 'param',
|
|
1658
|
+
location,
|
|
1659
|
+
$inferred: 'expand-param',
|
|
1660
|
+
};
|
|
1661
|
+
setProp( art.returns, '_origin', origin.returns );
|
|
1662
|
+
}
|
|
1663
|
+
}
|
|
1664
|
+
}
|
|
1623
1665
|
|
|
1624
1666
|
function extensionFor( art ) {
|
|
1625
1667
|
if (art.kind === 'annotate')
|
|
@@ -1351,7 +1351,8 @@ function initializeModel(csn, _options, messageFunctions)
|
|
|
1351
1351
|
// OData requires all elements along the path to be nullable: false (that is either key or notNull)
|
|
1352
1352
|
|
|
1353
1353
|
const finalType = getFinalTypeDef(eltCsn.items && eltCsn.items.type || eltCsn.type);
|
|
1354
|
-
const elements = eltCsn.elements || eltCsn.items && eltCsn.items.elements
|
|
1354
|
+
const elements = eltCsn.elements || eltCsn.items && eltCsn.items.elements ||
|
|
1355
|
+
(finalType && (finalType.elements || finalType.items && finalType.items.elements));
|
|
1355
1356
|
if(elements) {
|
|
1356
1357
|
Object.entries(elements).forEach(([eltName, elt]) => {
|
|
1357
1358
|
const newRefs = produceKeyRefPaths(elt, prefix + options.pathDelimiter + eltName);
|
|
@@ -1400,7 +1401,7 @@ function initializeModel(csn, _options, messageFunctions)
|
|
|
1400
1401
|
{name: pathSegment, '#': pathSegment ? 'std' : 'scalar'});
|
|
1401
1402
|
}
|
|
1402
1403
|
// many
|
|
1403
|
-
let type = elt.items || getFinalTypeDef(elt.type).items;
|
|
1404
|
+
let type = elt.items || elt.type && !isBuiltinType(elt.type) && getFinalTypeDef(elt.type).items;
|
|
1404
1405
|
if(type) {
|
|
1405
1406
|
error('odata-spec-violation-key-array', location,
|
|
1406
1407
|
{name: pathSegment, '#': pathSegment ? 'std' : 'scalar'});
|
package/lib/model/csnUtils.js
CHANGED
|
@@ -1107,6 +1107,9 @@ function applyTransformations( csn, customTransformers={}, artifactTransformers=
|
|
|
1107
1107
|
}
|
|
1108
1108
|
|
|
1109
1109
|
function dictionary( node, prop, dict ) {
|
|
1110
|
+
// Allow skipping dicts like actions in forHanaNew
|
|
1111
|
+
if(options.skipDict && options.skipDict[prop])
|
|
1112
|
+
return;
|
|
1110
1113
|
csnPath.push( prop );
|
|
1111
1114
|
for (let name of Object.getOwnPropertyNames( dict )) {
|
|
1112
1115
|
standard( dict, name, dict[name] );
|
package/lib/render/toCdl.js
CHANGED
|
@@ -715,7 +715,7 @@ function toCdsSourceCsn(csn, options) {
|
|
|
715
715
|
|
|
716
716
|
// Even the first step might have parameters and/or a filter
|
|
717
717
|
if (path.ref[0].args)
|
|
718
|
-
result += `(${renderArgs(path.ref[0]
|
|
718
|
+
result += `(${renderArgs(path.ref[0], ':', env)})`;
|
|
719
719
|
|
|
720
720
|
if (path.ref[0].where)
|
|
721
721
|
result += `[${path.ref[0].cardinality ? (`${path.ref[0].cardinality.max}: `) : ''}${renderExpr(path.ref[0].where, env, true, true)}]`;
|
|
@@ -1474,13 +1474,13 @@ function toCdsSourceCsn(csn, options) {
|
|
|
1474
1474
|
|
|
1475
1475
|
// Not really a path step but an object-like function call
|
|
1476
1476
|
if (s.func)
|
|
1477
|
-
return `${s.func}(${renderArgs(s
|
|
1477
|
+
return `${s.func}(${renderArgs(s, '=>', env)})`;
|
|
1478
1478
|
|
|
1479
1479
|
// Path step, possibly with view parameters and/or filters
|
|
1480
1480
|
let result = `${quoteOrUppercaseId(s.id)}`;
|
|
1481
1481
|
if (s.args) {
|
|
1482
1482
|
// View parameters
|
|
1483
|
-
result += `(${renderArgs(s
|
|
1483
|
+
result += `(${renderArgs(s, ':', env)})`;
|
|
1484
1484
|
}
|
|
1485
1485
|
if (s.where) {
|
|
1486
1486
|
// Filter, possibly with cardinality
|
|
@@ -1498,12 +1498,13 @@ function toCdsSourceCsn(csn, options) {
|
|
|
1498
1498
|
* Render function arguments or view parameters (positional if array, named if object/dict),
|
|
1499
1499
|
* using 'sep' as separator for positional parameters
|
|
1500
1500
|
*
|
|
1501
|
-
* @param {object
|
|
1501
|
+
* @param {object} node with `args` to render
|
|
1502
1502
|
* @param {string} sep
|
|
1503
1503
|
* @param {CdlRenderEnvironment} env
|
|
1504
1504
|
* @returns {string}
|
|
1505
1505
|
*/
|
|
1506
|
-
function renderArgs(
|
|
1506
|
+
function renderArgs(node, sep, env) {
|
|
1507
|
+
const args = node.args ? node.args : {};
|
|
1507
1508
|
// Positional arguments
|
|
1508
1509
|
if (Array.isArray(args))
|
|
1509
1510
|
return args.map(arg => renderExpr(arg, env)).join(', ');
|
package/lib/render/toHdbcds.js
CHANGED
|
@@ -630,7 +630,7 @@ function toHdbcdsSource(csn, options) {
|
|
|
630
630
|
|
|
631
631
|
// Even the first step might have parameters and/or a filter
|
|
632
632
|
if (path.ref[0].args)
|
|
633
|
-
result += `(${renderArgs(path.ref[0]
|
|
633
|
+
result += `(${renderArgs(path.ref[0], ':', env)})`;
|
|
634
634
|
|
|
635
635
|
if (path.ref[0].where)
|
|
636
636
|
result += `[${path.ref[0].cardinality ? (`${path.ref[0].cardinality.max}: `) : ''}${renderExpr(path.ref[0].where, env, true, true)}]`;
|
|
@@ -1321,13 +1321,13 @@ function toHdbcdsSource(csn, options) {
|
|
|
1321
1321
|
|
|
1322
1322
|
// Not really a path step but an object-like function call
|
|
1323
1323
|
if (s.func)
|
|
1324
|
-
return `${s.func}(${renderArgs(s
|
|
1324
|
+
return `${s.func}(${renderArgs(s, '=>', env)})`;
|
|
1325
1325
|
|
|
1326
1326
|
// Path step, possibly with view parameters and/or filters
|
|
1327
1327
|
let result = `${formatIdentifier(s.id)}`;
|
|
1328
1328
|
if (s.args) {
|
|
1329
1329
|
// View parameters
|
|
1330
|
-
result += `(${renderArgs(s
|
|
1330
|
+
result += `(${renderArgs(s, ':', env)})`;
|
|
1331
1331
|
}
|
|
1332
1332
|
if (s.where) {
|
|
1333
1333
|
// Filter, possibly with cardinality
|
|
@@ -1344,19 +1344,21 @@ function toHdbcdsSource(csn, options) {
|
|
|
1344
1344
|
* Render function arguments or view parameters (positional if array, named if object/dict),
|
|
1345
1345
|
* using 'sep' as separator for positional parameters
|
|
1346
1346
|
*
|
|
1347
|
-
* @param {object
|
|
1347
|
+
* @param {object} node with `args` to render
|
|
1348
1348
|
* @param {string} sep Seperator between arguments
|
|
1349
1349
|
* @param {CdlRenderEnvironment} env Environment
|
|
1350
1350
|
* @returns {string} Rendered arguments
|
|
1351
1351
|
*/
|
|
1352
|
-
function renderArgs(
|
|
1352
|
+
function renderArgs(node, sep, env) {
|
|
1353
|
+
const args = node.args ? node.args : {};
|
|
1353
1354
|
// Positional arguments
|
|
1354
1355
|
if (Array.isArray(args))
|
|
1355
1356
|
return args.map(arg => renderExpr(arg, env)).join(', ');
|
|
1356
1357
|
|
|
1357
1358
|
// Named arguments (object/dict)
|
|
1358
1359
|
else if (typeof args === 'object')
|
|
1359
|
-
|
|
1360
|
+
// if this is a function param which is not a reference to the model, we must not quote it
|
|
1361
|
+
return Object.keys(args).map(key => `${node.func ? key : formatIdentifier(key)} ${sep} ${renderExpr(args[key], env)}`).join(', ');
|
|
1360
1362
|
|
|
1361
1363
|
|
|
1362
1364
|
throw new Error(`Unknown args: ${JSON.stringify(args)}`);
|
package/lib/render/toSql.js
CHANGED
|
@@ -385,7 +385,7 @@ function toSqlDdl(csn, options) {
|
|
|
385
385
|
function reducesTypeSize(def) {
|
|
386
386
|
// HANA does not allow decreasing the value of any of those type parameters.
|
|
387
387
|
return def.old.type === def.new.type &&
|
|
388
|
-
|
|
388
|
+
[ 'length', 'precision', 'scale' ].some(param => def.new[param] < def.old[param]);
|
|
389
389
|
}
|
|
390
390
|
function getEltStr(defVariant, eltName) {
|
|
391
391
|
return defVariant.target
|
|
@@ -525,12 +525,12 @@ function toSqlDdl(csn, options) {
|
|
|
525
525
|
referentialConstraints[fileName] = renderReferentialConstraint(referentialConstraint, childEnv.indent, false, csn, options);
|
|
526
526
|
});
|
|
527
527
|
if (renderReferentialConstraintsAsHdbconstraint) {
|
|
528
|
-
Object.entries(referentialConstraints).forEach(
|
|
528
|
+
Object.entries(referentialConstraints).forEach(([ fileName, constraint ]) => {
|
|
529
529
|
resultObj.hdbconstraint[fileName] = constraint;
|
|
530
530
|
});
|
|
531
531
|
}
|
|
532
532
|
else {
|
|
533
|
-
Object.values(referentialConstraints).forEach(
|
|
533
|
+
Object.values(referentialConstraints).forEach((constraint) => {
|
|
534
534
|
result += `,\n${constraint}`;
|
|
535
535
|
});
|
|
536
536
|
}
|
|
@@ -546,8 +546,7 @@ function toSqlDdl(csn, options) {
|
|
|
546
546
|
= `UNIQUE INVERTED INDEX ${renderArtifactName(`${artifactName}_${cn}`)} ON ${tableName} (${c.map(cpath => quoteSqlId(cpath.ref[0])).join(', ')})`;
|
|
547
547
|
}
|
|
548
548
|
else {
|
|
549
|
-
result += `,\n${childEnv.indent}CONSTRAINT ${renderArtifactName(`${artifactName}_${cn}`)} UNIQUE (${
|
|
550
|
-
c.map(cpath => quoteSqlId(cpath.ref[0])).join(', ')})`;
|
|
549
|
+
result += `,\n${childEnv.indent}CONSTRAINT ${renderArtifactName(`${artifactName}_${cn}`)} UNIQUE (${c.map(cpath => quoteSqlId(cpath.ref[0])).join(', ')})`;
|
|
551
550
|
}
|
|
552
551
|
}
|
|
553
552
|
result += `${env.indent}\n)`;
|
|
@@ -659,8 +658,7 @@ function toSqlDdl(csn, options) {
|
|
|
659
658
|
if (duplicateChecker)
|
|
660
659
|
duplicateChecker.addElement(quotedElementName, elm.$location, elementName);
|
|
661
660
|
|
|
662
|
-
let result = `${env.indent + quotedElementName} ${
|
|
663
|
-
renderTypeReference(artifactName, elementName, elm)
|
|
661
|
+
let result = `${env.indent + quotedElementName} ${renderTypeReference(artifactName, elementName, elm)
|
|
664
662
|
}${renderNullability(elm, true)}`;
|
|
665
663
|
if (elm.default)
|
|
666
664
|
result += ` DEFAULT ${renderExpr(elm.default, env)}`;
|
|
@@ -956,7 +954,7 @@ function toSqlDdl(csn, options) {
|
|
|
956
954
|
// An empty actual parameter list is rendered as `()`.
|
|
957
955
|
const ref = csn.definitions[path.ref[0].id] || csn.definitions[path.ref[0]];
|
|
958
956
|
if (ref && ref.params) {
|
|
959
|
-
result += `(${renderArgs(path.ref[0]
|
|
957
|
+
result += `(${renderArgs(path.ref[0] || {}, '=>', env, syntax)})`;
|
|
960
958
|
}
|
|
961
959
|
else if ([ 'udf' ].includes(syntax)) {
|
|
962
960
|
// if syntax is user defined function, render empty argument list
|
|
@@ -978,21 +976,23 @@ function toSqlDdl(csn, options) {
|
|
|
978
976
|
* Render function arguments or view parameters (positional if array, named if object/dict),
|
|
979
977
|
* using 'sep' as separator for positional parameters
|
|
980
978
|
*
|
|
981
|
-
* @param {
|
|
979
|
+
* @param {object} node with `args` to render
|
|
982
980
|
* @param {string} sep Separator between args
|
|
983
981
|
* @param {object} env Render environment
|
|
984
982
|
* @param {string} syntax Some magic A2J paramter - for calcview parameter rendering
|
|
985
983
|
* @returns {string} Rendered arguments
|
|
986
984
|
* @throws Throws if args is not an array or object.
|
|
987
985
|
*/
|
|
988
|
-
function renderArgs(
|
|
986
|
+
function renderArgs(node, sep, env, syntax) {
|
|
987
|
+
const args = node.args ? node.args : {};
|
|
989
988
|
// Positional arguments
|
|
990
989
|
if (Array.isArray(args))
|
|
991
990
|
return args.map(arg => renderExpr(arg, env)).join(', ');
|
|
992
991
|
|
|
993
992
|
// Named arguments (object/dict)
|
|
994
993
|
else if (typeof args === 'object')
|
|
995
|
-
|
|
994
|
+
// if this is a function param which is not a reference to the model, we must not quote it
|
|
995
|
+
return Object.keys(args).map(key => `${node.func ? key : decorateParameter(key, syntax)} ${sep} ${renderExpr(args[key], env)}`).join(', ');
|
|
996
996
|
|
|
997
997
|
|
|
998
998
|
throw new Error(`Unknown args: ${JSON.stringify(args)}`);
|
|
@@ -1151,12 +1151,11 @@ function toSqlDdl(csn, options) {
|
|
|
1151
1151
|
const childEnv = increaseIndent(env);
|
|
1152
1152
|
result += `SELECT${select.distinct ? ' DISTINCT' : ''}`;
|
|
1153
1153
|
// FIXME: We probably also need to consider `excluding` here ?
|
|
1154
|
-
result += `\n${
|
|
1155
|
-
(select.
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
.join(',\n')}\n`;
|
|
1154
|
+
result += `\n${(select.columns || [ '*' ])
|
|
1155
|
+
.filter(col => !(select.mixin || Object.create(null))[firstPathStepId(col.ref)]) // No mixin columns
|
|
1156
|
+
.map(col => renderViewColumn(col, childEnv))
|
|
1157
|
+
.filter(s => s !== '')
|
|
1158
|
+
.join(',\n')}\n`;
|
|
1160
1159
|
result += `${env.indent}FROM ${renderViewSource(artifactName, select.from, env)}`;
|
|
1161
1160
|
if (select.where)
|
|
1162
1161
|
result += `\n${env.indent}WHERE ${renderExpr(select.where, env, true, true)}`;
|
|
@@ -1412,23 +1411,23 @@ function toSqlDdl(csn, options) {
|
|
|
1412
1411
|
case 'number':
|
|
1413
1412
|
case 'boolean':
|
|
1414
1413
|
case 'null':
|
|
1415
|
-
|
|
1414
|
+
// 17.42, NULL, TRUE
|
|
1416
1415
|
return String(x.val).toUpperCase();
|
|
1417
1416
|
case 'x':
|
|
1418
|
-
|
|
1417
|
+
// x'f000'
|
|
1419
1418
|
return `${x.literal}'${x.val}'`;
|
|
1420
1419
|
case 'date':
|
|
1421
1420
|
case 'time':
|
|
1422
1421
|
case 'timestamp':
|
|
1423
1422
|
if (options.toSql.dialect === 'sqlite') {
|
|
1424
|
-
|
|
1423
|
+
// simple string literal '2017-11-02'
|
|
1425
1424
|
return `'${x.val}'`;
|
|
1426
1425
|
}
|
|
1427
1426
|
// date'2017-11-02'
|
|
1428
1427
|
return `${x.literal}'${x.val}'`;
|
|
1429
1428
|
|
|
1430
1429
|
case 'string':
|
|
1431
|
-
|
|
1430
|
+
// 'foo', with proper escaping
|
|
1432
1431
|
return `'${x.val.replace(/'/g, '\'\'')}'`;
|
|
1433
1432
|
case 'object':
|
|
1434
1433
|
if (x.val === null)
|
|
@@ -1593,13 +1592,13 @@ function toSqlDdl(csn, options) {
|
|
|
1593
1592
|
|
|
1594
1593
|
// Not really a path step but an object-like function call
|
|
1595
1594
|
if (s.func)
|
|
1596
|
-
return `${s.func}(${renderArgs(s
|
|
1595
|
+
return `${s.func}(${renderArgs(s, '=>', env, null)})`;
|
|
1597
1596
|
|
|
1598
1597
|
// Path step, possibly with view parameters and/or filters
|
|
1599
1598
|
let result = `${quoteSqlId(s.id)}`;
|
|
1600
1599
|
if (s.args) {
|
|
1601
1600
|
// View parameters
|
|
1602
|
-
result += `(${renderArgs(s
|
|
1601
|
+
result += `(${renderArgs(s, '=>', env, null)})`;
|
|
1603
1602
|
}
|
|
1604
1603
|
if (s.where) {
|
|
1605
1604
|
// Filter, possibly with cardinality
|
|
@@ -33,7 +33,7 @@ const { implicitAs } = require('../../model/csnRefs');
|
|
|
33
33
|
function renderFunc( funcName, node, dialect, renderArgs) {
|
|
34
34
|
if (funcWithoutParen( node, dialect ))
|
|
35
35
|
return funcName;
|
|
36
|
-
return `${funcName}(${renderArgs( node
|
|
36
|
+
return `${funcName}(${renderArgs( node )})`;
|
|
37
37
|
}
|
|
38
38
|
|
|
39
39
|
/**
|
|
@@ -552,7 +552,7 @@ function expandStructureReferences(csn, options, pathDelimiter, { error, info, t
|
|
|
552
552
|
for (const part of Object.keys(base)) {
|
|
553
553
|
if (excluding.indexOf(part) === -1) {
|
|
554
554
|
// The thing is shadowed - ignore names present because of .inline, as those "disappear"
|
|
555
|
-
if (names[part] && !subs[names[part]].inline) {
|
|
555
|
+
if (names[part] !== undefined && !subs[names[part]].inline) {
|
|
556
556
|
replaced[part] = true;
|
|
557
557
|
star.push(subs[names[part]]);
|
|
558
558
|
}
|
|
@@ -249,6 +249,15 @@ function handleExists(csn, options, error) {
|
|
|
249
249
|
const {
|
|
250
250
|
ref, head, tail,
|
|
251
251
|
} = getFirstAssoc(current, exprPath.concat(i));
|
|
252
|
+
|
|
253
|
+
const lastAssoc = getLastAssoc(current, exprPath.concat(i));
|
|
254
|
+
// toE.toF.id -> we must not end on a non-assoc - this will also be caught downstream by
|
|
255
|
+
// '“EXISTS” can only be used with associations/compositions, found $(TYPE)'
|
|
256
|
+
// But the error might not be clear, since it could be because of our rewritten stuff. The later check
|
|
257
|
+
// checks for exists id -> our rewrite turns toE.toF.id into toE[exists toF[exists id]], leading to the same error
|
|
258
|
+
if (lastAssoc.tail.length > 0)
|
|
259
|
+
error(null, current.$path, { id: lastAssoc.tail[0].id ? lastAssoc.tail[0].id : lastAssoc.tail[0], name: lastAssoc.ref.id ? lastAssoc.ref.id : lastAssoc.ref }, 'Unexpected path step $(ID) after association $(NAME) in "EXISTS"');
|
|
260
|
+
|
|
252
261
|
const newThing = [ ...head, nestFilters(head.length + 1, ref, tail, exprPath.concat([ i ])) ];
|
|
253
262
|
expr[i].ref = newThing;
|
|
254
263
|
}
|
|
@@ -278,15 +287,10 @@ function handleExists(csn, options, error) {
|
|
|
278
287
|
const current = expr[i];
|
|
279
288
|
const isPrefixedWithTableAlias = firstLinkIsEntityOrQuerySource(exprPath.concat(i));
|
|
280
289
|
const base = getBase(queryBase, isPrefixedWithTableAlias, current, exprPath.concat(i));
|
|
281
|
-
const { root, ref
|
|
282
|
-
|
|
283
|
-
if (tail.length > 0) {
|
|
284
|
-
error(null, current.$path, { id: tail[0].id ? tail[0].id : tail[0], name: ref.id ? ref.id : ref }, 'Unexpected path step $(ID) after association $(NAME) in "EXISTS"');
|
|
285
|
-
continue;
|
|
286
|
-
}
|
|
290
|
+
const { root, ref } = getFirstAssoc(current, exprPath.concat(i));
|
|
287
291
|
|
|
288
292
|
if (!root.target) {
|
|
289
|
-
error(null,
|
|
293
|
+
error(null, exprPath.concat(i), { type: root.type }, '“EXISTS” can only be used with associations/compositions, found $(TYPE)');
|
|
290
294
|
return { result: [], leftovers: [] };
|
|
291
295
|
}
|
|
292
296
|
|
|
@@ -477,6 +481,27 @@ function handleExists(csn, options, error) {
|
|
|
477
481
|
};
|
|
478
482
|
}
|
|
479
483
|
|
|
484
|
+
/**
|
|
485
|
+
* Get the last association from the expression part - similar to getFirstAssoc
|
|
486
|
+
*
|
|
487
|
+
* @param {object} xprPart
|
|
488
|
+
* @param {CSN.Path} path
|
|
489
|
+
* @returns {{head: Array, root: CSN.Element, ref: string|object, tail: Array}} The last assoc (root), the corresponding ref (ref), anything before the ref (head) and the rest of the ref (tail).
|
|
490
|
+
*/
|
|
491
|
+
function getLastAssoc(xprPart, path) {
|
|
492
|
+
const { links, art } = inspectRef(path);
|
|
493
|
+
for (let i = xprPart.ref.length - 1; i > -1; i--) {
|
|
494
|
+
if (links[i].art && links[i].art.target) {
|
|
495
|
+
return {
|
|
496
|
+
head: (i === 0 ? [] : xprPart.ref.slice(0, i)), root: links[i].art, ref: xprPart.ref[i], tail: xprPart.ref.slice(i + 1),
|
|
497
|
+
};
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
return {
|
|
501
|
+
head: (xprPart.ref.length === 1 ? [] : xprPart.ref.slice(0, xprPart.ref.length - 1)), root: art, ref: xprPart.ref[xprPart.ref.length - 1], tail: [],
|
|
502
|
+
};
|
|
503
|
+
}
|
|
504
|
+
|
|
480
505
|
/**
|
|
481
506
|
* Check (using inspectRef -> links), wether the first path step is an entity or query source
|
|
482
507
|
*
|
|
@@ -10,7 +10,7 @@ const structuralNodeHandlers = {
|
|
|
10
10
|
returns: traverseTyped,
|
|
11
11
|
on: traverseArray,
|
|
12
12
|
keys: traverseArray,
|
|
13
|
-
ref:
|
|
13
|
+
ref: traverseArray,
|
|
14
14
|
query: traverseTyped,
|
|
15
15
|
SELECT: traverseTyped,
|
|
16
16
|
SET: traverseTyped,
|
|
@@ -33,10 +33,6 @@ function structuralPath(csn, path) {
|
|
|
33
33
|
return traverseDict(csn.definitions, path, 1, ['definitions']);
|
|
34
34
|
}
|
|
35
35
|
|
|
36
|
-
function traverseRef(obj, path, index, typeStack) {
|
|
37
|
-
return traverseArray(obj, path, index, typeStack);
|
|
38
|
-
}
|
|
39
|
-
|
|
40
36
|
function traverseArray(obj, path, index, typeStack) {
|
|
41
37
|
if(!Array.isArray(obj)) return typeStack;
|
|
42
38
|
const name = path[index];
|