Create a flag oldJsonColumnAsObj inside a new future object for ensuring backward compatibility before converting the IS JSON type column values to JSON object

This commit is contained in:
Sharad Chandran R 2023-12-05 16:03:57 +05:30
parent 0c129d3eec
commit 8a2973adb0
8 changed files with 82 additions and 56 deletions

View File

@ -13,6 +13,16 @@ node-oracledb `v6.3.0 <https://github.com/oracle/node-oracledb/compare/v6.2.0...
Common Changes
++++++++++++++
#) VARCHAR2 and LOB columns which contain JSON, and have the "IS JSON" check
constraint enabled, can now be fetched in the same way as columns of type
JSON. In node-oracledb :ref:`Thick mode <enablingthick>` this requires
Oracle Client 19 or higher. Applications can get this new fetch behaviour
by setting the new oracledb property
:attr:`oracledb.future.oldJsonColumnAsObj` to `true`. The default value
for this property is `false` which retains the existing fetch behaviour.
In a future version, the new fetch behaviour will become default and
setting this property will no longer be needed.
#) Added constant ``oracledb.DB_TYPE_XMLTYPE`` to represent data of type
``SYS.XMLTYPE`` in metadata ``fetchType`` and ``dbType`` attributes.
Previously the constant used was ``oracledb.DB_TYPE_LONG`` in Thick mode.
@ -21,13 +31,6 @@ Common Changes
Software Development Kits (SDKs) to generate
:ref:`authentication tokens <tokenbasedauthentication>`.
#) VARCHAR2 and LOB columns which contain JSON, and have the "IS JSON" check
constraint enabled, are now fetched in the same way as columns of type
JSON. In node-oracledb :ref:`Thick mode <enablingthick>` this requires
Oracle Client 19 or higher. Applications can get the old behaviour by
using :attr:`oracledb.fetchTypeHandler` to replace the new default
conversion.
#) Added new connection properties :attr:`connection.dbDomain`,
:attr:`connection.dbName`, :attr:`connection.maxOpenCursors`,
:attr:`connection.serviceName` and :attr:`connection.transactionInProgress`

53
lib/future.js Normal file
View File

@ -0,0 +1,53 @@
// Copyright (c) 2023, Oracle and/or its affiliates.
//-----------------------------------------------------------------------------
//
// This software is dual-licensed to you under the Universal Permissive License
// (UPL) 1.0 as shown at https://oss.oracle.com/licenses/upl and Apache License
// 2.0 as shown at http://www.apache.org/licenses/LICENSE-2.0. You may choose
// either license.
//
// If you elect to accept the software under the Apache License, Version 2.0,
// the following applies:
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://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.
//
//-----------------------------------------------------------------------------
'use strict';
const errors = require('./errors.js');
// future object used for managing backwards incompatible changes.
class Future {
constructor() {
this._featureFlags = {};
this._featureFlags.oldJsonColumnAsObj = false;
}
get oldJsonColumnAsObj() {
return this._featureFlags.oldJsonColumnAsObj;
}
// fetch VARCHAR2 and LOB columns that contain JSON data (and have
// the "IS JSON" constraint enabled) in the same way that columns
// of type JSON (which requires Oracle Database 21 and higher) are fetched.
set oldJsonColumnAsObj(value) {
errors.assertPropValue(typeof value === 'boolean', "oldJsonColumnAsObj");
this._featureFlags.oldJsonColumnAsObj = value;
}
}
module.exports = new Future;

View File

@ -30,6 +30,7 @@ const constants = require('../constants.js');
const errors = require('../errors.js');
const nodbUtil = require('../util.js');
const settings = require('../settings.js');
const future = require('../future.js');
const types = require('../types.js');
const Lob = require('../lob.js');
@ -106,7 +107,7 @@ class ResultSetImpl {
// If IsJson is set convert to JSON objects unless
// user defined output type handler overwrites it.
if (metadata.isJson && metadata.dbType !== types.DB_TYPE_JSON
if (future.oldJsonColumnAsObj && metadata.isJson && metadata.dbType !== types.DB_TYPE_JSON
&& userConverter === undefined) {
const outConverter = async function(val) {
if (!val) {

View File

@ -48,6 +48,7 @@ const AqDeqOptions = require('./aqDeqOptions.js');
const AqEnqOptions = require('./aqEnqOptions.js');
const AqMessage = require('./aqMessage.js');
const AqQueue = require('./aqQueue.js');
const future = require('./future.js');
const BaseDbObject = require('./dbObject.js');
const Connection = require('./connection.js');
const Lob = require('./lob.js');
@ -1013,6 +1014,9 @@ module.exports = {
ARRAY: constants.OUT_FORMAT_ARRAY,
OBJECT: constants.OUT_FORMAT_OBJECT,
// Instances
future,
// property getters
get autoCommit() {
return settings.autoCommit;

View File

@ -199,7 +199,6 @@ describe('3. examples.js', function() {
let conn = null;
const testData = { "userId": 1, "userName": "Chris", "location": "Australia" };
let featureAvailable = true;
const defaultFetchTypeHandler = oracledb.fetchTypeHandler;
before(async function() {
conn = await oracledb.getConnection(dbConfig);
@ -228,17 +227,6 @@ describe('3. examples.js', function() {
sql = "INSERT INTO nodb_purchaseorder (po_document) VALUES (:bv)";
const result = await conn.execute(sql, [s]);
assert.strictEqual(result.rowsAffected, 1);
if (await testsUtil.isJsonMetaDataRunnable()) {
oracledb.fetchTypeHandler = function(metaData) {
// overwrite default converter for VARCHAR2 type.
if (metaData.isJson && metaData.dbType === oracledb.DB_TYPE_VARCHAR) {
const myConverter = (v) => {
return v;
};
return {converter: myConverter};
}
};
}
}); // before
after(async function() {
@ -247,7 +235,6 @@ describe('3. examples.js', function() {
await conn.execute("DROP TABLE nodb_purchaseorder PURGE");
}
await conn.close();
oracledb.fetchTypeHandler = defaultFetchTypeHandler;
}
}); // after

View File

@ -504,8 +504,8 @@ describe('271. fetchTypeHandler.js', function() {
const clobVal = '[-1, 2, 3]';
const blobVal = Buffer.from('{ "KeyA": 8, "KeyB": "A String" }');
const charVal = '[-2, 2, 3, [34, 23, 24]]';
const defaultFetchTypeHandler = oracledb.fetchTypeHandler;
oracledb.future.oldJsonColumnAsObj = true;
await connection.execute(plsql);
const sql = `INSERT into ${TABLE} VALUES (:1, :2, :3)`;
await connection.execute(sql, [clobVal, blobVal, charVal]);
@ -515,10 +515,12 @@ describe('271. fetchTypeHandler.js', function() {
assert.deepStrictEqual(result.rows[0][1], JSON.parse(blobVal));
assert.deepStrictEqual(result.rows[0][2], JSON.parse(charVal));
// User defined handler can overwrite the default behaviour of
// converting IS JSON columns to JSON objects.
// fetchtype handlers given preference than oldJsonColumnAsObj setting.
const defaultFetchTypeHandler = oracledb.fetchTypeHandler;
// register typehandler only for BLOB.
// For other columns, they are still returned as JSON object.
oracledb.fetchTypeHandler = function(metaData) {
// overwrite only for BLOB type to return LOB object.
if (metaData.isJson && metaData.dbType === oracledb.DB_TYPE_BLOB) {
const myConverter = (v) => {
return v;
@ -526,16 +528,16 @@ describe('271. fetchTypeHandler.js', function() {
return { converter: myConverter };
}
};
// mark fetch type for CLOB column as string but it will be
// converted to JSON object unless handler for CLOB is written.
result = await connection.execute(`select cdata, bdata, chardata from ${TABLE}`, {}, {
fetchInfo: { CDATA: { type: oracledb.STRING } }
});
oracledb.fetchTypeHandler = defaultFetchTypeHandler;
result = await connection.execute(`select * from ${TABLE}`);
assert.deepStrictEqual(result.rows[0][0], JSON.parse(clobVal));
assert(result.rows[0][1] instanceof oracledb.Lob);
const blobData = await result.rows[0][1].getData();
assert.deepStrictEqual(blobData, blobVal);
assert.deepStrictEqual(result.rows[0][2], JSON.parse(charVal));
// restore the global settings.
oracledb.future.oldJsonColumnAsObj = false;
oracledb.fetchTypeHandler = defaultFetchTypeHandler;
await connection.execute(testsUtil.sqlDropTable(TABLE));
});

View File

@ -65,7 +65,6 @@ describe('253. jsonBind1.js', function() {
" p_inout := p_inout; \n" +
"END;";
let skip = false;
const defaultFetchTypeHandler = oracledb.fetchTypeHandler;
before (async function() {
oracledb.outFormat = oracledb.OUT_FORMAT_OBJECT;
oracledb.extendedMetaData = true;
@ -74,15 +73,6 @@ describe('253. jsonBind1.js', function() {
skip = true;
this.skip();
}
oracledb.fetchTypeHandler = function(metaData) {
// overwrite default converter for CLOB, BLOB and VARCHAR type.
if (metaData.isJson && metaData.dbType !== oracledb.DB_TYPE_JSON) {
const myConverter = (v) => {
return v;
};
return {converter: myConverter};
}
};
await conn.execute(create_table_sql);
await conn.commit();
});
@ -95,7 +85,6 @@ describe('253. jsonBind1.js', function() {
if (conn) {
await conn.close();
}
oracledb.fetchTypeHandler = defaultFetchTypeHandler;
});
beforeEach(async function() {

View File

@ -38,7 +38,6 @@ const testsUtil = require('./testsUtil.js');
describe('254. jsonBind2.js', function() {
let conn = null;
const outFormatBak = oracledb.outFormat;
const defaultFetchTypeHandler = oracledb.fetchTypeHandler;
before (async function() {
oracledb.outFormat = oracledb.OUT_FORMAT_OBJECT;
@ -47,23 +46,11 @@ describe('254. jsonBind2.js', function() {
if (conn.oracleServerVersion < 1201000200) {
this.skip();
}
if (await testsUtil.isJsonMetaDataRunnable()) {
oracledb.fetchTypeHandler = function(metaData) {
// overwrite default converter for CLOB, BLOB and VARCHAR type.
if (metaData.isJson && metaData.dbType !== oracledb.DB_TYPE_JSON) {
const myConverter = (v) => {
return v;
};
return {converter: myConverter};
}
};
}
});
after (async function() {
oracledb.outFormat = outFormatBak;
await conn.close();
oracledb.fetchTypeHandler = defaultFetchTypeHandler;
});
describe('254.1 Map javascript object into BLOB', function() {