@sap/cds-compiler
Version:
CDS (Core Data Services) compiler and backends
163 lines (151 loc) • 5.54 kB
JavaScript
'use strict';
const { forEachMemberRecursively } = require('../model/csnUtils');
// Only to be used with validator.js - a correct this value needs to be provided!
/**
* @typedef {object} ValidatorThis
* @property {CSN.Model} csn
* @property {CSN.Options} options
* @property {CSN.Artifact} artifact
* @property {object} csnUtils
* @property {Function} error
*/
/**
* Check that required actual parameters on 'node.type' are set, that their values are in the correct range etc.
*
* @this {ValidatorThis}
* @param {CSN.Element} member the element to be checked
* @param {string} memberName the elements name
* @param {string} prop which kind of member are we looking at
* @param {CSN.Path} path the path to the member
*/
function checkTypeParameters( member, memberName, prop, path ) {
// Types don't manifest on the database, only elements of entities/views are of interest
if (this.artifact.kind !== 'entity')
return;
// These are SQL-specific checks; effective CSN is not a SQL output
if (this.options.transformation === 'effective')
return;
if (member.type && !member.virtual) {
_checkTypeParameters.call(this, member, path);
// For structured types referenced by name (e.g. `struct: T`), the member has no
// .elements at this point, so forEachMemberRecursively won't descend into T's elements.
// We need to look up the type definition and check its elements explicitly.
if (!member.elements && this.csnUtils.isStructured(member)) {
const typeDef = this.csn.definitions[member.type];
if (typeDef?.elements) {
forEachMemberRecursively(typeDef, (sub, subName, subProp, subPath) => {
if (sub.type && !sub.virtual)
_checkTypeParameters.call(this, sub, subPath);
}, path);
}
}
}
}
/**
* @this {ValidatorThis}
* @param {object} node
* @param {CSN.Path} path
*/
function _checkTypeParameters( node, path ) {
const typeInfo = this.csnUtils.getFinalTypeInfo(node.type);
const absolute = typeInfo?.type;
if (!absolute)
return;
// Effective parameter value: explicit on node, or inherited from type chain
const effectiveParam = paramName => node[paramName] ?? typeInfo[paramName];
switch (absolute) {
case 'cds.String':
case 'cds.Binary':
case 'cds.hana.VARCHAR': {
checkTypeParamValue.call(this, node, effectiveParam, absolute, 'length', { min: 1, max: 5000 }, path);
break;
}
case 'cds.Decimal': {
const precision = effectiveParam('precision');
const scale = effectiveParam('scale');
if (precision || scale) {
checkTypeParamValue.call(this, node, effectiveParam, absolute, 'precision', { max: 38 }, path);
checkTypeParamValue.call(this, node, effectiveParam, absolute, 'scale', { max: precision }, path);
}
break;
}
case 'cds.hana.BINARY':
case 'cds.hana.NCHAR':
case 'cds.hana.CHAR': {
checkTypeParamValue.call(this, node, effectiveParam, absolute, 'length', { min: 1, max: 2000 }, path);
break;
}
case 'cds.hana.ST_POINT':
case 'cds.hana.ST_GEOMETRY': {
checkTypeParamValue.call(this, node, effectiveParam, absolute, 'srid', { max: Number.MAX_SAFE_INTEGER }, path);
break;
}
case 'cds.Map': {
if (this.options.sqlDialect === 'plain')
this.error('ref-unsupported-type', path, { '#': 'dialect', type: absolute, value: 'plain' });
break;
}
case 'cds.Vector': {
if (this.options.sqlDialect === 'plain') {
this.error('ref-unsupported-type', path, {
'#': 'dialect',
type: absolute,
value: 'plain',
});
}
// Technical limitation of SQLite vector extension
else if (this.options.sqlDialect === 'sqlite' && effectiveParam('length') && effectiveParam('length') % 4 !== 0) {
this.error('ref-unexpected-args', path, {
'#': 'vector_length', elemref: path.at(-1),
});
}
break;
}
default:
break; // nothing to check for unknown types
}
}
/**
* Check that the value of the type property `paramName` (e.g. length, precision, scale ...)
* is in a given range.
*
* @this {ValidatorThis}
* @param {object} node
* @param {Function} effectiveParam
* @param {string} resolvedType
* @param {string} paramName
* @param {object} range
* @param {CSN.Path} path
* @returns {boolean}
*/
function checkTypeParamValue( node, effectiveParam, resolvedType, paramName, range = null, path = null ) {
const paramValue = effectiveParam(paramName);
if (paramValue == null)
return true;
if (range) {
if (isMaxParameterLengthRestricted.call(this, resolvedType) && range.max && paramValue > range.max) {
this.error('type-unexpected-argument', path, {
'#': 'max', prop: paramName, type: resolvedType, number: range.max, $reviewed: false,
});
return false;
}
if (range.min && paramValue < range.min) {
this.error('type-unexpected-argument', path, {
'#': 'min', prop: paramName, type: resolvedType, number: range.min, $reviewed: false,
});
return false;
}
}
return true;
}
/**
* Check if the maximum length of the value of the given type is restricted.
*
* @this {ValidatorThis}
* @param {string} type
* @returns {boolean}
*/
function isMaxParameterLengthRestricted( type ) {
return !(this.options.toSql && type === 'cds.String' && (this.options.sqlDialect === 'sqlite' || this.options.sqlDialect === 'plain'));
}
module.exports = checkTypeParameters;