From a0c803d1a2b8c4ce8c58f11fd5e5db4e25197f49 Mon Sep 17 00:00:00 2001 From: chrispader Date: Tue, 14 Jul 2026 15:08:40 +0200 Subject: [PATCH 1/5] 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/5] 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 { From d73e9de221274e0925a4be2b1418c0b6c2620250 Mon Sep 17 00:00:00 2001 From: chrispader Date: Fri, 17 Jul 2026 15:31:01 +0200 Subject: [PATCH 3/5] test: cover query result external memory --- example/tests/unit/index.ts | 3 +++ .../tests/unit/specs/externalMemory.spec.ts | 26 +++++++++++++++++++ 2 files changed, 29 insertions(+) create mode 100644 example/tests/unit/specs/externalMemory.spec.ts diff --git a/example/tests/unit/index.ts b/example/tests/unit/index.ts index 5a51835b..442b4c76 100644 --- a/example/tests/unit/index.ts +++ b/example/tests/unit/index.ts @@ -6,6 +6,7 @@ import registerExecuteBatchUnitTests from './specs/operations/executeBatch.spec' import registerTypeORMUnitTestsSpecs from './specs/typeorm.spec' import registerDatabaseQueueUnitTests from './specs/DatabaseQueue.spec' import registerSqliteVecUnitTestsSpecs from './specs/sqlite-vec.spec' +import registerExternalMemoryUnitTests from './specs/externalMemory.spec' export function registerUnitTests() { beforeEach(setupTestDb) @@ -16,6 +17,8 @@ export function registerUnitTests() { registerExecuteBatchUnitTests() }) + registerExternalMemoryUnitTests() + registerDatabaseQueueUnitTests() } diff --git a/example/tests/unit/specs/externalMemory.spec.ts b/example/tests/unit/specs/externalMemory.spec.ts new file mode 100644 index 00000000..9e07905f --- /dev/null +++ b/example/tests/unit/specs/externalMemory.spec.ts @@ -0,0 +1,26 @@ +import { describe, expect, it } from '@tests/TestApi' +import { testDb } from '@tests/db' + +const LARGE_RESULT_ROW_COUNT = 25_000 + +export default function registerExternalMemoryUnitTests() { + describe('External memory', () => { + it('does not reject a large query result as too much external memory', () => { + const result = testDb.execute(` + WITH RECURSIVE numbers(value) AS ( + SELECT 1 + UNION ALL + SELECT value + 1 FROM numbers WHERE value < ${LARGE_RESULT_ROW_COUNT} + ) + SELECT value FROM numbers + `) + + expect(result.rows).not.toBe(undefined) + expect(result.rows?.length).toBe(LARGE_RESULT_ROW_COUNT) + expect(result.rows?.item(0)).toEqual({ value: 1 }) + expect(result.rows?.item(LARGE_RESULT_ROW_COUNT - 1)).toEqual({ + value: LARGE_RESULT_ROW_COUNT, + }) + }) + }) +} From 6fbc0ba8aa4dde49e6b6b04002c3cca7f5c3b89b Mon Sep 17 00:00:00 2001 From: chrispader Date: Thu, 23 Jul 2026 15:56:40 +0200 Subject: [PATCH 4/5] Revert "test: cover query result external memory" This reverts commit d73e9de221274e0925a4be2b1418c0b6c2620250. --- example/tests/unit/index.ts | 3 --- .../tests/unit/specs/externalMemory.spec.ts | 26 ------------------- 2 files changed, 29 deletions(-) delete mode 100644 example/tests/unit/specs/externalMemory.spec.ts diff --git a/example/tests/unit/index.ts b/example/tests/unit/index.ts index 442b4c76..5a51835b 100644 --- a/example/tests/unit/index.ts +++ b/example/tests/unit/index.ts @@ -6,7 +6,6 @@ import registerExecuteBatchUnitTests from './specs/operations/executeBatch.spec' import registerTypeORMUnitTestsSpecs from './specs/typeorm.spec' import registerDatabaseQueueUnitTests from './specs/DatabaseQueue.spec' import registerSqliteVecUnitTestsSpecs from './specs/sqlite-vec.spec' -import registerExternalMemoryUnitTests from './specs/externalMemory.spec' export function registerUnitTests() { beforeEach(setupTestDb) @@ -17,8 +16,6 @@ export function registerUnitTests() { registerExecuteBatchUnitTests() }) - registerExternalMemoryUnitTests() - registerDatabaseQueueUnitTests() } diff --git a/example/tests/unit/specs/externalMemory.spec.ts b/example/tests/unit/specs/externalMemory.spec.ts deleted file mode 100644 index 9e07905f..00000000 --- a/example/tests/unit/specs/externalMemory.spec.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { describe, expect, it } from '@tests/TestApi' -import { testDb } from '@tests/db' - -const LARGE_RESULT_ROW_COUNT = 25_000 - -export default function registerExternalMemoryUnitTests() { - describe('External memory', () => { - it('does not reject a large query result as too much external memory', () => { - const result = testDb.execute(` - WITH RECURSIVE numbers(value) AS ( - SELECT 1 - UNION ALL - SELECT value + 1 FROM numbers WHERE value < ${LARGE_RESULT_ROW_COUNT} - ) - SELECT value FROM numbers - `) - - expect(result.rows).not.toBe(undefined) - expect(result.rows?.length).toBe(LARGE_RESULT_ROW_COUNT) - expect(result.rows?.item(0)).toEqual({ value: 1 }) - expect(result.rows?.item(LARGE_RESULT_ROW_COUNT - 1)).toEqual({ - value: LARGE_RESULT_ROW_COUNT, - }) - }) - }) -} From f1f3a690ebf419535e030d1edea01f12908a223b Mon Sep 17 00:00:00 2001 From: chrispader Date: Thu, 23 Jul 2026 15:56:44 +0200 Subject: [PATCH 5/5] Revert "fix: improve SQLite result memory handling" This reverts commit fb4c48415428f54995a29c8e1d8897e434c4cc09. --- .../HybridNitroSQLiteQueryResult.cpp | 33 ++----------- .../HybridNitroSQLiteQueryResult.hpp | 2 +- .../cpp/operations.cpp | 47 ++----------------- .../cpp/operations.hpp | 3 -- .../cpp/sqliteExecuteBatch.cpp | 6 +-- 5 files changed, 13 insertions(+), 78 deletions(-) diff --git a/packages/react-native-nitro-sqlite/cpp/hybridObjects/HybridNitroSQLiteQueryResult.cpp b/packages/react-native-nitro-sqlite/cpp/hybridObjects/HybridNitroSQLiteQueryResult.cpp index 93d10a66..81aedb33 100644 --- a/packages/react-native-nitro-sqlite/cpp/hybridObjects/HybridNitroSQLiteQueryResult.cpp +++ b/packages/react-native-nitro-sqlite/cpp/hybridObjects/HybridNitroSQLiteQueryResult.cpp @@ -10,23 +10,6 @@ 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: @@ -35,15 +18,9 @@ namespace { */ size_t getRowExternalMemorySize(const SQLiteQueryResultRow& row) { size_t bucketMemory = row.bucket_count() * sizeof(void*); - 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; + constexpr size_t nodePadding = 24; + size_t nodesMemory = row.size() * (sizeof(std::pair) + nodePadding); + return bucketMemory + nodesMemory; } /** @@ -72,10 +49,10 @@ namespace { * - Metadata contents, especially the `name` string on each metadata entry. */ size_t getMetadataExternalMemorySize(const SQLiteQueryTableMetadata& metadata) { - size_t size = metadata.bucket_count() * sizeof(void*); - size += metadata.size() * (sizeof(SQLiteQueryTableMetadata::value_type) + nodePadding); + size_t size = 0; 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 144f3f7e..f70c6063 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(std::move(metadata)) {} + : HybridObject(TAG), _insertId(insertId), _rowsAffected(rowsAffected), _results(std::move(results)), _metadata(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 90100623..289defe5 100644 --- a/packages/react-native-nitro-sqlite/cpp/operations.cpp +++ b/packages/react-native-nitro-sqlite/cpp/operations.cpp @@ -25,6 +25,7 @@ using namespace margelo::nitro::rnnitrosqlite; namespace margelo::rnnitrosqlite { + static constexpr double kInt64MinAsDouble = static_cast(std::numeric_limits::min()); static constexpr double kInt64UpperBoundAsDouble = -kInt64MinAsDouble; @@ -127,7 +128,8 @@ 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); @@ -256,48 +258,7 @@ 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(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}; + return std::make_shared(results, static_cast(latestInsertRowId), rowsAffected, metadata); } 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 6f5de45c..f485bdf5 100644 --- a/packages/react-native-nitro-sqlite/cpp/operations.hpp +++ b/packages/react-native-nitro-sqlite/cpp/operations.hpp @@ -19,9 +19,6 @@ 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 e698eed8..ed4be4d8 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 = sqliteExecuteForRowsAffected(dbName, command.sql, command.params); - rowsAffected += result.rowsAffected; + auto result = sqliteExecute(dbName, command.sql, command.params); + rowsAffected += result->getRowsAffected(); } sqliteExecuteLiteral(dbName, "COMMIT"); return {