Skip to content

Commit 58df9f1

Browse files
committed
Add query streaming (PR 321)
1 parent b5365ec commit 58df9f1

File tree

9 files changed

+896
-24
lines changed

9 files changed

+896
-24
lines changed

README.md

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,10 @@ The add-on is stable, well documented, and has a comprehensive test suite.
1313
The node-oracledb project is open source and maintained by Oracle Corp. The home page is on the
1414
[Oracle Technology Network](http://www.oracle.com/technetwork/database/database-technologies/scripting-languages/node_js/).
1515

16-
1716
### Node-oracledb supports:
1817

1918
- [SQL and PL/SQL execution](https://github.com/oracle/node-oracledb/blob/master/doc/api.md#sqlexecution)
20-
- [Fetching of large result sets](https://github.com/oracle/node-oracledb/blob/master/doc/api.md#resultsethandling)
19+
- Fetching of query results by [callbacks](https://github.com/oracle/node-oracledb/blob/master/doc/api.md#resultsethandling) or [streams](https://github.com/oracle/node-oracledb/blob/master/doc/api.md#streamingresults)
2120
- [REF CURSORs](https://github.com/oracle/node-oracledb/blob/master/doc/api.md#refcursors)
2221
- [Large Objects: CLOBs and BLOBs](https://github.com/oracle/node-oracledb/blob/master/doc/api.md#lobhandling)
2322
- [Query results as JavaScript objects or array ](https://github.com/oracle/node-oracledb/blob/master/doc/api.md#queryoutputformats)

doc/api.md

Lines changed: 130 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,8 @@ limitations under the License.
5252
- 3.2.14 [queueRequests](#propdbqueuerequests)
5353
- 3.2.15 [queueTimeout](#propdbqueuetimeout)
5454
- 3.2.16 [stmtCacheSize](#propdbstmtcachesize)
55-
- 3.2.17 [version](#propdbversion)
55+
- 3.2.17 [streamNumRows](#propdbstreamnumrows)
56+
- 3.2.18 [version](#propdbversion)
5657
- 3.3 [Oracledb Methods](#oracledbmethods)
5758
- 3.3.1 [createPool()](#createpool)
5859
- 3.3.2 [getConnection()](#getconnectiondb)
@@ -71,8 +72,9 @@ limitations under the License.
7172
- 4.2.3.2 [execute(): Bind Parameters](#executebindParams)
7273
- 4.2.3.3 [execute(): Options](#executeoptions)
7374
- 4.2.3.4 [execute(): Callback Function](#executecallback)
74-
- 4.2.4 [release()](#release)
75-
- 4.2.5 [rollback()](#rollback)
75+
- 4.2.4 [queryStream()](#querystream)
76+
- 4.2.5 [release()](#release)
77+
- 4.2.6 [rollback()](#rollback)
7678
5. [Lob Class](#lobclass)
7779
- 5.1 [Lob Properties](#lobproperties)
7880
- 5.1.1 [chunkSize](#proplobchunksize)
@@ -113,10 +115,11 @@ limitations under the License.
113115
- 9.1 [SELECT Statements](#select)
114116
- 9.1.1 [Fetching Rows](#fetchingrows)
115117
- 9.1.2 [Result Set Handling](#resultsethandling)
116-
- 9.1.3 [Query Output Formats](#queryoutputformats)
117-
- 9.1.4 [Query Column Metadata](#querymeta)
118-
- 9.1.5 [Result Type Mapping](#typemap)
119-
- 9.1.6 [Row Prefetching](#rowprefetching)
118+
- 9.1.3 [Streaming Query Results](#streamingresults)
119+
- 9.1.4 [Query Output Formats](#queryoutputformats)
120+
- 9.1.5 [Query Column Metadata](#querymeta)
121+
- 9.1.6 [Result Type Mapping](#typemap)
122+
- 9.1.7 [Row Prefetching](#rowprefetching)
120123
10. [PL/SQL Execution](#plsqlexecution)
121124
- 10.1 [PL/SQL Stored Procedures](#plsqlproc)
122125
- 10.2 [PL/SQL Stored Functions](#plsqlfunc)
@@ -744,7 +747,26 @@ var oracledb = require('oracledb');
744747
oracledb.stmtCacheSize = 30;
745748
```
746749

747-
#### <a name="propdbversion"></a> 3.2.17 version
750+
#### <a name="propdbstreamnumrows"></a> 3.2.17 streamNumRows
751+
752+
A value used when streaming rows with [`queryStream()`](#querystream).
753+
It does not limit the total number of rows returned by the stream.
754+
The value is passed to internal [getRows()](#getrows) calls and is
755+
used only for tuning because `getRows()` may be internally called one
756+
or more times when streaming results.
757+
758+
The default value is 100.
759+
760+
This property may be overridden in a [`queryStream()`](#querystream) call.
761+
762+
##### Example
763+
764+
```javascript
765+
var oracledb = require('oracledb');
766+
oracledb.streamNumRows = 100;
767+
```
768+
769+
#### <a name="propdbversion"></a> 3.2.18 version
748770
```
749771
readonly Number version
750772
```
@@ -1402,7 +1424,35 @@ rows affected, for example the number of rows inserted. For non-DML
14021424
statements such as queries, or if no rows are affected, then
14031425
`rowsAffected` will be zero.
14041426

1405-
#### <a name="release"></a> 4.2.4 release()
1427+
#### <a name="querystream"></a> 4.2.4 queryStream()
1428+
1429+
##### Prototype
1430+
1431+
```
1432+
stream.Readable queryStream(String sql, [Object bindParams, [Object options]]);
1433+
```
1434+
1435+
##### Return Value
1436+
1437+
This function will return a readable stream for queries.
1438+
1439+
##### Description
1440+
1441+
This function provides query streaming support. The input of this
1442+
function is same as `execute()` however a callback is not used.
1443+
Instead this function returns a stream used to fetch data. See
1444+
[Streaming Results](#streamingresults) for more information.
1445+
1446+
The connection must remain open until the stream is completely read.
1447+
1448+
##### Parameters
1449+
1450+
See [connection.execute()](#execute).
1451+
1452+
An additional options attribute `streamNumRows` can be set. This
1453+
overrides *Oracledb* [`streamNumRows`](#propdbstreamnumrows).
1454+
1455+
#### <a name="release"></a> 4.2.5 release()
14061456

14071457
##### Prototype
14081458

@@ -1445,7 +1495,7 @@ Callback function parameter | Description
14451495
----------------------------|-------------
14461496
*Error error* | If `release()` succeeds, `error` is NULL. If an error occurs, then `error` contains the [error message](#errorobj).
14471497

1448-
#### <a name="rollback"></a> 4.2.5 rollback()
1498+
#### <a name="rollback"></a> 4.2.6 rollback()
14491499

14501500
##### Prototype
14511501

@@ -2218,6 +2268,9 @@ A SQL or PL/SQL statement may be executed using the *Connection*
22182268
After all database calls on the connection complete, the application
22192269
should use the [`release()`](#release) call to release the connection.
22202270
2271+
Queries may optionally be streamed using the *Connection*
2272+
[`queryStream()`](#querystream) method.
2273+
22212274
### <a name="select"></a> 9.1 SELECT Statements
22222275
22232276
#### <a name="fetchingrows"></a> 9.1.1 Fetching Rows
@@ -2244,8 +2297,10 @@ restricted to [`maxRows`](#propdbmaxrows):
22442297
#### <a name="resultsethandling"></a> 9.1.2 Result Set Handling
22452298
22462299
When the number of query rows is relatively big, or can't be
2247-
predicted, it is recommended to use a [`ResultSet`](#resultsetclass).
2248-
This prevents query results being unexpectedly truncated by the
2300+
predicted, it is recommended to use a [`ResultSet`](#resultsetclass)
2301+
with callbacks, as described in this section, or via the ResultSet
2302+
stream wrapper, as described [later](#streamingresults). This
2303+
prevents query results being unexpectedly truncated by the
22492304
[`maxRows`](#propdbmaxrows) limit and removes the need to oversize
22502305
`maxRows` to avoid such truncation. Otherwise, for queries that
22512306
return a known small number of rows, non-result set queries may have
@@ -2343,7 +2398,65 @@ function fetchRowsFromRS(connection, resultSet, numRows)
23432398
}
23442399
```
23452400
2346-
#### <a name="queryoutputformats"></a> 9.1.3 Query Output Formats
2401+
#### <a name="streamingresults"></a> 9.1.3 Streaming Query Results
2402+
2403+
Streaming query results allows data to be piped to other streams, for
2404+
example when dealing with HTTP responses.
2405+
2406+
Use [`connection.queryStream()`](#querystream) to create a stream and
2407+
listen for events. Each row is returned as a `data` event. Query
2408+
metadata is available via a `metadata` event. The `end` event
2409+
indicates the end of the query results.
2410+
2411+
The connection must remain open until the stream is completely read.
2412+
2413+
Query results must be fetched to completion to avoid resource leaks.
2414+
2415+
The query stream implementation is a wrapper over the
2416+
[ResultSet Class](#resultsetclass). In particular, calls to
2417+
[getRows()](#getrows) are made internally to fetch each successive
2418+
subset of data, each row of which will generate a `data` event. The
2419+
number of rows fetched from the database by each `getRows()` call is
2420+
specified by the [`oracledb.streamNumRows`](#propdbstreamnumrows)
2421+
value or the `queryStream()` option attribute `streamNumRows`. This
2422+
value does not alter the number of rows returned by the stream since
2423+
`getRows()` will be called each time more rows are needed. However
2424+
the value can be used to tune performance.
2425+
2426+
There is no explicit ResultSet `close()` call for streaming query
2427+
results. This call will be executed internally when all data has been
2428+
fetched. If you need to be able to stop a query before retrieving all
2429+
data, use a [ResultSet with callbacks](#resultsethandling).
2430+
2431+
An example of streaming query results is:
2432+
2433+
```javascript
2434+
var stream = connection.queryStream('SELECT employees_name FROM employees',
2435+
[], // no bind variables
2436+
{ streamNumRows: 100 } // Used for tuning. Does not affect how many rows are returned.
2437+
// Default is 100
2438+
);
2439+
2440+
stream.on('error', function (error) {
2441+
// handle any error...
2442+
});
2443+
2444+
stream.on('data', function (data) {
2445+
// handle data row...
2446+
});
2447+
2448+
stream.on('end', function () {
2449+
// release connection...
2450+
});
2451+
2452+
stream.on('metadata', function (metadata) {
2453+
// access metadata of query
2454+
});
2455+
2456+
// listen to any other standard stream events...
2457+
```
2458+
2459+
#### <a name="queryoutputformats"></a> 9.1.4 Query Output Formats
23472460
23482461
Query rows may be returned as an array of column values, or as
23492462
Javascript objects, depending on the values of
@@ -2413,7 +2526,7 @@ names follow Oracle's standard name-casing rules. They will commonly
24132526
be uppercase, since most applications create tables using unquoted,
24142527
case-insensitive names.
24152528
2416-
#### <a name="querymeta"></a> 9.1.4 Query Column Metadata
2529+
#### <a name="querymeta"></a> 9.1.5 Query Column Metadata
24172530
24182531
The column names of a query are returned in the
24192532
[`execute()`](#execute) callback's `result.metaData` parameter
@@ -2446,7 +2559,7 @@ The names are in uppercase. This is the default casing behavior for
24462559
Oracle client programs when a database table is created with unquoted,
24472560
case-insensitive column names.
24482561
2449-
#### <a name="typemap"></a> 9.1.5 Result Type Mapping
2562+
#### <a name="typemap"></a> 9.1.6 Result Type Mapping
24502563
24512564
Oracle character, number and date columns can be selected. Data types
24522565
that are currently unsupported give a "datatype is not supported"
@@ -2617,7 +2730,7 @@ you may want to bind using `type: oracledb.STRING`. Output would be:
26172730
{ x: '-71.48923', y: '42.72347' }
26182731
```
26192732
2620-
#### <a name="rowprefetching"></a> 9.1.6 Row Prefetching
2733+
#### <a name="rowprefetching"></a> 9.1.7 Row Prefetching
26212734
26222735
[Prefetching](http://docs.oracle.com/database/121/LNOCI/oci04sql.htm#LNOCI16355) is a query tuning feature allowing resource usage to be
26232736
optimized. It allows multiple rows to be returned in each network
@@ -2850,7 +2963,7 @@ connection.execute(
28502963
```
28512964
28522965
The query rows can be handled using a
2853-
[ResultSet](http://localhost:8899/doc/api.md#resultsethandling).
2966+
[ResultSet](#resultsethandling).
28542967
28552968
Remember to first enable output using `DBMS_OUTPUT.ENABLE(NULL)`.
28562969

examples/selectstream.js

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
/* Copyright (c) 2016, Oracle and/or its affiliates. All rights reserved. */
2+
3+
/******************************************************************************
4+
*
5+
* You may not use the identified files except in compliance with the Apache
6+
* License, Version 2.0 (the "License.")
7+
*
8+
* You may obtain a copy of the License at
9+
* http://www.apache.org/licenses/LICENSE-2.0.
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
13+
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
*
15+
* See the License for the specific language governing permissions and
16+
* limitations under the License.
17+
*
18+
* NAME
19+
* selectstream.js
20+
*
21+
* DESCRIPTION
22+
* Executes a basic query using a Readable Stream.
23+
* Uses Oracle's sample HR schema.
24+
*
25+
* Scripts to create the HR schema can be found at:
26+
* https://github.com/oracle/db-sample-schemas
27+
*
28+
*****************************************************************************/
29+
30+
var oracledb = require('oracledb');
31+
var dbConfig = require('./dbconfig.js');
32+
33+
oracledb.getConnection(
34+
{
35+
user : dbConfig.user,
36+
password : dbConfig.password,
37+
connectString : dbConfig.connectString
38+
},
39+
function(err, connection)
40+
{
41+
if (err) {
42+
console.error(err.message);
43+
return;
44+
}
45+
46+
var stream = connection.queryStream(
47+
'SELECT first_name, last_name FROM employees ORDER BY employee_id',
48+
[], // no bind variables
49+
{ streamNumRows: 100 } // Used for tuning. Does not affect how many rows are returned.
50+
// Default is 100
51+
);
52+
53+
stream.on('error', function (error) {
54+
// console.log("stream 'error' event");
55+
console.error(error);
56+
return;
57+
});
58+
59+
stream.on('metadata', function (metadata) {
60+
// console.log("stream 'metadata' event");
61+
console.log(metadata);
62+
});
63+
64+
stream.on('data', function (data) {
65+
// console.log("stream 'data' event");
66+
console.log(data);
67+
});
68+
69+
stream.on('end', function () {
70+
// console.log("stream 'end' event");
71+
connection.release(
72+
function(err) {
73+
if (err) {
74+
console.error(err.message);
75+
}
76+
});
77+
});
78+
});

lib/connection.js

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,18 @@
1818
*****************************************************************************/
1919

2020
var resultset = require('./resultset.js');
21+
var Stream = require('./resultset-read-stream');
22+
23+
// The queryStream function is similar to execute except that it immediately
24+
// returns a readable stream.
25+
function queryStream(sql, binding, options) {
26+
var self = this;
27+
var stream;
28+
29+
stream = new Stream(self, sql, binding, options);
30+
31+
return stream;
32+
}
2133

2234
// This execute function is used to override the execute method of the Connection
2335
// class, which is defined in the C layer. The override allows us to do things
@@ -110,18 +122,26 @@ module.break = function() {
110122
// The extend method is used to extend the Connection instance from the C layer with
111123
// custom properties and method overrides. References to the original methods are
112124
// maintained so they can be invoked by the overriding method at the right time.
113-
function extend(conn, pool) {
125+
function extend(conn, oracledb, pool) {
114126
// Using Object.defineProperties to add properties to the Connection instance with
115127
// special properties, such as enumerable but not writable.
116128
Object.defineProperties(
117129
conn,
118130
{
131+
_oracledb: { // storing a reference to the base instance to avoid circular references with require
132+
value: oracledb
133+
},
119134
_pool: {
120135
value: pool
121136
},
122137
_execute: {
123138
value: conn.execute
124139
},
140+
queryStream: {
141+
value: queryStream,
142+
enumerable: true,
143+
writable: true
144+
},
125145
execute: {
126146
value: execute,
127147
enumerable: true,

0 commit comments

Comments
 (0)