From 5063367567fd1abe47bafc2095fb4f9090a260e3 Mon Sep 17 00:00:00 2001 From: Daniel Lockyer Date: Sat, 30 Apr 2022 10:47:27 +0100 Subject: [PATCH] Improved performance by fetching column names once refs https://github.com/TryGhost/node-sqlite3/pull/1471/commits/ad569c00d642650347555ddea38e126a04fcf788 - 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 --- src/macros.h | 7 ++++++ src/statement.cc | 57 ++++++++++++++++++++++++------------------------ src/statement.h | 3 ++- 3 files changed, 38 insertions(+), 29 deletions(-) diff --git a/src/macros.h b/src/macros.h index 1c1a7c445..90c99741e 100644 --- a/src/macros.h +++ b/src/macros.h @@ -170,6 +170,13 @@ inline bool OtherIsInt(Napi::Number source) { stmt->Process(); \ stmt->db->Process(); +#define FETCH_COLUMN_NAMES(_handle, columns) \ + int cols = sqlite3_column_count(_handle); \ + for (int i = 0; i < cols; i++) { \ + const char* name = sqlite3_column_name(_handle, i); \ + columns.push_back(Napi::String::New(env, name)); \ + } + #define BACKUP_BEGIN(type) \ assert(baton); \ assert(baton->backup); \ diff --git a/src/statement.cc b/src/statement.cc index 1981df954..3db8fa7db 100644 --- a/src/statement.cc +++ b/src/statement.cc @@ -442,7 +442,9 @@ void Statement::Work_AfterGet(napi_env e, napi_status status, void* data) { if (!cb.IsUndefined() && cb.IsFunction()) { if (stmt->status == SQLITE_ROW) { // Create the result array from the data we acquired. - Napi::Value argv[] = { env.Null(), RowToJS(env, &baton->row) }; + std::vector names; + FETCH_COLUMN_NAMES(stmt->_handle, names); + Napi::Value argv[] = { env.Null(), RowToJS(env, &baton->row, names) }; TRY_CATCH_CALL(stmt->Value(), cb, 2, argv); } else { @@ -584,27 +586,23 @@ void Statement::Work_AfterAll(napi_env e, napi_status status, void* data) { // Fire callbacks. Napi::Function cb = baton->callback.Value(); if (!cb.IsUndefined() && cb.IsFunction()) { + Napi::Array result(Napi::Array::New(env, baton->rows.size())); + if (baton->rows.size()) { + std::vector names; + FETCH_COLUMN_NAMES(stmt->_handle, names); + // Create the result array from the data we acquired. - Napi::Array result(Napi::Array::New(env, baton->rows.size())); Rows::const_iterator it = baton->rows.begin(); Rows::const_iterator end = baton->rows.end(); for (int i = 0; it < end; ++it, i++) { std::unique_ptr row(*it); - (result).Set(i, RowToJS(env,row.get())); + result.Set(i, RowToJS(env, row.get(), names)); } - - Napi::Value argv[] = { env.Null(), result }; - TRY_CATCH_CALL(stmt->Value(), cb, 2, argv); - } - else { - // There were no result rows. - Napi::Value argv[] = { - env.Null(), - Napi::Array::New(env, 0) - }; - TRY_CATCH_CALL(stmt->Value(), cb, 2, argv); } + + Napi::Value argv[] = { env.Null(), result }; + TRY_CATCH_CALL(stmt->Value(), cb, 2, argv); } } @@ -700,6 +698,7 @@ void Statement::AsyncEach(uv_async_t* handle) { Napi::Env env = async->stmt->Env(); Napi::HandleScope scope(env); + Napi::Function cb = async->item_cb.Value(); while (true) { // Get the contents out of the data cache for us to process in the JS callback. @@ -712,8 +711,11 @@ void Statement::AsyncEach(uv_async_t* handle) { break; } - Napi::Function cb = async->item_cb.Value(); if (!cb.IsUndefined() && cb.IsFunction()) { + if (async->stmt->columns.size() == 0) { + FETCH_COLUMN_NAMES(async->stmt->_handle, async->stmt->columns); + } + Napi::Value argv[2]; argv[0] = env.Null(); @@ -721,22 +723,22 @@ void Statement::AsyncEach(uv_async_t* handle) { Rows::const_iterator end = rows.end(); for (int i = 0; it < end; ++it, i++) { std::unique_ptr row(*it); - argv[1] = RowToJS(env,row.get()); + argv[1] = RowToJS(env, row.get(), async->stmt->columns); async->retrieved++; TRY_CATCH_CALL(async->stmt->Value(), cb, 2, argv); } } } - Napi::Function cb = async->completed_cb.Value(); if (async->completed) { - if (!cb.IsEmpty() && - cb.IsFunction()) { + async->stmt->columns.clear(); + Napi::Function completed_cb = async->completed_cb.Value(); + if (!completed_cb.IsEmpty() && completed_cb.IsFunction()) { Napi::Value argv[] = { env.Null(), Napi::Number::New(env, async->retrieved) }; - TRY_CATCH_CALL(async->stmt->Value(), cb, 2, argv); + TRY_CATCH_CALL(async->stmt->Value(), completed_cb, 2, argv); } uv_close(reinterpret_cast(handle), CloseCallback); } @@ -796,7 +798,7 @@ void Statement::Work_AfterReset(napi_env e, napi_status status, void* data) { STATEMENT_END(); } -Napi::Value Statement::RowToJS(Napi::Env env, Row* row) { +Napi::Value Statement::RowToJS(Napi::Env env, Row* row, std::vector names) { Napi::EscapableHandleScope scope(env); Napi::Object result = Napi::Object::New(env); @@ -826,7 +828,7 @@ Napi::Value Statement::RowToJS(Napi::Env env, Row* row) { } break; } - (result).Set(Napi::String::New(env, field->name.c_str()), value); + result.Set(names[i], value); DELETE_FIELD(field); } @@ -839,26 +841,25 @@ void Statement::GetRow(Row* row, sqlite3_stmt* stmt) { for (int i = 0; i < cols; i++) { int type = sqlite3_column_type(stmt, i); - const char* name = sqlite3_column_name(stmt, i); switch (type) { case SQLITE_INTEGER: { - row->push_back(new Values::Integer(name, sqlite3_column_int64(stmt, i))); + row->push_back(new Values::Integer(i, sqlite3_column_int64(stmt, i))); } break; case SQLITE_FLOAT: { - row->push_back(new Values::Float(name, sqlite3_column_double(stmt, i))); + row->push_back(new Values::Float(i, sqlite3_column_double(stmt, i))); } break; case SQLITE_TEXT: { const char* text = (const char*)sqlite3_column_text(stmt, i); int length = sqlite3_column_bytes(stmt, i); - row->push_back(new Values::Text(name, length, text)); + row->push_back(new Values::Text(i, length, text)); } break; case SQLITE_BLOB: { const void* blob = sqlite3_column_blob(stmt, i); int length = sqlite3_column_bytes(stmt, i); - row->push_back(new Values::Blob(name, length, blob)); + row->push_back(new Values::Blob(i, length, blob)); } break; case SQLITE_NULL: { - row->push_back(new Values::Null(name)); + row->push_back(new Values::Null(i)); } break; default: assert(false); diff --git a/src/statement.h b/src/statement.h index 904e52175..f11c8192a 100644 --- a/src/statement.h +++ b/src/statement.h @@ -225,7 +225,7 @@ class Statement : public Napi::ObjectWrap { bool Bind(const Parameters ¶meters); static void GetRow(Row* row, sqlite3_stmt* stmt); - static Napi::Value RowToJS(Napi::Env env, Row* row); + static Napi::Value RowToJS(Napi::Env env, Row* row, std::vector names); void Schedule(Work_Callback callback, Baton* baton); void Process(); void CleanQueue(); @@ -242,6 +242,7 @@ class Statement : public Napi::ObjectWrap { bool locked; bool finalized; std::queue queue; + std::vector columns; }; }