Skip to content

Commit

Permalink
Improved performance by fetching column names once
Browse files Browse the repository at this point in the history
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
  • Loading branch information
daniellockyer committed Apr 30, 2022
1 parent 216e935 commit 5063367
Show file tree
Hide file tree
Showing 3 changed files with 38 additions and 29 deletions.
7 changes: 7 additions & 0 deletions src/macros.h
Expand Up @@ -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); \
Expand Down
57 changes: 29 additions & 28 deletions src/statement.cc
Expand Up @@ -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<Napi::String> 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 {
Expand Down Expand Up @@ -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<Napi::String> 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> 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);
}
}

Expand Down Expand Up @@ -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.
Expand All @@ -712,31 +711,34 @@ 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();

Rows::const_iterator it = rows.begin();
Rows::const_iterator end = rows.end();
for (int i = 0; it < end; ++it, i++) {
std::unique_ptr<Row> 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<uv_handle_t*>(handle), CloseCallback);
}
Expand Down Expand Up @@ -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<Napi::String> names) {
Napi::EscapableHandleScope scope(env);

Napi::Object result = Napi::Object::New(env);
Expand Down Expand Up @@ -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);
}
Expand All @@ -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);
Expand Down
3 changes: 2 additions & 1 deletion src/statement.h
Expand Up @@ -225,7 +225,7 @@ class Statement : public Napi::ObjectWrap<Statement> {
bool Bind(const Parameters &parameters);

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<Napi::String> names);
void Schedule(Work_Callback callback, Baton* baton);
void Process();
void CleanQueue();
Expand All @@ -242,6 +242,7 @@ class Statement : public Napi::ObjectWrap<Statement> {
bool locked;
bool finalized;
std::queue<Call*> queue;
std::vector<Napi::String> columns;
};

}
Expand Down

0 comments on commit 5063367

Please sign in to comment.