// Copyright (c) 2023, 2024, Oracle and/or its affiliates. //----------------------------------------------------------------------------- // // This software is dual-licensed to you under the Universal Permissive License // (UPL) 1.0 as shown at https://oss.oracle.com/licenses/upl and Apache License // 2.0 as shown at http://www.apache.org/licenses/LICENSE-2.0. You may choose // either license. // // If you elect to accept the software under the Apache License, Version 2.0, // the following applies: // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // https://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // //----------------------------------------------------------------------------- 'use strict'; const BaseDbObject = require('./dbObject.js'); const { Buffer } = require('buffer'); const Lob = require('./lob.js'); const ResultSet = require('./resultset.js'); const constants = require('./constants.js'); const errors = require('./errors.js'); const util = require('util'); const types = require('./types.js'); const nodbUtil = require('./util.js'); //----------------------------------------------------------------------------- // checkType() // // Checks that the type of the data matches one of the given types. If the type // has not been specified yet, the first type is assumed to be the correct one. // // A failure to match results in an exception being thrown. The data in the // info parameter is used to determine which error should be thrown. //----------------------------------------------------------------------------- function checkType(info, options) { if (info.type === undefined && arguments.length > 2) { info.type = arguments[2]; } else { let matches = false; for (let i = 2; i < arguments.length; i++) { if (info.type === arguments[i]) { matches = true; break; } } if (!matches) { if (info.attrName) { errors.throwErr(errors.ERR_WRONG_VALUE_FOR_DBOBJECT_ATTR, info.attrName, info.fqn); } else if (info.fqn) { errors.throwErr(errors.ERR_WRONG_VALUE_FOR_DBOBJECT_ELEM, info.fqn); } else if (info.isArray && info.name) { errors.throwErr(errors.ERR_INCOMPATIBLE_TYPE_ARRAY_BIND, options.pos, info.name); } else if (info.isArray) { errors.throwErr(errors.ERR_INCOMPATIBLE_TYPE_ARRAY_INDEX_BIND, options.pos, info.pos); } else { errors.throwErr(errors.ERR_BIND_VALUE_AND_TYPE_MISMATCH); } } } } //----------------------------------------------------------------------------- // transformJsonValue() // // Returns a normalized JSON value. Scalar values are returned unchanged. // Arrays are returned as a new array with transformed JSON values. Objects are // returned as new objects with keys "fields" and "values", both of which // are arrays (with the value transformed to JSON values). //----------------------------------------------------------------------------- function transformJsonValue(value) { // handle simple scalars if (value === undefined || value === null || typeof value === 'number' || typeof value === 'string' || typeof value === 'boolean' || Buffer.isBuffer(value) || util.types.isDate(value) || nodbUtil.isVectorValue(value)) return value; // arrays are transformed to a new array with processed values if (Array.isArray(value)) { const outValue = new Array(value.length); for (let i = 0; i < value.length; i++) { outValue[i] = transformJsonValue(value[i]); } return outValue; } // database objects are treated as empty objects if (value instanceof BaseDbObject) return {fields: [], values: []}; // JsonId is a special type to represent autogenerated id // for SODA documents. if (value instanceof types.JsonId) { return value; } // all other objects are transformed to an object with two arrays (fields // and values) const outValue = {}; outValue.fields = Object.getOwnPropertyNames(value); outValue.values = new Array(outValue.fields.length); for (let i = 0; i < outValue.fields.length; i++) { outValue.values[i] = transformJsonValue(value[outValue.fields[i]]); } return outValue; } //----------------------------------------------------------------------------- // transformValueIn() // // Processes the value supplied by the caller and returns a normalized value, // if necessary, for use by the implementation. All checks are performed on the // value to ensure it is suitable for the type information supplied. If no type // information is supplied, however, the value defines it instead! //----------------------------------------------------------------------------- function transformValueIn(info, value, options) { // null and undefined can always be set so nothing needs to be done if (value === undefined || value === null) return undefined; // handle setting plain JS values to database objects if (info.type === types.DB_TYPE_OBJECT) { let obj = value; if (!(value instanceof BaseDbObject)) { obj = new info.typeClass(value); } return obj._impl; // handle setting plain JS values to JSON } else if (info.type === types.DB_TYPE_JSON) { return transformJsonValue(value); // handle strings } else if (typeof value === 'string') { checkType(info, options, types.DB_TYPE_VARCHAR, types.DB_TYPE_NVARCHAR, types.DB_TYPE_CHAR, types.DB_TYPE_NCHAR, types.DB_TYPE_CLOB, types.DB_TYPE_NCLOB); if (info.type !== types.DB_TYPE_CLOB && info.type !== types.DB_TYPE_NCLOB) { const valueLen = Buffer.byteLength(value); if (info.maxSize === undefined || valueLen > info.maxSize) { if (info.checkSize) { errors.throwErr(errors.ERR_MAX_SIZE_TOO_SMALL, info.maxSize, valueLen, options.pos); } info.maxSize = valueLen; } } return value; // handle numbers } else if (typeof value === 'number' || typeof value === 'bigint') { checkType(info, options, types.DB_TYPE_NUMBER, types.DB_TYPE_BINARY_INTEGER, types.DB_TYPE_BINARY_FLOAT, types.DB_TYPE_BINARY_DOUBLE); if (Number.isNaN(value) && info.type === types.DB_TYPE_NUMBER) { errors.throwErr(errors.ERR_NAN_VALUE); } return value; // handle booleans } else if (typeof value === 'boolean') { checkType(info, options, types.DB_TYPE_BOOLEAN); return value; // handle dates } else if (util.types.isDate(value)) { checkType(info, options, types.DB_TYPE_TIMESTAMP, types.DB_TYPE_TIMESTAMP_TZ, types.DB_TYPE_TIMESTAMP_LTZ, types.DB_TYPE_DATE); return value; // handle binding buffers } else if (Buffer.isBuffer(value)) { checkType(info, options, types.DB_TYPE_RAW, types.DB_TYPE_BLOB); if (info.type === types.DB_TYPE_RAW && (info.maxSize === undefined || value.length > info.maxSize)) { if (info.checkSize) { errors.throwErr(errors.ERR_MAX_SIZE_TOO_SMALL, info.maxSize, value.length, options.pos); } info.maxSize = value.length; } return value; // handle result sets } else if (value instanceof ResultSet) { checkType(info, options, types.DB_TYPE_CURSOR); return value._impl; // handle binding LOBs } else if (value instanceof Lob) { checkType(info, options, value.type); return value._impl; // handle database objects } else if (value instanceof BaseDbObject) { checkType(info, options, types.DB_TYPE_OBJECT); return value._impl; // handle vectors } else if (nodbUtil.isVectorValue(value)) { checkType(info, options, types.DB_TYPE_VECTOR); return value; } else if (info.type === types.DB_TYPE_VECTOR && Array.isArray(value)) { return new Float64Array(value); // handle arrays } else if (options.allowArray && Array.isArray(value)) { info.isArray = true; if (info.dir === constants.BIND_IN) { info.maxArraySize = value.length || 1; } else if (info.maxArraySize === undefined) { errors.throwErr(errors.ERR_REQUIRED_MAX_ARRAY_SIZE); } else if (value.length > info.maxArraySize) { errors.throwErr(errors.ERR_INVALID_ARRAY_SIZE); } options.allowArray = false; const transformed = new Array(value.length); for (let i = 0; i < value.length; i++) { options.pos = i; transformed[i] = transformValueIn(info, value[i], options); } return transformed; } // no suitable bind value found if (info.type === undefined) errors.throwErr(errors.ERR_INVALID_BIND_DATA_TYPE, 2); checkType(info, options); } // define exports module.exports = { transformJsonValue, transformValueIn };