269 lines
9.0 KiB
JavaScript
269 lines
9.0 KiB
JavaScript
// 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
|
|
|
|
};
|