better-sqlite3
Advanced tools
| Backup::Backup( | ||
| Database* db, | ||
| sqlite3* dest_handle, | ||
| sqlite3_backup* backup_handle, | ||
| sqlite3_uint64 id, | ||
| bool unlink | ||
| ) : | ||
| node::ObjectWrap(), | ||
| db(db), | ||
| dest_handle(dest_handle), | ||
| backup_handle(backup_handle), | ||
| id(id), | ||
| alive(true), | ||
| unlink(unlink) { | ||
| assert(db != NULL); | ||
| assert(dest_handle != NULL); | ||
| assert(backup_handle != NULL); | ||
| db->AddBackup(this); | ||
| } | ||
| Backup::~Backup() { | ||
| if (alive) db->RemoveBackup(this); | ||
| CloseHandles(); | ||
| } | ||
| // Whenever this is used, db->RemoveBackup must be invoked beforehand. | ||
| void Backup::CloseHandles() { | ||
| if (alive) { | ||
| alive = false; | ||
| std::string filename(sqlite3_db_filename(dest_handle, "main")); | ||
| sqlite3_backup_finish(backup_handle); | ||
| int status = sqlite3_close(dest_handle); | ||
| assert(status == SQLITE_OK); ((void)status); | ||
| if (unlink) remove(filename.c_str()); | ||
| } | ||
| } | ||
| INIT(Backup::Init) { | ||
| v8::Local<v8::FunctionTemplate> t = NewConstructorTemplate(isolate, data, JS_new, "Backup"); | ||
| SetPrototypeMethod(isolate, data, t, "transfer", JS_transfer); | ||
| SetPrototypeMethod(isolate, data, t, "close", JS_close); | ||
| return t->GetFunction(OnlyContext).ToLocalChecked(); | ||
| } | ||
| NODE_METHOD(Backup::JS_new) { | ||
| UseAddon; | ||
| if (!addon->privileged_info) return ThrowTypeError("Disabled constructor"); | ||
| assert(info.IsConstructCall()); | ||
| Database* db = Unwrap<Database>(addon->privileged_info->This()); | ||
| REQUIRE_DATABASE_OPEN(db->GetState()); | ||
| REQUIRE_DATABASE_NOT_BUSY(db->GetState()); | ||
| v8::Local<v8::Object> database = (*addon->privileged_info)[0].As<v8::Object>(); | ||
| v8::Local<v8::String> attachedName = (*addon->privileged_info)[1].As<v8::String>(); | ||
| v8::Local<v8::String> destFile = (*addon->privileged_info)[2].As<v8::String>(); | ||
| bool unlink = (*addon->privileged_info)[3].As<v8::Boolean>()->Value(); | ||
| UseIsolate; | ||
| sqlite3* dest_handle; | ||
| v8::String::Utf8Value dest_file(isolate, destFile); | ||
| v8::String::Utf8Value attached_name(isolate, attachedName); | ||
| int mask = (SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE); | ||
| if (sqlite3_open_v2(*dest_file, &dest_handle, mask, NULL) != SQLITE_OK) { | ||
| Database::ThrowSqliteError(addon, dest_handle); | ||
| int status = sqlite3_close(dest_handle); | ||
| assert(status == SQLITE_OK); ((void)status); | ||
| return; | ||
| } | ||
| sqlite3_extended_result_codes(dest_handle, 1); | ||
| sqlite3_limit(dest_handle, SQLITE_LIMIT_LENGTH, INT_MAX); | ||
| sqlite3_backup* backup_handle = sqlite3_backup_init(dest_handle, "main", db->GetHandle(), *attached_name); | ||
| if (backup_handle == NULL) { | ||
| Database::ThrowSqliteError(addon, dest_handle); | ||
| int status = sqlite3_close(dest_handle); | ||
| assert(status == SQLITE_OK); ((void)status); | ||
| return; | ||
| } | ||
| Backup* backup = new Backup(db, dest_handle, backup_handle, addon->NextId(), unlink); | ||
| backup->Wrap(info.This()); | ||
| SetFrozen(isolate, OnlyContext, info.This(), addon->cs.database, database); | ||
| info.GetReturnValue().Set(info.This()); | ||
| } | ||
| NODE_METHOD(Backup::JS_transfer) { | ||
| Backup* backup = Unwrap<Backup>(info.This()); | ||
| REQUIRE_ARGUMENT_INT32(first, int pages); | ||
| REQUIRE_DATABASE_OPEN(backup->db->GetState()); | ||
| assert(backup->db->GetState()->busy == false); | ||
| assert(backup->alive == true); | ||
| sqlite3_backup* backup_handle = backup->backup_handle; | ||
| int status = sqlite3_backup_step(backup_handle, pages) & 0xff; | ||
| Addon* addon = backup->db->GetAddon(); | ||
| if (status == SQLITE_OK || status == SQLITE_DONE || status == SQLITE_BUSY) { | ||
| int total_pages = sqlite3_backup_pagecount(backup_handle); | ||
| int remaining_pages = sqlite3_backup_remaining(backup_handle); | ||
| UseIsolate; | ||
| UseContext; | ||
| v8::Local<v8::Object> result = v8::Object::New(isolate); | ||
| result->Set(ctx, addon->cs.totalPages.Get(isolate), v8::Int32::New(isolate, total_pages)).FromJust(); | ||
| result->Set(ctx, addon->cs.remainingPages.Get(isolate), v8::Int32::New(isolate, remaining_pages)).FromJust(); | ||
| info.GetReturnValue().Set(result); | ||
| if (status == SQLITE_DONE) backup->unlink = false; | ||
| } else { | ||
| Database::ThrowSqliteError(addon, sqlite3_errstr(status), status); | ||
| } | ||
| } | ||
| NODE_METHOD(Backup::JS_close) { | ||
| Backup* backup = Unwrap<Backup>(info.This()); | ||
| assert(backup->db->GetState()->busy == false); | ||
| if (backup->alive) backup->db->RemoveBackup(backup); | ||
| backup->CloseHandles(); | ||
| info.GetReturnValue().Set(info.This()); | ||
| } |
| class Backup : public node::ObjectWrap { | ||
| public: | ||
| ~Backup(); | ||
| // Whenever this is used, db->RemoveBackup must be invoked beforehand. | ||
| void CloseHandles(); | ||
| // Used to support ordered containers. | ||
| static inline bool Compare(Backup const * const a, Backup const * const b) { | ||
| return a->id < b->id; | ||
| } | ||
| static INIT(Init); | ||
| private: | ||
| explicit Backup( | ||
| Database* db, | ||
| sqlite3* dest_handle, | ||
| sqlite3_backup* backup_handle, | ||
| sqlite3_uint64 id, | ||
| bool unlink | ||
| ); | ||
| static NODE_METHOD(JS_new); | ||
| static NODE_METHOD(JS_transfer); | ||
| static NODE_METHOD(JS_close); | ||
| Database* const db; | ||
| sqlite3* const dest_handle; | ||
| sqlite3_backup* const backup_handle; | ||
| const sqlite3_uint64 id; | ||
| bool alive; | ||
| bool unlink; | ||
| }; |
| const int Database::MAX_BUFFER_SIZE = ( | ||
| node::Buffer::kMaxLength > INT_MAX | ||
| ? INT_MAX | ||
| : static_cast<int>(node::Buffer::kMaxLength) | ||
| ); | ||
| const int Database::MAX_STRING_SIZE = ( | ||
| v8::String::kMaxLength > INT_MAX | ||
| ? INT_MAX | ||
| : static_cast<int>(v8::String::kMaxLength) | ||
| ); | ||
| Database::Database( | ||
| v8::Isolate* isolate, | ||
| Addon* addon, | ||
| sqlite3* db_handle, | ||
| v8::Local<v8::Value> logger | ||
| ) : | ||
| node::ObjectWrap(), | ||
| db_handle(db_handle), | ||
| open(true), | ||
| busy(false), | ||
| safe_ints(false), | ||
| unsafe_mode(false), | ||
| was_js_error(false), | ||
| has_logger(logger->IsFunction()), | ||
| iterators(0), | ||
| addon(addon), | ||
| logger(isolate, logger), | ||
| stmts(), | ||
| backups() { | ||
| assert(db_handle != NULL); | ||
| addon->dbs.insert(this); | ||
| } | ||
| Database::~Database() { | ||
| if (open) addon->dbs.erase(this); | ||
| CloseHandles(); | ||
| } | ||
| // Whenever this is used, addon->dbs.erase() must be invoked beforehand. | ||
| void Database::CloseHandles() { | ||
| if (open) { | ||
| open = false; | ||
| for (Statement* stmt : stmts) stmt->CloseHandles(); | ||
| for (Backup* backup : backups) backup->CloseHandles(); | ||
| stmts.clear(); | ||
| backups.clear(); | ||
| int status = sqlite3_close(db_handle); | ||
| assert(status == SQLITE_OK); ((void)status); | ||
| } | ||
| } | ||
| void Database::ThrowDatabaseError() { | ||
| if (was_js_error) was_js_error = false; | ||
| else ThrowSqliteError(addon, db_handle); | ||
| } | ||
| void Database::ThrowSqliteError(Addon* addon, sqlite3* db_handle) { | ||
| assert(db_handle != NULL); | ||
| ThrowSqliteError(addon, sqlite3_errmsg(db_handle), sqlite3_extended_errcode(db_handle)); | ||
| } | ||
| void Database::ThrowSqliteError(Addon* addon, const char* message, int code) { | ||
| assert(message != NULL); | ||
| assert((code & 0xff) != SQLITE_OK); | ||
| assert((code & 0xff) != SQLITE_ROW); | ||
| assert((code & 0xff) != SQLITE_DONE); | ||
| EasyIsolate; | ||
| v8::Local<v8::Value> args[2] = { | ||
| StringFromUtf8(isolate, message, -1), | ||
| addon->cs.Code(isolate, code) | ||
| }; | ||
| isolate->ThrowException(addon->SqliteError.Get(isolate) | ||
| ->NewInstance(OnlyContext, 2, args) | ||
| .ToLocalChecked()); | ||
| } | ||
| // Allows Statements to log their executed SQL. | ||
| bool Database::Log(v8::Isolate* isolate, sqlite3_stmt* handle) { | ||
| assert(was_js_error == false); | ||
| if (!has_logger) return false; | ||
| char* expanded = sqlite3_expanded_sql(handle); | ||
| v8::Local<v8::Value> arg = StringFromUtf8(isolate, expanded ? expanded : sqlite3_sql(handle), -1); | ||
| was_js_error = logger.Get(isolate).As<v8::Function>() | ||
| ->Call(OnlyContext, v8::Undefined(isolate), 1, &arg) | ||
| .IsEmpty(); | ||
| if (expanded) sqlite3_free(expanded); | ||
| return was_js_error; | ||
| } | ||
| bool Database::Deserialize( | ||
| v8::Local<v8::Object> buffer, | ||
| Addon* addon, | ||
| sqlite3* db_handle, | ||
| bool readonly | ||
| ) { | ||
| size_t length = node::Buffer::Length(buffer); | ||
| unsigned char* data = (unsigned char*)sqlite3_malloc64(length); | ||
| unsigned int flags = SQLITE_DESERIALIZE_FREEONCLOSE | SQLITE_DESERIALIZE_RESIZEABLE; | ||
| if (readonly) { | ||
| flags |= SQLITE_DESERIALIZE_READONLY; | ||
| } | ||
| if (length) { | ||
| if (!data) { | ||
| ThrowError("Out of memory"); | ||
| return false; | ||
| } | ||
| memcpy(data, node::Buffer::Data(buffer), length); | ||
| } | ||
| int status = sqlite3_deserialize(db_handle, "main", data, length, length, flags); | ||
| if (status != SQLITE_OK) { | ||
| ThrowSqliteError(addon, status == SQLITE_ERROR ? "unable to deserialize database" : sqlite3_errstr(status), status); | ||
| return false; | ||
| } | ||
| return true; | ||
| } | ||
| void Database::FreeSerialization(char* data, void* _) { | ||
| sqlite3_free(data); | ||
| } | ||
| INIT(Database::Init) { | ||
| v8::Local<v8::FunctionTemplate> t = NewConstructorTemplate(isolate, data, JS_new, "Database"); | ||
| SetPrototypeMethod(isolate, data, t, "prepare", JS_prepare); | ||
| SetPrototypeMethod(isolate, data, t, "exec", JS_exec); | ||
| SetPrototypeMethod(isolate, data, t, "backup", JS_backup); | ||
| SetPrototypeMethod(isolate, data, t, "serialize", JS_serialize); | ||
| SetPrototypeMethod(isolate, data, t, "function", JS_function); | ||
| SetPrototypeMethod(isolate, data, t, "aggregate", JS_aggregate); | ||
| SetPrototypeMethod(isolate, data, t, "table", JS_table); | ||
| SetPrototypeMethod(isolate, data, t, "loadExtension", JS_loadExtension); | ||
| SetPrototypeMethod(isolate, data, t, "close", JS_close); | ||
| SetPrototypeMethod(isolate, data, t, "defaultSafeIntegers", JS_defaultSafeIntegers); | ||
| SetPrototypeMethod(isolate, data, t, "unsafeMode", JS_unsafeMode); | ||
| SetPrototypeGetter(isolate, data, t, "open", JS_open); | ||
| SetPrototypeGetter(isolate, data, t, "inTransaction", JS_inTransaction); | ||
| return t->GetFunction(OnlyContext).ToLocalChecked(); | ||
| } | ||
| NODE_METHOD(Database::JS_new) { | ||
| assert(info.IsConstructCall()); | ||
| REQUIRE_ARGUMENT_STRING(first, v8::Local<v8::String> filename); | ||
| REQUIRE_ARGUMENT_STRING(second, v8::Local<v8::String> filenameGiven); | ||
| REQUIRE_ARGUMENT_BOOLEAN(third, bool in_memory); | ||
| REQUIRE_ARGUMENT_BOOLEAN(fourth, bool readonly); | ||
| REQUIRE_ARGUMENT_BOOLEAN(fifth, bool must_exist); | ||
| REQUIRE_ARGUMENT_INT32(sixth, int timeout); | ||
| REQUIRE_ARGUMENT_ANY(seventh, v8::Local<v8::Value> logger); | ||
| REQUIRE_ARGUMENT_ANY(eighth, v8::Local<v8::Value> buffer); | ||
| UseAddon; | ||
| UseIsolate; | ||
| sqlite3* db_handle; | ||
| v8::String::Utf8Value utf8(isolate, filename); | ||
| int mask = readonly ? SQLITE_OPEN_READONLY | ||
| : must_exist ? SQLITE_OPEN_READWRITE | ||
| : (SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE); | ||
| if (sqlite3_open_v2(*utf8, &db_handle, mask, NULL) != SQLITE_OK) { | ||
| ThrowSqliteError(addon, db_handle); | ||
| int status = sqlite3_close(db_handle); | ||
| assert(status == SQLITE_OK); ((void)status); | ||
| return; | ||
| } | ||
| assert(sqlite3_db_mutex(db_handle) == NULL); | ||
| sqlite3_extended_result_codes(db_handle, 1); | ||
| sqlite3_busy_timeout(db_handle, timeout); | ||
| sqlite3_limit(db_handle, SQLITE_LIMIT_LENGTH, MAX_BUFFER_SIZE < MAX_STRING_SIZE ? MAX_BUFFER_SIZE : MAX_STRING_SIZE); | ||
| sqlite3_limit(db_handle, SQLITE_LIMIT_SQL_LENGTH, MAX_STRING_SIZE); | ||
| int status = sqlite3_db_config(db_handle, SQLITE_DBCONFIG_ENABLE_LOAD_EXTENSION, 1, NULL); | ||
| assert(status == SQLITE_OK); ((void)status); | ||
| status = sqlite3_db_config(db_handle, SQLITE_DBCONFIG_DEFENSIVE, 1, NULL); | ||
| assert(status == SQLITE_OK); ((void)status); | ||
| if (node::Buffer::HasInstance(buffer) && !Deserialize(buffer.As<v8::Object>(), addon, db_handle, readonly)) { | ||
| int status = sqlite3_close(db_handle); | ||
| assert(status == SQLITE_OK); ((void)status); | ||
| return; | ||
| } | ||
| UseContext; | ||
| Database* db = new Database(isolate, addon, db_handle, logger); | ||
| db->Wrap(info.This()); | ||
| SetFrozen(isolate, ctx, info.This(), addon->cs.memory, v8::Boolean::New(isolate, in_memory)); | ||
| SetFrozen(isolate, ctx, info.This(), addon->cs.readonly, v8::Boolean::New(isolate, readonly)); | ||
| SetFrozen(isolate, ctx, info.This(), addon->cs.name, filenameGiven); | ||
| info.GetReturnValue().Set(info.This()); | ||
| } | ||
| NODE_METHOD(Database::JS_prepare) { | ||
| REQUIRE_ARGUMENT_STRING(first, v8::Local<v8::String> source); | ||
| REQUIRE_ARGUMENT_OBJECT(second, v8::Local<v8::Object> database); | ||
| REQUIRE_ARGUMENT_BOOLEAN(third, bool pragmaMode); | ||
| (void)source; | ||
| (void)database; | ||
| (void)pragmaMode; | ||
| UseAddon; | ||
| UseIsolate; | ||
| v8::Local<v8::Function> c = addon->Statement.Get(isolate); | ||
| addon->privileged_info = &info; | ||
| v8::MaybeLocal<v8::Object> maybeStatement = c->NewInstance(OnlyContext, 0, NULL); | ||
| addon->privileged_info = NULL; | ||
| if (!maybeStatement.IsEmpty()) info.GetReturnValue().Set(maybeStatement.ToLocalChecked()); | ||
| } | ||
| NODE_METHOD(Database::JS_exec) { | ||
| Database* db = Unwrap<Database>(info.This()); | ||
| REQUIRE_ARGUMENT_STRING(first, v8::Local<v8::String> source); | ||
| REQUIRE_DATABASE_OPEN(db); | ||
| REQUIRE_DATABASE_NOT_BUSY(db); | ||
| REQUIRE_DATABASE_NO_ITERATORS_UNLESS_UNSAFE(db); | ||
| db->busy = true; | ||
| UseIsolate; | ||
| v8::String::Utf8Value utf8(isolate, source); | ||
| const char* sql = *utf8; | ||
| const char* tail; | ||
| int status; | ||
| const bool has_logger = db->has_logger; | ||
| sqlite3* const db_handle = db->db_handle; | ||
| sqlite3_stmt* handle; | ||
| for (;;) { | ||
| while (IS_SKIPPED(*sql)) ++sql; | ||
| status = sqlite3_prepare_v2(db_handle, sql, -1, &handle, &tail); | ||
| sql = tail; | ||
| if (!handle) break; | ||
| if (has_logger && db->Log(isolate, handle)) { | ||
| sqlite3_finalize(handle); | ||
| status = -1; | ||
| break; | ||
| } | ||
| do status = sqlite3_step(handle); | ||
| while (status == SQLITE_ROW); | ||
| status = sqlite3_finalize(handle); | ||
| if (status != SQLITE_OK) break; | ||
| } | ||
| db->busy = false; | ||
| if (status != SQLITE_OK) { | ||
| db->ThrowDatabaseError(); | ||
| } | ||
| } | ||
| NODE_METHOD(Database::JS_backup) { | ||
| REQUIRE_ARGUMENT_OBJECT(first, v8::Local<v8::Object> database); | ||
| REQUIRE_ARGUMENT_STRING(second, v8::Local<v8::String> attachedName); | ||
| REQUIRE_ARGUMENT_STRING(third, v8::Local<v8::String> destFile); | ||
| REQUIRE_ARGUMENT_BOOLEAN(fourth, bool unlink); | ||
| (void)database; | ||
| (void)attachedName; | ||
| (void)destFile; | ||
| (void)unlink; | ||
| UseAddon; | ||
| UseIsolate; | ||
| v8::Local<v8::Function> c = addon->Backup.Get(isolate); | ||
| addon->privileged_info = &info; | ||
| v8::MaybeLocal<v8::Object> maybeBackup = c->NewInstance(OnlyContext, 0, NULL); | ||
| addon->privileged_info = NULL; | ||
| if (!maybeBackup.IsEmpty()) info.GetReturnValue().Set(maybeBackup.ToLocalChecked()); | ||
| } | ||
| NODE_METHOD(Database::JS_serialize) { | ||
| Database* db = Unwrap<Database>(info.This()); | ||
| REQUIRE_ARGUMENT_STRING(first, v8::Local<v8::String> attachedName); | ||
| REQUIRE_DATABASE_OPEN(db); | ||
| REQUIRE_DATABASE_NOT_BUSY(db); | ||
| REQUIRE_DATABASE_NO_ITERATORS(db); | ||
| UseIsolate; | ||
| v8::String::Utf8Value attached_name(isolate, attachedName); | ||
| sqlite3_int64 length = -1; | ||
| unsigned char* data = sqlite3_serialize(db->db_handle, *attached_name, &length, 0); | ||
| if (!data && length) { | ||
| ThrowError("Out of memory"); | ||
| return; | ||
| } | ||
| info.GetReturnValue().Set( | ||
| SAFE_NEW_BUFFER(isolate, reinterpret_cast<char*>(data), length, FreeSerialization, NULL).ToLocalChecked() | ||
| ); | ||
| } | ||
| NODE_METHOD(Database::JS_function) { | ||
| Database* db = Unwrap<Database>(info.This()); | ||
| REQUIRE_ARGUMENT_FUNCTION(first, v8::Local<v8::Function> fn); | ||
| REQUIRE_ARGUMENT_STRING(second, v8::Local<v8::String> nameString); | ||
| REQUIRE_ARGUMENT_INT32(third, int argc); | ||
| REQUIRE_ARGUMENT_INT32(fourth, int safe_ints); | ||
| REQUIRE_ARGUMENT_BOOLEAN(fifth, bool deterministic); | ||
| REQUIRE_ARGUMENT_BOOLEAN(sixth, bool direct_only); | ||
| REQUIRE_DATABASE_OPEN(db); | ||
| REQUIRE_DATABASE_NOT_BUSY(db); | ||
| REQUIRE_DATABASE_NO_ITERATORS(db); | ||
| UseIsolate; | ||
| v8::String::Utf8Value name(isolate, nameString); | ||
| int mask = SQLITE_UTF8; | ||
| if (deterministic) mask |= SQLITE_DETERMINISTIC; | ||
| if (direct_only) mask |= SQLITE_DIRECTONLY; | ||
| safe_ints = safe_ints < 2 ? safe_ints : static_cast<int>(db->safe_ints); | ||
| if (sqlite3_create_function_v2(db->db_handle, *name, argc, mask, new CustomFunction(isolate, db, *name, fn, safe_ints), CustomFunction::xFunc, NULL, NULL, CustomFunction::xDestroy) != SQLITE_OK) { | ||
| db->ThrowDatabaseError(); | ||
| } | ||
| } | ||
| NODE_METHOD(Database::JS_aggregate) { | ||
| Database* db = Unwrap<Database>(info.This()); | ||
| REQUIRE_ARGUMENT_ANY(first, v8::Local<v8::Value> start); | ||
| REQUIRE_ARGUMENT_FUNCTION(second, v8::Local<v8::Function> step); | ||
| REQUIRE_ARGUMENT_ANY(third, v8::Local<v8::Value> inverse); | ||
| REQUIRE_ARGUMENT_ANY(fourth, v8::Local<v8::Value> result); | ||
| REQUIRE_ARGUMENT_STRING(fifth, v8::Local<v8::String> nameString); | ||
| REQUIRE_ARGUMENT_INT32(sixth, int argc); | ||
| REQUIRE_ARGUMENT_INT32(seventh, int safe_ints); | ||
| REQUIRE_ARGUMENT_BOOLEAN(eighth, bool deterministic); | ||
| REQUIRE_ARGUMENT_BOOLEAN(ninth, bool direct_only); | ||
| REQUIRE_DATABASE_OPEN(db); | ||
| REQUIRE_DATABASE_NOT_BUSY(db); | ||
| REQUIRE_DATABASE_NO_ITERATORS(db); | ||
| UseIsolate; | ||
| v8::String::Utf8Value name(isolate, nameString); | ||
| auto xInverse = inverse->IsFunction() ? CustomAggregate::xInverse : NULL; | ||
| auto xValue = xInverse ? CustomAggregate::xValue : NULL; | ||
| int mask = SQLITE_UTF8; | ||
| if (deterministic) mask |= SQLITE_DETERMINISTIC; | ||
| if (direct_only) mask |= SQLITE_DIRECTONLY; | ||
| safe_ints = safe_ints < 2 ? safe_ints : static_cast<int>(db->safe_ints); | ||
| if (sqlite3_create_window_function(db->db_handle, *name, argc, mask, new CustomAggregate(isolate, db, *name, start, step, inverse, result, safe_ints), CustomAggregate::xStep, CustomAggregate::xFinal, xValue, xInverse, CustomAggregate::xDestroy) != SQLITE_OK) { | ||
| db->ThrowDatabaseError(); | ||
| } | ||
| } | ||
| NODE_METHOD(Database::JS_table) { | ||
| Database* db = Unwrap<Database>(info.This()); | ||
| REQUIRE_ARGUMENT_FUNCTION(first, v8::Local<v8::Function> factory); | ||
| REQUIRE_ARGUMENT_STRING(second, v8::Local<v8::String> nameString); | ||
| REQUIRE_ARGUMENT_BOOLEAN(third, bool eponymous); | ||
| REQUIRE_DATABASE_OPEN(db); | ||
| REQUIRE_DATABASE_NOT_BUSY(db); | ||
| REQUIRE_DATABASE_NO_ITERATORS(db); | ||
| UseIsolate; | ||
| v8::String::Utf8Value name(isolate, nameString); | ||
| sqlite3_module* module = eponymous ? &CustomTable::EPONYMOUS_MODULE : &CustomTable::MODULE; | ||
| db->busy = true; | ||
| if (sqlite3_create_module_v2(db->db_handle, *name, module, new CustomTable(isolate, db, *name, factory), CustomTable::Destructor) != SQLITE_OK) { | ||
| db->ThrowDatabaseError(); | ||
| } | ||
| db->busy = false; | ||
| } | ||
| NODE_METHOD(Database::JS_loadExtension) { | ||
| Database* db = Unwrap<Database>(info.This()); | ||
| v8::Local<v8::String> entryPoint; | ||
| REQUIRE_ARGUMENT_STRING(first, v8::Local<v8::String> filename); | ||
| if (info.Length() > 1) { REQUIRE_ARGUMENT_STRING(second, entryPoint); } | ||
| REQUIRE_DATABASE_OPEN(db); | ||
| REQUIRE_DATABASE_NOT_BUSY(db); | ||
| REQUIRE_DATABASE_NO_ITERATORS(db); | ||
| UseIsolate; | ||
| char* error; | ||
| int status = sqlite3_load_extension( | ||
| db->db_handle, | ||
| *v8::String::Utf8Value(isolate, filename), | ||
| entryPoint.IsEmpty() ? NULL : *v8::String::Utf8Value(isolate, entryPoint), | ||
| &error | ||
| ); | ||
| if (status != SQLITE_OK) { | ||
| ThrowSqliteError(db->addon, error, status); | ||
| } | ||
| sqlite3_free(error); | ||
| } | ||
| NODE_METHOD(Database::JS_close) { | ||
| Database* db = Unwrap<Database>(info.This()); | ||
| if (db->open) { | ||
| REQUIRE_DATABASE_NOT_BUSY(db); | ||
| REQUIRE_DATABASE_NO_ITERATORS(db); | ||
| db->addon->dbs.erase(db); | ||
| db->CloseHandles(); | ||
| } | ||
| } | ||
| NODE_METHOD(Database::JS_defaultSafeIntegers) { | ||
| Database* db = Unwrap<Database>(info.This()); | ||
| if (info.Length() == 0) db->safe_ints = true; | ||
| else { REQUIRE_ARGUMENT_BOOLEAN(first, db->safe_ints); } | ||
| } | ||
| NODE_METHOD(Database::JS_unsafeMode) { | ||
| Database* db = Unwrap<Database>(info.This()); | ||
| if (info.Length() == 0) db->unsafe_mode = true; | ||
| else { REQUIRE_ARGUMENT_BOOLEAN(first, db->unsafe_mode); } | ||
| sqlite3_db_config(db->db_handle, SQLITE_DBCONFIG_DEFENSIVE, static_cast<int>(!db->unsafe_mode), NULL); | ||
| } | ||
| NODE_GETTER(Database::JS_open) { | ||
| info.GetReturnValue().Set(Unwrap<Database>(info.This())->open); | ||
| } | ||
| NODE_GETTER(Database::JS_inTransaction) { | ||
| Database* db = Unwrap<Database>(info.This()); | ||
| info.GetReturnValue().Set(db->open && !static_cast<bool>(sqlite3_get_autocommit(db->db_handle))); | ||
| } |
| class Database : public node::ObjectWrap { | ||
| public: | ||
| ~Database(); | ||
| // Whenever this is used, addon->dbs.erase() must be invoked beforehand. | ||
| void CloseHandles(); | ||
| // Used to support ordered containers. | ||
| class CompareDatabase { public: | ||
| inline bool operator() (Database const * const a, Database const * const b) const { | ||
| return a < b; | ||
| } | ||
| }; | ||
| class CompareStatement { public: | ||
| inline bool operator() (Statement const * const a, Statement const * const b) const { | ||
| return Statement::Compare(a, b); | ||
| } | ||
| }; | ||
| class CompareBackup { public: | ||
| inline bool operator() (Backup const * const a, Backup const * const b) const { | ||
| return Backup::Compare(a, b); | ||
| } | ||
| }; | ||
| // Proper error handling logic for when an sqlite3 operation fails. | ||
| void ThrowDatabaseError(); | ||
| static void ThrowSqliteError(Addon* addon, sqlite3* db_handle); | ||
| static void ThrowSqliteError(Addon* addon, const char* message, int code); | ||
| // Allows Statements to log their executed SQL. | ||
| bool Log(v8::Isolate* isolate, sqlite3_stmt* handle); | ||
| // Allow Statements to manage themselves when created and garbage collected. | ||
| inline void AddStatement(Statement* stmt) { stmts.insert(stmts.end(), stmt); } | ||
| inline void RemoveStatement(Statement* stmt) { stmts.erase(stmt); } | ||
| // Allow Backups to manage themselves when created and garbage collected. | ||
| inline void AddBackup(Backup* backup) { backups.insert(backups.end(), backup); } | ||
| inline void RemoveBackup(Backup* backup) { backups.erase(backup); } | ||
| // A view for Statements to see and modify Database state. | ||
| // The order of these fields must exactly match their actual order. | ||
| struct State { | ||
| const bool open; | ||
| bool busy; | ||
| const bool safe_ints; | ||
| const bool unsafe_mode; | ||
| bool was_js_error; | ||
| const bool has_logger; | ||
| unsigned short iterators; | ||
| Addon* const addon; | ||
| }; | ||
| inline State* GetState() { return reinterpret_cast<State*>(&open); } | ||
| inline sqlite3* GetHandle() { return db_handle; } | ||
| inline Addon* GetAddon() { return addon; } | ||
| static INIT(Init); | ||
| private: | ||
| explicit Database( | ||
| v8::Isolate* isolate, | ||
| Addon* addon, | ||
| sqlite3* db_handle, | ||
| v8::Local<v8::Value> logger | ||
| ); | ||
| static NODE_METHOD(JS_new); | ||
| static NODE_METHOD(JS_prepare); | ||
| static NODE_METHOD(JS_exec); | ||
| static NODE_METHOD(JS_backup); | ||
| static NODE_METHOD(JS_serialize); | ||
| static NODE_METHOD(JS_function); | ||
| static NODE_METHOD(JS_aggregate); | ||
| static NODE_METHOD(JS_table); | ||
| static NODE_METHOD(JS_loadExtension); | ||
| static NODE_METHOD(JS_close); | ||
| static NODE_METHOD(JS_defaultSafeIntegers); | ||
| static NODE_METHOD(JS_unsafeMode); | ||
| static NODE_GETTER(JS_open); | ||
| static NODE_GETTER(JS_inTransaction); | ||
| static bool Deserialize(v8::Local<v8::Object> buffer, Addon* addon, sqlite3* db_handle, bool readonly); | ||
| static void FreeSerialization(char* data, void* _); | ||
| static const int MAX_BUFFER_SIZE; | ||
| static const int MAX_STRING_SIZE; | ||
| sqlite3* const db_handle; | ||
| bool open; | ||
| bool busy; | ||
| bool safe_ints; | ||
| bool unsafe_mode; | ||
| bool was_js_error; | ||
| const bool has_logger; | ||
| unsigned short iterators; | ||
| Addon* const addon; | ||
| const v8::Global<v8::Value> logger; | ||
| std::set<Statement*, CompareStatement> stmts; | ||
| std::set<Backup*, CompareBackup> backups; | ||
| }; |
| StatementIterator::StatementIterator(Statement* stmt, bool bound) : | ||
| node::ObjectWrap(), | ||
| stmt(stmt), | ||
| handle(stmt->handle), | ||
| db_state(stmt->db->GetState()), | ||
| bound(bound), | ||
| safe_ints(stmt->safe_ints), | ||
| mode(stmt->mode), | ||
| alive(true), | ||
| logged(!db_state->has_logger) { | ||
| assert(stmt != NULL); | ||
| assert(handle != NULL); | ||
| assert(stmt->bound == bound); | ||
| assert(stmt->alive == true); | ||
| assert(stmt->locked == false); | ||
| assert(db_state->iterators < USHRT_MAX); | ||
| stmt->locked = true; | ||
| db_state->iterators += 1; | ||
| } | ||
| // The ~Statement destructor currently covers any state this object creates. | ||
| // Additionally, we actually DON'T want to revert stmt->locked or db_state | ||
| // ->iterators in this destructor, to ensure deterministic database access. | ||
| StatementIterator::~StatementIterator() {} | ||
| void StatementIterator::Next(NODE_ARGUMENTS info) { | ||
| assert(alive == true); | ||
| db_state->busy = true; | ||
| if (!logged) { | ||
| logged = true; | ||
| if (stmt->db->Log(OnlyIsolate, handle)) { | ||
| db_state->busy = false; | ||
| Throw(); | ||
| return; | ||
| } | ||
| } | ||
| int status = sqlite3_step(handle); | ||
| db_state->busy = false; | ||
| if (status == SQLITE_ROW) { | ||
| UseIsolate; | ||
| UseContext; | ||
| info.GetReturnValue().Set( | ||
| NewRecord(isolate, ctx, Data::GetRowJS(isolate, ctx, handle, safe_ints, mode), db_state->addon, false) | ||
| ); | ||
| } else { | ||
| if (status == SQLITE_DONE) Return(info); | ||
| else Throw(); | ||
| } | ||
| } | ||
| void StatementIterator::Return(NODE_ARGUMENTS info) { | ||
| Cleanup(); | ||
| STATEMENT_RETURN_LOGIC(DoneRecord(OnlyIsolate, db_state->addon)); | ||
| } | ||
| void StatementIterator::Throw() { | ||
| Cleanup(); | ||
| Database* db = stmt->db; | ||
| STATEMENT_THROW_LOGIC(); | ||
| } | ||
| void StatementIterator::Cleanup() { | ||
| assert(alive == true); | ||
| alive = false; | ||
| stmt->locked = false; | ||
| db_state->iterators -= 1; | ||
| sqlite3_reset(handle); | ||
| } | ||
| INIT(StatementIterator::Init) { | ||
| v8::Local<v8::FunctionTemplate> t = NewConstructorTemplate(isolate, data, JS_new, "StatementIterator"); | ||
| SetPrototypeMethod(isolate, data, t, "next", JS_next); | ||
| SetPrototypeMethod(isolate, data, t, "return", JS_return); | ||
| SetPrototypeSymbolMethod(isolate, data, t, v8::Symbol::GetIterator(isolate), JS_symbolIterator); | ||
| return t->GetFunction(OnlyContext).ToLocalChecked(); | ||
| } | ||
| NODE_METHOD(StatementIterator::JS_new) { | ||
| UseAddon; | ||
| if (!addon->privileged_info) return ThrowTypeError("Disabled constructor"); | ||
| assert(info.IsConstructCall()); | ||
| StatementIterator* iter; | ||
| { | ||
| NODE_ARGUMENTS info = *addon->privileged_info; | ||
| STATEMENT_START_LOGIC(REQUIRE_STATEMENT_RETURNS_DATA, DOES_ADD_ITERATOR); | ||
| iter = new StatementIterator(stmt, bound); | ||
| } | ||
| UseIsolate; | ||
| UseContext; | ||
| iter->Wrap(info.This()); | ||
| SetFrozen(isolate, ctx, info.This(), addon->cs.statement, addon->privileged_info->This()); | ||
| info.GetReturnValue().Set(info.This()); | ||
| } | ||
| NODE_METHOD(StatementIterator::JS_next) { | ||
| StatementIterator* iter = Unwrap<StatementIterator>(info.This()); | ||
| REQUIRE_DATABASE_NOT_BUSY(iter->db_state); | ||
| if (iter->alive) iter->Next(info); | ||
| else info.GetReturnValue().Set(DoneRecord(OnlyIsolate, iter->db_state->addon)); | ||
| } | ||
| NODE_METHOD(StatementIterator::JS_return) { | ||
| StatementIterator* iter = Unwrap<StatementIterator>(info.This()); | ||
| REQUIRE_DATABASE_NOT_BUSY(iter->db_state); | ||
| if (iter->alive) iter->Return(info); | ||
| else info.GetReturnValue().Set(DoneRecord(OnlyIsolate, iter->db_state->addon)); | ||
| } | ||
| NODE_METHOD(StatementIterator::JS_symbolIterator) { | ||
| info.GetReturnValue().Set(info.This()); | ||
| } |
| class StatementIterator : public node::ObjectWrap { | ||
| public: | ||
| // The ~Statement destructor currently covers any state this object creates. | ||
| // Additionally, we actually DON'T want to revert stmt->locked or db_state | ||
| // ->iterators in this destructor, to ensure deterministic database access. | ||
| ~StatementIterator(); | ||
| static INIT(Init); | ||
| private: | ||
| explicit StatementIterator(Statement* stmt, bool bound); | ||
| void Next(NODE_ARGUMENTS info); | ||
| void Return(NODE_ARGUMENTS info); | ||
| void Throw(); | ||
| void Cleanup(); | ||
| static inline v8::Local<v8::Object> NewRecord( | ||
| v8::Isolate* isolate, | ||
| v8::Local<v8::Context> ctx, | ||
| v8::Local<v8::Value> value, | ||
| Addon* addon, | ||
| bool done | ||
| ) { | ||
| v8::Local<v8::Object> record = v8::Object::New(isolate); | ||
| record->Set(ctx, addon->cs.value.Get(isolate), value).FromJust(); | ||
| record->Set(ctx, addon->cs.done.Get(isolate), v8::Boolean::New(isolate, done)).FromJust(); | ||
| return record; | ||
| } | ||
| static inline v8::Local<v8::Object> DoneRecord(v8::Isolate* isolate, Addon* addon) { | ||
| return NewRecord(isolate, OnlyContext, v8::Undefined(isolate), addon, true); | ||
| } | ||
| static NODE_METHOD(JS_new); | ||
| static NODE_METHOD(JS_next); | ||
| static NODE_METHOD(JS_return); | ||
| static NODE_METHOD(JS_symbolIterator); | ||
| Statement* const stmt; | ||
| sqlite3_stmt* const handle; | ||
| Database::State* const db_state; | ||
| const bool bound; | ||
| const bool safe_ints; | ||
| const char mode; | ||
| bool alive; | ||
| bool logged; | ||
| }; |
| Statement::Statement( | ||
| Database* db, | ||
| sqlite3_stmt* handle, | ||
| sqlite3_uint64 id, | ||
| bool returns_data | ||
| ) : | ||
| node::ObjectWrap(), | ||
| db(db), | ||
| handle(handle), | ||
| extras(new Extras(id)), | ||
| alive(true), | ||
| locked(false), | ||
| bound(false), | ||
| has_bind_map(false), | ||
| safe_ints(db->GetState()->safe_ints), | ||
| mode(Data::FLAT), | ||
| returns_data(returns_data) { | ||
| assert(db != NULL); | ||
| assert(handle != NULL); | ||
| assert(db->GetState()->open); | ||
| assert(!db->GetState()->busy); | ||
| db->AddStatement(this); | ||
| } | ||
| Statement::~Statement() { | ||
| if (alive) db->RemoveStatement(this); | ||
| CloseHandles(); | ||
| delete extras; | ||
| } | ||
| // Whenever this is used, db->RemoveStatement must be invoked beforehand. | ||
| void Statement::CloseHandles() { | ||
| if (alive) { | ||
| alive = false; | ||
| sqlite3_finalize(handle); | ||
| } | ||
| } | ||
| // Returns the Statement's bind map (creates it upon first execution). | ||
| BindMap* Statement::GetBindMap(v8::Isolate* isolate) { | ||
| if (has_bind_map) return &extras->bind_map; | ||
| BindMap* bind_map = &extras->bind_map; | ||
| int param_count = sqlite3_bind_parameter_count(handle); | ||
| for (int i = 1; i <= param_count; ++i) { | ||
| const char* name = sqlite3_bind_parameter_name(handle, i); | ||
| if (name != NULL) bind_map->Add(isolate, name + 1, i); | ||
| } | ||
| has_bind_map = true; | ||
| return bind_map; | ||
| } | ||
| Statement::Extras::Extras(sqlite3_uint64 id) | ||
| : bind_map(0), id(id) {} | ||
| INIT(Statement::Init) { | ||
| v8::Local<v8::FunctionTemplate> t = NewConstructorTemplate(isolate, data, JS_new, "Statement"); | ||
| SetPrototypeMethod(isolate, data, t, "run", JS_run); | ||
| SetPrototypeMethod(isolate, data, t, "get", JS_get); | ||
| SetPrototypeMethod(isolate, data, t, "all", JS_all); | ||
| SetPrototypeMethod(isolate, data, t, "iterate", JS_iterate); | ||
| SetPrototypeMethod(isolate, data, t, "bind", JS_bind); | ||
| SetPrototypeMethod(isolate, data, t, "pluck", JS_pluck); | ||
| SetPrototypeMethod(isolate, data, t, "expand", JS_expand); | ||
| SetPrototypeMethod(isolate, data, t, "raw", JS_raw); | ||
| SetPrototypeMethod(isolate, data, t, "safeIntegers", JS_safeIntegers); | ||
| SetPrototypeMethod(isolate, data, t, "columns", JS_columns); | ||
| SetPrototypeGetter(isolate, data, t, "busy", JS_busy); | ||
| return t->GetFunction(OnlyContext).ToLocalChecked(); | ||
| } | ||
| NODE_METHOD(Statement::JS_new) { | ||
| UseAddon; | ||
| if (!addon->privileged_info) { | ||
| return ThrowTypeError("Statements can only be constructed by the db.prepare() method"); | ||
| } | ||
| assert(info.IsConstructCall()); | ||
| Database* db = Unwrap<Database>(addon->privileged_info->This()); | ||
| REQUIRE_DATABASE_OPEN(db->GetState()); | ||
| REQUIRE_DATABASE_NOT_BUSY(db->GetState()); | ||
| v8::Local<v8::String> source = (*addon->privileged_info)[0].As<v8::String>(); | ||
| v8::Local<v8::Object> database = (*addon->privileged_info)[1].As<v8::Object>(); | ||
| bool pragmaMode = (*addon->privileged_info)[2].As<v8::Boolean>()->Value(); | ||
| int flags = SQLITE_PREPARE_PERSISTENT; | ||
| if (pragmaMode) { | ||
| REQUIRE_DATABASE_NO_ITERATORS_UNLESS_UNSAFE(db->GetState()); | ||
| flags = 0; | ||
| } | ||
| UseIsolate; | ||
| v8::String::Utf8Value utf8(isolate, source); | ||
| sqlite3_stmt* handle; | ||
| const char* tail; | ||
| if (sqlite3_prepare_v3(db->GetHandle(), *utf8, utf8.length() + 1, flags, &handle, &tail) != SQLITE_OK) { | ||
| return db->ThrowDatabaseError(); | ||
| } | ||
| if (handle == NULL) { | ||
| return ThrowRangeError("The supplied SQL string contains no statements"); | ||
| } | ||
| // https://github.com/WiseLibs/better-sqlite3/issues/975#issuecomment-1520934678 | ||
| for (char c; (c = *tail); ) { | ||
| if (IS_SKIPPED(c)) { | ||
| ++tail; | ||
| continue; | ||
| } | ||
| if (c == '/' && tail[1] == '*') { | ||
| tail += 2; | ||
| for (char c; (c = *tail); ++tail) { | ||
| if (c == '*' && tail[1] == '/') { | ||
| tail += 2; | ||
| break; | ||
| } | ||
| } | ||
| } else if (c == '-' && tail[1] == '-') { | ||
| tail += 2; | ||
| for (char c; (c = *tail); ++tail) { | ||
| if (c == '\n') { | ||
| ++tail; | ||
| break; | ||
| } | ||
| } | ||
| } else { | ||
| sqlite3_finalize(handle); | ||
| return ThrowRangeError("The supplied SQL string contains more than one statement"); | ||
| } | ||
| } | ||
| UseContext; | ||
| bool returns_data = sqlite3_column_count(handle) >= 1 || pragmaMode; | ||
| Statement* stmt = new Statement(db, handle, addon->NextId(), returns_data); | ||
| stmt->Wrap(info.This()); | ||
| SetFrozen(isolate, ctx, info.This(), addon->cs.reader, v8::Boolean::New(isolate, returns_data)); | ||
| SetFrozen(isolate, ctx, info.This(), addon->cs.readonly, v8::Boolean::New(isolate, sqlite3_stmt_readonly(handle) != 0)); | ||
| SetFrozen(isolate, ctx, info.This(), addon->cs.source, source); | ||
| SetFrozen(isolate, ctx, info.This(), addon->cs.database, database); | ||
| info.GetReturnValue().Set(info.This()); | ||
| } | ||
| NODE_METHOD(Statement::JS_run) { | ||
| STATEMENT_START(ALLOW_ANY_STATEMENT, DOES_MUTATE); | ||
| sqlite3* db_handle = db->GetHandle(); | ||
| int total_changes_before = sqlite3_total_changes(db_handle); | ||
| sqlite3_step(handle); | ||
| if (sqlite3_reset(handle) == SQLITE_OK) { | ||
| int changes = sqlite3_total_changes(db_handle) == total_changes_before ? 0 : sqlite3_changes(db_handle); | ||
| sqlite3_int64 id = sqlite3_last_insert_rowid(db_handle); | ||
| Addon* addon = db->GetAddon(); | ||
| UseContext; | ||
| v8::Local<v8::Object> result = v8::Object::New(isolate); | ||
| result->Set(ctx, addon->cs.changes.Get(isolate), v8::Int32::New(isolate, changes)).FromJust(); | ||
| result->Set(ctx, addon->cs.lastInsertRowid.Get(isolate), | ||
| stmt->safe_ints | ||
| ? v8::BigInt::New(isolate, id).As<v8::Value>() | ||
| : v8::Number::New(isolate, (double)id).As<v8::Value>() | ||
| ).FromJust(); | ||
| STATEMENT_RETURN(result); | ||
| } | ||
| STATEMENT_THROW(); | ||
| } | ||
| NODE_METHOD(Statement::JS_get) { | ||
| STATEMENT_START(REQUIRE_STATEMENT_RETURNS_DATA, DOES_NOT_MUTATE); | ||
| int status = sqlite3_step(handle); | ||
| if (status == SQLITE_ROW) { | ||
| v8::Local<v8::Value> result = Data::GetRowJS(isolate, OnlyContext, handle, stmt->safe_ints, stmt->mode); | ||
| sqlite3_reset(handle); | ||
| STATEMENT_RETURN(result); | ||
| } else if (status == SQLITE_DONE) { | ||
| sqlite3_reset(handle); | ||
| STATEMENT_RETURN(v8::Undefined(isolate)); | ||
| } | ||
| sqlite3_reset(handle); | ||
| STATEMENT_THROW(); | ||
| } | ||
| NODE_METHOD(Statement::JS_all) { | ||
| STATEMENT_START(REQUIRE_STATEMENT_RETURNS_DATA, DOES_NOT_MUTATE); | ||
| UseContext; | ||
| const bool safe_ints = stmt->safe_ints; | ||
| const char mode = stmt->mode; | ||
| #if !defined(NODE_MODULE_VERSION) || NODE_MODULE_VERSION < 127 | ||
| bool js_error = false; | ||
| uint32_t row_count = 0; | ||
| v8::Local<v8::Array> result = v8::Array::New(isolate, 0); | ||
| while (sqlite3_step(handle) == SQLITE_ROW) { | ||
| if (row_count == 0xffffffff) { ThrowRangeError("Array overflow (too many rows returned)"); js_error = true; break; } | ||
| result->Set(ctx, row_count++, Data::GetRowJS(isolate, ctx, handle, safe_ints, mode)).FromJust(); | ||
| } | ||
| if (sqlite3_reset(handle) == SQLITE_OK && !js_error) { | ||
| STATEMENT_RETURN(result); | ||
| } | ||
| if (js_error) db->GetState()->was_js_error = true; | ||
| STATEMENT_THROW(); | ||
| #else | ||
| v8::LocalVector<v8::Value> rows(isolate); | ||
| rows.reserve(8); | ||
| if (mode == Data::FLAT) { | ||
| RowBuilder rowBuilder(isolate, handle, safe_ints); | ||
| while (sqlite3_step(handle) == SQLITE_ROW) { | ||
| rows.emplace_back(rowBuilder.GetRowJS()); | ||
| } | ||
| } else { | ||
| while (sqlite3_step(handle) == SQLITE_ROW) { | ||
| rows.emplace_back(Data::GetRowJS(isolate, ctx, handle, safe_ints, mode)); | ||
| } | ||
| } | ||
| if (sqlite3_reset(handle) == SQLITE_OK) { | ||
| if (rows.size() > 0xffffffff) { | ||
| ThrowRangeError("Array overflow (too many rows returned)"); | ||
| db->GetState()->was_js_error = true; | ||
| } else { | ||
| STATEMENT_RETURN(v8::Array::New(isolate, rows.data(), rows.size())); | ||
| } | ||
| } | ||
| STATEMENT_THROW(); | ||
| #endif | ||
| } | ||
| NODE_METHOD(Statement::JS_iterate) { | ||
| UseAddon; | ||
| UseIsolate; | ||
| v8::Local<v8::Function> c = addon->StatementIterator.Get(isolate); | ||
| addon->privileged_info = &info; | ||
| v8::MaybeLocal<v8::Object> maybeIterator = c->NewInstance(OnlyContext, 0, NULL); | ||
| addon->privileged_info = NULL; | ||
| if (!maybeIterator.IsEmpty()) info.GetReturnValue().Set(maybeIterator.ToLocalChecked()); | ||
| } | ||
| NODE_METHOD(Statement::JS_bind) { | ||
| Statement* stmt = Unwrap<Statement>(info.This()); | ||
| if (stmt->bound) return ThrowTypeError("The bind() method can only be invoked once per statement object"); | ||
| REQUIRE_DATABASE_OPEN(stmt->db->GetState()); | ||
| REQUIRE_DATABASE_NOT_BUSY(stmt->db->GetState()); | ||
| REQUIRE_STATEMENT_NOT_LOCKED(stmt); | ||
| STATEMENT_BIND(stmt->handle); | ||
| stmt->bound = true; | ||
| info.GetReturnValue().Set(info.This()); | ||
| } | ||
| NODE_METHOD(Statement::JS_pluck) { | ||
| Statement* stmt = Unwrap<Statement>(info.This()); | ||
| if (!stmt->returns_data) return ThrowTypeError("The pluck() method is only for statements that return data"); | ||
| REQUIRE_DATABASE_NOT_BUSY(stmt->db->GetState()); | ||
| REQUIRE_STATEMENT_NOT_LOCKED(stmt); | ||
| bool use = true; | ||
| if (info.Length() != 0) { REQUIRE_ARGUMENT_BOOLEAN(first, use); } | ||
| stmt->mode = use ? Data::PLUCK : stmt->mode == Data::PLUCK ? Data::FLAT : stmt->mode; | ||
| info.GetReturnValue().Set(info.This()); | ||
| } | ||
| NODE_METHOD(Statement::JS_expand) { | ||
| Statement* stmt = Unwrap<Statement>(info.This()); | ||
| if (!stmt->returns_data) return ThrowTypeError("The expand() method is only for statements that return data"); | ||
| REQUIRE_DATABASE_NOT_BUSY(stmt->db->GetState()); | ||
| REQUIRE_STATEMENT_NOT_LOCKED(stmt); | ||
| bool use = true; | ||
| if (info.Length() != 0) { REQUIRE_ARGUMENT_BOOLEAN(first, use); } | ||
| stmt->mode = use ? Data::EXPAND : stmt->mode == Data::EXPAND ? Data::FLAT : stmt->mode; | ||
| info.GetReturnValue().Set(info.This()); | ||
| } | ||
| NODE_METHOD(Statement::JS_raw) { | ||
| Statement* stmt = Unwrap<Statement>(info.This()); | ||
| if (!stmt->returns_data) return ThrowTypeError("The raw() method is only for statements that return data"); | ||
| REQUIRE_DATABASE_NOT_BUSY(stmt->db->GetState()); | ||
| REQUIRE_STATEMENT_NOT_LOCKED(stmt); | ||
| bool use = true; | ||
| if (info.Length() != 0) { REQUIRE_ARGUMENT_BOOLEAN(first, use); } | ||
| stmt->mode = use ? Data::RAW : stmt->mode == Data::RAW ? Data::FLAT : stmt->mode; | ||
| info.GetReturnValue().Set(info.This()); | ||
| } | ||
| NODE_METHOD(Statement::JS_safeIntegers) { | ||
| Statement* stmt = Unwrap<Statement>(info.This()); | ||
| REQUIRE_DATABASE_NOT_BUSY(stmt->db->GetState()); | ||
| REQUIRE_STATEMENT_NOT_LOCKED(stmt); | ||
| if (info.Length() == 0) stmt->safe_ints = true; | ||
| else { REQUIRE_ARGUMENT_BOOLEAN(first, stmt->safe_ints); } | ||
| info.GetReturnValue().Set(info.This()); | ||
| } | ||
| NODE_METHOD(Statement::JS_columns) { | ||
| Statement* stmt = Unwrap<Statement>(info.This()); | ||
| if (!stmt->returns_data) return ThrowTypeError("The columns() method is only for statements that return data"); | ||
| REQUIRE_DATABASE_OPEN(stmt->db->GetState()); | ||
| REQUIRE_DATABASE_NOT_BUSY(stmt->db->GetState()); | ||
| Addon* addon = stmt->db->GetAddon(); | ||
| UseIsolate; | ||
| #if !defined(NODE_MODULE_VERSION) || NODE_MODULE_VERSION < 127 | ||
| UseContext; | ||
| int column_count = sqlite3_column_count(stmt->handle); | ||
| v8::Local<v8::Array> columns = v8::Array::New(isolate); | ||
| v8::Local<v8::String> name = addon->cs.name.Get(isolate); | ||
| v8::Local<v8::String> columnName = addon->cs.column.Get(isolate); | ||
| v8::Local<v8::String> tableName = addon->cs.table.Get(isolate); | ||
| v8::Local<v8::String> databaseName = addon->cs.database.Get(isolate); | ||
| v8::Local<v8::String> typeName = addon->cs.type.Get(isolate); | ||
| for (int i = 0; i < column_count; ++i) { | ||
| v8::Local<v8::Object> column = v8::Object::New(isolate); | ||
| column->Set(ctx, name, | ||
| InternalizedFromUtf8OrNull(isolate, sqlite3_column_name(stmt->handle, i), -1) | ||
| ).FromJust(); | ||
| column->Set(ctx, columnName, | ||
| InternalizedFromUtf8OrNull(isolate, sqlite3_column_origin_name(stmt->handle, i), -1) | ||
| ).FromJust(); | ||
| column->Set(ctx, tableName, | ||
| InternalizedFromUtf8OrNull(isolate, sqlite3_column_table_name(stmt->handle, i), -1) | ||
| ).FromJust(); | ||
| column->Set(ctx, databaseName, | ||
| InternalizedFromUtf8OrNull(isolate, sqlite3_column_database_name(stmt->handle, i), -1) | ||
| ).FromJust(); | ||
| column->Set(ctx, typeName, | ||
| InternalizedFromUtf8OrNull(isolate, sqlite3_column_decltype(stmt->handle, i), -1) | ||
| ).FromJust(); | ||
| columns->Set(ctx, i, column).FromJust(); | ||
| } | ||
| info.GetReturnValue().Set(columns); | ||
| #else | ||
| v8::LocalVector<v8::Name> keys(isolate); | ||
| keys.reserve(5); | ||
| keys.emplace_back(addon->cs.name.Get(isolate).As<v8::Name>()); | ||
| keys.emplace_back(addon->cs.column.Get(isolate).As<v8::Name>()); | ||
| keys.emplace_back(addon->cs.table.Get(isolate).As<v8::Name>()); | ||
| keys.emplace_back(addon->cs.database.Get(isolate).As<v8::Name>()); | ||
| keys.emplace_back(addon->cs.type.Get(isolate).As<v8::Name>()); | ||
| int column_count = sqlite3_column_count(stmt->handle); | ||
| v8::LocalVector<v8::Value> columns(isolate); | ||
| columns.reserve(column_count); | ||
| for (int i = 0; i < column_count; ++i) { | ||
| v8::LocalVector<v8::Value> values(isolate); | ||
| keys.reserve(5); | ||
| values.emplace_back( | ||
| InternalizedFromUtf8OrNull(isolate, sqlite3_column_name(stmt->handle, i), -1) | ||
| ); | ||
| values.emplace_back( | ||
| InternalizedFromUtf8OrNull(isolate, sqlite3_column_origin_name(stmt->handle, i), -1) | ||
| ); | ||
| values.emplace_back( | ||
| InternalizedFromUtf8OrNull(isolate, sqlite3_column_table_name(stmt->handle, i), -1) | ||
| ); | ||
| values.emplace_back( | ||
| InternalizedFromUtf8OrNull(isolate, sqlite3_column_database_name(stmt->handle, i), -1) | ||
| ); | ||
| values.emplace_back( | ||
| InternalizedFromUtf8OrNull(isolate, sqlite3_column_decltype(stmt->handle, i), -1) | ||
| ); | ||
| columns.emplace_back( | ||
| v8::Object::New(isolate, | ||
| v8::Object::New(isolate)->GetPrototype(), | ||
| keys.data(), | ||
| values.data(), | ||
| keys.size() | ||
| ) | ||
| ); | ||
| } | ||
| info.GetReturnValue().Set( | ||
| v8::Array::New(isolate, columns.data(), columns.size()) | ||
| ); | ||
| #endif | ||
| } | ||
| NODE_GETTER(Statement::JS_busy) { | ||
| Statement* stmt = Unwrap<Statement>(info.This()); | ||
| info.GetReturnValue().Set(stmt->alive && stmt->locked); | ||
| } |
| class Statement : public node::ObjectWrap { friend class StatementIterator; | ||
| public: | ||
| ~Statement(); | ||
| // Whenever this is used, db->RemoveStatement must be invoked beforehand. | ||
| void CloseHandles(); | ||
| // Used to support ordered containers. | ||
| static inline bool Compare(Statement const * const a, Statement const * const b) { | ||
| return a->extras->id < b->extras->id; | ||
| } | ||
| // Returns the Statement's bind map (creates it upon first execution). | ||
| BindMap* GetBindMap(v8::Isolate* isolate); | ||
| static INIT(Init); | ||
| private: | ||
| // A class for holding values that are less often used. | ||
| class Extras { friend class Statement; | ||
| explicit Extras(sqlite3_uint64 id); | ||
| BindMap bind_map; | ||
| const sqlite3_uint64 id; | ||
| }; | ||
| explicit Statement( | ||
| Database* db, | ||
| sqlite3_stmt* handle, | ||
| sqlite3_uint64 id, | ||
| bool returns_data | ||
| ); | ||
| static NODE_METHOD(JS_new); | ||
| static NODE_METHOD(JS_run); | ||
| static NODE_METHOD(JS_get); | ||
| static NODE_METHOD(JS_all); | ||
| static NODE_METHOD(JS_iterate); | ||
| static NODE_METHOD(JS_bind); | ||
| static NODE_METHOD(JS_pluck); | ||
| static NODE_METHOD(JS_expand); | ||
| static NODE_METHOD(JS_raw); | ||
| static NODE_METHOD(JS_safeIntegers); | ||
| static NODE_METHOD(JS_columns); | ||
| static NODE_GETTER(JS_busy); | ||
| Database* const db; | ||
| sqlite3_stmt* const handle; | ||
| Extras* const extras; | ||
| bool alive; | ||
| bool locked; | ||
| bool bound; | ||
| bool has_bind_map; | ||
| bool safe_ints; | ||
| char mode; | ||
| const bool returns_data; | ||
| }; |
| class BindMap { | ||
| public: | ||
| // This nested class represents a single mapping between a parameter name | ||
| // and its associated parameter index in a prepared statement. | ||
| class Pair { friend class BindMap; | ||
| public: | ||
| inline int GetIndex() { | ||
| return index; | ||
| } | ||
| inline v8::Local<v8::String> GetName(v8::Isolate* isolate) { | ||
| return name.Get(isolate); | ||
| } | ||
| private: | ||
| explicit Pair(v8::Isolate* isolate, const char* name, int index) | ||
| : name(isolate, InternalizedFromUtf8(isolate, name, -1)), index(index) {} | ||
| explicit Pair(v8::Isolate* isolate, Pair* pair) | ||
| : name(isolate, pair->name), index(pair->index) {} | ||
| const v8::Global<v8::String> name; | ||
| const int index; | ||
| }; | ||
| explicit BindMap(char _) { | ||
| assert(_ == 0); | ||
| pairs = NULL; | ||
| capacity = 0; | ||
| length = 0; | ||
| } | ||
| ~BindMap() { | ||
| while (length) pairs[--length].~Pair(); | ||
| FREE_ARRAY<Pair>(pairs); | ||
| } | ||
| inline Pair* GetPairs() { | ||
| return pairs; | ||
| } | ||
| inline int GetSize() { | ||
| return length; | ||
| } | ||
| // Adds a pair to the bind map, expanding the capacity if necessary. | ||
| void Add(v8::Isolate* isolate, const char* name, int index) { | ||
| assert(name != NULL); | ||
| if (capacity == length) Grow(isolate); | ||
| new (pairs + length++) Pair(isolate, name, index); | ||
| } | ||
| private: | ||
| void Grow(v8::Isolate* isolate) { | ||
| assert(capacity == length); | ||
| capacity = (capacity << 1) | 2; | ||
| Pair* new_pairs = ALLOC_ARRAY<Pair>(capacity); | ||
| for (int i = 0; i < length; ++i) { | ||
| new (new_pairs + i) Pair(isolate, pairs + i); | ||
| pairs[i].~Pair(); | ||
| } | ||
| FREE_ARRAY<Pair>(pairs); | ||
| pairs = new_pairs; | ||
| } | ||
| Pair* pairs; | ||
| int capacity; | ||
| int length; | ||
| }; |
| class Binder { | ||
| public: | ||
| explicit Binder(sqlite3_stmt* _handle) { | ||
| handle = _handle; | ||
| param_count = sqlite3_bind_parameter_count(_handle); | ||
| anon_index = 0; | ||
| success = true; | ||
| } | ||
| bool Bind(NODE_ARGUMENTS info, int argc, Statement* stmt) { | ||
| assert(anon_index == 0); | ||
| Result result = BindArgs(info, argc, stmt); | ||
| if (success && result.count != param_count) { | ||
| if (result.count < param_count) { | ||
| if (!result.bound_object && stmt->GetBindMap(OnlyIsolate)->GetSize()) { | ||
| Fail(ThrowTypeError, "Missing named parameters"); | ||
| } else { | ||
| Fail(ThrowRangeError, "Too few parameter values were provided"); | ||
| } | ||
| } else { | ||
| Fail(ThrowRangeError, "Too many parameter values were provided"); | ||
| } | ||
| } | ||
| return success; | ||
| } | ||
| private: | ||
| struct Result { | ||
| int count; | ||
| bool bound_object; | ||
| }; | ||
| static bool IsPlainObject(v8::Isolate* isolate, v8::Local<v8::Object> obj) { | ||
| v8::Local<v8::Value> proto = obj->GetPrototype(); | ||
| v8::Local<v8::Context> ctx = obj->GetCreationContext().ToLocalChecked(); | ||
| ctx->Enter(); | ||
| v8::Local<v8::Value> baseProto = v8::Object::New(isolate)->GetPrototype(); | ||
| ctx->Exit(); | ||
| return proto->StrictEquals(baseProto) || proto->StrictEquals(v8::Null(isolate)); | ||
| } | ||
| void Fail(void (*Throw)(const char* _), const char* message) { | ||
| assert(success == true); | ||
| assert((Throw == NULL) == (message == NULL)); | ||
| assert(Throw == ThrowError || Throw == ThrowTypeError || Throw == ThrowRangeError || Throw == NULL); | ||
| if (Throw) Throw(message); | ||
| success = false; | ||
| } | ||
| int NextAnonIndex() { | ||
| while (sqlite3_bind_parameter_name(handle, ++anon_index) != NULL) {} | ||
| return anon_index; | ||
| } | ||
| // Binds the value at the given index or throws an appropriate error. | ||
| void BindValue(v8::Isolate* isolate, v8::Local<v8::Value> value, int index) { | ||
| int status = Data::BindValueFromJS(isolate, handle, index, value); | ||
| if (status != SQLITE_OK) { | ||
| switch (status) { | ||
| case -1: | ||
| return Fail(ThrowTypeError, "SQLite3 can only bind numbers, strings, bigints, buffers, and null"); | ||
| case SQLITE_TOOBIG: | ||
| return Fail(ThrowRangeError, "The bound string, buffer, or bigint is too big"); | ||
| case SQLITE_RANGE: | ||
| return Fail(ThrowRangeError, "Too many parameter values were provided"); | ||
| case SQLITE_NOMEM: | ||
| return Fail(ThrowError, "Out of memory"); | ||
| default: | ||
| return Fail(ThrowError, "An unexpected error occured while trying to bind parameters"); | ||
| } | ||
| assert(false); | ||
| } | ||
| } | ||
| // Binds each value in the array or throws an appropriate error. | ||
| // The number of successfully bound parameters is returned. | ||
| int BindArray(v8::Isolate* isolate, v8::Local<v8::Array> arr) { | ||
| UseContext; | ||
| uint32_t length = arr->Length(); | ||
| if (length > INT_MAX) { | ||
| Fail(ThrowRangeError, "Too many parameter values were provided"); | ||
| return 0; | ||
| } | ||
| int len = static_cast<int>(length); | ||
| for (int i = 0; i < len; ++i) { | ||
| v8::MaybeLocal<v8::Value> maybeValue = arr->Get(ctx, i); | ||
| if (maybeValue.IsEmpty()) { | ||
| Fail(NULL, NULL); | ||
| return i; | ||
| } | ||
| BindValue(isolate, maybeValue.ToLocalChecked(), NextAnonIndex()); | ||
| if (!success) { | ||
| return i; | ||
| } | ||
| } | ||
| return len; | ||
| } | ||
| // Binds all named parameters using the values found in the given object. | ||
| // The number of successfully bound parameters is returned. | ||
| // If a named parameter is missing from the object, an error is thrown. | ||
| // This should only be invoked once per instance. | ||
| int BindObject(v8::Isolate* isolate, v8::Local<v8::Object> obj, Statement* stmt) { | ||
| UseContext; | ||
| BindMap* bind_map = stmt->GetBindMap(isolate); | ||
| BindMap::Pair* pairs = bind_map->GetPairs(); | ||
| int len = bind_map->GetSize(); | ||
| for (int i = 0; i < len; ++i) { | ||
| v8::Local<v8::String> key = pairs[i].GetName(isolate); | ||
| // Check if the named parameter was provided. | ||
| v8::Maybe<bool> has_property = obj->HasOwnProperty(ctx, key); | ||
| if (has_property.IsNothing()) { | ||
| Fail(NULL, NULL); | ||
| return i; | ||
| } | ||
| if (!has_property.FromJust()) { | ||
| v8::String::Utf8Value param_name(isolate, key); | ||
| Fail(ThrowRangeError, (std::string("Missing named parameter \"") + *param_name + "\"").c_str()); | ||
| return i; | ||
| } | ||
| // Get the current property value. | ||
| v8::MaybeLocal<v8::Value> maybeValue = obj->Get(ctx, key); | ||
| if (maybeValue.IsEmpty()) { | ||
| Fail(NULL, NULL); | ||
| return i; | ||
| } | ||
| BindValue(isolate, maybeValue.ToLocalChecked(), pairs[i].GetIndex()); | ||
| if (!success) { | ||
| return i; | ||
| } | ||
| } | ||
| return len; | ||
| } | ||
| // Binds all parameters using the values found in the arguments object. | ||
| // Anonymous parameter values can be directly in the arguments object or in an Array. | ||
| // Named parameter values can be provided in a plain Object argument. | ||
| // Only one plain Object argument may be provided. | ||
| // If an error occurs, an appropriate error is thrown. | ||
| // The return value is a struct indicating how many parameters were successfully bound | ||
| // and whether or not it tried to bind an object. | ||
| Result BindArgs(NODE_ARGUMENTS info, int argc, Statement* stmt) { | ||
| UseIsolate; | ||
| int count = 0; | ||
| bool bound_object = false; | ||
| for (int i = 0; i < argc; ++i) { | ||
| v8::Local<v8::Value> arg = info[i]; | ||
| if (arg->IsArray()) { | ||
| count += BindArray(isolate, arg.As<v8::Array>()); | ||
| if (!success) break; | ||
| continue; | ||
| } | ||
| if (arg->IsObject() && !node::Buffer::HasInstance(arg)) { | ||
| v8::Local<v8::Object> obj = arg.As<v8::Object>(); | ||
| if (IsPlainObject(isolate, obj)) { | ||
| if (bound_object) { | ||
| Fail(ThrowTypeError, "You cannot specify named parameters in two different objects"); | ||
| break; | ||
| } | ||
| bound_object = true; | ||
| count += BindObject(isolate, obj, stmt); | ||
| if (!success) break; | ||
| continue; | ||
| } else if (stmt->GetBindMap(isolate)->GetSize()) { | ||
| Fail(ThrowTypeError, "Named parameters can only be passed within plain objects"); | ||
| break; | ||
| } | ||
| } | ||
| BindValue(isolate, arg, NextAnonIndex()); | ||
| if (!success) break; | ||
| count += 1; | ||
| } | ||
| return { count, bound_object }; | ||
| } | ||
| sqlite3_stmt* handle; | ||
| int param_count; | ||
| int anon_index; // This value should only be used by NextAnonIndex() | ||
| bool success; // This value should only be set by Fail() | ||
| }; |
| class CS { | ||
| public: | ||
| v8::Local<v8::String> Code(v8::Isolate* isolate, int code) { | ||
| auto element = codes.find(code); | ||
| if (element != codes.end()) return element->second.Get(isolate); | ||
| return StringFromUtf8(isolate, (std::string("UNKNOWN_SQLITE_ERROR_") + std::to_string(code)).c_str(), -1); | ||
| } | ||
| explicit CS(v8::Isolate* isolate) { | ||
| SetString(isolate, database, "database"); | ||
| SetString(isolate, reader, "reader"); | ||
| SetString(isolate, source, "source"); | ||
| SetString(isolate, memory, "memory"); | ||
| SetString(isolate, readonly, "readonly"); | ||
| SetString(isolate, name, "name"); | ||
| SetString(isolate, next, "next"); | ||
| SetString(isolate, length, "length"); | ||
| SetString(isolate, done, "done"); | ||
| SetString(isolate, value, "value"); | ||
| SetString(isolate, changes, "changes"); | ||
| SetString(isolate, lastInsertRowid, "lastInsertRowid"); | ||
| SetString(isolate, statement, "statement"); | ||
| SetString(isolate, column, "column"); | ||
| SetString(isolate, table, "table"); | ||
| SetString(isolate, type, "type"); | ||
| SetString(isolate, totalPages, "totalPages"); | ||
| SetString(isolate, remainingPages, "remainingPages"); | ||
| SetCode(isolate, SQLITE_OK, "SQLITE_OK"); | ||
| SetCode(isolate, SQLITE_ERROR, "SQLITE_ERROR"); | ||
| SetCode(isolate, SQLITE_INTERNAL, "SQLITE_INTERNAL"); | ||
| SetCode(isolate, SQLITE_PERM, "SQLITE_PERM"); | ||
| SetCode(isolate, SQLITE_ABORT, "SQLITE_ABORT"); | ||
| SetCode(isolate, SQLITE_BUSY, "SQLITE_BUSY"); | ||
| SetCode(isolate, SQLITE_LOCKED, "SQLITE_LOCKED"); | ||
| SetCode(isolate, SQLITE_NOMEM, "SQLITE_NOMEM"); | ||
| SetCode(isolate, SQLITE_READONLY, "SQLITE_READONLY"); | ||
| SetCode(isolate, SQLITE_INTERRUPT, "SQLITE_INTERRUPT"); | ||
| SetCode(isolate, SQLITE_IOERR, "SQLITE_IOERR"); | ||
| SetCode(isolate, SQLITE_CORRUPT, "SQLITE_CORRUPT"); | ||
| SetCode(isolate, SQLITE_NOTFOUND, "SQLITE_NOTFOUND"); | ||
| SetCode(isolate, SQLITE_FULL, "SQLITE_FULL"); | ||
| SetCode(isolate, SQLITE_CANTOPEN, "SQLITE_CANTOPEN"); | ||
| SetCode(isolate, SQLITE_PROTOCOL, "SQLITE_PROTOCOL"); | ||
| SetCode(isolate, SQLITE_EMPTY, "SQLITE_EMPTY"); | ||
| SetCode(isolate, SQLITE_SCHEMA, "SQLITE_SCHEMA"); | ||
| SetCode(isolate, SQLITE_TOOBIG, "SQLITE_TOOBIG"); | ||
| SetCode(isolate, SQLITE_CONSTRAINT, "SQLITE_CONSTRAINT"); | ||
| SetCode(isolate, SQLITE_MISMATCH, "SQLITE_MISMATCH"); | ||
| SetCode(isolate, SQLITE_MISUSE, "SQLITE_MISUSE"); | ||
| SetCode(isolate, SQLITE_NOLFS, "SQLITE_NOLFS"); | ||
| SetCode(isolate, SQLITE_AUTH, "SQLITE_AUTH"); | ||
| SetCode(isolate, SQLITE_FORMAT, "SQLITE_FORMAT"); | ||
| SetCode(isolate, SQLITE_RANGE, "SQLITE_RANGE"); | ||
| SetCode(isolate, SQLITE_NOTADB, "SQLITE_NOTADB"); | ||
| SetCode(isolate, SQLITE_NOTICE, "SQLITE_NOTICE"); | ||
| SetCode(isolate, SQLITE_WARNING, "SQLITE_WARNING"); | ||
| SetCode(isolate, SQLITE_ROW, "SQLITE_ROW"); | ||
| SetCode(isolate, SQLITE_DONE, "SQLITE_DONE"); | ||
| SetCode(isolate, SQLITE_ERROR_MISSING_COLLSEQ, "SQLITE_ERROR_MISSING_COLLSEQ"); | ||
| SetCode(isolate, SQLITE_ERROR_RETRY, "SQLITE_ERROR_RETRY"); | ||
| SetCode(isolate, SQLITE_ERROR_SNAPSHOT, "SQLITE_ERROR_SNAPSHOT"); | ||
| SetCode(isolate, SQLITE_IOERR_READ, "SQLITE_IOERR_READ"); | ||
| SetCode(isolate, SQLITE_IOERR_SHORT_READ, "SQLITE_IOERR_SHORT_READ"); | ||
| SetCode(isolate, SQLITE_IOERR_WRITE, "SQLITE_IOERR_WRITE"); | ||
| SetCode(isolate, SQLITE_IOERR_FSYNC, "SQLITE_IOERR_FSYNC"); | ||
| SetCode(isolate, SQLITE_IOERR_DIR_FSYNC, "SQLITE_IOERR_DIR_FSYNC"); | ||
| SetCode(isolate, SQLITE_IOERR_TRUNCATE, "SQLITE_IOERR_TRUNCATE"); | ||
| SetCode(isolate, SQLITE_IOERR_FSTAT, "SQLITE_IOERR_FSTAT"); | ||
| SetCode(isolate, SQLITE_IOERR_UNLOCK, "SQLITE_IOERR_UNLOCK"); | ||
| SetCode(isolate, SQLITE_IOERR_RDLOCK, "SQLITE_IOERR_RDLOCK"); | ||
| SetCode(isolate, SQLITE_IOERR_DELETE, "SQLITE_IOERR_DELETE"); | ||
| SetCode(isolate, SQLITE_IOERR_BLOCKED, "SQLITE_IOERR_BLOCKED"); | ||
| SetCode(isolate, SQLITE_IOERR_NOMEM, "SQLITE_IOERR_NOMEM"); | ||
| SetCode(isolate, SQLITE_IOERR_ACCESS, "SQLITE_IOERR_ACCESS"); | ||
| SetCode(isolate, SQLITE_IOERR_CHECKRESERVEDLOCK, "SQLITE_IOERR_CHECKRESERVEDLOCK"); | ||
| SetCode(isolate, SQLITE_IOERR_LOCK, "SQLITE_IOERR_LOCK"); | ||
| SetCode(isolate, SQLITE_IOERR_CLOSE, "SQLITE_IOERR_CLOSE"); | ||
| SetCode(isolate, SQLITE_IOERR_DIR_CLOSE, "SQLITE_IOERR_DIR_CLOSE"); | ||
| SetCode(isolate, SQLITE_IOERR_SHMOPEN, "SQLITE_IOERR_SHMOPEN"); | ||
| SetCode(isolate, SQLITE_IOERR_SHMSIZE, "SQLITE_IOERR_SHMSIZE"); | ||
| SetCode(isolate, SQLITE_IOERR_SHMLOCK, "SQLITE_IOERR_SHMLOCK"); | ||
| SetCode(isolate, SQLITE_IOERR_SHMMAP, "SQLITE_IOERR_SHMMAP"); | ||
| SetCode(isolate, SQLITE_IOERR_SEEK, "SQLITE_IOERR_SEEK"); | ||
| SetCode(isolate, SQLITE_IOERR_DELETE_NOENT, "SQLITE_IOERR_DELETE_NOENT"); | ||
| SetCode(isolate, SQLITE_IOERR_MMAP, "SQLITE_IOERR_MMAP"); | ||
| SetCode(isolate, SQLITE_IOERR_GETTEMPPATH, "SQLITE_IOERR_GETTEMPPATH"); | ||
| SetCode(isolate, SQLITE_IOERR_CONVPATH, "SQLITE_IOERR_CONVPATH"); | ||
| SetCode(isolate, SQLITE_IOERR_VNODE, "SQLITE_IOERR_VNODE"); | ||
| SetCode(isolate, SQLITE_IOERR_AUTH, "SQLITE_IOERR_AUTH"); | ||
| SetCode(isolate, SQLITE_IOERR_BEGIN_ATOMIC, "SQLITE_IOERR_BEGIN_ATOMIC"); | ||
| SetCode(isolate, SQLITE_IOERR_COMMIT_ATOMIC, "SQLITE_IOERR_COMMIT_ATOMIC"); | ||
| SetCode(isolate, SQLITE_IOERR_ROLLBACK_ATOMIC, "SQLITE_IOERR_ROLLBACK_ATOMIC"); | ||
| SetCode(isolate, SQLITE_IOERR_DATA, "SQLITE_IOERR_DATA"); | ||
| SetCode(isolate, SQLITE_IOERR_CORRUPTFS, "SQLITE_IOERR_CORRUPTFS"); | ||
| SetCode(isolate, SQLITE_IOERR_IN_PAGE, "SQLITE_IOERR_IN_PAGE"); | ||
| SetCode(isolate, SQLITE_LOCKED_SHAREDCACHE, "SQLITE_LOCKED_SHAREDCACHE"); | ||
| SetCode(isolate, SQLITE_LOCKED_VTAB, "SQLITE_LOCKED_VTAB"); | ||
| SetCode(isolate, SQLITE_BUSY_RECOVERY, "SQLITE_BUSY_RECOVERY"); | ||
| SetCode(isolate, SQLITE_BUSY_SNAPSHOT, "SQLITE_BUSY_SNAPSHOT"); | ||
| SetCode(isolate, SQLITE_CANTOPEN_NOTEMPDIR, "SQLITE_CANTOPEN_NOTEMPDIR"); | ||
| SetCode(isolate, SQLITE_CANTOPEN_ISDIR, "SQLITE_CANTOPEN_ISDIR"); | ||
| SetCode(isolate, SQLITE_CANTOPEN_FULLPATH, "SQLITE_CANTOPEN_FULLPATH"); | ||
| SetCode(isolate, SQLITE_CANTOPEN_CONVPATH, "SQLITE_CANTOPEN_CONVPATH"); | ||
| SetCode(isolate, SQLITE_CANTOPEN_DIRTYWAL, "SQLITE_CANTOPEN_DIRTYWAL"); | ||
| SetCode(isolate, SQLITE_CANTOPEN_SYMLINK, "SQLITE_CANTOPEN_SYMLINK"); | ||
| SetCode(isolate, SQLITE_CORRUPT_VTAB, "SQLITE_CORRUPT_VTAB"); | ||
| SetCode(isolate, SQLITE_CORRUPT_SEQUENCE, "SQLITE_CORRUPT_SEQUENCE"); | ||
| SetCode(isolate, SQLITE_CORRUPT_INDEX, "SQLITE_CORRUPT_INDEX"); | ||
| SetCode(isolate, SQLITE_READONLY_RECOVERY, "SQLITE_READONLY_RECOVERY"); | ||
| SetCode(isolate, SQLITE_READONLY_CANTLOCK, "SQLITE_READONLY_CANTLOCK"); | ||
| SetCode(isolate, SQLITE_READONLY_ROLLBACK, "SQLITE_READONLY_ROLLBACK"); | ||
| SetCode(isolate, SQLITE_READONLY_DBMOVED, "SQLITE_READONLY_DBMOVED"); | ||
| SetCode(isolate, SQLITE_READONLY_CANTINIT, "SQLITE_READONLY_CANTINIT"); | ||
| SetCode(isolate, SQLITE_READONLY_DIRECTORY, "SQLITE_READONLY_DIRECTORY"); | ||
| SetCode(isolate, SQLITE_ABORT_ROLLBACK, "SQLITE_ABORT_ROLLBACK"); | ||
| SetCode(isolate, SQLITE_CONSTRAINT_CHECK, "SQLITE_CONSTRAINT_CHECK"); | ||
| SetCode(isolate, SQLITE_CONSTRAINT_COMMITHOOK, "SQLITE_CONSTRAINT_COMMITHOOK"); | ||
| SetCode(isolate, SQLITE_CONSTRAINT_FOREIGNKEY, "SQLITE_CONSTRAINT_FOREIGNKEY"); | ||
| SetCode(isolate, SQLITE_CONSTRAINT_FUNCTION, "SQLITE_CONSTRAINT_FUNCTION"); | ||
| SetCode(isolate, SQLITE_CONSTRAINT_NOTNULL, "SQLITE_CONSTRAINT_NOTNULL"); | ||
| SetCode(isolate, SQLITE_CONSTRAINT_PRIMARYKEY, "SQLITE_CONSTRAINT_PRIMARYKEY"); | ||
| SetCode(isolate, SQLITE_CONSTRAINT_TRIGGER, "SQLITE_CONSTRAINT_TRIGGER"); | ||
| SetCode(isolate, SQLITE_CONSTRAINT_UNIQUE, "SQLITE_CONSTRAINT_UNIQUE"); | ||
| SetCode(isolate, SQLITE_CONSTRAINT_VTAB, "SQLITE_CONSTRAINT_VTAB"); | ||
| SetCode(isolate, SQLITE_CONSTRAINT_ROWID, "SQLITE_CONSTRAINT_ROWID"); | ||
| SetCode(isolate, SQLITE_CONSTRAINT_PINNED, "SQLITE_CONSTRAINT_PINNED"); | ||
| SetCode(isolate, SQLITE_CONSTRAINT_DATATYPE, "SQLITE_CONSTRAINT_DATATYPE"); | ||
| SetCode(isolate, SQLITE_NOTICE_RECOVER_WAL, "SQLITE_NOTICE_RECOVER_WAL"); | ||
| SetCode(isolate, SQLITE_NOTICE_RECOVER_ROLLBACK, "SQLITE_NOTICE_RECOVER_ROLLBACK"); | ||
| SetCode(isolate, SQLITE_NOTICE_RBU, "SQLITE_NOTICE_RBU"); | ||
| SetCode(isolate, SQLITE_WARNING_AUTOINDEX, "SQLITE_WARNING_AUTOINDEX"); | ||
| SetCode(isolate, SQLITE_AUTH_USER, "SQLITE_AUTH_USER"); | ||
| SetCode(isolate, SQLITE_OK_LOAD_PERMANENTLY, "SQLITE_OK_LOAD_PERMANENTLY"); | ||
| SetCode(isolate, SQLITE_OK_SYMLINK, "SQLITE_OK_SYMLINK"); | ||
| } | ||
| v8::Global<v8::String> database; | ||
| v8::Global<v8::String> reader; | ||
| v8::Global<v8::String> source; | ||
| v8::Global<v8::String> memory; | ||
| v8::Global<v8::String> readonly; | ||
| v8::Global<v8::String> name; | ||
| v8::Global<v8::String> next; | ||
| v8::Global<v8::String> length; | ||
| v8::Global<v8::String> done; | ||
| v8::Global<v8::String> value; | ||
| v8::Global<v8::String> changes; | ||
| v8::Global<v8::String> lastInsertRowid; | ||
| v8::Global<v8::String> statement; | ||
| v8::Global<v8::String> column; | ||
| v8::Global<v8::String> table; | ||
| v8::Global<v8::String> type; | ||
| v8::Global<v8::String> totalPages; | ||
| v8::Global<v8::String> remainingPages; | ||
| private: | ||
| static void SetString(v8::Isolate* isolate, v8::Global<v8::String>& constant, const char* str) { | ||
| constant.Reset(isolate, InternalizedFromLatin1(isolate, str)); | ||
| } | ||
| void SetCode(v8::Isolate* isolate, int code, const char* str) { | ||
| codes.emplace(std::piecewise_construct, | ||
| std::forward_as_tuple(code), | ||
| std::forward_as_tuple(isolate, InternalizedFromLatin1(isolate, str))); | ||
| } | ||
| std::unordered_map<int, v8::Global<v8::String> > codes; | ||
| }; |
| class CustomAggregate : public CustomFunction { | ||
| public: | ||
| explicit CustomAggregate( | ||
| v8::Isolate* isolate, | ||
| Database* db, | ||
| const char* name, | ||
| v8::Local<v8::Value> start, | ||
| v8::Local<v8::Function> step, | ||
| v8::Local<v8::Value> inverse, | ||
| v8::Local<v8::Value> result, | ||
| bool safe_ints | ||
| ) : | ||
| CustomFunction(isolate, db, name, step, safe_ints), | ||
| invoke_result(result->IsFunction()), | ||
| invoke_start(start->IsFunction()), | ||
| inverse(isolate, inverse->IsFunction() ? inverse.As<v8::Function>() : v8::Local<v8::Function>()), | ||
| result(isolate, result->IsFunction() ? result.As<v8::Function>() : v8::Local<v8::Function>()), | ||
| start(isolate, start) {} | ||
| static void xStep(sqlite3_context* invocation, int argc, sqlite3_value** argv) { | ||
| xStepBase(invocation, argc, argv, &CustomAggregate::fn); | ||
| } | ||
| static void xInverse(sqlite3_context* invocation, int argc, sqlite3_value** argv) { | ||
| xStepBase(invocation, argc, argv, &CustomAggregate::inverse); | ||
| } | ||
| static void xValue(sqlite3_context* invocation) { | ||
| xValueBase(invocation, false); | ||
| } | ||
| static void xFinal(sqlite3_context* invocation) { | ||
| xValueBase(invocation, true); | ||
| } | ||
| private: | ||
| static inline void xStepBase(sqlite3_context* invocation, int argc, sqlite3_value** argv, const v8::Global<v8::Function> CustomAggregate::*ptrtm) { | ||
| AGGREGATE_START(); | ||
| v8::Local<v8::Value> args_fast[5]; | ||
| v8::Local<v8::Value>* args = argc <= 4 ? args_fast : ALLOC_ARRAY<v8::Local<v8::Value>>(argc + 1); | ||
| args[0] = acc->value.Get(isolate); | ||
| if (argc != 0) Data::GetArgumentsJS(isolate, args + 1, argv, argc, self->safe_ints); | ||
| v8::MaybeLocal<v8::Value> maybeReturnValue = (self->*ptrtm).Get(isolate)->Call(OnlyContext, v8::Undefined(isolate), argc + 1, args); | ||
| if (args != args_fast) delete[] args; | ||
| if (maybeReturnValue.IsEmpty()) { | ||
| self->PropagateJSError(invocation); | ||
| } else { | ||
| v8::Local<v8::Value> returnValue = maybeReturnValue.ToLocalChecked(); | ||
| if (!returnValue->IsUndefined()) acc->value.Reset(isolate, returnValue); | ||
| } | ||
| } | ||
| static inline void xValueBase(sqlite3_context* invocation, bool is_final) { | ||
| AGGREGATE_START(); | ||
| if (!is_final) { | ||
| acc->is_window = true; | ||
| } else if (acc->is_window) { | ||
| DestroyAccumulator(invocation); | ||
| return; | ||
| } | ||
| v8::Local<v8::Value> result = acc->value.Get(isolate); | ||
| if (self->invoke_result) { | ||
| v8::MaybeLocal<v8::Value> maybeResult = self->result.Get(isolate)->Call(OnlyContext, v8::Undefined(isolate), 1, &result); | ||
| if (maybeResult.IsEmpty()) { | ||
| self->PropagateJSError(invocation); | ||
| return; | ||
| } | ||
| result = maybeResult.ToLocalChecked(); | ||
| } | ||
| Data::ResultValueFromJS(isolate, invocation, result, self); | ||
| if (is_final) DestroyAccumulator(invocation); | ||
| } | ||
| struct Accumulator { public: | ||
| v8::Global<v8::Value> value; | ||
| bool initialized; | ||
| bool is_window; | ||
| }; | ||
| Accumulator* GetAccumulator(sqlite3_context* invocation) { | ||
| Accumulator* acc = static_cast<Accumulator*>(sqlite3_aggregate_context(invocation, sizeof(Accumulator))); | ||
| if (!acc->initialized) { | ||
| assert(acc->value.IsEmpty()); | ||
| acc->initialized = true; | ||
| if (invoke_start) { | ||
| v8::MaybeLocal<v8::Value> maybeSeed = start.Get(isolate).As<v8::Function>()->Call(OnlyContext, v8::Undefined(isolate), 0, NULL); | ||
| if (maybeSeed.IsEmpty()) PropagateJSError(invocation); | ||
| else acc->value.Reset(isolate, maybeSeed.ToLocalChecked()); | ||
| } else { | ||
| assert(!start.IsEmpty()); | ||
| acc->value.Reset(isolate, start); | ||
| } | ||
| } | ||
| return acc; | ||
| } | ||
| static void DestroyAccumulator(sqlite3_context* invocation) { | ||
| Accumulator* acc = static_cast<Accumulator*>(sqlite3_aggregate_context(invocation, sizeof(Accumulator))); | ||
| assert(acc->initialized); | ||
| acc->value.Reset(); | ||
| } | ||
| void PropagateJSError(sqlite3_context* invocation) { | ||
| DestroyAccumulator(invocation); | ||
| CustomFunction::PropagateJSError(invocation); | ||
| } | ||
| const bool invoke_result; | ||
| const bool invoke_start; | ||
| const v8::Global<v8::Function> inverse; | ||
| const v8::Global<v8::Function> result; | ||
| const v8::Global<v8::Value> start; | ||
| }; |
| class CustomFunction : protected DataConverter { | ||
| public: | ||
| explicit CustomFunction( | ||
| v8::Isolate* isolate, | ||
| Database* db, | ||
| const char* name, | ||
| v8::Local<v8::Function> fn, | ||
| bool safe_ints | ||
| ) : | ||
| name(name), | ||
| db(db), | ||
| isolate(isolate), | ||
| fn(isolate, fn), | ||
| safe_ints(safe_ints) {} | ||
| virtual ~CustomFunction() {} | ||
| static void xDestroy(void* self) { | ||
| delete static_cast<CustomFunction*>(self); | ||
| } | ||
| static void xFunc(sqlite3_context* invocation, int argc, sqlite3_value** argv) { | ||
| FUNCTION_START(); | ||
| v8::Local<v8::Value> args_fast[4]; | ||
| v8::Local<v8::Value>* args = NULL; | ||
| if (argc != 0) { | ||
| args = argc <= 4 ? args_fast : ALLOC_ARRAY<v8::Local<v8::Value>>(argc); | ||
| Data::GetArgumentsJS(isolate, args, argv, argc, self->safe_ints); | ||
| } | ||
| v8::MaybeLocal<v8::Value> maybeReturnValue = self->fn.Get(isolate)->Call(OnlyContext, v8::Undefined(isolate), argc, args); | ||
| if (args != args_fast) delete[] args; | ||
| if (maybeReturnValue.IsEmpty()) self->PropagateJSError(invocation); | ||
| else Data::ResultValueFromJS(isolate, invocation, maybeReturnValue.ToLocalChecked(), self); | ||
| } | ||
| protected: | ||
| void PropagateJSError(sqlite3_context* invocation) { | ||
| assert(db->GetState()->was_js_error == false); | ||
| db->GetState()->was_js_error = true; | ||
| sqlite3_result_error(invocation, "", 0); | ||
| } | ||
| std::string GetDataErrorPrefix() { | ||
| return std::string("User-defined function ") + name + "() returned"; | ||
| } | ||
| private: | ||
| const std::string name; | ||
| Database* const db; | ||
| protected: | ||
| v8::Isolate* const isolate; | ||
| const v8::Global<v8::Function> fn; | ||
| const bool safe_ints; | ||
| }; |
| class CustomTable { | ||
| public: | ||
| explicit CustomTable( | ||
| v8::Isolate* isolate, | ||
| Database* db, | ||
| const char* name, | ||
| v8::Local<v8::Function> factory | ||
| ) : | ||
| addon(db->GetAddon()), | ||
| isolate(isolate), | ||
| db(db), | ||
| name(name), | ||
| factory(isolate, factory) {} | ||
| static void Destructor(void* self) { | ||
| delete static_cast<CustomTable*>(self); | ||
| } | ||
| static sqlite3_module MODULE; | ||
| static sqlite3_module EPONYMOUS_MODULE; | ||
| private: | ||
| // This nested class is instantiated on each CREATE VIRTUAL TABLE statement. | ||
| class VTab { friend class CustomTable; | ||
| explicit VTab( | ||
| CustomTable* parent, | ||
| v8::Local<v8::Function> generator, | ||
| std::vector<std::string> parameter_names, | ||
| bool safe_ints | ||
| ) : | ||
| parent(parent), | ||
| parameter_count(parameter_names.size()), | ||
| safe_ints(safe_ints), | ||
| generator(parent->isolate, generator), | ||
| parameter_names(parameter_names) { | ||
| ((void)base); | ||
| } | ||
| static inline CustomTable::VTab* Upcast(sqlite3_vtab* vtab) { | ||
| return reinterpret_cast<VTab*>(vtab); | ||
| } | ||
| inline sqlite3_vtab* Downcast() { | ||
| return reinterpret_cast<sqlite3_vtab*>(this); | ||
| } | ||
| sqlite3_vtab base; | ||
| CustomTable * const parent; | ||
| const int parameter_count; | ||
| const bool safe_ints; | ||
| const v8::Global<v8::Function> generator; | ||
| const std::vector<std::string> parameter_names; | ||
| }; | ||
| // This nested class is instantiated each time a virtual table is scanned. | ||
| class Cursor { friend class CustomTable; | ||
| static inline CustomTable::Cursor* Upcast(sqlite3_vtab_cursor* cursor) { | ||
| return reinterpret_cast<Cursor*>(cursor); | ||
| } | ||
| inline sqlite3_vtab_cursor* Downcast() { | ||
| return reinterpret_cast<sqlite3_vtab_cursor*>(this); | ||
| } | ||
| inline CustomTable::VTab* GetVTab() { | ||
| return VTab::Upcast(base.pVtab); | ||
| } | ||
| sqlite3_vtab_cursor base; | ||
| v8::Global<v8::Object> iterator; | ||
| v8::Global<v8::Function> next; | ||
| v8::Global<v8::Array> row; | ||
| bool done; | ||
| sqlite_int64 rowid; | ||
| }; | ||
| // This nested class is used by Data::ResultValueFromJS to report errors. | ||
| class TempDataConverter : DataConverter { friend class CustomTable; | ||
| explicit TempDataConverter(CustomTable* parent) : | ||
| parent(parent), | ||
| status(SQLITE_OK) {} | ||
| void PropagateJSError(sqlite3_context* invocation) { | ||
| status = SQLITE_ERROR; | ||
| parent->PropagateJSError(); | ||
| } | ||
| std::string GetDataErrorPrefix() { | ||
| return std::string("Virtual table module \"") + parent->name + "\" yielded"; | ||
| } | ||
| CustomTable * const parent; | ||
| int status; | ||
| }; | ||
| // Although this function does nothing, we cannot use xConnect directly, | ||
| // because that would cause SQLite to register an eponymous virtual table. | ||
| static int xCreate(sqlite3* db_handle, void* _self, int argc, const char* const * argv, sqlite3_vtab** output, char** errOutput) { | ||
| return xConnect(db_handle, _self, argc, argv, output, errOutput); | ||
| } | ||
| // This method uses the factory function to instantiate a new virtual table. | ||
| static int xConnect(sqlite3* db_handle, void* _self, int argc, const char* const * argv, sqlite3_vtab** output, char** errOutput) { | ||
| CustomTable* self = static_cast<CustomTable*>(_self); | ||
| v8::Isolate* isolate = self->isolate; | ||
| v8::HandleScope scope(isolate); | ||
| UseContext; | ||
| v8::Local<v8::Value>* args = ALLOC_ARRAY<v8::Local<v8::Value>>(argc); | ||
| for (int i = 0; i < argc; ++i) { | ||
| args[i] = StringFromUtf8(isolate, argv[i], -1); | ||
| } | ||
| // Run the factory function to receive a new virtual table definition. | ||
| v8::MaybeLocal<v8::Value> maybeReturnValue = self->factory.Get(isolate)->Call(ctx, v8::Undefined(isolate), argc, args); | ||
| delete[] args; | ||
| if (maybeReturnValue.IsEmpty()) { | ||
| self->PropagateJSError(); | ||
| return SQLITE_ERROR; | ||
| } | ||
| // Extract each part of the virtual table definition. | ||
| v8::Local<v8::Array> returnValue = maybeReturnValue.ToLocalChecked().As<v8::Array>(); | ||
| v8::Local<v8::String> sqlString = returnValue->Get(ctx, 0).ToLocalChecked().As<v8::String>(); | ||
| v8::Local<v8::Function> generator = returnValue->Get(ctx, 1).ToLocalChecked().As<v8::Function>(); | ||
| v8::Local<v8::Array> parameterNames = returnValue->Get(ctx, 2).ToLocalChecked().As<v8::Array>(); | ||
| int safe_ints = returnValue->Get(ctx, 3).ToLocalChecked().As<v8::Int32>()->Value(); | ||
| bool direct_only = returnValue->Get(ctx, 4).ToLocalChecked().As<v8::Boolean>()->Value(); | ||
| v8::String::Utf8Value sql(isolate, sqlString); | ||
| safe_ints = safe_ints < 2 ? safe_ints : static_cast<int>(self->db->GetState()->safe_ints); | ||
| // Copy the parameter names into a std::vector. | ||
| std::vector<std::string> parameter_names; | ||
| for (int i = 0, len = parameterNames->Length(); i < len; ++i) { | ||
| v8::Local<v8::String> parameterName = parameterNames->Get(ctx, i).ToLocalChecked().As<v8::String>(); | ||
| v8::String::Utf8Value parameter_name(isolate, parameterName); | ||
| parameter_names.emplace_back(*parameter_name); | ||
| } | ||
| // Pass our SQL table definition to SQLite (this should never fail). | ||
| if (sqlite3_declare_vtab(db_handle, *sql) != SQLITE_OK) { | ||
| *errOutput = sqlite3_mprintf("failed to declare virtual table \"%s\"", argv[2]); | ||
| return SQLITE_ERROR; | ||
| } | ||
| if (direct_only && sqlite3_vtab_config(db_handle, SQLITE_VTAB_DIRECTONLY) != SQLITE_OK) { | ||
| *errOutput = sqlite3_mprintf("failed to configure virtual table \"%s\"", argv[2]); | ||
| return SQLITE_ERROR; | ||
| } | ||
| // Return the successfully created virtual table. | ||
| *output = (new VTab(self, generator, parameter_names, safe_ints))->Downcast(); | ||
| return SQLITE_OK; | ||
| } | ||
| static int xDisconnect(sqlite3_vtab* vtab) { | ||
| delete VTab::Upcast(vtab); | ||
| return SQLITE_OK; | ||
| } | ||
| static int xOpen(sqlite3_vtab* vtab, sqlite3_vtab_cursor** output) { | ||
| *output = (new Cursor())->Downcast(); | ||
| return SQLITE_OK; | ||
| } | ||
| static int xClose(sqlite3_vtab_cursor* cursor) { | ||
| delete Cursor::Upcast(cursor); | ||
| return SQLITE_OK; | ||
| } | ||
| // This method uses a fresh cursor to start a new scan of a virtual table. | ||
| // The args and idxNum are provided by xBestIndex (idxStr is unused). | ||
| // idxNum is a bitmap that provides the proper indices of the received args. | ||
| static int xFilter(sqlite3_vtab_cursor* _cursor, int idxNum, const char* idxStr, int argc, sqlite3_value** argv) { | ||
| Cursor* cursor = Cursor::Upcast(_cursor); | ||
| VTab* vtab = cursor->GetVTab(); | ||
| CustomTable* self = vtab->parent; | ||
| Addon* addon = self->addon; | ||
| v8::Isolate* isolate = self->isolate; | ||
| v8::HandleScope scope(isolate); | ||
| UseContext; | ||
| // Convert the SQLite arguments into JavaScript arguments. Note that | ||
| // the values in argv may be in the wrong order, so we fix that here. | ||
| v8::Local<v8::Value> args_fast[4]; | ||
| v8::Local<v8::Value>* args = NULL; | ||
| int parameter_count = vtab->parameter_count; | ||
| if (parameter_count != 0) { | ||
| args = parameter_count <= 4 ? args_fast : ALLOC_ARRAY<v8::Local<v8::Value>>(parameter_count); | ||
| int argn = 0; | ||
| bool safe_ints = vtab->safe_ints; | ||
| for (int i = 0; i < parameter_count; ++i) { | ||
| if (idxNum & 1 << i) { | ||
| args[i] = Data::GetValueJS(isolate, argv[argn++], safe_ints); | ||
| // If any arguments are NULL, the result set is necessarily | ||
| // empty, so don't bother to run the generator function. | ||
| if (args[i]->IsNull()) { | ||
| if (args != args_fast) delete[] args; | ||
| cursor->done = true; | ||
| return SQLITE_OK; | ||
| } | ||
| } else { | ||
| args[i] = v8::Undefined(isolate); | ||
| } | ||
| } | ||
| } | ||
| // Invoke the generator function to create a new iterator. | ||
| v8::MaybeLocal<v8::Value> maybeIterator = vtab->generator.Get(isolate)->Call(ctx, v8::Undefined(isolate), parameter_count, args); | ||
| if (args != args_fast) delete[] args; | ||
| if (maybeIterator.IsEmpty()) { | ||
| self->PropagateJSError(); | ||
| return SQLITE_ERROR; | ||
| } | ||
| // Store the iterator and its next() method; we'll be using it a lot. | ||
| v8::Local<v8::Object> iterator = maybeIterator.ToLocalChecked().As<v8::Object>(); | ||
| v8::Local<v8::Function> next = iterator->Get(ctx, addon->cs.next.Get(isolate)).ToLocalChecked().As<v8::Function>(); | ||
| cursor->iterator.Reset(isolate, iterator); | ||
| cursor->next.Reset(isolate, next); | ||
| cursor->rowid = 0; | ||
| // Advance the iterator/cursor to the first row. | ||
| return xNext(cursor->Downcast()); | ||
| } | ||
| // This method advances a virtual table's cursor to the next row. | ||
| // SQLite will call this method repeatedly, driving the generator function. | ||
| static int xNext(sqlite3_vtab_cursor* _cursor) { | ||
| Cursor* cursor = Cursor::Upcast(_cursor); | ||
| CustomTable* self = cursor->GetVTab()->parent; | ||
| Addon* addon = self->addon; | ||
| v8::Isolate* isolate = self->isolate; | ||
| v8::HandleScope scope(isolate); | ||
| UseContext; | ||
| v8::Local<v8::Object> iterator = cursor->iterator.Get(isolate); | ||
| v8::Local<v8::Function> next = cursor->next.Get(isolate); | ||
| v8::MaybeLocal<v8::Value> maybeRecord = next->Call(ctx, iterator, 0, NULL); | ||
| if (maybeRecord.IsEmpty()) { | ||
| self->PropagateJSError(); | ||
| return SQLITE_ERROR; | ||
| } | ||
| v8::Local<v8::Object> record = maybeRecord.ToLocalChecked().As<v8::Object>(); | ||
| bool done = record->Get(ctx, addon->cs.done.Get(isolate)).ToLocalChecked().As<v8::Boolean>()->Value(); | ||
| if (!done) { | ||
| cursor->row.Reset(isolate, record->Get(ctx, addon->cs.value.Get(isolate)).ToLocalChecked().As<v8::Array>()); | ||
| } | ||
| cursor->done = done; | ||
| cursor->rowid += 1; | ||
| return SQLITE_OK; | ||
| } | ||
| // If this method returns 1, SQLite will stop scanning the virtual table. | ||
| static int xEof(sqlite3_vtab_cursor* cursor) { | ||
| return Cursor::Upcast(cursor)->done; | ||
| } | ||
| // This method extracts some column from the cursor's current row. | ||
| static int xColumn(sqlite3_vtab_cursor* _cursor, sqlite3_context* invocation, int column) { | ||
| Cursor* cursor = Cursor::Upcast(_cursor); | ||
| CustomTable* self = cursor->GetVTab()->parent; | ||
| TempDataConverter temp_data_converter(self); | ||
| v8::Isolate* isolate = self->isolate; | ||
| v8::HandleScope scope(isolate); | ||
| v8::Local<v8::Array> row = cursor->row.Get(isolate); | ||
| v8::MaybeLocal<v8::Value> maybeColumnValue = row->Get(OnlyContext, column); | ||
| if (maybeColumnValue.IsEmpty()) { | ||
| temp_data_converter.PropagateJSError(NULL); | ||
| } else { | ||
| Data::ResultValueFromJS(isolate, invocation, maybeColumnValue.ToLocalChecked(), &temp_data_converter); | ||
| } | ||
| return temp_data_converter.status; | ||
| } | ||
| // This method outputs the rowid of the cursor's current row. | ||
| static int xRowid(sqlite3_vtab_cursor* cursor, sqlite_int64* output) { | ||
| *output = Cursor::Upcast(cursor)->rowid; | ||
| return SQLITE_OK; | ||
| } | ||
| // This method tells SQLite how to *plan* queries on our virtual table. | ||
| // It gets invoked (typically multiple times) during db.prepare(). | ||
| static int xBestIndex(sqlite3_vtab* vtab, sqlite3_index_info* output) { | ||
| int parameter_count = VTab::Upcast(vtab)->parameter_count; | ||
| int argument_count = 0; | ||
| std::vector<std::pair<int, int>> forwarded; | ||
| for (int i = 0, len = output->nConstraint; i < len; ++i) { | ||
| auto item = output->aConstraint[i]; | ||
| // The SQLITE_INDEX_CONSTRAINT_LIMIT and SQLITE_INDEX_CONSTRAINT_OFFSET | ||
| // operators have no left-hand operand, and so for those operators the | ||
| // corresponding item.iColumn is meaningless. | ||
| // We don't care those constraints. | ||
| if (item.op == SQLITE_INDEX_CONSTRAINT_LIMIT || item.op == SQLITE_INDEX_CONSTRAINT_OFFSET) { | ||
| continue; | ||
| } | ||
| // We only care about constraints on parameters, not regular columns. | ||
| if (item.iColumn >= 0 && item.iColumn < parameter_count) { | ||
| if (item.op != SQLITE_INDEX_CONSTRAINT_EQ) { | ||
| sqlite3_free(vtab->zErrMsg); | ||
| vtab->zErrMsg = sqlite3_mprintf( | ||
| "virtual table parameter \"%s\" can only be constrained by the '=' operator", | ||
| VTab::Upcast(vtab)->parameter_names.at(item.iColumn).c_str()); | ||
| return SQLITE_ERROR; | ||
| } | ||
| if (!item.usable) { | ||
| // Don't allow SQLite to make plans that ignore arguments. | ||
| // Otherwise, a user could pass arguments, but then they | ||
| // could appear undefined in the generator function. | ||
| return SQLITE_CONSTRAINT; | ||
| } | ||
| forwarded.emplace_back(item.iColumn, i); | ||
| } | ||
| } | ||
| // Tell SQLite to forward arguments to xFilter. | ||
| std::sort(forwarded.begin(), forwarded.end()); | ||
| for (std::pair<int, int> pair : forwarded) { | ||
| int bit = 1 << pair.first; | ||
| if (!(output->idxNum & bit)) { | ||
| output->idxNum |= bit; | ||
| output->aConstraintUsage[pair.second].argvIndex = ++argument_count; | ||
| output->aConstraintUsage[pair.second].omit = 1; | ||
| } | ||
| } | ||
| // Use a very high estimated cost so SQLite is not tempted to invoke the | ||
| // generator function within a loop, if it can be avoided. | ||
| output->estimatedCost = output->estimatedRows = 1000000000 / (argument_count + 1); | ||
| return SQLITE_OK; | ||
| } | ||
| void PropagateJSError() { | ||
| assert(db->GetState()->was_js_error == false); | ||
| db->GetState()->was_js_error = true; | ||
| } | ||
| Addon* const addon; | ||
| v8::Isolate* const isolate; | ||
| Database* const db; | ||
| const std::string name; | ||
| const v8::Global<v8::Function> factory; | ||
| }; | ||
| sqlite3_module CustomTable::MODULE = { | ||
| 0, /* iVersion */ | ||
| xCreate, /* xCreate */ | ||
| xConnect, /* xConnect */ | ||
| xBestIndex, /* xBestIndex */ | ||
| xDisconnect, /* xDisconnect */ | ||
| xDisconnect, /* xDestroy */ | ||
| xOpen, /* xOpen */ | ||
| xClose, /* xClose */ | ||
| xFilter, /* xFilter */ | ||
| xNext, /* xNext */ | ||
| xEof, /* xEof */ | ||
| xColumn, /* xColumn */ | ||
| xRowid, /* xRowid */ | ||
| NULL, /* xUpdate */ | ||
| NULL, /* xBegin */ | ||
| NULL, /* xSync */ | ||
| NULL, /* xCommit */ | ||
| NULL, /* xRollback */ | ||
| NULL, /* xFindMethod */ | ||
| NULL, /* xRename */ | ||
| NULL, /* xSavepoint */ | ||
| NULL, /* xRelease */ | ||
| NULL, /* xRollbackTo */ | ||
| NULL, /* xShadowName */ | ||
| NULL /* xIntegrity */ | ||
| }; | ||
| sqlite3_module CustomTable::EPONYMOUS_MODULE = { | ||
| 0, /* iVersion */ | ||
| NULL, /* xCreate */ | ||
| xConnect, /* xConnect */ | ||
| xBestIndex, /* xBestIndex */ | ||
| xDisconnect, /* xDisconnect */ | ||
| xDisconnect, /* xDestroy */ | ||
| xOpen, /* xOpen */ | ||
| xClose, /* xClose */ | ||
| xFilter, /* xFilter */ | ||
| xNext, /* xNext */ | ||
| xEof, /* xEof */ | ||
| xColumn, /* xColumn */ | ||
| xRowid, /* xRowid */ | ||
| NULL, /* xUpdate */ | ||
| NULL, /* xBegin */ | ||
| NULL, /* xSync */ | ||
| NULL, /* xCommit */ | ||
| NULL, /* xRollback */ | ||
| NULL, /* xFindMethod */ | ||
| NULL, /* xRename */ | ||
| NULL, /* xSavepoint */ | ||
| NULL, /* xRelease */ | ||
| NULL, /* xRollbackTo */ | ||
| NULL, /* xShadowName */ | ||
| NULL /* xIntegrity */ | ||
| }; |
| class DataConverter { | ||
| public: | ||
| void ThrowDataConversionError(sqlite3_context* invocation, bool isBigInt) { | ||
| if (isBigInt) { | ||
| ThrowRangeError((GetDataErrorPrefix() + " a bigint that was too big").c_str()); | ||
| } else { | ||
| ThrowTypeError((GetDataErrorPrefix() + " an invalid value").c_str()); | ||
| } | ||
| PropagateJSError(invocation); | ||
| } | ||
| protected: | ||
| virtual void PropagateJSError(sqlite3_context* invocation) = 0; | ||
| virtual std::string GetDataErrorPrefix() = 0; | ||
| }; |
| #define JS_VALUE_TO_SQLITE(to, value, isolate, ...) \ | ||
| if (value->IsNumber()) { \ | ||
| return sqlite3_##to##_double( \ | ||
| __VA_ARGS__, \ | ||
| value.As<v8::Number>()->Value() \ | ||
| ); \ | ||
| } else if (value->IsBigInt()) { \ | ||
| bool lossless; \ | ||
| int64_t v = value.As<v8::BigInt>()->Int64Value(&lossless); \ | ||
| if (lossless) { \ | ||
| return sqlite3_##to##_int64(__VA_ARGS__, v); \ | ||
| } \ | ||
| } else if (value->IsString()) { \ | ||
| v8::String::Utf8Value utf8(isolate, value.As<v8::String>()); \ | ||
| return sqlite3_##to##_text( \ | ||
| __VA_ARGS__, \ | ||
| *utf8, \ | ||
| utf8.length(), \ | ||
| SQLITE_TRANSIENT \ | ||
| ); \ | ||
| } else if (node::Buffer::HasInstance(value)) { \ | ||
| const char* data = node::Buffer::Data(value); \ | ||
| return sqlite3_##to##_blob( \ | ||
| __VA_ARGS__, \ | ||
| data ? data : "", \ | ||
| node::Buffer::Length(value), \ | ||
| SQLITE_TRANSIENT \ | ||
| ); \ | ||
| } else if (value->IsNull() || value->IsUndefined()) { \ | ||
| return sqlite3_##to##_null(__VA_ARGS__); \ | ||
| } | ||
| #define SQLITE_VALUE_TO_JS(from, isolate, safe_ints, ...) \ | ||
| switch (sqlite3_##from##_type(__VA_ARGS__)) { \ | ||
| case SQLITE_INTEGER: \ | ||
| if (safe_ints) { \ | ||
| return v8::BigInt::New( \ | ||
| isolate, \ | ||
| sqlite3_##from##_int64(__VA_ARGS__) \ | ||
| ); \ | ||
| } \ | ||
| case SQLITE_FLOAT: \ | ||
| return v8::Number::New( \ | ||
| isolate, \ | ||
| sqlite3_##from##_double(__VA_ARGS__) \ | ||
| ); \ | ||
| case SQLITE_TEXT: \ | ||
| return StringFromUtf8( \ | ||
| isolate, \ | ||
| reinterpret_cast<const char*>(sqlite3_##from##_text(__VA_ARGS__)), \ | ||
| sqlite3_##from##_bytes(__VA_ARGS__) \ | ||
| ); \ | ||
| case SQLITE_BLOB: \ | ||
| return node::Buffer::Copy( \ | ||
| isolate, \ | ||
| static_cast<const char*>(sqlite3_##from##_blob(__VA_ARGS__)), \ | ||
| sqlite3_##from##_bytes(__VA_ARGS__) \ | ||
| ).ToLocalChecked(); \ | ||
| default: \ | ||
| assert(sqlite3_##from##_type(__VA_ARGS__) == SQLITE_NULL); \ | ||
| return v8::Null(isolate); \ | ||
| } \ | ||
| assert(false); | ||
| namespace Data { | ||
| static const char FLAT = 0; | ||
| static const char PLUCK = 1; | ||
| static const char EXPAND = 2; | ||
| static const char RAW = 3; | ||
| v8::Local<v8::Value> GetValueJS(v8::Isolate* isolate, sqlite3_stmt* handle, int column, bool safe_ints) { | ||
| SQLITE_VALUE_TO_JS(column, isolate, safe_ints, handle, column); | ||
| } | ||
| v8::Local<v8::Value> GetValueJS(v8::Isolate* isolate, sqlite3_value* value, bool safe_ints) { | ||
| SQLITE_VALUE_TO_JS(value, isolate, safe_ints, value); | ||
| } | ||
| v8::Local<v8::Value> GetExpandedRowJS(v8::Isolate* isolate, v8::Local<v8::Context> ctx, sqlite3_stmt* handle, bool safe_ints) { | ||
| v8::Local<v8::Object> row = v8::Object::New(isolate); | ||
| int column_count = sqlite3_column_count(handle); | ||
| for (int i = 0; i < column_count; ++i) { | ||
| const char* table_raw = sqlite3_column_table_name(handle, i); | ||
| v8::Local<v8::String> table = InternalizedFromUtf8(isolate, table_raw == NULL ? "$" : table_raw, -1); | ||
| v8::Local<v8::String> column = InternalizedFromUtf8(isolate, sqlite3_column_name(handle, i), -1); | ||
| v8::Local<v8::Value> value = Data::GetValueJS(isolate, handle, i, safe_ints); | ||
| if (row->HasOwnProperty(ctx, table).FromJust()) { | ||
| row->Get(ctx, table).ToLocalChecked().As<v8::Object>()->Set(ctx, column, value).FromJust(); | ||
| } else { | ||
| v8::Local<v8::Object> nested = v8::Object::New(isolate); | ||
| row->Set(ctx, table, nested).FromJust(); | ||
| nested->Set(ctx, column, value).FromJust(); | ||
| } | ||
| } | ||
| return row; | ||
| } | ||
| #if !defined(NODE_MODULE_VERSION) || NODE_MODULE_VERSION < 127 | ||
| v8::Local<v8::Value> GetFlatRowJS(v8::Isolate* isolate, v8::Local<v8::Context> ctx, sqlite3_stmt* handle, bool safe_ints) { | ||
| v8::Local<v8::Object> row = v8::Object::New(isolate); | ||
| int column_count = sqlite3_column_count(handle); | ||
| for (int i = 0; i < column_count; ++i) { | ||
| row->Set(ctx, | ||
| InternalizedFromUtf8(isolate, sqlite3_column_name(handle, i), -1), | ||
| Data::GetValueJS(isolate, handle, i, safe_ints) | ||
| ).FromJust(); | ||
| } | ||
| return row; | ||
| } | ||
| v8::Local<v8::Value> GetRawRowJS(v8::Isolate* isolate, v8::Local<v8::Context> ctx, sqlite3_stmt* handle, bool safe_ints) { | ||
| v8::Local<v8::Array> row = v8::Array::New(isolate); | ||
| int column_count = sqlite3_column_count(handle); | ||
| for (int i = 0; i < column_count; ++i) { | ||
| row->Set(ctx, i, Data::GetValueJS(isolate, handle, i, safe_ints)).FromJust(); | ||
| } | ||
| return row; | ||
| } | ||
| v8::Local<v8::Value> GetRowJS(v8::Isolate* isolate, v8::Local<v8::Context> ctx, sqlite3_stmt* handle, bool safe_ints, char mode) { | ||
| if (mode == FLAT) return GetFlatRowJS(isolate, ctx, handle, safe_ints); | ||
| if (mode == PLUCK) return GetValueJS(isolate, handle, 0, safe_ints); | ||
| if (mode == EXPAND) return GetExpandedRowJS(isolate, ctx, handle, safe_ints); | ||
| if (mode == RAW) return GetRawRowJS(isolate, ctx, handle, safe_ints); | ||
| assert(false); | ||
| return v8::Local<v8::Value>(); | ||
| } | ||
| #else | ||
| v8::Local<v8::Value> GetFlatRowJS(v8::Isolate* isolate, sqlite3_stmt* handle, bool safe_ints) { | ||
| int column_count = sqlite3_column_count(handle); | ||
| v8::LocalVector<v8::Name> keys(isolate); | ||
| v8::LocalVector<v8::Value> values(isolate); | ||
| keys.reserve(column_count); | ||
| values.reserve(column_count); | ||
| for (int i = 0; i < column_count; ++i) { | ||
| keys.emplace_back( | ||
| InternalizedFromUtf8(isolate, sqlite3_column_name(handle, i), -1).As<v8::Name>() | ||
| ); | ||
| values.emplace_back( | ||
| Data::GetValueJS(isolate, handle, i, safe_ints) | ||
| ); | ||
| } | ||
| return v8::Object::New( | ||
| isolate, | ||
| v8::Object::New(isolate)->GetPrototype(), | ||
| keys.data(), | ||
| values.data(), | ||
| column_count | ||
| ); | ||
| } | ||
| v8::Local<v8::Value> GetRawRowJS(v8::Isolate* isolate, sqlite3_stmt* handle, bool safe_ints) { | ||
| int column_count = sqlite3_column_count(handle); | ||
| v8::LocalVector<v8::Value> row(isolate); | ||
| row.reserve(column_count); | ||
| for (int i = 0; i < column_count; ++i) { | ||
| row.emplace_back(Data::GetValueJS(isolate, handle, i, safe_ints)); | ||
| } | ||
| return v8::Array::New(isolate, row.data(), row.size()); | ||
| } | ||
| v8::Local<v8::Value> GetRowJS(v8::Isolate* isolate, v8::Local<v8::Context> ctx, sqlite3_stmt* handle, bool safe_ints, char mode) { | ||
| if (mode == FLAT) return GetFlatRowJS(isolate, handle, safe_ints); | ||
| if (mode == PLUCK) return GetValueJS(isolate, handle, 0, safe_ints); | ||
| if (mode == EXPAND) return GetExpandedRowJS(isolate, ctx, handle, safe_ints); | ||
| if (mode == RAW) return GetRawRowJS(isolate, handle, safe_ints); | ||
| assert(false); | ||
| return v8::Local<v8::Value>(); | ||
| } | ||
| #endif | ||
| void GetArgumentsJS(v8::Isolate* isolate, v8::Local<v8::Value>* out, sqlite3_value** values, int argument_count, bool safe_ints) { | ||
| assert(argument_count > 0); | ||
| for (int i = 0; i < argument_count; ++i) { | ||
| out[i] = Data::GetValueJS(isolate, values[i], safe_ints); | ||
| } | ||
| } | ||
| int BindValueFromJS(v8::Isolate* isolate, sqlite3_stmt* handle, int index, v8::Local<v8::Value> value) { | ||
| JS_VALUE_TO_SQLITE(bind, value, isolate, handle, index); | ||
| return value->IsBigInt() ? SQLITE_TOOBIG : -1; | ||
| } | ||
| void ResultValueFromJS(v8::Isolate* isolate, sqlite3_context* invocation, v8::Local<v8::Value> value, DataConverter* converter) { | ||
| JS_VALUE_TO_SQLITE(result, value, isolate, invocation); | ||
| converter->ThrowDataConversionError(invocation, value->IsBigInt()); | ||
| } | ||
| } |
| inline v8::Local<v8::String> StringFromUtf8(v8::Isolate* isolate, const char* data, int length) { | ||
| return v8::String::NewFromUtf8(isolate, data, v8::NewStringType::kNormal, length).ToLocalChecked(); | ||
| } | ||
| inline v8::Local<v8::String> InternalizedFromUtf8(v8::Isolate* isolate, const char* data, int length) { | ||
| return v8::String::NewFromUtf8(isolate, data, v8::NewStringType::kInternalized, length).ToLocalChecked(); | ||
| } | ||
| inline v8::Local<v8::Value> InternalizedFromUtf8OrNull(v8::Isolate* isolate, const char* data, int length) { | ||
| if (data == NULL) return v8::Null(isolate); | ||
| return InternalizedFromUtf8(isolate, data, length); | ||
| } | ||
| inline v8::Local<v8::String> InternalizedFromLatin1(v8::Isolate* isolate, const char* str) { | ||
| return v8::String::NewFromOneByte(isolate, reinterpret_cast<const uint8_t*>(str), v8::NewStringType::kInternalized).ToLocalChecked(); | ||
| } | ||
| inline void SetFrozen(v8::Isolate* isolate, v8::Local<v8::Context> ctx, v8::Local<v8::Object> obj, v8::Global<v8::String>& key, v8::Local<v8::Value> value) { | ||
| obj->DefineOwnProperty(ctx, key.Get(isolate), value, static_cast<v8::PropertyAttribute>(v8::DontDelete | v8::ReadOnly)).FromJust(); | ||
| } | ||
| void ThrowError(const char* message) { EasyIsolate; isolate->ThrowException(v8::Exception::Error(StringFromUtf8(isolate, message, -1))); } | ||
| void ThrowTypeError(const char* message) { EasyIsolate; isolate->ThrowException(v8::Exception::TypeError(StringFromUtf8(isolate, message, -1))); } | ||
| void ThrowRangeError(const char* message) { EasyIsolate; isolate->ThrowException(v8::Exception::RangeError(StringFromUtf8(isolate, message, -1))); } | ||
| // Determines whether to skip the given character at the start of an SQL string. | ||
| inline bool IS_SKIPPED(char c) { | ||
| return c == ' ' || c == ';' || (c >= '\t' && c <= '\r'); | ||
| } | ||
| // Allocates an empty array, without calling constructors/initializers. | ||
| template<class T> inline T* ALLOC_ARRAY(size_t count) { | ||
| return static_cast<T*>(::operator new[](count * sizeof(T))); | ||
| } | ||
| // Deallocates an array, without calling destructors. | ||
| template<class T> inline void FREE_ARRAY(T* array_pointer) { | ||
| ::operator delete[](array_pointer); | ||
| } | ||
| v8::Local<v8::FunctionTemplate> NewConstructorTemplate( | ||
| v8::Isolate* isolate, | ||
| v8::Local<v8::External> data, | ||
| v8::FunctionCallback func, | ||
| const char* name | ||
| ) { | ||
| v8::Local<v8::FunctionTemplate> t = v8::FunctionTemplate::New(isolate, func, data); | ||
| t->InstanceTemplate()->SetInternalFieldCount(1); | ||
| t->SetClassName(InternalizedFromLatin1(isolate, name)); | ||
| return t; | ||
| } | ||
| void SetPrototypeMethod( | ||
| v8::Isolate* isolate, | ||
| v8::Local<v8::External> data, | ||
| v8::Local<v8::FunctionTemplate> recv, | ||
| const char* name, | ||
| v8::FunctionCallback func | ||
| ) { | ||
| v8::HandleScope scope(isolate); | ||
| recv->PrototypeTemplate()->Set( | ||
| InternalizedFromLatin1(isolate, name), | ||
| v8::FunctionTemplate::New(isolate, func, data, v8::Signature::New(isolate, recv)) | ||
| ); | ||
| } | ||
| void SetPrototypeSymbolMethod( | ||
| v8::Isolate* isolate, | ||
| v8::Local<v8::External> data, | ||
| v8::Local<v8::FunctionTemplate> recv, | ||
| v8::Local<v8::Symbol> symbol, | ||
| v8::FunctionCallback func | ||
| ) { | ||
| v8::HandleScope scope(isolate); | ||
| recv->PrototypeTemplate()->Set( | ||
| symbol, | ||
| v8::FunctionTemplate::New(isolate, func, data, v8::Signature::New(isolate, recv)) | ||
| ); | ||
| } | ||
| void SetPrototypeGetter( | ||
| v8::Isolate* isolate, | ||
| v8::Local<v8::External> data, | ||
| v8::Local<v8::FunctionTemplate> recv, | ||
| const char* name, | ||
| v8::AccessorNameGetterCallback func | ||
| ) { | ||
| v8::HandleScope scope(isolate); | ||
| recv->InstanceTemplate()->SetNativeDataProperty( | ||
| InternalizedFromLatin1(isolate, name), | ||
| func, | ||
| 0, | ||
| data | ||
| ); | ||
| } | ||
| #if defined(V8_ENABLE_SANDBOX) | ||
| // When V8 Sandbox is enabled (in newer Electron versions), we need to use Buffer::Copy | ||
| // instead of Buffer::New to ensure the ArrayBuffer backing store is allocated inside the sandbox | ||
| static inline v8::MaybeLocal<v8::Object> BufferSandboxNew(v8::Isolate* isolate, char* data, size_t length, void (*finalizeCallback)(char*, void*), void* finalizeHint) { | ||
| v8::MaybeLocal<v8::Object> buffer = node::Buffer::Copy(isolate, data, length); | ||
| finalizeCallback(data, finalizeHint); | ||
| return buffer; | ||
| } | ||
| #define SAFE_NEW_BUFFER(env, data, length, finalizeCallback, finalizeHint) BufferSandboxNew(env, data, length, finalizeCallback, finalizeHint) | ||
| #else | ||
| // When V8 Sandbox is not enabled, we can use the more efficient Buffer::New | ||
| #define SAFE_NEW_BUFFER(env, data, length, finalizeCallback, finalizeHint) node::Buffer::New(env, data, length, finalizeCallback, finalizeHint) | ||
| #endif |
| #define NODE_ARGUMENTS const v8::FunctionCallbackInfo<v8::Value>& | ||
| #define NODE_ARGUMENTS_POINTER const v8::FunctionCallbackInfo<v8::Value>* | ||
| #define NODE_METHOD(name) void name(NODE_ARGUMENTS info) | ||
| #define NODE_GETTER(name) void name(v8::Local<v8::Name> _, const v8::PropertyCallbackInfo<v8::Value>& info) | ||
| #define INIT(name) v8::Local<v8::Function> name(v8::Isolate* isolate, v8::Local<v8::External> data) | ||
| #define EasyIsolate v8::Isolate* isolate = v8::Isolate::GetCurrent() | ||
| #define OnlyIsolate info.GetIsolate() | ||
| #define OnlyContext isolate->GetCurrentContext() | ||
| #define OnlyAddon static_cast<Addon*>(info.Data().As<v8::External>()->Value()) | ||
| #define UseIsolate v8::Isolate* isolate = OnlyIsolate | ||
| #define UseContext v8::Local<v8::Context> ctx = OnlyContext | ||
| #define UseAddon Addon* addon = OnlyAddon | ||
| #define Unwrap node::ObjectWrap::Unwrap | ||
| #define REQUIRE_ARGUMENT_ANY(at, var) \ | ||
| if (info.Length() <= (at())) \ | ||
| return ThrowTypeError("Expected a "#at" argument"); \ | ||
| var = info[at()] | ||
| #define _REQUIRE_ARGUMENT(at, var, Type, message, ...) \ | ||
| if (info.Length() <= (at()) || !info[at()]->Is##Type()) \ | ||
| return ThrowTypeError("Expected "#at" argument to be "#message); \ | ||
| var = (info[at()].As<v8::Type>())__VA_ARGS__ | ||
| #define REQUIRE_ARGUMENT_INT32(at, var) \ | ||
| _REQUIRE_ARGUMENT(at, var, Int32, a 32-bit signed integer, ->Value()) | ||
| #define REQUIRE_ARGUMENT_BOOLEAN(at, var) \ | ||
| _REQUIRE_ARGUMENT(at, var, Boolean, a boolean, ->Value()) | ||
| #define REQUIRE_ARGUMENT_STRING(at, var) \ | ||
| _REQUIRE_ARGUMENT(at, var, String, a string) | ||
| #define REQUIRE_ARGUMENT_OBJECT(at, var) \ | ||
| _REQUIRE_ARGUMENT(at, var, Object, an object) | ||
| #define REQUIRE_ARGUMENT_FUNCTION(at, var) \ | ||
| _REQUIRE_ARGUMENT(at, var, Function, a function) | ||
| #define REQUIRE_DATABASE_OPEN(db) \ | ||
| if (!db->open) \ | ||
| return ThrowTypeError("The database connection is not open") | ||
| #define REQUIRE_DATABASE_NOT_BUSY(db) \ | ||
| if (db->busy) \ | ||
| return ThrowTypeError("This database connection is busy executing a query") | ||
| #define REQUIRE_DATABASE_NO_ITERATORS(db) \ | ||
| if (db->iterators) \ | ||
| return ThrowTypeError("This database connection is busy executing a query") | ||
| #define REQUIRE_DATABASE_NO_ITERATORS_UNLESS_UNSAFE(db) \ | ||
| if (!db->unsafe_mode) { \ | ||
| REQUIRE_DATABASE_NO_ITERATORS(db); \ | ||
| } ((void)0) | ||
| #define REQUIRE_STATEMENT_NOT_LOCKED(stmt) \ | ||
| if (stmt->locked) \ | ||
| return ThrowTypeError("This statement is busy executing a query") | ||
| #define first() 0 | ||
| #define second() 1 | ||
| #define third() 2 | ||
| #define fourth() 3 | ||
| #define fifth() 4 | ||
| #define sixth() 5 | ||
| #define seventh() 6 | ||
| #define eighth() 7 | ||
| #define ninth() 8 | ||
| #define tenth() 9 |
| #define STATEMENT_BIND(handle) \ | ||
| Binder binder(handle); \ | ||
| if (!binder.Bind(info, info.Length(), stmt)) { \ | ||
| sqlite3_clear_bindings(handle); \ | ||
| return; \ | ||
| } ((void)0) | ||
| #define STATEMENT_THROW_LOGIC() \ | ||
| db->ThrowDatabaseError(); \ | ||
| if (!bound) { sqlite3_clear_bindings(handle); } \ | ||
| return | ||
| #define STATEMENT_RETURN_LOGIC(return_value) \ | ||
| info.GetReturnValue().Set(return_value); \ | ||
| if (!bound) { sqlite3_clear_bindings(handle); } \ | ||
| return | ||
| #define STATEMENT_START_LOGIC(RETURNS_DATA_CHECK, MUTATE_CHECK) \ | ||
| Statement* stmt = Unwrap<Statement>(info.This()); \ | ||
| RETURNS_DATA_CHECK(); \ | ||
| sqlite3_stmt* handle = stmt->handle; \ | ||
| Database* db = stmt->db; \ | ||
| REQUIRE_DATABASE_OPEN(db->GetState()); \ | ||
| REQUIRE_DATABASE_NOT_BUSY(db->GetState()); \ | ||
| MUTATE_CHECK(); \ | ||
| const bool bound = stmt->bound; \ | ||
| if (!bound) { \ | ||
| STATEMENT_BIND(handle); \ | ||
| } else if (info.Length() > 0) { \ | ||
| return ThrowTypeError("This statement already has bound parameters"); \ | ||
| } ((void)0) | ||
| #define STATEMENT_THROW() db->GetState()->busy = false; STATEMENT_THROW_LOGIC() | ||
| #define STATEMENT_RETURN(x) db->GetState()->busy = false; STATEMENT_RETURN_LOGIC(x) | ||
| #define STATEMENT_START(x, y) \ | ||
| STATEMENT_START_LOGIC(x, y); \ | ||
| db->GetState()->busy = true; \ | ||
| UseIsolate; \ | ||
| if (db->Log(isolate, handle)) { \ | ||
| STATEMENT_THROW(); \ | ||
| } ((void)0) | ||
| #define DOES_NOT_MUTATE() REQUIRE_STATEMENT_NOT_LOCKED(stmt) | ||
| #define DOES_MUTATE() \ | ||
| REQUIRE_STATEMENT_NOT_LOCKED(stmt); \ | ||
| REQUIRE_DATABASE_NO_ITERATORS_UNLESS_UNSAFE(db->GetState()) | ||
| #define DOES_ADD_ITERATOR() \ | ||
| DOES_NOT_MUTATE(); \ | ||
| if (db->GetState()->iterators == USHRT_MAX) \ | ||
| return ThrowRangeError("Too many active database iterators") | ||
| #define REQUIRE_STATEMENT_RETURNS_DATA() \ | ||
| if (!stmt->returns_data) \ | ||
| return ThrowTypeError("This statement does not return data. Use run() instead") | ||
| #define ALLOW_ANY_STATEMENT() \ | ||
| ((void)0) | ||
| #define _FUNCTION_START(type) \ | ||
| type* self = static_cast<type*>(sqlite3_user_data(invocation)); \ | ||
| v8::Isolate* isolate = self->isolate; \ | ||
| v8::HandleScope scope(isolate) | ||
| #define FUNCTION_START() \ | ||
| _FUNCTION_START(CustomFunction) | ||
| #define AGGREGATE_START() \ | ||
| _FUNCTION_START(CustomAggregate); \ | ||
| Accumulator* acc = self->GetAccumulator(invocation); \ | ||
| if (acc->value.IsEmpty()) return |
| class RowBuilder { | ||
| public: | ||
| explicit RowBuilder( | ||
| v8::Isolate* isolate, | ||
| sqlite3_stmt* handle, | ||
| bool safe_ints | ||
| ) : | ||
| isolate(isolate), | ||
| handle(handle), | ||
| column_count(-1), | ||
| safe_ints(safe_ints), | ||
| keys(isolate) {} | ||
| v8::Local<v8::Value> GetRowJS() { | ||
| if (column_count < 0) { | ||
| column_count = sqlite3_column_count(handle); | ||
| keys.reserve(column_count); | ||
| for (int i = 0; i < column_count; ++i) { | ||
| keys.emplace_back( | ||
| InternalizedFromUtf8(isolate, sqlite3_column_name(handle, i), -1) | ||
| .As<v8::Name>() | ||
| ); | ||
| } | ||
| } | ||
| v8::LocalVector<v8::Value> values(isolate); | ||
| values.reserve(column_count); | ||
| for (int i = 0; i < column_count; ++i) { | ||
| values.emplace_back( | ||
| Data::GetValueJS(isolate, handle, i, safe_ints) | ||
| ); | ||
| } | ||
| return v8::Object::New(isolate, | ||
| v8::Object::New(isolate)->GetPrototype(), | ||
| keys.data(), | ||
| values.data(), | ||
| column_count | ||
| ); | ||
| } | ||
| private: | ||
| v8::Isolate* isolate; | ||
| sqlite3_stmt* handle; | ||
| int column_count; | ||
| const bool safe_ints; | ||
| v8::LocalVector<v8::Name> keys; | ||
| }; |
+1
-1
@@ -22,3 +22,3 @@ #!/usr/bin/env bash | ||
| YEAR="2025" | ||
| VERSION="3500200" | ||
| VERSION="3500400" | ||
@@ -25,0 +25,0 @@ # Defines below are sorted alphabetically |
+2
-2
| { | ||
| "name": "better-sqlite3", | ||
| "version": "12.3.0", | ||
| "version": "12.4.1", | ||
| "description": "The fastest and simplest library for SQLite in Node.js.", | ||
@@ -14,3 +14,3 @@ "homepage": "http://github.com/WiseLibs/better-sqlite3", | ||
| "binding.gyp", | ||
| "src/*.[ch]pp", | ||
| "src/**/*.[ch]pp", | ||
| "lib/**", | ||
@@ -17,0 +17,0 @@ "deps/**" |
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is too big to display
Filesystem access
Supply chain riskAccesses the file system, and could potentially read sensitive data.
Found 1 instance in 1 package
Filesystem access
Supply chain riskAccesses the file system, and could potentially read sensitive data.
Found 1 instance in 1 package
AI-detected potential code anomaly
Supply chain riskAI has identified unusual behaviors that may pose a security risk.
Found 1 instance in 1 package
10133375
1.03%49
68.97%3
-25%