Skip to content

Commit 183b9be

Browse files
committed
Support BigInt values for DbObject Number type attributes (Issue #1710)
1 parent a3633c6 commit 183b9be

11 files changed

+413
-33
lines changed

doc/src/release_notes.rst

+5
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,11 @@ node-oracledb `v6.8.0 <https://github.com/oracle/node-oracledb/compare/v6.7.1...
1414
Common Changes
1515
++++++++++++++
1616

17+
#) Added support for `BigInt` values to be passed to Database Objects.
18+
Exported new function `dbObjectTypeHandler` from `oracledb` which can be
19+
used to convert strings passed to the handler to `BigInt`.
20+
See `Issue #1710 <https://github.com/oracle/node-oracledb/issues/1710>`__.
21+
1722
#) Added support for Oracle Database 23ai sparse vectors.
1823

1924
#) Fixed :attr:`~dbObject.length` property for the database object

lib/connection.js

+48
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,48 @@ const oson = require('./impl/datahandlers/oson.js');
5050
// and destroyed with another!
5151
const _subscriptions = new Map();
5252

53+
// default closure for NUMBER type.
54+
const defaultNumberConverter = (v) => (v === null) ? null : parseFloat(v);
55+
56+
//---------------------------------------------------------------------------
57+
// _determineDbObjTypeConverter()
58+
//
59+
// Determines the converter associated with each DB type and its metadata.
60+
// This function is called once during metadata construction and the
61+
// converters are invoked when retriving DBObject values.
62+
//---------------------------------------------------------------------------
63+
function _determineDbObjTypeConverter(metadata, options) {
64+
// clear any previous converter functions that may have been
65+
// retained
66+
delete metadata.converter;
67+
68+
// If a DBfetch type handler is specified, update converter,
69+
// if available.
70+
if (options.dbObjectTypeHandler) {
71+
const result = options.dbObjectTypeHandler(metadata);
72+
if (result !== undefined) {
73+
errors.assert(typeof result === 'object',
74+
errors.ERR_DB_FETCH_TYPE_HANDLER_RETURN_VALUE);
75+
if (result.converter !== undefined) {
76+
errors.assert(typeof result.converter === 'function',
77+
errors.ERR_DB_FETCH_TYPE_HANDLER_CONVERTER);
78+
}
79+
if ([types.DB_TYPE_CLOB, types.DB_TYPE_NCLOB, types.DB_TYPE_BLOB,
80+
types.DB_TYPE_BFILE].includes(metadata.type)) {
81+
// converters for LOB's are not supported.
82+
return errors.throwErr(errors.ERR_NOT_IMPLEMENTED,
83+
'DbObjConverter for LOBs');
84+
}
85+
metadata.converter = result.converter;
86+
}
87+
}
88+
if (metadata.type === types.DB_TYPE_NUMBER && !metadata.converter) {
89+
// set default converter for NUMBER type as they are returned as strings
90+
// from DB.
91+
metadata.converter = defaultNumberConverter;
92+
}
93+
}
94+
5395
// define class
5496
class Connection extends EventEmitter {
5597

@@ -106,9 +148,11 @@ class Connection extends EventEmitter {
106148
const cls = this._getDbObjectClass(objType.elementTypeClass);
107149
objType.elementTypeClass = cls;
108150
}
151+
const options = {dbObjectTypeHandler: settings.dbObjectTypeHandler};
109152
if (objType.isCollection) {
110153
nodbUtil.addTypeProperties(objType, "elementType");
111154
objType.elementTypeInfo.type = objType.elementType;
155+
_determineDbObjTypeConverter(objType.elementTypeInfo, options);
112156
}
113157
if (objType.attributes) {
114158
const props = {};
@@ -126,6 +170,10 @@ class Connection extends EventEmitter {
126170
}
127171
};
128172
props[attr.name] = prop;
173+
174+
// calculate for each attribute metadata as converters might change
175+
// based on precision, scale, maxSize,..
176+
_determineDbObjTypeConverter(attr, options);
129177
}
130178
Object.defineProperties(DbObject.prototype, props);
131179
}

lib/dbObject.js

