From 0da2122954b5df38dd5b8b1e5e38960ec41fb942 Mon Sep 17 00:00:00 2001 From: Oscar Franco Date: Sat, 25 Jan 2025 07:36:22 +0100 Subject: [PATCH] Add clang-format rules to not clash too much with xcode and android studio --- .clang-format | 2 + cpp/DBHostObject.cpp | 1199 ++++++++++++------------- cpp/DBHostObject.h | 107 +-- cpp/DumbHostObject.cpp | 69 +- cpp/DumbHostObject.h | 23 +- cpp/PreparedStatementHostObject.cpp | 198 +++-- cpp/PreparedStatementHostObject.h | 54 +- cpp/SmartHostObject.cpp | 26 +- cpp/SmartHostObject.h | 11 +- cpp/ThreadPool.cpp | 158 ++-- cpp/ThreadPool.h | 56 +- cpp/bindings.cpp | 220 ++--- cpp/bridge.cpp | 1252 ++++++++++++++------------- cpp/bridge.h | 6 +- cpp/libsql/bridge.cpp | 1128 ++++++++++++------------ cpp/libsql/bridge.h | 4 +- cpp/libsql/libsql.h | 110 ++- cpp/logs.h | 34 +- cpp/types.h | 24 +- cpp/utils.cpp | 13 +- 20 files changed, 2395 insertions(+), 2299 deletions(-) create mode 100644 .clang-format diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000..5bf2f83 --- /dev/null +++ b/.clang-format @@ -0,0 +1,2 @@ +BasedOnStyle: LLVM +IndentWidth: 4 \ No newline at end of file diff --git a/cpp/DBHostObject.cpp b/cpp/DBHostObject.cpp index 43cc075..77fe141 100644 --- a/cpp/DBHostObject.cpp +++ b/cpp/DBHostObject.cpp @@ -19,110 +19,117 @@ namespace react = facebook::react; #ifdef OP_SQLITE_USE_LIBSQL void DBHostObject::flush_pending_reactive_queries( const std::shared_ptr &resolve) { - invoker->invokeAsync( - [this, resolve]() { resolve->asObject(rt).asFunction(rt).call(rt, {}); }); + invoker->invokeAsync([this, resolve]() { + resolve->asObject(rt).asFunction(rt).call(rt, {}); + }); } #else void DBHostObject::flush_pending_reactive_queries( const std::shared_ptr &resolve) { - for (const auto &query_ptr : pending_reactive_queries) { - auto query = query_ptr.get(); + for (const auto &query_ptr : pending_reactive_queries) { + auto query = query_ptr.get(); - std::vector results; - std::shared_ptr> metadata = - std::make_shared>(); + std::vector results; + std::shared_ptr> metadata = + std::make_shared>(); - auto status = opsqlite_execute_prepared_statement(db, query->stmt, &results, - metadata); + auto status = opsqlite_execute_prepared_statement(db, query->stmt, + &results, metadata); - invoker->invokeAsync( - [this, results = std::make_shared>(results), - callback = query->callback, metadata, status = std::move(status)] { - auto jsiResult = create_result(rt, status, results.get(), metadata); - callback->asObject(rt).asFunction(rt).call(rt, jsiResult); - }); - } + invoker->invokeAsync( + [this, + results = std::make_shared>(results), + callback = query->callback, metadata, status = std::move(status)] { + auto jsiResult = + create_result(rt, status, results.get(), metadata); + callback->asObject(rt).asFunction(rt).call(rt, jsiResult); + }); + } - pending_reactive_queries.clear(); + pending_reactive_queries.clear(); - invoker->invokeAsync( - [this, resolve]() { resolve->asObject(rt).asFunction(rt).call(rt, {}); }); + invoker->invokeAsync([this, resolve]() { + resolve->asObject(rt).asFunction(rt).call(rt, {}); + }); } void DBHostObject::on_commit() { - invoker->invokeAsync( - [this] { commit_hook_callback->asObject(rt).asFunction(rt).call(rt); }); + invoker->invokeAsync( + [this] { commit_hook_callback->asObject(rt).asFunction(rt).call(rt); }); } void DBHostObject::on_rollback() { - invoker->invokeAsync( - [this] { rollback_hook_callback->asObject(rt).asFunction(rt).call(rt); }); + invoker->invokeAsync([this] { + rollback_hook_callback->asObject(rt).asFunction(rt).call(rt); + }); } void DBHostObject::on_update(const std::string &table, const std::string &operation, long long row_id) { - if (update_hook_callback != nullptr) { - invoker->invokeAsync( - [this, callback = update_hook_callback, table, operation, row_id] { - auto res = jsi::Object(rt); - res.setProperty(rt, "table", jsi::String::createFromUtf8(rt, table)); - res.setProperty(rt, "operation", - jsi::String::createFromUtf8(rt, operation)); - res.setProperty(rt, "rowId", jsi::Value(static_cast(row_id))); - - callback->asObject(rt).asFunction(rt).call(rt, res); - }); - } - - for (const auto &query_ptr : reactive_queries) { - auto query = query_ptr.get(); - if (query->discriminators.empty()) { - continue; + if (update_hook_callback != nullptr) { + invoker->invokeAsync( + [this, callback = update_hook_callback, table, operation, row_id] { + auto res = jsi::Object(rt); + res.setProperty(rt, "table", + jsi::String::createFromUtf8(rt, table)); + res.setProperty(rt, "operation", + jsi::String::createFromUtf8(rt, operation)); + res.setProperty(rt, "rowId", + jsi::Value(static_cast(row_id))); + + callback->asObject(rt).asFunction(rt).call(rt, res); + }); } - bool shouldFire = false; - - for (const auto &discriminator : query->discriminators) { - // Tables don't match then skip - if (discriminator.table != table) { - continue; - } - - // If no ids are specified, then we should fire - if (discriminator.ids.empty()) { - shouldFire = true; - break; - } - - // If ids are specified, then we should check if the rowId matches - for (const auto &discrimator_id : discriminator.ids) { - if (row_id == discrimator_id) { - shouldFire = true; - break; + for (const auto &query_ptr : reactive_queries) { + auto query = query_ptr.get(); + if (query->discriminators.empty()) { + continue; } - } - } - if (shouldFire) { - pending_reactive_queries.insert(query_ptr); + bool shouldFire = false; + + for (const auto &discriminator : query->discriminators) { + // Tables don't match then skip + if (discriminator.table != table) { + continue; + } + + // If no ids are specified, then we should fire + if (discriminator.ids.empty()) { + shouldFire = true; + break; + } + + // If ids are specified, then we should check if the rowId matches + for (const auto &discrimator_id : discriminator.ids) { + if (row_id == discrimator_id) { + shouldFire = true; + break; + } + } + } + + if (shouldFire) { + pending_reactive_queries.insert(query_ptr); + } } - } } void DBHostObject::auto_register_update_hook() { - if (update_hook_callback == nullptr && reactive_queries.empty() && - is_update_hook_registered) { - opsqlite_deregister_update_hook(db); - is_update_hook_registered = false; - return; - } - - if (is_update_hook_registered) { - return; - } - - opsqlite_register_update_hook(db, this); - is_update_hook_registered = true; + if (update_hook_callback == nullptr && reactive_queries.empty() && + is_update_hook_registered) { + opsqlite_deregister_update_hook(db); + is_update_hook_registered = false; + return; + } + + if (is_update_hook_registered) { + return; + } + + opsqlite_register_update_hook(db, this); + is_update_hook_registered = true; } #endif @@ -137,9 +144,9 @@ DBHostObject::DBHostObject(jsi::Runtime &rt, std::string &url, std::string &auth_token, std::shared_ptr invoker) : db_name(url), invoker(std::move(invoker)), rt(rt) { - db = opsqlite_libsql_open_remote(url, auth_token); + db = opsqlite_libsql_open_remote(url, auth_token); - create_jsi_functions(); + create_jsi_functions(); } DBHostObject::DBHostObject(jsi::Runtime &rt, @@ -148,9 +155,10 @@ DBHostObject::DBHostObject(jsi::Runtime &rt, std::string &url, std::string &auth_token, int sync_interval) : db_name(db_name), invoker(std::move(invoker)), rt(rt) { - db = opsqlite_libsql_open_sync(db_name, path, url, auth_token, sync_interval); + db = opsqlite_libsql_open_sync(db_name, path, url, auth_token, + sync_interval); - create_jsi_functions(); + create_jsi_functions(); } #endif @@ -163,657 +171,688 @@ DBHostObject::DBHostObject(jsi::Runtime &rt, std::string &base_path, std::string &encryption_key) : base_path(base_path), invoker(std::move(invoker)), db_name(db_name), rt(rt) { - _thread_pool = std::make_shared(); + _thread_pool = std::make_shared(); #ifdef OP_SQLITE_USE_SQLCIPHER - db = opsqlite_open(db_name, path, crsqlite_path, sqlite_vec_path, - encryption_key); + db = opsqlite_open(db_name, path, crsqlite_path, sqlite_vec_path, + encryption_key); #elif OP_SQLITE_USE_LIBSQL - db = opsqlite_libsql_open(db_name, path, crsqlite_path); + db = opsqlite_libsql_open(db_name, path, crsqlite_path); #else - db = opsqlite_open(db_name, path, crsqlite_path, sqlite_vec_path); + db = opsqlite_open(db_name, path, crsqlite_path, sqlite_vec_path); #endif - create_jsi_functions(); + create_jsi_functions(); }; void DBHostObject::create_jsi_functions() { - function_map["attach"] = HOSTFN("attach") { - if (count < 3) { - throw std::runtime_error( - "[op-sqlite][attach] Incorrect number of arguments"); - } - if (!args[0].isString() || !args[1].isString() || !args[2].isString()) { - throw std::runtime_error("[op-sqlite] name, database to attach and " - "alias must be strings"); - } + function_map["attach"] = HOSTFN("attach") { + if (count < 3) { + throw std::runtime_error( + "[op-sqlite][attach] Incorrect number of arguments"); + } + if (!args[0].isString() || !args[1].isString() || !args[2].isString()) { + throw std::runtime_error("[op-sqlite] name, database to attach and " + "alias must be strings"); + } - std::string secondary_db_path = std::string(base_path); - if (count > 3) { - if (!args[3].isString()) { - throw std::runtime_error( - "[op-sqlite][attach] database location must be a string"); - } + std::string secondary_db_path = std::string(base_path); + if (count > 3) { + if (!args[3].isString()) { + throw std::runtime_error( + "[op-sqlite][attach] database location must be a string"); + } - secondary_db_path += "/" + args[3].asString(rt).utf8(rt); - } + secondary_db_path += "/" + args[3].asString(rt).utf8(rt); + } - std::string main_db_name = args[0].asString(rt).utf8(rt); - std::string secondary_db_name = args[1].asString(rt).utf8(rt); - std::string alias = args[2].asString(rt).utf8(rt); + std::string main_db_name = args[0].asString(rt).utf8(rt); + std::string secondary_db_name = args[1].asString(rt).utf8(rt); + std::string alias = args[2].asString(rt).utf8(rt); #ifdef OP_SQLITE_USE_LIBSQL - opsqlite_libsql_attach(db, secondary_db_path, secondary_db_name, alias); + opsqlite_libsql_attach(db, secondary_db_path, secondary_db_name, alias); #else - opsqlite_attach(db, main_db_name, secondary_db_path, secondary_db_name, - alias); + opsqlite_attach(db, main_db_name, secondary_db_path, secondary_db_name, + alias); #endif - return {}; - }); + return {}; + }); - function_map["detach"] = HOSTFN("detach") { - if (count < 2) { - throw std::runtime_error( - "[op-sqlite][detach] Incorrect number of arguments"); - } - if (!args[0].isString() || !args[1].isString()) { - throw std::runtime_error( - "[op-sqlite] database name and alias must be a strings"); - } + function_map["detach"] = HOSTFN("detach") { + if (count < 2) { + throw std::runtime_error( + "[op-sqlite][detach] Incorrect number of arguments"); + } + if (!args[0].isString() || !args[1].isString()) { + throw std::runtime_error( + "[op-sqlite] database name and alias must be a strings"); + } - std::string dbName = args[0].asString(rt).utf8(rt); - std::string alias = args[1].asString(rt).utf8(rt); + std::string dbName = args[0].asString(rt).utf8(rt); + std::string alias = args[1].asString(rt).utf8(rt); #ifdef OP_SQLITE_USE_LIBSQL - opsqlite_libsql_detach(db, alias); + opsqlite_libsql_detach(db, alias); #else - opsqlite_detach(db, dbName, alias); + opsqlite_detach(db, dbName, alias); #endif - return {}; - }); + return {}; + }); - function_map["close"] = HOSTFN("close") { - invalidated = true; + function_map["close"] = HOSTFN("close") { + invalidated = true; #ifdef OP_SQLITE_USE_LIBSQL - opsqlite_libsql_close(db); + opsqlite_libsql_close(db); #else - opsqlite_close(db); + opsqlite_close(db); #endif - return {}; - }); + return {}; + }); - function_map["delete"] = HOSTFN("delete") { - invalidated = true; + function_map["delete"] = HOSTFN("delete") { + invalidated = true; - std::string path = std::string(base_path); + std::string path = std::string(base_path); - if (count == 1) { - if (!args[1].isString()) { - throw std::runtime_error( - "[op-sqlite][open] database location must be a string"); - } + if (count == 1) { + if (!args[1].isString()) { + throw std::runtime_error( + "[op-sqlite][open] database location must be a string"); + } - std::string location = args[1].asString(rt).utf8(rt); + std::string location = args[1].asString(rt).utf8(rt); - if (!location.empty()) { - if (location == ":memory:") { - path = ":memory:"; - } else if (location.rfind('/', 0) == 0) { - path = location; - } else { - path = path + "/" + location; + if (!location.empty()) { + if (location == ":memory:") { + path = ":memory:"; + } else if (location.rfind('/', 0) == 0) { + path = location; + } else { + path = path + "/" + location; + } + } } - } - } #ifdef OP_SQLITE_USE_LIBSQL - opsqlite_libsql_remove(db, db_name, path); + opsqlite_libsql_remove(db, db_name, path); #else - opsqlite_remove(db, db_name, path); + opsqlite_remove(db, db_name, path); #endif - return {}; - }); + return {}; + }); - function_map["executeRaw"] = HOSTFN("executeRaw") { - const std::string query = args[0].asString(rt).utf8(rt); - std::vector params = count == 2 && args[1].isObject() - ? to_variant_vec(rt, args[1]) - : std::vector(); + function_map["executeRaw"] = HOSTFN("executeRaw") { + const std::string query = args[0].asString(rt).utf8(rt); + std::vector params = count == 2 && args[1].isObject() + ? to_variant_vec(rt, args[1]) + : std::vector(); - auto promiseCtr = rt.global().getPropertyAsFunction(rt, "Promise"); + auto promiseCtr = rt.global().getPropertyAsFunction(rt, "Promise"); auto promise = promiseCtr.callAsConstructor(rt, HOSTFN("executor") { - auto resolve = std::make_shared(rt, args[0]); - auto reject = std::make_shared(rt, args[1]); + auto resolve = std::make_shared(rt, args[0]); + auto reject = std::make_shared(rt, args[1]); - auto task = [this, &rt, query, params, resolve, reject]() { - try { - std::vector> results; + auto task = [this, &rt, query, params, resolve, reject]() { + try { + std::vector> results; #ifdef OP_SQLITE_USE_LIBSQL - auto status = - opsqlite_libsql_execute_raw(db, query, ¶ms, &results); + auto status = opsqlite_libsql_execute_raw( + db, query, ¶ms, &results); #else - auto status = opsqlite_execute_raw(db, query, ¶ms, &results); + auto status = + opsqlite_execute_raw(db, query, ¶ms, &results); #endif - if (invalidated) { - return; - } - - invoker->invokeAsync([&rt, results = std::move(results), - status = std::move(status), resolve, reject] { - auto jsiResult = create_raw_result(rt, status, &results); - resolve->asObject(rt).asFunction(rt).call(rt, std::move(jsiResult)); - }); - } catch (std::runtime_error &e) { - auto what = e.what(); - invoker->invokeAsync([&rt, what, reject] { - auto errorCtr = rt.global().getPropertyAsFunction(rt, "Error"); - auto error = errorCtr.callAsConstructor( - rt, jsi::String::createFromAscii(rt, what)); - reject->asObject(rt).asFunction(rt).call(rt, error); - }); - } catch (std::exception &exc) { - auto what = exc.what(); - invoker->invokeAsync([&rt, what, reject] { - auto errorCtr = rt.global().getPropertyAsFunction(rt, "Error"); - auto error = errorCtr.callAsConstructor( - rt, jsi::String::createFromAscii(rt, what)); - reject->asObject(rt).asFunction(rt).call(rt, error); - }); - } - }; - - _thread_pool->queueWork(task); - - return {}; + if (invalidated) { + return; + } + + invoker->invokeAsync([&rt, results = std::move(results), + status = std::move(status), resolve, + reject] { + auto jsiResult = + create_raw_result(rt, status, &results); + resolve->asObject(rt).asFunction(rt).call( + rt, std::move(jsiResult)); + }); + } catch (std::runtime_error &e) { + auto what = e.what(); + invoker->invokeAsync([&rt, what, reject] { + auto errorCtr = + rt.global().getPropertyAsFunction(rt, "Error"); + auto error = errorCtr.callAsConstructor( + rt, jsi::String::createFromAscii(rt, what)); + reject->asObject(rt).asFunction(rt).call(rt, error); + }); + } catch (std::exception &exc) { + auto what = exc.what(); + invoker->invokeAsync([&rt, what, reject] { + auto errorCtr = + rt.global().getPropertyAsFunction(rt, "Error"); + auto error = errorCtr.callAsConstructor( + rt, jsi::String::createFromAscii(rt, what)); + reject->asObject(rt).asFunction(rt).call(rt, error); + }); + } + }; + + _thread_pool->queueWork(task); + + return {}; })); return promise; - }); + }); - function_map["executeSync"] = HOSTFN("executeSync") { - std::string query = args[0].asString(rt).utf8(rt); - std::vector params; + function_map["executeSync"] = HOSTFN("executeSync") { + std::string query = args[0].asString(rt).utf8(rt); + std::vector params; - if (count == 2) { - params = to_variant_vec(rt, args[1]); - } + if (count == 2) { + params = to_variant_vec(rt, args[1]); + } #ifdef OP_SQLITE_USE_LIBSQL - auto status = opsqlite_libsql_execute(db, query, ¶ms); + auto status = opsqlite_libsql_execute(db, query, ¶ms); #else - auto status = opsqlite_execute(db, query, ¶ms); + auto status = opsqlite_execute(db, query, ¶ms); #endif - return create_js_rows(rt, status); - }); + return create_js_rows(rt, status); + }); - function_map["execute"] = HOSTFN("execute") { - const std::string query = args[0].asString(rt).utf8(rt); - std::vector params = count == 2 && args[1].isObject() - ? to_variant_vec(rt, args[1]) - : std::vector(); + function_map["execute"] = HOSTFN("execute") { + const std::string query = args[0].asString(rt).utf8(rt); + std::vector params = count == 2 && args[1].isObject() + ? to_variant_vec(rt, args[1]) + : std::vector(); - auto promiseCtr = rt.global().getPropertyAsFunction(rt, "Promise"); + auto promiseCtr = rt.global().getPropertyAsFunction(rt, "Promise"); auto promise = promiseCtr.callAsConstructor(rt, HOSTFN("executor") { - auto task = [this, &rt, query, params, - resolve = std::make_shared(rt, args[0]), - reject = std::make_shared(rt, args[1])]() { - try { + auto task = [this, &rt, query, params, + resolve = std::make_shared(rt, args[0]), + reject = std::make_shared(rt, args[1])]() { + try { #ifdef OP_SQLITE_USE_LIBSQL - auto status = opsqlite_libsql_execute(db, query, ¶ms); + auto status = opsqlite_libsql_execute(db, query, ¶ms); #else - auto status = opsqlite_execute(db, query, ¶ms); + auto status = opsqlite_execute(db, query, ¶ms); #endif - if (invalidated) { - return; - } - - invoker->invokeAsync([&rt, status = std::move(status), resolve, - reject] { - auto jsiResult = create_js_rows(rt, status); - resolve->asObject(rt).asFunction(rt).call(rt, std::move(jsiResult)); - }); - // On Android RN is broken and does not correctly match - // runtime_error to the generic exception We have to - // explicitly catch it - // https://github.com/facebook/react-native/issues/48027 - } catch (std::runtime_error &e) { - auto what = e.what(); - invoker->invokeAsync([&rt, what = std::string(what), reject] { - auto errorCtr = rt.global().getPropertyAsFunction(rt, "Error"); - auto error = errorCtr.callAsConstructor( - rt, jsi::String::createFromAscii(rt, what)); - reject->asObject(rt).asFunction(rt).call(rt, error); - }); - } catch (std::exception &exc) { - auto what = exc.what(); - invoker->invokeAsync([&rt, what = std::string(what), reject] { - auto errorCtr = rt.global().getPropertyAsFunction(rt, "Error"); - auto error = errorCtr.callAsConstructor( - rt, jsi::String::createFromAscii(rt, what)); - reject->asObject(rt).asFunction(rt).call(rt, error); - }); - } - }; - - _thread_pool->queueWork(task); - - return {}; + if (invalidated) { + return; + } + + invoker->invokeAsync( + [&rt, status = std::move(status), resolve, reject] { + auto jsiResult = create_js_rows(rt, status); + resolve->asObject(rt).asFunction(rt).call( + rt, std::move(jsiResult)); + }); + // On Android RN is broken and does not correctly match + // runtime_error to the generic exception We have to + // explicitly catch it + // https://github.com/facebook/react-native/issues/48027 + } catch (std::runtime_error &e) { + auto what = e.what(); + invoker->invokeAsync( + [&rt, what = std::string(what), reject] { + auto errorCtr = + rt.global().getPropertyAsFunction(rt, "Error"); + auto error = errorCtr.callAsConstructor( + rt, jsi::String::createFromAscii(rt, what)); + reject->asObject(rt).asFunction(rt).call(rt, error); + }); + } catch (std::exception &exc) { + auto what = exc.what(); + invoker->invokeAsync( + [&rt, what = std::string(what), reject] { + auto errorCtr = + rt.global().getPropertyAsFunction(rt, "Error"); + auto error = errorCtr.callAsConstructor( + rt, jsi::String::createFromAscii(rt, what)); + reject->asObject(rt).asFunction(rt).call(rt, error); + }); + } + }; + + _thread_pool->queueWork(task); + + return {}; })); return promise; - }); + }); - function_map["executeWithHostObjects"] = HOSTFN("executeWithHostObjects") { - const std::string query = args[0].asString(rt).utf8(rt); - std::vector params; + function_map["executeWithHostObjects"] = HOSTFN("executeWithHostObjects") { + const std::string query = args[0].asString(rt).utf8(rt); + std::vector params; - if (count == 2) { - const jsi::Value &originalParams = args[1]; - params = to_variant_vec(rt, originalParams); - } + if (count == 2) { + const jsi::Value &originalParams = args[1]; + params = to_variant_vec(rt, originalParams); + } - auto promiseCtr = rt.global().getPropertyAsFunction(rt, "Promise"); + auto promiseCtr = rt.global().getPropertyAsFunction(rt, "Promise"); auto promise = promiseCtr.callAsConstructor(rt, HOSTFN("executor") { - auto resolve = std::make_shared(rt, args[0]); - auto reject = std::make_shared(rt, args[1]); - - auto task = [&rt, this, query, params, resolve, reject]() { - try { - std::vector results; - std::shared_ptr> metadata = - std::make_shared>(); + auto resolve = std::make_shared(rt, args[0]); + auto reject = std::make_shared(rt, args[1]); + + auto task = [&rt, this, query, params, resolve, reject]() { + try { + std::vector results; + std::shared_ptr> metadata = + std::make_shared>(); #ifdef OP_SQLITE_USE_LIBSQL - auto status = opsqlite_libsql_execute_with_host_objects( - db, query, ¶ms, &results, metadata); + auto status = opsqlite_libsql_execute_with_host_objects( + db, query, ¶ms, &results, metadata); #else - auto status = opsqlite_execute_host_objects(db, query, ¶ms, - &results, metadata); + auto status = opsqlite_execute_host_objects( + db, query, ¶ms, &results, metadata); #endif - if (invalidated) { - return; - } - - invoker->invokeAsync( - [&rt, - results = std::make_shared>(results), - metadata, status = std::move(status), resolve, reject] { - auto jsiResult = - create_result(rt, status, results.get(), metadata); - resolve->asObject(rt).asFunction(rt).call(rt, - std::move(jsiResult)); - }); - } catch (std::runtime_error &e) { - auto what = e.what(); - invoker->invokeAsync([&rt, what = std::string(what), reject] { - auto errorCtr = rt.global().getPropertyAsFunction(rt, "Error"); - auto error = errorCtr.callAsConstructor( - rt, jsi::String::createFromAscii(rt, what)); - reject->asObject(rt).asFunction(rt).call(rt, error); - }); - } catch (std::exception &exc) { - auto what = exc.what(); - invoker->invokeAsync([&rt, what, reject] { - auto errorCtr = rt.global().getPropertyAsFunction(rt, "Error"); - auto error = errorCtr.callAsConstructor( - rt, jsi::String::createFromAscii(rt, what)); - reject->asObject(rt).asFunction(rt).call(rt, error); - }); - } - }; - - _thread_pool->queueWork(task); - - return {}; + if (invalidated) { + return; + } + + invoker->invokeAsync( + [&rt, + results = + std::make_shared>( + results), + metadata, status = std::move(status), resolve, + reject] { + auto jsiResult = create_result( + rt, status, results.get(), metadata); + resolve->asObject(rt).asFunction(rt).call( + rt, std::move(jsiResult)); + }); + } catch (std::runtime_error &e) { + auto what = e.what(); + invoker->invokeAsync( + [&rt, what = std::string(what), reject] { + auto errorCtr = + rt.global().getPropertyAsFunction(rt, "Error"); + auto error = errorCtr.callAsConstructor( + rt, jsi::String::createFromAscii(rt, what)); + reject->asObject(rt).asFunction(rt).call(rt, error); + }); + } catch (std::exception &exc) { + auto what = exc.what(); + invoker->invokeAsync([&rt, what, reject] { + auto errorCtr = + rt.global().getPropertyAsFunction(rt, "Error"); + auto error = errorCtr.callAsConstructor( + rt, jsi::String::createFromAscii(rt, what)); + reject->asObject(rt).asFunction(rt).call(rt, error); + }); + } + }; + + _thread_pool->queueWork(task); + + return {}; })); return promise; - }); + }); - function_map["executeBatch"] = HOSTFN("executeBatch") { - if (count < 1) { - throw std::runtime_error( - "[op-sqlite][executeAsyncBatch] Incorrect parameter count"); - } + function_map["executeBatch"] = HOSTFN("executeBatch") { + if (count < 1) { + throw std::runtime_error( + "[op-sqlite][executeAsyncBatch] Incorrect parameter count"); + } - const jsi::Value ¶ms = args[0]; + const jsi::Value ¶ms = args[0]; - if (params.isNull() || params.isUndefined()) { - throw std::runtime_error( - "[op-sqlite][executeAsyncBatch] - An array of SQL " - "commands or parameters is needed"); - } + if (params.isNull() || params.isUndefined()) { + throw std::runtime_error( + "[op-sqlite][executeAsyncBatch] - An array of SQL " + "commands or parameters is needed"); + } - const jsi::Array &batchParams = params.asObject(rt).asArray(rt); + const jsi::Array &batchParams = params.asObject(rt).asArray(rt); - std::vector commands; - to_batch_arguments(rt, batchParams, &commands); + std::vector commands; + to_batch_arguments(rt, batchParams, &commands); - auto promiseCtr = rt.global().getPropertyAsFunction(rt, "Promise"); + auto promiseCtr = rt.global().getPropertyAsFunction(rt, "Promise"); auto promise = promiseCtr.callAsConstructor(rt, HOSTFN("executor") { - auto resolve = std::make_shared(rt, args[0]); - auto reject = std::make_shared(rt, args[1]); + auto resolve = std::make_shared(rt, args[0]); + auto reject = std::make_shared(rt, args[1]); - auto task = [this, &rt, commands, resolve, reject]() { - try { + auto task = [this, &rt, commands, resolve, reject]() { + try { #ifdef OP_SQLITE_USE_LIBSQL - auto batchResult = opsqlite_libsql_execute_batch(db, &commands); + auto batchResult = + opsqlite_libsql_execute_batch(db, &commands); #else - auto batchResult = opsqlite_execute_batch(db, &commands); + auto batchResult = opsqlite_execute_batch(db, &commands); #endif - if (invalidated) { - return; - } - - invoker->invokeAsync( - [&rt, batchResult = std::move(batchResult), resolve] { - auto res = jsi::Object(rt); - res.setProperty(rt, "rowsAffected", - jsi::Value(batchResult.affectedRows)); - resolve->asObject(rt).asFunction(rt).call(rt, std::move(res)); - }); - } catch (std::runtime_error &e) { - auto what = e.what(); - invoker->invokeAsync([&rt, what, reject] { - auto errorCtr = rt.global().getPropertyAsFunction(rt, "Error"); - auto error = errorCtr.callAsConstructor( - rt, jsi::String::createFromAscii(rt, what)); - reject->asObject(rt).asFunction(rt).call(rt, error); - }); - } catch (std::exception &exc) { - auto what = exc.what(); - invoker->invokeAsync([&rt, what, reject] { - auto errorCtr = rt.global().getPropertyAsFunction(rt, "Error"); - auto error = errorCtr.callAsConstructor( - rt, jsi::String::createFromAscii(rt, what)); - reject->asObject(rt).asFunction(rt).call(rt, error); - }); - } - }; - _thread_pool->queueWork(task); - - return {}; + if (invalidated) { + return; + } + + invoker->invokeAsync([&rt, + batchResult = std::move(batchResult), + resolve] { + auto res = jsi::Object(rt); + res.setProperty(rt, "rowsAffected", + jsi::Value(batchResult.affectedRows)); + resolve->asObject(rt).asFunction(rt).call( + rt, std::move(res)); + }); + } catch (std::runtime_error &e) { + auto what = e.what(); + invoker->invokeAsync([&rt, what, reject] { + auto errorCtr = + rt.global().getPropertyAsFunction(rt, "Error"); + auto error = errorCtr.callAsConstructor( + rt, jsi::String::createFromAscii(rt, what)); + reject->asObject(rt).asFunction(rt).call(rt, error); + }); + } catch (std::exception &exc) { + auto what = exc.what(); + invoker->invokeAsync([&rt, what, reject] { + auto errorCtr = + rt.global().getPropertyAsFunction(rt, "Error"); + auto error = errorCtr.callAsConstructor( + rt, jsi::String::createFromAscii(rt, what)); + reject->asObject(rt).asFunction(rt).call(rt, error); + }); + } + }; + _thread_pool->queueWork(task); + + return {}; })); return promise; - }); + }); #ifdef OP_SQLITE_USE_LIBSQL - function_map["sync"] = HOSTFN("sync") { - opsqlite_libsql_sync(db); - return {}; - }); + function_map["sync"] = HOSTFN("sync") { + opsqlite_libsql_sync(db); + return {}; + }); #else - function_map["loadFile"] = HOSTFN("loadFile") { - if (count < 1) { - throw std::runtime_error( - "[op-sqlite][loadFile] Incorrect parameter count"); - return {}; - } + function_map["loadFile"] = HOSTFN("loadFile") { + if (count < 1) { + throw std::runtime_error( + "[op-sqlite][loadFile] Incorrect parameter count"); + return {}; + } - const std::string sqlFileName = args[0].asString(rt).utf8(rt); + const std::string sqlFileName = args[0].asString(rt).utf8(rt); - auto promiseCtr = rt.global().getPropertyAsFunction(rt, "Promise"); + auto promiseCtr = rt.global().getPropertyAsFunction(rt, "Promise"); auto promise = promiseCtr.callAsConstructor(rt, HOSTFN("executor") { - auto resolve = std::make_shared(rt, args[0]); - auto reject = std::make_shared(rt, args[1]); - - auto task = [&rt, this, sqlFileName, resolve, reject]() { - try { - const auto result = import_sql_file(db, sqlFileName); - - invoker->invokeAsync([&rt, result, resolve] { - auto res = jsi::Object(rt); - res.setProperty(rt, "rowsAffected", - jsi::Value(result.affectedRows)); - res.setProperty(rt, "commands", jsi::Value(result.commands)); - resolve->asObject(rt).asFunction(rt).call(rt, std::move(res)); - }); - } catch (std::runtime_error &e) { - auto what = e.what(); - invoker->invokeAsync([&rt, what = std::string(what), reject] { - auto errorCtr = rt.global().getPropertyAsFunction(rt, "Error"); - auto error = errorCtr.callAsConstructor( - rt, jsi::String::createFromAscii(rt, what)); - reject->asObject(rt).asFunction(rt).call(rt, error); - }); - } catch (std::exception &exc) { - auto what = exc.what(); - invoker->invokeAsync([&rt, what = std::string(what), reject] { - auto errorCtr = rt.global().getPropertyAsFunction(rt, "Error"); - auto error = errorCtr.callAsConstructor( - rt, jsi::String::createFromAscii(rt, what)); - reject->asObject(rt).asFunction(rt).call(rt, error); - }); - } - }; - _thread_pool->queueWork(task); - return {}; + auto resolve = std::make_shared(rt, args[0]); + auto reject = std::make_shared(rt, args[1]); + + auto task = [&rt, this, sqlFileName, resolve, reject]() { + try { + const auto result = import_sql_file(db, sqlFileName); + + invoker->invokeAsync([&rt, result, resolve] { + auto res = jsi::Object(rt); + res.setProperty(rt, "rowsAffected", + jsi::Value(result.affectedRows)); + res.setProperty(rt, "commands", + jsi::Value(result.commands)); + resolve->asObject(rt).asFunction(rt).call( + rt, std::move(res)); + }); + } catch (std::runtime_error &e) { + auto what = e.what(); + invoker->invokeAsync( + [&rt, what = std::string(what), reject] { + auto errorCtr = + rt.global().getPropertyAsFunction(rt, "Error"); + auto error = errorCtr.callAsConstructor( + rt, jsi::String::createFromAscii(rt, what)); + reject->asObject(rt).asFunction(rt).call(rt, error); + }); + } catch (std::exception &exc) { + auto what = exc.what(); + invoker->invokeAsync( + [&rt, what = std::string(what), reject] { + auto errorCtr = + rt.global().getPropertyAsFunction(rt, "Error"); + auto error = errorCtr.callAsConstructor( + rt, jsi::String::createFromAscii(rt, what)); + reject->asObject(rt).asFunction(rt).call(rt, error); + }); + } + }; + _thread_pool->queueWork(task); + return {}; })); return promise; - }); + }); - function_map["updateHook"] = HOSTFN("updateHook") { - auto callback = std::make_shared(rt, args[0]); + function_map["updateHook"] = HOSTFN("updateHook") { + auto callback = std::make_shared(rt, args[0]); - if (callback->isUndefined() || callback->isNull()) { - update_hook_callback = nullptr; - } else { - update_hook_callback = callback; - } + if (callback->isUndefined() || callback->isNull()) { + update_hook_callback = nullptr; + } else { + update_hook_callback = callback; + } - auto_register_update_hook(); - return {}; - }); + auto_register_update_hook(); + return {}; + }); - function_map["commitHook"] = HOSTFN("commitHook") { - if (count < 1) { - throw std::runtime_error("[op-sqlite][commitHook] callback needed"); - } + function_map["commitHook"] = HOSTFN("commitHook") { + if (count < 1) { + throw std::runtime_error("[op-sqlite][commitHook] callback needed"); + } - auto callback = std::make_shared(rt, args[0]); - if (callback->isUndefined() || callback->isNull()) { - opsqlite_deregister_commit_hook(db); - return {}; - } - commit_hook_callback = callback; - opsqlite_register_commit_hook(db, this); + auto callback = std::make_shared(rt, args[0]); + if (callback->isUndefined() || callback->isNull()) { + opsqlite_deregister_commit_hook(db); + return {}; + } + commit_hook_callback = callback; + opsqlite_register_commit_hook(db, this); - return {}; - }); + return {}; + }); - function_map["rollbackHook"] = HOSTFN("rollbackHook") { - if (count < 1) { - throw std::runtime_error("[op-sqlite][rollbackHook] callback needed"); - } + function_map["rollbackHook"] = HOSTFN("rollbackHook") { + if (count < 1) { + throw std::runtime_error( + "[op-sqlite][rollbackHook] callback needed"); + } - auto callback = std::make_shared(rt, args[0]); + auto callback = std::make_shared(rt, args[0]); - if (callback->isUndefined() || callback->isNull()) { - opsqlite_deregister_rollback_hook(db); - return {}; - } - rollback_hook_callback = callback; + if (callback->isUndefined() || callback->isNull()) { + opsqlite_deregister_rollback_hook(db); + return {}; + } + rollback_hook_callback = callback; - opsqlite_register_rollback_hook(db, this); - return {}; - }); + opsqlite_register_rollback_hook(db, this); + return {}; + }); - function_map["loadExtension"] = HOSTFN("loadExtension") { - auto path = args[0].asString(rt).utf8(rt); - std::string entry_point; - if (count > 1 && args[1].isString()) { - entry_point = args[1].asString(rt).utf8(rt); - } + function_map["loadExtension"] = HOSTFN("loadExtension") { + auto path = args[0].asString(rt).utf8(rt); + std::string entry_point; + if (count > 1 && args[1].isString()) { + entry_point = args[1].asString(rt).utf8(rt); + } - opsqlite_load_extension(db, path, entry_point); - return {}; - }); - - function_map["reactiveExecute"] = HOSTFN("reactiveExecute") { - auto query = args[0].asObject(rt); - - const std::string query_str = - query.getProperty(rt, "query").asString(rt).utf8(rt); - auto js_args = query.getProperty(rt, "arguments"); - auto js_discriminators = - query.getProperty(rt, "fireOn").asObject(rt).asArray(rt); - auto variant_args = to_variant_vec(rt, js_args); - - sqlite3_stmt *stmt = opsqlite_prepare_statement(db, query_str); - opsqlite_bind_statement(stmt, &variant_args); - - auto callback = - std::make_shared(query.getProperty(rt, "callback")); - - std::vector discriminators; - - for (size_t i = 0; i < js_discriminators.length(rt); i++) { - auto js_discriminator = - js_discriminators.getValueAtIndex(rt, i).asObject(rt); - std::string table = - js_discriminator.getProperty(rt, "table").asString(rt).utf8(rt); - std::vector ids; - if (js_discriminator.hasProperty(rt, "ids")) { - auto js_ids = - js_discriminator.getProperty(rt, "ids").asObject(rt).asArray(rt); - for (size_t j = 0; j < js_ids.length(rt); j++) { - ids.push_back( - static_cast(js_ids.getValueAtIndex(rt, j).asNumber())); + opsqlite_load_extension(db, path, entry_point); + return {}; + }); + + function_map["reactiveExecute"] = HOSTFN("reactiveExecute") { + auto query = args[0].asObject(rt); + + const std::string query_str = + query.getProperty(rt, "query").asString(rt).utf8(rt); + auto js_args = query.getProperty(rt, "arguments"); + auto js_discriminators = + query.getProperty(rt, "fireOn").asObject(rt).asArray(rt); + auto variant_args = to_variant_vec(rt, js_args); + + sqlite3_stmt *stmt = opsqlite_prepare_statement(db, query_str); + opsqlite_bind_statement(stmt, &variant_args); + + auto callback = + std::make_shared(query.getProperty(rt, "callback")); + + std::vector discriminators; + + for (size_t i = 0; i < js_discriminators.length(rt); i++) { + auto js_discriminator = + js_discriminators.getValueAtIndex(rt, i).asObject(rt); + std::string table = + js_discriminator.getProperty(rt, "table").asString(rt).utf8(rt); + std::vector ids; + if (js_discriminator.hasProperty(rt, "ids")) { + auto js_ids = js_discriminator.getProperty(rt, "ids") + .asObject(rt) + .asArray(rt); + for (size_t j = 0; j < js_ids.length(rt); j++) { + ids.push_back(static_cast( + js_ids.getValueAtIndex(rt, j).asNumber())); + } + } + discriminators.push_back({table, ids}); } - } - discriminators.push_back({table, ids}); - } - std::shared_ptr reactiveQuery = - std::make_shared( - ReactiveQuery{stmt, discriminators, callback}); + std::shared_ptr reactiveQuery = + std::make_shared( + ReactiveQuery{stmt, discriminators, callback}); - reactive_queries.push_back(reactiveQuery); + reactive_queries.push_back(reactiveQuery); - auto_register_update_hook(); + auto_register_update_hook(); - auto unsubscribe = HOSTFN("unsubscribe") { - auto it = std::find(reactive_queries.begin(), reactive_queries.end(), - reactiveQuery); - if (it != reactive_queries.end()) { - reactive_queries.erase(it); - } - auto_register_update_hook(); - return {}; - }); + auto unsubscribe = HOSTFN("unsubscribe") { + auto it = std::find(reactive_queries.begin(), + reactive_queries.end(), reactiveQuery); + if (it != reactive_queries.end()) { + reactive_queries.erase(it); + } + auto_register_update_hook(); + return {}; + }); - return unsubscribe; - }); + return unsubscribe; + }); #endif - function_map["prepareStatement"] = HOSTFN("prepareStatement") { - auto query = args[0].asString(rt).utf8(rt); + function_map["prepareStatement"] = HOSTFN("prepareStatement") { + auto query = args[0].asString(rt).utf8(rt); #ifdef OP_SQLITE_USE_LIBSQL - libsql_stmt_t statement = opsqlite_libsql_prepare_statement(db, query); + libsql_stmt_t statement = opsqlite_libsql_prepare_statement(db, query); #else - sqlite3_stmt *statement = opsqlite_prepare_statement(db, query); + sqlite3_stmt *statement = opsqlite_prepare_statement(db, query); #endif - auto preparedStatementHostObject = - std::make_shared(db, db_name, statement, - invoker, _thread_pool); - - return jsi::Object::createFromHostObject(rt, preparedStatementHostObject); - }); - - function_map["getDbPath"] = HOSTFN("getDbPath") { - std::string path = std::string(base_path); - - if (count == 1) { - if (!args[0].isString()) { - throw std::runtime_error( - "[op-sqlite][open] database location must be a string"); - } - - std::string last_path = args[0].asString(rt).utf8(rt); - - if (last_path == ":memory:") { - path = ":memory:"; - } else if (last_path.rfind('/', 0) == 0) { - path = last_path; - } else { - path = path + "/" + last_path; - } - } + auto preparedStatementHostObject = + std::make_shared( + db, db_name, statement, invoker, _thread_pool); + + return jsi::Object::createFromHostObject(rt, + preparedStatementHostObject); + }); + + function_map["getDbPath"] = HOSTFN("getDbPath") { + std::string path = std::string(base_path); - auto result = opsqlite_get_db_path(db_name, path); - return jsi::String::createFromUtf8(rt, result); - }); + if (count == 1) { + if (!args[0].isString()) { + throw std::runtime_error( + "[op-sqlite][open] database location must be a string"); + } - function_map["flushPendingReactiveQueries"] = - HOSTFN("flushPendingReactiveQueries") { - auto promiseCtr = rt.global().getPropertyAsFunction(rt, "Promise"); + std::string last_path = args[0].asString(rt).utf8(rt); + + if (last_path == ":memory:") { + path = ":memory:"; + } else if (last_path.rfind('/', 0) == 0) { + path = last_path; + } else { + path = path + "/" + last_path; + } + } + + auto result = opsqlite_get_db_path(db_name, path); + return jsi::String::createFromUtf8(rt, result); + }); + + function_map["flushPendingReactiveQueries"] = + HOSTFN("flushPendingReactiveQueries") { + auto promiseCtr = rt.global().getPropertyAsFunction(rt, "Promise"); auto promise = promiseCtr.callAsConstructor(rt, HOSTFN("executor") { - auto resolve = std::make_shared(rt, args[0]); + auto resolve = std::make_shared(rt, args[0]); - auto task = [&rt, this, resolve]() { - flush_pending_reactive_queries(resolve); - }; + auto task = [&rt, this, resolve]() { + flush_pending_reactive_queries(resolve); + }; - _thread_pool->queueWork(task); + _thread_pool->queueWork(task); - return {}; + return {}; })); return promise; - }); + }); } std::vector DBHostObject::getPropertyNames(jsi::Runtime &_rt) { - std::vector keys; - keys.reserve(function_map.size()); - for (const auto &pair : function_map) { - keys.emplace_back(jsi::PropNameID::forUtf8(_rt, pair.first)); - } - return keys; + std::vector keys; + keys.reserve(function_map.size()); + for (const auto &pair : function_map) { + keys.emplace_back(jsi::PropNameID::forUtf8(_rt, pair.first)); + } + return keys; } jsi::Value DBHostObject::get(jsi::Runtime &_rt, const jsi::PropNameID &propNameID) { - auto name = propNameID.utf8(rt); - if (function_map.count(name) != 1) { - return HOST_STATIC_FN(name.c_str()) { - throw std::runtime_error( - "[op-sqlite] Function " + name + - " not implemented for current backend (libsql or sqlcipher)"); - }); - } + auto name = propNameID.utf8(rt); + if (function_map.count(name) != 1) { + return HOST_STATIC_FN(name.c_str()) { + throw std::runtime_error( + "[op-sqlite] Function " + name + + " not implemented for current backend (libsql or sqlcipher)"); + }); + } - return {rt, function_map[name]}; + return {rt, function_map[name]}; } void DBHostObject::set(jsi::Runtime &_rt, const jsi::PropNameID &name, const jsi::Value &value) { - throw std::runtime_error("You cannot write to this object!"); + throw std::runtime_error("You cannot write to this object!"); } void DBHostObject::invalidate() { - if (invalidated) { - return; - } + if (invalidated) { + return; + } - invalidated = true; - _thread_pool->restartPool(); + invalidated = true; + _thread_pool->restartPool(); #ifdef OP_SQLITE_USE_LIBSQL - opsqlite_libsql_close(db); + opsqlite_libsql_close(db); #else - if (db != nullptr) { - opsqlite_close(db); - db = nullptr; - } + if (db != nullptr) { + opsqlite_close(db); + db = nullptr; + } #endif } diff --git a/cpp/DBHostObject.h b/cpp/DBHostObject.h index 8567a2e..3f703dc 100644 --- a/cpp/DBHostObject.h +++ b/cpp/DBHostObject.h @@ -19,79 +19,80 @@ namespace jsi = facebook::jsi; namespace react = facebook::react; struct PendingReactiveInvocation { - std::string db_name; - std::string table; - std::string rowid; + std::string db_name; + std::string table; + std::string rowid; }; struct TableRowDiscriminator { - std::string table; - std::vector ids; + std::string table; + std::vector ids; }; struct ReactiveQuery { #ifndef OP_SQLITE_USE_LIBSQL - sqlite3_stmt *stmt; + sqlite3_stmt *stmt; #endif - std::vector discriminators; - std::shared_ptr callback; + std::vector discriminators; + std::shared_ptr callback; }; class JSI_EXPORT DBHostObject : public jsi::HostObject { -public: - // Normal constructor shared between all backends - DBHostObject(jsi::Runtime &rt, std::string &base_path, - std::shared_ptr invoker, - std::string &db_name, std::string &path, - std::string &crsqlite_path, std::string &sqlite_vec_path, - std::string &encryption_key); + public: + // Normal constructor shared between all backends + DBHostObject(jsi::Runtime &rt, std::string &base_path, + std::shared_ptr invoker, + std::string &db_name, std::string &path, + std::string &crsqlite_path, std::string &sqlite_vec_path, + std::string &encryption_key); #ifdef OP_SQLITE_USE_LIBSQL - // Constructor for remoteOpen, purely for remote databases - DBHostObject(jsi::Runtime &rt, std::string &url, std::string &auth_token, - std::shared_ptr invoker); + // Constructor for remoteOpen, purely for remote databases + DBHostObject(jsi::Runtime &rt, std::string &url, std::string &auth_token, + std::shared_ptr invoker); - // Constructor for a local database with remote sync - DBHostObject(jsi::Runtime &rt, std::shared_ptr invoker, - std::string &db_name, std::string &path, std::string &url, - std::string &auth_token, int sync_interval); + // Constructor for a local database with remote sync + DBHostObject(jsi::Runtime &rt, std::shared_ptr invoker, + std::string &db_name, std::string &path, std::string &url, + std::string &auth_token, int sync_interval); #endif - std::vector getPropertyNames(jsi::Runtime &rt) override; - jsi::Value get(jsi::Runtime &rt, const jsi::PropNameID &propNameID) override; - void set(jsi::Runtime &rt, const jsi::PropNameID &name, - const jsi::Value &value) override; - void on_update(const std::string &table, const std::string &operation, - long long row_id); - void on_commit(); - void on_rollback(); - void invalidate(); - ~DBHostObject() override; + std::vector getPropertyNames(jsi::Runtime &rt) override; + jsi::Value get(jsi::Runtime &rt, + const jsi::PropNameID &propNameID) override; + void set(jsi::Runtime &rt, const jsi::PropNameID &name, + const jsi::Value &value) override; + void on_update(const std::string &table, const std::string &operation, + long long row_id); + void on_commit(); + void on_rollback(); + void invalidate(); + ~DBHostObject() override; -private: - std::set> pending_reactive_queries; - void auto_register_update_hook(); - void create_jsi_functions(); - void - flush_pending_reactive_queries(const std::shared_ptr &resolve); + private: + std::set> pending_reactive_queries; + void auto_register_update_hook(); + void create_jsi_functions(); + void + flush_pending_reactive_queries(const std::shared_ptr &resolve); - std::unordered_map function_map; - std::string base_path; - std::shared_ptr invoker; - std::shared_ptr _thread_pool; - std::string db_name; - std::shared_ptr update_hook_callback; - std::shared_ptr commit_hook_callback; - std::shared_ptr rollback_hook_callback; - jsi::Runtime &rt; - std::vector> reactive_queries; - std::vector pending_reactive_invocations; - bool is_update_hook_registered = false; - bool invalidated = false; + std::unordered_map function_map; + std::string base_path; + std::shared_ptr invoker; + std::shared_ptr _thread_pool; + std::string db_name; + std::shared_ptr update_hook_callback; + std::shared_ptr commit_hook_callback; + std::shared_ptr rollback_hook_callback; + jsi::Runtime &rt; + std::vector> reactive_queries; + std::vector pending_reactive_invocations; + bool is_update_hook_registered = false; + bool invalidated = false; #ifdef OP_SQLITE_USE_LIBSQL - DB db; + DB db; #else - sqlite3 *db; + sqlite3 *db; #endif }; diff --git a/cpp/DumbHostObject.cpp b/cpp/DumbHostObject.cpp index de54a83..0f67a91 100644 --- a/cpp/DumbHostObject.cpp +++ b/cpp/DumbHostObject.cpp @@ -9,63 +9,64 @@ namespace jsi = facebook::jsi; DumbHostObject::DumbHostObject( std::shared_ptr> metadata) { - this->metadata = metadata; + this->metadata = metadata; }; std::vector DumbHostObject::getPropertyNames(jsi::Runtime &rt) { - std::vector keys; + std::vector keys; - for (auto field : *metadata) { - // TODO improve this by generating the propName once on metadata creation - keys.push_back(jsi::PropNameID::forAscii( - rt, std::get(field.fields[0].second))); - } + for (auto field : *metadata) { + // TODO improve this by generating the propName once on metadata + // creation + keys.push_back(jsi::PropNameID::forAscii( + rt, std::get(field.fields[0].second))); + } - return keys; + return keys; } jsi::Value DumbHostObject::get(jsi::Runtime &rt, const jsi::PropNameID &propNameID) { - auto name = propNameID.utf8(rt); - auto fields = metadata.get(); - for (int i = 0; i < fields->size(); i++) { - auto fieldName = std::get(fields->at(i).fields[0].second); - if (fieldName == name) { - return to_jsi(rt, values.at(i)); + auto name = propNameID.utf8(rt); + auto fields = metadata.get(); + for (int i = 0; i < fields->size(); i++) { + auto fieldName = std::get(fields->at(i).fields[0].second); + if (fieldName == name) { + return to_jsi(rt, values.at(i)); + } } - } - for (auto pairField : ownValues) { - if (name == pairField.first) { - return to_jsi(rt, pairField.second); + for (auto pairField : ownValues) { + if (name == pairField.first) { + return to_jsi(rt, pairField.second); + } } - } - return {}; + return {}; } void DumbHostObject::set(jsi::Runtime &rt, const jsi::PropNameID &name, const jsi::Value &value) { - auto key = name.utf8(rt); - auto fields = metadata.get(); - for (int i = 0; i < fields->size(); i++) { - auto fieldName = std::get(fields->at(i).fields[0].second); - if (fieldName == key) { - values[i] = to_variant(rt, value); - return; + auto key = name.utf8(rt); + auto fields = metadata.get(); + for (int i = 0; i < fields->size(); i++) { + auto fieldName = std::get(fields->at(i).fields[0].second); + if (fieldName == key) { + values[i] = to_variant(rt, value); + return; + } } - } - for (auto pairField : ownValues) { - if (key == pairField.first) { - pairField.second = to_variant(rt, value); - return; + for (auto pairField : ownValues) { + if (key == pairField.first) { + pairField.second = to_variant(rt, value); + return; + } } - } - ownValues.push_back(std::make_pair(key, to_variant(rt, value))); + ownValues.push_back(std::make_pair(key, to_variant(rt, value))); } } // namespace opsqlite diff --git a/cpp/DumbHostObject.h b/cpp/DumbHostObject.h index 9b5e406..5c91d4b 100644 --- a/cpp/DumbHostObject.h +++ b/cpp/DumbHostObject.h @@ -12,24 +12,25 @@ namespace opsqlite { namespace jsi = facebook::jsi; class JSI_EXPORT DumbHostObject : public jsi::HostObject { -public: - DumbHostObject() = default; + public: + DumbHostObject() = default; - explicit DumbHostObject( - std::shared_ptr> metadata); + explicit DumbHostObject( + std::shared_ptr> metadata); - std::vector getPropertyNames(jsi::Runtime &rt) override; + std::vector getPropertyNames(jsi::Runtime &rt) override; - jsi::Value get(jsi::Runtime &rt, const jsi::PropNameID &propNameID) override; + jsi::Value get(jsi::Runtime &rt, + const jsi::PropNameID &propNameID) override; - void set(jsi::Runtime &rt, const jsi::PropNameID &name, - const jsi::Value &value) override; + void set(jsi::Runtime &rt, const jsi::PropNameID &name, + const jsi::Value &value) override; - std::vector values; + std::vector values; - std::shared_ptr> metadata; + std::shared_ptr> metadata; - std::vector> ownValues; + std::vector> ownValues; }; } // namespace opsqlite diff --git a/cpp/PreparedStatementHostObject.cpp b/cpp/PreparedStatementHostObject.cpp index 6dba885..4c744c0 100644 --- a/cpp/PreparedStatementHostObject.cpp +++ b/cpp/PreparedStatementHostObject.cpp @@ -13,132 +13,138 @@ namespace jsi = facebook::jsi; std::vector PreparedStatementHostObject::getPropertyNames(jsi::Runtime &rt) { - std::vector keys; + std::vector keys; - return keys; + return keys; } jsi::Value PreparedStatementHostObject::get(jsi::Runtime &rt, const jsi::PropNameID &propNameID) { - auto name = propNameID.utf8(rt); + auto name = propNameID.utf8(rt); - if (name == "bind") { - return HOSTFN("bind") { - if (_stmt == nullptr) { - throw std::runtime_error("statement has been freed"); - } + if (name == "bind") { + return HOSTFN("bind") { + if (_stmt == nullptr) { + throw std::runtime_error("statement has been freed"); + } - const jsi::Value &js_params = args[0]; - std::vector params = to_variant_vec(rt, js_params); + const jsi::Value &js_params = args[0]; + std::vector params = to_variant_vec(rt, js_params); - auto promiseCtr = rt.global().getPropertyAsFunction(rt, "Promise"); + auto promiseCtr = rt.global().getPropertyAsFunction(rt, "Promise"); auto promise = promiseCtr.callAsConstructor( rt, HOSTFN("executor") { - auto resolve = std::make_shared(rt, args[0]); - auto reject = std::make_shared(rt, args[1]); - auto task = [&rt, this, resolve, reject, - invoker = this->_js_call_invoker, params]() { - try { + auto resolve = std::make_shared(rt, args[0]); + auto reject = std::make_shared(rt, args[1]); + auto task = [&rt, this, resolve, reject, + invoker = this->_js_call_invoker, params]() { + try { #ifdef OP_SQLITE_USE_LIBSQL - opsqlite_libsql_bind_statement(_stmt, ¶ms); + opsqlite_libsql_bind_statement(_stmt, ¶ms); #else - opsqlite_bind_statement(_stmt, ¶ms); + opsqlite_bind_statement(_stmt, ¶ms); #endif - invoker->invokeAsync([&rt, resolve] { - resolve->asObject(rt).asFunction(rt).call(rt, {}); - }); - } catch (const std::runtime_error &e) { - invoker->invokeAsync([&rt, e, reject] { - auto errorCtr = rt.global().getPropertyAsFunction(rt, "Error"); - auto error = errorCtr.callAsConstructor( - rt, jsi::String::createFromUtf8(rt, e.what())); - reject->asObject(rt).asFunction(rt).call(rt, error); - }); - } catch (const std::exception &e) { - invoker->invokeAsync([&rt, e, reject] { - auto errorCtr = rt.global().getPropertyAsFunction(rt, "Error"); - auto error = errorCtr.callAsConstructor( - rt, jsi::String::createFromUtf8(rt, e.what())); - reject->asObject(rt).asFunction(rt).call(rt, error); - }); - } - }; - - _thread_pool->queueWork(task); - - return {}; + invoker->invokeAsync([&rt, resolve] { + resolve->asObject(rt).asFunction(rt).call(rt, {}); + }); + } catch (const std::runtime_error &e) { + invoker->invokeAsync([&rt, e, reject] { + auto errorCtr = + rt.global().getPropertyAsFunction(rt, "Error"); + auto error = errorCtr.callAsConstructor( + rt, jsi::String::createFromUtf8(rt, e.what())); + reject->asObject(rt).asFunction(rt).call(rt, error); + }); + } catch (const std::exception &e) { + invoker->invokeAsync([&rt, e, reject] { + auto errorCtr = + rt.global().getPropertyAsFunction(rt, "Error"); + auto error = errorCtr.callAsConstructor( + rt, jsi::String::createFromUtf8(rt, e.what())); + reject->asObject(rt).asFunction(rt).call(rt, error); + }); + } + }; + + _thread_pool->queueWork(task); + + return {}; })); return promise; - }); - } + }); + } - if (name == "execute") { - return HOSTFN("execute") { - if (_stmt == nullptr) { - throw std::runtime_error("statement has been freed"); - } + if (name == "execute") { + return HOSTFN("execute") { + if (_stmt == nullptr) { + throw std::runtime_error("statement has been freed"); + } - auto promiseCtr = rt.global().getPropertyAsFunction(rt, "Promise"); + auto promiseCtr = rt.global().getPropertyAsFunction(rt, "Promise"); auto promise = promiseCtr.callAsConstructor(rt, HOSTFN("executor") { - auto resolve = std::make_shared(rt, args[0]); - auto reject = std::make_shared(rt, args[1]); - - auto task = [&rt, this, resolve, reject, - invoker = this->_js_call_invoker]() { - std::vector results; - std::shared_ptr> metadata = - std::make_shared>(); - try { + auto resolve = std::make_shared(rt, args[0]); + auto reject = std::make_shared(rt, args[1]); + + auto task = [&rt, this, resolve, reject, + invoker = this->_js_call_invoker]() { + std::vector results; + std::shared_ptr> metadata = + std::make_shared>(); + try { #ifdef OP_SQLITE_USE_LIBSQL - auto status = opsqlite_libsql_execute_prepared_statement( - _db, _stmt, &results, metadata); + auto status = + opsqlite_libsql_execute_prepared_statement( + _db, _stmt, &results, metadata); #else - auto status = opsqlite_execute_prepared_statement( - _db, _stmt, &results, metadata); + auto status = opsqlite_execute_prepared_statement( + _db, _stmt, &results, metadata); #endif - invoker->invokeAsync( - [&rt, status = std::move(status), - results = - std::make_shared>(results), - metadata, resolve] { - auto jsiResult = - create_result(rt, status, results.get(), metadata); - resolve->asObject(rt).asFunction(rt).call( - rt, std::move(jsiResult)); - }); - } catch (std::exception &exc) { - invoker->invokeAsync([&rt, &exc, reject] { - auto errorCtr = rt.global().getPropertyAsFunction(rt, "Error"); - auto error = errorCtr.callAsConstructor( - rt, jsi::String::createFromUtf8(rt, exc.what())); - reject->asObject(rt).asFunction(rt).call(rt, error); - }); - } - }; - - _thread_pool->queueWork(task); - - return {}; + invoker->invokeAsync( + [&rt, status = std::move(status), + results = + std::make_shared>( + results), + metadata, resolve] { + auto jsiResult = create_result( + rt, status, results.get(), metadata); + resolve->asObject(rt).asFunction(rt).call( + rt, std::move(jsiResult)); + }); + } catch (std::exception &exc) { + invoker->invokeAsync([&rt, &exc, reject] { + auto errorCtr = + rt.global().getPropertyAsFunction(rt, "Error"); + auto error = errorCtr.callAsConstructor( + rt, + jsi::String::createFromUtf8(rt, exc.what())); + reject->asObject(rt).asFunction(rt).call(rt, error); + }); + } + }; + + _thread_pool->queueWork(task); + + return {}; })); return promise; - }); - } + }); + } - return {}; + return {}; } PreparedStatementHostObject::~PreparedStatementHostObject() { #ifdef OP_SQLITE_USE_LIBSQL - if (_stmt != nullptr) { - libsql_free_stmt(_stmt); - _stmt = nullptr; - } + if (_stmt != nullptr) { + libsql_free_stmt(_stmt); + _stmt = nullptr; + } #else - if (_stmt != nullptr) { -// sqlite3_finalize(_stmt); - _stmt = nullptr; - } + if (_stmt != nullptr) { + // sqlite3_finalize(_stmt); + _stmt = nullptr; + } #endif } diff --git a/cpp/PreparedStatementHostObject.h b/cpp/PreparedStatementHostObject.h index 71895f6..2e25514 100644 --- a/cpp/PreparedStatementHostObject.h +++ b/cpp/PreparedStatementHostObject.h @@ -4,8 +4,8 @@ #include #include #ifdef OP_SQLITE_USE_LIBSQL -#include "libsql/bridge.h" #include "libsql.h" +#include "libsql/bridge.h" #else #include #endif @@ -18,40 +18,42 @@ namespace jsi = facebook::jsi; namespace react = facebook::react; class PreparedStatementHostObject : public jsi::HostObject { -public: + public: #ifdef OP_SQLITE_USE_LIBSQL - PreparedStatementHostObject( - DB const &db, std::string name, libsql_stmt_t stmt, - std::shared_ptr js_call_invoker, - std::shared_ptr thread_pool) - : _db(db), _name(std::move(name)), _stmt(stmt), _js_call_invoker(js_call_invoker), - _thread_pool(thread_pool) {}; + PreparedStatementHostObject( + DB const &db, std::string name, libsql_stmt_t stmt, + std::shared_ptr js_call_invoker, + std::shared_ptr thread_pool) + : _db(db), _name(std::move(name)), _stmt(stmt), + _js_call_invoker(js_call_invoker), _thread_pool(thread_pool) {}; #else - PreparedStatementHostObject( - sqlite3 *db, std::string name, sqlite3_stmt *stmt, - std::shared_ptr js_call_invoker, - std::shared_ptr thread_pool) - : _db(db), _name(std::move(name)), _stmt(stmt), _js_call_invoker(std::move(js_call_invoker)), - _thread_pool(std::move(thread_pool)) {}; + PreparedStatementHostObject( + sqlite3 *db, std::string name, sqlite3_stmt *stmt, + std::shared_ptr js_call_invoker, + std::shared_ptr thread_pool) + : _db(db), _name(std::move(name)), _stmt(stmt), + _js_call_invoker(std::move(js_call_invoker)), + _thread_pool(std::move(thread_pool)) {}; #endif - ~PreparedStatementHostObject() override; + ~PreparedStatementHostObject() override; - std::vector getPropertyNames(jsi::Runtime &rt) override; + std::vector getPropertyNames(jsi::Runtime &rt) override; - jsi::Value get(jsi::Runtime &rt, const jsi::PropNameID &propNameID) override; + jsi::Value get(jsi::Runtime &rt, + const jsi::PropNameID &propNameID) override; -private: - std::string _name; + private: + std::string _name; #ifdef OP_SQLITE_USE_LIBSQL - DB _db; - libsql_stmt_t _stmt; + DB _db; + libsql_stmt_t _stmt; #else - sqlite3 *_db; - // This shouldn't be de-allocated until sqlite3_finalize is called on it - sqlite3_stmt *_stmt; + sqlite3 *_db; + // This shouldn't be de-allocated until sqlite3_finalize is called on it + sqlite3_stmt *_stmt; #endif - std::shared_ptr _js_call_invoker; - std::shared_ptr _thread_pool; + std::shared_ptr _js_call_invoker; + std::shared_ptr _thread_pool; }; } // namespace opsqlite diff --git a/cpp/SmartHostObject.cpp b/cpp/SmartHostObject.cpp index c306da8..dcb87af 100644 --- a/cpp/SmartHostObject.cpp +++ b/cpp/SmartHostObject.cpp @@ -7,28 +7,28 @@ namespace jsi = facebook::jsi; std::vector SmartHostObject::getPropertyNames(jsi::Runtime &rt) { - std::vector keys; + std::vector keys; - keys.reserve(fields.size()); -for (const auto& field : fields) { - keys.emplace_back(jsi::PropNameID::forAscii(rt, field.first)); - } + keys.reserve(fields.size()); + for (const auto &field : fields) { + keys.emplace_back(jsi::PropNameID::forAscii(rt, field.first)); + } - return keys; + return keys; } jsi::Value SmartHostObject::get(jsi::Runtime &rt, const jsi::PropNameID &propNameID) { - auto name = propNameID.utf8(rt); + auto name = propNameID.utf8(rt); - for (const auto& field : fields) { - auto fieldName = field.first; - if (fieldName == name) { - return to_jsi(rt, field.second); + for (const auto &field : fields) { + auto fieldName = field.first; + if (fieldName == name) { + return to_jsi(rt, field.second); + } } - } - return {}; + return {}; } } // namespace opsqlite diff --git a/cpp/SmartHostObject.h b/cpp/SmartHostObject.h index 87ffddd..abaf20c 100644 --- a/cpp/SmartHostObject.h +++ b/cpp/SmartHostObject.h @@ -10,14 +10,15 @@ namespace opsqlite { namespace jsi = facebook::jsi; class JSI_EXPORT SmartHostObject : public jsi::HostObject { -public: - SmartHostObject() = default; + public: + SmartHostObject() = default; - std::vector getPropertyNames(jsi::Runtime &rt) override; + std::vector getPropertyNames(jsi::Runtime &rt) override; - jsi::Value get(jsi::Runtime &rt, const jsi::PropNameID &propNameID) override; + jsi::Value get(jsi::Runtime &rt, + const jsi::PropNameID &propNameID) override; - std::vector> fields; + std::vector> fields; }; } // namespace opsqlite diff --git a/cpp/ThreadPool.cpp b/cpp/ThreadPool.cpp index 545da0b..e91efaf 100644 --- a/cpp/ThreadPool.cpp +++ b/cpp/ThreadPool.cpp @@ -3,116 +3,118 @@ namespace opsqlite { ThreadPool::ThreadPool() : done(false) { - // This returns the number of threads supported by the system. If the - // function can't figure out this information, it returns 0. 0 is not good, - // so we create at least 1 -// auto numberOfThreads = std::thread::hardware_concurrency(); -// if (numberOfThreads == 0) { -// numberOfThreads = 1; -// } - - auto numberOfThreads = 1; - for (unsigned i = 0; i < numberOfThreads; ++i) { - // The threads will execute the private member `doWork`. Note that we need - // to pass a reference to the function (namespaced with the class name) as - // the first argument, and the current object as second argument - threads.emplace_back(&ThreadPool::doWork, this); - } + // This returns the number of threads supported by the system. If the + // function can't figure out this information, it returns 0. 0 is not good, + // so we create at least 1 + // auto numberOfThreads = std::thread::hardware_concurrency(); + // if (numberOfThreads == 0) { + // numberOfThreads = 1; + // } + + auto numberOfThreads = 1; + for (unsigned i = 0; i < numberOfThreads; ++i) { + // The threads will execute the private member `doWork`. Note that we + // need to pass a reference to the function (namespaced with the class + // name) as the first argument, and the current object as second + // argument + threads.emplace_back(&ThreadPool::doWork, this); + } } // The destructor joins all the threads so the program can exit gracefully. // This will be executed if there is any exception (e.g. creating the threads) ThreadPool::~ThreadPool() { - // So threads know it's time to shut down - done = true; + // So threads know it's time to shut down + done = true; - // Wake up all the threads, so they can finish and be joined - workQueueConditionVariable.notify_all(); + // Wake up all the threads, so they can finish and be joined + workQueueConditionVariable.notify_all(); - for (auto &thread : threads) { - if (thread.joinable()) { - thread.join(); + for (auto &thread : threads) { + if (thread.joinable()) { + thread.join(); + } } - } - threads.clear(); + threads.clear(); } // This function will be called by the server every time there is a request // that needs to be processed by the thread pool -void ThreadPool::queueWork(const std::function& task) { - // Grab the mutex - std::lock_guard g(workQueueMutex); +void ThreadPool::queueWork(const std::function &task) { + // Grab the mutex + std::lock_guard g(workQueueMutex); - // Push the request to the queue - workQueue.push(task); + // Push the request to the queue + workQueue.push(task); - // Notify one thread that there are requests to process - workQueueConditionVariable.notify_one(); + // Notify one thread that there are requests to process + workQueueConditionVariable.notify_one(); } // Function used by the threads to grab work from the queue void ThreadPool::doWork() { - // Loop while the queue is not destructing - while (!done) { - std::function task; - - // Create a scope, so we don't lock the queue for longer than necessary - { - std::unique_lock g(workQueueMutex); - workQueueConditionVariable.wait(g, [&] { - // Only wake up if there are elements in the queue or the program is - // shutting down - return !workQueue.empty() || done; - }); - - // If we are shutting down exit without trying to process more work - if (done) { - break; - } - - task = workQueue.front(); - workQueue.pop(); + // Loop while the queue is not destructing + while (!done) { + std::function task; + + // Create a scope, so we don't lock the queue for longer than necessary + { + std::unique_lock g(workQueueMutex); + workQueueConditionVariable.wait(g, [&] { + // Only wake up if there are elements in the queue or the + // program is shutting down + return !workQueue.empty() || done; + }); + + // If we are shutting down exit without trying to process more work + if (done) { + break; + } + + task = workQueue.front(); + workQueue.pop(); + } + ++busy; + task(); + --busy; } - ++busy; - task(); - --busy; - } } void ThreadPool::waitFinished() { - std::unique_lock g(workQueueMutex); - workQueueConditionVariable.wait( - g, [&] { return workQueue.empty() && (busy == 0); }); + std::unique_lock g(workQueueMutex); + workQueueConditionVariable.wait( + g, [&] { return workQueue.empty() && (busy == 0); }); } void ThreadPool::restartPool() { - // So threads know it's time to shut down - done = true; + // So threads know it's time to shut down + done = true; - // Wake up all the threads, so they can finish and be joined - workQueueConditionVariable.notify_all(); + // Wake up all the threads, so they can finish and be joined + workQueueConditionVariable.notify_all(); - for (auto &thread : threads) { - if (thread.joinable()) { - thread.join(); + for (auto &thread : threads) { + if (thread.joinable()) { + thread.join(); + } } - } - threads.clear(); + threads.clear(); - auto numberOfThreads = std::thread::hardware_concurrency(); - if (numberOfThreads == 0) { - numberOfThreads = 1; - } + auto numberOfThreads = std::thread::hardware_concurrency(); + if (numberOfThreads == 0) { + numberOfThreads = 1; + } - for (unsigned i = 0; i < numberOfThreads; ++i) { - // The threads will execute the private member `doWork`. Note that we need - // to pass a reference to the function (namespaced with the class name) as - // the first argument, and the current object as second argument - threads.emplace_back(&ThreadPool::doWork, this); - } + for (unsigned i = 0; i < numberOfThreads; ++i) { + // The threads will execute the private member `doWork`. Note that we + // need to pass a reference to the function (namespaced with the class + // name) as the first argument, and the current object as second + // argument + threads.emplace_back(&ThreadPool::doWork, this); + } - done = false; + done = false; } } // namespace opsqlite diff --git a/cpp/ThreadPool.h b/cpp/ThreadPool.h index cb84a30..846f3f3 100644 --- a/cpp/ThreadPool.h +++ b/cpp/ThreadPool.h @@ -11,34 +11,34 @@ namespace opsqlite { class ThreadPool { -public: - ThreadPool(); - ~ThreadPool(); - void queueWork(const std::function& task); - void waitFinished(); - void restartPool(); - -private: - unsigned int busy{}; - // This condition variable is used for the threads to wait until there is work - // to do - std::condition_variable_any workQueueConditionVariable; - - // We store the threads in a vector, so we can later stop them gracefully - std::vector threads; - - // Mutex to protect workQueue - std::mutex workQueueMutex; - - // Queue of requests waiting to be processed - std::queue> workQueue; - - // This will be set to true when the thread pool is shutting down. This tells - // the threads to stop looping and finish - bool done; - - // Function used by the threads to grab work from the queue - void doWork(); + public: + ThreadPool(); + ~ThreadPool(); + void queueWork(const std::function &task); + void waitFinished(); + void restartPool(); + + private: + unsigned int busy{}; + // This condition variable is used for the threads to wait until there is + // work to do + std::condition_variable_any workQueueConditionVariable; + + // We store the threads in a vector, so we can later stop them gracefully + std::vector threads; + + // Mutex to protect workQueue + std::mutex workQueueMutex; + + // Queue of requests waiting to be processed + std::queue> workQueue; + + // This will be set to true when the thread pool is shutting down. This + // tells the threads to stop looping and finish + bool done; + + // Function used by the threads to grab work from the queue + void doWork(); }; } // namespace opsqlite \ No newline at end of file diff --git a/cpp/bindings.cpp b/cpp/bindings.cpp index 1c15f1a..83b94ac 100644 --- a/cpp/bindings.cpp +++ b/cpp/bindings.cpp @@ -27,143 +27,147 @@ std::vector> dbs; // React native will try to clean the module on JS context invalidation // (CodePush/Hot Reload) The clearState function is called void invalidate() { - for (const auto &db : dbs) { - db->invalidate(); - } + for (const auto &db : dbs) { + db->invalidate(); + } - // Clear our existing vector of shared pointers so they can be garbage - // collected - dbs.clear(); + // Clear our existing vector of shared pointers so they can be garbage + // collected + dbs.clear(); } void install(jsi::Runtime &rt, const std::shared_ptr &invoker, const char *base_path, const char *crsqlite_path, const char *sqlite_vec_path) { - _base_path = std::string(base_path); - _crsqlite_path = std::string(crsqlite_path); - _sqlite_vec_path = std::string(sqlite_vec_path); - - auto open = HOST_STATIC_FN("open") { - jsi::Object options = args[0].asObject(rt); - std::string name = options.getProperty(rt, "name").asString(rt).utf8(rt); - std::string path = std::string(_base_path); - std::string location; - std::string encryption_key; - - if (options.hasProperty(rt, "location")) { - location = options.getProperty(rt, "location").asString(rt).utf8(rt); - } - - if (options.hasProperty(rt, "encryptionKey")) { - encryption_key = - options.getProperty(rt, "encryptionKey").asString(rt).utf8(rt); - } + _base_path = std::string(base_path); + _crsqlite_path = std::string(crsqlite_path); + _sqlite_vec_path = std::string(sqlite_vec_path); + + auto open = HOST_STATIC_FN("open") { + jsi::Object options = args[0].asObject(rt); + std::string name = + options.getProperty(rt, "name").asString(rt).utf8(rt); + std::string path = std::string(_base_path); + std::string location; + std::string encryption_key; + + if (options.hasProperty(rt, "location")) { + location = + options.getProperty(rt, "location").asString(rt).utf8(rt); + } + + if (options.hasProperty(rt, "encryptionKey")) { + encryption_key = + options.getProperty(rt, "encryptionKey").asString(rt).utf8(rt); + } #ifdef OP_SQLITE_USE_SQLCIPHER - if (encryption_key.empty()) { - log_to_console(rt, "Encryption key is missing for SQLCipher"); - } + if (encryption_key.empty()) { + log_to_console(rt, "Encryption key is missing for SQLCipher"); + } #endif - if (!location.empty()) { - if (location == ":memory:") { - path = ":memory:"; - } else if (location.rfind('/', 0) == 0) { - path = location; - } else { - path = path + "/" + location; - } - } - - std::shared_ptr db = std::make_shared( - rt, path, invoker, name, path, _crsqlite_path, _sqlite_vec_path, - encryption_key); - dbs.emplace_back(db); - return jsi::Object::createFromHostObject(rt, db); - }); - - auto is_sqlcipher = HOST_STATIC_FN("isSQLCipher") { + if (!location.empty()) { + if (location == ":memory:") { + path = ":memory:"; + } else if (location.rfind('/', 0) == 0) { + path = location; + } else { + path = path + "/" + location; + } + } + + std::shared_ptr db = std::make_shared( + rt, path, invoker, name, path, _crsqlite_path, _sqlite_vec_path, + encryption_key); + dbs.emplace_back(db); + return jsi::Object::createFromHostObject(rt, db); + }); + + auto is_sqlcipher = HOST_STATIC_FN("isSQLCipher") { #ifdef OP_SQLITE_USE_SQLCIPHER - return true; + return true; #else - return false; + return false; #endif - }); + }); - auto is_ios_embedded = HOST_STATIC_FN("isIOSEmbedded") { + auto is_ios_embedded = HOST_STATIC_FN("isIOSEmbedded") { #ifdef OP_SQLITE_USE_PHONE_VERSION - return true; + return true; #else - return false; + return false; #endif - }); + }); - auto is_libsql = HOST_STATIC_FN("isLibsql") { + auto is_libsql = HOST_STATIC_FN("isLibsql") { #ifdef OP_SQLITE_USE_LIBSQL - return true; + return true; #else - return false; + return false; #endif - }); + }); #ifdef OP_SQLITE_USE_LIBSQL - auto open_remote = HOST_STATIC_FN("openRemote") { - jsi::Object options = args[0].asObject(rt); - std::string url = options.getProperty(rt, "url").asString(rt).utf8(rt); - std::string auth_token = - options.getProperty(rt, "authToken").asString(rt).utf8(rt); - - std::shared_ptr db = - std::make_shared(rt, url, auth_token, invoker); - return jsi::Object::createFromHostObject(rt, db); - }); - - auto open_sync = HOST_STATIC_FN("openSync") { - jsi::Object options = args[0].asObject(rt); - std::string name = options.getProperty(rt, "name").asString(rt).utf8(rt); - std::string path = std::string(_base_path); - std::string url = options.getProperty(rt, "url").asString(rt).utf8(rt); - std::string auth_token = - options.getProperty(rt, "authToken").asString(rt).utf8(rt); - int sync_interval = 0; - if (options.hasProperty(rt, "syncInterval")) { - sync_interval = - static_cast(options.getProperty(rt, "syncInterval").asNumber()); - } - std::string location; - - if (options.hasProperty(rt, "location")) { - location = options.getProperty(rt, "location").asString(rt).utf8(rt); - } - - if (!location.empty()) { - if (location == ":memory:") { - path = ":memory:"; - } else if (location.rfind("/", 0) == 0) { - path = location; - } else { - path = path + "/" + location; - } - } - - std::shared_ptr db = std::make_shared( - rt, invoker, name, path, url, auth_token, sync_interval); - return jsi::Object::createFromHostObject(rt, db); - }); + auto open_remote = HOST_STATIC_FN("openRemote") { + jsi::Object options = args[0].asObject(rt); + std::string url = options.getProperty(rt, "url").asString(rt).utf8(rt); + std::string auth_token = + options.getProperty(rt, "authToken").asString(rt).utf8(rt); + + std::shared_ptr db = + std::make_shared(rt, url, auth_token, invoker); + return jsi::Object::createFromHostObject(rt, db); + }); + + auto open_sync = HOST_STATIC_FN("openSync") { + jsi::Object options = args[0].asObject(rt); + std::string name = + options.getProperty(rt, "name").asString(rt).utf8(rt); + std::string path = std::string(_base_path); + std::string url = options.getProperty(rt, "url").asString(rt).utf8(rt); + std::string auth_token = + options.getProperty(rt, "authToken").asString(rt).utf8(rt); + int sync_interval = 0; + if (options.hasProperty(rt, "syncInterval")) { + sync_interval = static_cast( + options.getProperty(rt, "syncInterval").asNumber()); + } + std::string location; + + if (options.hasProperty(rt, "location")) { + location = + options.getProperty(rt, "location").asString(rt).utf8(rt); + } + + if (!location.empty()) { + if (location == ":memory:") { + path = ":memory:"; + } else if (location.rfind("/", 0) == 0) { + path = location; + } else { + path = path + "/" + location; + } + } + + std::shared_ptr db = std::make_shared( + rt, invoker, name, path, url, auth_token, sync_interval); + return jsi::Object::createFromHostObject(rt, db); + }); #endif - jsi::Object module = jsi::Object(rt); - module.setProperty(rt, "open", std::move(open)); - module.setProperty(rt, "isSQLCipher", std::move(is_sqlcipher)); - module.setProperty(rt, "isLibsql", std::move(is_libsql)); - module.setProperty(rt, "isIOSEmbedded", std::move(is_ios_embedded)); + jsi::Object module = jsi::Object(rt); + module.setProperty(rt, "open", std::move(open)); + module.setProperty(rt, "isSQLCipher", std::move(is_sqlcipher)); + module.setProperty(rt, "isLibsql", std::move(is_libsql)); + module.setProperty(rt, "isIOSEmbedded", std::move(is_ios_embedded)); #ifdef OP_SQLITE_USE_LIBSQL - module.setProperty(rt, "openRemote", std::move(open_remote)); - module.setProperty(rt, "openSync", std::move(open_sync)); + module.setProperty(rt, "openRemote", std::move(open_remote)); + module.setProperty(rt, "openSync", std::move(open_sync)); #endif - rt.global().setProperty(rt, "__OPSQLiteProxy", std::move(module)); + rt.global().setProperty(rt, "__OPSQLiteProxy", std::move(module)); } } // namespace opsqlite diff --git a/cpp/bridge.cpp b/cpp/bridge.cpp index e785600..c977ed7 100644 --- a/cpp/bridge.cpp +++ b/cpp/bridge.cpp @@ -25,38 +25,42 @@ namespace opsqlite { inline void opsqlite_bind_statement(sqlite3_stmt *statement, const std::vector *values) { - sqlite3_clear_bindings(statement); - - size_t size = values->size(); - - for (int ii = 0; ii < size; ii++) { - int stmt_index = ii + 1; - JSVariant value = values->at(ii); - - std::visit( - [&](auto &&v) { - using T = std::decay_t; - - if constexpr (std::is_same_v) { - sqlite3_bind_int(statement, stmt_index, static_cast(v)); - } else if constexpr (std::is_same_v) { - sqlite3_bind_int(statement, stmt_index, v); - } else if constexpr (std::is_same_v) { - sqlite3_bind_double(statement, stmt_index, static_cast(v)); - } else if constexpr (std::is_same_v) { - sqlite3_bind_double(statement, stmt_index, v); - } else if constexpr (std::is_same_v) { - sqlite3_bind_text(statement, stmt_index, v.c_str(), - static_cast(v.length()), SQLITE_TRANSIENT); - } else if constexpr (std::is_same_v) { - sqlite3_bind_blob(statement, stmt_index, v.data.get(), - static_cast(v.size), SQLITE_TRANSIENT); - } else { - sqlite3_bind_null(statement, stmt_index); - } - }, - value); - } + sqlite3_clear_bindings(statement); + + size_t size = values->size(); + + for (int ii = 0; ii < size; ii++) { + int stmt_index = ii + 1; + JSVariant value = values->at(ii); + + std::visit( + [&](auto &&v) { + using T = std::decay_t; + + if constexpr (std::is_same_v) { + sqlite3_bind_int(statement, stmt_index, + static_cast(v)); + } else if constexpr (std::is_same_v) { + sqlite3_bind_int(statement, stmt_index, v); + } else if constexpr (std::is_same_v) { + sqlite3_bind_double(statement, stmt_index, + static_cast(v)); + } else if constexpr (std::is_same_v) { + sqlite3_bind_double(statement, stmt_index, v); + } else if constexpr (std::is_same_v) { + sqlite3_bind_text(statement, stmt_index, v.c_str(), + static_cast(v.length()), + SQLITE_TRANSIENT); + } else if constexpr (std::is_same_v) { + sqlite3_bind_blob(statement, stmt_index, v.data.get(), + static_cast(v.size), + SQLITE_TRANSIENT); + } else { + sqlite3_bind_null(statement, stmt_index); + } + }, + value); + } } /// Returns the completely formed db path, but it also creates any sub-folders @@ -64,18 +68,18 @@ inline void opsqlite_bind_statement(sqlite3_stmt *statement, std::string opsqlite_get_db_path(std::string const &db_name, std::string const &location) { - if (location == ":memory:") { - return location; - } + if (location == ":memory:") { + return location; + } - // Will return false if the directory already exists, no need to check - std::filesystem::create_directories(location); + // Will return false if the directory already exists, no need to check + std::filesystem::create_directories(location); - if (!location.empty() && location.back() != '/') { - return location + "/" + db_name; - } + if (!location.empty() && location.back() != '/') { + return location + "/" + db_name; + } - return location + db_name; + return location + db_name; } #ifdef OP_SQLITE_USE_SQLCIPHER @@ -88,363 +92,371 @@ sqlite3 *opsqlite_open(std::string const &name, std::string const &path, [[maybe_unused]] std::string const &crsqlite_path, [[maybe_unused]] std::string const &sqlite_vec_path) { #endif - std::string final_path = opsqlite_get_db_path(name, path); - char *errMsg; - sqlite3 *db; + std::string final_path = opsqlite_get_db_path(name, path); + char *errMsg; + sqlite3 *db; - int flags = - SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE | SQLITE_OPEN_FULLMUTEX; + int flags = + SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE | SQLITE_OPEN_FULLMUTEX; - int status = sqlite3_open_v2(final_path.c_str(), &db, flags, nullptr); + int status = sqlite3_open_v2(final_path.c_str(), &db, flags, nullptr); - if (status != SQLITE_OK) { - throw std::runtime_error(sqlite3_errmsg(db)); - } + if (status != SQLITE_OK) { + throw std::runtime_error(sqlite3_errmsg(db)); + } #ifdef OP_SQLITE_USE_SQLCIPHER - if (!encryption_key.empty()) { - opsqlite_execute(db, "PRAGMA key = '" + encryption_key + "'", nullptr); - } + if (!encryption_key.empty()) { + opsqlite_execute(db, "PRAGMA key = '" + encryption_key + "'", nullptr); + } #endif #ifndef OP_SQLITE_USE_PHONE_VERSION - sqlite3_enable_load_extension(db, 1); + sqlite3_enable_load_extension(db, 1); #endif #ifdef OP_SQLITE_USE_CRSQLITE - const char *crsqliteEntryPoint = "sqlite3_crsqlite_init"; + const char *crsqliteEntryPoint = "sqlite3_crsqlite_init"; - sqlite3_load_extension(db, crsqlite_path.c_str(), crsqliteEntryPoint, - &errMsg); + sqlite3_load_extension(db, crsqlite_path.c_str(), crsqliteEntryPoint, + &errMsg); - if (errMsg != nullptr) { - throw std::runtime_error(errMsg); - } + if (errMsg != nullptr) { + throw std::runtime_error(errMsg); + } #endif #ifdef OP_SQLITE_USE_SQLITE_VEC - const char *vec_entry_point = "sqlite3_vec_init"; + const char *vec_entry_point = "sqlite3_vec_init"; - sqlite3_load_extension(db, sqlite_vec_path.c_str(), vec_entry_point, &errMsg); + sqlite3_load_extension(db, sqlite_vec_path.c_str(), vec_entry_point, + &errMsg); - if (errMsg != nullptr) { - throw std::runtime_error(errMsg); - } + if (errMsg != nullptr) { + throw std::runtime_error(errMsg); + } #endif - TOKENIZER_LIST + TOKENIZER_LIST - return db; + return db; } void opsqlite_close(sqlite3 *db) { #ifdef OP_SQLITE_USE_CRSQLITE - opsqlite_execute(db, "select crsql_finalize();", nullptr); + opsqlite_execute(db, "select crsql_finalize();", nullptr); #endif - sqlite3_close_v2(db); + sqlite3_close_v2(db); } void opsqlite_attach(sqlite3 *db, std::string const &main_db_name, std::string const &doc_path, std::string const &secondary_db_name, std::string const &alias) { - auto secondary_db_path = opsqlite_get_db_path(secondary_db_name, doc_path); - auto statement = "ATTACH DATABASE '" + secondary_db_path + "' AS " + alias; + auto secondary_db_path = opsqlite_get_db_path(secondary_db_name, doc_path); + auto statement = "ATTACH DATABASE '" + secondary_db_path + "' AS " + alias; - opsqlite_execute(db, statement, nullptr); + opsqlite_execute(db, statement, nullptr); } void opsqlite_detach(sqlite3 *db, std::string const &main_db_name, std::string const &alias) { - std::string statement = "DETACH DATABASE " + alias; - opsqlite_execute(db, statement, nullptr); + std::string statement = "DETACH DATABASE " + alias; + opsqlite_execute(db, statement, nullptr); } void opsqlite_remove(sqlite3 *db, std::string const &name, std::string const &doc_path) { - opsqlite_close(db); + opsqlite_close(db); - std::string db_path = opsqlite_get_db_path(name, doc_path); + std::string db_path = opsqlite_get_db_path(name, doc_path); - if (!file_exists(db_path)) { - throw std::runtime_error("op-sqlite: db file not found:" + db_path); - } + if (!file_exists(db_path)) { + throw std::runtime_error("op-sqlite: db file not found:" + db_path); + } - remove(db_path.c_str()); + remove(db_path.c_str()); } BridgeResult opsqlite_execute_prepared_statement( sqlite3 *db, sqlite3_stmt *statement, std::vector *results, std::shared_ptr> &metadatas) { - const char *errorMessage; - - bool isConsuming = true; - bool isFailed = false; - - int result = SQLITE_OK; - - int i, count, column_type; - std::string column_name, column_declared_type; + const char *errorMessage; - while (isConsuming) { - result = sqlite3_step(statement); + bool isConsuming = true; + bool isFailed = false; - switch (result) { - case SQLITE_ROW: { - i = 0; - DumbHostObject row = DumbHostObject(metadatas); + int result = SQLITE_OK; - count = sqlite3_column_count(statement); - - while (i < count) { - column_type = sqlite3_column_type(statement, i); - - switch (column_type) { - case SQLITE_INTEGER: { - /** - * Warning this will loose precision because JS can - * only represent Integers up to 53 bits - */ - double column_value = sqlite3_column_double(statement, i); - row.values.emplace_back(column_value); - break; - } - - case SQLITE_FLOAT: { - double column_value = sqlite3_column_double(statement, i); - row.values.emplace_back(column_value); - break; - } + int i, count, column_type; + std::string column_name, column_declared_type; - case SQLITE_TEXT: { - const char *column_value = - reinterpret_cast(sqlite3_column_text(statement, i)); - int byteLen = sqlite3_column_bytes(statement, i); - // Specify length too; in case string contains NULL in the middle - row.values.emplace_back(std::string(column_value, byteLen)); - break; - } + while (isConsuming) { + result = sqlite3_step(statement); + + switch (result) { + case SQLITE_ROW: { + i = 0; + DumbHostObject row = DumbHostObject(metadatas); + + count = sqlite3_column_count(statement); + + while (i < count) { + column_type = sqlite3_column_type(statement, i); + + switch (column_type) { + case SQLITE_INTEGER: { + /** + * Warning this will loose precision because JS can + * only represent Integers up to 53 bits + */ + double column_value = sqlite3_column_double(statement, i); + row.values.emplace_back(column_value); + break; + } + + case SQLITE_FLOAT: { + double column_value = sqlite3_column_double(statement, i); + row.values.emplace_back(column_value); + break; + } + + case SQLITE_TEXT: { + const char *column_value = reinterpret_cast( + sqlite3_column_text(statement, i)); + int byteLen = sqlite3_column_bytes(statement, i); + // Specify length too; in case string contains NULL in the + // middle + row.values.emplace_back(std::string(column_value, byteLen)); + break; + } + + case SQLITE_BLOB: { + int blob_size = sqlite3_column_bytes(statement, i); + const void *blob = sqlite3_column_blob(statement, i); + auto *data = new uint8_t[blob_size]; + // You cannot share raw memory between native and JS + // always copy the data + memcpy(data, blob, blob_size); + row.values.emplace_back( + ArrayBuffer{.data = std::shared_ptr{data}, + .size = static_cast(blob_size)}); + break; + } + + case SQLITE_NULL: + // Intentionally left blank + + default: + row.values.emplace_back(nullptr); + break; + } + i++; + } + + results->emplace_back(row); - case SQLITE_BLOB: { - int blob_size = sqlite3_column_bytes(statement, i); - const void *blob = sqlite3_column_blob(statement, i); - auto *data = new uint8_t[blob_size]; - // You cannot share raw memory between native and JS - // always copy the data - memcpy(data, blob, blob_size); - row.values.emplace_back( - ArrayBuffer{.data = std::shared_ptr{data}, - .size = static_cast(blob_size)}); - break; + break; } - case SQLITE_NULL: - // Intentionally left blank + case SQLITE_DONE: + if (metadatas != nullptr) { + i = 0; + count = sqlite3_column_count(statement); + + while (i < count) { + column_name = sqlite3_column_name(statement, i); + const char *type = sqlite3_column_decltype(statement, i); + auto metadata = SmartHostObject(); + metadata.fields.emplace_back("name", column_name); + metadata.fields.emplace_back("index", i); + metadata.fields.emplace_back( + "type", type == nullptr ? "UNKNOWN" : type); + + metadatas->emplace_back(metadata); + i++; + } + } + isConsuming = false; + break; default: - row.values.emplace_back(nullptr); - break; + errorMessage = sqlite3_errmsg(db); + isFailed = true; + isConsuming = false; } - i++; - } - - results->emplace_back(row); - - break; } - case SQLITE_DONE: - if (metadatas != nullptr) { - i = 0; - count = sqlite3_column_count(statement); - - while (i < count) { - column_name = sqlite3_column_name(statement, i); - const char *type = sqlite3_column_decltype(statement, i); - auto metadata = SmartHostObject(); - metadata.fields.emplace_back("name", column_name); - metadata.fields.emplace_back("index", i); - metadata.fields.emplace_back("type", - type == nullptr ? "UNKNOWN" : type); - - metadatas->emplace_back(metadata); - i++; - } - } - isConsuming = false; - break; + sqlite3_reset(statement); - default: - errorMessage = sqlite3_errmsg(db); - isFailed = true; - isConsuming = false; + if (isFailed) { + throw std::runtime_error( + "[op-sqlite] SQLite code: " + std::to_string(result) + + " execution error: " + std::string(errorMessage)); } - } - - sqlite3_reset(statement); - - if (isFailed) { - throw std::runtime_error( - "[op-sqlite] SQLite code: " + std::to_string(result) + - " execution error: " + std::string(errorMessage)); - } - int changedRowCount = sqlite3_changes(db); - long long latestInsertRowId = sqlite3_last_insert_rowid(db); + int changedRowCount = sqlite3_changes(db); + long long latestInsertRowId = sqlite3_last_insert_rowid(db); - return {.affectedRows = changedRowCount, - .insertId = static_cast(latestInsertRowId)}; + return {.affectedRows = changedRowCount, + .insertId = static_cast(latestInsertRowId)}; } sqlite3_stmt *opsqlite_prepare_statement(sqlite3 *db, std::string const &query) { - sqlite3_stmt *statement; + sqlite3_stmt *statement; - const char *queryStr = query.c_str(); + const char *queryStr = query.c_str(); - int statementStatus = - sqlite3_prepare_v2(db, queryStr, -1, &statement, nullptr); + int statementStatus = + sqlite3_prepare_v2(db, queryStr, -1, &statement, nullptr); - if (statementStatus == SQLITE_ERROR) { - const char *message = sqlite3_errmsg(db); - throw std::runtime_error("[op-sqlite] SQL prepare statement error: " + - std::string(message)); - } + if (statementStatus == SQLITE_ERROR) { + const char *message = sqlite3_errmsg(db); + throw std::runtime_error("[op-sqlite] SQL prepare statement error: " + + std::string(message)); + } - return statement; + return statement; } BridgeResult opsqlite_execute(sqlite3 *db, std::string const &query, const std::vector *params) { - sqlite3_stmt *statement; - const char *errorMessage = nullptr; - const char *remainingStatement = nullptr; - bool has_failed = false; - int status, current_column, column_count, column_type; - std::string column_name, column_declared_type; - std::vector column_names; - std::vector> rows; - rows.reserve(20); - std::vector row; - - do { - const char *query_str = - remainingStatement == nullptr ? query.c_str() : remainingStatement; - - status = - sqlite3_prepare_v2(db, query_str, -1, &statement, &remainingStatement); - - if (status != SQLITE_OK) { - errorMessage = sqlite3_errmsg(db); - throw std::runtime_error("[op-sqlite] sqlite query error: " + - std::string(errorMessage)); - } - - // The statement did not fail to parse but there is nothing to do, just - // skip to the end - if (statement == nullptr) { - continue; - } - - if (params != nullptr && !params->empty()) { - opsqlite_bind_statement(statement, params); - } - - column_count = sqlite3_column_count(statement); - column_names.reserve(column_count); - bool is_consuming_rows = true; - double double_value; - const char *string_value; - - // Do a first pass to get the column names - for (int i = 0; i < column_count; i++) { - column_name = sqlite3_column_name(statement, i); - column_names.emplace_back(column_name); - } + sqlite3_stmt *statement; + const char *errorMessage = nullptr; + const char *remainingStatement = nullptr; + bool has_failed = false; + int status, current_column, column_count, column_type; + std::string column_name, column_declared_type; + std::vector column_names; + std::vector> rows; + rows.reserve(20); + std::vector row; + + do { + const char *query_str = + remainingStatement == nullptr ? query.c_str() : remainingStatement; + + status = sqlite3_prepare_v2(db, query_str, -1, &statement, + &remainingStatement); + + if (status != SQLITE_OK) { + errorMessage = sqlite3_errmsg(db); + throw std::runtime_error("[op-sqlite] sqlite query error: " + + std::string(errorMessage)); + } - while (is_consuming_rows) { - status = sqlite3_step(statement); + // The statement did not fail to parse but there is nothing to do, just + // skip to the end + if (statement == nullptr) { + continue; + } - switch (status) { - case SQLITE_ROW: - current_column = 0; - row = std::vector(); - row.reserve(column_count); + if (params != nullptr && !params->empty()) { + opsqlite_bind_statement(statement, params); + } - while (current_column < column_count) { - column_type = sqlite3_column_type(statement, current_column); + column_count = sqlite3_column_count(statement); + column_names.reserve(column_count); + bool is_consuming_rows = true; + double double_value; + const char *string_value; - switch (column_type) { + // Do a first pass to get the column names + for (int i = 0; i < column_count; i++) { + column_name = sqlite3_column_name(statement, i); + column_names.emplace_back(column_name); + } - case SQLITE_INTEGER: - // intentional fallthrough - case SQLITE_FLOAT: { - double_value = sqlite3_column_double(statement, current_column); - row.emplace_back(double_value); - break; - } - - case SQLITE_TEXT: { - string_value = reinterpret_cast( - sqlite3_column_text(statement, current_column)); - int len = sqlite3_column_bytes(statement, current_column); - // Specify length too; in case string contains NULL in the middle - row.emplace_back(std::string(string_value, len)); - break; - } - - case SQLITE_BLOB: { - int blob_size = sqlite3_column_bytes(statement, current_column); - const void *blob = sqlite3_column_blob(statement, current_column); - auto *data = new uint8_t[blob_size]; - memcpy(data, blob, blob_size); - row.emplace_back( - ArrayBuffer{.data = std::shared_ptr{data}, + while (is_consuming_rows) { + status = sqlite3_step(statement); + + switch (status) { + case SQLITE_ROW: + current_column = 0; + row = std::vector(); + row.reserve(column_count); + + while (current_column < column_count) { + column_type = + sqlite3_column_type(statement, current_column); + + switch (column_type) { + + case SQLITE_INTEGER: + // intentional fallthrough + case SQLITE_FLOAT: { + double_value = + sqlite3_column_double(statement, current_column); + row.emplace_back(double_value); + break; + } + + case SQLITE_TEXT: { + string_value = reinterpret_cast( + sqlite3_column_text(statement, current_column)); + int len = + sqlite3_column_bytes(statement, current_column); + // Specify length too; in case string contains NULL in + // the middle + row.emplace_back(std::string(string_value, len)); + break; + } + + case SQLITE_BLOB: { + int blob_size = + sqlite3_column_bytes(statement, current_column); + const void *blob = + sqlite3_column_blob(statement, current_column); + auto *data = new uint8_t[blob_size]; + memcpy(data, blob, blob_size); + row.emplace_back(ArrayBuffer{ + .data = std::shared_ptr{data}, .size = static_cast(blob_size)}); - break; - } - - case SQLITE_NULL: - // Intentionally left blank to switch to default case - default: - row.emplace_back(nullptr); - break; - } - - current_column++; + break; + } + + case SQLITE_NULL: + // Intentionally left blank to switch to default case + default: + row.emplace_back(nullptr); + break; + } + + current_column++; + } + + rows.emplace_back(std::move(row)); + break; + + case SQLITE_DONE: + is_consuming_rows = false; + break; + + default: + has_failed = true; + is_consuming_rows = false; + } } - rows.emplace_back(std::move(row)); - break; + sqlite3_finalize(statement); + } while (remainingStatement != nullptr && + strcmp(remainingStatement, "") != 0 && !has_failed); - case SQLITE_DONE: - is_consuming_rows = false; - break; - - default: - has_failed = true; - is_consuming_rows = false; - } + if (has_failed) { + const char *message = sqlite3_errmsg(db); + throw std::runtime_error("[op-sqlite] statement execution error: " + + std::string(message)); } - sqlite3_finalize(statement); - } while (remainingStatement != nullptr && - strcmp(remainingStatement, "") != 0 && !has_failed); - - if (has_failed) { - const char *message = sqlite3_errmsg(db); - throw std::runtime_error("[op-sqlite] statement execution error: " + - std::string(message)); - } - - int changedRowCount = sqlite3_changes(db); - long long latestInsertRowId = sqlite3_last_insert_rowid(db); - return {.affectedRows = changedRowCount, - .insertId = static_cast(latestInsertRowId), - .rows = std::move(rows), - .column_names = std::move(column_names)}; + int changedRowCount = sqlite3_changes(db); + long long latestInsertRowId = sqlite3_last_insert_rowid(db); + return {.affectedRows = changedRowCount, + .insertId = static_cast(latestInsertRowId), + .rows = std::move(rows), + .column_names = std::move(column_names)}; } BridgeResult opsqlite_execute_host_objects( @@ -452,156 +464,162 @@ BridgeResult opsqlite_execute_host_objects( std::vector *results, std::shared_ptr> &metadatas) { - sqlite3_stmt *statement; - const char *errorMessage; - const char *remainingStatement = nullptr; + sqlite3_stmt *statement; + const char *errorMessage; + const char *remainingStatement = nullptr; - bool isConsuming = true; - bool isFailed = false; + bool isConsuming = true; + bool isFailed = false; - int result = SQLITE_OK; + int result = SQLITE_OK; - do { - const char *queryStr = - remainingStatement == nullptr ? query.c_str() : remainingStatement; + do { + const char *queryStr = + remainingStatement == nullptr ? query.c_str() : remainingStatement; - int statementStatus = - sqlite3_prepare_v2(db, queryStr, -1, &statement, &remainingStatement); - - if (statementStatus != SQLITE_OK) { - const char *message = sqlite3_errmsg(db); - throw std::runtime_error( - "[op-sqlite] SQL statement error on opsqlite_execute:\n" + - std::to_string(statementStatus) + " description:\n" + - std::string(message)); - } - - // The statement did not fail to parse but there is nothing to do, just - // skip to the end - if (statement == nullptr) { - continue; - } + int statementStatus = sqlite3_prepare_v2(db, queryStr, -1, &statement, + &remainingStatement); - if (params != nullptr && !params->empty()) { - opsqlite_bind_statement(statement, params); - } - - int i, count, column_type; - std::string column_name, column_declared_type; - - while (isConsuming) { - result = sqlite3_step(statement); - - switch (result) { - case SQLITE_ROW: { - if (results == nullptr) { - break; + if (statementStatus != SQLITE_OK) { + const char *message = sqlite3_errmsg(db); + throw std::runtime_error( + "[op-sqlite] SQL statement error on opsqlite_execute:\n" + + std::to_string(statementStatus) + " description:\n" + + std::string(message)); } - i = 0; - DumbHostObject row = DumbHostObject(metadatas); - - count = sqlite3_column_count(statement); - - while (i < count) { - column_type = sqlite3_column_type(statement, i); + // The statement did not fail to parse but there is nothing to do, just + // skip to the end + if (statement == nullptr) { + continue; + } - switch (column_type) { - case SQLITE_INTEGER: { - /** - * Warning this will loose precision because JS can - * only represent Integers up to 53 bits - */ - double column_value = sqlite3_column_double(statement, i); - row.values.emplace_back(column_value); - break; - } + if (params != nullptr && !params->empty()) { + opsqlite_bind_statement(statement, params); + } - case SQLITE_FLOAT: { - double column_value = sqlite3_column_double(statement, i); - row.values.emplace_back(column_value); - break; - } - - case SQLITE_TEXT: { - const char *column_value = reinterpret_cast( - sqlite3_column_text(statement, i)); - int byteLen = sqlite3_column_bytes(statement, i); - // Specify length too; in case string contains NULL in the middle - row.values.emplace_back(std::string(column_value, byteLen)); - break; - } - - case SQLITE_BLOB: { - int blob_size = sqlite3_column_bytes(statement, i); - const void *blob = sqlite3_column_blob(statement, i); - auto *data = new uint8_t[blob_size]; - // You cannot share raw memory between native and JS - // always copy the data - memcpy(data, blob, blob_size); - row.values.emplace_back( - ArrayBuffer{.data = std::shared_ptr{data}, + int i, count, column_type; + std::string column_name, column_declared_type; + + while (isConsuming) { + result = sqlite3_step(statement); + + switch (result) { + case SQLITE_ROW: { + if (results == nullptr) { + break; + } + + i = 0; + DumbHostObject row = DumbHostObject(metadatas); + + count = sqlite3_column_count(statement); + + while (i < count) { + column_type = sqlite3_column_type(statement, i); + + switch (column_type) { + case SQLITE_INTEGER: { + /** + * Warning this will loose precision because JS can + * only represent Integers up to 53 bits + */ + double column_value = + sqlite3_column_double(statement, i); + row.values.emplace_back(column_value); + break; + } + + case SQLITE_FLOAT: { + double column_value = + sqlite3_column_double(statement, i); + row.values.emplace_back(column_value); + break; + } + + case SQLITE_TEXT: { + const char *column_value = + reinterpret_cast( + sqlite3_column_text(statement, i)); + int byteLen = sqlite3_column_bytes(statement, i); + // Specify length too; in case string contains NULL in + // the middle + row.values.emplace_back( + std::string(column_value, byteLen)); + break; + } + + case SQLITE_BLOB: { + int blob_size = sqlite3_column_bytes(statement, i); + const void *blob = sqlite3_column_blob(statement, i); + auto *data = new uint8_t[blob_size]; + // You cannot share raw memory between native and JS + // always copy the data + memcpy(data, blob, blob_size); + row.values.emplace_back(ArrayBuffer{ + .data = std::shared_ptr{data}, .size = static_cast(blob_size)}); - break; - } - - case SQLITE_NULL: - // Intentionally left blank - - default: - row.values.emplace_back(nullptr); - break; - } - i++; + break; + } + + case SQLITE_NULL: + // Intentionally left blank + + default: + row.values.emplace_back(nullptr); + break; + } + i++; + } + + results->emplace_back(row); + break; + } + + case SQLITE_DONE: + if (metadatas != nullptr) { + i = 0; + count = sqlite3_column_count(statement); + + while (i < count) { + column_name = sqlite3_column_name(statement, i); + const char *type = + sqlite3_column_decltype(statement, i); + auto metadata = SmartHostObject(); + metadata.fields.emplace_back("name", column_name); + metadata.fields.emplace_back("index", i); + metadata.fields.emplace_back( + "type", type == nullptr ? "UNKNOWN" : type); + + metadatas->push_back(metadata); + i++; + } + } + isConsuming = false; + break; + + default: + errorMessage = sqlite3_errmsg(db); + isFailed = true; + isConsuming = false; + } } - results->emplace_back(row); - break; - } - - case SQLITE_DONE: - if (metadatas != nullptr) { - i = 0; - count = sqlite3_column_count(statement); + sqlite3_finalize(statement); + } while (remainingStatement != nullptr && + strcmp(remainingStatement, "") != 0 && !isFailed); - while (i < count) { - column_name = sqlite3_column_name(statement, i); - const char *type = sqlite3_column_decltype(statement, i); - auto metadata = SmartHostObject(); - metadata.fields.emplace_back("name", column_name); - metadata.fields.emplace_back("index", i); - metadata.fields.emplace_back("type", - type == nullptr ? "UNKNOWN" : type); - - metadatas->push_back(metadata); - i++; - } - } - isConsuming = false; - break; - - default: - errorMessage = sqlite3_errmsg(db); - isFailed = true; - isConsuming = false; - } + if (isFailed) { + throw std::runtime_error( + "[op-sqlite] SQLite error code: " + std::to_string(result) + + ", description: " + std::string(errorMessage)); } - sqlite3_finalize(statement); - } while (remainingStatement != nullptr && - strcmp(remainingStatement, "") != 0 && !isFailed); - - if (isFailed) { - throw std::runtime_error( - "[op-sqlite] SQLite error code: " + std::to_string(result) + - ", description: " + std::string(errorMessage)); - } + int changedRowCount = sqlite3_changes(db); + long long latestInsertRowId = sqlite3_last_insert_rowid(db); - int changedRowCount = sqlite3_changes(db); - long long latestInsertRowId = sqlite3_last_insert_rowid(db); - - return {.affectedRows = changedRowCount, - .insertId = static_cast(latestInsertRowId)}; + return {.affectedRows = changedRowCount, + .insertId = static_cast(latestInsertRowId)}; } /// Executes returning data in raw arrays, a small performance optimization @@ -610,244 +628,248 @@ BridgeResult opsqlite_execute_raw(sqlite3 *db, std::string const &query, const std::vector *params, std::vector> *results) { - sqlite3_stmt *statement; - const char *errorMessage; - const char *remainingStatement = nullptr; - - bool isConsuming = true; - bool isFailed = false; + sqlite3_stmt *statement; + const char *errorMessage; + const char *remainingStatement = nullptr; - int step = SQLITE_OK; + bool isConsuming = true; + bool isFailed = false; - do { - const char *queryStr = - remainingStatement == nullptr ? query.c_str() : remainingStatement; + int step = SQLITE_OK; - int statementStatus = - sqlite3_prepare_v2(db, queryStr, -1, &statement, &remainingStatement); + do { + const char *queryStr = + remainingStatement == nullptr ? query.c_str() : remainingStatement; - if (statementStatus != SQLITE_OK) { - const char *message = sqlite3_errmsg(db); - throw std::runtime_error( - "[op-sqlite] SQL statement error:" + std::to_string(statementStatus) + - " description:" + std::string(message)); - } + int statementStatus = sqlite3_prepare_v2(db, queryStr, -1, &statement, + &remainingStatement); - // The statement did not fail to parse but there is nothing to do, just - // skip to the end - if (statement == nullptr) { - continue; - } - - if (params != nullptr && !params->empty()) { - opsqlite_bind_statement(statement, params); - } - - int i, column_type; - std::string column_name, column_declared_type; - - int column_count = sqlite3_column_count(statement); - - while (isConsuming) { - step = sqlite3_step(statement); - - switch (step) { - case SQLITE_ROW: { - if (results == nullptr) { - break; + if (statementStatus != SQLITE_OK) { + const char *message = sqlite3_errmsg(db); + throw std::runtime_error("[op-sqlite] SQL statement error:" + + std::to_string(statementStatus) + + " description:" + std::string(message)); } - std::vector row; - row.reserve(column_count); - - i = 0; + // The statement did not fail to parse but there is nothing to do, just + // skip to the end + if (statement == nullptr) { + continue; + } - while (i < column_count) { - column_type = sqlite3_column_type(statement, i); + if (params != nullptr && !params->empty()) { + opsqlite_bind_statement(statement, params); + } - switch (column_type) { - case SQLITE_INTEGER: - case SQLITE_FLOAT: { - double column_value = sqlite3_column_double(statement, i); - row.emplace_back(column_value); - break; - } - - case SQLITE_TEXT: { - const char *column_value = reinterpret_cast( - sqlite3_column_text(statement, i)); - int byteLen = sqlite3_column_bytes(statement, i); - // Specify length too; in case string contains NULL in the middle - row.emplace_back(std::string(column_value, byteLen)); - break; - } - - case SQLITE_BLOB: { - int blob_size = sqlite3_column_bytes(statement, i); - const void *blob = sqlite3_column_blob(statement, i); - auto *data = new uint8_t[blob_size]; - memcpy(data, blob, blob_size); - row.emplace_back( - ArrayBuffer{.data = std::shared_ptr{data}, + int i, column_type; + std::string column_name, column_declared_type; + + int column_count = sqlite3_column_count(statement); + + while (isConsuming) { + step = sqlite3_step(statement); + + switch (step) { + case SQLITE_ROW: { + if (results == nullptr) { + break; + } + + std::vector row; + row.reserve(column_count); + + i = 0; + + while (i < column_count) { + column_type = sqlite3_column_type(statement, i); + + switch (column_type) { + case SQLITE_INTEGER: + case SQLITE_FLOAT: { + double column_value = + sqlite3_column_double(statement, i); + row.emplace_back(column_value); + break; + } + + case SQLITE_TEXT: { + const char *column_value = + reinterpret_cast( + sqlite3_column_text(statement, i)); + int byteLen = sqlite3_column_bytes(statement, i); + // Specify length too; in case string contains NULL in + // the middle + row.emplace_back(std::string(column_value, byteLen)); + break; + } + + case SQLITE_BLOB: { + int blob_size = sqlite3_column_bytes(statement, i); + const void *blob = sqlite3_column_blob(statement, i); + auto *data = new uint8_t[blob_size]; + memcpy(data, blob, blob_size); + row.emplace_back(ArrayBuffer{ + .data = std::shared_ptr{data}, .size = static_cast(blob_size)}); - break; - } - - case SQLITE_NULL: - // intentional fallthrough - default: - row.emplace_back(nullptr); - break; - } - i++; + break; + } + + case SQLITE_NULL: + // intentional fallthrough + default: + row.emplace_back(nullptr); + break; + } + i++; + } + + results->emplace_back(row); + + break; + } + + case SQLITE_DONE: + isConsuming = false; + break; + + default: + errorMessage = sqlite3_errmsg(db); + isFailed = true; + isConsuming = false; + } } - results->emplace_back(row); + sqlite3_finalize(statement); + } while (remainingStatement != nullptr && + strcmp(remainingStatement, "") != 0 && !isFailed); - break; - } - - case SQLITE_DONE: - isConsuming = false; - break; - - default: - errorMessage = sqlite3_errmsg(db); - isFailed = true; - isConsuming = false; - } + if (isFailed) { + throw std::runtime_error( + "[op-sqlite] SQLite error code: " + std::to_string(step) + + ", description: " + std::string(errorMessage)); } - sqlite3_finalize(statement); - } while (remainingStatement != nullptr && - strcmp(remainingStatement, "") != 0 && !isFailed); - - if (isFailed) { - throw std::runtime_error( - "[op-sqlite] SQLite error code: " + std::to_string(step) + - ", description: " + std::string(errorMessage)); - } - - int changedRowCount = sqlite3_changes(db); - long long latestInsertRowId = sqlite3_last_insert_rowid(db); + int changedRowCount = sqlite3_changes(db); + long long latestInsertRowId = sqlite3_last_insert_rowid(db); - return {.affectedRows = changedRowCount, - .insertId = static_cast(latestInsertRowId)}; + return {.affectedRows = changedRowCount, + .insertId = static_cast(latestInsertRowId)}; } std::string operation_to_string(int operation_type) { - switch (operation_type) { - case SQLITE_INSERT: - return "INSERT"; + switch (operation_type) { + case SQLITE_INSERT: + return "INSERT"; - case SQLITE_DELETE: - return "DELETE"; + case SQLITE_DELETE: + return "DELETE"; - case SQLITE_UPDATE: - return "UPDATE"; + case SQLITE_UPDATE: + return "UPDATE"; - default: - throw std::runtime_error("Unknown SQLite operation on hook"); - } + default: + throw std::runtime_error("Unknown SQLite operation on hook"); + } } void update_callback(void *db_host_object_ptr, int operation_type, [[maybe_unused]] char const *database, char const *table, sqlite3_int64 row_id) { - auto db_host_object = reinterpret_cast(db_host_object_ptr); - db_host_object->on_update(std::string(table), - operation_to_string(operation_type), row_id); + auto db_host_object = reinterpret_cast(db_host_object_ptr); + db_host_object->on_update(std::string(table), + operation_to_string(operation_type), row_id); } void opsqlite_register_update_hook(sqlite3 *db, void *db_host_object) { - sqlite3_update_hook(db, &update_callback, (void *)db_host_object); + sqlite3_update_hook(db, &update_callback, (void *)db_host_object); } void opsqlite_deregister_update_hook(sqlite3 *db) { - sqlite3_update_hook(db, nullptr, nullptr); + sqlite3_update_hook(db, nullptr, nullptr); } int commit_callback(void *db_host_object_ptr) { - auto db_host_object = reinterpret_cast(db_host_object_ptr); - db_host_object->on_commit(); - return 0; + auto db_host_object = reinterpret_cast(db_host_object_ptr); + db_host_object->on_commit(); + return 0; } void opsqlite_register_commit_hook(sqlite3 *db, void *db_host_object_ptr) { - sqlite3_commit_hook(db, &commit_callback, db_host_object_ptr); + sqlite3_commit_hook(db, &commit_callback, db_host_object_ptr); } void opsqlite_deregister_commit_hook(sqlite3 *db) { - sqlite3_commit_hook(db, nullptr, nullptr); + sqlite3_commit_hook(db, nullptr, nullptr); } void rollback_callback(void *db_host_object_ptr) { - auto db_host_object = reinterpret_cast(db_host_object_ptr); - db_host_object->on_rollback(); + auto db_host_object = reinterpret_cast(db_host_object_ptr); + db_host_object->on_rollback(); } void opsqlite_register_rollback_hook(sqlite3 *db, void *db_host_object_ptr) { - sqlite3_rollback_hook(db, &rollback_callback, db_host_object_ptr); + sqlite3_rollback_hook(db, &rollback_callback, db_host_object_ptr); } void opsqlite_deregister_rollback_hook(sqlite3 *db) { - sqlite3_rollback_hook(db, nullptr, nullptr); + sqlite3_rollback_hook(db, nullptr, nullptr); } void opsqlite_load_extension(sqlite3 *db, std::string &path, std::string &entry_point) { #ifdef OP_SQLITE_USE_PHONE_VERSION - throw std::runtime_error("[op-sqlite] Embedded version of SQLite does not " - "support loading extensions"); + throw std::runtime_error("[op-sqlite] Embedded version of SQLite does not " + "support loading extensions"); #else - int status = 0; - status = sqlite3_enable_load_extension(db, 1); + int status = 0; + status = sqlite3_enable_load_extension(db, 1); - if (status != SQLITE_OK) { - throw std::runtime_error("Could not enable extension loading"); - } + if (status != SQLITE_OK) { + throw std::runtime_error("Could not enable extension loading"); + } - const char *entry_point_cstr = nullptr; - if (!entry_point.empty()) { - entry_point_cstr = entry_point.c_str(); - } + const char *entry_point_cstr = nullptr; + if (!entry_point.empty()) { + entry_point_cstr = entry_point.c_str(); + } - char *error_message; + char *error_message; - status = sqlite3_load_extension(db, path.c_str(), entry_point_cstr, - &error_message); - if (status != SQLITE_OK) { - throw std::runtime_error(error_message); - } + status = sqlite3_load_extension(db, path.c_str(), entry_point_cstr, + &error_message); + if (status != SQLITE_OK) { + throw std::runtime_error(error_message); + } #endif } -BatchResult opsqlite_execute_batch(sqlite3 *db, - const std::vector *commands) { - size_t commandCount = commands->size(); - if (commandCount <= 0) { - throw std::runtime_error("No SQL commands provided"); - } - - int affectedRows = 0; - opsqlite_execute(db, "BEGIN EXCLUSIVE TRANSACTION", nullptr); - for (int i = 0; i < commandCount; i++) { - const auto &command = commands->at(i); - // We do not provide a datastructure to receive query data because we - // don't need/want to handle this results in a batch execution - try { - auto result = opsqlite_execute(db, command.sql, &command.params); - affectedRows += result.affectedRows; - } catch (std::exception &exc) { - opsqlite_execute(db, "ROLLBACK", nullptr); - throw exc; +BatchResult +opsqlite_execute_batch(sqlite3 *db, + const std::vector *commands) { + size_t commandCount = commands->size(); + if (commandCount <= 0) { + throw std::runtime_error("No SQL commands provided"); + } + + int affectedRows = 0; + opsqlite_execute(db, "BEGIN EXCLUSIVE TRANSACTION", nullptr); + for (int i = 0; i < commandCount; i++) { + const auto &command = commands->at(i); + // We do not provide a datastructure to receive query data because we + // don't need/want to handle this results in a batch execution + try { + auto result = opsqlite_execute(db, command.sql, &command.params); + affectedRows += result.affectedRows; + } catch (std::exception &exc) { + opsqlite_execute(db, "ROLLBACK", nullptr); + throw exc; + } } - } - opsqlite_execute(db, "COMMIT", nullptr); - return BatchResult{ - .affectedRows = affectedRows, - .commands = static_cast(commandCount), - }; + opsqlite_execute(db, "COMMIT", nullptr); + return BatchResult{ + .affectedRows = affectedRows, + .commands = static_cast(commandCount), + }; } } // namespace opsqlite diff --git a/cpp/bridge.h b/cpp/bridge.h index 65c0726..4e662d6 100644 --- a/cpp/bridge.h +++ b/cpp/bridge.h @@ -23,9 +23,9 @@ std::string opsqlite_get_db_path(std::string const &db_name, #ifdef OP_SQLITE_USE_SQLCIPHER sqlite3 *opsqlite_open(std::string const &dbName, std::string const &path, - std::string const &crsqlite_path, - std::string const &sqlite_vec_path, - std::string const &encryption_key); + std::string const &crsqlite_path, + std::string const &sqlite_vec_path, + std::string const &encryption_key); #else sqlite3 *opsqlite_open(std::string const &name, std::string const &path, [[maybe_unused]] std::string const &crsqlite_path, diff --git a/cpp/libsql/bridge.cpp b/cpp/libsql/bridge.cpp index 6426ebb..8d64341 100644 --- a/cpp/libsql/bridge.cpp +++ b/cpp/libsql/bridge.cpp @@ -22,446 +22,448 @@ namespace opsqlite { std::string opsqlite_get_db_path(std::string const &db_name, std::string const &location) { - if (location == ":memory:") { - return location; - } + if (location == ":memory:") { + return location; + } - // Will return false if the directory already exists, no need to check - std::filesystem::create_directories(location); + // Will return false if the directory already exists, no need to check + std::filesystem::create_directories(location); - if (!location.empty() && location.back() != '/') { - return location + "/" + db_name; - } + if (!location.empty() && location.back() != '/') { + return location + "/" + db_name; + } - return location + db_name; + return location + db_name; } DB opsqlite_libsql_open_sync(std::string const &name, std::string const &base_path, std::string const &url, std::string const &auth_token, int sync_interval) { - std::string path = opsqlite_get_db_path(name, base_path); - - int status; - libsql_database_t db; - libsql_connection_t c; - const char *err = nullptr; - - libsql_config config = {.db_path = path.c_str(), - .primary_url = url.c_str(), - .auth_token = auth_token.c_str(), - .read_your_writes = '1', - .encryption_key = nullptr, - .sync_interval = sync_interval, - .with_webpki = '1'}; - status = libsql_open_sync_with_config(config, &db, &err); - if (status != 0) { - throw std::runtime_error(err); - } - - status = libsql_connect(db, &c, &err); - - if (status != 0) { - throw std::runtime_error(err); - } - - return {.db = db, .c = c}; + std::string path = opsqlite_get_db_path(name, base_path); + + int status; + libsql_database_t db; + libsql_connection_t c; + const char *err = nullptr; + + libsql_config config = {.db_path = path.c_str(), + .primary_url = url.c_str(), + .auth_token = auth_token.c_str(), + .read_your_writes = '1', + .encryption_key = nullptr, + .sync_interval = sync_interval, + .with_webpki = '1'}; + status = libsql_open_sync_with_config(config, &db, &err); + if (status != 0) { + throw std::runtime_error(err); + } + + status = libsql_connect(db, &c, &err); + + if (status != 0) { + throw std::runtime_error(err); + } + + return {.db = db, .c = c}; } DB opsqlite_libsql_open(std::string const &name, std::string const &last_path, std::string const &crsqlitePath) { - std::string path = opsqlite_get_db_path(name, last_path); + std::string path = opsqlite_get_db_path(name, last_path); - int status; - libsql_database_t db; - libsql_connection_t c; - const char *err = nullptr; + int status; + libsql_database_t db; + libsql_connection_t c; + const char *err = nullptr; - status = libsql_open_file(path.c_str(), &db, &err); + status = libsql_open_file(path.c_str(), &db, &err); - if (status != 0) { - throw std::runtime_error(err); - } + if (status != 0) { + throw std::runtime_error(err); + } - status = libsql_connect(db, &c, &err); + status = libsql_connect(db, &c, &err); - if (status != 0) { - throw std::runtime_error(err); - } + if (status != 0) { + throw std::runtime_error(err); + } #ifdef OP_SQLITE_USE_CRSQLITE - const char *errMsg; - const char *crsqliteEntryPoint = "sqlite3_crsqlite_init"; + const char *errMsg; + const char *crsqliteEntryPoint = "sqlite3_crsqlite_init"; - status = libsql_load_extension(c, crsqlitePath.c_str(), crsqliteEntryPoint, - &errMsg); + status = libsql_load_extension(c, crsqlitePath.c_str(), crsqliteEntryPoint, + &errMsg); - if (status != 0) { - return {.type = SQLiteError, .message = errMsg}; - } else { - LOGI("Loaded CRSQlite successfully"); - } + if (status != 0) { + return {.type = SQLiteError, .message = errMsg}; + } else { + LOGI("Loaded CRSQlite successfully"); + } #endif - return {.db = db, .c = c}; + return {.db = db, .c = c}; } DB opsqlite_libsql_open_remote(std::string const &url, std::string const &auth_token) { - int status; - libsql_database_t db; - libsql_connection_t c; - const char *err = nullptr; + int status; + libsql_database_t db; + libsql_connection_t c; + const char *err = nullptr; - status = libsql_open_remote_with_webpki(url.c_str(), auth_token.c_str(), &db, - &err); + status = libsql_open_remote_with_webpki(url.c_str(), auth_token.c_str(), + &db, &err); - if (status != 0) { - throw std::runtime_error(err); - } + if (status != 0) { + throw std::runtime_error(err); + } - status = libsql_connect(db, &c, &err); + status = libsql_connect(db, &c, &err); - if (status != 0) { - throw std::runtime_error(err); - } + if (status != 0) { + throw std::runtime_error(err); + } - return {.db = db, .c = c}; + return {.db = db, .c = c}; } void opsqlite_libsql_close(DB &db) { - if (db.c != nullptr) { - libsql_disconnect(db.c); - db.c = nullptr; - } - if (db.db != nullptr) { - libsql_close(db.db); - db.db = nullptr; - } + if (db.c != nullptr) { + libsql_disconnect(db.c); + db.c = nullptr; + } + if (db.db != nullptr) { + libsql_close(db.db); + db.db = nullptr; + } } void opsqlite_libsql_attach(DB const &db, std::string const &docPath, std::string const &databaseToAttach, std::string const &alias) { - std::string dbPath = opsqlite_get_db_path(databaseToAttach, docPath); - std::string statement = "ATTACH DATABASE '" + dbPath + "' AS " + alias; + std::string dbPath = opsqlite_get_db_path(databaseToAttach, docPath); + std::string statement = "ATTACH DATABASE '" + dbPath + "' AS " + alias; - opsqlite_libsql_execute(db, statement, nullptr); + opsqlite_libsql_execute(db, statement, nullptr); } void opsqlite_libsql_detach(DB const &db, std::string const &alias) { - std::string statement = "DETACH DATABASE " + alias; - opsqlite_libsql_execute(db, statement, nullptr); + std::string statement = "DETACH DATABASE " + alias; + opsqlite_libsql_execute(db, statement, nullptr); } void opsqlite_libsql_sync(DB const &db) { - const char *err = nullptr; + const char *err = nullptr; - int status = libsql_sync(db.db, &err); + int status = libsql_sync(db.db, &err); - if (status != 0) { - throw std::runtime_error(err); - } + if (status != 0) { + throw std::runtime_error(err); + } } void opsqlite_libsql_remove(DB &db, std::string const &name, std::string const &path) { - opsqlite_libsql_close(db); + opsqlite_libsql_close(db); - std::string full_path = opsqlite_get_db_path(name, path); + std::string full_path = opsqlite_get_db_path(name, path); - if (!file_exists(full_path)) { - throw std::runtime_error("[op-sqlite]: Database file not found" + - full_path); - } + if (!file_exists(full_path)) { + throw std::runtime_error("[op-sqlite]: Database file not found" + + full_path); + } - remove(full_path.c_str()); + remove(full_path.c_str()); } void opsqlite_libsql_bind_statement(libsql_stmt_t statement, const std::vector *values) { - const char *err; - size_t size = values->size(); - - for (int ii = 0; ii < size; ii++) { - int index = ii + 1; - JSVariant value = values->at(ii); - int status; - - if (std::holds_alternative(value)) { - status = libsql_bind_int(statement, index, - static_cast(std::get(value)), &err); - } else if (std::holds_alternative(value)) { - status = libsql_bind_int(statement, index, std::get(value), &err); - } else if (std::holds_alternative(value)) { - status = - libsql_bind_int(statement, index, std::get(value), &err); - } else if (std::holds_alternative(value)) { - status = - libsql_bind_float(statement, index, std::get(value), &err); - } else if (std::holds_alternative(value)) { - std::string str = std::get(value); - status = libsql_bind_string(statement, index, str.c_str(), &err); - } else if (std::holds_alternative(value)) { - ArrayBuffer buffer = std::get(value); - status = libsql_bind_blob(statement, index, buffer.data.get(), - static_cast(buffer.size), &err); - } else { - status = libsql_bind_null(statement, index, &err); - } - - if (status != 0) { - throw std::runtime_error(err); + const char *err; + size_t size = values->size(); + + for (int ii = 0; ii < size; ii++) { + int index = ii + 1; + JSVariant value = values->at(ii); + int status; + + if (std::holds_alternative(value)) { + status = + libsql_bind_int(statement, index, + static_cast(std::get(value)), &err); + } else if (std::holds_alternative(value)) { + status = + libsql_bind_int(statement, index, std::get(value), &err); + } else if (std::holds_alternative(value)) { + status = libsql_bind_int(statement, index, + std::get(value), &err); + } else if (std::holds_alternative(value)) { + status = libsql_bind_float(statement, index, + std::get(value), &err); + } else if (std::holds_alternative(value)) { + std::string str = std::get(value); + status = libsql_bind_string(statement, index, str.c_str(), &err); + } else if (std::holds_alternative(value)) { + ArrayBuffer buffer = std::get(value); + status = libsql_bind_blob(statement, index, buffer.data.get(), + static_cast(buffer.size), &err); + } else { + status = libsql_bind_null(statement, index, &err); + } + + if (status != 0) { + throw std::runtime_error(err); + } } - } } BridgeResult opsqlite_libsql_execute_prepared_statement( DB const &db, libsql_stmt_t stmt, std::vector *results, const std::shared_ptr> &metadatas) { - libsql_rows_t rows; - libsql_row_t row; - - int status; - const char *err = nullptr; - - status = libsql_query_stmt(stmt, &rows, &err); - - if (status != 0) { - throw std::runtime_error(err); - } + libsql_rows_t rows; + libsql_row_t row; - bool metadata_set = false; + int status; + const char *err = nullptr; - int num_cols = libsql_column_count(rows); - while ((status = libsql_next_row(rows, &row, &err)) == 0) { + status = libsql_query_stmt(stmt, &rows, &err); - if (!err && !row) { - break; + if (status != 0) { + throw std::runtime_error(err); } - DumbHostObject row_host_object = DumbHostObject(metadatas); - - for (int col = 0; col < num_cols; col++) { - int type; - - libsql_column_type(rows, row, col, &type, &err); - - switch (type) { - case LIBSQL_INT: - long long int_value; - status = libsql_get_int(row, col, &int_value, &err); - row_host_object.values.emplace_back(int_value); - break; - - case LIBSQL_FLOAT: - double float_value; - status = libsql_get_float(row, col, &float_value, &err); - row_host_object.values.emplace_back(float_value); - break; - - case LIBSQL_TEXT: - const char *text_value; - status = libsql_get_string(row, col, &text_value, &err); - row_host_object.values.emplace_back(text_value); - break; - - case LIBSQL_BLOB: { - blob value_blob; - libsql_get_blob(row, col, &value_blob, &err); - auto *data = new uint8_t[value_blob.len]; - // You cannot share raw memory between native and JS - // always copy the data - memcpy(data, value_blob.ptr, value_blob.len); - libsql_free_blob(value_blob); - row_host_object.values.emplace_back( - ArrayBuffer{.data = std::shared_ptr{data}, - .size = static_cast(value_blob.len)}); - break; - } - - case LIBSQL_NULL: - // intentional fall-through - default: - row_host_object.values.emplace_back(nullptr); - break; - } - - if (status != 0) { - fprintf(stderr, "%s\n", err); - throw std::runtime_error("libsql error"); - } - - // On the first interation through the columns, set the metadata - if (!metadata_set && metadatas != nullptr) { - const char *col_name; - status = libsql_column_name(rows, col, &col_name, &err); - - auto metadata = SmartHostObject(); - metadata.fields.emplace_back("name", col_name); - metadata.fields.emplace_back("index", col); - metadata.fields.emplace_back("type", "UNKNOWN"); - // metadata.fields.push_back( - // std::make_pair("type", type == -1 ? "UNKNOWN" : - // type)); - - metadatas->push_back(metadata); - } + bool metadata_set = false; + + int num_cols = libsql_column_count(rows); + while ((status = libsql_next_row(rows, &row, &err)) == 0) { + + if (!err && !row) { + break; + } + + DumbHostObject row_host_object = DumbHostObject(metadatas); + + for (int col = 0; col < num_cols; col++) { + int type; + + libsql_column_type(rows, row, col, &type, &err); + + switch (type) { + case LIBSQL_INT: + long long int_value; + status = libsql_get_int(row, col, &int_value, &err); + row_host_object.values.emplace_back(int_value); + break; + + case LIBSQL_FLOAT: + double float_value; + status = libsql_get_float(row, col, &float_value, &err); + row_host_object.values.emplace_back(float_value); + break; + + case LIBSQL_TEXT: + const char *text_value; + status = libsql_get_string(row, col, &text_value, &err); + row_host_object.values.emplace_back(text_value); + break; + + case LIBSQL_BLOB: { + blob value_blob; + libsql_get_blob(row, col, &value_blob, &err); + auto *data = new uint8_t[value_blob.len]; + // You cannot share raw memory between native and JS + // always copy the data + memcpy(data, value_blob.ptr, value_blob.len); + libsql_free_blob(value_blob); + row_host_object.values.emplace_back( + ArrayBuffer{.data = std::shared_ptr{data}, + .size = static_cast(value_blob.len)}); + break; + } + + case LIBSQL_NULL: + // intentional fall-through + default: + row_host_object.values.emplace_back(nullptr); + break; + } + + if (status != 0) { + fprintf(stderr, "%s\n", err); + throw std::runtime_error("libsql error"); + } + + // On the first interation through the columns, set the metadata + if (!metadata_set && metadatas != nullptr) { + const char *col_name; + status = libsql_column_name(rows, col, &col_name, &err); + + auto metadata = SmartHostObject(); + metadata.fields.emplace_back("name", col_name); + metadata.fields.emplace_back("index", col); + metadata.fields.emplace_back("type", "UNKNOWN"); + // metadata.fields.push_back( + // std::make_pair("type", type == -1 ? + // "UNKNOWN" : type)); + + metadatas->push_back(metadata); + } + } + + if (results != nullptr) { + results->push_back(row_host_object); + } + + metadata_set = true; + err = nullptr; } - if (results != nullptr) { - results->push_back(row_host_object); + if (status != 0) { + fprintf(stderr, "%s\n", err); } - metadata_set = true; - err = nullptr; - } + libsql_free_rows(rows); - if (status != 0) { - fprintf(stderr, "%s\n", err); - } + unsigned long long changes = libsql_changes(db.c); + long long insert_row_id = libsql_last_insert_rowid(db.c); - libsql_free_rows(rows); + libsql_reset_stmt(stmt, &err); - unsigned long long changes = libsql_changes(db.c); - long long insert_row_id = libsql_last_insert_rowid(db.c); - - libsql_reset_stmt(stmt, &err); - - return {.affectedRows = static_cast(changes), - .insertId = static_cast(insert_row_id)}; + return {.affectedRows = static_cast(changes), + .insertId = static_cast(insert_row_id)}; } libsql_stmt_t opsqlite_libsql_prepare_statement(DB const &db, std::string const &query) { - libsql_stmt_t stmt; + libsql_stmt_t stmt; - const char *err; + const char *err; - int status = libsql_prepare(db.c, query.c_str(), &stmt, &err); + int status = libsql_prepare(db.c, query.c_str(), &stmt, &err); - if (status != 0) { - throw std::runtime_error(err); - } + if (status != 0) { + throw std::runtime_error(err); + } - return stmt; + return stmt; } BridgeResult opsqlite_libsql_execute(DB const &db, std::string const &query, const std::vector *params) { - std::vector column_names; - std::vector> out_rows; - std::vector out_row; - libsql_rows_t rows; - libsql_row_t row; - libsql_stmt_t stmt; - int status; - const char *err = nullptr; - - status = libsql_prepare(db.c, query.c_str(), &stmt, &err); - - if (status != 0) { - throw std::runtime_error(err); - } + std::vector column_names; + std::vector> out_rows; + std::vector out_row; + libsql_rows_t rows; + libsql_row_t row; + libsql_stmt_t stmt; + int status; + const char *err = nullptr; - if (params != nullptr && !params->empty()) { - opsqlite_libsql_bind_statement(stmt, params); - } + status = libsql_prepare(db.c, query.c_str(), &stmt, &err); - status = libsql_query_stmt(stmt, &rows, &err); + if (status != 0) { + throw std::runtime_error(err); + } - if (status != 0) { - throw std::runtime_error(err); - } + if (params != nullptr && !params->empty()) { + opsqlite_libsql_bind_statement(stmt, params); + } - // Get the column names on the first pass - int column_count = libsql_column_count(rows); - const char *col_name; + status = libsql_query_stmt(stmt, &rows, &err); - for (int i = 0; i < column_count; i++) { - status = libsql_column_name(rows, i, &col_name, &err); if (status != 0) { - throw std::runtime_error(err); - } - column_names.emplace_back(col_name); - } - - long long int_value; - double float_value; - const char *text_value; - blob blob_value; - - status = libsql_next_row(rows, &row, &err); - while (status == 0) { - out_row = std::vector(); - - if (!err && !row) { - break; - } - - for (int col = 0; col < column_count; col++) { - int type; - - libsql_column_type(rows, row, col, &type, &err); - - switch (type) { - case LIBSQL_INT: - status = libsql_get_int(row, col, &int_value, &err); - out_row.emplace_back(int_value); - break; - - case LIBSQL_FLOAT: - status = libsql_get_float(row, col, &float_value, &err); - out_row.emplace_back(float_value); - break; - - case LIBSQL_TEXT: - status = libsql_get_string(row, col, &text_value, &err); - out_row.emplace_back(text_value); - break; - - case LIBSQL_BLOB: { - libsql_get_blob(row, col, &blob_value, &err); - auto data = new uint8_t[blob_value.len]; - // You cannot share raw memory between native and JS - // always copy the data - memcpy(data, blob_value.ptr, blob_value.len); - libsql_free_blob(blob_value); - out_row.emplace_back( - ArrayBuffer{.data = std::shared_ptr{data}, - .size = static_cast(blob_value.len)}); - break; - } - - case LIBSQL_NULL: - // intentional fall-through - default: - out_row.emplace_back(nullptr); - break; - } - - if (status != 0) { throw std::runtime_error(err); - } } - out_rows.emplace_back(out_row); - err = nullptr; + // Get the column names on the first pass + int column_count = libsql_column_count(rows); + const char *col_name; + + for (int i = 0; i < column_count; i++) { + status = libsql_column_name(rows, i, &col_name, &err); + if (status != 0) { + throw std::runtime_error(err); + } + column_names.emplace_back(col_name); + } + + long long int_value; + double float_value; + const char *text_value; + blob blob_value; + status = libsql_next_row(rows, &row, &err); - } + while (status == 0) { + out_row = std::vector(); + + if (!err && !row) { + break; + } + + for (int col = 0; col < column_count; col++) { + int type; + + libsql_column_type(rows, row, col, &type, &err); + + switch (type) { + case LIBSQL_INT: + status = libsql_get_int(row, col, &int_value, &err); + out_row.emplace_back(int_value); + break; + + case LIBSQL_FLOAT: + status = libsql_get_float(row, col, &float_value, &err); + out_row.emplace_back(float_value); + break; + + case LIBSQL_TEXT: + status = libsql_get_string(row, col, &text_value, &err); + out_row.emplace_back(text_value); + break; + + case LIBSQL_BLOB: { + libsql_get_blob(row, col, &blob_value, &err); + auto data = new uint8_t[blob_value.len]; + // You cannot share raw memory between native and JS + // always copy the data + memcpy(data, blob_value.ptr, blob_value.len); + libsql_free_blob(blob_value); + out_row.emplace_back( + ArrayBuffer{.data = std::shared_ptr{data}, + .size = static_cast(blob_value.len)}); + break; + } + + case LIBSQL_NULL: + // intentional fall-through + default: + out_row.emplace_back(nullptr); + break; + } + + if (status != 0) { + throw std::runtime_error(err); + } + } + + out_rows.emplace_back(out_row); + err = nullptr; + status = libsql_next_row(rows, &row, &err); + } - libsql_free_rows(rows); - libsql_free_stmt(stmt); + libsql_free_rows(rows); + libsql_free_stmt(stmt); - unsigned long long changes = libsql_changes(db.c); - long long insert_row_id = libsql_last_insert_rowid(db.c); + unsigned long long changes = libsql_changes(db.c); + long long insert_row_id = libsql_last_insert_rowid(db.c); - return {.affectedRows = static_cast(changes), - .insertId = static_cast(insert_row_id), - .rows = std::move(out_rows), - .column_names = std::move(column_names)}; + return {.affectedRows = static_cast(changes), + .insertId = static_cast(insert_row_id), + .rows = std::move(out_rows), + .column_names = std::move(column_names)}; } BridgeResult opsqlite_libsql_execute_with_host_objects( @@ -469,126 +471,126 @@ BridgeResult opsqlite_libsql_execute_with_host_objects( const std::vector *params, std::vector *results, const std::shared_ptr> &metadatas) { - libsql_rows_t rows; - libsql_row_t row; - libsql_stmt_t stmt; - int status; - const char *err = nullptr; - - status = libsql_prepare(db.c, query.c_str(), &stmt, &err); - - if (status != 0) { - throw std::runtime_error(err); - } - - if (params != nullptr && !params->empty()) { - opsqlite_libsql_bind_statement(stmt, params); - } - - status = libsql_query_stmt(stmt, &rows, &err); - - if (status != 0) { - throw std::runtime_error(err); - } - - bool metadata_set = false; - - int num_cols = libsql_column_count(rows); - while ((status = libsql_next_row(rows, &row, &err)) == 0) { - - if (!err && !row) { - break; - } - - DumbHostObject row_host_object = DumbHostObject(metadatas); - - for (int col = 0; col < num_cols; col++) { - int type; - - libsql_column_type(rows, row, col, &type, &err); - - switch (type) { - case LIBSQL_INT: - long long int_value; - status = libsql_get_int(row, col, &int_value, &err); - row_host_object.values.emplace_back(int_value); - break; - - case LIBSQL_FLOAT: - double float_value; - status = libsql_get_float(row, col, &float_value, &err); - row_host_object.values.emplace_back(float_value); - break; - - case LIBSQL_TEXT: - const char *text_value; - status = libsql_get_string(row, col, &text_value, &err); - row_host_object.values.emplace_back(text_value); - break; - - case LIBSQL_BLOB: { - blob value_blob; - libsql_get_blob(row, col, &value_blob, &err); - auto *data = new uint8_t[value_blob.len]; - // You cannot share raw memory between native and JS - // always copy the data - memcpy(data, value_blob.ptr, value_blob.len); - libsql_free_blob(value_blob); - row_host_object.values.emplace_back( - ArrayBuffer{.data = std::shared_ptr{data}, - .size = static_cast(value_blob.len)}); - break; - } - - case LIBSQL_NULL: - // intentional fall-through - default: - row_host_object.values.emplace_back(nullptr); - break; - } - - if (status != 0) { - fprintf(stderr, "%s\n", err); - throw std::runtime_error(err); - } + libsql_rows_t rows; + libsql_row_t row; + libsql_stmt_t stmt; + int status; + const char *err = nullptr; - // On the first interation through the columns, set the metadata - if (!metadata_set && metadatas != nullptr) { - const char *col_name; - status = libsql_column_name(rows, col, &col_name, &err); + status = libsql_prepare(db.c, query.c_str(), &stmt, &err); - auto metadata = SmartHostObject(); - metadata.fields.emplace_back("name", col_name); - metadata.fields.emplace_back("index", col); - metadata.fields.emplace_back("type", "UNKNOWN"); - // metadata.fields.push_back( - // std::make_pair("type", type == -1 ? "UNKNOWN" : - // type)); + if (status != 0) { + throw std::runtime_error(err); + } - metadatas->push_back(metadata); - } + if (params != nullptr && !params->empty()) { + opsqlite_libsql_bind_statement(stmt, params); } - if (results != nullptr) { - results->push_back(row_host_object); + status = libsql_query_stmt(stmt, &rows, &err); + + if (status != 0) { + throw std::runtime_error(err); } - metadata_set = true; - err = nullptr; - } + bool metadata_set = false; + + int num_cols = libsql_column_count(rows); + while ((status = libsql_next_row(rows, &row, &err)) == 0) { + + if (!err && !row) { + break; + } + + DumbHostObject row_host_object = DumbHostObject(metadatas); + + for (int col = 0; col < num_cols; col++) { + int type; + + libsql_column_type(rows, row, col, &type, &err); + + switch (type) { + case LIBSQL_INT: + long long int_value; + status = libsql_get_int(row, col, &int_value, &err); + row_host_object.values.emplace_back(int_value); + break; + + case LIBSQL_FLOAT: + double float_value; + status = libsql_get_float(row, col, &float_value, &err); + row_host_object.values.emplace_back(float_value); + break; + + case LIBSQL_TEXT: + const char *text_value; + status = libsql_get_string(row, col, &text_value, &err); + row_host_object.values.emplace_back(text_value); + break; + + case LIBSQL_BLOB: { + blob value_blob; + libsql_get_blob(row, col, &value_blob, &err); + auto *data = new uint8_t[value_blob.len]; + // You cannot share raw memory between native and JS + // always copy the data + memcpy(data, value_blob.ptr, value_blob.len); + libsql_free_blob(value_blob); + row_host_object.values.emplace_back( + ArrayBuffer{.data = std::shared_ptr{data}, + .size = static_cast(value_blob.len)}); + break; + } + + case LIBSQL_NULL: + // intentional fall-through + default: + row_host_object.values.emplace_back(nullptr); + break; + } + + if (status != 0) { + fprintf(stderr, "%s\n", err); + throw std::runtime_error(err); + } + + // On the first interation through the columns, set the metadata + if (!metadata_set && metadatas != nullptr) { + const char *col_name; + status = libsql_column_name(rows, col, &col_name, &err); + + auto metadata = SmartHostObject(); + metadata.fields.emplace_back("name", col_name); + metadata.fields.emplace_back("index", col); + metadata.fields.emplace_back("type", "UNKNOWN"); + // metadata.fields.push_back( + // std::make_pair("type", type == -1 ? + // "UNKNOWN" : type)); + + metadatas->push_back(metadata); + } + } + + if (results != nullptr) { + results->push_back(row_host_object); + } + + metadata_set = true; + err = nullptr; + } - if (status != 0) { - fprintf(stderr, "%s\n", err); - } + if (status != 0) { + fprintf(stderr, "%s\n", err); + } - libsql_free_rows(rows); - libsql_free_stmt(stmt); + libsql_free_rows(rows); + libsql_free_stmt(stmt); - unsigned long long changes = libsql_changes(db.c); - long long insert_row_id = libsql_last_insert_rowid(db.c); + unsigned long long changes = libsql_changes(db.c); + long long insert_row_id = libsql_last_insert_rowid(db.c); - return {.affectedRows = static_cast(changes), - .insertId = static_cast(insert_row_id)}; + return {.affectedRows = static_cast(changes), + .insertId = static_cast(insert_row_id)}; } /// Executes returning data in raw arrays, a small performance optimization @@ -598,139 +600,139 @@ opsqlite_libsql_execute_raw(DB const &db, std::string const &query, const std::vector *params, std::vector> *results) { - libsql_rows_t rows; - libsql_row_t row; - libsql_stmt_t stmt; - int status; - const char *err = nullptr; - - status = libsql_prepare(db.c, query.c_str(), &stmt, &err); - - if (status != 0) { - throw std::runtime_error(err); - } - - if (params != nullptr && !params->empty()) { - opsqlite_libsql_bind_statement(stmt, params); - } - - status = libsql_query_stmt(stmt, &rows, &err); - - if (status != 0) { - throw std::runtime_error(err); - } - - int num_cols = libsql_column_count(rows); - while ((status = libsql_next_row(rows, &row, &err)) == 0) { - - if (!err && !row) { - break; - } - - std::vector row_vector; - - for (int col = 0; col < num_cols; col++) { - int type; - - libsql_column_type(rows, row, col, &type, &err); - - switch (type) { - case LIBSQL_INT: - long long int_value; - status = libsql_get_int(row, col, &int_value, &err); - row_vector.emplace_back(int_value); - break; - - case LIBSQL_FLOAT: - double float_value; - status = libsql_get_float(row, col, &float_value, &err); - row_vector.emplace_back(float_value); - break; - - case LIBSQL_TEXT: - const char *text_value; - status = libsql_get_string(row, col, &text_value, &err); - row_vector.emplace_back(text_value); - break; - - case LIBSQL_BLOB: { - blob value_blob; - libsql_get_blob(row, col, &value_blob, &err); - auto *data = new uint8_t[value_blob.len]; - // You cannot share raw memory between native and JS - // always copy the data - memcpy(data, value_blob.ptr, value_blob.len); - libsql_free_blob(value_blob); - row_vector.emplace_back( - ArrayBuffer{.data = std::shared_ptr{data}, - .size = static_cast(value_blob.len)}); - break; - } - - case LIBSQL_NULL: - // intentional fall-through - default: - row_vector.emplace_back(nullptr); - break; - } - - if (status != 0) { - fprintf(stderr, "%s\n", err); - throw std::runtime_error("libsql error"); - } + libsql_rows_t rows; + libsql_row_t row; + libsql_stmt_t stmt; + int status; + const char *err = nullptr; + + status = libsql_prepare(db.c, query.c_str(), &stmt, &err); + + if (status != 0) { + throw std::runtime_error(err); } - if (results != nullptr) { - results->push_back(row_vector); + if (params != nullptr && !params->empty()) { + opsqlite_libsql_bind_statement(stmt, params); } - err = nullptr; - } + status = libsql_query_stmt(stmt, &rows, &err); - if (status != 0) { - fprintf(stderr, "%s\n", err); - } + if (status != 0) { + throw std::runtime_error(err); + } - libsql_free_rows(rows); - libsql_free_stmt(stmt); + int num_cols = libsql_column_count(rows); + while ((status = libsql_next_row(rows, &row, &err)) == 0) { + + if (!err && !row) { + break; + } + + std::vector row_vector; + + for (int col = 0; col < num_cols; col++) { + int type; + + libsql_column_type(rows, row, col, &type, &err); + + switch (type) { + case LIBSQL_INT: + long long int_value; + status = libsql_get_int(row, col, &int_value, &err); + row_vector.emplace_back(int_value); + break; + + case LIBSQL_FLOAT: + double float_value; + status = libsql_get_float(row, col, &float_value, &err); + row_vector.emplace_back(float_value); + break; + + case LIBSQL_TEXT: + const char *text_value; + status = libsql_get_string(row, col, &text_value, &err); + row_vector.emplace_back(text_value); + break; + + case LIBSQL_BLOB: { + blob value_blob; + libsql_get_blob(row, col, &value_blob, &err); + auto *data = new uint8_t[value_blob.len]; + // You cannot share raw memory between native and JS + // always copy the data + memcpy(data, value_blob.ptr, value_blob.len); + libsql_free_blob(value_blob); + row_vector.emplace_back( + ArrayBuffer{.data = std::shared_ptr{data}, + .size = static_cast(value_blob.len)}); + break; + } + + case LIBSQL_NULL: + // intentional fall-through + default: + row_vector.emplace_back(nullptr); + break; + } + + if (status != 0) { + fprintf(stderr, "%s\n", err); + throw std::runtime_error("libsql error"); + } + } + + if (results != nullptr) { + results->push_back(row_vector); + } + + err = nullptr; + } - unsigned long long changes = libsql_changes(db.c); - long long insert_row_id = libsql_last_insert_rowid(db.c); + if (status != 0) { + fprintf(stderr, "%s\n", err); + } + + libsql_free_rows(rows); + libsql_free_stmt(stmt); + + unsigned long long changes = libsql_changes(db.c); + long long insert_row_id = libsql_last_insert_rowid(db.c); - return {.affectedRows = static_cast(changes), - .insertId = static_cast(insert_row_id)}; + return {.affectedRows = static_cast(changes), + .insertId = static_cast(insert_row_id)}; } BatchResult opsqlite_libsql_execute_batch(DB const &db, const std::vector *commands) { - size_t commandCount = commands->size(); - if (commandCount <= 0) { - throw std::runtime_error("No SQL commands provided"); - } - - try { - int affectedRows = 0; - opsqlite_libsql_execute(db, "BEGIN EXCLUSIVE TRANSACTION", nullptr); - for (int i = 0; i < commandCount; i++) { - auto command = commands->at(i); - // We do not provide a datastructure to receive query data because we - // don't need/want to handle this results in a batch execution - auto result = - opsqlite_libsql_execute(db, command.sql, &command.params); - affectedRows += result.affectedRows; - } - opsqlite_libsql_execute(db, "COMMIT", nullptr); - return BatchResult{ - .affectedRows = affectedRows, - .commands = static_cast(commandCount), - }; - } catch (std::exception &exc) { - opsqlite_libsql_execute(db, "ROLLBACK", nullptr); - return BatchResult{ - .message = exc.what(), - }; - } + size_t commandCount = commands->size(); + if (commandCount <= 0) { + throw std::runtime_error("No SQL commands provided"); + } + + try { + int affectedRows = 0; + opsqlite_libsql_execute(db, "BEGIN EXCLUSIVE TRANSACTION", nullptr); + for (int i = 0; i < commandCount; i++) { + auto command = commands->at(i); + // We do not provide a datastructure to receive query data because + // we don't need/want to handle this results in a batch execution + auto result = + opsqlite_libsql_execute(db, command.sql, &command.params); + affectedRows += result.affectedRows; + } + opsqlite_libsql_execute(db, "COMMIT", nullptr); + return BatchResult{ + .affectedRows = affectedRows, + .commands = static_cast(commandCount), + }; + } catch (std::exception &exc) { + opsqlite_libsql_execute(db, "ROLLBACK", nullptr); + return BatchResult{ + .message = exc.what(), + }; + } } } // namespace opsqlite diff --git a/cpp/libsql/bridge.h b/cpp/libsql/bridge.h index 8c4d921..bb9c75b 100644 --- a/cpp/libsql/bridge.h +++ b/cpp/libsql/bridge.h @@ -25,8 +25,8 @@ typedef std::function CommitCallback; typedef std::function RollbackCallback; struct DB { - libsql_database_t db; - libsql_connection_t c; + libsql_database_t db; + libsql_connection_t c; }; std::string opsqlite_get_db_path(std::string const &name, diff --git a/cpp/libsql/libsql.h b/cpp/libsql/libsql.h index 971d2eb..60b515c 100644 --- a/cpp/libsql/libsql.h +++ b/cpp/libsql/libsql.h @@ -28,18 +28,18 @@ typedef struct libsql_stmt libsql_stmt; typedef const libsql_database *libsql_database_t; typedef struct { - int frame_no; - int frames_synced; + int frame_no; + int frames_synced; } replicated; typedef struct { - const char *db_path; - const char *primary_url; - const char *auth_token; - char read_your_writes; - const char *encryption_key; - int sync_interval; - char with_webpki; + const char *db_path; + const char *primary_url; + const char *auth_token; + char read_your_writes; + const char *encryption_key; + int sync_interval; + char with_webpki; } libsql_config; typedef const libsql_connection *libsql_connection_t; @@ -53,8 +53,8 @@ typedef const libsql_rows_future *libsql_rows_future_t; typedef const libsql_row *libsql_row_t; typedef struct { - const char *ptr; - int len; + const char *ptr; + int len; } blob; #ifdef __cplusplus @@ -65,63 +65,68 @@ int libsql_enable_internal_tracing(void); int libsql_sync(libsql_database_t db, const char **out_err_msg); -int libsql_sync2(libsql_database_t db, replicated *out_replicated, const char **out_err_msg); +int libsql_sync2(libsql_database_t db, replicated *out_replicated, + const char **out_err_msg); -int libsql_open_sync(const char *db_path, - const char *primary_url, - const char *auth_token, - char read_your_writes, - const char *encryption_key, - libsql_database_t *out_db, +int libsql_open_sync(const char *db_path, const char *primary_url, + const char *auth_token, char read_your_writes, + const char *encryption_key, libsql_database_t *out_db, const char **out_err_msg); -int libsql_open_sync_with_webpki(const char *db_path, - const char *primary_url, - const char *auth_token, - char read_your_writes, +int libsql_open_sync_with_webpki(const char *db_path, const char *primary_url, + const char *auth_token, char read_your_writes, const char *encryption_key, libsql_database_t *out_db, const char **out_err_msg); -int libsql_open_sync_with_config(libsql_config config, libsql_database_t *out_db, const char **out_err_msg); +int libsql_open_sync_with_config(libsql_config config, + libsql_database_t *out_db, + const char **out_err_msg); -int libsql_open_ext(const char *url, libsql_database_t *out_db, const char **out_err_msg); +int libsql_open_ext(const char *url, libsql_database_t *out_db, + const char **out_err_msg); -int libsql_open_file(const char *url, libsql_database_t *out_db, const char **out_err_msg); +int libsql_open_file(const char *url, libsql_database_t *out_db, + const char **out_err_msg); -int libsql_open_remote(const char *url, const char *auth_token, libsql_database_t *out_db, const char **out_err_msg); +int libsql_open_remote(const char *url, const char *auth_token, + libsql_database_t *out_db, const char **out_err_msg); -int libsql_open_remote_with_webpki(const char *url, - const char *auth_token, +int libsql_open_remote_with_webpki(const char *url, const char *auth_token, libsql_database_t *out_db, const char **out_err_msg); void libsql_close(libsql_database_t db); -int libsql_connect(libsql_database_t db, libsql_connection_t *out_conn, const char **out_err_msg); +int libsql_connect(libsql_database_t db, libsql_connection_t *out_conn, + const char **out_err_msg); -int libsql_load_extension(libsql_connection_t conn, - const char *path, - const char *entry_point, - const char **out_err_msg); +int libsql_load_extension(libsql_connection_t conn, const char *path, + const char *entry_point, const char **out_err_msg); int libsql_reset(libsql_connection_t conn, const char **out_err_msg); void libsql_disconnect(libsql_connection_t conn); -int libsql_prepare(libsql_connection_t conn, const char *sql, libsql_stmt_t *out_stmt, const char **out_err_msg); +int libsql_prepare(libsql_connection_t conn, const char *sql, + libsql_stmt_t *out_stmt, const char **out_err_msg); -int libsql_bind_int(libsql_stmt_t stmt, int idx, long long value, const char **out_err_msg); +int libsql_bind_int(libsql_stmt_t stmt, int idx, long long value, + const char **out_err_msg); -int libsql_bind_float(libsql_stmt_t stmt, int idx, double value, const char **out_err_msg); +int libsql_bind_float(libsql_stmt_t stmt, int idx, double value, + const char **out_err_msg); int libsql_bind_null(libsql_stmt_t stmt, int idx, const char **out_err_msg); -int libsql_bind_string(libsql_stmt_t stmt, int idx, const char *value, const char **out_err_msg); +int libsql_bind_string(libsql_stmt_t stmt, int idx, const char *value, + const char **out_err_msg); -int libsql_bind_blob(libsql_stmt_t stmt, int idx, const unsigned char *value, int value_len, const char **out_err_msg); +int libsql_bind_blob(libsql_stmt_t stmt, int idx, const unsigned char *value, + int value_len, const char **out_err_msg); -int libsql_query_stmt(libsql_stmt_t stmt, libsql_rows_t *out_rows, const char **out_err_msg); +int libsql_query_stmt(libsql_stmt_t stmt, libsql_rows_t *out_rows, + const char **out_err_msg); int libsql_execute_stmt(libsql_stmt_t stmt, const char **out_err_msg); @@ -129,9 +134,11 @@ int libsql_reset_stmt(libsql_stmt_t stmt, const char **out_err_msg); void libsql_free_stmt(libsql_stmt_t stmt); -int libsql_query(libsql_connection_t conn, const char *sql, libsql_rows_t *out_rows, const char **out_err_msg); +int libsql_query(libsql_connection_t conn, const char *sql, + libsql_rows_t *out_rows, const char **out_err_msg); -int libsql_execute(libsql_connection_t conn, const char *sql, const char **out_err_msg); +int libsql_execute(libsql_connection_t conn, const char *sql, + const char **out_err_msg); void libsql_free_rows(libsql_rows_t res); @@ -141,27 +148,34 @@ void libsql_wait_result(libsql_rows_future_t res); int libsql_column_count(libsql_rows_t res); -int libsql_column_name(libsql_rows_t res, int col, const char **out_name, const char **out_err_msg); +int libsql_column_name(libsql_rows_t res, int col, const char **out_name, + const char **out_err_msg); -int libsql_column_type(libsql_rows_t res, libsql_row_t row, int col, int *out_type, const char **out_err_msg); +int libsql_column_type(libsql_rows_t res, libsql_row_t row, int col, + int *out_type, const char **out_err_msg); uint64_t libsql_changes(libsql_connection_t conn); int64_t libsql_last_insert_rowid(libsql_connection_t conn); -int libsql_next_row(libsql_rows_t res, libsql_row_t *out_row, const char **out_err_msg); +int libsql_next_row(libsql_rows_t res, libsql_row_t *out_row, + const char **out_err_msg); void libsql_free_row(libsql_row_t res); -int libsql_get_string(libsql_row_t res, int col, const char **out_value, const char **out_err_msg); +int libsql_get_string(libsql_row_t res, int col, const char **out_value, + const char **out_err_msg); void libsql_free_string(const char *ptr); -int libsql_get_int(libsql_row_t res, int col, long long *out_value, const char **out_err_msg); +int libsql_get_int(libsql_row_t res, int col, long long *out_value, + const char **out_err_msg); -int libsql_get_float(libsql_row_t res, int col, double *out_value, const char **out_err_msg); +int libsql_get_float(libsql_row_t res, int col, double *out_value, + const char **out_err_msg); -int libsql_get_blob(libsql_row_t res, int col, blob *out_blob, const char **out_err_msg); +int libsql_get_blob(libsql_row_t res, int col, blob *out_blob, + const char **out_err_msg); void libsql_free_blob(blob b); diff --git a/cpp/logs.h b/cpp/logs.h index 7f1d499..42ca6f1 100644 --- a/cpp/logs.h +++ b/cpp/logs.h @@ -15,26 +15,26 @@ #include #define LOG_TAG "op-sqlite" #define LOGV(...) \ - printf(" "); \ - printf(__VA_ARGS__); \ - printf("\t - <%s> \n", LOG_TAG); + printf(" "); \ + printf(__VA_ARGS__); \ + printf("\t - <%s> \n", LOG_TAG); #define LOGD(...) \ - printf(" "); \ - printf(__VA_ARGS__); \ - printf("\t - <%s> \n", LOG_TAG); + printf(" "); \ + printf(__VA_ARGS__); \ + printf("\t - <%s> \n", LOG_TAG); #define LOGI(...) \ - printf(" "); \ - printf(__VA_ARGS__); \ - printf("\t - <%s> \n", LOG_TAG); + printf(" "); \ + printf(__VA_ARGS__); \ + printf("\t - <%s> \n", LOG_TAG); #define LOGW(...) \ - printf(" * Warning: "); \ - printf(__VA_ARGS__); \ - printf("\t - <%s> \n", LOG_TAG); + printf(" * Warning: "); \ + printf(__VA_ARGS__); \ + printf("\t - <%s> \n", LOG_TAG); #define LOGE(...) \ - printf(" *** Error: "); \ - printf(__VA_ARGS__); \ - printf("\t - <%s> \n", LOG_TAG); + printf(" *** Error: "); \ + printf(__VA_ARGS__); \ + printf("\t - <%s> \n", LOG_TAG); #define LOGSIMPLE(...) \ - printf(" "); \ - printf(__VA_ARGS__); + printf(" "); \ + printf(__VA_ARGS__); #endif // ANDROID diff --git a/cpp/types.h b/cpp/types.h index d64ced5..d13509e 100644 --- a/cpp/types.h +++ b/cpp/types.h @@ -6,28 +6,28 @@ #include struct ArrayBuffer { - std::shared_ptr data; - size_t size; + std::shared_ptr data; + size_t size; }; using JSVariant = std::variant; struct BridgeResult { - std::string message; - int affectedRows; - double insertId; - std::vector> rows; - std::vector column_names; + std::string message; + int affectedRows; + double insertId; + std::vector> rows; + std::vector column_names; }; struct BatchResult { - std::string message; - int affectedRows; - int commands; + std::string message; + int affectedRows; + int commands; }; struct BatchArguments { - std::string sql; - std::vector params; + std::string sql; + std::vector params; }; diff --git a/cpp/utils.cpp b/cpp/utils.cpp index a9c03ea..b1b0ced 100644 --- a/cpp/utils.cpp +++ b/cpp/utils.cpp @@ -241,13 +241,13 @@ void to_batch_arguments(jsi::Runtime &rt, jsi::Array const &tuples, const std::string query = tuple.getValueAtIndex(rt, 0).asString(rt).utf8(rt); - if(length == 1) { + if (length == 1) { commands->push_back({query}); continue; } - + const jsi::Value &tuple_params = tuple.getValueAtIndex(rt, 1); - + if (!tuple_params.isUndefined() && tuple_params.asObject(rt).isArray(rt) && tuple_params.asObject(rt).asArray(rt).length(rt) > 0 && @@ -261,13 +261,12 @@ void to_batch_arguments(jsi::Runtime &rt, jsi::Array const &tuples, tuple_params.asObject(rt).asArray(rt); for (int x = 0; x < params_array.length(rt); x++) { const jsi::Value &p = params_array.getValueAtIndex(rt, x); - auto params = std::vector( - to_variant_vec(rt, p)); + auto params = std::vector(to_variant_vec(rt, p)); commands->push_back({query, params}); } } else { - auto params = std::vector( - to_variant_vec(rt, tuple_params)); + auto params = + std::vector(to_variant_vec(rt, tuple_params)); commands->push_back({query, params}); } }