Use Result Sets exclusively in C++ code and move direct fetch to JavaScript.

This results in a net performance benefit and simplifies the C++ code considerably.
This commit is contained in:
Christopher Jones 2018-02-06 13:14:19 +11:00
parent 549dc5bef5
commit b713c76bc8
7 changed files with 218 additions and 291 deletions

View File

@ -69,30 +69,81 @@ function queryStream(sql, binding, options) {
return stream; return stream;
} }
// fetchRowsToReturn is used to materialize the rows for an execute call using
// the resultSet returned from the C layer.
function fetchRowsToReturn(oracledb, executeOpts, result, executeCb) {
var rowsFetched = [];
var fetchArraySize;
var maxRows;
fetchArraySize = executeOpts.fetchArraySize;
if (fetchArraySize === undefined) {
fetchArraySize = oracledb.fetchArraySize;
}
maxRows = executeOpts.maxRows;
if (maxRows === undefined) {
maxRows = oracledb.maxRows;
}
var fetchRowsCb = function(err, rows) {
if (err) {
executeCb(err);
return;
}
if (rows) {
rowsFetched = rowsFetched.concat(rows);
}
if (rowsFetched.length == maxRows || rows.length < fetchArraySize) {
result.rows = rowsFetched;
delete result.resultSet;
executeCb(null, result);
return;
}
result.resultSet.getRows(fetchArraySize, fetchRowsCb);
};
result.resultSet.getRows(fetchArraySize, fetchRowsCb);
}
// This execute function is used to override the execute method of the Connection // This execute function is used to override the execute method of the Connection
// class, which is defined in the C layer. The override allows us to do things // class, which is defined in the C layer. The override allows us to do things
// like extend out the resultSet instance prior to passing it to the caller. // like extend out the resultSet instance prior to passing it to the caller.
function execute(a1, a2, a3, a4) { function execute(sql, a2, a3, a4) {
var self = this; var self = this;
var binds = [];
var executeOpts = {};
var executeCb; var executeCb;
var executeOpts;
var custExecuteCb; var custExecuteCb;
nodbUtil.assert(arguments.length > 1 && arguments.length < 5, 'NJS-009'); nodbUtil.assert(arguments.length > 1 && arguments.length < 5, 'NJS-009');
nodbUtil.assert(typeof a1 === 'string', 'NJS-006', 1); nodbUtil.assert(typeof sql === 'string', 'NJS-006', 1);
switch (arguments.length) { switch (arguments.length) {
case 2: case 2:
nodbUtil.assert(typeof a2 === 'function', 'NJS-006', 2); nodbUtil.assert(typeof a2 === 'function', 'NJS-006', 2);
executeCb = a2;
break; break;
case 3: case 3:
nodbUtil.assert(nodbUtil.isObjectOrArray(a2), 'NJS-006', 2); nodbUtil.assert(nodbUtil.isObjectOrArray(a2), 'NJS-006', 2);
nodbUtil.assert(typeof a3 === 'function', 'NJS-006', 3); nodbUtil.assert(typeof a3 === 'function', 'NJS-006', 3);
binds = a2;
executeCb = a3;
break; break;
case 4: case 4:
nodbUtil.assert(nodbUtil.isObjectOrArray(a2), 'NJS-006', 2); nodbUtil.assert(nodbUtil.isObjectOrArray(a2), 'NJS-006', 2);
nodbUtil.assert(nodbUtil.isObject(a3), 'NJS-006', 3); nodbUtil.assert(nodbUtil.isObject(a3), 'NJS-006', 3);
nodbUtil.assert(typeof a4 === 'function', 'NJS-006', 4); nodbUtil.assert(typeof a4 === 'function', 'NJS-006', 4);
binds = a2;
executeOpts = a3;
executeCb = a4;
break; break;
} }
@ -108,36 +159,28 @@ function execute(a1, a2, a3, a4) {
// Need to extend resultsets which may come from either the query results // Need to extend resultsets which may come from either the query results
// or outBinds. // or outBinds.
if (result.resultSet) { if (result.resultSet) {
resultset.extend(result.resultSet, self._oracledb, executeOpts); if (executeOpts.resultSet) {
} else if (result.outBinds) { resultset.extend(result.resultSet, self._oracledb, executeOpts);
outBindsKeys = Object.keys(result.outBinds); executeCb(null, result);
} else {
fetchRowsToReturn(self._oracledb, executeOpts, result, executeCb);
}
} else {
if (result.outBinds) {
outBindsKeys = Object.keys(result.outBinds);
for (outBindsIdx = 0; outBindsIdx < outBindsKeys.length; outBindsIdx += 1) { for (outBindsIdx = 0; outBindsIdx < outBindsKeys.length; outBindsIdx += 1) {
if (result.outBinds[outBindsKeys[outBindsIdx]] instanceof self._oracledb.ResultSet) { if (result.outBinds[outBindsKeys[outBindsIdx]] instanceof self._oracledb.ResultSet) {
resultset.extend(result.outBinds[outBindsKeys[outBindsIdx]], self._oracledb, executeOpts); resultset.extend(result.outBinds[outBindsKeys[outBindsIdx]], self._oracledb, executeOpts);
}
} }
} }
executeCb(null, result);
} }
executeCb(null, result);
}; };
switch (arguments.length) { self._execute.call(self, sql, binds, executeOpts, custExecuteCb);
case 4:
executeCb = a4;
executeOpts = a3;
self._execute.call(self, a1, a2, a3, custExecuteCb);
break;
case 3:
executeCb = a3;
executeOpts = a2;
self._execute.call(self, a1, a2, custExecuteCb);
break;
case 2:
executeCb = a2;
self._execute.call(self, a1, custExecuteCb);
break;
}
} }
executePromisified = nodbUtil.promisify(execute); executePromisified = nodbUtil.promisify(execute);

