Skip to content

sqlite: refactor prepared statement iterator #57569

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Apr 3, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/env_properties.h
Original file line number Diff line number Diff line change
Expand Up @@ -448,6 +448,7 @@
V(shutdown_wrap_template, v8::ObjectTemplate) \
V(socketaddress_constructor_template, v8::FunctionTemplate) \
V(sqlite_statement_sync_constructor_template, v8::FunctionTemplate) \
V(sqlite_statement_sync_iterator_constructor_template, v8::FunctionTemplate) \
V(sqlite_session_constructor_template, v8::FunctionTemplate) \
V(streambaseentry_ctor_template, v8::FunctionTemplate) \
V(streambaseoutputstream_constructor_template, v8::ObjectTemplate) \
Expand Down
302 changes: 122 additions & 180 deletions src/node_sqlite.cc
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@ using v8::ConstructorBehavior;
using v8::Context;
using v8::DontDelete;
using v8::Exception;
using v8::External;
using v8::Function;
using v8::FunctionCallback;
using v8::FunctionCallbackInfo;
Expand Down Expand Up @@ -1615,142 +1614,12 @@ void StatementSync::All(const FunctionCallbackInfo<Value>& args) {
args.GetReturnValue().Set(Array::New(isolate, rows.data(), rows.size()));
}

void StatementSync::IterateReturnCallback(
const FunctionCallbackInfo<Value>& args) {
Environment* env = Environment::GetCurrent(args);
auto isolate = env->isolate();
auto context = isolate->GetCurrentContext();

auto self = args.This();
// iterator has fetch all result or break, prevent next func to return result
if (self->Set(context, env->isfinished_string(), Boolean::New(isolate, true))
.IsNothing()) {
// An error will have been scheduled.
return;
}

Local<Value> val;
if (!self->Get(context, env->statement_string()).ToLocal(&val)) {
// An error will have been scheduled.
return;
}
auto external_stmt = Local<External>::Cast(val);
auto stmt = static_cast<StatementSync*>(external_stmt->Value());
if (!stmt->IsFinalized()) {
sqlite3_reset(stmt->statement_);
}

LocalVector<Name> keys(isolate, {env->done_string(), env->value_string()});
LocalVector<Value> values(isolate,
{Boolean::New(isolate, true), Null(isolate)});

DCHECK_EQ(keys.size(), values.size());
Local<Object> result = Object::New(
isolate, Null(isolate), keys.data(), values.data(), keys.size());
args.GetReturnValue().Set(result);
}

