Skip to content

Commit f8354d4

Browse files
committed
Add CQN client initated connection support
1 parent a568de9 commit f8354d4

File tree

8 files changed

+296
-50
lines changed

8 files changed

+296
-50
lines changed

doc/api.md

Lines changed: 65 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -204,16 +204,17 @@ For installation information, see the [Node-oracledb Installation Instructions][
204204
- 4.2.16.2 [`subscribe()`: Options](#consubscribeoptions)
205205
- 4.2.16.2.1 [`binds`](#consubscribeoptbinds)
206206
- 4.2.16.2.2 [`callback`](#consubscribeoptcallback)
207-
- 4.2.16.2.3 [`groupingClass`](#consubscribeoptgroupingclass)
208-
- 4.2.16.2.4 [`groupingType`](#consubscribeoptgroupingtype)
209-
- 4.2.16.2.5 [`groupingValue`](#consubscribeoptgroupingvalue)
210-
- 4.2.16.2.6 [`ipAddress`](#consubscribeoptipaddress)
211-
- 4.2.16.2.7 [`namespace`](#consubscribeoptnamespace)
212-
- 4.2.16.2.8 [`operations`](#consubscribeoptoperations)
213-
- 4.2.16.2.9 [`port`](#consubscribeoptport)
214-
- 4.2.16.2.10 [`qos`](#consubscribeoptqos)
215-
- 4.2.16.2.11 [`sql`](#consubscribeoptsql)
216-
- 4.2.16.2.12 [`timeout`](#consubscribeopttimeout)
207+
- 4.2.16.2.3 [`clientInitiated`](#consubscribeoptclientinitiated)
208+
- 4.2.16.2.4 [`groupingClass`](#consubscribeoptgroupingclass)
209+
- 4.2.16.2.5 [`groupingType`](#consubscribeoptgroupingtype)
210+
- 4.2.16.2.6 [`groupingValue`](#consubscribeoptgroupingvalue)
211+
- 4.2.16.2.7 [`ipAddress`](#consubscribeoptipaddress)
212+
- 4.2.16.2.8 [`namespace`](#consubscribeoptnamespace)
213+
- 4.2.16.2.9 [`operations`](#consubscribeoptoperations)
214+
- 4.2.16.2.10 [`port`](#consubscribeoptport)
215+
- 4.2.16.2.11 [`qos`](#consubscribeoptqos)
216+
- 4.2.16.2.12 [`sql`](#consubscribeoptsql)
217+
- 4.2.16.2.13 [`timeout`](#consubscribeopttimeout)
217218
- 4.2.16.3 [`subscribe()`: Callback Function](#consubscribecallback)
218219
- 4.2.17 [`unsubscribe()`](#conunsubscribe)
219220
5. [AqQueue Class](#aqqueueclass)
@@ -3946,7 +3947,23 @@ The `message` parameter in the notification callback is an object containing the
39463947
- [`oracledb.SUBSCR_EVENT_TYPE_OBJ_CHANGE`](#oracledbconstantssubscription) - object-level notifications are being used (Database Change Notification).
39473948
- [`oracledb.SUBSCR_EVENT_TYPE_QUERY_CHANGE`](#oracledbconstantssubscription) - query-level notifications are being used (Continuous Query Notification).
39483949

3949-
###### <a name="consubscribeoptgroupingclass"></a> 4.2.16.2.3 `groupingClass`
3950+
###### <a name="consubscribeoptclientinitiated"></a> 4.2.16.2.3 `clientInitiated`
3951+
3952+
```
3953+
Boolean clientInitiated
3954+
```
3955+
3956+
This property enables CQN "client initiated" connections which internally use the same
3957+
approach as normal connections to the database, and do not require the database
3958+
to be able to connect back to the application. Since client initiated
3959+
connections do not need additional network configuration, they have ease-of-use
3960+
and security advantages.
3961+
3962+
The default is *false*.
3963+
3964+
This property was added in node-oracledb 4.2. It is available when Oracle Database and the Oracle client libraries are version 19.4 or higher.
3965+
3966+
###### <a name="consubscribeoptgroupingclass"></a> 4.2.16.2.4 `groupingClass`
39503967

39513968
```
39523969
Number groupingClass
@@ -3957,7 +3974,7 @@ An integer mask which currently, if set, can only contain the value
39573974
this value is set then notifications are grouped by time into a single
39583975
notification.
39593976

3960-
###### <a name="consubscribeoptgroupingtype"></a> 4.2.16.2.4 `groupingType`
3977+
###### <a name="consubscribeoptgroupingtype"></a> 4.2.16.2.5 `groupingType`
39613978

39623979
```
39633980
Number groupingType
@@ -3970,7 +3987,7 @@ or
39703987
[`oracledb.SUBSCR_GROUPING_TYPE_LAST`](#oracledbconstantssubscription)
39713988
indicating the last notification in the group should be sent.
39723989

3973-
###### <a name="consubscribeoptgroupingvalue"></a> 4.2.16.2.5 `groupingValue`
3990+
###### <a name="consubscribeoptgroupingvalue"></a> 4.2.16.2.6 `groupingValue`
39743991

39753992
```
39763993
Number groupingValue
@@ -3982,7 +3999,7 @@ then `groupingValue` can be used to set the number of seconds over
39823999
which notifications will be grouped together, invoking `callback`
39834000
once. If `groupingClass` is not set, then `groupingValue` is ignored.
39844001

3985-
###### <a name="consubscribeoptipaddress"></a> 4.2.16.2.6 `ipAddress`
4002+
###### <a name="consubscribeoptipaddress"></a> 4.2.16.2.7 `ipAddress`
39864003

39874004
```
39884005
String ipAddress
@@ -3992,7 +4009,7 @@ A string containing an IPv4 or IPv6 address on which the subscription
39924009
should listen to receive notifications. If not specified, then the
39934010
Oracle Client library will select an IP address.
39944011

3995-
###### <a name="consubscribeoptnamespace"></a> 4.2.16.2.7 `namespace`
4012+
###### <a name="consubscribeoptnamespace"></a> 4.2.16.2.8 `namespace`
39964013

39974014
```
39984015
Number namespace
@@ -4007,7 +4024,7 @@ You can use `oracledb.SUBSCR_NAMESPACE_AQ` to get notifications that
40074024
Advanced Queuing messages are available to be dequeued, see
40084025
[Advanced Queuing Notifications](#aqnotifications).
40094026

4010-
###### <a name="consubscribeoptoperations"></a> 4.2.16.2.8 `operations`
4027+
###### <a name="consubscribeoptoperations"></a> 4.2.16.2.9 `operations`
40114028

40124029
```
40134030
Number operations
@@ -4017,7 +4034,7 @@ An integer mask containing one or more of the operation type
40174034
[`oracledb.CQN_OPCODE_*`](#oracledbconstantscqn) constants to indicate
40184035
what types of database change should generation notifications.
40194036

4020-
###### <a name="consubscribeoptport"></a> 4.2.16.2.9 `port`
4037+
###### <a name="consubscribeoptport"></a> 4.2.16.2.10 `port`
40214038

40224039
```
40234040
Number port
@@ -4027,7 +4044,7 @@ The port number on which the subscription should listen to receive
40274044
notifications. If not specified, then the Oracle Client library will
40284045
select a port number.
40294046

4030-
###### <a name="consubscribeoptqos"></a> 4.2.16.2.10 `qos`
4047+
###### <a name="consubscribeoptqos"></a> 4.2.16.2.11 `qos`
40314048

40324049
```
40334050
Number qos
@@ -4036,15 +4053,15 @@ Number qos
40364053
An integer mask containing one or more of the quality of service
40374054
[`oracledb.SUBSCR_QOS_*`](#oracledbconstantssubscription) constants.
40384055

4039-
###### <a name="consubscribeoptsql"></a> 4.2.16.2.11 `sql`
4056+
###### <a name="consubscribeoptsql"></a> 4.2.16.2.12 `sql`
40404057

40414058
```
40424059
String sql
40434060
```
40444061

40454062
The SQL query string to use for notifications.
40464063

4047-
###### <a name="consubscribeopttimeout"></a> 4.2.16.2.12 `timeout`
4064+
###### <a name="consubscribeopttimeout"></a> 4.2.16.2.13 `timeout`
40484065

40494066
The number of seconds the subscription should remain active. Once
40504067
this length of time has been reached, the subscription is
@@ -12503,34 +12520,38 @@ satisfaction.
1250312520

1250412521
## <a name="cqn"></a> 25. Continuous Query Notification (CQN)
1250512522

12506-
[Continuous Query Notification (CQN)][99] lets node-oracledb
12507-
applications register a JavaScript method that is invoked when changed
12508-
data is committed to the database, regardless of the user or the
12509-
application that made the change. For example your application may be
12510-
interested in knowing if a table used for lookup data has changed so
12511-
the application can update a local cache of that table.
12523+
[Continuous Query Notification (CQN)][99] lets node-oracledb applications
12524+
subscribe to receive notification when changed data is committed to the
12525+
database, regardless of the user or the application that made the change. For
12526+
example your application may be interested in knowing if a table used for lookup
12527+
data has changed so that the application can update a local cache of that table.
12528+
CQN can invoke a JavaScript method, which can perform the action.
1251212529

1251312530
CQN is suitable for infrequently modified tables. It is recommended
1251412531
to avoid frequent subscription and unsubscription.
1251512532

12516-
The connection must be created with [`events`](#propdbevents) mode
12517-
*true*.
12518-
12519-
The database must be able to connect to the node-oracledb machine for
12520-
notifications to be received. Typically this means that the machine
12521-
running node-oracledb needs a fixed IP address. Note
12522-
`connection.subscribe()` does not verify that this reverse connection
12523-
is possible. If there is any problem sending a notification, then the
12524-
callback method will not be invoked. The configuration options can
12525-
include an [`ipAddress`](#consubscribeoptipaddress) and
12526-
[`port`](#consubscribeoptport) on which to listen for notifications,
12527-
otherwise the database chooses values.
12528-
12529-
To register interest in database changes, the
12530-
[`connection.subscribe()`](#consubscribe) method is passed an
12531-
arbitrary name and an [`options`](#consubscribeoptions) object that
12532-
controls notification. In particular `options` contains a valid SQL
12533-
query and a JavaScript callback:
12533+
By default, CQN requires the database to be able to connect back to the
12534+
node-oracledb application for notifications to be received. This typically
12535+
means that the machine running node-oracledb needs a fixed IP address. Note
12536+
`connection.subscribe()` does not verify that this reverse connection is
12537+
possible. If there is any problem sending a notification, then the callback
12538+
method will not be invoked. The configuration options can include an
12539+
[`ipAddress`](#consubscribeoptipaddress) and [`port`](#consubscribeoptport) on
12540+
which to listen for notifications, otherwise the database chooses values.
12541+
12542+
Alternatively, when using Oracle Database and Oracle client libraries 19.4, or
12543+
later, subscriptions can set the optional
12544+
[`clientInitiated`](#consubscribeoptclientinitiated) property to *true*. This
12545+
makes CQN internally use the same approach as normal connections to the
12546+
database, and does not require the database to be able to connect back to the
12547+
application. Since client initiated CQN notifications do not need additional
12548+
network configuration, they have ease-of-use and security advantages.
12549+
12550+
To register interest in database changes, the connection must be created with
12551+
[`events`](#propdbevents) mode *true*. Then the
12552+
[`connection.subscribe()`](#consubscribe) method is passed an arbitrary name and
12553+
an [`options`](#consubscribeoptions) object that controls notification. In
12554+
particular `options` contains a valid SQL query and a JavaScript callback:
1253412555

1253512556
```javascript
1253612557
function myCallback(message) {

src/njsConnection.c

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2773,6 +2773,7 @@ static bool njsConnection_subscribeAsync(njsBaton *baton)
27732773
params.groupingClass = (uint8_t) baton->subscrGroupingClass;
27742774
params.groupingValue = baton->subscrGroupingValue;
27752775
params.groupingType = (uint8_t) baton->subscrGroupingType;
2776+
params.clientInitiated = baton->clientInitiated;
27762777
if (dpiConn_subscribe(conn->handle, &params,
27772778
&baton->subscription->handle) < 0)
27782779
return njsBaton_setErrorDPI(baton);
@@ -2888,6 +2889,9 @@ static bool njsConnection_subscribeProcessArgs(njsBaton *baton, napi_env env,
28882889
if (!njsBaton_getUnsignedIntFromArg(baton, env, args, 1,
28892890
"groupingType", &baton->subscrGroupingType, NULL))
28902891
return false;
2892+
if (!njsBaton_getBoolFromArg(baton, env, args, 1, "clientInitiated",
2893+
&baton->clientInitiated, NULL))
2894+
return false;
28912895
if (!njsBaton_getValueFromArg(baton, env, args, 1, "callback",
28922896
napi_function, &callback, NULL))
28932897
return false;

src/njsModule.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -465,6 +465,7 @@ struct njsBaton {
465465
bool isDropped;
466466
bool replaced;
467467
bool force;
468+
bool clientInitiated;
468469

469470
// LOB buffer (requires free only if string was used)
470471
uint64_t bufferSize;

test/cqn01.js

Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
/* Copyright (c) 2019, 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+
* 224. cqn01.js
20+
*
21+
* DESCRIPTION
22+
* Test CQN client initiated connections.
23+
*
24+
*****************************************************************************/
25+
'use strict';
26+
27+
const oracledb = require('oracledb');
28+
const should = require('should');
29+
const dbconfig = require('./dbconfig.js');
30+
const testsUtil = require('./testsUtil.js');
31+
32+
describe('224. cqn01.js', function() {
33+
34+
let isRunnable = false;
35+
let conn, connAsDBA;
36+
37+
before(async function() {
38+
39+
const prep = await testsUtil.checkPrerequisites(1904000000, 1904000000);
40+
isRunnable = prep && dbconfig.test.DBA_PRIVILEGE;
41+
42+
if (!isRunnable) {
43+
this.skip();
44+
return;
45+
} else {
46+
47+
try {
48+
49+
let credential = {
50+
user: dbconfig.test.DBA_user,
51+
password: dbconfig.test.DBA_password,
52+
connectString: dbconfig.connectString,
53+
privilege: oracledb.SYSDBA
54+
};
55+
connAsDBA = await oracledb.getConnection(credential);
56+
57+
let sql = `GRANT CHANGE NOTIFICATION TO ${dbconfig.user}`;
58+
await connAsDBA.execute(sql);
59+
60+
conn = await oracledb.getConnection({
61+
...dbconfig,
62+
events: true
63+
});
64+
65+
} catch (err) {
66+
should.not.exist(err);
67+
}
68+
69+
}
70+
}); // before()
71+
72+
after(async function() {
73+
if (!isRunnable) {
74+
return;
75+
} else {
76+
try {
77+
let sql = `REVOKE CHANGE NOTIFICATION FROM ${dbconfig.user}`;
78+
await connAsDBA.execute(sql);
79+
80+
await conn.close();
81+
await connAsDBA.close();
82+
} catch (err) {
83+
should.not.exist(err);
84+
}
85+
}
86+
}); // after()
87+
88+
it('224.1', async () => {
89+
try {
90+
const TABLE = 'nodb_tab_cqn_01';
91+
let sql =
92+
`CREATE TABLE ${TABLE} (
93+
k NUMBER
94+
)`;
95+
let plsql = testsUtil.sqlCreateTable(TABLE, sql);
96+
await conn.execute(plsql);
97+
98+
const myCallback = function(message) {
99+
// should.strictEqual(message.type, oracledb.SUBSCR_EVENT_TYPE_QUERY_CHANGE);
100+
// should.strictEqual(message.registered, true);
101+
// const table = message.queries[0].tables[0];
102+
// const tableName = dbconfig.user.toUpperCase() + '.' + TABLE.toUpperCase();
103+
// should.strictEqual(table.name, tableName);
104+
// should.strictEqual(table.operation, oracledb.CQN_OPCODE_INSERT);
105+
console.log(message);
106+
};
107+
108+
const options = {
109+
callback : myCallback,
110+
sql: `SELECT * FROM ${TABLE} WHERE k > :bv`,
111+
binds: { bv : 100 },
112+
timeout : 20,
113+
qos : oracledb.SUBSCR_QOS_QUERY | oracledb.SUBSCR_QOS_ROWIDS,
114+
clientInitiated: true
115+
};
116+
117+
await conn.subscribe('nodb_sub_01', options);
118+
119+
sql = `INSERT INTO ${TABLE} VALUES (101)`;
120+
await conn.execute(sql);
121+
122+
await conn.commit();
123+
124+
await conn.unsubscribe('nodb_sub_01');
125+
126+
sql = `DROP TABLE ${TABLE} PURGE`;
127+
await conn.execute(sql);
128+
} catch (err) {
129+
should.not.exist(err);
130+
}
131+
132+
}); // 224.1
133+
});

0 commit comments

Comments
 (0)