UNPKG

12.1 kBJavaScriptView Raw
1// Copyright (c) 2016, 2023, 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'use strict';
28
29const QueryStream = require('./queryStream.js');
30const BaseDbObject = require('./dbObject.js');
31const nodbUtil = require('./util.js');
32const constants = require('./constants.js');
33const Lob = require('./lob.js');
34const errors = require('./errors.js');
35
36class ResultSet {
37
38 constructor() {
39 this._rowCache = [];
40 this._processingStarted = false;
41 this._convertedToStream = false;
42 this._allowGetRowCall = false;
43 this._isActive = false;
44 }
45
46 //---------------------------------------------------------------------------
47 // _getAllRows()
48 //
49 // Return all of the rows in the result set.
50 //---------------------------------------------------------------------------
51 async _getAllRows() {
52
53 try {
54
55 // retain initial values of the maximum number of rows to fetch and the
56 // number of rows to fetch from the database at a single time
57 let maxRows = this._impl.maxRows;
58 let fetchArraySize = this._impl.fetchArraySize;
59
60 // fetch all rows
61 let rowsFetched = [];
62 while (true) { // eslint-disable-line
63 if (maxRows > 0 && fetchArraySize >= maxRows) {
64 fetchArraySize = maxRows;
65 }
66 const rows = await this._getRows(fetchArraySize);
67 if (rows) {
68 await this._processRows(rows, true);
69 rowsFetched = rowsFetched.concat(rows);
70 }
71 if (rows.length == maxRows || rows.length < fetchArraySize) {
72 break;
73 }
74 if (maxRows > 0) {
75 maxRows -= rows.length;
76 }
77 }
78
79 return rowsFetched;
80
81 } finally {
82 await this._impl.close();
83 delete this._impl;
84 }
85
86 }
87
88 //---------------------------------------------------------------------------
89 // _getRows()
90 //
91 // Return up to the specified number of rows from the result set. If nested
92 // cursors are possible, setup the execute options so that they can be
93 // examined within the implementation's setup routine.
94 //---------------------------------------------------------------------------
95 async _getRows(numRows) {
96 let options = {};
97 if (this._impl.nestedCursorIndices.length > 0) {
98 options = {
99 connection: this._connection,
100 outFormat: this._impl.outFormat,
101 fetchArraySize: this._impl.fetchArraySize,
102 dbObjectAsPojo: this._impl.dbObjectAsPojo,
103 maxRows: this._impl.maxRows,
104 fetchTypeMap: this._impl.fetchTypeMap
105 };
106 }
107 return await this._impl.getRows(numRows, options);
108 }
109
110 //---------------------------------------------------------------------------
111 // _processRows()
112 //
113 // Process rows returned by the implementation. This will transform result
114 // set and LOB implementations into user facing objects. It will also perform
115 // any fetched that are needed (if a result set is undesirable)
116 //---------------------------------------------------------------------------
117 async _processRows(rows, expandNestedCursors) {
118
119 // transform any nested cursors into user facing objects
120 for (const i of this._impl.nestedCursorIndices) {
121 for (let j = 0; j < rows.length; j++) {
122 const val = rows[j][i];
123 if (val) {
124 const resultSet = new ResultSet();
125 resultSet._setup(this._connection, val);
126 this._impl.metaData[i].metaData = val.metaData;
127 if (expandNestedCursors) {
128 rows[j][i] = await resultSet._getAllRows();
129 } else {
130 rows[j][i] = resultSet;
131 }
132 }
133 }
134 }
135
136 // transform any LOBs into user facing objects
137 for (const i of this._impl.lobIndices) {
138 for (let j = 0; j < rows.length; j++) {
139 const val = rows[j][i];
140 if (val) {
141 const lob = rows[j][i] = new Lob();
142 lob._setup(val, true);
143 }
144 }
145 }
146
147 // transform any database objects into user facing objects
148 for (const i of this._impl.dbObjectIndices) {
149 const dbObjectClass = this._impl.metaData[i].dbTypeClass;
150 for (let j = 0; j < rows.length; j++) {
151 const val = rows[j][i];
152 if (val) {
153 const obj = rows[j][i] = Object.create(dbObjectClass.prototype);
154 obj._impl = val;
155 if (this._impl.dbObjectAsPojo) {
156 rows[j][i] = obj._toPojo();
157 } else if (obj.isCollection) {
158 rows[j][i] = new Proxy(obj, BaseDbObject._collectionProxyHandler);
159 }
160 }
161 }
162 }
163
164 // run any conversion functions, if applicable
165 // NOTE: we mark the connection as no longer in progress before making
166 // calls to the converter function; this is needed to allow calls against
167 // the database (like getting LOB data) to succeed, as this code is running
168 // in the middle of a call to connection.execute() or resultSet.getRows()
169 for (const i of this._impl.converterIndices) {
170 const fn = this._impl.metaData[i].converter;
171 this._connection._impl._inProgress = false;
172 try {
173 for (let j = 0; j < rows.length; j++) {
174 let result = fn(rows[j][i]);
175 if (result instanceof Promise) {
176 result = await result;
177 }
178 rows[j][i] = result;
179 }
180 } finally {
181 this._connection._impl._inProgress = true;
182 }
183 }
184
185 // create objects, if desired
186 if (this._impl.outFormat === constants.OUT_FORMAT_OBJECT) {
187 for (let i = 0; i < rows.length; i++) {
188 const origRow = rows[i];
189 const newRow = rows[i] = {};
190 const metaData = this._impl.metaData;
191 for (let j = 0; j < metaData.length; j++) {
192 newRow[metaData[j].name] = origRow[j];
193 }
194 }
195 }
196
197 }
198
199 //---------------------------------------------------------------------------
200 // _setup()
201 //
202 // Setup a result set.
203 // ---------------------------------------------------------------------------
204 _setup(connection, resultSetImpl) {
205 this._connection = connection;
206 this._impl = resultSetImpl;
207 }
208
209 //---------------------------------------------------------------------------
210 // close()
211 //
212 // Close the result set and make it unusable for further operations.
213 //---------------------------------------------------------------------------
214 async close() {
215 errors.assertArgCount(arguments, 0, 0);
216 errors.assert(this._impl && this._connection._impl, errors.ERR_INVALID_RS);
217
218 if (this._convertedToStream) {
219 errors.throwErr(errors.ERR_CANNOT_INVOKE_RS_METHODS);
220 }
221
222 this._processingStarted = true;
223 const resultSetImpl = this._impl;
224 delete this._impl;
225 await resultSetImpl.close();
226 }
227
228 //---------------------------------------------------------------------------
229 // getRow()
230 //
231 // Returns a single row to the caller from the result set, if one is
232 // available. Rows are buffered in a JavaScript array in order to avoid trips
233 // through the thread pool that would be required if implemented in C.
234 //---------------------------------------------------------------------------
235 async getRow() {
236 errors.assertArgCount(arguments, 0, 0);
237 errors.assert(this._impl && this._connection._impl, errors.ERR_INVALID_RS);
238
239 if (this._convertedToStream && !this._allowGetRowCall) {
240 errors.throwErr(errors.ERR_CANNOT_INVOKE_RS_METHODS);
241 }
242
243 this._allowGetRowCall = false;
244 this._processingStarted = true;
245
246 if (this._rowCache.length == 0) {
247 const rows = await this._getRows(this._impl.fetchArraySize);
248 await this._processRows(rows, false);
249 this._rowCache = rows;
250 }
251 return this._rowCache.shift();
252 }
253
254 //---------------------------------------------------------------------------
255 // getRows()
256 //
257 // Check to see if any rows are in the JS buffer (which could result from
258 // interspersed calls to getRow() and getRows()). If no rows are in the
259 // buffer, the call is just proxied to the implementation layer. Otherwise,
260 // rows are pulled from the buffer and potentially concatenated with rows
261 // from calls to the implementation's getRows().
262 //---------------------------------------------------------------------------
263 async getRows(numRows) {
264 let rowsNeeded;
265
266 errors.assertArgCount(arguments, 0, 1);
267 errors.assert(this._impl && this._connection._impl, errors.ERR_INVALID_RS);
268
269 if (arguments.length == 0) {
270 numRows = 0;
271 } else {
272 errors.assertParamValue(Number.isInteger(numRows) && numRows >= 0, 1);
273 }
274
275 if (this._convertedToStream) {
276 errors.throwErr(errors.ERR_CANNOT_INVOKE_RS_METHODS);
277 }
278
279 this._processingStarted = true;
280
281 let requestedRows;
282 if (numRows == 0) {
283 requestedRows = this._rowCache;
284 const fetchArraySize = this._impl.fetchArraySize;
285 while (true) { // eslint-disable-line
286 const rows = await this._getRows(fetchArraySize);
287 if (rows) {
288 await this._processRows(rows, false);
289 requestedRows = requestedRows.concat(rows);
290 }
291 if (rows.length < fetchArraySize)
292 break;
293 }
294 return requestedRows;
295 }
296
297 if (this._rowCache.length === 0) {
298 requestedRows = await this._getRows(numRows);
299 await this._processRows(requestedRows, false);
300 } else {
301 rowsNeeded = numRows - this._rowCache.length;
302 if (rowsNeeded <= 0) {
303 requestedRows = this._rowCache.splice(0, numRows);
304 } else {
305 const rows = await this._getRows(rowsNeeded);
306 await this._processRows(rows, false);
307 requestedRows = this._rowCache.concat(rows);
308 this._rowCache = [];
309 }
310 }
311
312 return requestedRows;
313 }
314
315 //---------------------------------------------------------------------------
316 // metaData()
317 //
318 // Property returning the metadata associated with the result set.
319 //---------------------------------------------------------------------------
320 get metaData() {
321 if (this._impl) {
322 return this._impl.metaData;
323 }
324 return undefined;
325 }
326
327 //---------------------------------------------------------------------------
328 // toQueryStream()
329 //
330 // Converts a result set to a QueryStream object.
331 //---------------------------------------------------------------------------
332 toQueryStream() {
333 errors.assertArgCount(arguments, 0, 0);
334
335 if (this._processingStarted) {
336 errors.throwErr(errors.ERR_CANNOT_CONVERT_RS_TO_STREAM);
337 }
338
339 if (this._convertedToStream) {
340 errors.throwErr(errors.ERR_RS_ALREADY_CONVERTED);
341 }
342
343 this._convertedToStream = true;
344
345 return new QueryStream(this);
346 }
347
348 [Symbol.asyncIterator]() {
349 const resultSet = this;
350 return {
351 async next() {
352 const row = await resultSet.getRow();
353 return {value: row, done: row === undefined};
354 },
355 return() {
356 return {done: true};
357 }
358 };
359 }
360
361}
362
363nodbUtil.wrapFns(ResultSet.prototype, errors.ERR_BUSY_RS,
364 "close",
365 "getRow",
366 "getRows");
367
368module.exports = ResultSet;