Skip to content

Commit 2aee11d

Browse files
committed
sqlite, test: expose sqlite online backup api
1 parent 7409a1d commit 2aee11d

File tree

3 files changed

+381
-0
lines changed

3 files changed

+381
-0
lines changed

src/node_sqlite.cc

Lines changed: 296 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
#include "node_errors.h"
99
#include "node_mem-inl.h"
1010
#include "sqlite3.h"
11+
#include "threadpoolwork-inl.h"
1112
#include "util-inl.h"
1213

1314
#include <cinttypes>
@@ -29,6 +30,7 @@ using v8::FunctionCallback;
2930
using v8::FunctionCallbackInfo;
3031
using v8::FunctionTemplate;
3132
using v8::Global;
33+
using v8::HandleScope;
3234
using v8::Int32;
3335
using v8::Integer;
3436
using v8::Isolate;
@@ -40,6 +42,7 @@ using v8::NewStringType;
4042
using v8::Null;
4143
using v8::Number;
4244
using v8::Object;
45+
using v8::Promise;
4346
using v8::SideEffectType;
4447
using v8::String;
4548
using v8::TryCatch;
@@ -81,6 +84,24 @@ inline MaybeLocal<Object> CreateSQLiteError(Isolate* isolate,
8184
return e;
8285
}
8386

