react-native-nitro-sqlite 9.6.0 → 9.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/RNNitroSQLite.podspec +56 -20
- package/android/CMakeLists.txt +15 -0
- package/android/build.gradle +14 -0
- package/cpp/NitroSQLiteException.hpp +5 -0
- package/cpp/databaseMigration.cpp +123 -0
- package/cpp/databaseMigration.hpp +13 -0
- package/cpp/hybridObjects/HybridNitroSQLite.cpp +65 -16
- package/cpp/hybridObjects/HybridNitroSQLite.hpp +5 -0
- package/cpp/hybridObjects/HybridNitroSQLiteQueryResult.cpp +5 -4
- package/cpp/hybridObjects/HybridNitroSQLiteQueryResult.hpp +1 -1
- package/cpp/importSqlFile.cpp +10 -5
- package/cpp/importSqlFile.hpp +5 -1
- package/cpp/operations.cpp +232 -168
- package/cpp/operations.hpp +31 -1
- package/cpp/sqliteExecuteBatch.cpp +17 -16
- package/cpp/sqliteExecuteBatch.hpp +4 -0
- package/ios/OnLoad.mm +26 -3
- package/lib/commonjs/DatabaseQueue.js +4 -17
- package/lib/commonjs/DatabaseQueue.js.map +1 -1
- package/lib/commonjs/operations/execute.js +28 -11
- package/lib/commonjs/operations/execute.js.map +1 -1
- package/lib/commonjs/operations/session.js +34 -9
- package/lib/commonjs/operations/session.js.map +1 -1
- package/lib/commonjs/operations/transaction.js +5 -5
- package/lib/commonjs/operations/transaction.js.map +1 -1
- package/lib/module/DatabaseQueue.js +4 -15
- package/lib/module/DatabaseQueue.js.map +1 -1
- package/lib/module/operations/execute.js +24 -12
- package/lib/module/operations/execute.js.map +1 -1
- package/lib/module/operations/session.js +36 -11
- package/lib/module/operations/session.js.map +1 -1
- package/lib/module/operations/transaction.js +6 -6
- package/lib/module/operations/transaction.js.map +1 -1
- package/lib/typescript/commonjs/DatabaseQueue.d.ts +1 -3
- package/lib/typescript/commonjs/DatabaseQueue.d.ts.map +1 -1
- package/lib/typescript/commonjs/operations/execute.d.ts +6 -0
- package/lib/typescript/commonjs/operations/execute.d.ts.map +1 -1
- package/lib/typescript/commonjs/operations/session.d.ts.map +1 -1
- package/lib/typescript/commonjs/specs/NitroSQLite.nitro.d.ts.map +1 -1
- package/lib/typescript/commonjs/specs/NitroSQLiteQueryResult.nitro.d.ts.map +1 -1
- package/lib/typescript/module/DatabaseQueue.d.ts +1 -3
- package/lib/typescript/module/DatabaseQueue.d.ts.map +1 -1
- package/lib/typescript/module/operations/execute.d.ts +6 -0
- package/lib/typescript/module/operations/execute.d.ts.map +1 -1
- package/lib/typescript/module/operations/session.d.ts.map +1 -1
- package/lib/typescript/module/specs/NitroSQLite.nitro.d.ts.map +1 -1
- package/lib/typescript/module/specs/NitroSQLiteQueryResult.nitro.d.ts.map +1 -1
- package/nitrogen/generated/android/RNNitroSQLiteOnLoad.cpp +2 -2
- package/nitrogen/generated/android/c++/JHybridNitroSQLiteOnLoadSpec.hpp +2 -2
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/rnnitrosqlite/HybridNitroSQLiteOnLoadSpec.kt +2 -0
- package/nitrogen/generated/ios/RNNitroSQLite+autolinking.rb +2 -0
- package/package.json +9 -7
- package/src/DatabaseQueue.ts +9 -19
- package/src/operations/execute.ts +52 -12
- package/src/operations/session.ts +57 -11
- package/src/operations/transaction.ts +6 -6
- package/src/specs/NitroSQLite.nitro.ts +4 -1
- package/src/specs/NitroSQLiteQueryResult.nitro.ts +4 -1
- package/android/.gradle/8.9/checksums/checksums.lock +0 -0
- package/android/.gradle/8.9/checksums/md5-checksums.bin +0 -0
- package/android/.gradle/8.9/checksums/sha1-checksums.bin +0 -0
- package/android/.gradle/8.9/dependencies-accessors/gc.properties +0 -0
- package/android/.gradle/8.9/fileChanges/last-build.bin +0 -0
- package/android/.gradle/8.9/fileHashes/fileHashes.lock +0 -0
- package/android/.gradle/8.9/gc.properties +0 -0
- package/android/.gradle/buildOutputCleanup/buildOutputCleanup.lock +0 -0
- package/android/.gradle/buildOutputCleanup/cache.properties +0 -2
- package/android/.gradle/vcs-1/gc.properties +0 -0
package/cpp/operations.cpp
CHANGED
|
@@ -7,68 +7,131 @@
|
|
|
7
7
|
#include <cmath>
|
|
8
8
|
#include <ctime>
|
|
9
9
|
#include <iostream>
|
|
10
|
+
#include <limits>
|
|
10
11
|
#include <map>
|
|
12
|
+
#include <memory>
|
|
13
|
+
#include <mutex>
|
|
11
14
|
#include <optional>
|
|
12
15
|
#include <sqlite3.h>
|
|
13
16
|
#include <sstream>
|
|
14
17
|
#include <unistd.h>
|
|
15
18
|
|
|
19
|
+
#ifdef NITRO_SQLITE_VEC
|
|
20
|
+
// Angle-bracket so it resolves via -I (CocoaPods intercepts quoted includes).
|
|
21
|
+
#include <registerVectorExtensions.hpp>
|
|
22
|
+
#endif
|
|
23
|
+
|
|
16
24
|
using namespace facebook;
|
|
17
25
|
using namespace margelo::nitro;
|
|
18
26
|
using namespace margelo::nitro::rnnitrosqlite;
|
|
19
27
|
|
|
20
28
|
namespace margelo::rnnitrosqlite {
|
|
21
29
|
|
|
22
|
-
|
|
30
|
+
static constexpr double kInt64MinAsDouble = static_cast<double>(std::numeric_limits<int64_t>::min());
|
|
31
|
+
static constexpr double kInt64UpperBoundAsDouble = -kInt64MinAsDouble;
|
|
32
|
+
|
|
33
|
+
namespace {
|
|
34
|
+
|
|
35
|
+
std::map<std::string, SQLiteConnectionPtr> dbMap;
|
|
36
|
+
std::mutex dbMapMutex;
|
|
37
|
+
std::mutex dbLifecycleMutex;
|
|
38
|
+
|
|
39
|
+
} // namespace
|
|
40
|
+
|
|
41
|
+
SQLiteConnection::SQLiteConnection(std::string connectionName, sqlite3* database) : name(std::move(connectionName)), database(database) {}
|
|
42
|
+
|
|
43
|
+
SQLiteConnection::~SQLiteConnection() {
|
|
44
|
+
close();
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
void SQLiteConnection::close() noexcept {
|
|
48
|
+
std::lock_guard lock(mutex);
|
|
49
|
+
if (database == nullptr) {
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
sqlite3_close_v2(database);
|
|
54
|
+
database = nullptr;
|
|
55
|
+
}
|
|
23
56
|
|
|
24
57
|
void sqliteOpenDb(const std::string& dbName, const std::string& docPath) {
|
|
58
|
+
std::lock_guard lifecycleLock(dbLifecycleMutex);
|
|
59
|
+
{
|
|
60
|
+
std::lock_guard lock(dbMapMutex);
|
|
61
|
+
if (dbMap.contains(dbName)) {
|
|
62
|
+
throw NitroSQLiteException::DatabaseAlreadyOpen(dbName);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
#ifdef NITRO_SQLITE_VEC
|
|
67
|
+
// Register before opening so the connection exposes vec0 + vec_*.
|
|
68
|
+
margelo::rnnitrosqlitevec::registerVectorExtensions();
|
|
69
|
+
#endif
|
|
70
|
+
|
|
25
71
|
std::string dbPath = get_db_path(dbName, docPath);
|
|
26
72
|
|
|
27
73
|
int sqlOpenFlags = SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE | SQLITE_OPEN_FULLMUTEX;
|
|
28
74
|
|
|
29
|
-
sqlite3*
|
|
30
|
-
int
|
|
31
|
-
|
|
75
|
+
sqlite3* rawDatabase = nullptr;
|
|
76
|
+
const int openStatus = sqlite3_open_v2(dbPath.c_str(), &rawDatabase, sqlOpenFlags, nullptr);
|
|
77
|
+
std::unique_ptr<sqlite3, decltype(&sqlite3_close_v2)> database(rawDatabase, sqlite3_close_v2);
|
|
78
|
+
|
|
79
|
+
if (openStatus != SQLITE_OK) {
|
|
80
|
+
const std::string errorMessage = rawDatabase == nullptr ? sqlite3_errstr(openStatus) : sqlite3_errmsg(rawDatabase);
|
|
81
|
+
throw NitroSQLiteException(NitroSQLiteExceptionType::DatabaseCannotBeOpened, errorMessage);
|
|
82
|
+
}
|
|
32
83
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
84
|
+
auto connection = std::make_shared<SQLiteConnection>(dbName, database.get());
|
|
85
|
+
database.release();
|
|
86
|
+
{
|
|
87
|
+
std::lock_guard lock(dbMapMutex);
|
|
88
|
+
const bool inserted = dbMap.emplace(dbName, connection).second;
|
|
89
|
+
if (!inserted) {
|
|
90
|
+
throw NitroSQLiteException::DatabaseAlreadyOpen(dbName);
|
|
91
|
+
}
|
|
37
92
|
}
|
|
38
93
|
}
|
|
39
94
|
|
|
40
95
|
void sqliteCloseDb(const std::string& dbName) {
|
|
96
|
+
std::lock_guard lifecycleLock(dbLifecycleMutex);
|
|
97
|
+
SQLiteConnectionPtr connection;
|
|
98
|
+
{
|
|
99
|
+
std::lock_guard lock(dbMapMutex);
|
|
100
|
+
auto iterator = dbMap.find(dbName);
|
|
101
|
+
if (iterator == dbMap.end()) {
|
|
102
|
+
throw NitroSQLiteException::DatabaseNotOpen(dbName);
|
|
103
|
+
}
|
|
41
104
|
|
|
42
|
-
|
|
43
|
-
|
|
105
|
+
connection = std::move(iterator->second);
|
|
106
|
+
dbMap.erase(iterator);
|
|
44
107
|
}
|
|
45
108
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
sqlite3_close_v2(db);
|
|
49
|
-
|
|
50
|
-
dbMap.erase(dbName);
|
|
109
|
+
connection->close();
|
|
51
110
|
}
|
|
52
111
|
|
|
53
112
|
void sqliteCloseAll() {
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
113
|
+
std::lock_guard lifecycleLock(dbLifecycleMutex);
|
|
114
|
+
std::map<std::string, SQLiteConnectionPtr> connections;
|
|
115
|
+
{
|
|
116
|
+
std::lock_guard lock(dbMapMutex);
|
|
117
|
+
connections.swap(dbMap);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
for (const auto& [_, connection] : connections) {
|
|
121
|
+
connection->close();
|
|
58
122
|
}
|
|
59
|
-
dbMap.clear();
|
|
60
123
|
}
|
|
61
124
|
|
|
62
125
|
void sqliteAttachDb(const std::string& mainDBName, const std::string& docPath, const std::string& databaseToAttach,
|
|
63
126
|
const std::string& alias) {
|
|
64
127
|
/**
|
|
65
|
-
* There is no need to check if mainDBName is opened because
|
|
128
|
+
* There is no need to check if mainDBName is opened because sqliteExecuteCommand will do that.
|
|
66
129
|
* */
|
|
67
130
|
std::string dbPath = get_db_path(databaseToAttach, docPath);
|
|
68
131
|
std::string statement = "ATTACH DATABASE '" + dbPath + "' AS " + alias;
|
|
69
132
|
|
|
70
133
|
try {
|
|
71
|
-
|
|
134
|
+
sqliteExecuteCommand(mainDBName, statement);
|
|
72
135
|
} catch (NitroSQLiteException& e) {
|
|
73
136
|
throw NitroSQLiteException(NitroSQLiteExceptionType::UnableToAttachToDatabase,
|
|
74
137
|
mainDBName + " was unable to attach another database: " + std::string(e.what()));
|
|
@@ -77,12 +140,12 @@ void sqliteAttachDb(const std::string& mainDBName, const std::string& docPath, c
|
|
|
77
140
|
|
|
78
141
|
void sqliteDetachDb(const std::string& mainDBName, const std::string& alias) {
|
|
79
142
|
/**
|
|
80
|
-
* There is no need to check if mainDBName is opened because
|
|
143
|
+
* There is no need to check if mainDBName is opened because sqliteExecuteCommand will do that.
|
|
81
144
|
* */
|
|
82
145
|
std::string statement = "DETACH DATABASE " + alias;
|
|
83
146
|
|
|
84
147
|
try {
|
|
85
|
-
|
|
148
|
+
sqliteExecuteCommand(mainDBName, statement);
|
|
86
149
|
} catch (NitroSQLiteException& e) {
|
|
87
150
|
throw NitroSQLiteException(NitroSQLiteExceptionType::UnableToAttachToDatabase,
|
|
88
151
|
mainDBName + " was unable to detach database: " + std::string(e.what()));
|
|
@@ -90,15 +153,26 @@ void sqliteDetachDb(const std::string& mainDBName, const std::string& alias) {
|
|
|
90
153
|
}
|
|
91
154
|
|
|
92
155
|
void sqliteRemoveDb(const std::string& dbName, const std::string& docPath) {
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
std::string dbFilePath = get_db_path(dbName, docPath);
|
|
156
|
+
std::lock_guard lifecycleLock(dbLifecycleMutex);
|
|
157
|
+
const std::string dbFilePath = get_db_path(dbName, docPath);
|
|
98
158
|
if (!file_exists(dbFilePath)) {
|
|
99
159
|
throw NitroSQLiteException::DatabaseFileNotFound(dbFilePath);
|
|
100
160
|
}
|
|
101
161
|
|
|
162
|
+
SQLiteConnectionPtr connection;
|
|
163
|
+
{
|
|
164
|
+
std::lock_guard lock(dbMapMutex);
|
|
165
|
+
auto iterator = dbMap.find(dbName);
|
|
166
|
+
if (iterator != dbMap.end()) {
|
|
167
|
+
connection = std::move(iterator->second);
|
|
168
|
+
dbMap.erase(iterator);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
if (connection) {
|
|
173
|
+
connection->close();
|
|
174
|
+
}
|
|
175
|
+
|
|
102
176
|
remove(dbFilePath.c_str());
|
|
103
177
|
}
|
|
104
178
|
|
|
@@ -111,7 +185,13 @@ void bindStatement(sqlite3_stmt* statement, const SQLiteQueryParams& values) {
|
|
|
111
185
|
} else if (std::holds_alternative<bool>(value)) {
|
|
112
186
|
sqlite3_bind_int(statement, sqliteIndex, std::get<bool>(value));
|
|
113
187
|
} else if (std::holds_alternative<double>(value)) {
|
|
114
|
-
|
|
188
|
+
// Bind whole numbers as INTEGER so vec0 rowid/pk/partition (which reject REAL) work; SQLite still coerces to REAL for REAL columns.
|
|
189
|
+
double doubleValue = std::get<double>(value);
|
|
190
|
+
if (std::trunc(doubleValue) == doubleValue && doubleValue >= kInt64MinAsDouble && doubleValue < kInt64UpperBoundAsDouble) {
|
|
191
|
+
sqlite3_bind_int64(statement, sqliteIndex, static_cast<sqlite3_int64>(doubleValue));
|
|
192
|
+
} else {
|
|
193
|
+
sqlite3_bind_double(statement, sqliteIndex, doubleValue);
|
|
194
|
+
}
|
|
115
195
|
} else if (std::holds_alternative<std::string>(value)) {
|
|
116
196
|
const auto stringValue = std::get<std::string>(value);
|
|
117
197
|
sqlite3_bind_text(statement, sqliteIndex, stringValue.c_str(), stringValue.length(), SQLITE_TRANSIENT);
|
|
@@ -122,173 +202,157 @@ void bindStatement(sqlite3_stmt* statement, const SQLiteQueryParams& values) {
|
|
|
122
202
|
}
|
|
123
203
|
}
|
|
124
204
|
|
|
125
|
-
|
|
126
|
-
const std::optional<SQLiteQueryParams>& params) {
|
|
127
|
-
if (dbMap.count(dbName) == 0) {
|
|
128
|
-
throw NitroSQLiteException::DatabaseNotOpen(dbName);
|
|
129
|
-
}
|
|
205
|
+
namespace {
|
|
130
206
|
|
|
131
|
-
|
|
207
|
+
struct SQLiteStatementFinalizer {
|
|
208
|
+
void operator()(sqlite3_stmt* statement) const noexcept {
|
|
209
|
+
if (statement != nullptr) {
|
|
210
|
+
sqlite3_finalize(statement);
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
};
|
|
214
|
+
|
|
215
|
+
using SQLiteStatement = std::unique_ptr<sqlite3_stmt, SQLiteStatementFinalizer>;
|
|
216
|
+
|
|
217
|
+
SQLiteStatement prepareStatement(sqlite3* db, const std::string& query, const std::optional<SQLiteQueryParams>& params) {
|
|
218
|
+
sqlite3_stmt* rawStatement = nullptr;
|
|
219
|
+
int statementStatus = sqlite3_prepare_v2(db, query.c_str(), -1, &rawStatement, nullptr);
|
|
220
|
+
SQLiteStatement statement(rawStatement);
|
|
221
|
+
|
|
222
|
+
if (statementStatus != SQLITE_OK) {
|
|
223
|
+
throw NitroSQLiteException::SqlExecution(sqlite3_errmsg(db));
|
|
224
|
+
}
|
|
132
225
|
|
|
133
|
-
sqlite3_stmt* statement;
|
|
134
|
-
int statementStatus = sqlite3_prepare_v2(db, query.c_str(), -1, &statement, NULL);
|
|
135
|
-
if (statementStatus == SQLITE_OK) // statement is correct, bind the passed parameters
|
|
136
|
-
{
|
|
137
226
|
if (params) {
|
|
138
|
-
bindStatement(statement, *params);
|
|
227
|
+
bindStatement(statement.get(), *params);
|
|
139
228
|
}
|
|
140
|
-
|
|
141
|
-
|
|
229
|
+
|
|
230
|
+
return statement;
|
|
142
231
|
}
|
|
143
232
|
|
|
144
|
-
|
|
145
|
-
|
|
233
|
+
template <typename OnRow>
|
|
234
|
+
void consumeStatement(sqlite3* db, sqlite3_stmt* statement, OnRow&& onRow) {
|
|
235
|
+
while (true) {
|
|
236
|
+
int result = sqlite3_step(statement);
|
|
146
237
|
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
SQLiteQueryResults results;
|
|
152
|
-
std::optional<SQLiteQueryTableMetadata> metadata = std::nullopt;
|
|
238
|
+
if (result == SQLITE_ROW) {
|
|
239
|
+
onRow(statement);
|
|
240
|
+
continue;
|
|
241
|
+
}
|
|
153
242
|
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
i = 0;
|
|
160
|
-
row = std::unordered_map<std::string, SQLiteValue>();
|
|
161
|
-
count = sqlite3_column_count(statement);
|
|
162
|
-
|
|
163
|
-
while (i < count) {
|
|
164
|
-
column_type = sqlite3_column_type(statement, i);
|
|
165
|
-
column_name = sqlite3_column_name(statement, i);
|
|
166
|
-
switch (column_type) {
|
|
167
|
-
|
|
168
|
-
case SQLITE_INTEGER: {
|
|
169
|
-
auto column_value = sqlite3_column_double(statement, i);
|
|
170
|
-
row[column_name] = column_value;
|
|
171
|
-
break;
|
|
172
|
-
}
|
|
173
|
-
case SQLITE_FLOAT: {
|
|
174
|
-
auto column_value = sqlite3_column_double(statement, i);
|
|
175
|
-
row[column_name] = column_value;
|
|
176
|
-
break;
|
|
177
|
-
}
|
|
178
|
-
case SQLITE_TEXT: {
|
|
179
|
-
auto column_value = reinterpret_cast<const char*>(sqlite3_column_text(statement, i));
|
|
180
|
-
sqlite3_column_bytes(statement, i);
|
|
181
|
-
row[column_name] = column_value;
|
|
182
|
-
break;
|
|
183
|
-
}
|
|
184
|
-
case SQLITE_BLOB: {
|
|
185
|
-
int blob_size = sqlite3_column_bytes(statement, i);
|
|
186
|
-
const void* blob = sqlite3_column_blob(statement, i);
|
|
187
|
-
// Copy the SQLite BLOB into a new native ArrayBuffer.
|
|
188
|
-
// This avoids manual memory management and unsafe pointer handling.
|
|
189
|
-
if (blob_size > 0) {
|
|
190
|
-
const auto* blob_data = reinterpret_cast<const uint8_t*>(blob);
|
|
191
|
-
row[column_name] = ArrayBuffer::copy(blob_data, static_cast<size_t>(blob_size));
|
|
192
|
-
} else {
|
|
193
|
-
// Represent empty BLOBs as an empty, but valid, ArrayBuffer.
|
|
194
|
-
row[column_name] = ArrayBuffer::allocate(0);
|
|
195
|
-
}
|
|
196
|
-
break;
|
|
197
|
-
}
|
|
198
|
-
case SQLITE_NULL:
|
|
199
|
-
// Intentionally left blank to switch to default case
|
|
200
|
-
default:
|
|
201
|
-
row[column_name] = NullType::null;
|
|
202
|
-
break;
|
|
203
|
-
}
|
|
204
|
-
i++;
|
|
205
|
-
}
|
|
206
|
-
results.push_back(std::move(row));
|
|
207
|
-
break;
|
|
208
|
-
case SQLITE_DONE:
|
|
209
|
-
i = 0;
|
|
210
|
-
count = sqlite3_column_count(statement);
|
|
211
|
-
while (i < count) {
|
|
212
|
-
column_name = sqlite3_column_name(statement, i);
|
|
213
|
-
const char* tp = sqlite3_column_decltype(statement, i);
|
|
214
|
-
column_declared_type = mapSQLiteTypeToColumnType(tp);
|
|
215
|
-
auto columnMeta = NitroSQLiteQueryColumnMetadata(std::move(column_name), std::move(column_declared_type), i);
|
|
216
|
-
|
|
217
|
-
if (!metadata) {
|
|
218
|
-
metadata = std::make_optional<SQLiteQueryTableMetadata>();
|
|
219
|
-
}
|
|
220
|
-
metadata->insert({column_name, columnMeta});
|
|
221
|
-
i++;
|
|
222
|
-
}
|
|
223
|
-
isConsuming = false;
|
|
224
|
-
break;
|
|
225
|
-
default:
|
|
226
|
-
isFailed = true;
|
|
227
|
-
isConsuming = false;
|
|
243
|
+
if (result == SQLITE_DONE) {
|
|
244
|
+
return;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
throw NitroSQLiteException::SqlExecution(sqlite3_errmsg(db));
|
|
228
248
|
}
|
|
229
249
|
}
|
|
230
250
|
|
|
231
|
-
|
|
251
|
+
} // namespace
|
|
232
252
|
|
|
233
|
-
|
|
234
|
-
|
|
253
|
+
SQLiteConnectionPtr sqliteGetOpenDatabase(const std::string& dbName) {
|
|
254
|
+
std::lock_guard lock(dbMapMutex);
|
|
255
|
+
auto iterator = dbMap.find(dbName);
|
|
256
|
+
if (iterator == dbMap.end()) {
|
|
257
|
+
throw NitroSQLiteException::DatabaseNotOpen(dbName);
|
|
235
258
|
}
|
|
236
259
|
|
|
237
|
-
|
|
238
|
-
long long latestInsertRowId = sqlite3_last_insert_rowid(db);
|
|
239
|
-
return std::make_shared<HybridNitroSQLiteQueryResult>(results, static_cast<double>(latestInsertRowId), rowsAffected, metadata);
|
|
260
|
+
return iterator->second;
|
|
240
261
|
}
|
|
241
262
|
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
263
|
+
std::shared_ptr<HybridNitroSQLiteQueryResult> sqliteExecute(const std::string& dbName, const std::string& query,
|
|
264
|
+
const std::optional<SQLiteQueryParams>& params) {
|
|
265
|
+
return sqliteExecute(sqliteGetOpenDatabase(dbName), query, params);
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
std::shared_ptr<HybridNitroSQLiteQueryResult> sqliteExecute(const SQLiteConnectionPtr& connection, const std::string& query,
|
|
269
|
+
const std::optional<SQLiteQueryParams>& params) {
|
|
270
|
+
std::lock_guard lock(connection->mutex);
|
|
271
|
+
sqlite3* db = connection->database;
|
|
272
|
+
if (db == nullptr) {
|
|
273
|
+
throw NitroSQLiteException::DatabaseNotOpen(connection->name);
|
|
246
274
|
}
|
|
247
275
|
|
|
248
|
-
|
|
276
|
+
auto statement = prepareStatement(db, query, params);
|
|
277
|
+
SQLiteQueryResults results;
|
|
249
278
|
|
|
250
|
-
|
|
251
|
-
|
|
279
|
+
consumeStatement(db, statement.get(), [&](sqlite3_stmt* currentStatement) {
|
|
280
|
+
SQLiteQueryResultRow row;
|
|
281
|
+
int count = sqlite3_column_count(currentStatement);
|
|
282
|
+
|
|
283
|
+
for (int i = 0; i < count; i++) {
|
|
284
|
+
int columnType = sqlite3_column_type(currentStatement, i);
|
|
285
|
+
std::string columnName = sqlite3_column_name(currentStatement, i);
|
|
286
|
+
|
|
287
|
+
switch (columnType) {
|
|
288
|
+
case SQLITE_INTEGER:
|
|
289
|
+
case SQLITE_FLOAT:
|
|
290
|
+
row[columnName] = sqlite3_column_double(currentStatement, i);
|
|
291
|
+
break;
|
|
292
|
+
case SQLITE_TEXT: {
|
|
293
|
+
auto columnValue = reinterpret_cast<const char*>(sqlite3_column_text(currentStatement, i));
|
|
294
|
+
row[columnName] = columnValue;
|
|
295
|
+
break;
|
|
296
|
+
}
|
|
297
|
+
case SQLITE_BLOB: {
|
|
298
|
+
int blobSize = sqlite3_column_bytes(currentStatement, i);
|
|
299
|
+
const void* blob = sqlite3_column_blob(currentStatement, i);
|
|
300
|
+
if (blobSize > 0) {
|
|
301
|
+
const auto* blobData = reinterpret_cast<const uint8_t*>(blob);
|
|
302
|
+
row[columnName] = ArrayBuffer::copy(blobData, static_cast<size_t>(blobSize));
|
|
303
|
+
} else {
|
|
304
|
+
row[columnName] = ArrayBuffer::allocate(0);
|
|
305
|
+
}
|
|
306
|
+
break;
|
|
307
|
+
}
|
|
308
|
+
case SQLITE_NULL:
|
|
309
|
+
default:
|
|
310
|
+
row[columnName] = NullType::null;
|
|
311
|
+
break;
|
|
312
|
+
}
|
|
313
|
+
}
|
|
252
314
|
|
|
253
|
-
|
|
254
|
-
|
|
315
|
+
results.push_back(std::move(row));
|
|
316
|
+
});
|
|
255
317
|
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
318
|
+
std::optional<SQLiteQueryTableMetadata> metadata = std::nullopt;
|
|
319
|
+
int count = sqlite3_column_count(statement.get());
|
|
320
|
+
for (int i = 0; i < count; i++) {
|
|
321
|
+
std::string columnName = sqlite3_column_name(statement.get(), i);
|
|
322
|
+
ColumnType columnDeclaredType = mapSQLiteTypeToColumnType(sqlite3_column_decltype(statement.get(), i));
|
|
323
|
+
auto columnMeta = NitroSQLiteQueryColumnMetadata(columnName, std::move(columnDeclaredType), i);
|
|
324
|
+
|
|
325
|
+
if (!metadata) {
|
|
326
|
+
metadata = std::make_optional<SQLiteQueryTableMetadata>();
|
|
327
|
+
}
|
|
328
|
+
metadata->insert({columnName, std::move(columnMeta)});
|
|
259
329
|
}
|
|
260
330
|
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
while (isConsuming) {
|
|
268
|
-
result = sqlite3_step(statement);
|
|
269
|
-
|
|
270
|
-
switch (result) {
|
|
271
|
-
case SQLITE_ROW:
|
|
272
|
-
isConsuming = true;
|
|
273
|
-
break;
|
|
331
|
+
int rowsAffected = sqlite3_changes(db);
|
|
332
|
+
long long latestInsertRowId = sqlite3_last_insert_rowid(db);
|
|
333
|
+
return std::make_shared<HybridNitroSQLiteQueryResult>(std::move(results), static_cast<double>(latestInsertRowId), rowsAffected,
|
|
334
|
+
std::move(metadata));
|
|
335
|
+
}
|
|
274
336
|
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
337
|
+
SQLiteOperationResult sqliteExecuteCommand(const std::string& dbName, const std::string& query,
|
|
338
|
+
const std::optional<SQLiteQueryParams>& params) {
|
|
339
|
+
return sqliteExecuteCommand(sqliteGetOpenDatabase(dbName), query, params);
|
|
340
|
+
}
|
|
278
341
|
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
342
|
+
SQLiteOperationResult sqliteExecuteCommand(const SQLiteConnectionPtr& connection, const std::string& query,
|
|
343
|
+
const std::optional<SQLiteQueryParams>& params) {
|
|
344
|
+
std::lock_guard lock(connection->mutex);
|
|
345
|
+
sqlite3* db = connection->database;
|
|
346
|
+
if (db == nullptr) {
|
|
347
|
+
throw NitroSQLiteException::DatabaseNotOpen(connection->name);
|
|
283
348
|
}
|
|
284
349
|
|
|
285
|
-
|
|
350
|
+
auto statement = prepareStatement(db, query, params);
|
|
351
|
+
bool isReadOnly = sqlite3_stmt_readonly(statement.get()) != 0;
|
|
286
352
|
|
|
287
|
-
|
|
288
|
-
throw NitroSQLiteException::SqlExecution(sqlite3_errmsg(db));
|
|
289
|
-
}
|
|
353
|
+
consumeStatement(db, statement.get(), [](sqlite3_stmt*) {});
|
|
290
354
|
|
|
291
|
-
return {.rowsAffected = sqlite3_changes(db)};
|
|
355
|
+
return {.rowsAffected = isReadOnly ? 0 : sqlite3_changes(db)};
|
|
292
356
|
}
|
|
293
357
|
|
|
294
358
|
} // namespace margelo::rnnitrosqlite
|
package/cpp/operations.hpp
CHANGED
|
@@ -2,9 +2,32 @@
|
|
|
2
2
|
|
|
3
3
|
#include "hybridObjects/HybridNitroSQLiteQueryResult.hpp"
|
|
4
4
|
#include "types.hpp"
|
|
5
|
+
#include <memory>
|
|
6
|
+
#include <mutex>
|
|
7
|
+
#include <sqlite3.h>
|
|
8
|
+
#include <string>
|
|
5
9
|
|
|
6
10
|
namespace margelo::rnnitrosqlite {
|
|
7
11
|
|
|
12
|
+
// Calls against one connection are serialized by `mutex`. Separate connections
|
|
13
|
+
// intentionally remain independent, so SQLITE_THREADSAFE=0 still requires the
|
|
14
|
+
// caller to serialize SQLite calls globally.
|
|
15
|
+
struct SQLiteConnection final {
|
|
16
|
+
SQLiteConnection(std::string name, sqlite3* database);
|
|
17
|
+
~SQLiteConnection();
|
|
18
|
+
|
|
19
|
+
SQLiteConnection(const SQLiteConnection&) = delete;
|
|
20
|
+
SQLiteConnection& operator=(const SQLiteConnection&) = delete;
|
|
21
|
+
|
|
22
|
+
void close() noexcept;
|
|
23
|
+
|
|
24
|
+
const std::string name;
|
|
25
|
+
sqlite3* database;
|
|
26
|
+
std::recursive_mutex mutex;
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
using SQLiteConnectionPtr = std::shared_ptr<SQLiteConnection>;
|
|
30
|
+
|
|
8
31
|
void sqliteOpenDb(const std::string& dbName, const std::string& docPath);
|
|
9
32
|
|
|
10
33
|
void sqliteCloseDb(const std::string& dbName);
|
|
@@ -16,10 +39,17 @@ void sqliteAttachDb(const std::string& mainDBName, const std::string& docPath, c
|
|
|
16
39
|
|
|
17
40
|
void sqliteDetachDb(const std::string& mainDBName, const std::string& alias);
|
|
18
41
|
|
|
42
|
+
SQLiteConnectionPtr sqliteGetOpenDatabase(const std::string& dbName);
|
|
43
|
+
|
|
19
44
|
std::shared_ptr<HybridNitroSQLiteQueryResult> sqliteExecute(const std::string& dbName, const std::string& query,
|
|
20
45
|
const std::optional<SQLiteQueryParams>& params);
|
|
46
|
+
std::shared_ptr<HybridNitroSQLiteQueryResult> sqliteExecute(const SQLiteConnectionPtr& connection, const std::string& query,
|
|
47
|
+
const std::optional<SQLiteQueryParams>& params);
|
|
21
48
|
|
|
22
|
-
SQLiteOperationResult
|
|
49
|
+
SQLiteOperationResult sqliteExecuteCommand(const std::string& dbName, const std::string& query,
|
|
50
|
+
const std::optional<SQLiteQueryParams>& params = std::nullopt);
|
|
51
|
+
SQLiteOperationResult sqliteExecuteCommand(const SQLiteConnectionPtr& connection, const std::string& query,
|
|
52
|
+
const std::optional<SQLiteQueryParams>& params = std::nullopt);
|
|
23
53
|
|
|
24
54
|
void sqliteCloseAll();
|
|
25
55
|
|
|
@@ -33,6 +33,11 @@ std::vector<BatchQuery> batchParamsToCommands(const std::vector<BatchQueryComman
|
|
|
33
33
|
}
|
|
34
34
|
|
|
35
35
|
SQLiteOperationResult sqliteExecuteBatch(const std::string& dbName, const std::vector<BatchQuery>& commands) {
|
|
36
|
+
return sqliteExecuteBatch(sqliteGetOpenDatabase(dbName), commands);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
SQLiteOperationResult sqliteExecuteBatch(const SQLiteConnectionPtr& connection, const std::vector<BatchQuery>& commands) {
|
|
40
|
+
std::lock_guard lock(connection->mutex);
|
|
36
41
|
size_t commandCount = commands.size();
|
|
37
42
|
if (commandCount <= 0) {
|
|
38
43
|
throw NitroSQLiteException(NitroSQLiteExceptionType::NoBatchCommandsProvided, "No SQL batch commands provided");
|
|
@@ -40,28 +45,24 @@ SQLiteOperationResult sqliteExecuteBatch(const std::string& dbName, const std::v
|
|
|
40
45
|
|
|
41
46
|
try {
|
|
42
47
|
int rowsAffected = 0;
|
|
43
|
-
|
|
44
|
-
for (
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
// We do not provide a datas tructure to receive query data because we don't need/want to handle this results in a batch execution
|
|
48
|
-
auto results = SQLiteQueryResults();
|
|
49
|
-
auto metadata = std::optional<SQLiteQueryTableMetadata>(std::nullopt);
|
|
50
|
-
try {
|
|
51
|
-
auto result = sqliteExecute(dbName, command.sql, command.params);
|
|
52
|
-
rowsAffected += result->getRowsAffected();
|
|
53
|
-
} catch (NitroSQLiteException& e) {
|
|
54
|
-
sqliteExecuteLiteral(dbName, "ROLLBACK");
|
|
55
|
-
throw e;
|
|
56
|
-
}
|
|
48
|
+
sqliteExecuteCommand(connection, "BEGIN EXCLUSIVE TRANSACTION");
|
|
49
|
+
for (const auto& command : commands) {
|
|
50
|
+
auto result = sqliteExecuteCommand(connection, command.sql, command.params);
|
|
51
|
+
rowsAffected += result.rowsAffected;
|
|
57
52
|
}
|
|
58
|
-
|
|
53
|
+
|
|
54
|
+
sqliteExecuteCommand(connection, "COMMIT");
|
|
59
55
|
return {
|
|
60
56
|
.rowsAffected = rowsAffected,
|
|
61
57
|
.commands = (int)commandCount,
|
|
62
58
|
};
|
|
63
59
|
} catch (NitroSQLiteException& e) {
|
|
64
|
-
|
|
60
|
+
// Roll back exactly once; a failed ROLLBACK must not mask the original error.
|
|
61
|
+
try {
|
|
62
|
+
sqliteExecuteCommand(connection, "ROLLBACK");
|
|
63
|
+
} catch (...) {
|
|
64
|
+
// ignore — surface the original error below
|
|
65
|
+
}
|
|
65
66
|
throw e;
|
|
66
67
|
}
|
|
67
68
|
}
|
|
@@ -5,12 +5,15 @@
|
|
|
5
5
|
|
|
6
6
|
#include "BatchQueryCommand.hpp"
|
|
7
7
|
#include "types.hpp"
|
|
8
|
+
#include <memory>
|
|
8
9
|
|
|
9
10
|
using namespace facebook;
|
|
10
11
|
using namespace margelo::nitro;
|
|
11
12
|
|
|
12
13
|
namespace margelo::rnnitrosqlite {
|
|
13
14
|
|
|
15
|
+
struct SQLiteConnection;
|
|
16
|
+
|
|
14
17
|
struct BatchQuery {
|
|
15
18
|
std::string sql;
|
|
16
19
|
std::optional<SQLiteQueryParams> params;
|
|
@@ -26,5 +29,6 @@ std::vector<BatchQuery> batchParamsToCommands(const std::vector<BatchQueryComman
|
|
|
26
29
|
* Execute a batch of commands in a exclusive transaction
|
|
27
30
|
*/
|
|
28
31
|
SQLiteOperationResult sqliteExecuteBatch(const std::string& dbName, const std::vector<BatchQuery>& commands);
|
|
32
|
+
SQLiteOperationResult sqliteExecuteBatch(const std::shared_ptr<SQLiteConnection>& connection, const std::vector<BatchQuery>& commands);
|
|
29
33
|
|
|
30
34
|
} // namespace margelo::rnnitrosqlite
|
package/ios/OnLoad.mm
CHANGED
|
@@ -30,9 +30,32 @@ using namespace margelo::nitro::rnnitrosqlite;
|
|
|
30
30
|
|
|
31
31
|
documentPath = [storeUrl path];
|
|
32
32
|
} else {
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
33
|
+
NSString *databaseLocation = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"RNNitroSQLite_DatabaseLocation"];
|
|
34
|
+
|
|
35
|
+
if ([databaseLocation isEqualToString:@"ApplicationSupport"]) {
|
|
36
|
+
// Library/Application Support is persistent, backed up, and never exposed to the user
|
|
37
|
+
// via the Files app (unlike the Documents directory, which becomes user-visible when
|
|
38
|
+
// the app enables file sharing).
|
|
39
|
+
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSApplicationSupportDirectory, NSUserDomainMask, true);
|
|
40
|
+
documentPath = [paths objectAtIndex:0];
|
|
41
|
+
|
|
42
|
+
NSFileManager *fileManager = [NSFileManager defaultManager];
|
|
43
|
+
if (![fileManager fileExistsAtPath:documentPath]) {
|
|
44
|
+
[fileManager createDirectoryAtPath:documentPath withIntermediateDirectories:YES attributes:nil error:nil];
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// Databases created before this option was enabled still live in Documents; each one is
|
|
48
|
+
// moved over when it is opened (see HybridNitroSQLite::open).
|
|
49
|
+
NSString *documentsDirectory = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, true) objectAtIndex:0];
|
|
50
|
+
HybridNitroSQLite::migrationDocPath = [documentsDirectory UTF8String];
|
|
51
|
+
} else {
|
|
52
|
+
if (databaseLocation != nil && ![databaseLocation isEqualToString:@"Documents"]) {
|
|
53
|
+
NSLog(@"Invalid RNNitroSQLite_DatabaseLocation value provided (%@). Supported values are \"Documents\" and \"ApplicationSupport\". Falling back to \"Documents\".", databaseLocation);
|
|
54
|
+
}
|
|
55
|
+
// Get iOS app's document directory (to safely store database .sqlite3 file)
|
|
56
|
+
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, true);
|
|
57
|
+
documentPath = [paths objectAtIndex:0];
|
|
58
|
+
}
|
|
36
59
|
}
|
|
37
60
|
|
|
38
61
|
HybridNitroSQLite::docPath = [documentPath UTF8String];
|