oracledb
Version:
A Node.js module for Oracle Database access from JavaScript and TypeScript
307 lines (280 loc) • 11 kB
JavaScript
// Copyright (c) 2022, 2023, 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.
//
//-----------------------------------------------------------------------------
;
const constants = require('../constants.js');
const errors = require('../errors.js');
const nodbUtil = require('../util.js');
const settings = require('../settings.js');
const future = require('../future.js');
const types = require('../types.js');
const Lob = require('../lob.js');
const oson = require('./datahandlers/oson.js');
const BaseImpl = require('./base.js');
// define implementation class
class ResultSetImpl extends BaseImpl {
//---------------------------------------------------------------------------
// _determineFetchType()
//
// Determine the fetch type to use for the specified metadata.
//---------------------------------------------------------------------------
_determineFetchType(metadata, options) {
// clear any previous fetch type and converter functions that may have been
// retained
delete metadata.fetchType;
delete metadata.converter;
// if a fetch type handler is specified, call it; if no value (undefined)
// is returned, the normal processing takes place
if (options.fetchTypeHandler) {
const result = options.fetchTypeHandler(metadata);
if (result !== undefined) {
errors.assert(typeof result === 'object',
errors.ERR_FETCH_TYPE_HANDLER_RETURN_VALUE);
if (result.type !== undefined) {
errors.assert(result.type instanceof types.DbType,
errors.ERR_FETCH_TYPE_HANDLER_TYPE);
}
if (result.converter !== undefined) {
errors.assert(typeof result.converter === 'function',
errors.ERR_FETCH_TYPE_HANDLER_CONVERTER);
}
metadata.fetchType = result.type;
metadata.converter = result.converter;
}
}
// continue processing if no fetch type was specified or no fetch type
// handler was supplied
if (!metadata.fetchType) {
// if the fetchTypeMap exists (calculated from the fetchInfo argument),
// use it to determine the fetch type
if (options.fetchTypeMap && options.fetchTypeMap.has(metadata.name)) {
metadata.fetchType = options.fetchTypeMap.get(metadata.name);
if (metadata.fetchType === constants.DEFAULT) {
metadata.fetchType =
types.DB_TYPE_FETCH_TYPE_MAP.get(metadata.dbType);
}
// otherwise, use the default fetch type map (calculated from the
// fetchAsString and fetchAsBuffer settings
} else {
metadata.fetchType = settings.fetchTypeMap.get(metadata.dbType);
}
}
// if the types do not match, verify that the conversion is supported and
// adjust the final fetch type to match what the database expects
if (metadata.fetchType !== metadata.dbType) {
const map = types.DB_TYPE_CONVERSION_MAP.get(metadata.dbType);
const actualFetchType = map && map.get(metadata.fetchType);
if (!actualFetchType) {
errors.throwErr(errors.ERR_UNSUPPORTED_CONVERSION,
metadata.dbType.name, metadata.fetchType.name);
}
metadata.fetchType = actualFetchType;
}
let converter;
const userConverter = metadata.converter;
// If IsJson or IsOson is set, convert to JSON objects unless
// user defined output type handler overwrites it.
if (metadata.dbType !== types.DB_TYPE_JSON && future.oldJsonColumnAsObj
&& userConverter === undefined) {
let outConverter;
if (metadata.isOson) {
outConverter = async function(val) {
if (!val) {
return val;
}
let result = val;
if (val instanceof Lob) {
result = await val.getData();
}
const decoder = new oson.OsonDecoder(result);
return decoder.decode();
};
converter = outConverter;
} else if (metadata.isJson) {
outConverter = async function(val) {
if (!val) {
return val;
}
let result = val;
if (val instanceof Lob) {
result = await val.getData();
}
if (result instanceof Buffer) {
result = result.toString();
}
result = JSON.parse(result);
return result;
};
converter = outConverter;
}
}
// in thin mode, Oracle NUMBER values are internally fetched as string in
// order to preserve precision so must be converted to JavaScript Number
// when needed; other numeric and date types are fetched natively as
// JavaScript Number and Date values and are converted to string using
// toString() when desired
if (settings.thin) {
if (metadata.dbType === types.DB_TYPE_NUMBER &&
metadata.fetchType === types.DB_TYPE_NUMBER) {
converter = (v) => (v === null) ? null : parseFloat(v);
} else if (metadata.fetchType === types.DB_TYPE_VARCHAR) {
if (metadata.dbType === types.DB_TYPE_BINARY_DOUBLE ||
metadata.dbType === types.DB_TYPE_BINARY_FLOAT ||
metadata.dbType === types.DB_TYPE_DATE ||
metadata.dbType === types.DB_TYPE_TIMESTAMP ||
metadata.dbType === types.DB_TYPE_TIMESTAMP_LTZ ||
metadata.dbType === types.DB_TYPE_TIMESTAMP_TZ) {
converter = (v) => (v === null) ? null : v.toString();
} else if (metadata.dbType === types.DB_TYPE_RAW) {
converter = (v) => (v === null) ? null : v.toString('hex').toUpperCase();
}
} else if (metadata.dbType === types.DB_TYPE_XMLTYPE) {
const xmlConverter = async function(val) {
if (!val) {
return val;
}
if (typeof val === 'string') {
return val;
}
return await val.getData();
};
converter = xmlConverter;
}
if (userConverter && converter) {
const internalConverter = converter;
converter = (v) => userConverter(internalConverter(v));
}
}
if (converter) {
metadata.converter = converter;
}
}
//---------------------------------------------------------------------------
// _getConnImpl()
//
// Common method on all classes that make use of a connection -- used to
// ensure serialization of all use of the connection.
//---------------------------------------------------------------------------
_getConnImpl() {
return this._parentObj;
}
//---------------------------------------------------------------------------
// _setup()
//
// Setup a result set. The metadata is examined to to determine if any
// columns need to be manipulated before being returned to the caller. If the
// rows fetched from the result set are expected to be objects, a unique set
// of attribute names are also determined.
//---------------------------------------------------------------------------
_setup(options, metaData) {
this._parentObj = options.connection._impl;
this.metaData = metaData;
this.lobIndices = [];
this.dbObjectIndices = [];
this.nestedCursorIndices = [];
this.converterIndices = [];
this.outFormat = options.outFormat;
this.fetchArraySize = options.fetchArraySize;
this.dbObjectAsPojo = options.dbObjectAsPojo;
this.maxRows = options.maxRows;
const names = new Map();
for (let i = 0; i < metaData.length; i++) {
const name = metaData[i].name;
if (!names.has(name)) {
names.set(name, i);
}
}
for (let i = 0; i < metaData.length; i++) {
const info = metaData[i];
if (info.dbTypeClass) {
const cls = options.connection._getDbObjectClass(info.dbTypeClass);
info.dbTypeClass = cls;
}
nodbUtil.addTypeProperties(info, "dbType");
this._determineFetchType(info, options);
if (info.fetchType === types.DB_TYPE_CURSOR) {
this.nestedCursorIndices.push(i);
} else if (info.fetchType === types.DB_TYPE_CLOB ||
info.fetchType === types.DB_TYPE_NCLOB ||
info.fetchType === types.DB_TYPE_BLOB ||
info.fetchType === types.DB_TYPE_BFILE) {
this.lobIndices.push(i);
} else if (info.fetchType === types.DB_TYPE_OBJECT) {
this.dbObjectIndices.push(i);
}
if (info.converter) {
this.converterIndices.push(i);
}
let name = info.name;
if (names.get(name) !== i) {
let seqNum = 0;
while (names.has(name)) {
seqNum = seqNum + 1;
name = `${info.name}_${seqNum}`;
}
names.set(name, i);
info.name = name;
}
}
}
//---------------------------------------------------------------------------
// close()
//
// Closes the result set.
//---------------------------------------------------------------------------
close() {
errors.throwNotImplemented("closing a result set");
}
//---------------------------------------------------------------------------
// getRows()
//
// Returns rows from a result set.
//---------------------------------------------------------------------------
getRows() {
errors.throwNotImplemented("getting rows");
}
//---------------------------------------------------------------------------
// _getAllRows() [INTERNAL]
//
// Fetches all the rows from the database to use internally.
//---------------------------------------------------------------------------
async _getAllRows() {
const fetchArraySize = 100;
// fetch all rows
let rowsFetched = [];
while (true) { // eslint-disable-line
// constant default value for fetchArraySize
const rows = await this.getRows(fetchArraySize, {});
if (rows) {
await this._processRows(rows, false);
rowsFetched = rowsFetched.concat(rows);
}
if (rows.length < fetchArraySize) {
break;
}
}
return rowsFetched;
}
}
module.exports = ResultSetImpl;