@@ -3856,8 +3856,42 @@ class alignas(64) OperationResult {
38563856 resolver->Resolve (context, Undefined (isolate)).Check ();
38573857 }
38583858 };
3859+ class PreparedStatement {
3860+ public:
3861+ explicit PreparedStatement (BaseObjectPtr<Database> db, sqlite3_stmt* stmt)
3862+ : db_(std::move(db)), stmt_(stmt) {}
3863+
3864+ void Connect (Isolate* isolate,
3865+ Local<Context> context,
3866+ Promise::Resolver* resolver) const {
3867+ auto * stmt = stmt_;
3868+ if (!db_->IsOpen ()) {
3869+ // Database is closing, therefore directly create a disposed Statement.
3870+ (void )sqlite3_finalize (stmt);
3871+ stmt = nullptr ;
3872+ }
3873+ auto stmt_obj =
3874+ Statement::Create (Environment::GetCurrent (context), db_, stmt);
3875+ if (stmt_obj) {
3876+ resolver->Resolve (context, stmt_obj->object ()).Check ();
3877+ } else {
3878+ Local<String> error_message =
3879+ String::NewFromUtf8 (isolate,
3880+ " Failed to create Statement object" ,
3881+ NewStringType::kNormal )
3882+ .ToLocalChecked ();
3883+ Local<Object> error =
3884+ Exception::Error (error_message)->ToObject (context).ToLocalChecked ();
3885+ resolver->Reject (context, error).Check ();
3886+ }
3887+ }
38593888
3860- using variant_type = std::variant<Void, Rejected>;
3889+ private:
3890+ BaseObjectPtr<Database> db_;
3891+ sqlite3_stmt* stmt_;
3892+ };
3893+
3894+ using variant_type = std::variant<Void, Rejected, PreparedStatement>;
38613895
38623896 public:
38633897 static OperationResult RejectErrorCode (OperationBase* origin,
@@ -3871,6 +3905,11 @@ class alignas(64) OperationResult {
38713905 static OperationResult ResolveVoid (OperationBase* origin) {
38723906 return OperationResult{origin, Void{}};
38733907 }
3908+ static OperationResult ResolvePreparedStatement (OperationBase* origin,
3909+ BaseObjectPtr<Database> db,
3910+ sqlite3_stmt* stmt) {
3911+ return OperationResult{origin, PreparedStatement{std::move (db), stmt}};
3912+ }
38743913
38753914 template <typename T>
38763915 requires std::constructible_from<variant_type, T>
@@ -3892,6 +3931,47 @@ class alignas(64) OperationResult {
38923931 variant_type result_;
38933932};
38943933
3934+ class PrepareStatementOperation : private OperationBase {
3935+ public:
3936+ PrepareStatementOperation (Global<Promise::Resolver>&& resolver,
3937+ BaseObjectPtr<Database>&& db,
3938+ std::pmr::string&& sql)
3939+ : OperationBase(std::move(resolver)),
3940+ db_ (std::move(db)),
3941+ sql_(std::move(sql)) {}
3942+
3943+ OperationResult operator ()(sqlite3* connection) {
3944+ sqlite3_stmt* stmt = nullptr ;
3945+ int error_code =
3946+ sqlite3_prepare_v2 (connection, sql_.c_str (), -1 , &stmt, nullptr );
3947+ return error_code == SQLITE_OK
3948+ ? OperationResult::ResolvePreparedStatement (
3949+ this , std::move (db_), stmt)
3950+ : OperationResult::RejectLastError (this , connection);
3951+ }
3952+
3953+ private:
3954+ BaseObjectPtr<Database> db_;
3955+ std::pmr::string sql_;
3956+ };
3957+
3958+ class FinalizeStatementOperation : private OperationBase {
3959+ public:
3960+ FinalizeStatementOperation (Global<Promise::Resolver>&& resolver,
3961+ sqlite3_stmt* stmt)
3962+ : OperationBase(std::move(resolver)), stmt_(stmt) {}
3963+
3964+ OperationResult operator ()(sqlite3* connection) {
3965+ int error_code = sqlite3_finalize (stmt_);
3966+ CHECK_NE (error_code, SQLITE_MISUSE);
3967+ stmt_ = nullptr ;
3968+ return OperationResult::ResolveVoid (this );
3969+ }
3970+
3971+ private:
3972+ sqlite3_stmt* stmt_;
3973+ };
3974+
38953975class ExecOperation : private OperationBase {
38963976 public:
38973977 ExecOperation (Global<Promise::Resolver>&& resolver, std::pmr::string&& sql)
@@ -3927,7 +4007,21 @@ class CloseOperation : private OperationBase {
39274007 }
39284008};
39294009
3930- using Operation = std::variant<ExecOperation, CloseOperation>;
4010+ using Operation = std::variant<ExecOperation,
4011+ PrepareStatementOperation,
4012+ FinalizeStatementOperation,
4013+ CloseOperation>;
4014+
4015+ template <typename T, typename V>
4016+ struct is_contained_in_variant ;
4017+ template <typename T, typename ... Args>
4018+ struct is_contained_in_variant <T, std::variant<Args...>> {
4019+ static constexpr bool value{(std::is_same_v<T, Args> || ...)};
4020+ };
4021+
4022+ template <typename Op>
4023+ inline constexpr bool is_operation_type =
4024+ is_contained_in_variant<Op, Operation>::value;
39314025
39324026enum class QueuePushResult {
39334027 kQueueFull = -1 ,
@@ -3955,7 +4049,8 @@ class DatabaseOperationQueue {
39554049 : QueuePushResult::kSuccess ;
39564050 }
39574051 template <typename Op, typename ... Args>
3958- requires std::constructible_from<Operation,
4052+ requires is_operation_type<Op> &&
4053+ std::constructible_from<Operation,
39594054 std::in_place_type_t <Op>,
39604055 Global<Promise::Resolver>&&,
39614056 Args&&...>
@@ -4214,6 +4309,7 @@ v8::Local<v8::FunctionTemplate> CreateDatabaseConstructorTemplate(
42144309
42154310 SetProtoMethod (isolate, tmpl, " close" , Database::Close);
42164311 SetProtoAsyncDispose (isolate, tmpl, Database::AsyncDispose);
4312+ SetProtoMethod (isolate, tmpl, " prepare" , Database::Prepare);
42174313 SetProtoMethod (isolate, tmpl, " exec" , Database::Exec);
42184314
42194315 Local<String> sqlite_type_key = FIXED_ONE_BYTE_STRING (isolate, " sqlite-type" );
@@ -4299,13 +4395,16 @@ Local<Promise> Database::AsyncDisposeImpl() {
42994395 // We can't use Schedule here, because Schedule queues a MicroTask which
43004396 // would try to access the Database object after destruction if this is
43014397 // called from the destructor.
4302- if (next_batch_ == nullptr ) {
4303- next_batch_ = std::make_unique<DatabaseOperationQueue>(
4304- 1U , std::pmr::get_default_resource ());
4305- }
4398+ // Furthermore we always need to schedule the CloseOperation in a separate
4399+ // batch, to ensure that it runs after all previously scheduled operations,
4400+ // because e.g. PrepareStatementOperations need to connect their results
4401+ // first.
4402+ ProcessNextBatch ();
4403+ next_batch_ = std::make_unique<DatabaseOperationQueue>(
4404+ 1U , std::pmr::get_default_resource ());
43064405 CHECK_NE (next_batch_->PushEmplace <CloseOperation>(isolate, resolver),
43074406 QueuePushResult::kQueueFull );
4308- executor_-> ScheduleBatch ( std::move (next_batch_) );
4407+ ProcessNextBatch ( );
43094408 executor_.release ()->Dispose ();
43104409
43114410 connection_ = nullptr ;
@@ -4354,9 +4453,43 @@ void Database::AsyncDispose(const v8::FunctionCallbackInfo<v8::Value>& args) {
43544453 return ;
43554454 }
43564455
4456+ // We don't need to dispose statements during destruction, because all
4457+ // Statement instances keep a BaseObjectPtr to the Database and therefore
4458+ // can't outlive the Database. Therefore this shouldn't be executed as part of
4459+ // db->AsyncDisposeImpl().
4460+ // Calling stmt->Dispose modifies the statements_ set, therefore make a copy.
4461+ for (Statement* stmt : std::vector<Statement*>(db->statements_ .begin (),
4462+ db->statements_ .end ())) {
4463+ stmt->Dispose ();
4464+ }
4465+
43574466 args.GetReturnValue ().Set (db->AsyncDisposeImpl ());
43584467}
43594468
4469+ void Database::Prepare (const v8::FunctionCallbackInfo<v8::Value>& args) {
4470+ Database* db;
4471+ ASSIGN_OR_RETURN_UNWRAP (&db, args.This ());
4472+ Environment* env = Environment::GetCurrent (args);
4473+ // TODO(BurningEnlightenment): these should be rejections
4474+ THROW_AND_RETURN_ON_BAD_STATE (env, !db->IsOpen (), " database is not open" );
4475+
4476+ if (!args[0 ]->IsString ()) {
4477+ THROW_ERR_INVALID_ARG_TYPE (env->isolate (),
4478+ " The \" sql\" argument must be a string." );
4479+ return ;
4480+ }
4481+ Utf8Value sql (env->isolate (), args[0 ].As <String>());
4482+ args.GetReturnValue ().Set (db->Schedule <PrepareStatementOperation>(
4483+ BaseObjectPtr<Database>(db), std::pmr::string (*sql, sql.length ())));
4484+ }
4485+
4486+ void Database::TrackStatement (Statement* statement) {
4487+ statements_.insert (statement);
4488+ }
4489+ void Database::UntrackStatement (Statement* statement) {
4490+ statements_.erase (statement);
4491+ }
4492+
43604493void Database::Exec (const v8::FunctionCallbackInfo<v8::Value>& args) {
43614494 Database* db;
43624495 // TODO(BurningEnlightenment): these should be rejections
@@ -4374,6 +4507,94 @@ void Database::Exec(const v8::FunctionCallbackInfo<v8::Value>& args) {
43744507 db->Schedule <ExecOperation>(std::pmr::string (*sql, sql.length ())));
43754508}
43764509
4510+ Statement::Statement (Environment* env,
4511+ v8::Local<v8::Object> object,
4512+ BaseObjectPtr<Database> db,
4513+ sqlite3_stmt* stmt)
4514+ : BaseObject(env, object), db_(std::move(db)), statement_(stmt) {
4515+ if (stmt == nullptr ) {
4516+ db_ = nullptr ;
4517+ } else {
4518+ CHECK_NOT_NULL (db_);
4519+ db_->TrackStatement (this );
4520+ }
4521+ }
4522+
4523+ Statement::~Statement () {
4524+ if (!IsDisposed ()) {
4525+ // Our operations keep a BaseObjectPtr to this Statement, so we can be sure
4526+ // that no operations are running or will run after this point. The only
4527+ // exception to this is the FinalizeStatementOperation, but it can only be
4528+ // queued by Statement::Dispose.
4529+ sqlite3_finalize (statement_);
4530+ db_->UntrackStatement (this );
4531+ }
4532+ }
4533+
4534+ void Statement::MemoryInfo (MemoryTracker* tracker) const {}
4535+
4536+ Local<FunctionTemplate> Statement::GetConstructorTemplate (Environment* env) {
4537+ Local<FunctionTemplate> tmpl = env->sqlite_statement_constructor_template ();
4538+ if (tmpl.IsEmpty ()) {
4539+ Isolate* isolate = env->isolate ();
4540+ tmpl = NewFunctionTemplate (isolate, IllegalConstructor);
4541+ tmpl->SetClassName (FIXED_ONE_BYTE_STRING (isolate, " Statement" ));
4542+ tmpl->InstanceTemplate ()->SetInternalFieldCount (
4543+ Statement::kInternalFieldCount );
4544+
4545+ SetProtoDispose (isolate, tmpl, Statement::Dispose);
4546+ SetSideEffectFreeGetter (isolate,
4547+ tmpl,
4548+ FIXED_ONE_BYTE_STRING (isolate, " isDisposed" ),
4549+ Statement::IsDisposedGetter);
4550+
4551+ env->set_sqlite_statement_constructor_template (tmpl);
4552+ }
4553+ return tmpl;
4554+ }
4555+ BaseObjectPtr<Statement> Statement::Create (Environment* env,
4556+ BaseObjectPtr<Database> db,
4557+ sqlite3_stmt* stmt) {
4558+ Local<Object> obj;
4559+ if (!GetConstructorTemplate (env)
4560+ ->InstanceTemplate ()
4561+ ->NewInstance (env->context ())
4562+ .ToLocal (&obj)) {
4563+ return nullptr ;
4564+ }
4565+
4566+ return MakeBaseObject<Statement>(env, obj, std::move (db), stmt);
4567+ }
4568+
4569+ void Statement::Dispose (const FunctionCallbackInfo<Value>& args) {
4570+ Statement* stmt;
4571+ ASSIGN_OR_RETURN_UNWRAP (&stmt, args.This ());
4572+ stmt->Dispose ();
4573+ args.GetReturnValue ().SetUndefined ();
4574+ }
4575+
4576+ void Statement::Dispose () {
4577+ if (IsDisposed ()) {
4578+ return ;
4579+ }
4580+ // Finalizing is a no-fail operation, so we don't need to check the result or
4581+ // return a promise.
4582+ (void )db_->Schedule <FinalizeStatementOperation>(
4583+ std::exchange (statement_, nullptr ));
4584+ std::exchange (db_, nullptr )->UntrackStatement (this );
4585+ }
4586+
4587+ void Statement::IsDisposedGetter (const FunctionCallbackInfo<Value>& args) {
4588+ Statement* stmt;
4589+ ASSIGN_OR_RETURN_UNWRAP (&stmt, args.This ());
4590+ Environment* env = Environment::GetCurrent (args);
4591+ args.GetReturnValue ().Set (Boolean::New (env->isolate (), stmt->IsDisposed ()));
4592+ }
4593+
4594+ bool Statement::IsDisposed () const {
4595+ return statement_ == nullptr ;
4596+ }
4597+
43774598void DefineConstants (Local<Object> target) {
43784599 NODE_DEFINE_CONSTANT (target, SQLITE_CHANGESET_OMIT);
43794600 NODE_DEFINE_CONSTANT (target, SQLITE_CHANGESET_REPLACE);
@@ -4450,9 +4671,8 @@ static void Initialize(Local<Object> target,
44504671 context, target, " Session" , Session::GetConstructorTemplate (env));
44514672 SetConstructorFunction (
44524673 context, target, " Database" , CreateDatabaseConstructorTemplate (env));
4453- target
4454- ->Set (context, FIXED_ONE_BYTE_STRING (isolate, " Statement" ), Null (isolate))
4455- .Check ();
4674+ SetConstructorFunction (
4675+ context, target, " Statement" , Statement::GetConstructorTemplate (env));
44564676
44574677 target->Set (context, env->constants_string (), constants).Check ();
44584678
0 commit comments