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;
}
// 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
// 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.
function execute(a1, a2, a3, a4) {
function execute(sql, a2, a3, a4) {
var self = this;
var binds = [];
var executeOpts = {};
var executeCb;
var executeOpts;
var custExecuteCb;
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) {
case 2:
nodbUtil.assert(typeof a2 === 'function', 'NJS-006', 2);
executeCb = a2;
break;
case 3:
nodbUtil.assert(nodbUtil.isObjectOrArray(a2), 'NJS-006', 2);
nodbUtil.assert(typeof a3 === 'function', 'NJS-006', 3);
binds = a2;
executeCb = a3;
break;
case 4:
nodbUtil.assert(nodbUtil.isObjectOrArray(a2), 'NJS-006', 2);
nodbUtil.assert(nodbUtil.isObject(a3), 'NJS-006', 3);
nodbUtil.assert(typeof a4 === 'function', 'NJS-006', 4);
binds = a2;
executeOpts = a3;
executeCb = a4;
break;
}
@ -108,36 +159,28 @@ function execute(a1, a2, a3, a4) {
// Need to extend resultsets which may come from either the query results
// or outBinds.
if (result.resultSet) {
resultset.extend(result.resultSet, self._oracledb, executeOpts);
} else if (result.outBinds) {
outBindsKeys = Object.keys(result.outBinds);
if (executeOpts.resultSet) {
resultset.extend(result.resultSet, self._oracledb, executeOpts);
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) {
if (result.outBinds[outBindsKeys[outBindsIdx]] instanceof self._oracledb.ResultSet) {
resultset.extend(result.outBinds[outBindsKeys[outBindsIdx]], self._oracledb, executeOpts);
for (outBindsIdx = 0; outBindsIdx < outBindsKeys.length; outBindsIdx += 1) {
if (result.outBinds[outBindsKeys[outBindsIdx]] instanceof self._oracledb.ResultSet) {
resultset.extend(result.outBinds[outBindsKeys[outBindsIdx]], self._oracledb, executeOpts);
}
}
}
executeCb(null, result);
}
executeCb(null, result);
};
switch (arguments.length) {
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;
}
self._execute.call(self, sql, binds, executeOpts, custExecuteCb);
}
executePromisified = nodbUtil.promisify(execute);

View File

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

View File

@ -282,7 +282,6 @@ public:
bool getRS;
bool autoCommit;
bool extendedMetaData;
bool keepQueryInfo;
bool isReturning;
bool isPLSQL;
uint64_t bufferSize;
@ -293,7 +292,6 @@ public:
Nan::Persistent<Object> jsCallingObj;
Nan::Persistent<Object> jsOracledb;
Nan::Persistent<Object> jsBuffer;
Nan::Persistent<Object> jsRows;
Nan::Persistent<Function> jsCallback;
njsBaton(Local<Function> callback, Local<Object> callingObj) :
@ -307,8 +305,8 @@ public:
fetchAsStringTypes(NULL), numFetchAsBufferTypes(0),
fetchAsBufferTypes(NULL), protoILob(NULL), externalAuth(false),
getRS(false), autoCommit(false), extendedMetaData(false),
keepQueryInfo(false), isReturning(false), isPLSQL(false),
bufferSize(0), bufferPtr(NULL), lobOffset(0), lobAmount(0) {
isReturning(false), isPLSQL(false), bufferSize(0), bufferPtr(NULL),
lobOffset(0), lobAmount(0) {
this->jsCallback.Reset(callback);
this->jsCallingObj.Reset(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()
// 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()
// Process binds passed through to execute call. A variable is created for
@ -1450,7 +1327,7 @@ NAN_METHOD(njsConnection::Execute)
std::string sql;
njsBaton *baton;
connection = (njsConnection*) ValidateArgs(info, 2, 4);
connection = (njsConnection*) ValidateArgs(info, 4, 4);
if (!connection)
return;
if (!connection->GetStringArg(info, 0, sql))
@ -1472,11 +1349,10 @@ NAN_METHOD(njsConnection::Execute)
baton->outFormat = oracledb->getOutFormat();
baton->autoCommit = oracledb->getAutoCommit();
baton->extendedMetaData = oracledb->getExtendedMetaData();
baton->getRS = false;
}
if (ok && info.Length() > 2)
if (ok)
ok = ProcessBinds(info, 1, baton);
if (ok && info.Length() > 3)
if (ok)
ProcessOptions(info, 2, baton);
baton->CheckJSException(&tryCatch);
baton->QueueWork("Execute", Async_Execute, Async_AfterExecute, 2);
@ -1504,7 +1380,7 @@ void njsConnection::Async_Execute(njsBaton *baton)
return;
}
// for queries, perform defines and ensure that rows have been fetched
// for queries, perform defines
if (baton->numQueryVars > 0) {
// perform defines
@ -1513,12 +1389,6 @@ void njsConnection::Async_Execute(njsBaton *baton)
baton->numQueryVars))
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
// and process any LOBS for out binds, as needed
} else {
@ -1531,14 +1401,6 @@ void njsConnection::Async_Execute(njsBaton *baton)
if (!ProcessVars(baton, baton->bindVars, baton->numBindVars, 1))
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> callingObj, rows;
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
if (baton->queryVars) {
@ -1598,22 +1429,10 @@ void njsConnection::Async_AfterExecute(njsBaton *baton, Local<Value> argv[])
GetMetaData(baton->queryVars, baton->numQueryVars,
baton->extendedMetaData));
// return result set, if requested to do so
if (baton->getRS) {
Local<Object> resultSet = njsResultSet::CreateFromBaton(baton);
Nan::Set(result, Nan::New<String>("rows").ToLocalChecked(),
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());
}
// assign result set
Local<Object> resultSet = njsResultSet::CreateFromBaton(baton);
Nan::Set(result, Nan::New<String>("resultSet").ToLocalChecked(),
resultSet);
} else {
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()
// 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<Value> GetMetaData(njsVariable *vars, uint32_t numVars,
bool extendedMetaData);
static bool GetRows(njsBaton* baton, Local<Object> &rows);
static bool ProcessFetch(njsBaton* baton);
static bool GetScalarValueFromVar(njsBaton *baton, njsVariable *var,
uint32_t pos, Local<Value> &value);
static bool ProcessQueryVars(njsBaton* baton, dpiStmt *dpiStmtHandle,
njsVariable *vars, uint32_t numVars);
static bool ProcessVars(njsBaton *baton, njsVariable *vars,
uint32_t numVars, uint32_t numElements);
static void Init(Handle<Object> target);
bool IsValid() const { return (dpiConnHandle) ? true : false; }
njsErrorType GetInvalidErrorType() const { return errInvalidConnection; }
@ -95,7 +97,6 @@ private:
static NAN_METHOD(Execute);
static void Async_Execute(njsBaton *baton);
static void Async_AfterExecute(njsBaton *baton, Local<Value> argv[]);
static void Async_ExecuteGetMoreRows(njsBaton *baton);
// Release Method on Connection class
static NAN_METHOD(Release);
@ -138,8 +139,6 @@ private:
Local<Value> value, uint32_t *bindType, uint32_t *maxSize,
njsBaton *baton, bool scalarOnly = false);
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,
Local<Value> &value);
static bool MapByName(njsBaton *baton, dpiQueryInfo *queryInfo,
@ -157,8 +156,6 @@ private:
njsBaton *baton);
static bool ProcessScalarBindValue(Local<Value> bindValue,
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,
unsigned int index, njsBaton *baton);
static void SetTextAttribute(Nan::NAN_SETTER_ARGS_TYPE args,

View File

@ -108,7 +108,7 @@ void njsResultSet::Init(Handle<Object> target)
//-----------------------------------------------------------------------------
// njsResultSet::CreateFromBaton()
// Create a new result set from the baton (
// Create a new result set from the baton.
//-----------------------------------------------------------------------------
Local<Object> njsResultSet::CreateFromBaton(njsBaton *baton)
{
@ -128,6 +128,10 @@ Local<Object> njsResultSet::CreateFromBaton(njsBaton *baton)
resultSet->outFormat = baton->outFormat;
resultSet->numQueryVars = baton->numQueryVars;
resultSet->queryVars = baton->queryVars;
if (!baton->getRS) {
resultSet->autoClose = true;
resultSet->maxRows = baton->maxRows;
}
baton->queryVars = NULL;
resultSet->activeBaton = NULL;
resultSet->jsConnection.Reset(baton->jsCallingObj);
@ -163,6 +167,7 @@ bool njsResultSet::CreateFromRefCursor(njsBaton *baton, dpiStmt *dpiStmtHandle,
resultSet->activeBaton = NULL;
resultSet->queryVars = queryVars;
resultSet->numQueryVars = numQueryVars;
baton->queryVars = NULL;
value = scope.Escape(obj);
return true;
}
@ -257,17 +262,11 @@ NAN_METHOD(njsResultSet::GetRows)
baton->error = njsMessages::Get(errBusyResultSet);
else if (baton->error.empty()) {
resultSet->activeBaton = baton;
baton->SetDPIStmtHandle(resultSet->dpiStmtHandle);
baton->SetDPIConnHandle(resultSet->dpiConnHandle);
baton->outFormat = resultSet->outFormat;
baton->queryVars = resultSet->queryVars;
baton->numQueryVars = resultSet->numQueryVars;
baton->keepQueryInfo = true;
baton->jsOracledb.Reset(resultSet->jsOracledb);
baton->maxRows = 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)
{
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[])
{
Nan::EscapableHandleScope scope;
Local<Object> rowAsObj, rows;
Local<Value> val, keyVal;
Local<Array> rowAsArray;
njsResultSet *resultSet;
njsVariable *var;
Local<Object> rows;
if (!njsConnection::GetRows(baton, rows))
return;
rows = Nan::New<Array>(baton->rowsFetched);
resultSet = (njsResultSet*) baton->callingObj;
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);
}
@ -354,7 +444,6 @@ void njsResultSet::Async_AfterClose(njsBaton *baton, Local<Value> argv[])
resultSet->jsConnection.Reset();
resultSet->jsOracledb.Reset();
baton->keepQueryInfo = false;
baton->queryVars = resultSet->queryVars;
baton->numQueryVars = resultSet->numQueryVars;
resultSet->queryVars = NULL;

View File

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