UNPKG

oracledb

Version:

A Node.js module for Oracle Database access from JavaScript and TypeScript

369 lines (322 loc) 12.1 kB
// Copyright (c) 2016, 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. // //----------------------------------------------------------------------------- 'use strict'; const QueryStream = require('./queryStream.js'); const BaseDbObject = require('./dbObject.js'); const nodbUtil = require('./util.js'); const constants = require('./constants.js'); const Lob = require('./lob.js'); const errors = require('./errors.js'); class ResultSet { constructor() { this._rowCache = []; this._processingStarted = false; this._convertedToStream = false; this._allowGetRowCall = false; this._isActive = false; } //--------------------------------------------------------------------------- // _getAllRows() // // Return all of the rows in the result set. //--------------------------------------------------------------------------- async _getAllRows() { try { // retain initial values of the maximum number of rows to fetch and the // number of rows to fetch from the database at a single time let maxRows = this._impl.maxRows; let fetchArraySize = this._impl.fetchArraySize; // fetch all rows let rowsFetched = []; while (true) { // eslint-disable-line if (maxRows > 0 && fetchArraySize >= maxRows) { fetchArraySize = maxRows; } const rows = await this._getRows(fetchArraySize); if (rows) { await this._processRows(rows, true); rowsFetched = rowsFetched.concat(rows); } if (rows.length == maxRows || rows.length < fetchArraySize) { break; } if (maxRows > 0) { maxRows -= rows.length; } } return rowsFetched; } finally { await this._impl.close(); delete this._impl; } } //--------------------------------------------------------------------------- // _getRows() // // Return up to the specified number of rows from the result set. If nested // cursors are possible, setup the execute options so that they can be // examined within the implementation's setup routine. //--------------------------------------------------------------------------- async _getRows(numRows) { let options = {}; if (this._impl.nestedCursorIndices.length > 0) { options = { connection: this._connection, outFormat: this._impl.outFormat, fetchArraySize: this._impl.fetchArraySize, dbObjectAsPojo: this._impl.dbObjectAsPojo, maxRows: this._impl.maxRows, fetchTypeMap: this._impl.fetchTypeMap }; } return await this._impl.getRows(numRows, options); } //--------------------------------------------------------------------------- // _processRows() // // Process rows returned by the implementation. This will transform result // set and LOB implementations into user facing objects. It will also perform // any fetched that are needed (if a result set is undesirable) //--------------------------------------------------------------------------- async _processRows(rows, expandNestedCursors) { // transform any nested cursors into user facing objects for (const i of this._impl.nestedCursorIndices) { for (let j = 0; j < rows.length; j++) { const val = rows[j][i]; if (val) { const resultSet = new ResultSet(); resultSet._setup(this._connection, val); this._impl.metaData[i].metaData = val.metaData; if (expandNestedCursors) { rows[j][i] = await resultSet._getAllRows(); } else { rows[j][i] = resultSet; } } } } // transform any LOBs into user facing objects for (const i of this._impl.lobIndices) { for (let j = 0; j < rows.length; j++) { const val = rows[j][i]; if (val) { const lob = rows[j][i] = new Lob(); lob._setup(val, true); } } } // transform any database objects into user facing objects for (const i of this._impl.dbObjectIndices) { const dbObjectClass = this._impl.metaData[i].dbTypeClass; for (let j = 0; j < rows.length; j++) { const val = rows[j][i]; if (val) { const obj = rows[j][i] = Object.create(dbObjectClass.prototype); obj._impl = val; if (this._impl.dbObjectAsPojo) { rows[j][i] = obj._toPojo(); } else if (obj.isCollection) { rows[j][i] = new Proxy(obj, BaseDbObject._collectionProxyHandler); } } } } // run any conversion functions, if applicable // NOTE: we mark the connection as no longer in progress before making // calls to the converter function; this is needed to allow calls against // the database (like getting LOB data) to succeed, as this code is running // in the middle of a call to connection.execute() or resultSet.getRows() for (const i of this._impl.converterIndices) { const fn = this._impl.metaData[i].converter; this._connection._impl._inProgress = false; try { for (let j = 0; j < rows.length; j++) { let result = fn(rows[j][i]); if (result instanceof Promise) { result = await result; } rows[j][i] = result; } } finally { this._connection._impl._inProgress = true; } } // create objects, if desired if (this._impl.outFormat === constants.OUT_FORMAT_OBJECT) { for (let i = 0; i < rows.length; i++) { const origRow = rows[i]; const newRow = rows[i] = {}; const metaData = this._impl.metaData; for (let j = 0; j < metaData.length; j++) { newRow[metaData[j].name] = origRow[j]; } } } } //--------------------------------------------------------------------------- // _setup() // // Setup a result set. // --------------------------------------------------------------------------- _setup(connection, resultSetImpl) { this._connection = connection; this._impl = resultSetImpl; } //--------------------------------------------------------------------------- // close() // // Close the result set and make it unusable for further operations. //--------------------------------------------------------------------------- async close() { errors.assertArgCount(arguments, 0, 0); errors.assert(this._impl && this._connection._impl, errors.ERR_INVALID_RS); if (this._convertedToStream) { errors.throwErr(errors.ERR_CANNOT_INVOKE_RS_METHODS); } this._processingStarted = true; const resultSetImpl = this._impl; delete this._impl; await resultSetImpl.close(); } //--------------------------------------------------------------------------- // getRow() // // Returns a single row to the caller from the result set, if one is // available. Rows are buffered in a JavaScript array in order to avoid trips // through the thread pool that would be required if implemented in C. //--------------------------------------------------------------------------- async getRow() { errors.assertArgCount(arguments, 0, 0); errors.assert(this._impl && this._connection._impl, errors.ERR_INVALID_RS); if (this._convertedToStream && !this._allowGetRowCall) { errors.throwErr(errors.ERR_CANNOT_INVOKE_RS_METHODS); } this._allowGetRowCall = false; this._processingStarted = true; if (this._rowCache.length == 0) { const rows = await this._getRows(this._impl.fetchArraySize); await this._processRows(rows, false); this._rowCache = rows; } return this._rowCache.shift(); } //--------------------------------------------------------------------------- // getRows() // // Check to see if any rows are in the JS buffer (which could result from // interspersed calls to getRow() and getRows()). If no rows are in the // buffer, the call is just proxied to the implementation layer. Otherwise, // rows are pulled from the buffer and potentially concatenated with rows // from calls to the implementation's getRows(). //--------------------------------------------------------------------------- async getRows(numRows) { let rowsNeeded; errors.assertArgCount(arguments, 0, 1); errors.assert(this._impl && this._connection._impl, errors.ERR_INVALID_RS); if (arguments.length == 0) { numRows = 0; } else { errors.assertParamValue(Number.isInteger(numRows) && numRows >= 0, 1); } if (this._convertedToStream) { errors.throwErr(errors.ERR_CANNOT_INVOKE_RS_METHODS); } this._processingStarted = true; let requestedRows; if (numRows == 0) { requestedRows = this._rowCache; const fetchArraySize = this._impl.fetchArraySize; while (true) { // eslint-disable-line const rows = await this._getRows(fetchArraySize); if (rows) { await this._processRows(rows, false); requestedRows = requestedRows.concat(rows); } if (rows.length < fetchArraySize) break; } return requestedRows; } if (this._rowCache.length === 0) { requestedRows = await this._getRows(numRows); await this._processRows(requestedRows, false); } else { rowsNeeded = numRows - this._rowCache.length; if (rowsNeeded <= 0) { requestedRows = this._rowCache.splice(0, numRows); } else { const rows = await this._getRows(rowsNeeded); await this._processRows(rows, false); requestedRows = this._rowCache.concat(rows); this._rowCache = []; } } return requestedRows; } //--------------------------------------------------------------------------- // metaData() // // Property returning the metadata associated with the result set. //--------------------------------------------------------------------------- get metaData() { if (this._impl) { return this._impl.metaData; } return undefined; } //--------------------------------------------------------------------------- // toQueryStream() // // Converts a result set to a QueryStream object. //--------------------------------------------------------------------------- toQueryStream() { errors.assertArgCount(arguments, 0, 0); if (this._processingStarted) { errors.throwErr(errors.ERR_CANNOT_CONVERT_RS_TO_STREAM); } if (this._convertedToStream) { errors.throwErr(errors.ERR_RS_ALREADY_CONVERTED); } this._convertedToStream = true; return new QueryStream(this); } [Symbol.asyncIterator]() { const resultSet = this; return { async next() { const row = await resultSet.getRow(); return {value: row, done: row === undefined}; }, return() { return {done: true}; } }; } } nodbUtil.wrapFns(ResultSet.prototype, errors.ERR_BUSY_RS, "close", "getRow", "getRows"); module.exports = ResultSet;