From a0c803d1a2b8c4ce8c58f11fd5e5db4e25197f49 Mon Sep 17 00:00:00 2001 From: chrispader Date: Tue, 14 Jul 2026 15:08:40 +0200 Subject: [PATCH 1/2] fix: correct query result memory estimate --- .../cpp/hybridObjects/HybridNitroSQLiteQueryResult.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/react-native-nitro-sqlite/cpp/hybridObjects/HybridNitroSQLiteQueryResult.cpp b/packages/react-native-nitro-sqlite/cpp/hybridObjects/HybridNitroSQLiteQueryResult.cpp index 1b9acfe9..81aedb33 100644 --- a/packages/react-native-nitro-sqlite/cpp/hybridObjects/HybridNitroSQLiteQueryResult.cpp +++ b/packages/react-native-nitro-sqlite/cpp/hybridObjects/HybridNitroSQLiteQueryResult.cpp @@ -19,7 +19,7 @@ namespace { size_t getRowExternalMemorySize(const SQLiteQueryResultRow& row) { size_t bucketMemory = row.bucket_count() * sizeof(void*); constexpr size_t nodePadding = 24; - size_t nodesMemory = row.size() * (sizeof(std::pair) * nodePadding); + size_t nodesMemory = row.size() * (sizeof(std::pair) + nodePadding); return bucketMemory + nodesMemory; } From fb4c48415428f54995a29c8e1d8897e434c4cc09 Mon Sep 17 00:00:00 2001 From: chrispader Date: Tue, 14 Jul 2026 15:18:31 +0200 Subject: [PATCH 2/2] fix: improve SQLite result memory handling --- .../HybridNitroSQLiteQueryResult.cpp | 33 +++++++++++-- .../HybridNitroSQLiteQueryResult.hpp | 2 +- .../cpp/operations.cpp | 47 +++++++++++++++++-- .../cpp/operations.hpp | 3 ++ .../cpp/sqliteExecuteBatch.cpp | 6 +-- 5 files changed, 78 insertions(+), 13 deletions(-) diff --git a/packages/react-native-nitro-sqlite/cpp/hybridObjects/HybridNitroSQLiteQueryResult.cpp b/packages/react-native-nitro-sqlite/cpp/hybridObjects/HybridNitroSQLiteQueryResult.cpp index 81aedb33..93d10a66 100644 --- a/packages/react-native-nitro-sqlite/cpp/hybridObjects/HybridNitroSQLiteQueryResult.cpp +++ b/packages/react-native-nitro-sqlite/cpp/hybridObjects/HybridNitroSQLiteQueryResult.cpp @@ -10,6 +10,23 @@ namespace margelo::nitro::rnnitrosqlite { namespace { + constexpr size_t nodePadding = 24; + + size_t getValueExternalMemorySize(const SQLiteValue& value) { + if (std::holds_alternative(value)) { + return std::get(value).capacity(); + } + + if (std::holds_alternative>(value)) { + const auto& buffer = std::get>(value); + if (buffer != nullptr && buffer->isOwner()) { + return buffer->size(); + } + } + + return 0; + } + /** * Compute the approximate external memory size of a single result row. * This includes: @@ -18,9 +35,15 @@ namespace { */ size_t getRowExternalMemorySize(const SQLiteQueryResultRow& row) { size_t bucketMemory = row.bucket_count() * sizeof(void*); - constexpr size_t nodePadding = 24; - size_t nodesMemory = row.size() * (sizeof(std::pair) + nodePadding); - return bucketMemory + nodesMemory; + size_t nodesMemory = row.size() * (sizeof(SQLiteQueryResultRow::value_type) + nodePadding); + size_t contentsMemory = 0; + + for (const auto& [columnName, value] : row) { + contentsMemory += columnName.capacity(); + contentsMemory += getValueExternalMemorySize(value); + } + + return bucketMemory + nodesMemory + contentsMemory; } /** @@ -49,10 +72,10 @@ namespace { * - Metadata contents, especially the `name` string on each metadata entry. */ size_t getMetadataExternalMemorySize(const SQLiteQueryTableMetadata& metadata) { - size_t size = 0; + size_t size = metadata.bucket_count() * sizeof(void*); + size += metadata.size() * (sizeof(SQLiteQueryTableMetadata::value_type) + nodePadding); for (const auto& [columnName, columnMeta] : metadata) { - size += columnName.capacity(); size += columnMeta.name.capacity(); } diff --git a/packages/react-native-nitro-sqlite/cpp/hybridObjects/HybridNitroSQLiteQueryResult.hpp b/packages/react-native-nitro-sqlite/cpp/hybridObjects/HybridNitroSQLiteQueryResult.hpp index f70c6063..144f3f7e 100644 --- a/packages/react-native-nitro-sqlite/cpp/hybridObjects/HybridNitroSQLiteQueryResult.hpp +++ b/packages/react-native-nitro-sqlite/cpp/hybridObjects/HybridNitroSQLiteQueryResult.hpp @@ -13,7 +13,7 @@ class HybridNitroSQLiteQueryResult : public HybridNitroSQLiteQueryResultSpec { HybridNitroSQLiteQueryResult() : HybridObject(TAG) {} HybridNitroSQLiteQueryResult(SQLiteQueryResults results, std::optional insertId, double rowsAffected, std::optional metadata) - : HybridObject(TAG), _insertId(insertId), _rowsAffected(rowsAffected), _results(std::move(results)), _metadata(metadata) {} + : HybridObject(TAG), _insertId(insertId), _rowsAffected(rowsAffected), _results(std::move(results)), _metadata(std::move(metadata)) {} private: std::optional _insertId; diff --git a/packages/react-native-nitro-sqlite/cpp/operations.cpp b/packages/react-native-nitro-sqlite/cpp/operations.cpp index 289defe5..90100623 100644 --- a/packages/react-native-nitro-sqlite/cpp/operations.cpp +++ b/packages/react-native-nitro-sqlite/cpp/operations.cpp @@ -25,7 +25,6 @@ using namespace margelo::nitro::rnnitrosqlite; namespace margelo::rnnitrosqlite { - static constexpr double kInt64MinAsDouble = static_cast(std::numeric_limits::min()); static constexpr double kInt64UpperBoundAsDouble = -kInt64MinAsDouble; @@ -128,8 +127,7 @@ void bindStatement(sqlite3_stmt* statement, const SQLiteQueryParams& values) { } else if (std::holds_alternative(value)) { // Bind whole numbers as INTEGER so vec0 rowid/pk/partition (which reject REAL) work; SQLite still coerces to REAL for REAL columns. double doubleValue = std::get(value); - if (std::trunc(doubleValue) == doubleValue && doubleValue >= kInt64MinAsDouble && - doubleValue < kInt64UpperBoundAsDouble) { + if (std::trunc(doubleValue) == doubleValue && doubleValue >= kInt64MinAsDouble && doubleValue < kInt64UpperBoundAsDouble) { sqlite3_bind_int64(statement, sqliteIndex, static_cast(doubleValue)); } else { sqlite3_bind_double(statement, sqliteIndex, doubleValue); @@ -258,7 +256,48 @@ std::shared_ptr sqliteExecute(const std::string& d int rowsAffected = sqlite3_changes(db); long long latestInsertRowId = sqlite3_last_insert_rowid(db); - return std::make_shared(results, static_cast(latestInsertRowId), rowsAffected, metadata); + return std::make_shared(std::move(results), static_cast(latestInsertRowId), rowsAffected, + std::move(metadata)); +} + +SQLiteOperationResult sqliteExecuteForRowsAffected(const std::string& dbName, const std::string& query, + const std::optional& params) { + if (dbMap.count(dbName) == 0) { + throw NitroSQLiteException::DatabaseNotOpen(dbName); + } + + auto db = dbMap[dbName]; + + sqlite3_stmt* statement; + int statementStatus = sqlite3_prepare_v2(db, query.c_str(), -1, &statement, NULL); + if (statementStatus == SQLITE_OK) { + if (params) { + bindStatement(statement, *params); + } + } else { + throw NitroSQLiteException::SqlExecution(sqlite3_errmsg(db)); + } + + bool isFailed = false; + while (true) { + int result = sqlite3_step(statement); + if (result == SQLITE_ROW) { + continue; + } + if (result != SQLITE_DONE) { + isFailed = true; + } + break; + } + + sqlite3_finalize(statement); + + if (isFailed) { + throw NitroSQLiteException::SqlExecution(sqlite3_errmsg(db)); + } + + int rowsAffected = sqlite3_changes(db); + return {.rowsAffected = rowsAffected}; } SQLiteOperationResult sqliteExecuteLiteral(const std::string& dbName, const std::string& query) { diff --git a/packages/react-native-nitro-sqlite/cpp/operations.hpp b/packages/react-native-nitro-sqlite/cpp/operations.hpp index f485bdf5..6f5de45c 100644 --- a/packages/react-native-nitro-sqlite/cpp/operations.hpp +++ b/packages/react-native-nitro-sqlite/cpp/operations.hpp @@ -19,6 +19,9 @@ void sqliteDetachDb(const std::string& mainDBName, const std::string& alias); std::shared_ptr sqliteExecute(const std::string& dbName, const std::string& query, const std::optional& params); +SQLiteOperationResult sqliteExecuteForRowsAffected(const std::string& dbName, const std::string& query, + const std::optional& params); + SQLiteOperationResult sqliteExecuteLiteral(const std::string& dbName, const std::string& query); void sqliteCloseAll(); diff --git a/packages/react-native-nitro-sqlite/cpp/sqliteExecuteBatch.cpp b/packages/react-native-nitro-sqlite/cpp/sqliteExecuteBatch.cpp index ed4be4d8..e698eed8 100644 --- a/packages/react-native-nitro-sqlite/cpp/sqliteExecuteBatch.cpp +++ b/packages/react-native-nitro-sqlite/cpp/sqliteExecuteBatch.cpp @@ -42,11 +42,11 @@ SQLiteOperationResult sqliteExecuteBatch(const std::string& dbName, const std::v int rowsAffected = 0; sqliteExecuteLiteral(dbName, "BEGIN EXCLUSIVE TRANSACTION"); for (int i = 0; i < commandCount; i++) { - const auto command = commands.at(i); + const auto& command = commands.at(i); // Batch only aggregates rowsAffected; per-command result rows are discarded. - auto result = sqliteExecute(dbName, command.sql, command.params); - rowsAffected += result->getRowsAffected(); + auto result = sqliteExecuteForRowsAffected(dbName, command.sql, command.params); + rowsAffected += result.rowsAffected; } sqliteExecuteLiteral(dbName, "COMMIT"); return {