View File

@ -181,7 +181,6 @@ njsBaton::~njsBaton()
jsCallingObj.Reset(); jsCallingObj.Reset();
jsOracledb.Reset(); jsOracledb.Reset();
jsBuffer.Reset(); jsBuffer.Reset();
jsRows.Reset();
ClearAsyncData(); ClearAsyncData();
} }
@ -236,27 +235,25 @@ void njsBaton::ClearAsyncData()
delete protoILob; delete protoILob;
protoILob = NULL; protoILob = NULL;
} }
if (!keepQueryInfo) { if (queryVars) {
if (queryVars) { delete [] queryVars;
delete [] queryVars; queryVars = NULL;
queryVars = NULL; numQueryVars = 0;
numQueryVars = 0; }
} if (fetchInfo) {
if (fetchInfo) { delete [] fetchInfo;
delete [] fetchInfo; fetchInfo = NULL;
fetchInfo = NULL; numFetchInfo = 0;
numFetchInfo = 0; }
} if (fetchAsStringTypes) {
if (fetchAsStringTypes) { delete [] fetchAsStringTypes;
delete [] fetchAsStringTypes; fetchAsStringTypes = NULL;
fetchAsStringTypes = NULL; numFetchAsStringTypes = 0;
numFetchAsStringTypes = 0; }
} if (fetchAsBufferTypes) {
if (fetchAsBufferTypes) { delete [] fetchAsBufferTypes;
delete [] fetchAsBufferTypes; fetchAsBufferTypes = NULL;
fetchAsBufferTypes = NULL; numFetchAsBufferTypes = 0;
numFetchAsBufferTypes = 0;
}
} }
} }
@ -323,26 +320,18 @@ void njsBaton::AsyncAfterWorkCallback(uv_work_t *req, int status)
callbackArgs[i] = Nan::Undefined(); callbackArgs[i] = Nan::Undefined();
} }
// if no JS callback available, just delete the baton // if this baton is considered the active baton, clear it
if (baton->jsCallback.IsEmpty()) if (baton->callingObj && baton == baton->callingObj->activeBaton)
delete baton; baton->callingObj->activeBaton = NULL;
// otherwise, call the JS callback // delete the baton before the callback is made so any unnecessary
else { // ODPI-C handles are released as soon as possible
Local<Function> callback = Nan::New<Function>(baton->jsCallback); Local<Function> callback = Nan::New<Function>(baton->jsCallback);
delete baton;
// if this baton is considered the active baton, clear it // make JS callback
if (baton->callingObj && baton == baton->callingObj->activeBaton) Nan::MakeCallback(Nan::GetCurrentContext()->Global(), callback,
baton->callingObj->activeBaton = NULL; numCallbackArgs, callbackArgs);
// delete the baton before the callback is made so any unnecessary
// ODPI-C handles are released as soon as possible
delete baton;
// make JS callback
Nan::MakeCallback(Nan::GetCurrentContext()->Global(), callback,
numCallbackArgs, callbackArgs);
}
// we no longer need the callback args // we no longer need the callback args
delete [] callbackArgs; delete [] callbackArgs;

View File

