@@ -3586,8 +3586,42 @@ class alignas(64) OperationResult {
35863586 resolver->Resolve (context, Undefined (isolate)).Check ();
35873587 }
35883588 };
3589+ class PreparedStatement {
3590+ public:
3591+ explicit PreparedStatement (BaseObjectPtr<Database> db, sqlite3_stmt* stmt)
3592+ : db_(std::move(db)), stmt_(stmt) {}
3593+
3594+ void Connect (Isolate* isolate,
3595+ Local<Context> context,
3596+ Promise::Resolver* resolver) const {
3597+ auto * stmt = stmt_;
3598+ if (!db_->IsOpen ()) {
3599+ // Database is closing, therefore directly create a disposed Statement.
3600+ (void )sqlite3_finalize (stmt);
3601+ stmt = nullptr ;
3602+ }
3603+ auto stmt_obj =
3604+ Statement::Create (Environment::GetCurrent (context), db_, stmt);
3605+ if (stmt_obj) {
3606+ resolver->Resolve (context, stmt_obj->object ()).Check ();
3607+ } else {
3608+ Local<String> error_message =
3609+ String::NewFromUtf8 (isolate,
3610+ " Failed to create Statement object" ,
3611+ NewStringType::kNormal )
3612+ .ToLocalChecked ();
3613+ Local<Object> error =
3614+ Exception::Error (error_message)->ToObject (context).ToLocalChecked ();
3615+ resolver->Reject (context, error).Check ();
3616+ }
3617+ }
35893618
3590- using variant_type = std::variant<Void, Rejected>;
3619+ private:
3620+ BaseObjectPtr<Database> db_;
3621+ sqlite3_stmt* stmt_;
3622+ };
3623+
3624+ using variant_type = std::variant<Void, Rejected, PreparedStatement>;
35913625
35923626 public:
35933627 static OperationResult RejectErrorCode (OperationBase* origin,
@@ -3601,6 +3635,11 @@ class alignas(64) OperationResult {
36013635 static OperationResult ResolveVoid (OperationBase* origin) {
36023636 return OperationResult{origin, Void{}};
36033637 }
3638+ static OperationResult ResolvePreparedStatement (OperationBase* origin,
3639+ BaseObjectPtr<Database> db,
3640+ sqlite3_stmt* stmt) {
3641+ return OperationResult{origin, PreparedStatement{std::move (db), stmt}};
3642+ }
36043643
36053644 template <typename T>
36063645 requires std::constructible_from<variant_type, T>
@@ -3622,6 +3661,47 @@ class alignas(64) OperationResult {
36223661 variant_type result_;
36233662};
36243663
3664+ class PrepareStatementOperation : private OperationBase {
3665+ public:
3666+ PrepareStatementOperation (Global<Promise::Resolver>&& resolver,
3667+ BaseObjectPtr<Database>&& db,
3668+ std::pmr::string&& sql)
3669+ : OperationBase(std::move(resolver)),
3670+ db_ (std::move(db)),
3671+ sql_(std::move(sql)) {}
3672+
3673+ OperationResult operator ()(sqlite3* connection) {
3674+ sqlite3_stmt* stmt = nullptr ;
3675+ int error_code =
3676+ sqlite3_prepare_v2 (connection, sql_.c_str (), -1 , &stmt, nullptr );
3677+ return error_code == SQLITE_OK
3678+ ? OperationResult::ResolvePreparedStatement (
3679+ this , std::move (db_), stmt)
3680+ : OperationResult::RejectLastError (this , connection);
3681+ }
3682+
3683+ private:
3684+ BaseObjectPtr<Database> db_;
3685+ std::pmr::string sql_;
3686+ };
3687+
3688+ class FinalizeStatementOperation : private OperationBase {
3689+ public:
3690+ FinalizeStatementOperation (Global<Promise::Resolver>&& resolver,
3691+ sqlite3_stmt* stmt)
3692+ : OperationBase(std::move(resolver)), stmt_(stmt) {}
3693+
3694+ OperationResult operator ()(sqlite3* connection) {
3695+ int error_code = sqlite3_finalize (stmt_);
3696+ CHECK_NE (error_code, SQLITE_MISUSE);
3697+ stmt_ = nullptr ;
3698+ return OperationResult::ResolveVoid (this );
3699+ }
3700+
3701+ private:
3702+ sqlite3_stmt* stmt_;
3703+ };
3704+
36253705class ExecOperation : private OperationBase {
36263706 public:
36273707 ExecOperation (Global<Promise::Resolver>&& resolver, std::pmr::string&& sql)
@@ -3657,7 +3737,21 @@ class CloseOperation : private OperationBase {
36573737 }
36583738};
36593739
3660- using Operation = std::variant<ExecOperation, CloseOperation>;
3740+ using Operation = std::variant<ExecOperation,
3741+ PrepareStatementOperation,
3742+ FinalizeStatementOperation,
3743+ CloseOperation>;
3744+
3745+ template <typename T, typename V>
3746+ struct is_contained_in_variant ;
3747+ template <typename T, typename ... Args>
3748+ struct is_contained_in_variant <T, std::variant<Args...>> {
3749+ static constexpr bool value{(std::is_same_v<T, Args> || ...)};
3750+ };
3751+
3752+ template <typename Op>
3753+ inline constexpr bool is_operation_type =
3754+ is_contained_in_variant<Op, Operation>::value;
36613755
36623756enum class QueuePushResult {
36633757 kQueueFull = -1 ,
@@ -3685,7 +3779,8 @@ class DatabaseOperationQueue {
36853779 : QueuePushResult::kSuccess ;
36863780 }
36873781 template <typename Op, typename ... Args>
3688- requires std::constructible_from<Operation,
3782+ requires is_operation_type<Op> &&
3783+ std::constructible_from<Operation,
36893784 std::in_place_type_t <Op>,
36903785 Global<Promise::Resolver>&&,
36913786 Args&&...>
@@ -3944,6 +4039,7 @@ v8::Local<v8::FunctionTemplate> CreateDatabaseConstructorTemplate(
39444039
39454040 SetProtoMethod (isolate, tmpl, " close" , Database::Close);
39464041 SetProtoAsyncDispose (isolate, tmpl, Database::AsyncDispose);
4042+ SetProtoMethod (isolate, tmpl, " prepare" , Database::Prepare);
39474043 SetProtoMethod (isolate, tmpl, " exec" , Database::Exec);
39484044
39494045 Local<String> sqlite_type_key = FIXED_ONE_BYTE_STRING (isolate, " sqlite-type" );
@@ -4029,13 +4125,16 @@ Local<Promise> Database::AsyncDisposeImpl() {
40294125 // We can't use Schedule here, because Schedule queues a MicroTask which
40304126 // would try to access the Database object after destruction if this is
40314127 // called from the destructor.
4032- if (next_batch_ == nullptr ) {
4033- next_batch_ = std::make_unique<DatabaseOperationQueue>(
4034- 1U , std::pmr::get_default_resource ());
4035- }
4128+ // Furthermore we always need to schedule the CloseOperation in a separate
4129+ // batch, to ensure that it runs after all previously scheduled operations,
4130+ // because e.g. PrepareStatementOperations need to connect their results
4131+ // first.
4132+ ProcessNextBatch ();
4133+ next_batch_ = std::make_unique<DatabaseOperationQueue>(
4134+ 1U , std::pmr::get_default_resource ());
40364135 CHECK_NE (next_batch_->PushEmplace <CloseOperation>(isolate, resolver),
40374136 QueuePushResult::kQueueFull );
4038- executor_-> ScheduleBatch ( std::move (next_batch_) );
4137+ ProcessNextBatch ( );
40394138 executor_.release ()->Dispose ();
40404139
40414140 connection_ = nullptr ;
@@ -4084,9 +4183,43 @@ void Database::AsyncDispose(const v8::FunctionCallbackInfo<v8::Value>& args) {
40844183 return ;
40854184 }
40864185
4186+ // We don't need to dispose statements during destruction, because all
4187+ // Statement instances keep a BaseObjectPtr to the Database and therefore
4188+ // can't outlive the Database. Therefore this shouldn't be executed as part of
4189+ // db->AsyncDisposeImpl().
4190+ // Calling stmt->Dispose modifies the statements_ set, therefore make a copy.
4191+ for (Statement* stmt : std::vector<Statement*>(db->statements_ .begin (),
4192+ db->statements_ .end ())) {
4193+ stmt->Dispose ();
4194+ }
4195+
40874196 args.GetReturnValue ().Set (db->AsyncDisposeImpl ());
40884197}
40894198
4199+ void Database::Prepare (const v8::FunctionCallbackInfo<v8::Value>& args) {
4200+ Database* db;
4201+ ASSIGN_OR_RETURN_UNWRAP (&db, args.This ());
4202+ Environment* env = Environment::GetCurrent (args);
4203+ // TODO(BurningEnlightenment): these should be rejections
4204+ THROW_AND_RETURN_ON_BAD_STATE (env, !db->IsOpen (), " database is not open" );
4205+
4206+ if (!args[0 ]->IsString ()) {
4207+ THROW_ERR_INVALID_ARG_TYPE (env->isolate (),
4208+ " The \" sql\" argument must be a string." );
4209+ return ;
4210+ }
4211+ Utf8Value sql (env->isolate (), args[0 ].As <String>());
4212+ args.GetReturnValue ().Set (db->Schedule <PrepareStatementOperation>(
4213+ BaseObjectPtr<Database>(db), std::pmr::string (*sql, sql.length ())));
4214+ }
4215+
4216+ void Database::TrackStatement (Statement* statement) {
4217+ statements_.insert (statement);
4218+ }
4219+ void Database::UntrackStatement (Statement* statement) {
4220+ statements_.erase (statement);
4221+ }
4222+
40904223void Database::Exec (const v8::FunctionCallbackInfo<v8::Value>& args) {
40914224 Database* db;
40924225 // TODO(BurningEnlightenment): these should be rejections
@@ -4104,6 +4237,94 @@ void Database::Exec(const v8::FunctionCallbackInfo<v8::Value>& args) {
41044237 db->Schedule <ExecOperation>(std::pmr::string (*sql, sql.length ())));
41054238}
41064239
4240+ Statement::Statement (Environment* env,
4241+ v8::Local<v8::Object> object,
4242+ BaseObjectPtr<Database> db,
4243+ sqlite3_stmt* stmt)
4244+ : BaseObject(env, object), db_(std::move(db)), statement_(stmt) {
4245+ if (stmt == nullptr ) {
4246+ db_ = nullptr ;
4247+ } else {
4248+ CHECK_NOT_NULL (db_);
4249+ db_->TrackStatement (this );
4250+ }
4251+ }
4252+
4253+ Statement::~Statement () {
4254+ if (!IsDisposed ()) {
4255+ // Our operations keep a BaseObjectPtr to this Statement, so we can be sure
4256+ // that no operations are running or will run after this point. The only
4257+ // exception to this is the FinalizeStatementOperation, but it can only be
4258+ // queued by Statement::Dispose.
4259+ sqlite3_finalize (statement_);
4260+ db_->UntrackStatement (this );
4261+ }
4262+ }
4263+
4264+ void Statement::MemoryInfo (MemoryTracker* tracker) const {}
4265+
4266+ Local<FunctionTemplate> Statement::GetConstructorTemplate (Environment* env) {
4267+ Local<FunctionTemplate> tmpl = env->sqlite_statement_constructor_template ();
4268+ if (tmpl.IsEmpty ()) {
4269+ Isolate* isolate = env->isolate ();
4270+ tmpl = NewFunctionTemplate (isolate, IllegalConstructor);
4271+ tmpl->SetClassName (FIXED_ONE_BYTE_STRING (isolate, " Statement" ));
4272+ tmpl->InstanceTemplate ()->SetInternalFieldCount (
4273+ Statement::kInternalFieldCount );
4274+
4275+ SetProtoDispose (isolate, tmpl, Statement::Dispose);
4276+ SetSideEffectFreeGetter (isolate,
4277+ tmpl,
4278+ FIXED_ONE_BYTE_STRING (isolate, " isDisposed" ),
4279+ Statement::IsDisposedGetter);
4280+
4281+ env->set_sqlite_statement_constructor_template (tmpl);
4282+ }
4283+ return tmpl;
4284+ }
4285+ BaseObjectPtr<Statement> Statement::Create (Environment* env,
4286+ BaseObjectPtr<Database> db,
4287+ sqlite3_stmt* stmt) {
4288+ Local<Object> obj;
4289+ if (!GetConstructorTemplate (env)
4290+ ->InstanceTemplate ()
4291+ ->NewInstance (env->context ())
4292+ .ToLocal (&obj)) {
4293+ return nullptr ;
4294+ }
4295+
4296+ return MakeBaseObject<Statement>(env, obj, std::move (db), stmt);
4297+ }
4298+
4299+ void Statement::Dispose (const FunctionCallbackInfo<Value>& args) {
4300+ Statement* stmt;
4301+ ASSIGN_OR_RETURN_UNWRAP (&stmt, args.This ());
4302+ stmt->Dispose ();
4303+ args.GetReturnValue ().SetUndefined ();
4304+ }
4305+
4306+ void Statement::Dispose () {
4307+ if (IsDisposed ()) {
4308+ return ;
4309+ }
4310+ // Finalizing is a no-fail operation, so we don't need to check the result or
4311+ // return a promise.
4312+ (void )db_->Schedule <FinalizeStatementOperation>(
4313+ std::exchange (statement_, nullptr ));
4314+ std::exchange (db_, nullptr )->UntrackStatement (this );
4315+ }
4316+
4317+ void Statement::IsDisposedGetter (const FunctionCallbackInfo<Value>& args) {
4318+ Statement* stmt;
4319+ ASSIGN_OR_RETURN_UNWRAP (&stmt, args.This ());
4320+ Environment* env = Environment::GetCurrent (args);
4321+ args.GetReturnValue ().Set (Boolean::New (env->isolate (), stmt->IsDisposed ()));
4322+ }
4323+
4324+ bool Statement::IsDisposed () const {
4325+ return statement_ == nullptr ;
4326+ }
4327+
41074328void DefineConstants (Local<Object> target) {
41084329 NODE_DEFINE_CONSTANT (target, SQLITE_CHANGESET_OMIT);
41094330 NODE_DEFINE_CONSTANT (target, SQLITE_CHANGESET_REPLACE);
@@ -4180,9 +4401,8 @@ static void Initialize(Local<Object> target,
41804401 context, target, " Session" , Session::GetConstructorTemplate (env));
41814402 SetConstructorFunction (
41824403 context, target, " Database" , CreateDatabaseConstructorTemplate (env));
4183- target
4184- ->Set (context, FIXED_ONE_BYTE_STRING (isolate, " Statement" ), Null (isolate))
4185- .Check ();
4404+ SetConstructorFunction (
4405+ context, target, " Statement" , Statement::GetConstructorTemplate (env));
41864406
41874407 target->Set (context, env->constants_string (), constants).Check ();
41884408
0 commit comments