Skip to content

Commit fb4c484

Browse files
committed
fix: improve SQLite result memory handling
1 parent a0c803d commit fb4c484

5 files changed

Lines changed: 78 additions & 13 deletions

File tree

packages/react-native-nitro-sqlite/cpp/hybridObjects/HybridNitroSQLiteQueryResult.cpp

Lines changed: 28 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,23 @@ namespace margelo::nitro::rnnitrosqlite {
1010

1111
namespace {
1212

13+
constexpr size_t nodePadding = 24;
14+
15+
size_t getValueExternalMemorySize(const SQLiteValue& value) {
16+
if (std::holds_alternative<std::string>(value)) {
17+
return std::get<std::string>(value).capacity();
18+
}
19+
20+
if (std::holds_alternative<std::shared_ptr<ArrayBuffer>>(value)) {
21+
const auto& buffer = std::get<std::shared_ptr<ArrayBuffer>>(value);
22+
if (buffer != nullptr && buffer->isOwner()) {
23+
return buffer->size();
24+
}
25+
}
26+
27+
return 0;
28+
}
29+
1330
/**
1431
* Compute the approximate external memory size of a single result row.
1532
* This includes:
@@ -18,9 +35,15 @@ namespace {
1835
*/
1936
size_t getRowExternalMemorySize(const SQLiteQueryResultRow& row) {
2037
size_t bucketMemory = row.bucket_count() * sizeof(void*);
21-
constexpr size_t nodePadding = 24;
22-
size_t nodesMemory = row.size() * (sizeof(std::pair<std::string, SQLiteValue>) + nodePadding);
23-
return bucketMemory + nodesMemory;
38+
size_t nodesMemory = row.size() * (sizeof(SQLiteQueryResultRow::value_type) + nodePadding);
39+
size_t contentsMemory = 0;
40+
41+
for (const auto& [columnName, value] : row) {
42+
contentsMemory += columnName.capacity();
43+
contentsMemory += getValueExternalMemorySize(value);
44+
}
45+
46+
return bucketMemory + nodesMemory + contentsMemory;
2447
}
2548

2649
/**
@@ -49,10 +72,10 @@ namespace {
4972
* - Metadata contents, especially the `name` string on each metadata entry.
5073
*/
5174
size_t getMetadataExternalMemorySize(const SQLiteQueryTableMetadata& metadata) {
52-
size_t size = 0;
75+
size_t size = metadata.bucket_count() * sizeof(void*);
76+
size += metadata.size() * (sizeof(SQLiteQueryTableMetadata::value_type) + nodePadding);
5377

5478
for (const auto& [columnName, columnMeta] : metadata) {
55-
5679
size += columnName.capacity();
5780
size += columnMeta.name.capacity();
5881
}

packages/react-native-nitro-sqlite/cpp/hybridObjects/HybridNitroSQLiteQueryResult.hpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ class HybridNitroSQLiteQueryResult : public HybridNitroSQLiteQueryResultSpec {
1313
HybridNitroSQLiteQueryResult() : HybridObject(TAG) {}
1414
HybridNitroSQLiteQueryResult(SQLiteQueryResults results, std::optional<double> insertId, double rowsAffected,
1515
std::optional<SQLiteQueryTableMetadata> metadata)
16-
: HybridObject(TAG), _insertId(insertId), _rowsAffected(rowsAffected), _results(std::move(results)), _metadata(metadata) {}
16+
: HybridObject(TAG), _insertId(insertId), _rowsAffected(rowsAffected), _results(std::move(results)), _metadata(std::move(metadata)) {}
1717

1818
private:
1919
std::optional<double> _insertId;

packages/react-native-nitro-sqlite/cpp/operations.cpp

Lines changed: 43 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,6 @@ using namespace margelo::nitro::rnnitrosqlite;
2525

2626
namespace margelo::rnnitrosqlite {
2727

28-
2928
static constexpr double kInt64MinAsDouble = static_cast<double>(std::numeric_limits<int64_t>::min());
3029
static constexpr double kInt64UpperBoundAsDouble = -kInt64MinAsDouble;
3130

@@ -128,8 +127,7 @@ void bindStatement(sqlite3_stmt* statement, const SQLiteQueryParams& values) {
128127
} else if (std::holds_alternative<double>(value)) {
129128
// Bind whole numbers as INTEGER so vec0 rowid/pk/partition (which reject REAL) work; SQLite still coerces to REAL for REAL columns.
130129
double doubleValue = std::get<double>(value);
131-
if (std::trunc(doubleValue) == doubleValue && doubleValue >= kInt64MinAsDouble &&
132-
doubleValue < kInt64UpperBoundAsDouble) {
130+
if (std::trunc(doubleValue) == doubleValue && doubleValue >= kInt64MinAsDouble && doubleValue < kInt64UpperBoundAsDouble) {
133131
sqlite3_bind_int64(statement, sqliteIndex, static_cast<sqlite3_int64>(doubleValue));
134132
} else {
135133
sqlite3_bind_double(statement, sqliteIndex, doubleValue);
@@ -258,7 +256,48 @@ std::shared_ptr<HybridNitroSQLiteQueryResult> sqliteExecute(const std::string& d
258256

259257
int rowsAffected = sqlite3_changes(db);
260258
long long latestInsertRowId = sqlite3_last_insert_rowid(db);
261-
return std::make_shared<HybridNitroSQLiteQueryResult>(results, static_cast<double>(latestInsertRowId), rowsAffected, metadata);
259+
return std::make_shared<HybridNitroSQLiteQueryResult>(std::move(results), static_cast<double>(latestInsertRowId), rowsAffected,
260+
std::move(metadata));
261+
}
262+
263+
SQLiteOperationResult sqliteExecuteForRowsAffected(const std::string& dbName, const std::string& query,
264+
const std::optional<SQLiteQueryParams>& params) {
265+
if (dbMap.count(dbName) == 0) {
266+
throw NitroSQLiteException::DatabaseNotOpen(dbName);
267+
}
268+
269+
auto db = dbMap[dbName];
270+
271+
sqlite3_stmt* statement;
272+
int statementStatus = sqlite3_prepare_v2(db, query.c_str(), -1, &statement, NULL);
273+
if (statementStatus == SQLITE_OK) {
274+
if (params) {
275+
bindStatement(statement, *params);
276+
}
277+
} else {
278+
throw NitroSQLiteException::SqlExecution(sqlite3_errmsg(db));
279+
}
280+
281+
bool isFailed = false;
282+
while (true) {
283+
int result = sqlite3_step(statement);
284+
if (result == SQLITE_ROW) {
285+
continue;
286+
}
287+
if (result != SQLITE_DONE) {
288+
isFailed = true;
289+
}
290+
break;
291+
}
292+
293+
sqlite3_finalize(statement);
294+
295+
if (isFailed) {
296+
throw NitroSQLiteException::SqlExecution(sqlite3_errmsg(db));
297+
}
298+
299+
int rowsAffected = sqlite3_changes(db);
300+
return {.rowsAffected = rowsAffected};
262301
}
263302

264303
SQLiteOperationResult sqliteExecuteLiteral(const std::string& dbName, const std::string& query) {

packages/react-native-nitro-sqlite/cpp/operations.hpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,9 @@ void sqliteDetachDb(const std::string& mainDBName, const std::string& alias);
1919
std::shared_ptr<HybridNitroSQLiteQueryResult> sqliteExecute(const std::string& dbName, const std::string& query,
2020
const std::optional<SQLiteQueryParams>& params);
2121

22+
SQLiteOperationResult sqliteExecuteForRowsAffected(const std::string& dbName, const std::string& query,
23+
const std::optional<SQLiteQueryParams>& params);
24+
2225
SQLiteOperationResult sqliteExecuteLiteral(const std::string& dbName, const std::string& query);
2326

2427
void sqliteCloseAll();

packages/react-native-nitro-sqlite/cpp/sqliteExecuteBatch.cpp

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -42,11 +42,11 @@ SQLiteOperationResult sqliteExecuteBatch(const std::string& dbName, const std::v
4242
int rowsAffected = 0;
4343
sqliteExecuteLiteral(dbName, "BEGIN EXCLUSIVE TRANSACTION");
4444
for (int i = 0; i < commandCount; i++) {
45-
const auto command = commands.at(i);
45+
const auto& command = commands.at(i);
4646

4747
// Batch only aggregates rowsAffected; per-command result rows are discarded.
48-
auto result = sqliteExecute(dbName, command.sql, command.params);
49-
rowsAffected += result->getRowsAffected();
48+
auto result = sqliteExecuteForRowsAffected(dbName, command.sql, command.params);
49+
rowsAffected += result.rowsAffected;
5050
}
5151
sqliteExecuteLiteral(dbName, "COMMIT");
5252
return {

0 commit comments

Comments
 (0)