@sap/cds-compiler
Version:
CDS (Core Data Services) compiler and backends
78 lines (69 loc) • 2.85 kB
JavaScript
'use strict';
const { requireForeignKeyAccess } = require('../checks/onConditions');
/**
* Filter expressions in an exists path must:
* - only contain fk-accesses for assocs. Unmanaged traversal / non-fk access is forbidden.
* - not contain a ref starting with $self
*
* @param {CSN.Artifact} parent
* @param {string} name
* @param {Array} expr
*/
function assertFilterOfExists( parent, name, expr ) {
for (let i = 0; i < expr.length - 1; i++) {
if (expr[i] === 'exists' && expr[i + 1].ref) {
i++;
const current = expr[i];
const { _links } = current;
const assocs = _links.filter(link => link.art?.target).map(link => current.ref[link.idx]);
ensureValidFilters.call(this, assocs);
}
}
}
/**
* Reject:
* - Unmanaged traversal / non-fk access.
* - ref's starting with $self
*
* @param {object[]} assocs Array of refs of assocs - possibly with a .where to check
*/
function ensureValidFilters( assocs ) {
for (const assoc of assocs) {
if (assoc.where) {
for (let i = 0; i < assoc.where.length; i++) {
const part = assoc.where[i];
if (part._links && !(assoc.where[i - 1] && assoc.where[i - 1] === 'exists')) {
if (part.$scope === '$self')
this.error('ref-unexpected-self', part.$path, { '#': 'exists-filter', elemref: assoc.id, id: part.ref[0] });
for (const link of part._links) {
if (link.art && link.art.target) {
if (link.art.keys) { // managed - allow FK access
const next = part._links[link.idx + 1];
if (next !== undefined) { // there is a next path step - check if it is a fk
requireForeignKeyAccess(part, i, (errorIndex) => {
const { ref } = assoc.where[part.$path[part.$path.length - 1]];
this.error('ref-expecting-foreign-key', part.$path, { alias: ref[errorIndex], id: assoc.id, name: ref[link.idx] });
});
}
else { // no traversal, ends on managed
this.error('ref-unexpected-assoc', part.$path, { '#': 'managed-filter', id: assoc.id, name: assoc.where[part.$path[part.$path.length - 1]].ref[link.idx] });
}
}
else { // unmanaged - always wrong
this.error('ref-unexpected-assoc', part.$path, { '#': 'unmanaged-filter', id: assoc.id, name: assoc.where[part.$path[part.$path.length - 1]].ref[link.idx] });
}
// Recursively drill down if the assoc-step has a filter
if (part.ref[link.idx].where)
ensureValidFilters.call(this, [ part.ref[link.idx] ]);
}
}
}
}
}
}
}
module.exports = {
having: assertFilterOfExists,
where: assertFilterOfExists,
xpr: assertFilterOfExists,
};