+14-9
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,7 @@ class BaseDbObject {
8686
//---------------------------------------------------------------------------
8787
_getAttrValue(attr) {
8888
const value = this._impl.getAttrValue(attr);
89-
return this._transformValueOut(value, attr.typeClass);
89+
return this._transformValueOut(value, attr.typeClass, attr);
9090
}
9191

9292
//---------------------------------------------------------------------------
@@ -138,16 +138,21 @@ class BaseDbObject {
138138
//
139139
// Transforms a value going out to the caller from the implementation.
140140
//---------------------------------------------------------------------------
141-
_transformValueOut(value, cls) {
141+
_transformValueOut(value, cls, metaData) {
142142
let outValue = value;
143143
if (value instanceof impl.LobImpl) {
144144
outValue = new Lob();
145145
outValue._setup(value, true);
146-
} else if (value instanceof impl.DbObjectImpl) {
147-
outValue = Object.create(cls.prototype);
148-
outValue._impl = value;
149-
if (outValue.isCollection) {
150-
outValue = new Proxy(outValue, BaseDbObject._collectionProxyHandler);
146+
} else {
147+
if (value instanceof impl.DbObjectImpl) {
148+
outValue = Object.create(cls.prototype);
149+
outValue._impl = value;
150+
if (outValue.isCollection) {
151+
outValue = new Proxy(outValue, BaseDbObject._collectionProxyHandler);
152+
}
153+
}
154+
if (metaData.converter) {
155+
outValue = metaData.converter(outValue);
151156
}
152157
}
153158
return outValue;
@@ -288,7 +293,7 @@ class BaseDbObject {
288293
errors.assertArgCount(arguments, 1, 1);
289294
errors.assertParamValue(Number.isInteger(index), 1);
290295
const value = this._impl.getElement(index);
291-
return this._transformValueOut(value, this.elementTypeClass);
296+
return this._transformValueOut(value, this.elementTypeClass, this._objType.elementTypeInfo);
292297
}
293298

294299
//---------------------------------------------------------------------------
@@ -352,7 +357,7 @@ class BaseDbObject {
352357
errors.assertArgCount(arguments, 0, 0);
353358
const values = this._impl.getValues();
354359
for (let i = 0; i < values.length; i++) {
355-
values[i] = this._transformValueOut(values[i], this.elementTypeClass);
360+
values[i] = this._transformValueOut(values[i], this.elementTypeClass, this._objType.elementTypeInfo);
356361
}
357362
return values;
358363
}

lib/errors.js

+8
Original file line numberDiff line numberDiff line change
@@ -171,6 +171,8 @@ const ERR_VECTOR_SPARSE_INVALID_JSON = 162;
171171
const ERR_VECTOR_SPARSE_INVALID_STRING = 163;
172172
const ERR_VECTOR_SPARSE_INVALID_INPUT = 164;
173173
const ERR_VECTOR_SPARSE_INDICES_ELEM_IS_NOT_VALID = 165;
174+
const ERR_DB_FETCH_TYPE_HANDLER_CONVERTER = 166;
175+
const ERR_DB_FETCH_TYPE_HANDLER_RETURN_VALUE = 167;
174176

175177
// Oracle Net layer errors start from 500
176178
const ERR_CONNECTION_CLOSED = 500;
@@ -494,6 +496,10 @@ messages.set(ERR_VECTOR_SPARSE_INVALID_INPUT, // NJS-164
494496
'SPARSE VECTOR Invalid Input Data');
495497
messages.set(ERR_VECTOR_SPARSE_INDICES_ELEM_IS_NOT_VALID, // NJS-165
496498
'SPARSE VECTOR indices element at index %d is not valid');
499+
messages.set(ERR_DB_FETCH_TYPE_HANDLER_CONVERTER, // NJS-166
500+
'DBFetchTypeHandler return value attribute "converter" must be a function');
501+
messages.set(ERR_DB_FETCH_TYPE_HANDLER_RETURN_VALUE, // NJS-167
502+
'DBFetchTypeHandler return value must be an object');
497503

498504
// Oracle Net layer errors
499505

@@ -963,6 +969,8 @@ module.exports = {
963969
ERR_VECTOR_SPARSE_INVALID_STRING,
964970
ERR_VECTOR_SPARSE_INVALID_INPUT,
965971
ERR_VECTOR_SPARSE_INDICES_ELEM_IS_NOT_VALID,
972+
ERR_DB_FETCH_TYPE_HANDLER_CONVERTER,
973+
ERR_DB_FETCH_TYPE_HANDLER_RETURN_VALUE,
966974
WRN_COMPILATION_CREATE,
967975
assert,
968976
assertArgCount,

lib/oracledb.js

+11
Original file line numberDiff line numberDiff line change
@@ -1289,6 +1289,10 @@ module.exports = {
12891289
return settings.fetchTypeHandler;
12901290
},
12911291

1292+
get dbObjectTypeHandler() {
1293+
return settings.dbObjectTypeHandler;
1294+
},
1295+
12921296
get lobPrefetchSize() {
12931297
return settings.lobPrefetchSize;
12941298
},
@@ -1434,6 +1438,13 @@ module.exports = {
14341438
settings.fetchTypeHandler = value;
14351439
},
14361440

1441+
set dbObjectTypeHandler(value) {
1442+
if (value !== undefined) {
1443+
errors.assertPropValue(typeof value === 'function', "dbObjectTypeHandler");
1444+
}
1445+
settings.dbObjectTypeHandler = value;
1446+
},
1447+
14371448
set lobPrefetchSize(value) {
14381449
errors.assertPropValue(Number.isInteger(value) && value >= 0,
14391450
"lobPrefetchSize");

lib/settings.js

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// Copyright (c) 2022, 2024, Oracle and/or its affiliates.
1+
// Copyright (c) 2022, 2025, Oracle and/or its affiliates.
22

33
//-----------------------------------------------------------------------------
44
//
@@ -64,6 +64,7 @@ class Settings {
6464
this.thinDriverInitialized = false;
6565
this.createFetchTypeMap(this.fetchAsString, this.fetchAsBuffer);
6666
this.fetchTypeHandler = undefined;
67+
this.dbObjectTypeHandler = undefined;
6768
this._JsonId = types.JsonId;
6869
this._SparseVector = types.SparseVector;
6970
}

lib/thin/dbObject.js

+1-4
Original file line numberDiff line numberDiff line change
@@ -368,10 +368,7 @@ class ThinDbObjectImpl extends DbObjectImpl {
368368
let isNull, obj, value;
369369
switch (type) {
370370
case types.DB_TYPE_NUMBER:
371-
value = buf.readOracleNumber();
372-
if (value !== null)
373-
value = parseFloat(value);
374-
return value;
371+
return buf.readOracleNumber();
375372
case types.DB_TYPE_BINARY_INTEGER:
376373
return buf.readBinaryInteger();
377374
case types.DB_TYPE_VARCHAR:

src/njsDbObject.c

+40-12
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// Copyright (c) 2019, 2023, Oracle and/or its affiliates.
1+
// Copyright (c) 2019, 2025, Oracle and/or its affiliates.
22

33
//-----------------------------------------------------------------------------
44
//
@@ -204,8 +204,14 @@ NJS_NAPI_METHOD_IMPL_SYNC(njsDbObject_getAttrValue, 1, &njsClassDefDbObject)
204204
njsDbObject *obj = (njsDbObject*) callingInstance;
205205
njsDbObjectAttr *attr;
206206
dpiData data;
207+
char numberAsString[200];
207208

208209
NJS_CHECK_NAPI(env, napi_unwrap(env, args[0], (void**) &attr))
210+
if (attr->typeInfo.oracleTypeNum == DPI_ORACLE_TYPE_NUMBER) {
211+
data.value.asBytes.ptr = numberAsString;
212+
data.value.asBytes.length = sizeof(numberAsString);
213+
data.value.asBytes.encoding = NULL;
214+
}
209215
if (dpiObject_getAttributeValue(obj->handle, attr->handle,
210216
attr->typeInfo.nativeTypeNum, &data) < 0)
211217
return njsUtils_throwErrorDPI(env, globals);
@@ -223,8 +229,14 @@ NJS_NAPI_METHOD_IMPL_SYNC(njsDbObject_getElement, 1, &njsClassDefDbObject)
223229
njsDbObject *obj = (njsDbObject*) callingInstance;
224230
int32_t index;
225231
dpiData data;
232+
char numberAsString[200];
226233

227234
NJS_CHECK_NAPI(env, napi_get_value_int32(env, args[0], &index))
235+
if (obj->type->elementTypeInfo.oracleTypeNum == DPI_ORACLE_TYPE_NUMBER) {
236+
data.value.asBytes.ptr = numberAsString;
237+
data.value.asBytes.length = sizeof(numberAsString);
238+
data.value.asBytes.encoding = NULL;
239+
}
228240
if (dpiObject_getElementValueByIndex(obj->handle, index,
229241
obj->type->elementTypeInfo.nativeTypeNum, &data) < 0 )
230242
return njsUtils_throwErrorDPI(env, globals);
@@ -459,6 +471,7 @@ NJS_NAPI_METHOD_IMPL_SYNC(njsDbObject_getValues, 0, &njsClassDefDbObject)
459471
uint32_t arrayPos;
460472
napi_value temp;
461473
dpiData data;
474+
char numberAsString[200];
462475

463476
// determine the size of the collection and create an array of that length
464477
if (dpiObject_getSize(obj->handle, &size) < 0)
@@ -471,6 +484,11 @@ NJS_NAPI_METHOD_IMPL_SYNC(njsDbObject_getValues, 0, &njsClassDefDbObject)
471484
if (dpiObject_getFirstIndex(obj->handle, &index, &exists) < 0)
472485
return njsUtils_throwErrorDPI(env, globals);
473486
while (exists) {
487+
if (obj->type->elementTypeInfo.oracleTypeNum == DPI_ORACLE_TYPE_NUMBER) {
488+
data.value.asBytes.ptr = numberAsString;
489+
data.value.asBytes.length = sizeof(numberAsString);
490+
data.value.asBytes.encoding = NULL;
491+
}
474492
if (dpiObject_getElementValueByIndex(obj->handle, index,
475493
obj->type->elementTypeInfo.nativeTypeNum, &data) < 0)
476494
return njsUtils_throwErrorDPI(env, globals);
@@ -627,6 +645,7 @@ static bool njsDbObject_transformFromOracle(njsDbObject *obj, napi_env env,
627645
case DPI_ORACLE_TYPE_NCHAR:
628646
case DPI_ORACLE_TYPE_VARCHAR:
629647
case DPI_ORACLE_TYPE_NVARCHAR:
648+
case DPI_ORACLE_TYPE_NUMBER:
630649
NJS_CHECK_NAPI(env, napi_create_string_utf8(env,
631650
data->value.asBytes.ptr, data->value.asBytes.length,
632651
value))
@@ -636,15 +655,6 @@ static bool njsDbObject_transformFromOracle(njsDbObject *obj, napi_env env,
636655
data->value.asBytes.length, data->value.asBytes.ptr, NULL,
637656
value))
638657
return true;
639-
case DPI_ORACLE_TYPE_NUMBER:
640-
if (typeInfo->nativeTypeNum == DPI_NATIVE_TYPE_INT64) {
641-
NJS_CHECK_NAPI(env, napi_create_int64(env, data->value.asInt64,
642-
value))
643-
} else {
644-
NJS_CHECK_NAPI(env, napi_create_double(env,
645-
data->value.asDouble, value))
646-
}
647-
return true;
648658
case DPI_ORACLE_TYPE_NATIVE_INT:
649659
NJS_CHECK_NAPI(env, napi_create_int64(env, data->value.asInt64,
650660
value))
@@ -712,6 +722,7 @@ static bool njsDbObject_transformToOracle(njsDbObject *obj, napi_env env,
712722
njsDbObjectAttr *attr, njsModuleGlobals *globals)
713723
{
714724
napi_value constructor, getComponentsFn;
725+
napi_value numStr;
715726
napi_valuetype valueType;
716727
njsDbObject *valueObj;
717728
bool check, tempBool;
@@ -738,8 +749,19 @@ static bool njsDbObject_transformToOracle(njsDbObject *obj, napi_env env,
738749
dpiData_setBytes(data, *strBuffer, (uint32_t) length);
739750
return true;
740751

741-
// numbers are handled as doubles in JavaScript
752+
// numbers are handled as doubles in JavaScript.
753+
// If property type is NUMBER, we write string.
742754
case napi_number:
755+
case napi_bigint:
756+
if (oracleTypeNum == DPI_ORACLE_TYPE_NUMBER) {
757+
// Write to NUMBER/ INTEGER properties astring.
758+
NJS_CHECK_NAPI(env, napi_coerce_to_string(env, value, &numStr))
759+
if (!njsUtils_copyStringFromJS(env, numStr, strBuffer, &length))
760+
return false;
761+
*nativeTypeNum = DPI_NATIVE_TYPE_BYTES;
762+
dpiData_setBytes(data, *strBuffer, (uint32_t)length);
763+
return true;
764+
}
743765
NJS_CHECK_NAPI(env, napi_get_value_double(env, value,
744766
&data->value.asDouble));
745767
*nativeTypeNum = DPI_NATIVE_TYPE_DOUBLE;
@@ -1033,7 +1055,13 @@ static bool njsDbObjectType_populateTypeInfo(njsDataTypeInfo *info,
10331055
napi_value temp;
10341056

10351057
info->oracleTypeNum = sourceInfo->oracleTypeNum;
1036-
info->nativeTypeNum = sourceInfo->defaultNativeTypeNum;
1058+
if (sourceInfo->oracleTypeNum == DPI_ORACLE_TYPE_NUMBER) {
1059+
// For NUMBER property, we want to retrieve always as
1060+
// DPI_NATIVE_TYPE_BYTES not DPI_NATIVE_TYPE_DOUBLE.
1061+
info->nativeTypeNum = DPI_NATIVE_TYPE_BYTES;
1062+
} else {
1063+
info->nativeTypeNum = sourceInfo->defaultNativeTypeNum;
1064+
}
10371065
info->precision = sourceInfo->precision;
10381066
info->scale = sourceInfo->scale;
10391067
info->dbSizeInBytes = sourceInfo->dbSizeInBytes;

0 commit comments

Comments
 (0)