87+
inline MaybeLocal<Object> CreateSQLiteError(Isolate* isolate, int errcode) {
88+
const char* errstr = sqlite3_errstr(errcode);
89+
Local<String> js_errmsg;
90+
Local<Object> e;
91+
Environment* env = Environment::GetCurrent(isolate);
92+
if (!String::NewFromUtf8(isolate, errstr).ToLocal(&js_errmsg) ||
93+
!CreateSQLiteError(isolate, errstr).ToLocal(&e) ||
94+
e->Set(isolate->GetCurrentContext(),
95+
env->errcode_string(),
96+
Integer::New(isolate, errcode))
97+
.IsNothing() ||
98+
e->Set(isolate->GetCurrentContext(), env->errstr_string(), js_errmsg)
99+
.IsNothing()) {
100+
return MaybeLocal<Object>();
101+
}
102+
return e;
103+
}
104+
84105
inline MaybeLocal<Object> CreateSQLiteError(Isolate* isolate, sqlite3* db) {
85106
int errcode = sqlite3_extended_errcode(db);
86107
const char* errstr = sqlite3_errstr(errcode);
@@ -128,6 +149,171 @@ inline void THROW_ERR_SQLITE_ERROR(Isolate* isolate, int errcode) {
128149
isolate->ThrowException(error);
129150
}
130151

152+
class BackupJob : public ThreadPoolWork {
153+
public:
154+
explicit BackupJob(Environment* env,
155+
DatabaseSync* source,
156+
Local<Promise::Resolver> resolver,
157+
std::string source_db,
158+
std::string destination_name,
159+
std::string dest_db,
160+
int pages,
161+
Local<Function> progressFunc)
162+
: ThreadPoolWork(env, "node_sqlite3.BackupJob"),
163+
env_(env),
164+
source_(source),
165+
source_db_(source_db),
166+
destination_name_(destination_name),
167+
dest_db_(dest_db),
168+
pages_(pages) {
169+
resolver_.Reset(env->isolate(), resolver);
170+
progressFunc_.Reset(env->isolate(), progressFunc);
171+
}
172+
173+
void ScheduleBackup() {
174+
Isolate* isolate = env()->isolate();
175+
HandleScope handle_scope(isolate);
176+
177+
backup_status_ = sqlite3_open(destination_name_.c_str(), &pDest_);
178+
179+
Local<Promise::Resolver> resolver =
180+
Local<Promise::Resolver>::New(env()->isolate(), resolver_);
181+
182+
Local<Object> e = Local<Object>();
183+
184+
if (backup_status_ != SQLITE_OK) {
185+
CreateSQLiteError(isolate, pDest_).ToLocal(&e);
186+
187+
Cleanup();
188+
189+
resolver->Reject(env()->context(), e).ToChecked();
190+
191+
return;
192+
}
193+
194+
pBackup_ = sqlite3_backup_init(
195+
pDest_, dest_db_.c_str(), source_->Connection(), source_db_.c_str());
196+
197+
if (pBackup_ == nullptr) {
198+
CreateSQLiteError(isolate, pDest_).ToLocal(&e);
199+
200+
sqlite3_close(pDest_);
201+
202+
resolver->Reject(env()->context(), e).ToChecked();
203+
204+
return;
205+
}
206+
207+
this->ScheduleWork();
208+
}
209+
210+
void DoThreadPoolWork() override {
211+
backup_status_ = sqlite3_backup_step(pBackup_, pages_);
212+
213+
const char* errstr = sqlite3_errstr(backup_status_);
214+
}
215+
216+
void AfterThreadPoolWork(int status) override {
217+
HandleScope handle_scope(env()->isolate());
218+
219+
if (resolver_.IsEmpty()) {
220+
Cleanup();
221+
222+
return;
223+
}
224+
225+
Local<Promise::Resolver> resolver =
226+
Local<Promise::Resolver>::New(env()->isolate(), resolver_);
227+
228+
if (!(backup_status_ == SQLITE_OK || backup_status_ == SQLITE_DONE ||
229+
backup_status_ == SQLITE_BUSY || backup_status_ == SQLITE_LOCKED)) {
230+
Local<Object> e = Local<Object>();
231+
232+
CreateSQLiteError(env()->isolate(), backup_status_).ToLocal(&e);
233+
234+
Cleanup();
235+
236+
resolver->Reject(env()->context(), e).ToChecked();
237+
238+
return;
239+
}
240+
241+
int total_pages = sqlite3_backup_pagecount(pBackup_);
242+
int remaining_pages = sqlite3_backup_remaining(pBackup_);
243+
244+
if (remaining_pages != 0) {
245+
Local<Function> fn =
246+
Local<Function>::New(env()->isolate(), progressFunc_);
247+
248+
if (!fn.IsEmpty()) {
249+
Local<Value> argv[] = {
250+
Integer::New(env()->isolate(), total_pages),
251+
Integer::New(env()->isolate(), remaining_pages),
252+
};
253+
254+
TryCatch try_catch(env()->isolate());
255+
fn->Call(env()->context(), Null(env()->isolate()), 2, argv)
256+
.FromMaybe(Local<Value>());
257+
258+
if (try_catch.HasCaught()) {
259+
Cleanup();
260+
261+
resolver->Reject(env()->context(), try_catch.Exception()).ToChecked();
262+
263+
return;
264+
}
265+
}
266+
267+
// There's still work to do
268+
this->ScheduleWork();
269+
270+
return;
271+
}
272+
273+
Local<String> message =
274+
String::NewFromUtf8(
275+
env()->isolate(), "Backup completed", NewStringType::kNormal)
276+
.ToLocalChecked();
277+
278+
Local<Object> e = Local<Object>();
279+
CreateSQLiteError(env()->isolate(), pDest_).ToLocal(&e);
280+
281+
Cleanup();
282+
283+
if (backup_status_ == SQLITE_OK) {
284+
resolver->Resolve(env()->context(), message).ToChecked();
285+
} else {
286+
resolver->Reject(env()->context(), e).ToChecked();
287+
}
288+
}
289+
290+
private:
291+
void Cleanup() {
292+
if (pBackup_) {
293+
sqlite3_backup_finish(pBackup_);
294+
}
295+
296+
if (pDest_) {
297+
backup_status_ = sqlite3_errcode(pDest_);
298+
sqlite3_close(pDest_);
299+
}
300+
}
301+
302+
// https://github.com/nodejs/node/blob/649da3b8377e030ea7b9a1bc0308451e26e28740/src/crypto/crypto_keygen.h#L126
303+
int backup_status_;
304+
Environment* env() const { return env_; }
305+
sqlite3* pDest_;
306+
sqlite3_backup* pBackup_;
307+
Environment* env_;
308+
DatabaseSync* source_;
309+
Global<Promise::Resolver> resolver_;
310+
Global<Function> progressFunc_;
311+
std::string source_db_;
312+
std::string destination_name_;
313+
std::string dest_db_;
314+
int pages_;
315+
};
316+
131317
class UserDefinedFunction {
132318
public:
133319
explicit UserDefinedFunction(Environment* env,
@@ -533,6 +719,115 @@ void DatabaseSync::Exec(const FunctionCallbackInfo<Value>& args) {
533719
CHECK_ERROR_OR_THROW(env->isolate(), db->connection_, r, SQLITE_OK, void());
534720
}
535721

722+
// database.backup(destination, { sourceDb, targetDb, rate, progress: (total,
723+
// remaining) => {} )
724+
void DatabaseSync::Backup(const FunctionCallbackInfo<Value>& args) {
725+
Environment* env = Environment::GetCurrent(args);
726+
727+
if (!args[0]->IsString()) {
728+
THROW_ERR_INVALID_ARG_TYPE(
729+
env->isolate(), "The \"destination\" argument must be a string.");
730+
return;
731+
}
732+
733+
int rate = 100;
734+
std::string source_db = "main";
735+
std::string dest_db = "main";
736+
737+
DatabaseSync* db;
738+
ASSIGN_OR_RETURN_UNWRAP(&db, args.This());
739+
740+
THROW_AND_RETURN_ON_BAD_STATE(env, !db->IsOpen(), "database is not open");
741+
742+
Utf8Value destFilename(env->isolate(), args[0].As<String>());
743+
Local<Function> progressFunc = Local<Function>();
744+
745+
if (args.Length() > 1) {
746+
if (!args[1]->IsObject()) {
747+
THROW_ERR_INVALID_ARG_TYPE(env->isolate(),
748+
"The \"options\" argument must be an object.");
749+
return;
750+
}
751+
752+
Local<Object> options = args[1].As<Object>();
753+
Local<String> progress_string =
754+
FIXED_ONE_BYTE_STRING(env->isolate(), "progress");
755+
Local<String> rate_string = FIXED_ONE_BYTE_STRING(env->isolate(), "rate");
756+
Local<String> target_db_string =
757+
FIXED_ONE_BYTE_STRING(env->isolate(), "targetDb");
758+
Local<String> source_db_string =
759+
FIXED_ONE_BYTE_STRING(env->isolate(), "sourceDb");
760+
761+
Local<Value> rateValue =
762+
options->Get(env->context(), rate_string).ToLocalChecked();
763+
764+
if (!rateValue->IsUndefined()) {
765+
if (!rateValue->IsInt32()) {
766+
THROW_ERR_INVALID_ARG_TYPE(
767+
env->isolate(),
768+
"The \"options.rate\" argument must be an integer.");
769+
return;
770+
}
771+
772+
rate = rateValue.As<Int32>()->Value();
773+
}
774+
775+
Local<Value> sourceDbValue =
776+
options->Get(env->context(), source_db_string).ToLocalChecked();
777+
778+
if (!sourceDbValue->IsUndefined()) {
779+
if (!sourceDbValue->IsString()) {
780+
THROW_ERR_INVALID_ARG_TYPE(
781+
env->isolate(),
782+
"The \"options.sourceDb\" argument must be a string.");
783+
return;
784+
}
785+
786+
source_db =
787+
Utf8Value(env->isolate(), sourceDbValue.As<String>()).ToString();
788+
}
789+
790+
Local<Value> targetDbValue =
791+
options->Get(env->context(), target_db_string).ToLocalChecked();
792+
793+
if (!targetDbValue->IsUndefined()) {
794+
if (!targetDbValue->IsString()) {
795+
THROW_ERR_INVALID_ARG_TYPE(
796+
env->isolate(),
797+
"The \"options.targetDb\" argument must be a string.");
798+
return;
799+
}
800+
801+
dest_db =
802+
Utf8Value(env->isolate(), targetDbValue.As<String>()).ToString();
803+
}
804+
805+
Local<Value> progressValue =
806+
options->Get(env->context(), progress_string).ToLocalChecked();
807+
808+
if (!progressValue->IsUndefined()) {
809+
if (!progressValue->IsFunction()) {
810+
THROW_ERR_INVALID_ARG_TYPE(
811+
env->isolate(),
812+
"The \"options.progress\" argument must be a function.");
813+
return;
814+
}
815+
816+
progressFunc = progressValue.As<Function>();
817+
}
818+
}
819+
820+
Local<Promise::Resolver> resolver = Promise::Resolver::New(env->context())
821+
.ToLocalChecked()
822+
.As<Promise::Resolver>();
823+
824+
args.GetReturnValue().Set(resolver->GetPromise());
825+
826+
BackupJob* job = new BackupJob(
827+
env, db, resolver, source_db, *destFilename, dest_db, rate, progressFunc);
828+
job->ScheduleBackup();
829+
}
830+
536831
void DatabaseSync::CustomFunction(const FunctionCallbackInfo<Value>& args) {
537832
DatabaseSync* db;
538833
ASSIGN_OR_RETURN_UNWRAP(&db, args.This());
@@ -1718,6 +2013,7 @@ static void Initialize(Local<Object> target,
17182013
SetProtoMethod(isolate, db_tmpl, "close", DatabaseSync::Close);
17192014
SetProtoMethod(isolate, db_tmpl, "prepare", DatabaseSync::Prepare);
17202015
SetProtoMethod(isolate, db_tmpl, "exec", DatabaseSync::Exec);
2016+
SetProtoMethod(isolate, db_tmpl, "backup", DatabaseSync::Backup);
17212017
SetProtoMethod(isolate, db_tmpl, "function", DatabaseSync::CustomFunction);
17222018
SetProtoMethod(
17232019
isolate, db_tmpl, "createSession", DatabaseSync::CreateSession);

src/node_sqlite.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ class DatabaseSync : public BaseObject {
5757
static void Close(const v8::FunctionCallbackInfo<v8::Value>& args);
5858
static void Prepare(const v8::FunctionCallbackInfo<v8::Value>& args);
5959
static void Exec(const v8::FunctionCallbackInfo<v8::Value>& args);
60+
static void Backup(const v8::FunctionCallbackInfo<v8::Value>& args);
6061
static void CustomFunction(const v8::FunctionCallbackInfo<v8::Value>& args);
6162
static void CreateSession(const v8::FunctionCallbackInfo<v8::Value>& args);
6263
static void ApplyChangeset(const v8::FunctionCallbackInfo<v8::Value>& args);
@@ -81,6 +82,7 @@ class DatabaseSync : public BaseObject {
8182
bool enable_load_extension_;
8283
sqlite3* connection_;
8384

85+
std::set<sqlite3_backup*> backups_;
8486
std::set<sqlite3_session*> sessions_;
8587
std::unordered_set<StatementSync*> statements_;
8688

0 commit comments

Comments
 (0)