void StatementSync::IterateNextCallback(
const FunctionCallbackInfo<Value>& args) {
Environment* env = Environment::GetCurrent(args);
auto isolate = env->isolate();
auto context = isolate->GetCurrentContext();

auto self = args.This();

Local<Value> val;
if (!self->Get(context, env->isfinished_string()).ToLocal(&val)) {
// An error will have been scheduled.
return;
}

// skip iteration if is_finished
auto is_finished = Local<Boolean>::Cast(val);
if (is_finished->Value()) {
Local<Name> keys[] = {env->done_string(), env->value_string()};
Local<Value> values[] = {Boolean::New(isolate, true), Null(isolate)};
static_assert(arraysize(keys) == arraysize(values));
Local<Object> result = Object::New(
isolate, Null(isolate), &keys[0], &values[0], arraysize(keys));
args.GetReturnValue().Set(result);
return;
}

if (!self->Get(context, env->statement_string()).ToLocal(&val)) {
// An error will have been scheduled.
return;
}

auto external_stmt = Local<External>::Cast(val);
auto stmt = static_cast<StatementSync*>(external_stmt->Value());

if (!self->Get(context, env->num_cols_string()).ToLocal(&val)) {
// An error will have been scheduled.
return;
}

auto num_cols = Local<Integer>::Cast(val)->Value();

THROW_AND_RETURN_ON_BAD_STATE(
env, stmt->IsFinalized(), "statement has been finalized");

int r = sqlite3_step(stmt->statement_);
if (r != SQLITE_ROW) {
CHECK_ERROR_OR_THROW(
env->isolate(), stmt->db_.get(), r, SQLITE_DONE, void());

// cleanup when no more rows to fetch
sqlite3_reset(stmt->statement_);
if (self->Set(
context, env->isfinished_string(), Boolean::New(isolate, true))
.IsNothing()) {
// An error would have been scheduled
return;
}

LocalVector<Name> keys(isolate, {env->done_string(), env->value_string()});
LocalVector<Value> values(isolate,
{Boolean::New(isolate, true), Null(isolate)});

DCHECK_EQ(keys.size(), values.size());
Local<Object> result = Object::New(
isolate, Null(isolate), keys.data(), values.data(), keys.size());
args.GetReturnValue().Set(result);
return;
}

LocalVector<Name> row_keys(isolate);
row_keys.reserve(num_cols);
LocalVector<Value> row_values(isolate);
row_values.reserve(num_cols);
for (int i = 0; i < num_cols; ++i) {
Local<Name> key;
if (!stmt->ColumnNameToName(i).ToLocal(&key)) return;
Local<Value> val;
if (!stmt->ColumnToValue(i).ToLocal(&val)) return;
row_keys.emplace_back(key);
row_values.emplace_back(val);
}

Local<Object> row = Object::New(
isolate, Null(isolate), row_keys.data(), row_values.data(), num_cols);

LocalVector<Name> keys(isolate, {env->done_string(), env->value_string()});
LocalVector<Value> values(isolate, {Boolean::New(isolate, false), row});

DCHECK_EQ(keys.size(), values.size());
Local<Object> result = Object::New(
isolate, Null(isolate), keys.data(), values.data(), keys.size());
args.GetReturnValue().Set(result);
}

