From 1ebee7bf5b325d2eb2d50783660236185d3ecebd Mon Sep 17 00:00:00 2001 From: Richard Willis Date: Fri, 24 Nov 2023 13:48:18 +0000 Subject: [PATCH] Fix unknown string encoding issues See https://github.com/drycpp/lmdbxx/issues/1#issuecomment-319564663 for more info --- README.md | 2 +- android/CMakeLists.txt | 2 +- android/cpp-adapter.cpp | 8 +- cpp/lmdb++.h | 557 +++++++++++++------------------------- cpp/react-native-lmdb.cpp | 20 +- cpp/react-native-lmdb.h | 2 +- example/src/App.tsx | 116 +++++--- ios/Lmdb.mm | 8 +- package.json | 29 +- src/index.tsx | 2 +- 10 files changed, 302 insertions(+), 444 deletions(-) diff --git a/README.md b/README.md index c38fbfe..52d97d2 100644 --- a/README.md +++ b/README.md @@ -70,7 +70,7 @@ See the [contributing guide](CONTRIBUTING.md) to learn how to contribute to the ## Credits - Thanks to [sysmas](https://www.symas.com/) for open sourcing lmdb. -- Thanks to [lmdb++](https://github.com/drycpp/lmdbxx) for the useful c++ wrapper. +- Thanks to [drycpp/lmdb++](https://github.com/drycpp/lmdbxx) & [hoytech/lmdb++](https://github.com/hoytech/lmdbxx) for the useful c++ wrapper. ## License diff --git a/android/CMakeLists.txt b/android/CMakeLists.txt index 878230b..0022cb5 100644 --- a/android/CMakeLists.txt +++ b/android/CMakeLists.txt @@ -1,7 +1,7 @@ cmake_minimum_required(VERSION 3.4.1) set (CMAKE_VERBOSE_MAKEFILE ON) -set (CMAKE_CXX_STANDARD 11) +set (CMAKE_CXX_STANDARD 17) add_library(cpp SHARED diff --git a/android/cpp-adapter.cpp b/android/cpp-adapter.cpp index e0eefc4..20c3d69 100644 --- a/android/cpp-adapter.cpp +++ b/android/cpp-adapter.cpp @@ -27,8 +27,12 @@ extern "C" JNIEXPORT jstring JNICALL Java_com_lmdb_LmdbModule_nativeGet(JNIEnv *env, jclass type, jstring key) { std::string keyStr = getString(env, key); - std::string result = rnlmdb::get(keyStr); - return (env)->NewStringUTF(result.c_str()); + auto result = rnlmdb::get(keyStr); + if (result.has_value()) { + return (env)->NewStringUTF(result.value().c_str()); + } else { + return nullptr; + } } extern "C" diff --git a/cpp/lmdb++.h b/cpp/lmdb++.h index 5c59ec9..01011a0 100644 --- a/cpp/lmdb++.h +++ b/cpp/lmdb++.h @@ -4,23 +4,22 @@ #define LMDBXX_H /** - * - C++11 wrapper for LMDB. + * - C++17 wrapper for LMDB. * * @author Arto Bendiken - * @see https://sourceforge.net/projects/lmdbxx/ + * @maintainer Doug Hoyte + * @see https://github.com/hoytech/lmdbxx */ #ifndef __cplusplus #error " requires a C++ compiler" #endif -#if __cplusplus < 201103L -#if !defined(_MSC_VER) || _MSC_VER < 1900 -#error " requires a C++11 compiler (CXXFLAGS='-std=c++11')" -#endif // _MSC_VER check +#if __cplusplus < 201703L +#error " requires a C++17 compiler (CXXFLAGS='-std=c++17')" #endif -/////////////////**/////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// #include "../lmdb/libraries/liblmdb/lmdb.h" /* for MDB_*, mdb_*() */ @@ -29,10 +28,12 @@ #endif #include /* for std::size_t */ #include /* for std::snprintf() */ -#include /* for std::strlen() */ +#include /* for std::memcpy() */ #include /* for std::runtime_error */ #include /* for std::string */ -#include /* for std::is_pod<> */ +#include /* for std::string_view */ +#include /* for std::numeric_limits<> */ +#include /* for std::addressof */ namespace lmdb { using mode = mdb_mode_t; @@ -256,7 +257,7 @@ namespace lmdb { #endif // TODO: mdb_env_set_assert() // TODO: mdb_reader_list() - // TODO: mdb_reader_check() + static inline void reader_check(MDB_env *env, int *dead); } /** @@ -517,6 +518,14 @@ lmdb::env_get_userctx(MDB_env* const env) { } #endif +static inline void +lmdb::reader_check(MDB_env *env, int *dead) { + const int rc = ::mdb_reader_check(env, dead); + if (rc != MDB_SUCCESS) { + error::raise("reader_check", rc); + } +} + //////////////////////////////////////////////////////////////////////////////// /* Procedural Interface: Transactions */ @@ -810,7 +819,7 @@ namespace lmdb { static inline MDB_txn* cursor_txn(MDB_cursor* cursor) noexcept; static inline MDB_dbi cursor_dbi(MDB_cursor* cursor) noexcept; static inline bool cursor_get(MDB_cursor* cursor, MDB_val* key, MDB_val* data, MDB_cursor_op op); - static inline void cursor_put(MDB_cursor* cursor, MDB_val* key, MDB_val* data, unsigned int flags); + static inline bool cursor_put(MDB_cursor* cursor, MDB_val* key, MDB_val* data, unsigned int flags); static inline void cursor_del(MDB_cursor* cursor, unsigned int flags); static inline void cursor_count(MDB_cursor* cursor, std::size_t& count); } @@ -886,15 +895,16 @@ lmdb::cursor_get(MDB_cursor* const cursor, * @throws lmdb::error on failure * @see http://symas.com/mdb/doc/group__mdb.html#ga1f83ccb40011837ff37cc32be01ad91e */ -static inline void +static inline bool lmdb::cursor_put(MDB_cursor* const cursor, MDB_val* const key, MDB_val* const data, const unsigned int flags = 0) { const int rc = ::mdb_cursor_put(cursor, key, data, flags); - if (rc != MDB_SUCCESS) { + if (rc != MDB_SUCCESS && rc != MDB_KEYEXIST) { error::raise("mdb_cursor_put", rc); } + return (rc == MDB_SUCCESS); } /** @@ -923,152 +933,6 @@ lmdb::cursor_count(MDB_cursor* const cursor, } } -//////////////////////////////////////////////////////////////////////////////// -/* Resource Interface: Values */ - -namespace lmdb { - class val; -} - -/** - * Wrapper class for `MDB_val` structures. - * - * @note Instances of this class are movable and copyable both. - * @see http://symas.com/mdb/doc/group__mdb.html#structMDB__val - */ -class lmdb::val { -protected: - MDB_val _val; - -public: - /** - * Default constructor. - */ - val() noexcept = default; - - /** - * Constructor. - */ - val(const std::string& data) noexcept - : val{data.data(), data.size()} {} - - /** - * Constructor. - */ - val(const char* const data) noexcept - : val{data, std::strlen(data)} {} - - /** - * Constructor. - */ - val(const void* const data, - const std::size_t size) noexcept - : _val{size, const_cast(data)} {} - - /** - * Move constructor. - */ - val(val&& other) noexcept = default; - - /** - * Move assignment operator. - */ - val& operator=(val&& other) noexcept = default; - - /** - * Destructor. - */ - ~val() noexcept = default; - - /** - * Returns an `MDB_val*` pointer. - */ - operator MDB_val*() noexcept { - return &_val; - } - - /** - * Returns an `MDB_val*` pointer. - */ - operator const MDB_val*() const noexcept { - return &_val; - } - - /** - * Determines whether this value is empty. - */ - bool empty() const noexcept { - return size() == 0; - } - - /** - * Returns the size of the data. - */ - std::size_t size() const noexcept { - return _val.mv_size; - } - - /** - * Returns a pointer to the data. - */ - template - T* data() noexcept { - return reinterpret_cast(_val.mv_data); - } - - /** - * Returns a pointer to the data. - */ - template - const T* data() const noexcept { - return reinterpret_cast(_val.mv_data); - } - - /** - * Returns a pointer to the data. - */ - char* data() noexcept { - return reinterpret_cast(_val.mv_data); - } - - /** - * Returns a pointer to the data. - */ - const char* data() const noexcept { - return reinterpret_cast(_val.mv_data); - } - - /** - * Assigns the value. - */ - template - val& assign(const T* const data, - const std::size_t size) noexcept { - _val.mv_size = size; - _val.mv_data = const_cast(reinterpret_cast(data)); - return *this; - } - - /** - * Assigns the value. - */ - val& assign(const char* const data) noexcept { - return assign(data, std::strlen(data)); - } - - /** - * Assigns the value. - */ - val& assign(const std::string& data) noexcept { - return assign(data.data(), data.size()); - } -}; - -#if !(defined(__COVERITY__) || defined(_MSC_VER)) -static_assert(std::is_pod::value, "lmdb::val must be a POD type"); -static_assert(sizeof(lmdb::val) == sizeof(MDB_val), "sizeof(lmdb::val) != sizeof(MDB_val)"); -#endif - //////////////////////////////////////////////////////////////////////////////// /* Resource Interface: Environment */ @@ -1183,6 +1047,12 @@ class lmdb::env { } } + int reader_check() { + int dead; + lmdb::reader_check(handle(), &dead); + return dead; + } + /** * Opens this environment. * @@ -1235,6 +1105,29 @@ class lmdb::env { lmdb::env_set_max_dbs(handle(), count); return *this; } + + mdb_filehandle_t get_fd() { + mdb_filehandle_t fd; + lmdb::env_get_fd(handle(), &fd); + return fd; + } + + /** + * @notice WARNING: This is a function to access LMDB's internal memory map, use at your own risk! + */ + std::string_view get_internal_map() { + if constexpr (sizeof(int) != 4 || sizeof(long) != 8 || sizeof(void*) != 8) error::raise("get_internal_map: only LP64 supported", 0); + + // This is a hack that depends on the internal layout of LMDB's MDB_env struct. Hopefully there + // will be a better way to get me_map at some point. + + char *me_map = *(char**)(((char*)_handle) + 56); + + MDB_envinfo arg; + mdb_env_info(_handle, &arg); + + return std::string_view(me_map, arg.me_mapsize); + } }; //////////////////////////////////////////////////////////////////////////////// @@ -1339,8 +1232,9 @@ class lmdb::txn { * @post `handle() == nullptr` */ void commit() { - lmdb::txn_commit(_handle); + auto h = _handle; _handle = nullptr; + lmdb::txn_commit(h); } /** @@ -1349,8 +1243,9 @@ class lmdb::txn { * @post `handle() == nullptr` */ void abort() noexcept { - lmdb::txn_abort(_handle); + auto h = _handle; _handle = nullptr; + lmdb::txn_abort(h); } /** @@ -1380,12 +1275,11 @@ namespace lmdb { /** * Resource class for `MDB_dbi` handles. * - * @note Instances of this class are movable, but not copyable. * @see http://symas.com/mdb/doc/group__mdb.html#gadbe68a06c448dfb62da16443d251a78b */ class lmdb::dbi { protected: - MDB_dbi _handle{0}; + MDB_dbi _handle{(std::numeric_limits::max)()}; public: static constexpr unsigned int default_flags = 0; @@ -1395,8 +1289,8 @@ class lmdb::dbi { * Opens a database handle. * * @param txn the transaction handle - * @param name - * @param flags + * @param name the database name, or nullptr + * @param flags dbi flags, ie MDB_CREATE * @throws lmdb::error on failure */ static dbi @@ -1408,30 +1302,38 @@ class lmdb::dbi { return dbi{handle}; } - /** - * Constructor. - * - * @param handle a valid `MDB_dbi` handle + /* + * This overload is so that DBI names can be passed in as string_views, which can be + * useful when storing the names of DBIs in the database itself. We need to convert it + * to a std::string to ensure that it is NUL-byte terminated. This overload comes after + * the const char* one above so that in that case there is no std::string overhead. */ - dbi(const MDB_dbi handle) noexcept - : _handle{handle} {} + + static dbi + open(MDB_txn* const txn, + std::string_view name, + const unsigned int flags = default_flags) { + MDB_dbi handle{}; + std::string nameStr(name); + lmdb::dbi_open(txn, nameStr.c_str(), flags, &handle); + return dbi{handle}; + } /** - * Move constructor. + * Constructor. + * + * @note Creates an uninitialized dbi instance. You must move or move-assign a valid dbi instance onto it before you can use it. */ - dbi(dbi&& other) noexcept { - std::swap(_handle, other._handle); - } + dbi() noexcept + : _handle{(std::numeric_limits::max)()} {} /** - * Move assignment operator. + * Constructor. + * + * @param handle a valid `MDB_dbi` handle */ - dbi& operator=(dbi&& other) noexcept { - if (this != &other) { - std::swap(_handle, other._handle); - } - return *this; - } + dbi(const MDB_dbi handle) noexcept + : _handle{handle} {} /** * Destructor. @@ -1521,67 +1423,17 @@ class lmdb::dbi { * @param data * @throws lmdb::error on failure */ - bool get(MDB_txn* const txn, - const val& key, - val& data) { - return lmdb::dbi_get(txn, handle(), key, data); - } - - /** - * Retrieves a key from this database. - * - * @param txn a transaction handle - * @param key - * @throws lmdb::error on failure - */ - template - bool get(MDB_txn* const txn, - const K& key) const { - const lmdb::val k{&key, sizeof(K)}; - lmdb::val v{}; - return lmdb::dbi_get(txn, handle(), k, v); - } - - /** - * Retrieves a key/value pair from this database. - * - * @param txn a transaction handle - * @param key - * @param val - * @throws lmdb::error on failure - */ - template - bool get(MDB_txn* const txn, - const K& key, - V& val) const { - const lmdb::val k{&key, sizeof(K)}; - lmdb::val v{}; - const bool result = lmdb::dbi_get(txn, handle(), k, v); - if (result) { - val = *v.data(); - } - return result; - } - /** - * Retrieves a key/value pair from this database. - * - * @param txn a transaction handle - * @param key a NUL-terminated string key - * @param val - * @throws lmdb::error on failure - */ - template bool get(MDB_txn* const txn, - const char* const key, - V& val) const { - const lmdb::val k{key, std::strlen(key)}; - lmdb::val v{}; - const bool result = lmdb::dbi_get(txn, handle(), k, v); - if (result) { - val = *v.data(); + const std::string_view key, + std::string_view& data) { + const MDB_val keyV{key.size(), const_cast(key.data())}; + MDB_val dataV{data.size(), const_cast(data.data())}; + bool ret = lmdb::dbi_get(txn, handle(), &keyV, &dataV); + if (ret) { + data = std::string_view(static_cast(dataV.mv_data), dataV.mv_size); } - return result; + return ret; } /** @@ -1594,95 +1446,25 @@ class lmdb::dbi { * @throws lmdb::error on failure */ bool put(MDB_txn* const txn, - const val& key, - val& data, - const unsigned int flags = default_put_flags) { - return lmdb::dbi_put(txn, handle(), key, data, flags); - } - - /** - * Stores a key into this database. - * - * @param txn a transaction handle - * @param key - * @param flags - * @throws lmdb::error on failure - */ - template - bool put(MDB_txn* const txn, - const K& key, - const unsigned int flags = default_put_flags) { - const lmdb::val k{&key, sizeof(K)}; - lmdb::val v{}; - return lmdb::dbi_put(txn, handle(), k, v, flags); - } - - /** - * Stores a key/value pair into this database. - * - * @param txn a transaction handle - * @param key - * @param val - * @param flags - * @throws lmdb::error on failure - */ - template - bool put(MDB_txn* const txn, - const K& key, - const V& val, - const unsigned int flags = default_put_flags) { - const lmdb::val k{&key, sizeof(K)}; - lmdb::val v{&val, sizeof(V)}; - return lmdb::dbi_put(txn, handle(), k, v, flags); - } - - /** - * Stores a key/value pair into this database. - * - * @param txn a transaction handle - * @param key a NUL-terminated string key - * @param val - * @param flags - * @throws lmdb::error on failure - */ - template - bool put(MDB_txn* const txn, - const char* const key, - const V& val, - const unsigned int flags = default_put_flags) { - const lmdb::val k{key, std::strlen(key)}; - lmdb::val v{&val, sizeof(V)}; - return lmdb::dbi_put(txn, handle(), k, v, flags); - } - - /** - * Stores a key/value pair into this database. - * - * @param txn a transaction handle - * @param key a NUL-terminated string key - * @param val a NUL-terminated string key - * @param flags - * @throws lmdb::error on failure - */ - bool put(MDB_txn* const txn, - const char* const key, - const char* const val, + const std::string_view key, + std::string_view data, const unsigned int flags = default_put_flags) { - const lmdb::val k{key, std::strlen(key)}; - lmdb::val v{val, std::strlen(val)}; - return lmdb::dbi_put(txn, handle(), k, v, flags); + const MDB_val keyV{key.size(), const_cast(key.data())}; + MDB_val dataV{data.size(), const_cast(data.data())}; + return lmdb::dbi_put(txn, handle(), &keyV, &dataV, flags); } /** - * Removes a key/value pair from this database. + * Removes a key from this database. * * @param txn a transaction handle * @param key * @throws lmdb::error on failure */ bool del(MDB_txn* const txn, - const val& key) { - return lmdb::dbi_del(txn, handle(), key); + const std::string_view key) { + const MDB_val keyV{key.size(), const_cast(key.data())}; + return lmdb::dbi_del(txn, handle(), &keyV); } /** @@ -1690,13 +1472,15 @@ class lmdb::dbi { * * @param txn a transaction handle * @param key + * @param val * @throws lmdb::error on failure */ - template bool del(MDB_txn* const txn, - const K& key) { - const lmdb::val k{&key, sizeof(K)}; - return lmdb::dbi_del(txn, handle(), k); + const std::string_view key, + const std::string_view val) { + const MDB_val keyV{key.size(), const_cast(key.data())}; + const MDB_val valV{val.size(), const_cast(val.data())}; + return lmdb::dbi_del(txn, handle(), &keyV, &valV); } }; @@ -1828,85 +1612,120 @@ class lmdb::cursor { * @param op * @throws lmdb::error on failure */ - bool get(MDB_val* const key, + bool get(std::string_view &key, const MDB_cursor_op op) { - return get(key, nullptr, op); + MDB_val keyV{key.size(), const_cast(key.data())}; + bool ret = lmdb::cursor_get(handle(), &keyV, nullptr, op); + if (ret) { + key = std::string_view(static_cast(keyV.mv_data), keyV.mv_size); + } + return ret; } /** - * Retrieves a key from the database. + * Retrieves a key/value pair from the database. * * @param key + * @param val * @param op * @throws lmdb::error on failure */ - bool get(lmdb::val& key, + bool get(std::string_view &key, + std::string_view &val, const MDB_cursor_op op) { - return get(key, nullptr, op); + MDB_val keyV{key.size(), const_cast(key.data())}; + MDB_val valV{val.size(), const_cast(val.data())}; + bool ret = lmdb::cursor_get(handle(), &keyV, &valV, op); + if (ret) { + key = std::string_view(static_cast(keyV.mv_data), keyV.mv_size); + val = std::string_view(static_cast(valV.mv_data), valV.mv_size); + } + return ret; } /** - * Retrieves a key/value pair from the database. + * Stores key/data pairs into the database. The cursor is positioned at the new item, or on failure usually near it. + * + * See MDB docs for flag values. * * @param key - * @param val (may be `nullptr`) - * @param op + * @param val + * @param flags * @throws lmdb::error on failure */ - bool get(MDB_val* const key, - MDB_val* const val, - const MDB_cursor_op op) { - return lmdb::cursor_get(handle(), key, val, op); + bool put(const std::string_view &key, + const std::string_view &val, + const unsigned int flags = 0) { + MDB_val keyV{key.size(), const_cast(key.data())}; + MDB_val valV{val.size(), const_cast(val.data())}; + return lmdb::cursor_put(handle(), &keyV, &valV, flags); } /** - * Retrieves a key/value pair from the database. + * Delete current key/data pair. * - * @param key - * @param val - * @param op - * @throws lmdb::error on failure + * @param flags Options for this operation. Values: + * MDB_NODUPDATA - delete all of the data items for the current key. This flag may only be specified if the database was opened with MDB_DUPSORT. */ - bool get(lmdb::val& key, - lmdb::val& val, - const MDB_cursor_op op) { - return lmdb::cursor_get(handle(), key, val, op); + void del(unsigned int flags = 0) { + lmdb::cursor_del(handle(), flags); } /** - * Retrieves a key/value pair from the database. + * Return count of duplicates for current key. This call is only valid on databases that support sorted duplicate data items MDB_DUPSORT. + */ + size_t count() { + std::size_t countp; + lmdb::cursor_count(handle(), countp); + return countp; + } +}; + +namespace lmdb { + /** + * Creates a std::string_view that points to the memory pointed to by v. * - * @param key - * @param val - * @param op - * @throws lmdb::error on failure + * @param v */ - bool get(std::string& key, - std::string& val, - const MDB_cursor_op op) { - lmdb::val k{}, v{}; - const bool found = get(k, v, op); - if (found) { - key.assign(k.data(), k.size()); - val.assign(v.data(), v.size()); - } - return found; + template + static inline std::string_view ptr_to_sv(T* v) { + return std::string_view(reinterpret_cast(v), sizeof(*v)); } /** - * Positions this cursor at the given key. + * Creates a std::string_view that points to the memory occupied by v. * - * @param key - * @param op - * @throws lmdb::error on failure + * @param v */ - template - bool find(const K& key, - const MDB_cursor_op op = MDB_SET) { - lmdb::val k{&key, sizeof(K)}; - return get(k, nullptr, op); + template + static inline std::string_view to_sv(const T &v) { + return std::string_view(reinterpret_cast(std::addressof(v)), sizeof(v)); } -}; + + /** + * Takes a std::string_view and casts its pointer as a pointer to the parameterized type. + * + * @param v + */ + template + static inline T* ptr_from_sv(std::string_view v) { + if (v.size() != sizeof(T)) error::raise("ptr_from_sv", MDB_BAD_VALSIZE); + return reinterpret_cast(const_cast(v.data())); + } + + /** + * Takes a std::string_view and dereferences it, returning a value of the parameterized type. + * + * @param v + */ + template + static inline T from_sv(std::string_view v) { + if (v.size() != sizeof(T)) error::raise("from_sv", MDB_BAD_VALSIZE); + T ret; + std::memcpy(&ret, const_cast(v.data()), sizeof(T)); + return ret; + } +} //////////////////////////////////////////////////////////////////////////////// diff --git a/cpp/react-native-lmdb.cpp b/cpp/react-native-lmdb.cpp index 9204423..0987a3c 100644 --- a/cpp/react-native-lmdb.cpp +++ b/cpp/react-native-lmdb.cpp @@ -1,5 +1,6 @@ #include "react-native-lmdb.h" #include "lmdb++.h" +#include namespace rnlmdb { lmdb::env env = nullptr; @@ -13,26 +14,23 @@ namespace rnlmdb { env.open(dbPath.c_str(), MDB_CREATE, 0664); } - std::string get(std::string key) + std::optional get(std::string key) { auto rtxn = lmdb::txn::begin(env, nullptr, MDB_RDONLY); auto dbi = lmdb::dbi::open(rtxn, nullptr); - auto keyValue = lmdb::val(key); - auto dataValue = lmdb::val(); - auto rc = ::mdb_get(rtxn, dbi, keyValue, dataValue); - rtxn.abort(); - if (rc != 0) { - return nullptr; - } else { - return dataValue.data(); + + std::string_view value; + if (dbi.get(rtxn, key, value)) { + return std::string{value}; } + return std::nullopt; } void put(std::string key, std::string value) { auto wtxn = lmdb::txn::begin(env); auto dbi = lmdb::dbi::open(wtxn, nullptr); - dbi.put(wtxn, key.c_str(), value.c_str()); + dbi.put(wtxn, key, value); wtxn.commit(); } @@ -40,7 +38,7 @@ namespace rnlmdb { { auto wtxn = lmdb::txn::begin(env); auto dbi = lmdb::dbi::open(wtxn, nullptr); - dbi.del(wtxn, lmdb::val(key)); + dbi.del(wtxn, key); wtxn.commit(); } } diff --git a/cpp/react-native-lmdb.h b/cpp/react-native-lmdb.h index d44fe27..1f9770b 100644 --- a/cpp/react-native-lmdb.h +++ b/cpp/react-native-lmdb.h @@ -9,7 +9,7 @@ namespace rnlmdb { void open(std::string dbPath, double mapSize); void put(std::string key, std::string value); - std::string get(std::string key); + std::optional get(std::string key); void del(std::string key); } diff --git a/example/src/App.tsx b/example/src/App.tsx index a7bfbdd..677c136 100644 --- a/example/src/App.tsx +++ b/example/src/App.tsx @@ -1,48 +1,100 @@ -import * as React from 'react'; +import React, { useState } from 'react'; -import { StyleSheet, View, Text } from 'react-native'; +import { StyleSheet, View, Text, Button } from 'react-native'; import { open } from 'react-native-lmdb'; -const nowOpen = performance.now(); -const { put, get } = open('mydb.mdb'); -const endOpen = performance.now() - nowOpen; +type RunResults = { + open?: number; + get10_1000?: number; + put10_1000?: number; + get2?: number; + put2?: number; +}; -const nowPut = performance.now(); +function getLongString() { + let x = '1234567890'; + const iterations = 5; + for (var i = 0; i < iterations; i++) { + x += x.concat(x); + } + return x; +} -put('key1', 'value1'); -put('key2', 'value2'); -const endPut = performance.now() - nowPut; +const longString = getLongString(); -const nowGet = performance.now(); -const value1 = get('key1'); -const value2 = get('key2'); -const endGet = performance.now() - nowGet; +const { put, get } = open('mydb123.mdb'); -const nowBatchPut = performance.now(); -for (let i = 0; i < 10_000; i++) { - put(`key${i}`, `value${i}`); -} -const endBatchPut = performance.now() - nowBatchPut; +export default function App() { + const [memoryLeakStarted, setMemoryLeakStarted] = useState(false); + const [runResults, setRunResults] = useState(); -const nowBatchGet = performance.now(); -for (let i = 0; i < 10_000; i++) { - get(`key${i}`); -} -const endBatchGet = performance.now() - nowBatchGet; + console.log('not found', get('not found')); -// del('key1'); + function runBenchmarks() { + const results: RunResults = {}; + const nowOpen = performance.now(); + results.open = performance.now() - nowOpen; -export default function App() { + const nowPut = performance.now(); + put('key1', JSON.stringify({ hello: 'world' })); + put('key2', JSON.stringify({ hello: 'world2' })); + results.put10_1000 = performance.now() - nowPut; + + const nowGet = performance.now(); + // @ts-ignore + console.log(JSON.stringify(JSON.parse(get('key1')))); + // @ts-ignore + console.log(JSON.stringify(JSON.parse(get('key2')))); + results.get2 = performance.now() - nowGet; + + const nowBatchPut = performance.now(); + for (let i = 0; i < 10_000; i++) { + put(`key${i}`, `value${i}`); + } + results.put10_1000 = performance.now() - nowBatchPut; + + const nowBatchGet = performance.now(); + for (let i = 0; i < 10_000; i++) { + get(`key${i}`); + } + results.get10_1000 = performance.now() - nowBatchGet; + + setRunResults(results); + } + + // Test if we have a memory leak in our native code. + // Write and read a very large string while freeing JS memory. + // This should max out disk operations. + // App memory should rise and fall as we free JS memory. + // As you continue to run the test app memory will slowly increase but I don't + // think this indicates a leak. + async function runMemoryLeakTest() { + setMemoryLeakStarted(true); + for (let i = 0; i < 200_000; i++) { + // force a gc every 20_000 + if (i % 20_000 === 0) { + await new Promise((res) => setTimeout(res)); + } + put(`key1`, longString); + get(`key1`); + } + setMemoryLeakStarted(false); + } return ( Hello LMDB - key1 value: {value1} - key2 value: {value2} - open: {endOpen}ms - put (2): {endPut}ms - get (2): {endGet}ms - put (10_000): {endBatchPut}ms - get (10_000): {endBatchGet}ms +