@op-engineering/op-sqlite 0.0.0-resolution-test
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/LICENSE +7 -0
- package/README.md +31 -0
- package/android/.project +17 -0
- package/android/.settings/org.eclipse.buildship.core.prefs +13 -0
- package/android/CMakeLists.txt +141 -0
- package/android/build.gradle +266 -0
- package/android/c_sources/tokenizers.cpp +88 -0
- package/android/c_sources/tokenizers.h +15 -0
- package/android/cpp-adapter.cpp +46 -0
- package/android/gradle.properties +1 -0
- package/android/jniLibs/arm64-v8a/libsql_experimental.a +0 -0
- package/android/jniLibs/armeabi-v7a/libsql_experimental.a +0 -0
- package/android/jniLibs/x86/libsql_experimental.a +0 -0
- package/android/jniLibs/x86_64/libsql_experimental.a +0 -0
- package/android/src/main/AndroidManifest.xml +1 -0
- package/android/src/main/java/com/op/sqlite/OPSQLiteBridge.kt +37 -0
- package/android/src/main/java/com/op/sqlite/OPSQLiteModule.kt +119 -0
- package/android/src/main/java/com/op/sqlite/OPSQLitePackage.kt +18 -0
- package/android/src/main/jniLibs/arm64-v8a/libcrsqlite.so +0 -0
- package/android/src/main/jniLibs/arm64-v8a/libsqlite_vec.so +0 -0
- package/android/src/main/jniLibs/armeabi-v7a/libcrsqlite.so +0 -0
- package/android/src/main/jniLibs/armeabi-v7a/libsqlite_vec.so +0 -0
- package/android/src/main/jniLibs/x86/libcrsqlite.so +0 -0
- package/android/src/main/jniLibs/x86/libsqlite_vec.so +0 -0
- package/android/src/main/jniLibs/x86_64/libcrsqlite.so +0 -0
- package/android/src/main/jniLibs/x86_64/libsqlite_vec.so +0 -0
- package/android/src/paper/java/com/op/sqlite/NativeOPSQLiteSpec.java +77 -0
- package/cpp/DBHostObject.cpp +852 -0
- package/cpp/DBHostObject.h +99 -0
- package/cpp/DumbHostObject.cpp +72 -0
- package/cpp/DumbHostObject.h +36 -0
- package/cpp/OPThreadPool.cpp +120 -0
- package/cpp/OPThreadPool.h +44 -0
- package/cpp/PreparedStatementHostObject.cpp +151 -0
- package/cpp/PreparedStatementHostObject.h +59 -0
- package/cpp/SmartHostObject.cpp +34 -0
- package/cpp/SmartHostObject.h +24 -0
- package/cpp/bindings.cpp +182 -0
- package/cpp/bindings.h +19 -0
- package/cpp/bridge.cpp +873 -0
- package/cpp/bridge.h +80 -0
- package/cpp/libsql/bridge.cpp +738 -0
- package/cpp/libsql/bridge.h +85 -0
- package/cpp/libsql/libsql.h +172 -0
- package/cpp/logs.h +40 -0
- package/cpp/macros.h +15 -0
- package/cpp/sqlcipher/sqlite3.c +262970 -0
- package/cpp/sqlcipher/sqlite3.h +13485 -0
- package/cpp/sqlite3.c +261454 -0
- package/cpp/sqlite3.h +13715 -0
- package/cpp/types.h +33 -0
- package/cpp/utils.cpp +327 -0
- package/cpp/utils.h +47 -0
- package/generate_tokenizers_header_file.rb +29 -0
- package/ios/OPSQLite.h +7 -0
- package/ios/OPSQLite.mm +157 -0
- package/ios/OPSQLite.xcodeproj/project.pbxproj +275 -0
- package/ios/crsqlite.xcframework/Info.plist +46 -0
- package/ios/crsqlite.xcframework/ios-arm64/crsqlite.framework/Info.plist +24 -0
- package/ios/crsqlite.xcframework/ios-arm64/crsqlite.framework/crsqlite +0 -0
- package/ios/crsqlite.xcframework/ios-arm64_x86_64-simulator/crsqlite.framework/Info.plist +24 -0
- package/ios/crsqlite.xcframework/ios-arm64_x86_64-simulator/crsqlite.framework/crsqlite +0 -0
- package/ios/libsql.xcframework/Info.plist +48 -0
- package/ios/libsql.xcframework/ios-arm64/Headers/libsql.h +172 -0
- package/ios/libsql.xcframework/ios-arm64/libsql_experimental.a +0 -0
- package/ios/libsql.xcframework/ios-arm64_x86_64-simulator/Headers/libsql.h +172 -0
- package/ios/libsql.xcframework/ios-arm64_x86_64-simulator/libsql_experimental.a +0 -0
- package/ios/sqlitevec.xcframework/Info.plist +71 -0
- package/ios/sqlitevec.xcframework/ios-arm64/sqlitevec.framework/Info.plist +24 -0
- package/ios/sqlitevec.xcframework/ios-arm64/sqlitevec.framework/sqlitevec +0 -0
- package/ios/sqlitevec.xcframework/ios-arm64_x86_64-simulator/sqlitevec.framework/Info.plist +24 -0
- package/ios/sqlitevec.xcframework/ios-arm64_x86_64-simulator/sqlitevec.framework/sqlitevec +0 -0
- package/ios/sqlitevec.xcframework/tvos-arm64/sqlitevec.framework/Info.plist +24 -0
- package/ios/sqlitevec.xcframework/tvos-arm64/sqlitevec.framework/sqlitevec +0 -0
- package/ios/sqlitevec.xcframework/tvos-arm64_x86_64-simulator/sqlitevec.framework/Info.plist +24 -0
- package/ios/sqlitevec.xcframework/tvos-arm64_x86_64-simulator/sqlitevec.framework/sqlitevec +0 -0
- package/lib/commonjs/NativeOPSQLite.js +9 -0
- package/lib/commonjs/NativeOPSQLite.js.map +1 -0
- package/lib/commonjs/Storage.js +60 -0
- package/lib/commonjs/Storage.js.map +1 -0
- package/lib/commonjs/index.js +365 -0
- package/lib/commonjs/index.js.map +1 -0
- package/lib/module/NativeOPSQLite.js +3 -0
- package/lib/module/NativeOPSQLite.js.map +1 -0
- package/lib/module/Storage.js +53 -0
- package/lib/module/Storage.js.map +1 -0
- package/lib/module/index.js +340 -0
- package/lib/module/index.js.map +1 -0
- package/lib/typescript/src/NativeOPSQLite.d.ts +15 -0
- package/lib/typescript/src/NativeOPSQLite.d.ts.map +1 -0
- package/lib/typescript/src/Storage.d.ts +23 -0
- package/lib/typescript/src/Storage.d.ts.map +1 -0
- package/lib/typescript/src/index.d.ts +319 -0
- package/lib/typescript/src/index.d.ts.map +1 -0
- package/op-sqlite.podspec +212 -0
- package/package.json +85 -0
- package/src/NativeOPSQLite.ts +17 -0
- package/src/Storage.ts +85 -0
- package/src/index.ts +722 -0
package/cpp/bridge.cpp
ADDED
|
@@ -0,0 +1,873 @@
|
|
|
1
|
+
// This file contains pure sqlite operations without JSI interaction
|
|
2
|
+
// Allows a clear defined boundary between the JSI and the SQLite operations
|
|
3
|
+
// so that threading operations are safe and contained within DBHostObject
|
|
4
|
+
|
|
5
|
+
#include "bridge.h"
|
|
6
|
+
#include "DBHostObject.h"
|
|
7
|
+
#include "DumbHostObject.h"
|
|
8
|
+
#include "SmartHostObject.h"
|
|
9
|
+
#include "logs.h"
|
|
10
|
+
#include "utils.h"
|
|
11
|
+
#include <filesystem>
|
|
12
|
+
#include <iostream>
|
|
13
|
+
#include <sstream>
|
|
14
|
+
#include <stdexcept>
|
|
15
|
+
#include <unordered_map>
|
|
16
|
+
#include <variant>
|
|
17
|
+
|
|
18
|
+
#ifdef TOKENIZERS_HEADER_PATH
|
|
19
|
+
#include TOKENIZERS_HEADER_PATH
|
|
20
|
+
#else
|
|
21
|
+
#define TOKENIZER_LIST
|
|
22
|
+
#endif
|
|
23
|
+
|
|
24
|
+
namespace opsqlite {
|
|
25
|
+
|
|
26
|
+
inline void opsqlite_bind_statement(sqlite3_stmt *statement,
|
|
27
|
+
const std::vector<JSVariant> *values) {
|
|
28
|
+
sqlite3_clear_bindings(statement);
|
|
29
|
+
|
|
30
|
+
size_t size = values->size();
|
|
31
|
+
|
|
32
|
+
for (int ii = 0; ii < size; ii++) {
|
|
33
|
+
int stmt_index = ii + 1;
|
|
34
|
+
JSVariant value = values->at(ii);
|
|
35
|
+
|
|
36
|
+
std::visit(
|
|
37
|
+
[&](auto &&v) {
|
|
38
|
+
using T = std::decay_t<decltype(v)>;
|
|
39
|
+
|
|
40
|
+
if constexpr (std::is_same_v<T, bool>) {
|
|
41
|
+
sqlite3_bind_int(statement, stmt_index,
|
|
42
|
+
static_cast<int>(v));
|
|
43
|
+
} else if constexpr (std::is_same_v<T, int>) {
|
|
44
|
+
sqlite3_bind_int(statement, stmt_index, v);
|
|
45
|
+
} else if constexpr (std::is_same_v<T, long long>) {
|
|
46
|
+
sqlite3_bind_double(statement, stmt_index,
|
|
47
|
+
static_cast<double>(v));
|
|
48
|
+
} else if constexpr (std::is_same_v<T, double>) {
|
|
49
|
+
sqlite3_bind_double(statement, stmt_index, v);
|
|
50
|
+
} else if constexpr (std::is_same_v<T, std::string>) {
|
|
51
|
+
sqlite3_bind_text(statement, stmt_index, v.c_str(),
|
|
52
|
+
static_cast<int>(v.length()),
|
|
53
|
+
SQLITE_TRANSIENT);
|
|
54
|
+
} else if constexpr (std::is_same_v<T, ArrayBuffer>) {
|
|
55
|
+
sqlite3_bind_blob(statement, stmt_index, v.data.get(),
|
|
56
|
+
static_cast<int>(v.size),
|
|
57
|
+
SQLITE_TRANSIENT);
|
|
58
|
+
} else {
|
|
59
|
+
sqlite3_bind_null(statement, stmt_index);
|
|
60
|
+
}
|
|
61
|
+
},
|
|
62
|
+
value);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/// Returns the completely formed db path, but it also creates any sub-folders
|
|
67
|
+
/// along the way
|
|
68
|
+
std::string opsqlite_get_db_path(std::string const &db_name,
|
|
69
|
+
std::string const &location) {
|
|
70
|
+
|
|
71
|
+
if (location == ":memory:") {
|
|
72
|
+
return location;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// Will return false if the directory already exists, no need to check
|
|
76
|
+
std::filesystem::create_directories(location);
|
|
77
|
+
|
|
78
|
+
if (!location.empty() && location.back() != '/') {
|
|
79
|
+
return location + "/" + db_name;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
return location + db_name;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
#ifdef OP_SQLITE_USE_SQLCIPHER
|
|
86
|
+
sqlite3 *opsqlite_open(std::string const &name, std::string const &path,
|
|
87
|
+
std::string const &crsqlite_path,
|
|
88
|
+
std::string const &sqlite_vec_path,
|
|
89
|
+
std::string const &encryption_key) {
|
|
90
|
+
#else
|
|
91
|
+
sqlite3 *opsqlite_open(std::string const &name, std::string const &path,
|
|
92
|
+
[[maybe_unused]] std::string const &crsqlite_path,
|
|
93
|
+
[[maybe_unused]] std::string const &sqlite_vec_path) {
|
|
94
|
+
#endif
|
|
95
|
+
std::string final_path = opsqlite_get_db_path(name, path);
|
|
96
|
+
char *errMsg;
|
|
97
|
+
sqlite3 *db;
|
|
98
|
+
|
|
99
|
+
int flags =
|
|
100
|
+
SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE | SQLITE_OPEN_FULLMUTEX;
|
|
101
|
+
|
|
102
|
+
int status = sqlite3_open_v2(final_path.c_str(), &db, flags, nullptr);
|
|
103
|
+
|
|
104
|
+
if (status != SQLITE_OK) {
|
|
105
|
+
throw std::runtime_error(sqlite3_errmsg(db));
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
#ifdef OP_SQLITE_USE_SQLCIPHER
|
|
109
|
+
if (!encryption_key.empty()) {
|
|
110
|
+
opsqlite_execute(db, "PRAGMA key = '" + encryption_key + "'", nullptr);
|
|
111
|
+
}
|
|
112
|
+
#endif
|
|
113
|
+
|
|
114
|
+
#ifndef OP_SQLITE_USE_PHONE_VERSION
|
|
115
|
+
sqlite3_enable_load_extension(db, 1);
|
|
116
|
+
#endif
|
|
117
|
+
|
|
118
|
+
#ifdef OP_SQLITE_USE_CRSQLITE
|
|
119
|
+
const char *crsqliteEntryPoint = "sqlite3_crsqlite_init";
|
|
120
|
+
|
|
121
|
+
sqlite3_load_extension(db, crsqlite_path.c_str(), crsqliteEntryPoint,
|
|
122
|
+
&errMsg);
|
|
123
|
+
|
|
124
|
+
if (errMsg != nullptr) {
|
|
125
|
+
throw std::runtime_error(errMsg);
|
|
126
|
+
}
|
|
127
|
+
#endif
|
|
128
|
+
|
|
129
|
+
#ifdef OP_SQLITE_USE_SQLITE_VEC
|
|
130
|
+
const char *vec_entry_point = "sqlite3_vec_init";
|
|
131
|
+
|
|
132
|
+
sqlite3_load_extension(db, sqlite_vec_path.c_str(), vec_entry_point,
|
|
133
|
+
&errMsg);
|
|
134
|
+
|
|
135
|
+
if (errMsg != nullptr) {
|
|
136
|
+
throw std::runtime_error(errMsg);
|
|
137
|
+
}
|
|
138
|
+
#endif
|
|
139
|
+
|
|
140
|
+
TOKENIZER_LIST
|
|
141
|
+
|
|
142
|
+
return db;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
void opsqlite_close(sqlite3 *db) {
|
|
146
|
+
#ifdef OP_SQLITE_USE_CRSQLITE
|
|
147
|
+
opsqlite_execute(db, "select crsql_finalize();", nullptr);
|
|
148
|
+
#endif
|
|
149
|
+
|
|
150
|
+
sqlite3_close_v2(db);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
void opsqlite_attach(sqlite3 *db, std::string const &doc_path,
|
|
154
|
+
std::string const &secondary_db_name,
|
|
155
|
+
std::string const &alias) {
|
|
156
|
+
auto secondary_db_path = opsqlite_get_db_path(secondary_db_name, doc_path);
|
|
157
|
+
auto statement = "ATTACH DATABASE '" + secondary_db_path + "' AS " + alias;
|
|
158
|
+
|
|
159
|
+
opsqlite_execute(db, statement, nullptr);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
void opsqlite_detach(sqlite3 *db, std::string const &alias) {
|
|
163
|
+
std::string statement = "DETACH DATABASE " + alias;
|
|
164
|
+
opsqlite_execute(db, statement, nullptr);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
void opsqlite_remove(sqlite3 *db, std::string const &name,
|
|
168
|
+
std::string const &doc_path) {
|
|
169
|
+
opsqlite_close(db);
|
|
170
|
+
|
|
171
|
+
std::string db_path = opsqlite_get_db_path(name, doc_path);
|
|
172
|
+
|
|
173
|
+
if (!file_exists(db_path)) {
|
|
174
|
+
throw std::runtime_error("op-sqlite: db file not found:" + db_path);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
remove(db_path.c_str());
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
BridgeResult opsqlite_execute_prepared_statement(
|
|
181
|
+
sqlite3 *db, sqlite3_stmt *statement, std::vector<DumbHostObject> *results,
|
|
182
|
+
std::shared_ptr<std::vector<SmartHostObject>> &metadatas) {
|
|
183
|
+
|
|
184
|
+
const char *errorMessage;
|
|
185
|
+
|
|
186
|
+
bool isConsuming = true;
|
|
187
|
+
bool isFailed = false;
|
|
188
|
+
|
|
189
|
+
int result = SQLITE_OK;
|
|
190
|
+
|
|
191
|
+
int i, count, column_type;
|
|
192
|
+
std::string column_name, column_declared_type;
|
|
193
|
+
|
|
194
|
+
while (isConsuming) {
|
|
195
|
+
result = sqlite3_step(statement);
|
|
196
|
+
|
|
197
|
+
switch (result) {
|
|
198
|
+
case SQLITE_ROW: {
|
|
199
|
+
i = 0;
|
|
200
|
+
DumbHostObject row = DumbHostObject(metadatas);
|
|
201
|
+
|
|
202
|
+
count = sqlite3_column_count(statement);
|
|
203
|
+
|
|
204
|
+
while (i < count) {
|
|
205
|
+
column_type = sqlite3_column_type(statement, i);
|
|
206
|
+
|
|
207
|
+
switch (column_type) {
|
|
208
|
+
case SQLITE_INTEGER: {
|
|
209
|
+
/**
|
|
210
|
+
* Warning this will loose precision because JS can
|
|
211
|
+
* only represent Integers up to 53 bits
|
|
212
|
+
*/
|
|
213
|
+
double column_value = sqlite3_column_double(statement, i);
|
|
214
|
+
row.values.emplace_back(column_value);
|
|
215
|
+
break;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
case SQLITE_FLOAT: {
|
|
219
|
+
double column_value = sqlite3_column_double(statement, i);
|
|
220
|
+
row.values.emplace_back(column_value);
|
|
221
|
+
break;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
case SQLITE_TEXT: {
|
|
225
|
+
const char *column_value = reinterpret_cast<const char *>(
|
|
226
|
+
sqlite3_column_text(statement, i));
|
|
227
|
+
int byteLen = sqlite3_column_bytes(statement, i);
|
|
228
|
+
// Specify length too; in case string contains NULL in the
|
|
229
|
+
// middle
|
|
230
|
+
row.values.emplace_back(std::string(column_value, byteLen));
|
|
231
|
+
break;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
case SQLITE_BLOB: {
|
|
235
|
+
int blob_size = sqlite3_column_bytes(statement, i);
|
|
236
|
+
const void *blob = sqlite3_column_blob(statement, i);
|
|
237
|
+
auto *data = new uint8_t[blob_size];
|
|
238
|
+
// You cannot share raw memory between native and JS
|
|
239
|
+
// always copy the data
|
|
240
|
+
memcpy(data, blob, blob_size);
|
|
241
|
+
row.values.emplace_back(
|
|
242
|
+
ArrayBuffer{.data = std::shared_ptr<uint8_t>{data},
|
|
243
|
+
.size = static_cast<size_t>(blob_size)});
|
|
244
|
+
break;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
case SQLITE_NULL:
|
|
248
|
+
// Intentionally left blank
|
|
249
|
+
|
|
250
|
+
default:
|
|
251
|
+
row.values.emplace_back(nullptr);
|
|
252
|
+
break;
|
|
253
|
+
}
|
|
254
|
+
i++;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
results->emplace_back(row);
|
|
258
|
+
|
|
259
|
+
break;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
case SQLITE_DONE:
|
|
263
|
+
if (metadatas != nullptr) {
|
|
264
|
+
i = 0;
|
|
265
|
+
count = sqlite3_column_count(statement);
|
|
266
|
+
|
|
267
|
+
while (i < count) {
|
|
268
|
+
column_name = sqlite3_column_name(statement, i);
|
|
269
|
+
const char *type = sqlite3_column_decltype(statement, i);
|
|
270
|
+
auto metadata = SmartHostObject();
|
|
271
|
+
metadata.fields.emplace_back("name", column_name);
|
|
272
|
+
metadata.fields.emplace_back("index", i);
|
|
273
|
+
metadata.fields.emplace_back(
|
|
274
|
+
"type", type == nullptr ? "UNKNOWN" : type);
|
|
275
|
+
|
|
276
|
+
metadatas->emplace_back(metadata);
|
|
277
|
+
i++;
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
isConsuming = false;
|
|
281
|
+
break;
|
|
282
|
+
|
|
283
|
+
default:
|
|
284
|
+
errorMessage = sqlite3_errmsg(db);
|
|
285
|
+
isFailed = true;
|
|
286
|
+
isConsuming = false;
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
sqlite3_reset(statement);
|
|
291
|
+
|
|
292
|
+
if (isFailed) {
|
|
293
|
+
throw std::runtime_error(
|
|
294
|
+
"[op-sqlite] SQLite code: " + std::to_string(result) +
|
|
295
|
+
" execution error: " + std::string(errorMessage));
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
int changedRowCount = sqlite3_changes(db);
|
|
299
|
+
long long latestInsertRowId = sqlite3_last_insert_rowid(db);
|
|
300
|
+
|
|
301
|
+
return {.affectedRows = changedRowCount,
|
|
302
|
+
.insertId = static_cast<double>(latestInsertRowId)};
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
sqlite3_stmt *opsqlite_prepare_statement(sqlite3 *db,
|
|
306
|
+
std::string const &query) {
|
|
307
|
+
sqlite3_stmt *statement;
|
|
308
|
+
|
|
309
|
+
const char *queryStr = query.c_str();
|
|
310
|
+
|
|
311
|
+
int statementStatus =
|
|
312
|
+
sqlite3_prepare_v2(db, queryStr, -1, &statement, nullptr);
|
|
313
|
+
|
|
314
|
+
if (statementStatus == SQLITE_ERROR) {
|
|
315
|
+
const char *message = sqlite3_errmsg(db);
|
|
316
|
+
throw std::runtime_error("[op-sqlite] SQL prepare statement error: " +
|
|
317
|
+
std::string(message));
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
return statement;
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
BridgeResult opsqlite_execute(sqlite3 *db, std::string const &query,
|
|
324
|
+
const std::vector<JSVariant> *params) {
|
|
325
|
+
sqlite3_stmt *statement;
|
|
326
|
+
const char *errorMessage = nullptr;
|
|
327
|
+
const char *remainingStatement = nullptr;
|
|
328
|
+
bool has_failed = false;
|
|
329
|
+
int status, current_column, column_count, column_type;
|
|
330
|
+
std::string column_name, column_declared_type;
|
|
331
|
+
std::vector<std::string> column_names;
|
|
332
|
+
std::vector<std::vector<JSVariant>> rows;
|
|
333
|
+
rows.reserve(20);
|
|
334
|
+
std::vector<JSVariant> row;
|
|
335
|
+
|
|
336
|
+
do {
|
|
337
|
+
const char *query_str =
|
|
338
|
+
remainingStatement == nullptr ? query.c_str() : remainingStatement;
|
|
339
|
+
|
|
340
|
+
status = sqlite3_prepare_v2(db, query_str, -1, &statement,
|
|
341
|
+
&remainingStatement);
|
|
342
|
+
|
|
343
|
+
if (status != SQLITE_OK) {
|
|
344
|
+
errorMessage = sqlite3_errmsg(db);
|
|
345
|
+
throw std::runtime_error("[op-sqlite] sqlite query error: " +
|
|
346
|
+
std::string(errorMessage));
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
// The statement did not fail to parse but there is nothing to do, just
|
|
350
|
+
// skip to the end
|
|
351
|
+
if (statement == nullptr) {
|
|
352
|
+
continue;
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
if (params != nullptr && !params->empty()) {
|
|
356
|
+
opsqlite_bind_statement(statement, params);
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
column_count = sqlite3_column_count(statement);
|
|
360
|
+
column_names.reserve(column_count);
|
|
361
|
+
bool is_consuming_rows = true;
|
|
362
|
+
double double_value;
|
|
363
|
+
const char *string_value;
|
|
364
|
+
|
|
365
|
+
// Do a first pass to get the column names
|
|
366
|
+
for (int i = 0; i < column_count; i++) {
|
|
367
|
+
column_name = sqlite3_column_name(statement, i);
|
|
368
|
+
column_names.emplace_back(column_name);
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
while (is_consuming_rows) {
|
|
372
|
+
status = sqlite3_step(statement);
|
|
373
|
+
|
|
374
|
+
switch (status) {
|
|
375
|
+
case SQLITE_ROW:
|
|
376
|
+
current_column = 0;
|
|
377
|
+
row = std::vector<JSVariant>();
|
|
378
|
+
row.reserve(column_count);
|
|
379
|
+
|
|
380
|
+
while (current_column < column_count) {
|
|
381
|
+
column_type =
|
|
382
|
+
sqlite3_column_type(statement, current_column);
|
|
383
|
+
|
|
384
|
+
switch (column_type) {
|
|
385
|
+
|
|
386
|
+
case SQLITE_INTEGER:
|
|
387
|
+
// intentional fallthrough
|
|
388
|
+
case SQLITE_FLOAT: {
|
|
389
|
+
double_value =
|
|
390
|
+
sqlite3_column_double(statement, current_column);
|
|
391
|
+
row.emplace_back(double_value);
|
|
392
|
+
break;
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
case SQLITE_TEXT: {
|
|
396
|
+
string_value = reinterpret_cast<const char *>(
|
|
397
|
+
sqlite3_column_text(statement, current_column));
|
|
398
|
+
int len =
|
|
399
|
+
sqlite3_column_bytes(statement, current_column);
|
|
400
|
+
// Specify length too; in case string contains NULL in
|
|
401
|
+
// the middle
|
|
402
|
+
row.emplace_back(std::string(string_value, len));
|
|
403
|
+
break;
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
case SQLITE_BLOB: {
|
|
407
|
+
int blob_size =
|
|
408
|
+
sqlite3_column_bytes(statement, current_column);
|
|
409
|
+
const void *blob =
|
|
410
|
+
sqlite3_column_blob(statement, current_column);
|
|
411
|
+
auto *data = new uint8_t[blob_size];
|
|
412
|
+
memcpy(data, blob, blob_size);
|
|
413
|
+
row.emplace_back(ArrayBuffer{
|
|
414
|
+
.data = std::shared_ptr<uint8_t>{data},
|
|
415
|
+
.size = static_cast<size_t>(blob_size)});
|
|
416
|
+
break;
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
case SQLITE_NULL:
|
|
420
|
+
// Intentionally left blank to switch to default case
|
|
421
|
+
default:
|
|
422
|
+
row.emplace_back(nullptr);
|
|
423
|
+
break;
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
current_column++;
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
rows.emplace_back(std::move(row));
|
|
430
|
+
break;
|
|
431
|
+
|
|
432
|
+
case SQLITE_DONE:
|
|
433
|
+
is_consuming_rows = false;
|
|
434
|
+
break;
|
|
435
|
+
|
|
436
|
+
default:
|
|
437
|
+
has_failed = true;
|
|
438
|
+
is_consuming_rows = false;
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
sqlite3_finalize(statement);
|
|
443
|
+
} while (remainingStatement != nullptr &&
|
|
444
|
+
strcmp(remainingStatement, "") != 0 && !has_failed);
|
|
445
|
+
|
|
446
|
+
if (has_failed) {
|
|
447
|
+
const char *message = sqlite3_errmsg(db);
|
|
448
|
+
throw std::runtime_error("[op-sqlite] statement execution error: " +
|
|
449
|
+
std::string(message));
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
int changedRowCount = sqlite3_changes(db);
|
|
453
|
+
long long latestInsertRowId = sqlite3_last_insert_rowid(db);
|
|
454
|
+
return {.affectedRows = changedRowCount,
|
|
455
|
+
.insertId = static_cast<double>(latestInsertRowId),
|
|
456
|
+
.rows = std::move(rows),
|
|
457
|
+
.column_names = std::move(column_names)};
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
BridgeResult opsqlite_execute_host_objects(
|
|
461
|
+
sqlite3 *db, std::string const &query, const std::vector<JSVariant> *params,
|
|
462
|
+
std::vector<DumbHostObject> *results,
|
|
463
|
+
std::shared_ptr<std::vector<SmartHostObject>> &metadatas) {
|
|
464
|
+
|
|
465
|
+
sqlite3_stmt *statement;
|
|
466
|
+
const char *errorMessage;
|
|
467
|
+
const char *remainingStatement = nullptr;
|
|
468
|
+
|
|
469
|
+
bool isConsuming = true;
|
|
470
|
+
bool isFailed = false;
|
|
471
|
+
|
|
472
|
+
int result = SQLITE_OK;
|
|
473
|
+
|
|
474
|
+
do {
|
|
475
|
+
const char *queryStr =
|
|
476
|
+
remainingStatement == nullptr ? query.c_str() : remainingStatement;
|
|
477
|
+
|
|
478
|
+
int statementStatus = sqlite3_prepare_v2(db, queryStr, -1, &statement,
|
|
479
|
+
&remainingStatement);
|
|
480
|
+
|
|
481
|
+
if (statementStatus != SQLITE_OK) {
|
|
482
|
+
const char *message = sqlite3_errmsg(db);
|
|
483
|
+
throw std::runtime_error(
|
|
484
|
+
"[op-sqlite] SQL statement error on opsqlite_execute:\n" +
|
|
485
|
+
std::to_string(statementStatus) + " description:\n" +
|
|
486
|
+
std::string(message));
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
// The statement did not fail to parse but there is nothing to do, just
|
|
490
|
+
// skip to the end
|
|
491
|
+
if (statement == nullptr) {
|
|
492
|
+
continue;
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
if (params != nullptr && !params->empty()) {
|
|
496
|
+
opsqlite_bind_statement(statement, params);
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
int i, count, column_type;
|
|
500
|
+
std::string column_name, column_declared_type;
|
|
501
|
+
|
|
502
|
+
while (isConsuming) {
|
|
503
|
+
result = sqlite3_step(statement);
|
|
504
|
+
|
|
505
|
+
switch (result) {
|
|
506
|
+
case SQLITE_ROW: {
|
|
507
|
+
if (results == nullptr) {
|
|
508
|
+
break;
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
i = 0;
|
|
512
|
+
DumbHostObject row = DumbHostObject(metadatas);
|
|
513
|
+
|
|
514
|
+
count = sqlite3_column_count(statement);
|
|
515
|
+
|
|
516
|
+
while (i < count) {
|
|
517
|
+
column_type = sqlite3_column_type(statement, i);
|
|
518
|
+
|
|
519
|
+
switch (column_type) {
|
|
520
|
+
case SQLITE_INTEGER: {
|
|
521
|
+
/**
|
|
522
|
+
* Warning this will loose precision because JS can
|
|
523
|
+
* only represent Integers up to 53 bits
|
|
524
|
+
*/
|
|
525
|
+
double column_value =
|
|
526
|
+
sqlite3_column_double(statement, i);
|
|
527
|
+
row.values.emplace_back(column_value);
|
|
528
|
+
break;
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
case SQLITE_FLOAT: {
|
|
532
|
+
double column_value =
|
|
533
|
+
sqlite3_column_double(statement, i);
|
|
534
|
+
row.values.emplace_back(column_value);
|
|
535
|
+
break;
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
case SQLITE_TEXT: {
|
|
539
|
+
const char *column_value =
|
|
540
|
+
reinterpret_cast<const char *>(
|
|
541
|
+
sqlite3_column_text(statement, i));
|
|
542
|
+
int byteLen = sqlite3_column_bytes(statement, i);
|
|
543
|
+
// Specify length too; in case string contains NULL in
|
|
544
|
+
// the middle
|
|
545
|
+
row.values.emplace_back(
|
|
546
|
+
std::string(column_value, byteLen));
|
|
547
|
+
break;
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
case SQLITE_BLOB: {
|
|
551
|
+
int blob_size = sqlite3_column_bytes(statement, i);
|
|
552
|
+
const void *blob = sqlite3_column_blob(statement, i);
|
|
553
|
+
auto *data = new uint8_t[blob_size];
|
|
554
|
+
// You cannot share raw memory between native and JS
|
|
555
|
+
// always copy the data
|
|
556
|
+
memcpy(data, blob, blob_size);
|
|
557
|
+
row.values.emplace_back(ArrayBuffer{
|
|
558
|
+
.data = std::shared_ptr<uint8_t>{data},
|
|
559
|
+
.size = static_cast<size_t>(blob_size)});
|
|
560
|
+
break;
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
case SQLITE_NULL:
|
|
564
|
+
// Intentionally left blank
|
|
565
|
+
|
|
566
|
+
default:
|
|
567
|
+
row.values.emplace_back(nullptr);
|
|
568
|
+
break;
|
|
569
|
+
}
|
|
570
|
+
i++;
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
results->emplace_back(row);
|
|
574
|
+
break;
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
case SQLITE_DONE:
|
|
578
|
+
if (metadatas != nullptr) {
|
|
579
|
+
i = 0;
|
|
580
|
+
count = sqlite3_column_count(statement);
|
|
581
|
+
|
|
582
|
+
while (i < count) {
|
|
583
|
+
column_name = sqlite3_column_name(statement, i);
|
|
584
|
+
const char *type =
|
|
585
|
+
sqlite3_column_decltype(statement, i);
|
|
586
|
+
auto metadata = SmartHostObject();
|
|
587
|
+
metadata.fields.emplace_back("name", column_name);
|
|
588
|
+
metadata.fields.emplace_back("index", i);
|
|
589
|
+
metadata.fields.emplace_back(
|
|
590
|
+
"type", type == nullptr ? "UNKNOWN" : type);
|
|
591
|
+
|
|
592
|
+
metadatas->push_back(metadata);
|
|
593
|
+
i++;
|
|
594
|
+
}
|
|
595
|
+
}
|
|
596
|
+
isConsuming = false;
|
|
597
|
+
break;
|
|
598
|
+
|
|
599
|
+
default:
|
|
600
|
+
errorMessage = sqlite3_errmsg(db);
|
|
601
|
+
isFailed = true;
|
|
602
|
+
isConsuming = false;
|
|
603
|
+
}
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
sqlite3_finalize(statement);
|
|
607
|
+
} while (remainingStatement != nullptr &&
|
|
608
|
+
strcmp(remainingStatement, "") != 0 && !isFailed);
|
|
609
|
+
|
|
610
|
+
if (isFailed) {
|
|
611
|
+
throw std::runtime_error(
|
|
612
|
+
"[op-sqlite] SQLite error code: " + std::to_string(result) +
|
|
613
|
+
", description: " + std::string(errorMessage));
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
int changedRowCount = sqlite3_changes(db);
|
|
617
|
+
long long latestInsertRowId = sqlite3_last_insert_rowid(db);
|
|
618
|
+
|
|
619
|
+
return {.affectedRows = changedRowCount,
|
|
620
|
+
.insertId = static_cast<double>(latestInsertRowId)};
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
/// Executes returning data in raw arrays, a small performance optimization
|
|
624
|
+
/// for certain use cases
|
|
625
|
+
BridgeResult
|
|
626
|
+
opsqlite_execute_raw(sqlite3 *db, std::string const &query,
|
|
627
|
+
const std::vector<JSVariant> *params,
|
|
628
|
+
std::vector<std::vector<JSVariant>> *results) {
|
|
629
|
+
sqlite3_stmt *statement;
|
|
630
|
+
const char *errorMessage;
|
|
631
|
+
const char *remainingStatement = nullptr;
|
|
632
|
+
|
|
633
|
+
bool isConsuming = true;
|
|
634
|
+
bool isFailed = false;
|
|
635
|
+
|
|
636
|
+
int step = SQLITE_OK;
|
|
637
|
+
|
|
638
|
+
do {
|
|
639
|
+
const char *queryStr =
|
|
640
|
+
remainingStatement == nullptr ? query.c_str() : remainingStatement;
|
|
641
|
+
|
|
642
|
+
int statementStatus = sqlite3_prepare_v2(db, queryStr, -1, &statement,
|
|
643
|
+
&remainingStatement);
|
|
644
|
+
|
|
645
|
+
if (statementStatus != SQLITE_OK) {
|
|
646
|
+
const char *message = sqlite3_errmsg(db);
|
|
647
|
+
throw std::runtime_error("[op-sqlite] SQL statement error:" +
|
|
648
|
+
std::to_string(statementStatus) +
|
|
649
|
+
" description:" + std::string(message));
|
|
650
|
+
}
|
|
651
|
+
|
|
652
|
+
// The statement did not fail to parse but there is nothing to do, just
|
|
653
|
+
// skip to the end
|
|
654
|
+
if (statement == nullptr) {
|
|
655
|
+
continue;
|
|
656
|
+
}
|
|
657
|
+
|
|
658
|
+
if (params != nullptr && !params->empty()) {
|
|
659
|
+
opsqlite_bind_statement(statement, params);
|
|
660
|
+
}
|
|
661
|
+
|
|
662
|
+
int i, column_type;
|
|
663
|
+
std::string column_name, column_declared_type;
|
|
664
|
+
|
|
665
|
+
int column_count = sqlite3_column_count(statement);
|
|
666
|
+
|
|
667
|
+
while (isConsuming) {
|
|
668
|
+
step = sqlite3_step(statement);
|
|
669
|
+
|
|
670
|
+
switch (step) {
|
|
671
|
+
case SQLITE_ROW: {
|
|
672
|
+
if (results == nullptr) {
|
|
673
|
+
break;
|
|
674
|
+
}
|
|
675
|
+
|
|
676
|
+
std::vector<JSVariant> row;
|
|
677
|
+
row.reserve(column_count);
|
|
678
|
+
|
|
679
|
+
i = 0;
|
|
680
|
+
|
|
681
|
+
while (i < column_count) {
|
|
682
|
+
column_type = sqlite3_column_type(statement, i);
|
|
683
|
+
|
|
684
|
+
switch (column_type) {
|
|
685
|
+
case SQLITE_INTEGER:
|
|
686
|
+
case SQLITE_FLOAT: {
|
|
687
|
+
double column_value =
|
|
688
|
+
sqlite3_column_double(statement, i);
|
|
689
|
+
row.emplace_back(column_value);
|
|
690
|
+
break;
|
|
691
|
+
}
|
|
692
|
+
|
|
693
|
+
case SQLITE_TEXT: {
|
|
694
|
+
const char *column_value =
|
|
695
|
+
reinterpret_cast<const char *>(
|
|
696
|
+
sqlite3_column_text(statement, i));
|
|
697
|
+
int byteLen = sqlite3_column_bytes(statement, i);
|
|
698
|
+
// Specify length too; in case string contains NULL in
|
|
699
|
+
// the middle
|
|
700
|
+
row.emplace_back(std::string(column_value, byteLen));
|
|
701
|
+
break;
|
|
702
|
+
}
|
|
703
|
+
|
|
704
|
+
case SQLITE_BLOB: {
|
|
705
|
+
int blob_size = sqlite3_column_bytes(statement, i);
|
|
706
|
+
const void *blob = sqlite3_column_blob(statement, i);
|
|
707
|
+
auto *data = new uint8_t[blob_size];
|
|
708
|
+
memcpy(data, blob, blob_size);
|
|
709
|
+
row.emplace_back(ArrayBuffer{
|
|
710
|
+
.data = std::shared_ptr<uint8_t>{data},
|
|
711
|
+
.size = static_cast<size_t>(blob_size)});
|
|
712
|
+
break;
|
|
713
|
+
}
|
|
714
|
+
|
|
715
|
+
case SQLITE_NULL:
|
|
716
|
+
// intentional fallthrough
|
|
717
|
+
default:
|
|
718
|
+
row.emplace_back(nullptr);
|
|
719
|
+
break;
|
|
720
|
+
}
|
|
721
|
+
i++;
|
|
722
|
+
}
|
|
723
|
+
|
|
724
|
+
results->emplace_back(row);
|
|
725
|
+
|
|
726
|
+
break;
|
|
727
|
+
}
|
|
728
|
+
|
|
729
|
+
case SQLITE_DONE:
|
|
730
|
+
isConsuming = false;
|
|
731
|
+
break;
|
|
732
|
+
|
|
733
|
+
default:
|
|
734
|
+
errorMessage = sqlite3_errmsg(db);
|
|
735
|
+
isFailed = true;
|
|
736
|
+
isConsuming = false;
|
|
737
|
+
}
|
|
738
|
+
}
|
|
739
|
+
|
|
740
|
+
sqlite3_finalize(statement);
|
|
741
|
+
} while (remainingStatement != nullptr &&
|
|
742
|
+
strcmp(remainingStatement, "") != 0 && !isFailed);
|
|
743
|
+
|
|
744
|
+
if (isFailed) {
|
|
745
|
+
throw std::runtime_error(
|
|
746
|
+
"[op-sqlite] SQLite error code: " + std::to_string(step) +
|
|
747
|
+
", description: " + std::string(errorMessage));
|
|
748
|
+
}
|
|
749
|
+
|
|
750
|
+
int changedRowCount = sqlite3_changes(db);
|
|
751
|
+
long long latestInsertRowId = sqlite3_last_insert_rowid(db);
|
|
752
|
+
|
|
753
|
+
return {.affectedRows = changedRowCount,
|
|
754
|
+
.insertId = static_cast<double>(latestInsertRowId)};
|
|
755
|
+
}
|
|
756
|
+
|
|
757
|
+
std::string operation_to_string(int operation_type) {
|
|
758
|
+
switch (operation_type) {
|
|
759
|
+
case SQLITE_INSERT:
|
|
760
|
+
return "INSERT";
|
|
761
|
+
|
|
762
|
+
case SQLITE_DELETE:
|
|
763
|
+
return "DELETE";
|
|
764
|
+
|
|
765
|
+
case SQLITE_UPDATE:
|
|
766
|
+
return "UPDATE";
|
|
767
|
+
|
|
768
|
+
default:
|
|
769
|
+
throw std::runtime_error("Unknown SQLite operation on hook");
|
|
770
|
+
}
|
|
771
|
+
}
|
|
772
|
+
|
|
773
|
+
void update_callback(void *db_host_object_ptr, int operation_type,
|
|
774
|
+
[[maybe_unused]] char const *database, char const *table,
|
|
775
|
+
sqlite3_int64 row_id) {
|
|
776
|
+
auto db_host_object = reinterpret_cast<DBHostObject *>(db_host_object_ptr);
|
|
777
|
+
db_host_object->on_update(std::string(table),
|
|
778
|
+
operation_to_string(operation_type), row_id);
|
|
779
|
+
}
|
|
780
|
+
|
|
781
|
+
void opsqlite_register_update_hook(sqlite3 *db, void *db_host_object) {
|
|
782
|
+
sqlite3_update_hook(db, &update_callback, (void *)db_host_object);
|
|
783
|
+
}
|
|
784
|
+
|
|
785
|
+
void opsqlite_deregister_update_hook(sqlite3 *db) {
|
|
786
|
+
sqlite3_update_hook(db, nullptr, nullptr);
|
|
787
|
+
}
|
|
788
|
+
|
|
789
|
+
int commit_callback(void *db_host_object_ptr) {
|
|
790
|
+
auto db_host_object = reinterpret_cast<DBHostObject *>(db_host_object_ptr);
|
|
791
|
+
db_host_object->on_commit();
|
|
792
|
+
return 0;
|
|
793
|
+
}
|
|
794
|
+
|
|
795
|
+
void opsqlite_register_commit_hook(sqlite3 *db, void *db_host_object_ptr) {
|
|
796
|
+
sqlite3_commit_hook(db, &commit_callback, db_host_object_ptr);
|
|
797
|
+
}
|
|
798
|
+
|
|
799
|
+
void opsqlite_deregister_commit_hook(sqlite3 *db) {
|
|
800
|
+
sqlite3_commit_hook(db, nullptr, nullptr);
|
|
801
|
+
}
|
|
802
|
+
|
|
803
|
+
void rollback_callback(void *db_host_object_ptr) {
|
|
804
|
+
auto db_host_object = reinterpret_cast<DBHostObject *>(db_host_object_ptr);
|
|
805
|
+
db_host_object->on_rollback();
|
|
806
|
+
}
|
|
807
|
+
|
|
808
|
+
void opsqlite_register_rollback_hook(sqlite3 *db, void *db_host_object_ptr) {
|
|
809
|
+
sqlite3_rollback_hook(db, &rollback_callback, db_host_object_ptr);
|
|
810
|
+
}
|
|
811
|
+
|
|
812
|
+
void opsqlite_deregister_rollback_hook(sqlite3 *db) {
|
|
813
|
+
sqlite3_rollback_hook(db, nullptr, nullptr);
|
|
814
|
+
}
|
|
815
|
+
|
|
816
|
+
void opsqlite_load_extension(sqlite3 *db, std::string &path,
|
|
817
|
+
std::string &entry_point) {
|
|
818
|
+
#ifdef OP_SQLITE_USE_PHONE_VERSION
|
|
819
|
+
throw std::runtime_error("[op-sqlite] Embedded version of SQLite does not "
|
|
820
|
+
"support loading extensions");
|
|
821
|
+
#else
|
|
822
|
+
int status = 0;
|
|
823
|
+
status = sqlite3_enable_load_extension(db, 1);
|
|
824
|
+
|
|
825
|
+
if (status != SQLITE_OK) {
|
|
826
|
+
throw std::runtime_error("Could not enable extension loading");
|
|
827
|
+
}
|
|
828
|
+
|
|
829
|
+
const char *entry_point_cstr = nullptr;
|
|
830
|
+
if (!entry_point.empty()) {
|
|
831
|
+
entry_point_cstr = entry_point.c_str();
|
|
832
|
+
}
|
|
833
|
+
|
|
834
|
+
char *error_message;
|
|
835
|
+
|
|
836
|
+
status = sqlite3_load_extension(db, path.c_str(), entry_point_cstr,
|
|
837
|
+
&error_message);
|
|
838
|
+
if (status != SQLITE_OK) {
|
|
839
|
+
throw std::runtime_error(error_message);
|
|
840
|
+
}
|
|
841
|
+
#endif
|
|
842
|
+
}
|
|
843
|
+
|
|
844
|
+
BatchResult
|
|
845
|
+
opsqlite_execute_batch(sqlite3 *db,
|
|
846
|
+
const std::vector<BatchArguments> *commands) {
|
|
847
|
+
size_t commandCount = commands->size();
|
|
848
|
+
if (commandCount <= 0) {
|
|
849
|
+
throw std::runtime_error("No SQL commands provided");
|
|
850
|
+
}
|
|
851
|
+
|
|
852
|
+
int affectedRows = 0;
|
|
853
|
+
opsqlite_execute(db, "BEGIN EXCLUSIVE TRANSACTION", nullptr);
|
|
854
|
+
for (int i = 0; i < commandCount; i++) {
|
|
855
|
+
const auto &command = commands->at(i);
|
|
856
|
+
// We do not provide a datastructure to receive query data because we
|
|
857
|
+
// don't need/want to handle this results in a batch execution
|
|
858
|
+
try {
|
|
859
|
+
auto result = opsqlite_execute(db, command.sql, &command.params);
|
|
860
|
+
affectedRows += result.affectedRows;
|
|
861
|
+
} catch (std::exception &exc) {
|
|
862
|
+
opsqlite_execute(db, "ROLLBACK", nullptr);
|
|
863
|
+
throw exc;
|
|
864
|
+
}
|
|
865
|
+
}
|
|
866
|
+
opsqlite_execute(db, "COMMIT", nullptr);
|
|
867
|
+
return BatchResult{
|
|
868
|
+
.affectedRows = affectedRows,
|
|
869
|
+
.commands = static_cast<int>(commandCount),
|
|
870
|
+
};
|
|
871
|
+
}
|
|
872
|
+
|
|
873
|
+
} // namespace opsqlite
|