Skip to content

Commit c38f3f7

Browse files
committed
Merge pull request #135 from Sage/prepare
Prepared statements to speed up large insert/update operations
2 parents ae5da11 + 8c966c4 commit c38f3f7

13 files changed

+384
-47
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,3 +5,4 @@ node_modules
55
*.swp
66
.lock*
77
.idea
8+
tests-settings-custom.*

README.md

Lines changed: 36 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -187,12 +187,12 @@ Javascript code:
187187
188188
### Querying large tables
189189
190-
To query large tables you should use a reader:
190+
To query large tables you should use a _reader_:
191191
192-
* `connection.reader(sql, args)` creates a reader
193-
* `reader.nextRow(callback)` returns the next row through the callback
192+
* `reader = connection.reader(sql, args)`: creates a reader
193+
* `reader.nextRow(callback)`: returns the next row through the callback
194194
* `reader.nextRows(count, callback)` returns the next `count` rows through the callback. `count` is optional and `nextRows` uses the prefetch row count when `count` is omitted.
195-
* `connection.setPrefetchRowCount(count)` configures the prefetch row count for the connection. Prefetching can have a dramatic impact on performance but uses more memory.
195+
* `connection.setPrefetchRowCount(count)`: configures the prefetch row count for the connection. Prefetching can have a dramatic impact on performance but uses more memory.
196196
197197
Example:
198198
@@ -221,6 +221,38 @@ doRead(function(err) {
221221
});
222222
```
223223
224+
### Large inserts or updates
225+
226+
To insert or update a large number of records you should use _prepared statements_ rather than individual `execute` calls on the connection object:
227+
228+
* `statement = connection.prepare(sql)`: creates a prepared statement.
229+
* `statement.execute(args, callback)`: executes the prepared statement with the values in `args`. You can call this repeatedly on the same `statement`.
230+
231+
Example:
232+
233+
```javascript
234+
235+
function doInsert(stmt, records, cb) {
236+
if (records.length > 0) {
237+
stmt.execute([records.shift()], function(err, count) {
238+
if (err) return cb(err);
239+
if (count !== 1) return cb(new Error("bad count: " + count));
240+
// recurse with remaining records
241+
doInsert(stmt, records, cb);
242+
});
243+
} else {
244+
// we are done
245+
return cb();
246+
}
247+
}
248+
249+
var statement = connection.prepare("INSERT INTO users (id, firstName, lastName) VALUES (:1, :2, :3)");
250+
doInsert(statement, users, function(err) {
251+
if (err) throw err; // or log it
252+
console.log("all records inserted");
253+
});
254+
```
255+
224256
# Limitations/Caveats
225257
226258
* Currently no native support for connection pooling (forthcoming; use generic-pool for now.)

binding.gyp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
"src/oracle_bindings.cpp",
77
"src/executeBaton.cpp",
88
"src/reader.cpp",
9+
"src/statement.cpp",
910
"src/outParam.cpp" ],
1011
"conditions": [
1112
["OS=='mac'", {

src/connection.cpp

Lines changed: 53 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
#include "executeBaton.h"
44
#include "commitBaton.h"
55
#include "rollbackBaton.h"
6+
#include "statement.h"
67
#include "reader.h"
78
#include "outParam.h"
89
#include <vector>
@@ -23,6 +24,7 @@ void Connection::Init(Handle<Object> target) {
2324
NODE_SET_PROTOTYPE_METHOD(uni::Deref(constructorTemplate), "execute", Execute);
2425
NODE_SET_PROTOTYPE_METHOD(uni::Deref(constructorTemplate), "executeSync", ExecuteSync);
2526
NODE_SET_PROTOTYPE_METHOD(uni::Deref(constructorTemplate), "readerHandle", CreateReader);
27+
NODE_SET_PROTOTYPE_METHOD(uni::Deref(constructorTemplate), "prepare", Prepare);
2628
NODE_SET_PROTOTYPE_METHOD(uni::Deref(constructorTemplate), "close", Close);
2729
NODE_SET_PROTOTYPE_METHOD(uni::Deref(constructorTemplate), "isConnected", IsConnected);
2830
NODE_SET_PROTOTYPE_METHOD(uni::Deref(constructorTemplate), "setAutoCommit", SetAutoCommit);
@@ -74,6 +76,23 @@ uni::CallbackType Connection::Execute(const uni::FunctionCallbackInfo& args) {
7476
UNI_RETURN(scope, args, Undefined());
7577
}
7678

79+
uni::CallbackType Connection::Prepare(const uni::FunctionCallbackInfo& args) {
80+
UNI_SCOPE(scope);
81+
Connection* connection = ObjectWrap::Unwrap<Connection>(args.This());
82+
83+
REQ_STRING_ARG(0, sql);
84+
85+
String::Utf8Value sqlVal(sql);
86+
87+
StatementBaton* baton = new StatementBaton(connection, *sqlVal, NULL);
88+
89+
Local<Object> statementHandle(uni::Deref(Statement::constructorTemplate)->GetFunction()->NewInstance());
90+
Statement* statement = ObjectWrap::Unwrap<Statement>(statementHandle);
91+
statement->setBaton(baton);
92+
93+
UNI_RETURN(scope, args, statementHandle);
94+
}
95+
7796
uni::CallbackType Connection::CreateReader(const uni::FunctionCallbackInfo& args) {
7897
UNI_SCOPE(scope);
7998
Connection* connection = ObjectWrap::Unwrap<Connection>(args.This());
@@ -413,25 +432,34 @@ void Connection::EIO_AfterRollback(uv_work_t* req, int status) {
413432
delete req;
414433
}
415434

416-
void Connection::EIO_Execute(uv_work_t* req) {
417-
ExecuteBaton* baton = static_cast<ExecuteBaton*>(req->data);
418-
435+
oracle::occi::Statement* Connection::CreateStatement(ExecuteBaton* baton) {
419436
baton->rows = NULL;
420437
baton->error = NULL;
421438

422-
oracle::occi::Statement* stmt = NULL;
423-
oracle::occi::ResultSet* rs = NULL;
439+
if (! baton->connection->m_connection) {
440+
baton->error = new std::string("Connection already closed");
441+
return NULL;
442+
}
424443
try {
425-
if(! baton->connection->m_connection) {
426-
baton->error = new std::string("Connection already closed");
427-
return;
428-
}
429-
stmt = baton->connection->m_connection->createStatement(baton->sql);
444+
oracle::occi::Statement* stmt = baton->connection->m_connection->createStatement(baton->sql);
430445
stmt->setAutoCommit(baton->connection->m_autoCommit);
431446
if (baton->connection->m_prefetchRowCount > 0) stmt->setPrefetchRowCount(baton->connection->m_prefetchRowCount);
432-
int outputParam = SetValuesOnStatement(stmt, baton);
433-
if (baton->error) goto cleanup;
447+
return stmt;
448+
} catch(oracle::occi::SQLException &ex) {
449+
baton->error = new string(ex.getMessage());
450+
return NULL;
451+
}
452+
}
453+
454+
void Connection::ExecuteStatement(ExecuteBaton* baton, oracle::occi::Statement* stmt) {
455+
oracle::occi::ResultSet* rs = NULL;
456+
457+
int outputParam = SetValuesOnStatement(stmt, baton);
458+
if (baton->error) goto cleanup;
459+
460+
if (!baton->outputs) baton->outputs = new std::vector<output_t*>();
434461

462+
try {
435463
int status = stmt->execute();
436464
if(status == oracle::occi::Statement::UPDATE_COUNT_AVAILABLE) {
437465
baton->updateCount = stmt->getUpdateCount();
@@ -508,12 +536,22 @@ void Connection::EIO_Execute(uv_work_t* req) {
508536
baton->error = new string("Unknown Error");
509537
}
510538
cleanup:
511-
if(stmt && rs) {
539+
if (rs) {
512540
stmt->closeResultSet(rs);
513541
rs = NULL;
514542
}
515-
if(stmt) {
516-
if(baton->connection->m_connection) {
543+
}
544+
545+
void Connection::EIO_Execute(uv_work_t* req) {
546+
ExecuteBaton* baton = static_cast<ExecuteBaton*>(req->data);
547+
548+
oracle::occi::Statement* stmt = CreateStatement(baton);
549+
if (baton->error) return;
550+
551+
ExecuteStatement(baton, stmt);
552+
553+
if (stmt) {
554+
if (baton->connection->m_connection) {
517555
baton->connection->m_connection->terminateStatement(stmt);
518556
}
519557
stmt = NULL;

src/connection.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,11 +77,14 @@ namespace uni {
7777
class Connection : public ObjectWrap {
7878
friend class Reader;
7979
friend class ReaderBaton;
80+
friend class Statement;
81+
friend class StatementBaton;
8082
public:
8183
static void Init(Handle<Object> target);
8284
static uni::CallbackType New(const uni::FunctionCallbackInfo& args);
8385
static uni::CallbackType Execute(const uni::FunctionCallbackInfo& args);
8486
static uni::CallbackType ExecuteSync(const uni::FunctionCallbackInfo& args);
87+
static uni::CallbackType Prepare(const uni::FunctionCallbackInfo& args);
8588
static uni::CallbackType CreateReader(const uni::FunctionCallbackInfo& args);
8689
static uni::CallbackType Close(const uni::FunctionCallbackInfo& args);
8790
static uni::CallbackType IsConnected(const uni::FunctionCallbackInfo& args);
@@ -105,6 +108,10 @@ class Connection : public ObjectWrap {
105108
oracle::occi::Environment* getEnvironment() { return m_environment; }
106109

107110
protected:
111+
// shared with Statement
112+
static oracle::occi::Statement* CreateStatement(ExecuteBaton* baton);
113+
static void ExecuteStatement(ExecuteBaton* baton, oracle::occi::Statement* stmt);
114+
108115
// shared with Reader
109116
oracle::occi::Connection* getConnection() { return m_connection; }
110117
bool getAutoCommit() { return m_autoCommit; }

src/executeBaton.cpp

Lines changed: 29 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,8 @@ ExecuteBaton::ExecuteBaton(Connection* connection, const char* sql, v8::Local<v8
1313
}
1414
this->outputs = new std::vector<output_t*>();
1515
this->error = NULL;
16-
CopyValuesToBaton(this, values);
16+
if (values) CopyValuesToBaton(this, values);
17+
this->rows = NULL;
1718
}
1819

1920
ExecuteBaton::~ExecuteBaton() {
@@ -24,6 +25,13 @@ ExecuteBaton::~ExecuteBaton() {
2425
delete col;
2526
}
2627

28+
ResetValues();
29+
ResetRows();
30+
ResetOutputs();
31+
ResetError();
32+
}
33+
34+
void ExecuteBaton::ResetValues() {
2735
for (std::vector<value_t*>::iterator iterator = values.begin(), end = values.end(); iterator != end; ++iterator) {
2836

2937
value_t* val = *iterator;
@@ -40,22 +48,11 @@ ExecuteBaton::~ExecuteBaton() {
4048
}
4149
delete val;
4250
}
43-
44-
ResetRows();
45-
46-
if(outputs) {
47-
for (std::vector<output_t*>::iterator iterator = outputs->begin(), end = outputs->end(); iterator != end; ++iterator) {
48-
output_t* o = *iterator;
49-
delete o;
50-
}
51-
delete outputs;
52-
}
53-
54-
if(error) delete error;
51+
values.clear();
5552
}
5653

5754
void ExecuteBaton::ResetRows() {
58-
if(rows) {
55+
if (rows) {
5956
for (std::vector<row_t*>::iterator iterator = rows->begin(), end = rows->end(); iterator != end; ++iterator) {
6057
row_t* currentRow = *iterator;
6158
delete currentRow;
@@ -66,6 +63,24 @@ void ExecuteBaton::ResetRows() {
6663
}
6764
}
6865

66+
void ExecuteBaton::ResetOutputs() {
67+
if (outputs) {
68+
for (std::vector<output_t*>::iterator iterator = outputs->begin(), end = outputs->end(); iterator != end; ++iterator) {
69+
output_t* o = *iterator;
70+
delete o;
71+
}
72+
delete outputs;
73+
outputs = NULL;
74+
}
75+
}
76+
77+
void ExecuteBaton::ResetError() {
78+
if (error) {
79+
delete error;
80+
error = NULL;
81+
}
82+
}
83+
6984
double CallDateMethod(v8::Local<v8::Date> date, const char* methodName) {
7085
Handle<Value> args[1]; // should be zero but on windows the compiler will not allow a zero length array
7186
Local<Value> result = Local<Function>::Cast(date->Get(String::New(methodName)))->Call(date, 0, args);

src/executeBaton.h

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,10 @@ class ExecuteBaton {
6161
public:
6262
ExecuteBaton(Connection* connection, const char* sql, v8::Local<v8::Array>* values, v8::Handle<v8::Function>* callback);
6363
~ExecuteBaton();
64+
void ResetValues();
6465
void ResetRows();
66+
void ResetOutputs();
67+
void ResetError();
6568

6669
Connection *connection;
6770
v8::Persistent<v8::Function> callback;
@@ -73,7 +76,6 @@ class ExecuteBaton {
7376
std::string* error;
7477
int updateCount;
7578

76-
private:
7779
static void CopyValuesToBaton(ExecuteBaton* baton, v8::Local<v8::Array>* values);
7880
};
7981

src/oracle_bindings.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11

22
#include "connection.h"
33
#include "oracle_bindings.h"
4+
#include "statement.h"
45
#include "reader.h"
56
#include "outParam.h"
67

@@ -187,6 +188,7 @@ extern "C" {
187188
static void init(Handle<Object> target) {
188189
OracleClient::Init(target);
189190
Connection::Init(target);
191+
Statement::Init(target);
190192
Reader::Init(target);
191193
OutParam::Init(target);
192194
}

src/readerBaton.h

Lines changed: 5 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,11 @@
33
#define _reader_baton_h_
44

55
#include "connection.h"
6-
#include "executeBaton.h"
6+
#include "statementBaton.h"
77

8-
class ReaderBaton : public ExecuteBaton {
8+
class ReaderBaton : public StatementBaton {
99
public:
10-
ReaderBaton(Connection* connection, const char* sql, v8::Local<v8::Array>* values) : ExecuteBaton(connection, sql, values, NULL) {
10+
ReaderBaton(Connection* connection, const char* sql, v8::Local<v8::Array>* values) : StatementBaton(connection, sql, values) {
1111
stmt = NULL;
1212
rs = NULL;
1313
done = false;
@@ -18,23 +18,15 @@ class ReaderBaton : public ExecuteBaton {
1818
}
1919

2020
void ResetStatement() {
21-
if(stmt && rs) {
21+
if (stmt && rs) {
2222
stmt->closeResultSet(rs);
2323
rs = NULL;
2424
}
25-
if(stmt) {
26-
if(connection->getConnection()) {
27-
connection->getConnection()->terminateStatement(stmt);
28-
}
29-
stmt = NULL;
30-
}
25+
StatementBaton::ResetStatement();
3126
}
3227

33-
oracle::occi::Statement* stmt;
3428
oracle::occi::ResultSet* rs;
3529
int count;
36-
bool done;
37-
bool busy;
3830
};
3931

4032
#endif

0 commit comments

Comments
 (0)