@ -282,7 +282,6 @@ public:
bool getRS; bool getRS;
bool autoCommit; bool autoCommit;
bool extendedMetaData; bool extendedMetaData;
bool keepQueryInfo;
bool isReturning; bool isReturning;
bool isPLSQL; bool isPLSQL;
uint64_t bufferSize; uint64_t bufferSize;
@ -293,7 +292,6 @@ public:
Nan::Persistent<Object> jsCallingObj; Nan::Persistent<Object> jsCallingObj;
Nan::Persistent<Object> jsOracledb; Nan::Persistent<Object> jsOracledb;
Nan::Persistent<Object> jsBuffer; Nan::Persistent<Object> jsBuffer;
Nan::Persistent<Object> jsRows;
Nan::Persistent<Function> jsCallback; Nan::Persistent<Function> jsCallback;
njsBaton(Local<Function> callback, Local<Object> callingObj) : njsBaton(Local<Function> callback, Local<Object> callingObj) :
@ -307,8 +305,8 @@ public:
fetchAsStringTypes(NULL), numFetchAsBufferTypes(0), fetchAsStringTypes(NULL), numFetchAsBufferTypes(0),
fetchAsBufferTypes(NULL), protoILob(NULL), externalAuth(false), fetchAsBufferTypes(NULL), protoILob(NULL), externalAuth(false),
getRS(false), autoCommit(false), extendedMetaData(false), getRS(false), autoCommit(false), extendedMetaData(false),
keepQueryInfo(false), isReturning(false), isPLSQL(false), isReturning(false), isPLSQL(false), bufferSize(0), bufferPtr(NULL),
bufferSize(0), bufferPtr(NULL), lobOffset(0), lobAmount(0) { lobOffset(0), lobAmount(0) {
this->jsCallback.Reset(callback); this->jsCallback.Reset(callback);
this->jsCallingObj.Reset(callingObj); this->jsCallingObj.Reset(callingObj);
this->callingObj = Nan::ObjectWrap::Unwrap<njsCommon>(callingObj); this->callingObj = Nan::ObjectWrap::Unwrap<njsCommon>(callingObj);

View File

@ -230,64 +230,6 @@ bool njsConnection::ProcessQueryVars(njsBaton *baton, dpiStmt *dpiStmtHandle,
} }
//-----------------------------------------------------------------------------
// njsConnection::ProcessFetch()
// Process fetch from DPI statement.
//-----------------------------------------------------------------------------
bool njsConnection::ProcessFetch(njsBaton *baton)
{
uint32_t i, numRowsToFetch;
njsVariable *var;
int moreRows;
// determine how many rows to fetch; use fetchArraySize unless it is less
// than maxRows (no need to waste memory!)
numRowsToFetch = baton->fetchArraySize;
if (baton->maxRows > 0 && baton->maxRows < baton->fetchArraySize)
numRowsToFetch = baton->maxRows;
// create ODPI-C variables and define them, if necessary
for (i = 0; i < baton->numQueryVars; i++) {
var = &baton->queryVars[i];
if (var->dpiVarHandle && var->maxArraySize >= numRowsToFetch)
continue;
if (var->dpiVarHandle) {
dpiVar_release(var->dpiVarHandle);
var->dpiVarHandle = NULL;
}
if (dpiConn_newVar(baton->dpiConnHandle, var->varTypeNum,
var->nativeTypeNum, numRowsToFetch, var->maxSize, 1, 0,
NULL, &var->dpiVarHandle, &var->dpiVarData) < 0) {
baton->GetDPIError();
return false;
}
var->maxArraySize = numRowsToFetch;
if (dpiStmt_define(baton->dpiStmtHandle, i + 1,
var->dpiVarHandle) < 0) {
baton->GetDPIError();
return false;
}
}
// set fetch array size as requested
if (dpiStmt_setFetchArraySize(baton->dpiStmtHandle, numRowsToFetch) < 0) {
baton->GetDPIError();
return false;
}
// perform fetch
if (dpiStmt_fetchRows(baton->dpiStmtHandle, numRowsToFetch,
&baton->bufferRowIndex, &baton->rowsFetched, &moreRows) < 0) {
baton->GetDPIError();
return false;
}
if (!moreRows)
baton->maxRows = baton->rowsFetched;
return ProcessVars(baton, baton->queryVars, baton->numQueryVars,
baton->rowsFetched);
}
//----------------------------------------------------------------------------- //-----------------------------------------------------------------------------
// njsConnection::ProcessVars() // njsConnection::ProcessVars()
// Process variables used during binding or fetching. REF cursors must have // Process variables used during binding or fetching. REF cursors must have
@ -571,71 +513,6 @@ Local<Value> njsConnection::GetMetaData(njsVariable *vars, uint32_t numVars,
} }
//-----------------------------------------------------------------------------
// njsConnection::GetRows()
// Populate rows array with the number of rows fetched into buffers.
//-----------------------------------------------------------------------------
bool njsConnection::GetRows(njsBaton *baton, Local<Object> &rows)
{
Nan::EscapableHandleScope scope;
Local<Object> rowAsObj, tempRows;
Local<Array> rowAsArray;
Local<Value> val, keyVal;
uint32_t rowOffset;
njsVariable *var;
// check to see if we have any rows from a previous invocation
Local<Object> origRowsObj = Nan::New(baton->jsRows);
if (origRowsObj.IsEmpty()) {
rowOffset = 0;
tempRows = Nan::New<Array>(baton->rowsFetched);
} else {
// if no rows fetched, just return previous invocation's array as there
// is no need to concatenate!
if (baton->rowsFetched == 0) {
rows = scope.Escape(origRowsObj);
return true;
}
// create a new array that can contain the previous invocation's array
// and the new rows fetched this invocation
Local<Array> origRows = Local<Array>::Cast(origRowsObj);
uint32_t numRows = baton->rowsFetched + origRows->Length();
tempRows = Nan::New<Array>(numRows);
for (uint32_t row = 0; row < origRows->Length(); row++) {
Local<Value> val = Nan::Get(origRows, row).ToLocalChecked();
Nan::Set(tempRows, row, val);
}
rowOffset = origRows->Length();
}
// populate rows fetched this invocation
for (uint32_t row = 0; row < baton->rowsFetched; row++) {
if (baton->outFormat == NJS_ROWS_ARRAY)
rowAsArray = Nan::New<Array>(baton->numQueryVars);
else rowAsObj = Nan::New<Object>();
for (uint32_t col = 0; col < baton->numQueryVars; col++) {
var = &baton->queryVars[col];
if (!njsConnection::GetScalarValueFromVar(baton, var, row, val))
return false;
if (baton->outFormat == NJS_ROWS_ARRAY)
Nan::Set(rowAsArray, col, val);
else {
keyVal = Nan::New<String>(var->name).ToLocalChecked();
Nan::Set(rowAsObj, keyVal, val);
}
}
if (baton->outFormat == NJS_ROWS_ARRAY)
Nan::Set(tempRows, row + rowOffset, rowAsArray);
else Nan::Set(tempRows, row + rowOffset, rowAsObj);
}
rows = scope.Escape(tempRows);
return true;
}
//----------------------------------------------------------------------------- //-----------------------------------------------------------------------------
// njsConnection::ProcessBinds() // njsConnection::ProcessBinds()
// Process binds passed through to execute call. A variable is created for // Process binds passed through to execute call. A variable is created for
@ -1450,7 +1327,7 @@ NAN_METHOD(njsConnection::Execute)
std::string sql; std::string sql;
njsBaton *baton; njsBaton *baton;
connection = (njsConnection*) ValidateArgs(info, 2, 4); connection = (njsConnection*) ValidateArgs(info, 4, 4);
if (!connection) if (!connection)
return; return;
if (!connection->GetStringArg(info, 0, sql)) if (!connection->GetStringArg(info, 0, sql))
@ -1472,11 +1349,10 @@ NAN_METHOD(njsConnection::Execute)
baton->outFormat = oracledb->getOutFormat(); baton->outFormat = oracledb->getOutFormat();
baton->autoCommit = oracledb->getAutoCommit(); baton->autoCommit = oracledb->getAutoCommit();
baton->extendedMetaData = oracledb->getExtendedMetaData(); baton->extendedMetaData = oracledb->getExtendedMetaData();
baton->getRS = false;
} }
if (ok && info.Length() > 2) if (ok)
ok = ProcessBinds(info, 1, baton); ok = ProcessBinds(info, 1, baton);
if (ok && info.Length() > 3) if (ok)
ProcessOptions(info, 2, baton); ProcessOptions(info, 2, baton);
baton->CheckJSException(&tryCatch); baton->CheckJSException(&tryCatch);
baton->QueueWork("Execute", Async_Execute, Async_AfterExecute, 2); baton->QueueWork("Execute", Async_Execute, Async_AfterExecute, 2);
@ -1504,7 +1380,7 @@ void njsConnection::Async_Execute(njsBaton *baton)
return; return;
} }
// for queries, perform defines and ensure that rows have been fetched // for queries, perform defines
if (baton->numQueryVars > 0) { if (baton->numQueryVars > 0) {
// perform defines // perform defines
@ -1513,12 +1389,6 @@ void njsConnection::Async_Execute(njsBaton *baton)
baton->numQueryVars)) baton->numQueryVars))
return; return;
// when not getting a result set, process fetch completely
if (!baton->getRS) {
if (!ProcessFetch(baton))
return;
}
// for all other statements, determine the number of rows affected // for all other statements, determine the number of rows affected
// and process any LOBS for out binds, as needed // and process any LOBS for out binds, as needed
} else { } else {
@ -1531,14 +1401,6 @@ void njsConnection::Async_Execute(njsBaton *baton)
if (!ProcessVars(baton, baton->bindVars, baton->numBindVars, 1)) if (!ProcessVars(baton, baton->bindVars, baton->numBindVars, 1))
return; return;
} }
// if not getting a result set and there are no more rows to fetch, we no
// longer require the statement so release it now
if (!baton->getRS && baton->rowsFetched == baton->maxRows) {
if (dpiStmt_release(baton->dpiStmtHandle) < 0)
baton->GetDPIError();
baton->dpiStmtHandle = NULL;
}
} }
@ -1552,37 +1414,6 @@ void njsConnection::Async_AfterExecute(njsBaton *baton, Local<Value> argv[])
Local<Object> result = Nan::New<v8::Object>(); Local<Object> result = Nan::New<v8::Object>();
Local<Object> callingObj, rows; Local<Object> callingObj, rows;
Local<Function> callback; Local<Function> callback;
njsBaton *newBaton;
// for direct fetch, first check to see if more round trips are required
if (baton->queryVars && !baton->getRS) {
if (!njsConnection::GetRows(baton, rows))
return;
if (baton->rowsFetched > 0 &&
(baton->maxRows == 0 || baton->rowsFetched < baton->maxRows)) {
callback = Nan::New<Function>(baton->jsCallback);
callingObj = Nan::New(baton->jsCallingObj);
newBaton = new njsBaton(callback, callingObj);
baton->jsCallback.Reset();
newBaton->fetchArraySize = baton->fetchArraySize;
if (baton->maxRows > 0)
newBaton->maxRows = baton->maxRows - baton->rowsFetched;
newBaton->jsRows.Reset(rows);
newBaton->dpiStmtHandle = baton->dpiStmtHandle;
baton->dpiStmtHandle = NULL;
newBaton->dpiConnHandle = baton->dpiConnHandle;
baton->dpiConnHandle = NULL;
newBaton->jsOracledb.Reset(baton->jsOracledb);
newBaton->queryVars = baton->queryVars;
newBaton->numQueryVars = baton->numQueryVars;
newBaton->outFormat = baton->outFormat;
newBaton->getRS = false;
baton->keepQueryInfo = true;
newBaton->QueueWork("Execute", Async_ExecuteGetMoreRows,
Async_AfterExecute, 2);
return;
}
}
// handle queries // handle queries
if (baton->queryVars) { if (baton->queryVars) {
@ -1598,22 +1429,10 @@ void njsConnection::Async_AfterExecute(njsBaton *baton, Local<Value> argv[])
GetMetaData(baton->queryVars, baton->numQueryVars, GetMetaData(baton->queryVars, baton->numQueryVars,
baton->extendedMetaData)); baton->extendedMetaData));
// return result set, if requested to do so // assign result set
if (baton->getRS) { Local<Object> resultSet = njsResultSet::CreateFromBaton(baton);
Local<Object> resultSet = njsResultSet::CreateFromBaton(baton); Nan::Set(result, Nan::New<String>("resultSet").ToLocalChecked(),
Nan::Set(result, Nan::New<String>("rows").ToLocalChecked(), resultSet);
Nan::Undefined());
Nan::Set(result, Nan::New<String>("resultSet").ToLocalChecked(),
resultSet);
// otherwise, return rows
} else {
Nan::Set(result, Nan::New<v8::String>("rows").ToLocalChecked(),
rows);
Nan::Set(result,
Nan::New<v8::String>("resultSet").ToLocalChecked(),
Nan::Undefined());
}
} else { } else {
Nan::DefineOwnProperty (result, Nan::DefineOwnProperty (result,
@ -1637,17 +1456,6 @@ void njsConnection::Async_AfterExecute(njsBaton *baton, Local<Value> argv[])
} }
//-----------------------------------------------------------------------------
// njsConnection::Async_ExecuteGetMoreRows()
// Worker function for njsConnection::Execute() method called when additional
// round trips to the database are required.
//-----------------------------------------------------------------------------
void njsConnection::Async_ExecuteGetMoreRows(njsBaton *baton)
{
ProcessFetch(baton);
}
//----------------------------------------------------------------------------- //-----------------------------------------------------------------------------
// njsConnection::Release() // njsConnection::Release()
// Releases the connection from use by JS. This releases the connection back // Releases the connection from use by JS. This releases the connection back

View File

@ -71,10 +71,12 @@ public:
static Local<Object> CreateFromBaton(njsBaton *baton); static Local<Object> CreateFromBaton(njsBaton *baton);
static Local<Value> GetMetaData(njsVariable *vars, uint32_t numVars, static Local<Value> GetMetaData(njsVariable *vars, uint32_t numVars,
bool extendedMetaData); bool extendedMetaData);
static bool GetRows(njsBaton* baton, Local<Object> &rows); static bool GetScalarValueFromVar(njsBaton *baton, njsVariable *var,
static bool ProcessFetch(njsBaton* baton); uint32_t pos, Local<Value> &value);
static bool ProcessQueryVars(njsBaton* baton, dpiStmt *dpiStmtHandle, static bool ProcessQueryVars(njsBaton* baton, dpiStmt *dpiStmtHandle,
njsVariable *vars, uint32_t numVars); njsVariable *vars, uint32_t numVars);
static bool ProcessVars(njsBaton *baton, njsVariable *vars,
uint32_t numVars, uint32_t numElements);
static void Init(Handle<Object> target); static void Init(Handle<Object> target);
bool IsValid() const { return (dpiConnHandle) ? true : false; } bool IsValid() const { return (dpiConnHandle) ? true : false; }
njsErrorType GetInvalidErrorType() const { return errInvalidConnection; } njsErrorType GetInvalidErrorType() const { return errInvalidConnection; }
@ -95,7 +97,6 @@ private:
static NAN_METHOD(Execute); static NAN_METHOD(Execute);
static void Async_Execute(njsBaton *baton); static void Async_Execute(njsBaton *baton);
static void Async_AfterExecute(njsBaton *baton, Local<Value> argv[]); static void Async_AfterExecute(njsBaton *baton, Local<Value> argv[]);
static void Async_ExecuteGetMoreRows(njsBaton *baton);
// Release Method on Connection class // Release Method on Connection class
static NAN_METHOD(Release); static NAN_METHOD(Release);
@ -138,8 +139,6 @@ private:
Local<Value> value, uint32_t *bindType, uint32_t *maxSize, Local<Value> value, uint32_t *bindType, uint32_t *maxSize,
njsBaton *baton, bool scalarOnly = false); njsBaton *baton, bool scalarOnly = false);
static Local<Value> GetOutBinds(njsBaton *baton); static Local<Value> GetOutBinds(njsBaton *baton);
static bool GetScalarValueFromVar(njsBaton *baton, njsVariable *var,
uint32_t pos, Local<Value> &value);
static bool GetValueFromVar(njsBaton *baton, njsVariable *var, static bool GetValueFromVar(njsBaton *baton, njsVariable *var,
Local<Value> &value); Local<Value> &value);
static bool MapByName(njsBaton *baton, dpiQueryInfo *queryInfo, static bool MapByName(njsBaton *baton, dpiQueryInfo *queryInfo,
@ -157,8 +156,6 @@ private:
njsBaton *baton); njsBaton *baton);
static bool ProcessScalarBindValue(Local<Value> bindValue, static bool ProcessScalarBindValue(Local<Value> bindValue,
njsVariable *var, uint32_t pos, njsBaton *baton); njsVariable *var, uint32_t pos, njsBaton *baton);
static bool ProcessVars(njsBaton *baton, njsVariable *vars,
uint32_t numVars, uint32_t numElements);
static bool ProcessOptions(Nan::NAN_METHOD_ARGS_TYPE args, static bool ProcessOptions(Nan::NAN_METHOD_ARGS_TYPE args,
unsigned int index, njsBaton *baton); unsigned int index, njsBaton *baton);
static void SetTextAttribute(Nan::NAN_SETTER_ARGS_TYPE args, static void SetTextAttribute(Nan::NAN_SETTER_ARGS_TYPE args,

View File

@ -108,7 +108,7 @@ void njsResultSet::Init(Handle<Object> target)
//----------------------------------------------------------------------------- //-----------------------------------------------------------------------------
// njsResultSet::CreateFromBaton() // njsResultSet::CreateFromBaton()
// Create a new result set from the baton ( // Create a new result set from the baton.
//----------------------------------------------------------------------------- //-----------------------------------------------------------------------------
Local<Object> njsResultSet::CreateFromBaton(njsBaton *baton) Local<Object> njsResultSet::CreateFromBaton(njsBaton *baton)
{ {
@ -128,6 +128,10 @@ Local<Object> njsResultSet::CreateFromBaton(njsBaton *baton)
resultSet->outFormat = baton->outFormat; resultSet->outFormat = baton->outFormat;
resultSet->numQueryVars = baton->numQueryVars; resultSet->numQueryVars = baton->numQueryVars;
resultSet->queryVars = baton->queryVars; resultSet->queryVars = baton->queryVars;
if (!baton->getRS) {
resultSet->autoClose = true;
resultSet->maxRows = baton->maxRows;
}
baton->queryVars = NULL; baton->queryVars = NULL;
resultSet->activeBaton = NULL; resultSet->activeBaton = NULL;
resultSet->jsConnection.Reset(baton->jsCallingObj); resultSet->jsConnection.Reset(baton->jsCallingObj);
@ -163,6 +167,7 @@ bool njsResultSet::CreateFromRefCursor(njsBaton *baton, dpiStmt *dpiStmtHandle,
resultSet->activeBaton = NULL; resultSet->activeBaton = NULL;
resultSet->queryVars = queryVars; resultSet->queryVars = queryVars;
resultSet->numQueryVars = numQueryVars; resultSet->numQueryVars = numQueryVars;
baton->queryVars = NULL;
value = scope.Escape(obj); value = scope.Escape(obj);
return true; return true;
} }
@ -257,17 +262,11 @@ NAN_METHOD(njsResultSet::GetRows)
baton->error = njsMessages::Get(errBusyResultSet); baton->error = njsMessages::Get(errBusyResultSet);
else if (baton->error.empty()) { else if (baton->error.empty()) {
resultSet->activeBaton = baton; resultSet->activeBaton = baton;
baton->SetDPIStmtHandle(resultSet->dpiStmtHandle);
baton->SetDPIConnHandle(resultSet->dpiConnHandle);
baton->outFormat = resultSet->outFormat; baton->outFormat = resultSet->outFormat;
baton->queryVars = resultSet->queryVars;
baton->numQueryVars = resultSet->numQueryVars;
baton->keepQueryInfo = true;
baton->jsOracledb.Reset(resultSet->jsOracledb); baton->jsOracledb.Reset(resultSet->jsOracledb);
baton->maxRows = maxRows;
baton->fetchArraySize = maxRows; baton->fetchArraySize = maxRows;
} }
baton->QueueWork("GetRowsCommon", Async_GetRows, Async_AfterGetRows, 2); baton->QueueWork("GetRows", Async_GetRows, Async_AfterGetRows, 2);
} }
@ -277,7 +276,71 @@ NAN_METHOD(njsResultSet::GetRows)
//----------------------------------------------------------------------------- //-----------------------------------------------------------------------------
void njsResultSet::Async_GetRows(njsBaton *baton) void njsResultSet::Async_GetRows(njsBaton *baton)
{ {
njsConnection::ProcessFetch(baton); njsResultSet *resultSet = (njsResultSet*) baton->callingObj;
uint32_t i, numRowsToFetch;
njsVariable *var;
int moreRows;
// determine how many rows to fetch; use fetchArraySize unless it is less
// than maxRows (no need to waste memory!)
numRowsToFetch = baton->fetchArraySize;
if (resultSet->maxRows > 0 && resultSet->maxRows < numRowsToFetch)
numRowsToFetch = resultSet->maxRows;
// create ODPI-C variables and define them, if necessary
for (i = 0; i < resultSet->numQueryVars; i++) {
var = &resultSet->queryVars[i];
if (var->dpiVarHandle && var->maxArraySize >= numRowsToFetch)
continue;
if (var->dpiVarHandle) {
if (dpiVar_release(var->dpiVarHandle) < 0) {
baton->GetDPIError();
return;
}
var->dpiVarHandle = NULL;
}
if (dpiConn_newVar(resultSet->dpiConnHandle, var->varTypeNum,
var->nativeTypeNum, numRowsToFetch, var->maxSize, 1, 0, NULL,
&var->dpiVarHandle, &var->dpiVarData) < 0) {
baton->GetDPIError();
return;
}
var->maxArraySize = numRowsToFetch;
if (dpiStmt_define(resultSet->dpiStmtHandle, i + 1,
var->dpiVarHandle) < 0) {
baton->GetDPIError();
return;
}
}
// set fetch array size as requested
if (dpiStmt_setFetchArraySize(resultSet->dpiStmtHandle,
numRowsToFetch) < 0) {
baton->GetDPIError();
return;
}
// perform fetch
if (dpiStmt_fetchRows(resultSet->dpiStmtHandle, numRowsToFetch,
&baton->bufferRowIndex, &baton->rowsFetched, &moreRows) < 0) {
baton->GetDPIError();
return;
}
// result sets that should be auto closed are closed if the result set
// is exhaused or the maximum number of rows has been fetched
if (moreRows && resultSet->maxRows > 0) {
if (baton->rowsFetched == resultSet->maxRows)
moreRows = 0;
else resultSet->maxRows -= baton->rowsFetched;
}
if (!moreRows && resultSet->autoClose) {
dpiStmt_release(resultSet->dpiStmtHandle);
resultSet->dpiStmtHandle = NULL;
}
njsConnection::ProcessVars(baton, resultSet->queryVars,
resultSet->numQueryVars, baton->rowsFetched);
} }
@ -288,10 +351,37 @@ void njsResultSet::Async_GetRows(njsBaton *baton)
void njsResultSet::Async_AfterGetRows(njsBaton *baton, Local<Value> argv[]) void njsResultSet::Async_AfterGetRows(njsBaton *baton, Local<Value> argv[])
{ {
Nan::EscapableHandleScope scope; Nan::EscapableHandleScope scope;
Local<Object> rowAsObj, rows;
Local<Value> val, keyVal;
Local<Array> rowAsArray;
njsResultSet *resultSet;
njsVariable *var;
Local<Object> rows; rows = Nan::New<Array>(baton->rowsFetched);
if (!njsConnection::GetRows(baton, rows)) resultSet = (njsResultSet*) baton->callingObj;
return; for (uint32_t row = 0; row < baton->rowsFetched; row++) {
if (baton->outFormat == NJS_ROWS_ARRAY)
rowAsArray = Nan::New<Array>(resultSet->numQueryVars);
else rowAsObj = Nan::New<Object>();
for (uint32_t col = 0; col < resultSet->numQueryVars; col++) {
var = &resultSet->queryVars[col];
if (!njsConnection::GetScalarValueFromVar(baton, var, row, val))
return;
if (baton->outFormat == NJS_ROWS_ARRAY)
Nan::Set(rowAsArray, col, val);
else {
keyVal = Nan::New<String>(var->name).ToLocalChecked();
Nan::Set(rowAsObj, keyVal, val);
}
}
if (baton->outFormat == NJS_ROWS_ARRAY)
Nan::Set(rows, row, rowAsArray);
else Nan::Set(rows, row, rowAsObj);
}
if (!resultSet->dpiStmtHandle) {
delete [] resultSet->queryVars;
resultSet->queryVars = NULL;
}
argv[1] = scope.Escape(rows); argv[1] = scope.Escape(rows);
} }
@ -354,7 +444,6 @@ void njsResultSet::Async_AfterClose(njsBaton *baton, Local<Value> argv[])
resultSet->jsConnection.Reset(); resultSet->jsConnection.Reset();
resultSet->jsOracledb.Reset(); resultSet->jsOracledb.Reset();
baton->keepQueryInfo = false;
baton->queryVars = resultSet->queryVars; baton->queryVars = resultSet->queryVars;
baton->numQueryVars = resultSet->numQueryVars; baton->numQueryVars = resultSet->numQueryVars;
resultSet->queryVars = NULL; resultSet->queryVars = NULL;

View File

@ -79,7 +79,8 @@ public:
private: private:
njsResultSet() : dpiStmtHandle(NULL), dpiConnHandle(NULL), numQueryVars(0), njsResultSet() : dpiStmtHandle(NULL), dpiConnHandle(NULL), numQueryVars(0),
queryVars(NULL), outFormat(0), extendedMetaData(false) {} queryVars(NULL), outFormat(0), maxRows(0),
extendedMetaData(false), autoClose(false) {}
~njsResultSet(); ~njsResultSet();
static NAN_METHOD(New); static NAN_METHOD(New);
@ -105,7 +106,9 @@ private:
uint32_t numQueryVars; uint32_t numQueryVars;
njsVariable *queryVars; njsVariable *queryVars;
uint32_t outFormat; uint32_t outFormat;
uint32_t maxRows;
bool extendedMetaData; bool extendedMetaData;
bool autoClose;
Nan::Persistent<Object> jsOracledb; Nan::Persistent<Object> jsOracledb;
Nan::Persistent<Object> jsConnection; Nan::Persistent<Object> jsConnection;