node-oracledb/lib/transformer.js

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
};