Added JsonId class to to represent JSON ID values returned by SODA in Oracle Database 23ai and higher in the _id attribute of documents stored in native SODA collections

This commit is contained in:
Sharad Chandran R 2024-05-02 20:03:06 +05:30
parent 63d8bb5a0b
commit 10c45fcb01
14 changed files with 177 additions and 4 deletions

View File

@ -10,6 +10,13 @@ For deprecated and desupported features, see :ref:`Deprecations and desupported
node-oracledb `v6.5.0 <https://github.com/oracle/node-oracledb/compare/v6.4.0...v6.5.0>`__ (TBD)
--------------------------------------------------------------------------------------------------------
Common Changes
++++++++++++++
#) Added class :ref:`oracledb.JsonId <jsonid>` to represent JSON ID values
returned by SODA in Oracle Database 23.4 and higher in the ``_id``
attribute of documents stored in native collections.
Thin Mode Changes
++++++++++++++++++

View File

@ -79,6 +79,7 @@ module.exports = {
TNS_JSON_TYPE_ARRAY: 0xc0,
TNS_JSON_TYPE_EXTENDED: 0x7b,
TNS_JSON_TYPE_VECTOR: 0x01,
TNS_JSON_TYPE_ID: 0x7e,
// timezone offsets
TZ_HOUR_OFFSET: 20,

View File

@ -143,6 +143,11 @@ class OsonDecoder extends BaseBuffer {
return this.readBytes(this.readUInt32BE()).toString();
} else if (nodeType === constants.TNS_JSON_TYPE_NUMBER_LENGTH_UINT8) {
return parseFloat(this.readOracleNumber());
} else if (nodeType === constants.TNS_JSON_TYPE_ID) {
const buf = this.readBytes(this.readUInt8());
const jsonId = new types.JsonId(buf.length);
buf.copy(jsonId);
return jsonId;
} else if (nodeType === constants.TNS_JSON_TYPE_BINARY_LENGTH_UINT16) {
return Buffer.from(this.readBytes(this.readUInt16BE()));
} else if (nodeType === constants.TNS_JSON_TYPE_BINARY_LENGTH_UINT32) {
@ -562,6 +567,11 @@ class OsonTreeSegment extends GrowableBuffer {
this.writeUInt32BE(buf.length);
this.writeBytes(buf);
} else if (value instanceof types.JsonId) {
this.writeUInt8(constants.TNS_JSON_TYPE_ID);
this.writeUInt8(value.length);
this.writeBytes(Buffer.from(value.buffer));
// handle objects
} else {
this._encodeObject(value, fnamesSeg);

View File

@ -832,6 +832,7 @@ module.exports = {
AqQueue,
BaseDbObject,
Connection,
JsonId: types.JsonId,
Lob,
Pool,
PoolStatistics,

View File

@ -63,6 +63,7 @@ class Settings {
this.thinDriverInitialized = false;
this.createFetchTypeMap(this.fetchAsString, this.fetchAsBuffer);
this.fetchTypeHandler = undefined;
this._JsonId = types.JsonId;
}
//---------------------------------------------------------------------------

View File

@ -105,6 +105,12 @@ function transformJsonValue(value) {
if (value instanceof BaseDbObject)
return {fields: [], values: []};
// JsonId is a special type to represent autogenerated id
// for SODA documents.
if (value instanceof types.JsonId) {
return value;
}
// all other objects are transformed to an object with two arrays (fields
// and values)
const outValue = {};

View File

@ -332,6 +332,13 @@ dbTypeByColumnTypeName.set("SMALLINT", DB_TYPE_NUMBER);
dbTypeByColumnTypeName.set("TIMESTAMP WITH LOCAL TZ", DB_TYPE_TIMESTAMP_LTZ);
dbTypeByColumnTypeName.set("TIMESTAMP WITH TZ", DB_TYPE_TIMESTAMP_TZ);
// It abstracts the autogenerated SODA Document key.
class JsonId extends Uint8Array {
toJSON() {
return (Buffer.from(this.buffer).toString('hex'));
}
}
module.exports = {
DbType,
DB_TYPE_BFILE,
@ -368,5 +375,6 @@ module.exports = {
DB_TYPE_XMLTYPE,
getTypeByColumnTypeName,
getTypeByNum,
getTypeByOraTypeNum
getTypeByOraTypeNum,
JsonId
};

View File

@ -426,9 +426,11 @@ bool njsBaton_getJsonNodeValue(njsBaton *baton, dpiJsonNode *node,
napi_value key, temp;
dpiJsonArray *array;
dpiJsonObject *obj;
void *destData = NULL;
size_t byteLength = 0;
double temp_double;
uint32_t i;
napi_value global, vectorBytes;
napi_value global, vectorBytes, arrBuf;
// null is a special case
if (node->nativeTypeNum == DPI_NATIVE_TYPE_NULL) {
@ -493,6 +495,15 @@ bool njsBaton_getJsonNodeValue(njsBaton *baton, dpiJsonNode *node,
NJS_CHECK_NAPI(env, napi_call_function(env, global,
baton->jsDecodeVectorFn, 1, &vectorBytes, value))
return true;
case DPI_ORACLE_TYPE_JSON_ID:
byteLength = node->value->asBytes.length;
NJS_CHECK_NAPI(env, napi_create_arraybuffer(env,
byteLength, &destData, &arrBuf))
memcpy(destData, node->value->asBytes.ptr, byteLength);
NJS_CHECK_NAPI(env, napi_new_instance(env,
baton->jsJsonIdConstructor, 1, &arrBuf, value))
return true;
default:
break;
}
@ -807,6 +818,10 @@ bool njsBaton_setJsValues(njsBaton *baton, napi_env env)
NJS_CHECK_NAPI(env, napi_get_reference_value(env,
baton->globals->jsEncodeVectorFn, &baton->jsEncodeVectorFn))
// acquire the JsonId constructor
NJS_CHECK_NAPI(env, napi_get_reference_value(env,
baton->globals->jsJsonIdConstructor, &baton->jsJsonIdConstructor))
return true;
}

View File

@ -243,9 +243,16 @@ static bool njsJsonBuffer_populateNode(njsJsonBuffer *buf, dpiJsonNode *node,
// handle buffers
NJS_CHECK_NAPI(env, napi_is_buffer(env, value, &check))
if (check) {
NJS_CHECK_NAPI(env, napi_instanceof(env, value,
baton->jsJsonIdConstructor, &check))
NJS_CHECK_NAPI(env, napi_get_buffer_info(env, value,
(void**) &tempBuffer, &tempBufferLength))
node->oracleTypeNum = DPI_ORACLE_TYPE_RAW;
if (check) {
// Handle JsonId
node->oracleTypeNum = DPI_ORACLE_TYPE_JSON_ID;
} else {
node->oracleTypeNum = DPI_ORACLE_TYPE_RAW;
}
node->nativeTypeNum = DPI_NATIVE_TYPE_BYTES;
node->value->asBytes.ptr = tempBuffer;
node->value->asBytes.length = (uint32_t) tempBufferLength;

View File

@ -126,6 +126,7 @@ static void njsModule_finalizeGlobals(napi_env env, void *finalize_data,
NJS_DELETE_REF_AND_CLEAR(globals->jsMakeDateFn);
NJS_DELETE_REF_AND_CLEAR(globals->jsDecodeVectorFn);
NJS_DELETE_REF_AND_CLEAR(globals->jsEncodeVectorFn);
NJS_DELETE_REF_AND_CLEAR(globals->jsJsonIdConstructor);
free(globals);
}
@ -189,6 +190,12 @@ static bool njsModule_populateGlobals(napi_env env, napi_value module,
&globals->jsSodaOperationConstructor))
return false;
// get the JsonId class
NJS_CHECK_NAPI(env, napi_get_named_property(env, settings, "_JsonId",
&temp))
NJS_CHECK_NAPI(env, napi_create_reference(env, temp, 1,
&globals->jsJsonIdConstructor))
// store a reference to the _makeDate() function
NJS_CHECK_NAPI(env, napi_get_named_property(env, settings,
"_getDateComponents", &temp))
@ -267,6 +274,7 @@ static bool njsModule_initDPI(napi_env env, napi_value *args,
// initialize structure
memset(&params, 0, sizeof(params));
params.useJsonId = 1; // Enable JSONID
if (*libDirLength > 0)
params.oracleClientLibDir = *libDir;
if (*configDirLength > 0)

View File

@ -469,6 +469,7 @@ struct njsBaton {
napi_value jsMakeDateFn;
napi_value jsDecodeVectorFn;
napi_value jsEncodeVectorFn;
napi_value jsJsonIdConstructor;
// calling object value (used for setting a reference on created objects)
napi_value jsCallingObj;
@ -556,6 +557,7 @@ struct njsModuleGlobals {
napi_ref jsMakeDateFn;
napi_ref jsDecodeVectorFn;
napi_ref jsEncodeVectorFn;
napi_ref jsJsonIdConstructor;
};
// data for class Pool exposed to JS.

View File

@ -724,4 +724,109 @@ describe('244.dataTypeJson.js', function() {
}); // 244.9
describe('244.10 Verify auto-generated SODA document key', function() {
const TABLE = 'nodb_244_63soda';
let supportsJsonId;
before('create table, insert data', async function() {
supportsJsonId = (testsUtil.getClientVersion() >= 2304000000) &&
(connection.oracleServerVersion >= 2304000000);
if (!supportsJsonId) {
this.skip();
}
const sql = `CREATE JSON COLLECTION TABLE if not exists ${TABLE}`;
await connection.execute(sql);
}); // before()
after(async function() {
if (!supportsJsonId) {
return;
}
const sql = `DROP TABLE if exists ${TABLE}`;
await connection.execute(sql);
}); // after()
it('244.10.1 Verify Json Id on select', async function() {
const inpDoc = {"name": "Jenny"};
let sql = ` insert into ${TABLE} values (:1)`;
let result = await connection.execute(sql, [{
type: oracledb.DB_TYPE_JSON,
val: inpDoc
}]);
// Verify _id is generated.
sql = `select * from ${TABLE}`;
result = await connection.execute(sql);
let genDoc = result.rows[0][0];
assert(("_id" in genDoc));
const autogenID = genDoc._id;
// Verify update with new values without passing _id.
inpDoc.name = "Scott";
sql = ` update ${TABLE} set DATA = :1`;
result = await connection.execute(sql, [{
type: oracledb.DB_TYPE_JSON,
val: inpDoc
}]);
sql = `select * from ${TABLE}`;
result = await connection.execute(sql);
genDoc = result.rows[0][0];
const updatedID = genDoc._id;
assert.deepStrictEqual(updatedID, autogenID);
assert.strictEqual(inpDoc.name, genDoc.name);
// Verify update with new values with passing _id from the generated Doc.
genDoc.name = "John";
sql = ` update ${TABLE} set DATA = :1`;
result = await connection.execute(sql, [{
type: oracledb.DB_TYPE_JSON,
val: genDoc
}]);
sql = `select * from ${TABLE}`;
result = await connection.execute(sql);
const updatedDoc = result.rows[0][0];
assert.deepStrictEqual(updatedDoc, genDoc);
const expectedJsonData = genDoc;
expectedJsonData._id = Buffer.from(autogenID).toString('hex');
assert.deepStrictEqual(JSON.stringify(expectedJsonData),
JSON.stringify(updatedDoc));
// Insert Document with Previously generated JsonId type.
const jsonId = new oracledb.JsonId(genDoc._id);
const inpDocWithJsonIdKey = {"_id": jsonId, "name": "Bob"};
sql = ` insert into ${TABLE} values (:1)`;
result = await connection.execute(sql, [{
type: oracledb.DB_TYPE_JSON,
val: inpDocWithJsonIdKey
}]);
sql = `select * from ${TABLE}`;
result = await connection.execute(sql);
genDoc = result.rows[1][0];
assert.deepStrictEqual(genDoc, inpDocWithJsonIdKey);
// overwrite the auto-generated _id with user key should fail.
genDoc._id = "RandomId";
sql = ` update ${TABLE} set DATA = :1`;
await assert.rejects(
async () => await connection.execute(sql, [{
type: oracledb.DB_TYPE_JSON,
val: genDoc
}]),
/ORA-54059:/ // cannot update an immutable column to a different value
);
// User provided keys should still work.
const inpDocWithUserKey = {"_id": 1, "name": "Jenny"};
sql = ` insert into ${TABLE} values (:1)`;
result = await connection.execute(sql, [{
type: oracledb.DB_TYPE_JSON,
val: inpDocWithUserKey
}]);
sql = `select * from ${TABLE}`;
result = await connection.execute(sql);
genDoc = result.rows[2][0];
assert.deepStrictEqual(genDoc, inpDocWithUserKey);
});
});
});

View File

@ -4907,6 +4907,8 @@ oracledb.OUT_FORMAT_OBJECT and resultSet = true
244.9.1 works with oracledb.fetchAsString
244.9.2 doesn't work with outFormat: oracledb.DB_TYPE_JSON
244.9.3 could work with fetchInfo oracledb.STRING
244.10 Verify auto-generated SODA document key
244.10.1 Verify Json Id on select
245. fetchLobAsStrBuf.js
245.1 CLOB,BLOB Insert

View File

@ -500,7 +500,7 @@ testsUtil.isDate = function(date) {
// return a fake version for thin to facilitate client version checks
testsUtil.getClientVersion = function() {
if (oracledb.thin)
return 2302000000;
return 2304000000;
return oracledb.oracleClientVersion;
};