void StatementSync::Iterate(const FunctionCallbackInfo<Value>& args) {
StatementSync* stmt;
ASSIGN_OR_RETURN_UNWRAP(&stmt, args.This());
Environment* env = Environment::GetCurrent(args);
THROW_AND_RETURN_ON_BAD_STATE(
env, stmt->IsFinalized(), "statement has been finalized");
auto isolate = env->isolate();
auto context = env->context();
int r = sqlite3_reset(stmt->statement_);
CHECK_ERROR_OR_THROW(env->isolate(), stmt->db_.get(), r, SQLITE_OK, void());
Expand All @@ -1759,67 +1628,28 @@ void StatementSync::Iterate(const FunctionCallbackInfo<Value>& args) {
return;
}

Local<Function> next_func;
Local<Function> return_func;
if (!Function::New(context, StatementSync::IterateNextCallback)
.ToLocal(&next_func) ||
!Function::New(context, StatementSync::IterateReturnCallback)
.ToLocal(&return_func)) {
// An error will have been scheduled.
return;
}

Local<Name> keys[] = {env->next_string(), env->return_string()};
Local<Value> values[] = {next_func, return_func};
static_assert(arraysize(keys) == arraysize(values));

Local<Object> global = context->Global();
Local<Value> js_iterator;
Local<Value> js_iterator_prototype;
if (!global->Get(context, env->iterator_string()).ToLocal(&js_iterator))
if (!global->Get(context, env->iterator_string()).ToLocal(&js_iterator)) {
return;
}
if (!js_iterator.As<Object>()
->Get(context, env->prototype_string())
.ToLocal(&js_iterator_prototype))
return;

Local<Object> iterable_iterator = Object::New(
isolate, js_iterator_prototype, &keys[0], &values[0], arraysize(keys));

auto num_cols_pd = v8::PropertyDescriptor(
v8::Integer::New(isolate, sqlite3_column_count(stmt->statement_)), false);
num_cols_pd.set_enumerable(false);
num_cols_pd.set_configurable(false);
if (iterable_iterator
->DefineProperty(context, env->num_cols_string(), num_cols_pd)
.IsNothing()) {
// An error will have been scheduled.
.ToLocal(&js_iterator_prototype)) {
return;
}

auto stmt_pd =
v8::PropertyDescriptor(v8::External::New(isolate, stmt), false);
stmt_pd.set_enumerable(false);
stmt_pd.set_configurable(false);
if (iterable_iterator
->DefineProperty(context, env->statement_string(), stmt_pd)
BaseObjectPtr<StatementSyncIterator> iter =
StatementSyncIterator::Create(env, BaseObjectPtr<StatementSync>(stmt));
if (iter->object()
->GetPrototype()
.As<Object>()
->SetPrototype(context, js_iterator_prototype)
.IsNothing()) {
// An error will have been scheduled.
return;
}

auto is_finished_pd =
v8::PropertyDescriptor(v8::Boolean::New(isolate, false), true);
stmt_pd.set_enumerable(false);
stmt_pd.set_configurable(false);
if (iterable_iterator
->DefineProperty(context, env->isfinished_string(), is_finished_pd)
.IsNothing()) {
// An error will have been scheduled.
return;
}

args.GetReturnValue().Set(iterable_iterator);
args.GetReturnValue().Set(iter->object());
}

void StatementSync::Get(const FunctionCallbackInfo<Value>& args) {
Expand Down Expand Up @@ -2118,6 +1948,118 @@ BaseObjectPtr<StatementSync> StatementSync::Create(
return MakeBaseObject<StatementSync>(env, obj, std::move(db), stmt);
}

StatementSyncIterator::StatementSyncIterator(Environment* env,
Local<Object> object,
BaseObjectPtr<StatementSync> stmt)
: BaseObject(env, object), stmt_(std::move(stmt)) {
MakeWeak();
done_ = false;
}

StatementSyncIterator::~StatementSyncIterator() {}
void StatementSyncIterator::MemoryInfo(MemoryTracker* tracker) const {}

Local<FunctionTemplate> StatementSyncIterator::GetConstructorTemplate(
Environment* env) {
Local<FunctionTemplate> tmpl =
env->sqlite_statement_sync_iterator_constructor_template();
if (tmpl.IsEmpty()) {
Isolate* isolate = env->isolate();
tmpl = NewFunctionTemplate(isolate, IllegalConstructor);
tmpl->SetClassName(FIXED_ONE_BYTE_STRING(isolate, "StatementSyncIterator"));
tmpl->InstanceTemplate()->SetInternalFieldCount(
StatementSync::kInternalFieldCount);
SetProtoMethod(isolate, tmpl, "next", StatementSyncIterator::Next);
SetProtoMethod(isolate, tmpl, "return", StatementSyncIterator::Return);
env->set_sqlite_statement_sync_iterator_constructor_template(tmpl);
}
return tmpl;
}

BaseObjectPtr<StatementSyncIterator> StatementSyncIterator::Create(
Environment* env, BaseObjectPtr<StatementSync> stmt) {
Local<Object> obj;
if (!GetConstructorTemplate(env)
->InstanceTemplate()
->NewInstance(env->context())
.ToLocal(&obj)) {
return BaseObjectPtr<StatementSyncIterator>();
}

return MakeBaseObject<StatementSyncIterator>(env, obj, std::move(stmt));
}

void StatementSyncIterator::Next(const FunctionCallbackInfo<Value>& args) {
StatementSyncIterator* iter;
ASSIGN_OR_RETURN_UNWRAP(&iter, args.This());
Environment* env = Environment::GetCurrent(args);
THROW_AND_RETURN_ON_BAD_STATE(
env, iter->stmt_->IsFinalized(), "statement has been finalized");
Isolate* isolate = env->isolate();
LocalVector<Name> keys(isolate, {env->done_string(), env->value_string()});
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit... LocalVector here might be overkill when a simple array would do. Non-blocking tho.


if (iter->done_) {
LocalVector<Value> values(isolate,
{Boolean::New(isolate, true), Null(isolate)});
Local<Object> result = Object::New(
isolate, Null(isolate), keys.data(), values.data(), keys.size());
args.GetReturnValue().Set(result);
return;
}

int r = sqlite3_step(iter->stmt_->statement_);
if (r != SQLITE_ROW) {
CHECK_ERROR_OR_THROW(
env->isolate(), iter->stmt_->db_.get(), r, SQLITE_DONE, void());
sqlite3_reset(iter->stmt_->statement_);
LocalVector<Value> values(isolate,
{Boolean::New(isolate, true), Null(isolate)});
Local<Object> result = Object::New(
isolate, Null(isolate), keys.data(), values.data(), keys.size());
args.GetReturnValue().Set(result);
return;
}

int num_cols = sqlite3_column_count(iter->stmt_->statement_);
LocalVector<Name> row_keys(isolate);
LocalVector<Value> row_values(isolate);
row_keys.reserve(num_cols);
row_values.reserve(num_cols);
for (int i = 0; i < num_cols; ++i) {
Local<Name> key;
if (!iter->stmt_->ColumnNameToName(i).ToLocal(&key)) return;
Local<Value> val;
if (!iter->stmt_->ColumnToValue(i).ToLocal(&val)) return;
row_keys.emplace_back(key);
row_values.emplace_back(val);
}

Local<Object> row = Object::New(
isolate, Null(isolate), row_keys.data(), row_values.data(), num_cols);
LocalVector<Value> values(isolate, {Boolean::New(isolate, false), row});
Local<Object> result = Object::New(
isolate, Null(isolate), keys.data(), values.data(), keys.size());
args.GetReturnValue().Set(result);
}

void StatementSyncIterator::Return(const FunctionCallbackInfo<Value>& args) {
StatementSyncIterator* iter;
ASSIGN_OR_RETURN_UNWRAP(&iter, args.This());
Environment* env = Environment::GetCurrent(args);
THROW_AND_RETURN_ON_BAD_STATE(
env, iter->stmt_->IsFinalized(), "statement has been finalized");
Isolate* isolate = env->isolate();

sqlite3_reset(iter->stmt_->statement_);
iter->done_ = true;
LocalVector<Name> keys(isolate, {env->done_string(), env->value_string()});
LocalVector<Value> values(isolate,
{Boolean::New(isolate, true), Null(isolate)});
Local<Object> result = Object::New(
isolate, Null(isolate), keys.data(), values.data(), keys.size());
args.GetReturnValue().Set(result);
}

Session::Session(Environment* env,
Local<Object> object,
BaseObjectWeakPtr<DatabaseSync> database,
Expand Down
27 changes: 23 additions & 4 deletions src/node_sqlite.h
Original file line number Diff line number Diff line change
Expand Up @@ -142,10 +142,29 @@ class StatementSync : public BaseObject {
v8::MaybeLocal<v8::Value> ColumnToValue(const int column);
v8::MaybeLocal<v8::Name> ColumnNameToName(const int column);

static void IterateNextCallback(
const v8::FunctionCallbackInfo<v8::Value>& args);
static void IterateReturnCallback(
const v8::FunctionCallbackInfo<v8::Value>& args);
friend class StatementSyncIterator;
};

class StatementSyncIterator : public BaseObject {
public:
StatementSyncIterator(Environment* env,
v8::Local<v8::Object> object,
BaseObjectPtr<StatementSync> stmt);
void MemoryInfo(MemoryTracker* tracker) const override;
static v8::Local<v8::FunctionTemplate> GetConstructorTemplate(
Environment* env);
static BaseObjectPtr<StatementSyncIterator> Create(
Environment* env, BaseObjectPtr<StatementSync> stmt);
static void Next(const v8::FunctionCallbackInfo<v8::Value>& args);
static void Return(const v8::FunctionCallbackInfo<v8::Value>& args);

SET_MEMORY_INFO_NAME(StatementSyncIterator)
SET_SELF_SIZE(StatementSyncIterator)

private:
~StatementSyncIterator() override;
BaseObjectPtr<StatementSync> stmt_;
bool done_;
};

using Sqlite3ChangesetGenFunc = int (*)(sqlite3_session*, int*, void**);
Expand Down
Loading
Loading