/* Copyright (c) 2021, Oracle and/or its affiliates. All rights reserved. */ /****************************************************************************** * * You may not use the identified files except in compliance with the Apache * License, Version 2.0 (the "License.") * * You may obtain a copy of the License at * http://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. * * NAME * 185. runCQN.js * * DESCRIPTION * Test Continuous Query Notification (CQN). * * To keep it simple, this test will not run on my macOS. Because the database * must be able to connect to the node-oracledb machine for notifications * to be received. Typically this means that the machine running node-oracledb * needs a fixed IP address. * * Due to the limitation of Mocha, it could not catch the stacked callback * errors. We may see errors as outputs. * *****************************************************************************/ 'use strict'; const oracledb = require('oracledb'); const should = require('should'); const assert = require('assert'); const dbconfig = require('./dbconfig.js'); const testsUtil = require('./testsUtil.js'); describe('185. runCQN.js', function() { let isRunnable = true; let conn, connAsDBA; before(async function() { if ((!dbconfig.test.DBA_PRIVILEGE) || (process.platform == 'darwin')) { isRunnable = false; } if (!isRunnable) { this.skip(); return; } else { try { let credential = { user: dbconfig.test.DBA_user, password: dbconfig.test.DBA_password, connectString: dbconfig.connectString, privilege: oracledb.SYSDBA }; connAsDBA = await oracledb.getConnection(credential); let sql = `GRANT CHANGE NOTIFICATION TO ${dbconfig.user}`; await connAsDBA.execute(sql); conn = await oracledb.getConnection({ ...dbconfig, events: true }); } catch (err) { should.not.exist(err); } } }); // before() after(async function() { if (!isRunnable) { return; } else { try { let sql = `REVOKE CHANGE NOTIFICATION FROM ${dbconfig.user}`; await connAsDBA.execute(sql); await conn.close(); await connAsDBA.close(); } catch (err) { should.not.exist(err); } } }); // after() it('185.1 examples/cqn1.js', async () => { try { const TABLE = 'nodb_tab_cqn_1'; let sql = `CREATE TABLE ${TABLE} ( k NUMBER )`; let plsql = testsUtil.sqlCreateTable(TABLE, sql); await conn.execute(plsql); const myCallback = function(message) { should.strictEqual(message.type, oracledb.SUBSCR_EVENT_TYPE_QUERY_CHANGE); should.strictEqual(message.registered, true); const table = message.queries[0].tables[0]; const tableName = dbconfig.user.toUpperCase() + '.' + TABLE.toUpperCase(); should.strictEqual(table.name, tableName); should.strictEqual(table.operation, oracledb.CQN_OPCODE_INSERT); }; const options = { callback : myCallback, sql: `SELECT * FROM ${TABLE} WHERE k > :bv`, binds: { bv : 100 }, timeout : 20, qos : oracledb.SUBSCR_QOS_QUERY | oracledb.SUBSCR_QOS_ROWIDS }; await conn.subscribe('sub1', options); sql = `INSERT INTO ${TABLE} VALUES (101)`; await conn.execute(sql); await conn.commit(); await conn.unsubscribe('sub1'); sql = `DROP TABLE ${TABLE} PURGE`; await conn.execute(sql); } catch (err) { should.not.exist(err); } }); // 185.1 it('185.2 SQL Delete operation', async () => { try { const TABLE = 'nodb_tab_cqn_2'; let sql = `CREATE TABLE ${TABLE} ( k NUMBER )`; let plsql = testsUtil.sqlCreateTable(TABLE, sql); await conn.execute(plsql); const myCallback = function(message) { should.strictEqual(message.type, oracledb.SUBSCR_EVENT_TYPE_QUERY_CHANGE); should.strictEqual(message.registered, true); const table = message.queries[0].tables[0]; const tableName = dbconfig.user.toUpperCase() + '.' + TABLE.toUpperCase(); should.strictEqual(table.name, tableName); let expect = oracledb.CQN_OPCODE_INSERT | oracledb.CQN_OPCODE_DELETE | oracledb.CQN_OPCODE_ALL_ROWS; should.strictEqual(expect, table.operation); }; const options = { callback : myCallback, sql: `SELECT * FROM ${TABLE}`, timeout : 20, qos : oracledb.SUBSCR_QOS_QUERY }; await conn.subscribe('sub2', options); sql = `INSERT INTO ${TABLE} VALUES (99)`; await conn.execute(sql); sql = `INSERT INTO ${TABLE} VALUES (102)`; await conn.execute(sql); sql = `DELETE FROM ${TABLE} WHERE k > :bv`; await conn.execute(sql, { bv : 100 }); await conn.commit(); await conn.unsubscribe('sub2'); sql = `DROP TABLE ${TABLE} PURGE`; await conn.execute(sql); } catch (err) { should.not.exist(err); } }); // 185.2 it('185.3 Specify the notification only for INSERT operation', async () => { try { const TABLE = 'nodb_tab_cqn_3'; let sql = `CREATE TABLE ${TABLE} ( k NUMBER )`; let plsql = testsUtil.sqlCreateTable(TABLE, sql); await conn.execute(plsql); const myCallback = function(message) { should.strictEqual(message.type, oracledb.SUBSCR_EVENT_TYPE_QUERY_CHANGE); should.strictEqual(message.registered, true); const table = message.queries[0].tables[0]; const tableName = dbconfig.user.toUpperCase() + '.' + TABLE.toUpperCase(); should.strictEqual(table.name, tableName); let expect = oracledb.CQN_OPCODE_INSERT | oracledb.CQN_OPCODE_ALL_ROWS; should.strictEqual(table.operation, expect); }; const options = { callback : myCallback, sql: `SELECT * FROM ${TABLE}`, timeout : 20, qos : oracledb.SUBSCR_QOS_QUERY, operations: oracledb.CQN_OPCODE_INSERT }; await conn.subscribe('sub3', options); sql = `DELETE FROM ${TABLE} WHERE k > :bv`; await conn.execute(sql, { bv : 100 }); sql = `INSERT INTO ${TABLE} VALUES (103)`; await conn.execute(sql); await conn.commit(); await conn.unsubscribe('sub3'); sql = `DROP TABLE ${TABLE} PURGE`; await conn.execute(sql); } catch (err) { should.not.exist(err); } }); // 185.3 it('185.4 Negative - provide invalid SQL in CQN option', async () => { try { const TABLE = 'nodb_tab_cqn_4'; const myCallback = function(message) { should.exist(message); }; const options = { callback : myCallback, sql: `DELETE FROM ${TABLE} WHERE k > :bv`, binds: { bv : 100 }, timeout : 20, qos : oracledb.SUBSCR_QOS_QUERY }; await assert.rejects( async () => { await conn.subscribe('sub4', options); }, /DPI-1013/ ); // DPI-1013: not supported } catch (err) { should.not.exist(err); } }); // 185.4 it('185.5 examples/cqn2.js', async () => { try { const TABLE = 'nodb_tab_cqn_5'; let sql = `CREATE TABLE ${TABLE} ( k NUMBER )`; let plsql = testsUtil.sqlCreateTable(TABLE, sql); await conn.execute(plsql); const myCallback = function(message) { should.strictEqual(message.type, oracledb.SUBSCR_EVENT_TYPE_OBJ_CHANGE); should.strictEqual(message.registered, true); }; const options = { callback : myCallback, sql: `SELECT * FROM ${TABLE}`, timeout : 60, qos : oracledb.SUBSCR_QOS_ROWIDS, // Group notifications in batches covering 1 second // intervals, and send a summary groupingClass : oracledb.SUBSCR_GROUPING_CLASS_TIME, groupingValue : 1, groupingType : oracledb.SUBSCR_GROUPING_TYPE_SUMMARY }; await conn.subscribe('sub5', options); sql = `INSERT INTO ${TABLE} VALUES (:1)`; let bindArr = [ [1], [2], [3], [4], [5], [6], [7] ]; for (let i = 0; i < bindArr.length; i++) { await conn.execute(sql, bindArr[i], { autoCommit: true }); } await conn.commit(); await conn.unsubscribe('sub5'); sql = `DROP TABLE ${TABLE} PURGE`; await conn.execute(sql); } catch (err) { should.not.exist(err); } }); // 185.5 it('185.6 Get the registration ID "regId" for subscriptions', async () => { try { const TABLE = 'nodb_tab_cqn_6'; let sql = `CREATE TABLE ${TABLE} ( k NUMBER )`; let plsql = testsUtil.sqlCreateTable(TABLE, sql); await conn.execute(plsql); const myCallback = function(message) { should.strictEqual(message.registered, true); }; const options = { callback : myCallback, sql: `SELECT * FROM ${TABLE} WHERE k > :bv`, binds: { bv : 100 }, timeout : 20, qos : oracledb.SUBSCR_QOS_QUERY | oracledb.SUBSCR_QOS_ROWIDS }; await conn.commit(); const result = await conn.subscribe('sub6', options); (result.regId).should.be.a.Number(); const tableName = dbconfig.user.toUpperCase() + '.' + TABLE.toUpperCase(); sql = `SELECT regid FROM USER_CHANGE_NOTIFICATION_REGS WHERE table_name = '${tableName}'`; const res = await conn.execute(sql, [], { outFormat: oracledb.OUT_FORMAT_OBJECT }); should.strictEqual(result.regId, res.rows[0].REGID); sql = `INSERT INTO ${TABLE} VALUES (101)`; await conn.execute(sql); await conn.commit(); await conn.unsubscribe('sub6'); sql = `DROP TABLE ${TABLE} PURGE`; await conn.execute(sql); } catch (err) { should.not.exist(err); } }); // 185.6 it('185.7 Negative - unsubscribe multiple times', async () => { try { const TABLE = 'nodb_tab_cqn_7'; let sql = `CREATE TABLE ${TABLE} ( k NUMBER )`; let plsql = testsUtil.sqlCreateTable(TABLE, sql); await conn.execute(plsql); const myCallback = function(message) { should.strictEqual(message.registered, true); }; const options = { callback : myCallback, sql: `SELECT * FROM ${TABLE} WHERE k > :bv`, binds: { bv : 100 }, timeout : 20, qos : oracledb.SUBSCR_QOS_QUERY | oracledb.SUBSCR_QOS_ROWIDS }; await conn.subscribe('sub7', options); sql = `INSERT INTO ${TABLE} VALUES (101)`; await conn.execute(sql); await conn.commit(); await conn.unsubscribe('sub7'); sql = `DROP TABLE ${TABLE} PURGE`; await conn.execute(sql); await assert.rejects( async () => { await conn.unsubscribe('sub7'); }, /NJS-061/ ); // NJS-061: invalid subscription } catch (err) { should.not.exist(err); } }); // 185.7 it('185.8 Negative - unsubscribe nonexistent subscriptions', async () => { try { await assert.rejects( async () => { await conn.unsubscribe('nonexist'); }, /NJS-061/ ); // NJS-061: invalid subscription } catch (err) { should.not.exist(err); } }); // 185.8 // A variation of 185.4 it('185.9 Negative - unsubscribe the invalid subscription', async () => { try { const TABLE = 'nodb_tab_cqn_9'; const myCallback = function(message) { should.strictEqual(message.registered, true); }; const options = { callback : myCallback, sql: `DELETE FROM ${TABLE} WHERE k > :bv`, binds: { bv : 100 }, timeout : 20, qos : oracledb.SUBSCR_QOS_QUERY }; await assert.rejects( async () => { await conn.subscribe('sub9', options); }, /DPI-1013/ ); // DPI-1013: not supported await conn.unsubscribe('sub9'); } catch (err) { should.not.exist(err); } }); // 185.9 });