Skip to content

Commit a3b8fc8

Browse files
committed
Add Oracle Database Sharding support
1 parent 2952472 commit a3b8fc8

File tree

13 files changed

+551
-85
lines changed

13 files changed

+551
-85
lines changed

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,9 @@
88
[`connection.clientInfo`](https://oracle.github.io/node-oracledb/doc/api.html#propconnclientinfo)
99
and [`connection.dbOp`](https://oracle.github.io/node-oracledb/doc/api.html#propconndbop).
1010

11+
- Added support for [Oracle
12+
Sharding](https://oracle.github.io/node-oracledb/doc/api.html#sharding).
13+
1114
- Fixed a [regression](https://github.com/oracle/node-oracledb/issues/1152) when
1215
binding dates with alternative JavaScript frameworks.
1316

doc/api.md

Lines changed: 272 additions & 50 deletions
Large diffs are not rendered by default.

lib/oracledb.js

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
'use strict';
2121

2222
const nodbUtil = require('./util.js');
23+
const util = require('util');
2324

2425
// This version of node-oracledb works with Node.js 8.16, 10.16 or
2526
// later. The test stops hard-to-interpret runtime errors and crashes
@@ -94,6 +95,12 @@ class OracleDb {
9495
this.createPool = nodbUtil.promisify(oracleDbInst, createPool);
9596
}
9697

98+
// temporary method for determining if an object is a date until
99+
// napi_is_date() can be used (when N-API v5 can be used)
100+
_isDate(val) {
101+
return util.isDate(val);
102+
}
103+
97104
// retrieves a pool from the pool cache (synchronous method)
98105
getPool(poolAlias) {
99106
let pool;

lib/pool.js

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121

2222
const EventEmitter = require('events');
2323
const nodbUtil = require('./util.js');
24+
const util = require('util');
2425

2526
// completeConnectionRequest does the actual work of getting a connection from
2627
// a pool. It's abstracted out so it can be called from getConnection and
@@ -480,6 +481,12 @@ class Pool extends EventEmitter {
480481
this.terminate = this.close;
481482
}
482483

484+
// temporary method for determining if an object is a date until
485+
// napi_is_date() can be used (when N-API v5 can be used)
486+
_isDate(val) {
487+
return util.isDate(val);
488+
}
489+
483490
}
484491

485492

src/njsBaton.c

Lines changed: 142 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,8 @@
2828
// methods used internally
2929
static bool njsBaton_completeAsyncHelper(njsBaton *baton, napi_env env,
3030
napi_value *callback, napi_value *callingObj);
31+
static void njsBaton_freeShardingKeys(uint8_t *numShardingKeyColumns,
32+
dpiShardingKeyColumn **shardingKeyColumns);
3133

3234

3335
//-----------------------------------------------------------------------------
@@ -359,10 +361,39 @@ void njsBaton_free(njsBaton *baton, napi_env env)
359361
}
360362
NJS_FREE_AND_CLEAR(baton->callbackArgs);
361363

364+
// free sharding and super sharding keys
365+
njsBaton_freeShardingKeys(&baton->numShardingKeyColumns,
366+
&baton->shardingKeyColumns);
367+
njsBaton_freeShardingKeys(&baton->numSuperShardingKeyColumns,
368+
&baton->superShardingKeyColumns);
369+
362370
free(baton);
363371
}
364372

365373

374+
//-----------------------------------------------------------------------------
375+
// njsBaton_freeShardingKeys()
376+
// To clean up array of ShardingKeys
377+
//-----------------------------------------------------------------------------
378+
void njsBaton_freeShardingKeys(uint8_t *numShardingKeyColumns,
379+
dpiShardingKeyColumn **shardingKeyColumns)
380+
{
381+
dpiShardingKeyColumn *shards = *shardingKeyColumns;
382+
uint32_t i;
383+
384+
for (i = 0; i < *numShardingKeyColumns; i++) {
385+
if (shards[i].oracleTypeNum == DPI_ORACLE_TYPE_VARCHAR &&
386+
shards[i].value.asBytes.ptr) {
387+
free(shards[i].value.asBytes.ptr);
388+
shards[i].value.asBytes.ptr = NULL;
389+
}
390+
}
391+
free(shards);
392+
*numShardingKeyColumns = 0;
393+
*shardingKeyColumns = NULL;
394+
}
395+
396+
366397
//-----------------------------------------------------------------------------
367398
// njsBaton_getBoolFromArg()
368399
// Gets a boolean value from the specified JavaScript object property, if
@@ -540,6 +571,109 @@ uint32_t njsBaton_getNumOutBinds(njsBaton *baton)
540571
}
541572

542573

574+
//-----------------------------------------------------------------------------
575+
// njsBaton_getShardingKeyColumnsFromArg()
576+
// Gets an array of sharding key columns from the specified JavaScript object
577+
// property, if possible. If the given property is undefined, no error is set
578+
// and the value is left untouched; otherwise, if the value is not an array,
579+
// the error is set on the baton.
580+
//-----------------------------------------------------------------------------
581+
bool njsBaton_getShardingKeyColumnsFromArg(njsBaton *baton, napi_env env,
582+
napi_value *args, int argIndex, const char *propertyName,
583+
uint8_t *numShardingKeyColumns,
584+
dpiShardingKeyColumn **shardingKeyColumns)
585+
{
586+
napi_value asNumber, value, element;
587+
dpiShardingKeyColumn *shards;
588+
napi_valuetype valueType;
589+
uint32_t arrLen, i;
590+
size_t numBytes;
591+
bool check;
592+
593+
// validate parameter
594+
if (!njsBaton_getValueFromArg(baton, env, args, argIndex, propertyName,
595+
napi_object, &value, NULL))
596+
return false;
597+
if (!value)
598+
return true;
599+
NJS_CHECK_NAPI(env, napi_is_array(env, value, &check))
600+
if (!check)
601+
return njsBaton_setError(baton, errNonArrayProvided);
602+
603+
// allocate space for sharding key columns; if array is empty, nothing
604+
// further to do!
605+
NJS_CHECK_NAPI(env, napi_get_array_length(env, value, &arrLen))
606+
if (arrLen == 0)
607+
return true;
608+
shards = calloc(arrLen, sizeof(dpiShardingKeyColumn));
609+
if (!shards)
610+
return njsBaton_setError(baton, errInsufficientMemory);
611+
*shardingKeyColumns = shards;
612+
*numShardingKeyColumns = (uint8_t)arrLen;
613+
614+
// process each element
615+
for (i = 0; i < arrLen; i++) {
616+
NJS_CHECK_NAPI(env, napi_get_element(env, value, i, &element))
617+
NJS_CHECK_NAPI(env, napi_typeof(env, element, &valueType))
618+
619+
// handle strings
620+
if (valueType == napi_string) {
621+
shards[i].nativeTypeNum = DPI_NATIVE_TYPE_BYTES;
622+
shards[i].oracleTypeNum = DPI_ORACLE_TYPE_VARCHAR;
623+
if (!njsUtils_copyStringFromJS(env, element,
624+
&shards[i].value.asBytes.ptr, &numBytes))
625+
return false;
626+
shards[i].value.asBytes.length = (uint32_t) numBytes;
627+
continue;
628+
}
629+
630+
// handle numbers
631+
if (valueType == napi_number) {
632+
shards[i].nativeTypeNum = DPI_NATIVE_TYPE_DOUBLE;
633+
shards[i].oracleTypeNum = DPI_ORACLE_TYPE_NUMBER;
634+
NJS_CHECK_NAPI(env, napi_get_value_double(env, element,
635+
&shards[i].value.asDouble));
636+
continue;
637+
}
638+
639+
// handle objects
640+
if (valueType == napi_object) {
641+
642+
// handle buffers
643+
NJS_CHECK_NAPI(env, napi_is_buffer(env, element, &check))
644+
if (check) {
645+
shards[i].nativeTypeNum = DPI_NATIVE_TYPE_BYTES;
646+
shards[i].oracleTypeNum = DPI_ORACLE_TYPE_RAW;
647+
NJS_CHECK_NAPI(env, napi_get_buffer_info(env, element,
648+
(void*) &shards[i].value.asBytes.ptr, &numBytes))
649+
shards[i].value.asBytes.length = (uint32_t) numBytes;
650+
continue;
651+
}
652+
653+
// handle dates
654+
if (!njsBaton_isDate(baton, env, element, &check))
655+
return false;
656+
if (check) {
657+
shards[i].nativeTypeNum = DPI_NATIVE_TYPE_DOUBLE;
658+
shards[i].oracleTypeNum = DPI_ORACLE_TYPE_DATE;
659+
NJS_CHECK_NAPI(env, napi_coerce_to_number(env, element,
660+
&asNumber))
661+
NJS_CHECK_NAPI(env, napi_get_value_double(env, asNumber,
662+
&shards[i].value.asDouble))
663+
continue;
664+
}
665+
666+
}
667+
668+
// no support for other types
669+
return njsBaton_setError(baton, errInvalidPropertyValueInParam,
670+
propertyName, argIndex + 1);
671+
}
672+
673+
return true;
674+
}
675+
676+
543677
//-----------------------------------------------------------------------------
544678
// njsBaton_getSodaDocument()
545679
// Examines the passed object. If it is a SODA document object, a reference
@@ -836,7 +970,13 @@ bool njsBaton_isDate(njsBaton *baton, napi_env env, napi_value value,
836970
{
837971
napi_value isDateObj;
838972

839-
NJS_CHECK_NAPI(env, napi_call_function(env, baton->jsConnection,
973+
if (!baton->jsIsDateObj) {
974+
NJS_CHECK_NAPI(env, napi_get_reference_value(env, baton->jsCallingObj,
975+
&baton->jsIsDateObj))
976+
NJS_CHECK_NAPI(env, napi_get_named_property(env, baton->jsIsDateObj,
977+
"_isDate", &baton->jsIsDateMethod))
978+
}
979+
NJS_CHECK_NAPI(env, napi_call_function(env, baton->jsIsDateObj,
840980
baton->jsIsDateMethod, 1, &value, &isDateObj))
841981
NJS_CHECK_NAPI(env, napi_get_value_bool(env, isDateObj, isDate))
842982
return true;
@@ -918,21 +1058,10 @@ void njsBaton_reportError(njsBaton *baton, napi_env env)
9181058
// njsBaton_setConstructors()
9191059
// Sets the constructors on the baton.
9201060
//-----------------------------------------------------------------------------
921-
bool njsBaton_setConstructors(njsBaton *baton, napi_env env, bool canBind)
1061+
bool njsBaton_setConstructors(njsBaton *baton, napi_env env)
9221062
{
9231063
napi_value global;
9241064

925-
// if binding is possible, get the connection (which is the calling object)
926-
// and acquire the method used for determining if a value is a date; this
927-
// is needed until such time as napi_is_date() is available for all LTS
928-
// releases
929-
if (canBind) {
930-
NJS_CHECK_NAPI(env, napi_get_reference_value(env, baton->jsCallingObj,
931-
&baton->jsConnection))
932-
NJS_CHECK_NAPI(env, napi_get_named_property(env, baton->jsConnection,
933-
"_isDate", &baton->jsIsDateMethod))
934-
}
935-
9361065
// acquire the Date constructor
9371066
NJS_CHECK_NAPI(env, napi_get_global(env, &global))
9381067
NJS_CHECK_NAPI(env, napi_get_named_property(env, global, "Date",

src/njsConnection.c

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -600,7 +600,7 @@ static bool njsConnection_executePostAsync(njsBaton *baton, napi_env env,
600600
napi_value implicitResults;
601601

602602
// create constructors used for various types that might be returned
603-
if (!njsBaton_setConstructors(baton, env, true))
603+
if (!njsBaton_setConstructors(baton, env))
604604
return false;
605605

606606
// create result object
@@ -693,7 +693,7 @@ static bool njsConnection_executeProcessArgs(njsBaton *baton,
693693
(void**) &baton->fetchAsStringTypes,
694694
&baton->numFetchAsStringTypes))
695695
return false;
696-
if (!njsBaton_setConstructors(baton, env, true))
696+
if (!njsBaton_setConstructors(baton, env))
697697
return false;
698698

699699
// get SQL from first argument
@@ -888,7 +888,7 @@ static bool njsConnection_executeManyProcessArgs(njsBaton *baton,
888888
{
889889
// setup defaults and define constructors for use in various checks
890890
baton->autoCommit = baton->oracleDb->autoCommit;
891-
if (!njsBaton_setConstructors(baton, env, true))
891+
if (!njsBaton_setConstructors(baton, env))
892892
return false;
893893

894894
// get SQL from first argument

src/njsErrors.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ static const char *njsErrorMessages[] = {
6060
"NJS-046: pool alias \"%s\" already exists in the connection pool cache", // errPoolWithAliasAlreadyExists
6161
"NJS-047: pool alias \"%s\" not found in connection pool cache", // errPoolWithAliasNotFound
6262
"NJS-052: invalid data type at array index %d for bind position %d", // errIncompatibleTypeArrayIndexBind
63-
"NJS-053: array value expected, a non-array value provided", //errNonArrayProvided
63+
"NJS-053: an array value was expected", //errNonArrayProvided
6464
"NJS-055: binding by position and name cannot be mixed", // errMixedBind
6565
"NJS-056: maxSize must be specified and not zero for bind position %u", // errMissingMaxSizeByPos
6666
"NJS-057: maxSize must be specified and not zero for bind \"%.*s\"", // errMissingMaxSizeByName

src/njsModule.h

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -423,6 +423,7 @@ struct njsBaton {
423423
// integer values
424424
uint32_t poolMin;
425425
uint32_t poolMax;
426+
uint32_t poolMaxPerShard;
426427
uint32_t poolIncrement;
427428
uint32_t poolTimeout;
428429
int32_t poolPingInterval;
@@ -472,17 +473,24 @@ struct njsBaton {
472473
// subscriptions (no free required)
473474
njsSubscription *subscription;
474475

476+
// sharding (requires free)
477+
dpiShardingKeyColumn *shardingKeyColumns;
478+
dpiShardingKeyColumn *superShardingKeyColumns;
479+
uint8_t numShardingKeyColumns;
480+
uint8_t numSuperShardingKeyColumns;
481+
475482
// references that are held (requires free)
476483
napi_ref jsBuffer;
477484
napi_ref jsCallingObj;
478485
napi_ref jsCallback;
479486
napi_ref jsSubscription;
480487

481488
// values required to check if a value is a date; this is only used when
482-
// binding data in connection.execute() and connection.executeMany();
483-
// this can be replaced with calls to napi_is_date() when it is available
484-
// for all LTS releases
485-
napi_value jsConnection;
489+
// binding data in connection.execute() and connection.executeMany() and
490+
// when managing sharding keys (when getting a connection); this can be
491+
// replaced with calls to napi_is_date() when it is available for all LTS
492+
// releases
493+
napi_value jsIsDateObj;
486494
napi_value jsIsDateMethod;
487495

488496
// constructors
@@ -573,6 +581,7 @@ struct njsOracleDb {
573581
uint32_t fetchArraySize;
574582
uint32_t poolMin;
575583
uint32_t poolMax;
584+
uint32_t poolMaxPerShard;
576585
uint32_t poolIncrement;
577586
uint32_t poolTimeout;
578587
uint32_t lobPrefetchSize;
@@ -614,6 +623,7 @@ struct njsPool {
614623
njsOracleDb *oracleDb;
615624
uint32_t poolMin;
616625
uint32_t poolMax;
626+
uint32_t poolMaxPerShard;
617627
uint32_t poolIncrement;
618628
uint32_t poolTimeout;
619629
uint32_t stmtCacheSize;
@@ -783,6 +793,9 @@ bool njsBaton_getFetchInfoFromArg(njsBaton *baton, napi_env env,
783793
bool njsBaton_getIntFromArg(njsBaton *baton, napi_env env, napi_value *args,
784794
int argIndex, const char *propertyName, int32_t *result, bool *found);
785795
uint32_t njsBaton_getNumOutBinds(njsBaton *baton);
796+
bool njsBaton_getShardingKeyColumnsFromArg(njsBaton *baton, napi_env env,
797+
napi_value *args, int argIndex, const char *propertyName,
798+
uint8_t *numShardKeys, dpiShardingKeyColumn **shards);
786799
bool njsBaton_getSodaDocument(njsBaton *baton, njsSodaDatabase *db,
787800
napi_env env, napi_value obj, dpiSodaDoc **handle);
788801
bool njsBaton_getStringFromArg(njsBaton *baton, napi_env env, napi_value *args,
@@ -808,7 +821,7 @@ bool njsBaton_queueWork(njsBaton *baton, napi_env env, const char *methodName,
808821
bool (*afterWorkCallback)(njsBaton*, napi_env, napi_value*),
809822
unsigned int numCallbackArgs);
810823
void njsBaton_reportError(njsBaton *baton, napi_env env);
811-
bool njsBaton_setConstructors(njsBaton *baton, napi_env env, bool canBind);
824+
bool njsBaton_setConstructors(njsBaton *baton, napi_env env);
812825
bool njsBaton_setError(njsBaton *baton, int errNum, ...);
813826
bool njsBaton_setErrorDPI(njsBaton *baton);
814827

0 commit comments

Comments
 (0)