Skip to content

Commit eda8681

Browse files
committed
Add IN OUT bind support for temporary LOBs and add IN OUT bind support for LOBs fetched as String or Buffer with more than 32K data.
1 parent 4a700ad commit eda8681

File tree

10 files changed

+590
-222
lines changed

10 files changed

+590
-222
lines changed

doc/api.md

Lines changed: 19 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -1536,10 +1536,8 @@ the connection is still open.
15361536
Open temporary LOB usage can be monitored using the view
15371537
[`V$TEMPORARY_LOBS`](http://docs.oracle.com/database/122/ADLOB/managing-LOBs.htm#ADLOB45157).
15381538

1539-
LOBs created with `createLob()` can be bound for IN and for OUT binds,
1540-
but not for IN OUT binds. This is to stop temporary LOB leaks where
1541-
node-oracledb is unable to free resources held by the initial
1542-
temporary LOB.
1539+
LOBs created with `createLob()` can be bound for IN, IN OUT and OUT
1540+
binds.
15431541

15441542
See [Working with CLOB and BLOB Data](#lobhandling) and [LOB Bind Parameters](#lobbinds) for more information.
15451543

@@ -3971,10 +3969,7 @@ If the data is larger than can be handled as a String or Buffer in
39713969
Node.js or node-oracledb, it will need to be streamed to
39723970
a [Lob](#lobclass), as discussed later.
39733971
See [LOB Bind Parameters](#lobbinds) for size considerations regarding
3974-
LOB binds. Note node-oracledb will internally use a temporary LOB to
3975-
insert String or Buffer data larger than 32 KB. This is transparent
3976-
to the application but size limits on the JavaScript String or Buffer
3977-
still apply.
3972+
LOB binds.
39783973
39793974
Given the table:
39803975
@@ -4167,11 +4162,6 @@ until [`lob.close()`](#lobclose) is called. Database Administrators
41674162
can track this usage by querying
41684163
[`V$TEMPORARY_LOBS`](http://docs.oracle.com/database/122/ADLOB/managing-LOBs.htm#ADLOB45157).
41694164
4170-
Temporary Lobs such as those created with `createLob()` can be bound
4171-
as IN and as OUT binds, but not for IN OUT binds. This is to stop
4172-
temporary LOB leaks where node-oracledb is unable to free resources
4173-
held by the initial temporary LOB.
4174-
41754165
#### Passing a Lob Into PL/SQL
41764166
41774167
The following insertion example is based on
@@ -4304,9 +4294,7 @@ If the data is larger than can be handled as a String or Buffer in
43044294
Node.js or node-oracledb, it will need to be explicitly streamed to
43054295
a [Lob](#lobclass), as discussed previously.
43064296
See [LOB Bind Parameters](#lobbinds) for size considerations regarding
4307-
LOB binds. Note node-oracledb will internally use a temporary LOB to
4308-
fetch String or Buffer data larger than 32 KB. This is transparent to
4309-
the application but size limits on the String or Buffer still apply.
4297+
LOB binds.
43104298
43114299
#### Selecting and Fetching Lob Instances
43124300
@@ -4975,14 +4963,9 @@ tables) or temporary LOBs (such as those created
49754963
with [`createLob()`](#connectioncreatelob) or returned by some SQL and
49764964
PL/SQL operations).
49774965
4978-
Persistent LOBs can be bound with direction `BIND_IN`, `BIND_OUT` or
4966+
LOBs can be bound with direction `BIND_IN`, `BIND_OUT` or
49794967
`BIND_INOUT`, depending on context.
49804968
4981-
Temporary LOBs can be bound with direction `BIND_IN` or `BIND_OUT`,
4982-
but not with `BIND_INOUT`. This is to prevent temporary LOB leaks
4983-
where node-oracledb is unable to free resources held by the initial
4984-
temporary LOB.
4985-
49864969
Note that any PL/SQL OUT LOB parameter should be initialized in the
49874970
PL/SQL block - even just to NULL - before the PL/SQL code
49884971
completes. Make sure to do this in all PL/SQL code paths including in
@@ -5001,32 +4984,23 @@ size used for these binds in the OUT direction is 200, so set
50014984
See [Working with CLOB and BLOB Data](#lobhandling) for examples and
50024985
more information on binding and working with LOBs.
50034986
5004-
#### Theoretical Limits (Bytes) for Binding LOBs to Strings and Buffers
5005-
5006-
DB Type | Bind Type | Direction | Oracle Client 12c Limit<sup>*</sup> | Oracle Client 11.2 Limit<sup>*</sup>
5007-
--------|-----------------|-------------|-------------------------|------------------------
5008-
CLOB | oracledb.STRING |BIND_IN | 1 GB | 64 KB
5009-
CLOB | oracledb.STRING |BIND_OUT | 1 GB | 64 KB
5010-
CLOB | oracledb.STRING |BIND_INOUT | 32 KB | 32 KB
5011-
BLOB | oracledb.BUFFER |BIND_IN | 1 GB | 64 KB
5012-
BLOB | oracledb.BUFFER |BIND_OUT | 1 GB | 64 KB
5013-
BLOB | oracledb.BUFFER |BIND_INOUT | 32 KB | 32 KB
5014-
5015-
<sup>*</sup>The largest usable data length is two bytes less than the
5016-
size shown for 12c and one byte less for 11.2.
4987+
#### Size Limits for Binding LOBs to Strings and Buffers
50174988
5018-
In practice, the limitation on binding IN or OUT is the memory
5019-
available to Node.js and the V8 engine. For data larger than several
5020-
megabytes, it is recommended to bind as `oracledb.CLOB` or
5021-
`oracledb.BLOB` and use [Lob streaming](#streamsandlobs). If you try
5022-
to create large Strings or Buffers in Node.js you will see errors like
5023-
*JavaScript heap out of memory*, or other space related messages.
4989+
When CLOBs are bound as `oracledb.STRING`, and BLOBs are bound as
4990+
`oracledb.BUFFER`, the theoretical maxium data length that can be
4991+
bound is 2 bytes less than 1 GB. When node-oracledb uses Oracle Client
4992+
11.2 the limit is 1 byte less than 64 KB.
50244993
5025-
Internally, for PL/SQL calls, node-oracledb uses temporary LOBs when
5026-
binding Strings and Buffers larger than 32 KB. Since temporary LOBs
5027-
cannot be used for IN OUT binds, the data size in this case is
5028-
restricted to 32 KB. For SQL call no temporary LOBs are used.
4994+
In practice, the limitation on binding is the memory available to
4995+
Node.js and the V8 engine. For data larger than several megabytes, it
4996+
is recommended to bind as `oracledb.CLOB` or `oracledb.BLOB` and
4997+
use [Lob streaming](#streamsandlobs). If you try to create large
4998+
Strings or Buffers in Node.js you will see errors like *JavaScript
4999+
heap out of memory*, or other space related messages.
50295000
5001+
Internally, temporary LOBs are used when binding Strings and Buffers
5002+
larger than 32 KB for PL/SQL calls. For SQL calls no temporary LOBs
5003+
are used.
50305004
50315005
### <a name="plsqlindexbybinds"></a> 13.6 PL/SQL Collection Associative Array (Index-by) Bind Parameters
50325006

src/njs/src/njsConnection.cpp

Lines changed: 27 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -2054,7 +2054,8 @@ void Connection::CLOB2String ( eBaton* executeBaton, unsigned int index )
20542054
unsigned long long byteAmount = 0;
20552055
// Set the charAmount to (maxSize + 1) to know whether given maxSize good
20562056
// enough to store PLSQL OUT data
2057-
unsigned long long charAmount = executeBaton->binds[index]->maxSize + 1;
2057+
unsigned long long charAmount =
2058+
executeBaton->extBinds[index]->fields.extLob.maxSize + 1;
20582059

20592060
executeBaton->binds[index]->type = DpiVarChar;
20602061
LOB2StringOrBuffer ( executeBaton, index, byteAmount, charAmount );
@@ -2078,7 +2079,8 @@ void Connection::BLOB2Buffer ( eBaton* executeBaton, unsigned int index )
20782079
{
20792080
// Set the charAmount to (maxSize + 1) to know whether given maxSize good
20802081
// enough to store PLSQL OUT data
2081-
unsigned long long byteAmount = executeBaton->binds[index]->maxSize + 1;
2082+
unsigned long long byteAmount =
2083+
executeBaton->extBinds[index]->fields.extLob.maxSize + 1;
20822084
unsigned long long charAmount = 0;
20832085

20842086
executeBaton->binds[index]->type = DpiRaw;
@@ -2141,7 +2143,8 @@ void Connection::LOB2StringOrBuffer ( eBaton* executeBaton, unsigned int index,
21412143
* of charAmount or byteAmount passed to Lob::read()
21422144
* If there is more data in the LOB than the maxSize, set the error
21432145
*/
2144-
if ( byteAmount > ( unsigned long long ) bind->maxSize )
2146+
if ( byteAmount > ( unsigned long long )
2147+
executeBaton->extBinds[index]->fields.extLob.maxSize )
21452148
{
21462149
executeBaton->error = NJSMessages::getErrorMsg(
21472150
errInsufficientBufferForBinds);
@@ -2313,7 +2316,9 @@ void Connection::Descr2StringOrBuffer ( eBaton* executeBaton )
23132316
}
23142317
else
23152318
{
2316-
if ( *bind->ind != -1 )
2319+
if ( Lob::isTempLob ( executeBaton->dpienv->envHandle(),
2320+
executeBaton->dpiconn->getErrh (),
2321+
*( Descriptor** ) bind->value ) )
23172322
{
23182323
Lob::freeTempLob ( executeBaton->dpiconn->getSvch (),
23192324
executeBaton->dpiconn->getErrh (),
@@ -2355,12 +2360,14 @@ void Connection::ConvertStringOrBuffer2LOB ( eBaton* executeBaton,
23552360
{
23562361
Bind *bind = executeBaton->binds[index];
23572362

2358-
// In case of IN bind convert string/buffer to LOB if input data
2359-
// is not NULL and data size more than 32k
2360-
if ( !bind->isOut && *bind->ind != -1 &&
2361-
( bind->len && *bind->len > NJS_THRESHOLD_SIZE_PLSQL_STRING_ARG ) )
2363+
// In case of IN or INOUT bind convert string/buffer to LOB if input data
2364+
// is not NULL and input data size more than 32k for strict IN binds or
2365+
// maxSize more than 32k for INOUT binds
2366+
if ( ( ( !bind->isOut || bind->isInOut ) && *bind->ind != -1 ) &&
2367+
( ( !bind->isOut && *bind->len > NJS_THRESHOLD_SIZE_PLSQL_STRING_ARG ) ||
2368+
( bind->isInOut && bind->maxSize > NJS_THRESHOLD_SIZE_PLSQL_STRING_ARG
2369+
) ) )
23622370
{
2363-
// This block is only for BIND_IN case
23642371
ExtBind* extBind = new ExtBind ();
23652372
if ( !extBind )
23662373
{
@@ -2369,25 +2376,24 @@ void Connection::ConvertStringOrBuffer2LOB ( eBaton* executeBaton,
23692376
goto exitConvertStringOrBuffer2LOB;
23702377
}
23712378
executeBaton->njsconn->InitExtBind ( extBind, NJS_EXTBIND_LOB );
2372-
// Set a flag to know that type conversion happened
2373-
// This helps in later steps to clean LOB resources
2374-
extBind->fields.extLob.isStringBuffer2LOB = true;
2375-
executeBaton->extBinds[index] = extBind;
2379+
extBind->fields.extLob.maxSize = bind->maxSize;
23762380

23772381
if ( bind->type == DpiVarChar )
23782382
{
2379-
// Convert String to CLOB
23802383
String2CLOB ( executeBaton, index );
23812384
}
23822385
else
23832386
{
2384-
// Convert Buffer to BLOB
23852387
Buffer2BLOB ( executeBaton, index );
23862388
}
2389+
// Set a flag to know that type conversion happened
2390+
// This helps in later steps to clean LOB resources
2391+
extBind->fields.extLob.isStringBuffer2LOB = true;
2392+
executeBaton->extBinds[index] = extBind;
23872393
}
2388-
else if ( bind->maxSize > NJS_THRESHOLD_SIZE_PLSQL_STRING_ARG )
2394+
else if ( ( bind->isOut || bind->isInOut ) &&
2395+
bind->maxSize > NJS_THRESHOLD_SIZE_PLSQL_STRING_ARG )
23892396
{
2390-
// This block is only for BIND_OUT case
23912397
ExtBind* extBind = new ExtBind ();
23922398

23932399
if ( !extBind )
@@ -2397,6 +2403,7 @@ void Connection::ConvertStringOrBuffer2LOB ( eBaton* executeBaton,
23972403
goto exitConvertStringOrBuffer2LOB;
23982404
}
23992405
executeBaton->njsconn->InitExtBind ( extBind, NJS_EXTBIND_LOB );
2406+
extBind->fields.extLob.maxSize = bind->maxSize;
24002407

24012408
// Set a flag to know that type conversion happened
24022409
// This helps in later steps for converting LOB to String/Buffer and
@@ -2476,6 +2483,7 @@ void Connection::InitExtBind ( ExtBind *extBind, ExtBindType fieldType )
24762483
{
24772484
extBind->fields.extLob.value = NULL;
24782485
extBind->fields.extLob.isStringBuffer2LOB = false;
2486+
extBind->fields.extLob.maxSize = 0;
24792487
}
24802488
}
24812489

@@ -2514,9 +2522,8 @@ void Connection::PrepareAndBind (eBaton* executeBaton)
25142522
if ( ( executeBaton->st == DpiStmtBegin ||
25152523
executeBaton->st == DpiStmtDeclare ||
25162524
executeBaton->st == DpiStmtCall ) &&
2517-
( !executeBaton->binds[index]->isInOut &&
25182525
( executeBaton->binds[index]->type == DpiVarChar ||
2519-
executeBaton->binds[index]->type == DpiRaw ) ) )
2526+
executeBaton->binds[index]->type == DpiRaw ) )
25202527
{
25212528
ConvertStringOrBuffer2LOB ( executeBaton, index );
25222529
}
@@ -2590,9 +2597,8 @@ void Connection::PrepareAndBind (eBaton* executeBaton)
25902597
if ( ( executeBaton->st == DpiStmtBegin ||
25912598
executeBaton->st == DpiStmtDeclare ||
25922599
executeBaton->st == DpiStmtCall ) &&
2593-
( !executeBaton->binds[index]->isInOut &&
25942600
( executeBaton->binds[index]->type == DpiVarChar ||
2595-
executeBaton->binds[index]->type == DpiRaw ) ) )
2601+
executeBaton->binds[index]->type == DpiRaw ) )
25962602
{
25972603
ConvertStringOrBuffer2LOB ( executeBaton, index );
25982604
}

src/njs/src/njsConnection.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -165,6 +165,8 @@ typedef struct ExtBind
165165
void *value; // Stores extra bind reference
166166
bool isStringBuffer2LOB; // Specifies whether string or buffer Converted
167167
// to LOB or not
168+
DPI_SZ_TYPE maxSize; // Size for the OUT or IN OUT bind value
169+
168170
} extLob;
169171
} fields;
170172
} ExtBind;

src/njs/src/njsIntLob.cpp

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -370,12 +370,7 @@ NJSErrorType ILob::preBind ( Bind *bind )
370370
{
371371
bind->type = this->dpiLobType_;
372372

373-
// Temp LOB INOUT bind not supported, check for it.
374-
if ( this->isTempLob_ && bind->isInOut )
375-
{
376-
errNum = errTempLOBINOUTBind;
377-
}
378-
else if ( !this->isValid_ || this->state_ == NJS_ACTIVE )
373+
if ( !this->isValid_ || this->state_ == NJS_ACTIVE )
379374
{
380375
errNum = this->getErrNumber ( true );
381376
}

src/njs/src/njsMessages.cpp

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -85,10 +85,9 @@ static const char *errMsg[] =
8585
"NJS-046: pool alias \"%s\" already exists in the connection pool cache", // errPoolWithAliasAlreadyExists
8686
"NJS-047: pool alias \"%s\" not found in the connection pool cache", // errPoolWithAliasNotFound
8787
"NJS-048: operation not permitted while Lob object is active in a bind operation", // errLOBBindActive
88-
"NJS-049: cannot use bind direction IN OUT for temporary LOBs", // errTempLOBINOUTBind
89-
"NJS-050: Temporary LOBs were open when the connection was closed", // errBusyConnTEMPLOB
90-
"NJS-051: data must be shorter than %d", // errBindValueTooLarge
91-
"NJS-052: \"%s\" must be less than %d", // errMaxValueTooLarge
88+
"NJS-049: Temporary LOBs were open when the connection was closed", // errBusyConnTEMPLOB
89+
"NJS-050: data must be shorter than %d", // errBindValueTooLarge
90+
"NJS-051: \"%s\" must be less than %d", // errMaxValueTooLarge
9291
};
9392

9493
string NJSMessages::getErrorMsg ( NJSErrorType err, ... )

src/njs/src/njsMessages.h

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,6 @@ typedef enum
8484
errPoolWithAliasAlreadyExists,
8585
errPoolWithAliasNotFound,
8686
errLOBBindActive,
87-
errTempLOBINOUTBind,
8887
errBusyConnTEMPLOB,
8988
errBindValueTooLarge,
9089
errMaxValueTooLarge,

0 commit comments

Comments
 (0)