// 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 { Buffer } = require('buffer'); const { Duplex } = require('stream'); const constants = require('./constants.js'); const errors = require('./errors.js'); const nodbUtil = require('./util.js'); const types = require('./types.js'); class Lob extends Duplex { constructor() { super({ decodeStrings: false }); this.offset = 1; this._isActive = false; this.once('finish', function() { if (this._autoCloseLob) { this.destroy(); } }); } // called by stream.destroy() and ensures that the LOB is closed if it has // not already been closed (never called directly) async _destroy(err, cb) { // if LOB was already closed, nothing to do! if (err && err.message.startsWith("NJS-108:")) delete this._impl; if (this._impl) { const lobImpl = this._impl; delete this._impl; try { await lobImpl.close(); } catch (closeErr) { cb(closeErr); return; } } cb(err); } // implementation of streaming read; if LOB is set to auto-close, the lob is // automatically closed when an error occurs or when there are no more bytes // to transfer; all that needs to be done here is to destroy the streaming // LOB async _read() { try { const data = await this._serializedRead(this.offset); if (data) { this.offset += data.length; this.push(data); } else { this.push(null); if (this._autoCloseLob) { this.destroy(); } } } catch (err) { if (this._autoCloseLob) this.destroy(err); throw err; } } // simple wrapper so that serialization can take place on a JavaScript fn async _readData(offset) { errors.assert(this._impl, errors.ERR_INVALID_LOB); try { return await this._impl.read(offset); } catch (err) { throw errors.transformErr(err, this._readData); } } // called to associate a LOB implementation with this user facing object _setup(lobImpl, autoCloseLob) { this._impl = lobImpl; this._chunkSize = lobImpl.getChunkSize(); this._pieceSize = lobImpl.getPieceSize(); this._length = lobImpl.getLength(); this._type = lobImpl.getType(); if (typeof this._type === 'number') { this._type = types.getTypeByNum(this._type); } this._autoCloseLob = autoCloseLob; } // implementation of streaming write; if LOB is set to auto-close, the lob is // automatically closed in the "finish" event; all that needs to be done here // is to destroy the streaming LOB async _write(data, encoding, cb) { // convert data if needed if (this.type == constants.DB_TYPE_BLOB && !Buffer.isBuffer(data)) { data = Buffer.from(data); } else if (this.type == constants.DB_TYPE_CLOB && Buffer.isBuffer(data)) { data = data.toString(); } try { await this._serializedWrite(this.offset, data); } catch (err) { if (this._autoCloseLob) this.destroy(err); cb(err); return; } this.offset += data.length; cb(null); } // simple wrapper so that serialization can take place on a JavaScript fn async _writeData(offset, data) { errors.assert(this._impl, errors.ERR_INVALID_LOB); try { await this._impl.write(offset, data); } catch (err) { throw errors.transformErr(err, this._writeData); } } //--------------------------------------------------------------------------- // chunkSize // // Property for the chunk size of the LOB. //--------------------------------------------------------------------------- get chunkSize() { return this._chunkSize; } //--------------------------------------------------------------------------- // close() // // Close the LOB and make it unusable for further operations. If the LOB is // already closed, nothing is done in order to support multiple close() // calls. // // This method is deprecated and will be removed in a future version of the // node-oracledb driver. Use lob.destroy() instead. NOTE: this method will // emit a duplicate "close" event in order to be compatible with previous // versions of node-oracledb. //--------------------------------------------------------------------------- async close() { errors.assertArgCount(arguments, 0, 0); if (this._impl) { const lobImpl = this._impl; delete this._impl; try { await lobImpl.close(); this.emit('close'); } catch (err) { this.destroy(err); } } } //--------------------------------------------------------------------------- // getData() // // Returns all of the data in the LOB as a single string or buffer. //--------------------------------------------------------------------------- async getData() { errors.assertArgCount(arguments, 0, 0); errors.assert(this._impl, errors.ERR_INVALID_LOB); return await this._impl.getData(); } //--------------------------------------------------------------------------- // length // // Property for the length of the LOB. //--------------------------------------------------------------------------- get length() { return this._length; } //--------------------------------------------------------------------------- // pieceSize // // Property for the size to use for each piece that is transferred when // reading from the LOB. //--------------------------------------------------------------------------- get pieceSize() { return this._pieceSize; } set pieceSize(value) { errors.assertPropValue(Number.isInteger(value) && value >= 0, "pieceSize"); errors.assert(this._impl, errors.ERR_INVALID_LOB); this._impl.setPieceSize(value); this._pieceSize = value; } //--------------------------------------------------------------------------- // type // // Property for the type of the LOB. //--------------------------------------------------------------------------- get type() { return this._type; } } nodbUtil.wrapFns(Lob.prototype, errors.ERR_BUSY_LOB, "close", "getData"); Lob.prototype._serializedRead = nodbUtil.serialize(Lob.prototype._readData); Lob.prototype._serializedWrite = nodbUtil.serialize(Lob.prototype._writeData); module.exports = Lob;