Files
SnowLuma/.pnpm-store/v11/files/4c/b182a686ae95891cbc1d911d8b94ee0fe942811f56cb999e4a4ed4f755e01ab8cdb20e5047cc2b0ad02f32d73e4328bdef991bdb81892a6ecca105755c4c0c
TanTan 2a69b23b47
Dev Build / cleanup (push) Canceled after 0s
Dev Build / typecheck (push) Canceled after 0s
Dev Build / build (linux-arm64, ubuntu-24.04-arm, linux-arm64) (push) Canceled after 0s
Dev Build / build (linux-x64, ubuntu-latest, linux-x64) (push) Canceled after 0s
Dev Build / build (win-x64, windows-latest, win32-x64) (push) Canceled after 0s
Dev Build / dispatch-dev-docker (push) Canceled after 0s
docs: 仓库链接指向本镜像仓库
2026-11-13 09:27:07 +08:00

236 lines
7.8 KiB
Plaintext

#ifndef NODE_SQLITE3_SRC_BACKUP_H
#define NODE_SQLITE3_SRC_BACKUP_H
#include "database.h"
#include <string>
#include <queue>
#include <set>
#include <sqlite3.h>
#include <napi.h>
using namespace Napi;
namespace node_sqlite3 {
/**
*
* A class for managing an sqlite3_backup object. For consistency
* with other node-sqlite3 classes, it maintains an internal queue
* of calls.
*
* Intended usage from node:
*
* var db = new sqlite3.Database('live.db');
* var backup = db.backup('backup.db');
* ...
* // in event loop, move backup forward when we have time.
* if (backup.idle) { backup.step(NPAGES); }
* if (backup.completed) { ... success ... }
* if (backup.failed) { ... sadness ... }
* // do other work in event loop - fine to modify live.db
* ...
*
* Here is how sqlite's backup api is exposed:
*
* - `sqlite3_backup_init`: This is implemented as
* `db.backup(filename, [callback])` or
* `db.backup(filename, destDbName, sourceDbName, filenameIsDest, [callback])`.
* - `sqlite3_backup_step`: `backup.step(pages, [callback])`.
* - `sqlite3_backup_finish`: `backup.finish([callback])`.
* - `sqlite3_backup_remaining`: `backup.remaining`.
* - `sqlite3_backup_pagecount`: `backup.pageCount`.
*
* There are the following read-only properties:
*
* - `backup.completed` is set to `true` when the backup
* succeeeds.
* - `backup.failed` is set to `true` when the backup
* has a fatal error.
* - `backup.idle` is set to `true` when no operation
* is currently in progress or queued for the backup.
* - `backup.remaining` is an integer with the remaining
* number of pages after the last call to `backup.step`
* (-1 if `step` not yet called).
* - `backup.pageCount` is an integer with the total number
* of pages measured during the last call to `backup.step`
* (-1 if `step` not yet called).
*
* There is the following writable property:
*
* - `backup.retryErrors`: an array of sqlite3 error codes
* that are treated as non-fatal - meaning, if they occur,
* backup.failed is not set, and the backup may continue.
* By default, this is `[sqlite3.BUSY, sqlite3.LOCKED]`.
*
* The `db.backup(filename, [callback])` shorthand is sufficient
* for making a backup of a database opened by node-sqlite3. If
* using attached or temporary databases, or moving data in the
* opposite direction, the more complete (but daunting)
* `db.backup(filename, destDbName, sourceDbName, filenameIsDest, [callback])`
* signature is provided.
*
* A backup will finish automatically when it succeeds or a fatal
* error occurs, meaning it is not necessary to call `db.finish()`.
* By default, SQLITE_LOCKED and SQLITE_BUSY errors are not
* treated as failures, and the backup will continue if they
* occur. The set of errors that are tolerated can be controlled
* by setting `backup.retryErrors`. To disable automatic
* finishing and stick strictly to sqlite's raw api, set
* `backup.retryErrors` to `[]`. In that case, it is necessary
* to call `backup.finish()`.
*
* In the same way as node-sqlite3 databases and statements,
* backup methods can be called safely without callbacks, due
* to an internal call queue. So for example this naive code
* will correctly back up a db, if there are no errors:
*
* var backup = db.backup('backup.db');
* backup.step(-1);
* backup.finish();
*
*/
class Backup : public Napi::ObjectWrap<Backup> {
public:
static Napi::Object Init(Napi::Env env, Napi::Object exports);
// Cross-class argument validation, against this env's constructor —
// see Database::HasInstanceIn.
static inline bool HasInstance(Napi::Value val) {
return Database::HasInstanceIn(val,
&Database::AddonData::backup_ctor);
}
struct Baton {
napi_async_work request = NULL;
Backup* backup;
Napi::FunctionReference callback;
Baton(Backup* backup_, Napi::Function cb_) : backup(backup_) {
backup->Ref();
callback.Reset(cb_, 1);
}
virtual ~Baton() {
if (request) napi_delete_async_work(backup->Env(), request);
backup->Unref();
callback.Reset();
}
};
struct InitializeBaton : Database::Baton {
Backup* backup;
std::string filename;
std::string sourceName;
std::string destName;
bool filenameIsDest;
InitializeBaton(Database* db_, Napi::Function cb_, Backup* backup_) :
Baton(db_, cb_), backup(backup_), filenameIsDest(true) {
backup->Ref();
}
virtual ~InitializeBaton() override {
backup->Unref();
if (db->IsClosed()) {
// The database handle was closed before the backup could be opened.
backup->FinishAll();
}
}
};
struct StepBaton : Baton {
int pages;
std::set<int> retryErrorsSet;
StepBaton(Backup* backup_, Napi::Function cb_, int pages_) :
Baton(backup_, cb_), pages(pages_) {}
virtual ~StepBaton() override = default;
};
typedef void (*Work_Callback)(Baton* baton);
struct Call {
Call(Work_Callback cb_, Baton* baton_) : callback(cb_), baton(baton_) {};
Work_Callback callback;
Baton* baton;
};
Backup(const Napi::CallbackInfo& info);
~Backup() {
if (!finished) {
FinishAll();
}
retryErrors.Reset();
}
WORK_DEFINITION(Step)
WORK_DEFINITION(Finish)
// End-of-call bookkeeping for an asynchronous backup operation.
void EndCall();
// Runs EndCall() on every exit path from a Work_After* handler.
// TRY_CATCH_CALL returns early when a JS callback throws, so doing the
// bookkeeping inline (the old trailing BACKUP_END() macro) was skipped
// on that path — leaving `locked` set and db->pending elevated forever.
// A leaked pending count permanently disables the connection's sync
// fast path and blocks every exclusive operation (close/exec/wait/
// loadExtension all assert pending == 0). Mirrors
// Statement::CallGuard.
struct CallGuard {
Backup* backup;
explicit CallGuard(Backup* b) : backup(b) {}
~CallGuard() { backup->EndCall(); }
CallGuard(const CallGuard&) = delete;
CallGuard& operator=(const CallGuard&) = delete;
};
Napi::Value IdleGetter(const Napi::CallbackInfo& info);
Napi::Value CompletedGetter(const Napi::CallbackInfo& info);
Napi::Value FailedGetter(const Napi::CallbackInfo& info);
Napi::Value PageCountGetter(const Napi::CallbackInfo& info);
Napi::Value RemainingGetter(const Napi::CallbackInfo& info);
Napi::Value RetryErrorGetter(const Napi::CallbackInfo& info);
void RetryErrorSetter(const Napi::CallbackInfo& info, const Napi::Value& value);
protected:
static void Work_BeginInitialize(Database::Baton* baton);
static void Work_Initialize(napi_env env, void* data);
static void Work_AfterInitialize(napi_env env, napi_status status, void* data);
void Schedule(Work_Callback callback, Baton* baton);
void Process();
void CleanQueue();
template <class T> static void Error(T* baton);
void FinishAll();
void FinishSqlite();
void GetRetryErrors(std::set<int>& retryErrorsSet);
// NULL when the constructor threw before validation completed; every
// destructor path gates on it.
Database* db = NULL;
sqlite3_backup* _handle = NULL;
sqlite3* _otherDb = NULL;
sqlite3* _destDb = NULL;
bool inited = false;
bool locked = true;
bool completed = false;
bool failed = false;
int remaining = -1;
int pageCount = -1;
bool finished = false;
int status;
std::string message;
std::queue<std::unique_ptr<Call>> queue;
Napi::Reference<Array> retryErrors;
};
}
#endif