1 | // Copyright (c) 2016, 2024, Oracle and/or its affiliates.
|
2 |
|
3 | //-----------------------------------------------------------------------------
|
4 | //
|
5 | // This software is dual-licensed to you under the Universal Permissive License
|
6 | // (UPL) 1.0 as shown at https://oss.oracle.com/licenses/upl and Apache License
|
7 | // 2.0 as shown at http://www.apache.org/licenses/LICENSE-2.0. You may choose
|
8 | // either license.
|
9 | //
|
10 | // If you elect to accept the software under the Apache License, Version 2.0,
|
11 | // the following applies:
|
12 | //
|
13 | // Licensed under the Apache License, Version 2.0 (the "License");
|
14 | // you may not use this file except in compliance with the License.
|
15 | // You may obtain a copy of the License at
|
16 | //
|
17 | // https://www.apache.org/licenses/LICENSE-2.0
|
18 | //
|
19 | // Unless required by applicable law or agreed to in writing, software
|
20 | // distributed under the License is distributed on an "AS IS" BASIS,
|
21 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
22 | // See the License for the specific language governing permissions and
|
23 | // limitations under the License.
|
24 | //
|
25 | //-----------------------------------------------------------------------------
|
26 |
|
27 | ;
|
28 |
|
29 | const { Buffer } = require('buffer');
|
30 | const { Duplex } = require('stream');
|
31 | const constants = require('./constants.js');
|
32 | const errors = require('./errors.js');
|
33 | const nodbUtil = require('./util.js');
|
34 | const types = require('./types.js');
|
35 |
|
36 | class Lob extends Duplex {
|
37 |
|
38 | constructor() {
|
39 | super({ decodeStrings: false });
|
40 | this.offset = 1;
|
41 | this._isActive = false;
|
42 | this.once('finish', function() {
|
43 | if (this._autoCloseLob) {
|
44 | this.destroy();
|
45 | }
|
46 | });
|
47 | }
|
48 |
|
49 | // called by stream.destroy() and ensures that the LOB is closed if it has
|
50 | // not already been closed (never called directly)
|
51 | async _destroy(err, cb) {
|
52 | // if LOB was already closed, nothing to do!
|
53 | if (err && err.message.startsWith("NJS-003:"))
|
54 | delete this._impl;
|
55 | if (this._impl) {
|
56 | const lobImpl = this._impl;
|
57 | delete this._impl;
|
58 | try {
|
59 | await lobImpl.close();
|
60 | } catch (closeErr) {
|
61 | cb(closeErr);
|
62 | return;
|
63 | }
|
64 | }
|
65 | cb(err);
|
66 | }
|
67 |
|
68 | // implementation of streaming read; if LOB is set to auto-close, the lob is
|
69 | // automatically closed when an error occurs or when there are no more bytes
|
70 | // to transfer; all that needs to be done here is to destroy the streaming
|
71 | // LOB
|
72 | async _read() {
|
73 | try {
|
74 | const data = await this._serializedRead(this.offset);
|
75 | if (data) {
|
76 | this.offset += data.length;
|
77 | this.push(data);
|
78 | } else {
|
79 | this.push(null);
|
80 | if (this._autoCloseLob) {
|
81 | this.destroy();
|
82 | }
|
83 | }
|
84 | } catch (err) {
|
85 | if (this._autoCloseLob)
|
86 | this.destroy(err);
|
87 | throw err;
|
88 | }
|
89 | }
|
90 |
|
91 | // simple wrapper so that serialization can take place on a JavaScript fn
|
92 | async _readData(offset) {
|
93 | errors.assert(this._impl, errors.ERR_INVALID_LOB);
|
94 | try {
|
95 | return await this._impl.read(offset);
|
96 | } catch (err) {
|
97 | throw errors.transformErr(err, this._readData);
|
98 | }
|
99 | }
|
100 |
|
101 | // called to associate a LOB implementation with this user facing object
|
102 | _setup(lobImpl, autoCloseLob) {
|
103 | this._impl = lobImpl;
|
104 | this._chunkSize = lobImpl.getChunkSize();
|
105 | this._pieceSize = lobImpl.getPieceSize();
|
106 | this._length = lobImpl.getLength();
|
107 | this._type = lobImpl.getType();
|
108 | if (typeof this._type === 'number') {
|
109 | this._type = types.getTypeByNum(this._type);
|
110 | }
|
111 | this._autoCloseLob = autoCloseLob;
|
112 | }
|
113 |
|
114 | // implementation of streaming write; if LOB is set to auto-close, the lob is
|
115 | // automatically closed in the "finish" event; all that needs to be done here
|
116 | // is to destroy the streaming LOB
|
117 | async _write(data, encoding, cb) {
|
118 |
|
119 | // convert data if needed
|
120 | if (this.type == constants.DB_TYPE_BLOB && !Buffer.isBuffer(data)) {
|
121 | data = Buffer.from(data);
|
122 | } else if (this.type == constants.DB_TYPE_CLOB &&
|
123 | Buffer.isBuffer(data)) {
|
124 | data = data.toString();
|
125 | }
|
126 |
|
127 | try {
|
128 | await this._serializedWrite(this.offset, data);
|
129 | } catch (err) {
|
130 | if (this._autoCloseLob)
|
131 | this.destroy(err);
|
132 | cb(err);
|
133 | return;
|
134 | }
|
135 | this.offset += data.length;
|
136 | cb(null);
|
137 |
|
138 | }
|
139 |
|
140 | // simple wrapper so that serialization can take place on a JavaScript fn
|
141 | async _writeData(offset, data) {
|
142 | errors.assert(this._impl, errors.ERR_INVALID_LOB);
|
143 | try {
|
144 | await this._impl.write(offset, data);
|
145 | } catch (err) {
|
146 | throw errors.transformErr(err, this._writeData);
|
147 | }
|
148 | }
|
149 |
|
150 | //---------------------------------------------------------------------------
|
151 | // chunkSize
|
152 | //
|
153 | // Property for the chunk size of the LOB.
|
154 | //---------------------------------------------------------------------------
|
155 | get chunkSize() {
|
156 | return this._chunkSize;
|
157 | }
|
158 |
|
159 | //---------------------------------------------------------------------------
|
160 | // close()
|
161 | //
|
162 | // Close the LOB and make it unusable for further operations. If the LOB is
|
163 | // already closed, nothing is done in order to support multiple close()
|
164 | // calls.
|
165 | //
|
166 | // This method is deprecated and will be removed in a future version of the
|
167 | // node-oracledb driver. Use lob.destroy() instead. NOTE: this method will
|
168 | // emit a duplicate "close" event in order to be compatible with previous
|
169 | // versions of node-oracledb.
|
170 | //---------------------------------------------------------------------------
|
171 | async close() {
|
172 | errors.assertArgCount(arguments, 0, 0);
|
173 | if (this._impl) {
|
174 | const lobImpl = this._impl;
|
175 | delete this._impl;
|
176 | try {
|
177 | await lobImpl.close();
|
178 | this.emit('close');
|
179 | } catch (err) {
|
180 | this.destroy(err);
|
181 | }
|
182 | }
|
183 | }
|
184 |
|
185 | //---------------------------------------------------------------------------
|
186 | // getData()
|
187 | //
|
188 | // Return a portion (or all) of the data in the LOB. Note that the amount
|
189 | // and offset are in bytes for BLOB and BFILE type LOBs and in UCS - 2 code
|
190 | // points for CLOB and NCLOB type LOBs.UCS-2 code points are equivalent
|
191 | // to characters for all but supplemental characters.If supplemental
|
192 | // characters are in the LOB, the offset and amount will have to be chosen
|
193 | // carefully to avoid splitting a character.
|
194 | // Returns data in the LOB as a single string or buffer.
|
195 | //---------------------------------------------------------------------------
|
196 | async getData(offset, amount) {
|
197 | errors.assertArgCount(arguments, 0, 2);
|
198 | if (offset === undefined) {
|
199 | offset = 1;
|
200 | } else {
|
201 | errors.assertParamValue(Number.isInteger(offset) && offset > 0, 1);
|
202 | }
|
203 | if (amount === undefined) {
|
204 | amount = 0;
|
205 | } else {
|
206 | errors.assertParamValue(Number.isInteger(amount) && amount > 0, 2);
|
207 | }
|
208 | errors.assert(this._impl, errors.ERR_INVALID_LOB);
|
209 | return await this._impl.getData(offset, amount);
|
210 | }
|
211 |
|
212 | //---------------------------------------------------------------------------
|
213 | // length
|
214 | //
|
215 | // Property for the length of the LOB.
|
216 | //---------------------------------------------------------------------------
|
217 | get length() {
|
218 | return this._length;
|
219 | }
|
220 |
|
221 | //---------------------------------------------------------------------------
|
222 | // pieceSize
|
223 | //
|
224 | // Property for the size to use for each piece that is transferred when
|
225 | // reading from the LOB.
|
226 | //---------------------------------------------------------------------------
|
227 | get pieceSize() {
|
228 | return this._pieceSize;
|
229 | }
|
230 |
|
231 | set pieceSize(value) {
|
232 | errors.assertPropValue(Number.isInteger(value) && value >= 0, "pieceSize");
|
233 | errors.assert(this._impl, errors.ERR_INVALID_LOB);
|
234 | this._impl.setPieceSize(value);
|
235 | this._pieceSize = value;
|
236 | }
|
237 |
|
238 | //---------------------------------------------------------------------------
|
239 | // type
|
240 | //
|
241 | // Property for the type of the LOB.
|
242 | //---------------------------------------------------------------------------
|
243 | get type() {
|
244 | return this._type;
|
245 | }
|
246 |
|
247 | }
|
248 |
|
249 | nodbUtil.wrapFns(Lob.prototype, errors.ERR_BUSY_LOB,
|
250 | "close",
|
251 | "getData");
|
252 | Lob.prototype._serializedRead = nodbUtil.serialize(Lob.prototype._readData);
|
253 | Lob.prototype._serializedWrite = nodbUtil.serialize(Lob.prototype._writeData);
|
254 |
|
255 | module.exports = Lob;
|