Skip to content

Commit 5063367

Browse files
committed
Improved performance by fetching column names once
refs ad569c0 - this commit optimizes the library by only fetching column names once whilst preparing the data to return - we save a lot of calls to the sqlite3 lib and this improves `.each` and `.all`, which would ordinarily do this once per row - `benchmark/select.js` statistics go from `db.each: 92.393ms, db.all: 101.076ms` to `db.each: 83.254ms, db.all: 93.211ms`, which is a ~9-10% improvement
1 parent 216e935 commit 5063367

File tree

3 files changed

+38
-29
lines changed

3 files changed

+38
-29
lines changed

src/macros.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,13 @@ inline bool OtherIsInt(Napi::Number source) {
170170
stmt->Process(); \
171171
stmt->db->Process();
172172

173+
#define FETCH_COLUMN_NAMES(_handle, columns) \
174+
int cols = sqlite3_column_count(_handle); \
175+
for (int i = 0; i < cols; i++) { \
176+
const char* name = sqlite3_column_name(_handle, i); \
177+
columns.push_back(Napi::String::New(env, name)); \
178+
}
179+
173180
#define BACKUP_BEGIN(type) \
174181
assert(baton); \
175182
assert(baton->backup); \

src/statement.cc

Lines changed: 29 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -442,7 +442,9 @@ void Statement::Work_AfterGet(napi_env e, napi_status status, void* data) {
442442
if (!cb.IsUndefined() && cb.IsFunction()) {
443443
if (stmt->status == SQLITE_ROW) {
444444
// Create the result array from the data we acquired.
445-
Napi::Value argv[] = { env.Null(), RowToJS(env, &baton->row) };
445+
std::vector<Napi::String> names;
446+
FETCH_COLUMN_NAMES(stmt->_handle, names);
447+
Napi::Value argv[] = { env.Null(), RowToJS(env, &baton->row, names) };
446448
TRY_CATCH_CALL(stmt->Value(), cb, 2, argv);
447449
}
448450
else {
@@ -584,27 +586,23 @@ void Statement::Work_AfterAll(napi_env e, napi_status status, void* data) {
584586
// Fire callbacks.
585587
Napi::Function cb = baton->callback.Value();
586588
if (!cb.IsUndefined() && cb.IsFunction()) {
589+
Napi::Array result(Napi::Array::New(env, baton->rows.size()));
590+
587591
if (baton->rows.size()) {
592+
std::vector<Napi::String> names;
593+
FETCH_COLUMN_NAMES(stmt->_handle, names);
594+
588595
// Create the result array from the data we acquired.
589-
Napi::Array result(Napi::Array::New(env, baton->rows.size()));
590596
Rows::const_iterator it = baton->rows.begin();
591597
Rows::const_iterator end = baton->rows.end();
592598
for (int i = 0; it < end; ++it, i++) {
593599
std::unique_ptr<Row> row(*it);
594-
(result).Set(i, RowToJS(env,row.get()));
600+
result.Set(i, RowToJS(env, row.get(), names));
595601
}
596-
597-
Napi::Value argv[] = { env.Null(), result };
598-
TRY_CATCH_CALL(stmt->Value(), cb, 2, argv);
599-
}
600-
else {
601-
// There were no result rows.
602-
Napi::Value argv[] = {
603-
env.Null(),
604-
Napi::Array::New(env, 0)
605-
};
606-
TRY_CATCH_CALL(stmt->Value(), cb, 2, argv);
607602
}
603+
604+
Napi::Value argv[] = { env.Null(), result };
605+
TRY_CATCH_CALL(stmt->Value(), cb, 2, argv);
608606
}
609607
}
610608

@@ -700,6 +698,7 @@ void Statement::AsyncEach(uv_async_t* handle) {
700698

701699
Napi::Env env = async->stmt->Env();
702700
Napi::HandleScope scope(env);
701+
Napi::Function cb = async->item_cb.Value();
703702

704703
while (true) {
705704
// Get the contents out of the data cache for us to process in the JS callback.
@@ -712,31 +711,34 @@ void Statement::AsyncEach(uv_async_t* handle) {
712711
break;
713712
}
714713

715-
Napi::Function cb = async->item_cb.Value();
716714
if (!cb.IsUndefined() && cb.IsFunction()) {
715+
if (async->stmt->columns.size() == 0) {
716+
FETCH_COLUMN_NAMES(async->stmt->_handle, async->stmt->columns);
717+
}
718+
717719
Napi::Value argv[2];
718720
argv[0] = env.Null();
719721

720722
Rows::const_iterator it = rows.begin();
721723
Rows::const_iterator end = rows.end();
722724
for (int i = 0; it < end; ++it, i++) {
723725
std::unique_ptr<Row> row(*it);
724-
argv[1] = RowToJS(env,row.get());
726+
argv[1] = RowToJS(env, row.get(), async->stmt->columns);
725727
async->retrieved++;
726728
TRY_CATCH_CALL(async->stmt->Value(), cb, 2, argv);
727729
}
728730
}
729731
}
730732

731-
Napi::Function cb = async->completed_cb.Value();
732733
if (async->completed) {
733-
if (!cb.IsEmpty() &&
734-
cb.IsFunction()) {
734+
async->stmt->columns.clear();
735+
Napi::Function completed_cb = async->completed_cb.Value();
736+
if (!completed_cb.IsEmpty() && completed_cb.IsFunction()) {
735737
Napi::Value argv[] = {
736738
env.Null(),
737739
Napi::Number::New(env, async->retrieved)
738740
};
739-
TRY_CATCH_CALL(async->stmt->Value(), cb, 2, argv);
741+
TRY_CATCH_CALL(async->stmt->Value(), completed_cb, 2, argv);
740742
}
741743
uv_close(reinterpret_cast<uv_handle_t*>(handle), CloseCallback);
742744
}
@@ -796,7 +798,7 @@ void Statement::Work_AfterReset(napi_env e, napi_status status, void* data) {
796798
STATEMENT_END();
797799
}
798800

799-
Napi::Value Statement::RowToJS(Napi::Env env, Row* row) {
801+
Napi::Value Statement::RowToJS(Napi::Env env, Row* row, std::vector<Napi::String> names) {
800802
Napi::EscapableHandleScope scope(env);
801803

802804
Napi::Object result = Napi::Object::New(env);
@@ -826,7 +828,7 @@ Napi::Value Statement::RowToJS(Napi::Env env, Row* row) {
826828
} break;
827829
}
828830

829-
(result).Set(Napi::String::New(env, field->name.c_str()), value);
831+
result.Set(names[i], value);
830832

831833
DELETE_FIELD(field);
832834
}
@@ -839,26 +841,25 @@ void Statement::GetRow(Row* row, sqlite3_stmt* stmt) {
839841

840842
for (int i = 0; i < cols; i++) {
841843
int type = sqlite3_column_type(stmt, i);
842-
const char* name = sqlite3_column_name(stmt, i);
843844
switch (type) {
844845
case SQLITE_INTEGER: {
845-
row->push_back(new Values::Integer(name, sqlite3_column_int64(stmt, i)));
846+
row->push_back(new Values::Integer(i, sqlite3_column_int64(stmt, i)));
846847
} break;
847848
case SQLITE_FLOAT: {
848-
row->push_back(new Values::Float(name, sqlite3_column_double(stmt, i)));
849+
row->push_back(new Values::Float(i, sqlite3_column_double(stmt, i)));
849850
} break;
850851
case SQLITE_TEXT: {
851852
const char* text = (const char*)sqlite3_column_text(stmt, i);
852853
int length = sqlite3_column_bytes(stmt, i);
853-
row->push_back(new Values::Text(name, length, text));
854+
row->push_back(new Values::Text(i, length, text));
854855
} break;
855856
case SQLITE_BLOB: {
856857
const void* blob = sqlite3_column_blob(stmt, i);
857858
int length = sqlite3_column_bytes(stmt, i);
858-
row->push_back(new Values::Blob(name, length, blob));
859+
row->push_back(new Values::Blob(i, length, blob));
859860
} break;
860861
case SQLITE_NULL: {
861-
row->push_back(new Values::Null(name));
862+
row->push_back(new Values::Null(i));
862863
} break;
863864
default:
864865
assert(false);

src/statement.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -225,7 +225,7 @@ class Statement : public Napi::ObjectWrap<Statement> {
225225
bool Bind(const Parameters &parameters);
226226

227227
static void GetRow(Row* row, sqlite3_stmt* stmt);
228-
static Napi::Value RowToJS(Napi::Env env, Row* row);
228+
static Napi::Value RowToJS(Napi::Env env, Row* row, std::vector<Napi::String> names);
229229
void Schedule(Work_Callback callback, Baton* baton);
230230
void Process();
231231
void CleanQueue();
@@ -242,6 +242,7 @@ class Statement : public Napi::ObjectWrap<Statement> {
242242
bool locked;
243243
bool finalized;
244244
std::queue<Call*> queue;
245+
std::vector<Napi::String> columns;
245246
};
246247

247248
}

0 commit comments

Comments
 (0)