UNPKG

@sap/cds-compiler

Version:

CDS (Core Data Services) compiler and backends

163 lines (151 loc) 5.54 kB
'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;