diff --git a/README.md b/README.md index fa9f5230..04f54594 100644 --- a/README.md +++ b/README.md @@ -22,6 +22,8 @@ You can find the [benchmarking code in the example app](https://github.com/OP-En Memory consumption is also 1/4 compared to `react-native-quick-sqlite`. This query used to take 1.2 GB of peak memory usage, and now runs in 250mbs. +You can also turn on Memory Mapping to make your queries even faster by skipping the kernel during I/O, this comes with some disadvantages though. If you want even more speed and you can re-use your queries you can use prepared statements. + # Encryption If you need to encrypt your entire database, there is [`op-sqlcipher`](https://github.com/OP-Engineering/op-sqlcipher), which is a fork of this library that uses [SQLCipher](https://github.com/sqlcipher/sqlcipher). It completely encrypts the database with minimal overhead. @@ -102,6 +104,21 @@ const largeDb = open({ }); ``` +# Speed + +op-sqlite is already the fastest solution it can be, but it doesn't mean you cannot tweak SQLite to be faster (at the cost of some disadvantages). One possible tweak is turning on [Memory Mapping](https://www.sqlite.org/mmap.html). It allows to read/write to/from the disk without going through the kernel. However, if your queries throw an error your application might crash. + +To turn on Memory Mapping, execute the following pragma statement after opening a db: + +```ts +const db = open({ + name: 'mydb.sqlite', +}); + +// 0 turns of memory mapping, any other number enables it with the cache size +db.execute('PRAGMA mmap_size=268435456'); +``` + # API ```typescript @@ -134,7 +151,7 @@ db = { } ``` -### Simple queries +## Simple queries The basic query is **synchronous**, it will block rendering on large operations, further below you will find async versions. @@ -162,7 +179,7 @@ try { } ``` -### Multiple statements in a single string +## Multiple statements in a single string You can execute multiple statements in a single operation. The API however is not really thought for this use case and the results (and their metadata) will be mangled, so you can discard it. @@ -186,7 +203,7 @@ let t2name = db.execute( console.log(t2name.rows?._array[0].name); // outputs "T2" ``` -### Transactions +## Transactions Throwing an error inside the callback will ROLLBACK the transaction. @@ -215,7 +232,7 @@ await db.transaction('myDatabase', (tx) => { }); ``` -### Batch operation +## Batch operation Batch execution allows the transactional execution of a set of commands @@ -232,7 +249,7 @@ const res = db.executeSqlBatch('myDatabase', commands); console.log(`Batch affected ${result.rowsAffected} rows`); ``` -### Dynamic Column Metadata +## Dynamic Column Metadata In some scenarios, dynamic applications may need to get some metadata information about the returned result set. @@ -253,7 +270,7 @@ metadata.forEach((column) => { }); ``` -### Async operations +## Async operations You might have too much SQL to process and it will cause your application to freeze. There are async versions for some of the operations. This will offload the SQLite processing to a different thread. @@ -267,7 +284,7 @@ db.executeAsync( ); ``` -### Blobs +## Blobs Blobs are supported via `ArrayBuffer`, you need to be careful about the semantics though. You cannot instantiate an instance of `ArrayBuffer` directly, nor pass a typed array directly. Here is an example: @@ -295,7 +312,22 @@ const result = db.execute('SELECT content FROM BlobTable'); const finalUint8 = new Uint8Array(result.rows!._array[0].content); ``` -### Attach or Detach other databases +## Prepared statements + +A lot of the work when executing queries is not iterating through the result set itself but, sometimes, planning the execution. If you have a query which is expensive but you can re-use (even if you have to change the arguments) you can use a `prepared statement`: + +```ts +const statement = db.prepareStatement('SELECT * FROM User WHERE name = ?;'); +statement.bind(['Oscar']); +let results1 = statement.execute(); + +statement.bind(['Carlos']); +let results2 = statement.execute(); +``` + +You only pay the price of parsing the query once, and each subsequent execution should be faster. + +# Attach or Detach other databases SQLite supports attaching or detaching other database files into your main database connection through an alias. You can do any operation you like on this attached database like JOIN results across tables in different schemas, or update data or objects. @@ -322,7 +354,7 @@ if (!detachResult.status) { } ``` -### Loading SQL Dump Files +# Loading SQL Dump Files If you have a SQL dump file, you can load it directly, with low memory consumption: @@ -334,7 +366,7 @@ const { rowsAffected, commands } = db }); ``` -## Hooks +# Hooks You can subscribe to changes in your database by using an update hook: @@ -396,7 +428,7 @@ db.commitHook(null); db.rollbackHook(null); ``` -## Use built-in SQLite +# Use built-in SQLite On iOS you can use the embedded SQLite, when running `pod-install` add an environment flag: @@ -406,11 +438,11 @@ OP_SQLITE_USE_PHONE_VERSION=1 npx pod-install On Android, it is not possible to link the OS SQLite. It is also a bad idea due to vendor changes, old android bugs, etc. Unfortunately, this means this library will add some megabytes to your app size. -## Enable compile-time options +# Enable compile-time options By specifying pre-processor flags, you can enable optional features like FTS5, Geopoly, etc. -### iOS +## iOS Add a `post_install` block to your `/ios/Podfile` like so: @@ -429,7 +461,7 @@ end Replace the `` part with the flags you want to add. For example, you could add `SQLITE_ENABLE_FTS5=1` to `GCC_PREPROCESSOR_DEFINITIONS` to enable FTS5 in the iOS project. -### Android +## Android You can specify flags via `/android/gradle.properties` like so: @@ -437,18 +469,18 @@ You can specify flags via `/android/gradle.properties` like so: OPSQLiteFlags="-DSQLITE_ENABLE_FTS5=1" ``` -## Additional configuration +# Additional configuration -### App groups (iOS only) +## App groups (iOS only) On iOS, the SQLite database can be placed in an app group, in order to make it accessible from other apps in that app group. E.g. for sharing capabilities. To use an app group, add the app group ID as the value for the `OPSQLite_AppGroup` key in your project's `Info.plist` file. You'll also need to configure the app group in your project settings. (Xcode -> Project Settings -> Signing & Capabilities -> Add Capability -> App Groups) -## Contribute +# Contribute You need to have clang-format installed (`brew install clang-format`) -## License +# License MIT License. diff --git a/android/CMakeLists.txt b/android/CMakeLists.txt index 2b2a08a1..78bdb427 100644 --- a/android/CMakeLists.txt +++ b/android/CMakeLists.txt @@ -29,8 +29,10 @@ add_library( ../cpp/ThreadPool.cpp ../cpp/sqlbatchexecutor.h ../cpp/sqlbatchexecutor.cpp - ../cpp/DynamicHostObject.cpp - ../cpp/DynamicHostObject.h + ../cpp/SmartHostObject.cpp + ../cpp/SmartHostObject.h + ../cpp/PreparedStatementHostObject.h + ../cpp/PreparedStatementHostObject.cpp ../cpp/DumbHostObject.cpp ../cpp/DumbHostObject.h ../cpp/macros.h diff --git a/benchmark.png b/benchmark.png index 9495cb3c..1b5cbd70 100644 Binary files a/benchmark.png and b/benchmark.png differ diff --git a/cpp/DumbHostObject.cpp b/cpp/DumbHostObject.cpp index 575a3a8e..0b59e1db 100644 --- a/cpp/DumbHostObject.cpp +++ b/cpp/DumbHostObject.cpp @@ -1,4 +1,5 @@ #include "DumbHostObject.h" +#include "SmartHostObject.h" #include "utils.h" #include @@ -7,7 +8,7 @@ namespace opsqlite { namespace jsi = facebook::jsi; DumbHostObject::DumbHostObject( - std::shared_ptr> metadata) { + std::shared_ptr> metadata) { this->metadata = metadata; }; diff --git a/cpp/DumbHostObject.h b/cpp/DumbHostObject.h index 1786c4c4..415d3580 100644 --- a/cpp/DumbHostObject.h +++ b/cpp/DumbHostObject.h @@ -3,7 +3,7 @@ #include -#include "DynamicHostObject.h" +#include "SmartHostObject.h" #include "types.h" #include #include @@ -17,7 +17,7 @@ class JSI_EXPORT DumbHostObject : public jsi::HostObject { public: DumbHostObject(){}; - DumbHostObject(std::shared_ptr> metadata); + DumbHostObject(std::shared_ptr> metadata); std::vector getPropertyNames(jsi::Runtime &rt); @@ -25,7 +25,7 @@ class JSI_EXPORT DumbHostObject : public jsi::HostObject { std::vector values; - std::shared_ptr> metadata; + std::shared_ptr> metadata; }; } // namespace opsqlite diff --git a/cpp/PreparedStatementHostObject.cpp b/cpp/PreparedStatementHostObject.cpp new file mode 100644 index 00000000..d2ba4a13 --- /dev/null +++ b/cpp/PreparedStatementHostObject.cpp @@ -0,0 +1,88 @@ +// +// PreparedStatementHostObject.cpp +// op-sqlite +// +// Created by Oscar Franco on 5/12/23. +// + +#include "PreparedStatementHostObject.h" +#include "bridge.h" +#include "macros.h" +#include "utils.h" + +namespace opsqlite { + +namespace jsi = facebook::jsi; + +PreparedStatementHostObject::PreparedStatementHostObject( + std::string dbName, sqlite3_stmt *statementPtr) + : _dbName(dbName), _statement(statementPtr) {} + +std::vector +PreparedStatementHostObject::getPropertyNames(jsi::Runtime &rt) { + std::vector keys; + + // for (auto field : fields) { + // keys.push_back(jsi::PropNameID::forAscii(rt, field.first)); + // } + + return keys; +} + +jsi::Value PreparedStatementHostObject::get(jsi::Runtime &rt, + const jsi::PropNameID &propNameID) { + auto name = propNameID.utf8(rt); + + if (name == "bind") { + return HOSTFN("bind", 1) { + if (_statement == NULL) { + throw std::runtime_error("statement has been freed"); + } + + std::vector params; + + const jsi::Value &originalParams = args[0]; + params = toVariantVec(rt, originalParams); + + std::vector results; + std::shared_ptr> metadata = + std::make_shared>(); + + sqlite_bind_statement(_statement, ¶ms); + + return {}; + }); + } + + if (name == "execute") { + return HOSTFN("execute", 1) { + if (_statement == NULL) { + throw std::runtime_error("statement has been freed"); + } + std::vector results; + std::shared_ptr> metadata = + std::make_shared>(); + + auto status = sqlite_execute_prepared_statement(_dbName, _statement, + &results, metadata); + + if (status.type == SQLiteError) { + throw std::runtime_error(status.message); + } + + auto jsiResult = createResult(rt, status, &results, metadata); + return jsiResult; + }); + } + + return {}; +} + +PreparedStatementHostObject::~PreparedStatementHostObject() { + if (_statement != NULL) { + sqlite3_finalize(_statement); + _statement = NULL; + } +} + +} // namespace opsqlite diff --git a/cpp/PreparedStatementHostObject.h b/cpp/PreparedStatementHostObject.h new file mode 100644 index 00000000..6d43915c --- /dev/null +++ b/cpp/PreparedStatementHostObject.h @@ -0,0 +1,36 @@ +// +// PreparedStatementHostObject.hpp +// op-sqlite +// +// Created by Oscar Franco on 5/12/23. +// + +#ifndef PreparedStatementHostObject_h +#define PreparedStatementHostObject_h + +#include +#include +#include +#include + +namespace opsqlite { +namespace jsi = facebook::jsi; + +class PreparedStatementHostObject : public jsi::HostObject { +public: + PreparedStatementHostObject(std::string dbName, sqlite3_stmt *statement); + virtual ~PreparedStatementHostObject(); + + std::vector getPropertyNames(jsi::Runtime &rt); + + jsi::Value get(jsi::Runtime &rt, const jsi::PropNameID &propNameID); + +private: + std::string _dbName; + // This shouldn't be de-allocated until sqlite3_finalize is called on it + sqlite3_stmt *_statement; +}; + +} // namespace opsqlite + +#endif /* PreparedStatementHostObject_hpp */ diff --git a/cpp/DynamicHostObject.cpp b/cpp/SmartHostObject.cpp similarity index 67% rename from cpp/DynamicHostObject.cpp rename to cpp/SmartHostObject.cpp index d137ac0f..5c1f96e6 100644 --- a/cpp/DynamicHostObject.cpp +++ b/cpp/SmartHostObject.cpp @@ -1,13 +1,12 @@ -#include "DynamicHostObject.h" +#include "SmartHostObject.h" #include "utils.h" -#include namespace opsqlite { namespace jsi = facebook::jsi; std::vector -DynamicHostObject::getPropertyNames(jsi::Runtime &rt) { +SmartHostObject::getPropertyNames(jsi::Runtime &rt) { std::vector keys; for (auto field : fields) { @@ -17,8 +16,8 @@ DynamicHostObject::getPropertyNames(jsi::Runtime &rt) { return keys; } -jsi::Value DynamicHostObject::get(jsi::Runtime &rt, - const jsi::PropNameID &propNameID) { +jsi::Value SmartHostObject::get(jsi::Runtime &rt, + const jsi::PropNameID &propNameID) { auto name = propNameID.utf8(rt); for (auto field : fields) { diff --git a/cpp/DynamicHostObject.h b/cpp/SmartHostObject.h similarity index 67% rename from cpp/DynamicHostObject.h rename to cpp/SmartHostObject.h index 62b36088..1ea2f6a4 100644 --- a/cpp/DynamicHostObject.h +++ b/cpp/SmartHostObject.h @@ -1,5 +1,5 @@ -#ifndef DynamicHostObject_h -#define DynamicHostObject_h +#ifndef SmartHostObject_h +#define SmartHostObject_h #include "types.h" #include @@ -10,9 +10,9 @@ namespace opsqlite { namespace jsi = facebook::jsi; -class JSI_EXPORT DynamicHostObject : public jsi::HostObject { +class JSI_EXPORT SmartHostObject : public jsi::HostObject { public: - DynamicHostObject(){}; + SmartHostObject(){}; std::vector getPropertyNames(jsi::Runtime &rt); @@ -23,4 +23,4 @@ class JSI_EXPORT DynamicHostObject : public jsi::HostObject { } // namespace opsqlite -#endif /* DynamicHostObject_h */ +#endif /* SmartHostObject_h */ diff --git a/cpp/bindings.cpp b/cpp/bindings.cpp index 80b67385..e73723b3 100644 --- a/cpp/bindings.cpp +++ b/cpp/bindings.cpp @@ -1,5 +1,6 @@ #include "bindings.h" #include "DumbHostObject.h" +#include "PreparedStatementHostObject.h" #include "ThreadPool.h" #include "bridge.h" #include "logs.h" @@ -7,6 +8,7 @@ #include "sqlbatchexecutor.h" #include "utils.h" #include +#include #include #include #include @@ -36,6 +38,10 @@ void clearState() { sqliteCloseAll(); // We then join all the threads before the context gets invalidated pool.restartPool(); + + updateHooks.clear(); + commitHooks.clear(); + rollbackHooks.clear(); } void install(jsi::Runtime &rt, @@ -204,8 +210,8 @@ void install(jsi::Runtime &rt, } std::vector results; - std::shared_ptr> metadata = - std::make_shared>(); + std::shared_ptr> metadata = + std::make_shared>(); auto status = sqliteExecute(dbName, query, ¶ms, &results, metadata); @@ -241,8 +247,8 @@ void install(jsi::Runtime &rt, reject]() { try { std::vector results; - std::shared_ptr> metadata = - std::make_shared>(); + std::shared_ptr> metadata = + std::make_shared>(); ; auto status = @@ -436,8 +442,8 @@ void install(jsi::Runtime &rt, std::string operation, int rowId) { std::vector params; std::vector results; - std::shared_ptr> metadata = - std::make_shared>(); + std::shared_ptr> metadata = + std::make_shared>(); ; if (operation != "DELETE") { @@ -523,6 +529,18 @@ void install(jsi::Runtime &rt, return {}; }); + auto prepareStatement = HOSTFN("prepareStatement", 1) { + auto dbName = args[0].asString(rt).utf8(rt); + auto query = args[1].asString(rt).utf8(rt); + + sqlite3_stmt *statement = sqlite_prepare_statement(dbName, query); + + auto preparedStatementHostObject = + std::make_shared(dbName, statement); + + return jsi::Object::createFromHostObject(rt, preparedStatementHostObject); + }); + jsi::Object module = jsi::Object(rt); module.setProperty(rt, "open", std::move(open)); @@ -538,6 +556,7 @@ void install(jsi::Runtime &rt, module.setProperty(rt, "updateHook", std::move(updateHook)); module.setProperty(rt, "commitHook", std::move(commitHook)); module.setProperty(rt, "rollbackHook", std::move(rollbackHook)); + module.setProperty(rt, "prepareStatement", std::move(prepareStatement)); rt.global().setProperty(rt, "__OPSQLiteProxy", std::move(module)); } diff --git a/cpp/bridge.cpp b/cpp/bridge.cpp index cc995de0..fbab3022 100644 --- a/cpp/bridge.cpp +++ b/cpp/bridge.cpp @@ -1,10 +1,8 @@ #include "bridge.h" #include "DumbHostObject.h" -#include "DynamicHostObject.h" +#include "SmartHostObject.h" #include "logs.h" #include -#include -#include #include #include #include @@ -226,11 +224,178 @@ inline void bindStatement(sqlite3_stmt *statement, } } +void sqlite_bind_statement(sqlite3_stmt *statement, + const std::vector *params) { + bindStatement(statement, params); +} + +BridgeResult sqlite_execute_prepared_statement( + std::string const dbName, sqlite3_stmt *statement, + std::vector *results, + std::shared_ptr> metadatas) { + if (dbMap.find(dbName) == dbMap.end()) { + return {.type = SQLiteError, + .message = "[op-sqlite]: Database " + dbName + " is not open"}; + } + + sqlite3_reset(statement); + + sqlite3 *db = dbMap[dbName]; + + const char *errorMessage; + + bool isConsuming = true; + bool isFailed = false; + + int result = SQLITE_OK; + + isConsuming = true; + + 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 == NULL) { + 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.push_back(JSVariant(column_value)); + break; + } + + case SQLITE_FLOAT: { + double column_value = sqlite3_column_double(statement, i); + row.values.push_back(JSVariant(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.push_back(JSVariant(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); + uint8_t *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.push_back( + JSVariant(ArrayBuffer{.data = std::shared_ptr{data}, + .size = static_cast(blob_size)})); + break; + } + + case SQLITE_NULL: + // Intentionally left blank + + default: + row.values.push_back(JSVariant(NULL)); + break; + } + i++; + } + if (results != nullptr) { + results->push_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.push_back(std::make_pair("name", column_name)); + metadata.fields.push_back(std::make_pair("index", i)); + metadata.fields.push_back( + std::make_pair("type", type == NULL ? "UNKNOWN" : type)); + + metadatas->push_back(metadata); + i++; + } + } + isConsuming = false; + break; + + default: + errorMessage = sqlite3_errmsg(db); + isFailed = true; + isConsuming = false; + } + } + + if (isFailed) { + + return {.type = SQLiteError, + .message = "[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); + + return {.type = SQLiteOk, + .affectedRows = changedRowCount, + .insertId = static_cast(latestInsertRowId)}; +} + +sqlite3_stmt *sqlite_prepare_statement(std::string const dbName, + std::string const &query) { + if (dbMap.find(dbName) == dbMap.end()) { + throw std::runtime_error("Database not opened"); + } + + sqlite3 *db = dbMap[dbName]; + + sqlite3_stmt *statement; + + const char *queryStr = query.c_str(); + + int statementStatus = sqlite3_prepare_v2(db, queryStr, -1, &statement, NULL); + + if (statementStatus == SQLITE_ERROR) { + const char *message = sqlite3_errmsg(db); + throw std::runtime_error("[op-sqlite] SQL statement error: " + + std::string(message)); + } + + return statement; +} + BridgeResult sqliteExecute(std::string const dbName, std::string const &query, const std::vector *params, std::vector *results, - std::shared_ptr> metadatas) { + std::shared_ptr> metadatas) { if (dbMap.find(dbName) == dbMap.end()) { return {.type = SQLiteError, @@ -263,7 +428,9 @@ sqliteExecute(std::string const dbName, std::string const &query, }; } - bindStatement(statement, params); + if (params != nullptr && params->size() > 0) { + bindStatement(statement, params); + } isConsuming = true; @@ -349,7 +516,7 @@ sqliteExecute(std::string const dbName, std::string const &query, while (i < count) { column_name = sqlite3_column_name(statement, i); const char *type = sqlite3_column_decltype(statement, i); - auto metadata = DynamicHostObject(); + auto metadata = SmartHostObject(); metadata.fields.push_back(std::make_pair("name", column_name)); metadata.fields.push_back(std::make_pair("index", i)); metadata.fields.push_back( diff --git a/cpp/bridge.h b/cpp/bridge.h index c835ba16..4b7f5a0b 100644 --- a/cpp/bridge.h +++ b/cpp/bridge.h @@ -2,9 +2,10 @@ #define bridge_h #include "DumbHostObject.h" -#include "DynamicHostObject.h" +#include "SmartHostObject.h" #include "types.h" #include "utils.h" +#include #include namespace opsqlite { @@ -30,7 +31,7 @@ BridgeResult sqliteExecute(std::string const dbName, std::string const &query, const std::vector *params, std::vector *results, - std::shared_ptr> metadatas); + std::shared_ptr> metadatas); BridgeResult sqliteExecuteLiteral(std::string const dbName, std::string const &query); @@ -50,6 +51,17 @@ BridgeResult registerRollbackHook(std::string const dbName, std::function const callback); BridgeResult unregisterRollbackHook(std::string const dbName); + +sqlite3_stmt *sqlite_prepare_statement(std::string const dbName, + std::string const &query); + +void sqlite_bind_statement(sqlite3_stmt *statement, + const std::vector *params); + +BridgeResult sqlite_execute_prepared_statement( + std::string const dbName, sqlite3_stmt *statement, + std::vector *results, + std::shared_ptr> metadatas); } // namespace opsqlite #endif /* bridge_h */ diff --git a/cpp/utils.cpp b/cpp/utils.cpp index 1777fbbd..e26e1f74 100644 --- a/cpp/utils.cpp +++ b/cpp/utils.cpp @@ -1,5 +1,5 @@ #include "utils.h" -#include "DynamicHostObject.h" +#include "SmartHostObject.h" #include "bridge.h" #include #include @@ -99,7 +99,7 @@ std::vector toVariantVec(jsi::Runtime &rt, jsi::Value createResult(jsi::Runtime &rt, BridgeResult status, std::vector *results, - std::shared_ptr> metadata) { + std::shared_ptr> metadata) { if (status.type == SQLiteError) { throw std::invalid_argument(status.message); } @@ -134,7 +134,7 @@ createResult(jsi::Runtime &rt, BridgeResult status, column_array.setValueAtIndex( rt, i, jsi::Object::createFromHostObject( - rt, std::make_shared(column))); + rt, std::make_shared(column))); } res.setProperty(rt, "metadata", std::move(column_array)); diff --git a/cpp/utils.h b/cpp/utils.h index bb78a773..1b263d5f 100644 --- a/cpp/utils.h +++ b/cpp/utils.h @@ -2,7 +2,7 @@ #define utils_h #include "DumbHostObject.h" -#include "DynamicHostObject.h" +#include "SmartHostObject.h" #include "types.h" #include #include @@ -35,10 +35,9 @@ jsi::Value toJSI(jsi::Runtime &rt, JSVariant value); std::vector toVariantVec(jsi::Runtime &rt, jsi::Value const &args); -jsi::Value -createResult(jsi::Runtime &rt, BridgeResult status, - std::vector *results, - std::shared_ptr> metadata); +jsi::Value createResult(jsi::Runtime &rt, BridgeResult status, + std::vector *results, + std::shared_ptr> metadata); BatchResult importSQLFile(std::string dbName, std::string fileLocation); diff --git a/example/ios/Podfile.lock b/example/ios/Podfile.lock index 5af7ac47..3930d246 100644 --- a/example/ios/Podfile.lock +++ b/example/ios/Podfile.lock @@ -15,6 +15,9 @@ PODS: - hermes-engine/Pre-built (= 0.72.6) - hermes-engine/Pre-built (0.72.6) - libevent (2.1.12) + - MMKV (1.3.2): + - MMKVCore (~> 1.3.2) + - MMKVCore (1.3.2) - op-sqlite (2.0.1): - React - React-callinvoker @@ -319,6 +322,9 @@ PODS: - React-jsinspector (0.72.6) - React-logger (0.72.6): - glog + - react-native-mmkv (2.11.0): + - MMKV (>= 1.2.13) + - React-Core - react-native-performance (5.1.0): - React-Core - react-native-restart (0.0.27): @@ -467,6 +473,7 @@ DEPENDENCIES: - React-jsiexecutor (from `../node_modules/react-native/ReactCommon/jsiexecutor`) - React-jsinspector (from `../node_modules/react-native/ReactCommon/jsinspector`) - React-logger (from `../node_modules/react-native/ReactCommon/logger`) + - react-native-mmkv (from `../node_modules/react-native-mmkv`) - react-native-performance (from `../node_modules/react-native-performance`) - react-native-restart (from `../node_modules/react-native-restart`) - react-native-safe-area-context (from `../node_modules/react-native-safe-area-context`) @@ -493,6 +500,8 @@ SPEC REPOS: trunk: - fmt - libevent + - MMKV + - MMKVCore - SocketRocket EXTERNAL SOURCES: @@ -541,6 +550,8 @@ EXTERNAL SOURCES: :path: "../node_modules/react-native/ReactCommon/jsinspector" React-logger: :path: "../node_modules/react-native/ReactCommon/logger" + react-native-mmkv: + :path: "../node_modules/react-native-mmkv" react-native-performance: :path: "../node_modules/react-native-performance" react-native-restart: @@ -593,6 +604,8 @@ SPEC CHECKSUMS: glog: 04b94705f318337d7ead9e6d17c019bd9b1f6b1b hermes-engine: 8057e75cfc1437b178ac86c8654b24e7fead7f60 libevent: 4049cae6c81cdb3654a443be001fb9bdceff7913 + MMKV: f21593c0af4b3f2a0ceb8f820f28bb639ea22bb7 + MMKVCore: 31b4cb83f8266467eef20a35b6d78e409a11060d op-sqlite: 64b2d465324963ba9d7cf9a0ea27997c42bb8b6e RCT-Folly: 424b8c9a7a0b9ab2886ffe9c3b041ef628fd4fb1 RCTRequired: 28469809442eb4eb5528462705f7d852948c8a74 @@ -609,6 +622,7 @@ SPEC CHECKSUMS: React-jsiexecutor: 3bf18ff7cb03cd8dfdce08fbbc0d15058c1d71ae React-jsinspector: 194e32c6aab382d88713ad3dd0025c5f5c4ee072 React-logger: cebf22b6cf43434e471dc561e5911b40ac01d289 + react-native-mmkv: e97c0c79403fb94577e5d902ab1ebd42b0715b43 react-native-performance: cef2b618d47b277fb5c3280b81a3aad1e72f2886 react-native-restart: 7595693413fe3ca15893702f2c8306c62a708162 react-native-safe-area-context: 39c2d8be3328df5d437ac1700f4f3a4f75716acc diff --git a/example/package.json b/example/package.json index 54efcc19..d7b44587 100644 --- a/example/package.json +++ b/example/package.json @@ -20,6 +20,7 @@ "nativewind": "^2.0.11", "react": "18.2.0", "react-native": "0.72.6", + "react-native-mmkv": "^2.11.0", "react-native-performance": "^5.1.0", "react-native-restart": "^0.0.27", "react-native-safe-area-context": "^4.5.0", diff --git a/example/src/App.tsx b/example/src/App.tsx index 1605a432..026b1a2a 100644 --- a/example/src/App.tsx +++ b/example/src/App.tsx @@ -1,4 +1,3 @@ -import 'react-native-get-random-values'; import React, {useEffect, useState} from 'react'; import { ActivityIndicator, @@ -17,6 +16,11 @@ import RNRestart from 'react-native-restart'; import {registerHooksTests} from './tests/hooks.spec'; import {open} from '@op-engineering/op-sqlite'; import clsx from 'clsx'; +import {preparedStatementsTests} from './tests/preparedStatements.spec'; +import performance from 'react-native-performance'; +import {MMKV} from 'react-native-mmkv'; + +export const mmkv = new MMKV(); const StyledScrollView = styled(ScrollView, { props: { @@ -29,12 +33,24 @@ export default function App() { const [results, setResults] = useState([]); const [times, setTimes] = useState([]); const [accessingTimes, setAccessingTimes] = useState([]); + const [prepareTimes, setPrepareTimes] = useState([]); + const [prepareExecutionTimes, setPrepareExecutionTimes] = useState( + [], + ); + const [sqliteMMSetTime, setSqliteMMSetTime] = useState(0); + const [mmkvSetTime, setMMKVSetTime] = useState(0); + const [sqliteGetTime, setSqliteMMGetTime] = useState(0); + const [mmkvGetTime, setMMKVGetTime] = useState(0); useEffect(() => { setResults([]); - runTests(dbSetupTests, queriesTests, blobTests, registerHooksTests).then( - setResults, - ); + runTests( + dbSetupTests, + queriesTests, + blobTests, + registerHooksTests, + preparedStatementsTests, + ).then(setResults); }, []); const createLargeDb = async () => { @@ -55,6 +71,8 @@ export default function App() { const times = await queryLargeDB(); setTimes(times.loadFromDb); setAccessingTimes(times.access); + setPrepareTimes(times.prepare); + setPrepareExecutionTimes(times.preparedExecution); } catch (e) { console.error(e); } finally { @@ -62,6 +80,45 @@ export default function App() { } }; + const testAgainstMMKV = () => { + const db = open({ + name: 'mmkvTestDb', + }); + + db.execute('PRAGMA mmap_size=268435456'); + db.execute('PRAGMA journal_mode = OFF;'); + db.execute('DROP TABLE IF EXISTS mmkvTest;'); + db.execute('CREATE TABLE mmkvTest (text TEXT);'); + + let insertStatment = db.prepareStatement( + 'INSERT INTO "mmkvTest" (text) VALUES (?)', + ); + insertStatment.bind(['quack']); + + let start = performance.now(); + insertStatment.execute(); + let end = performance.now(); + setSqliteMMSetTime(end - start); + + start = performance.now(); + mmkv.set('mmkvDef', 'quack'); + end = performance.now(); + setMMKVSetTime(end - start); + + let readStatement = db.prepareStatement('SELECT text from mmkvTest;'); + start = performance.now(); + readStatement.execute(); + end = performance.now(); + setSqliteMMGetTime(end - start); + + start = performance.now(); + mmkv.getString('mmkvDef'); + end = performance.now(); + setMMKVGetTime(end - start); + + db.close(); + }; + const queryAndReload = async () => { queryLargeDB(); setTimeout(() => { @@ -83,6 +140,29 @@ export default function App() { Tools +