react-native-nitro-storage 0.11.0 → 0.13.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/CHANGELOG.md +47 -0
- package/README.md +71 -11
- package/SECURITY.md +2 -2
- package/android/build.gradle +5 -0
- package/android/src/main/cpp/AndroidStorageAdapterCpp.cpp +12 -13
- package/android/src/main/cpp/JniSize.hpp +21 -0
- package/android/src/main/java/com/nitrostorage/AndroidStorageAdapter.kt +124 -16
- package/android/src/main/java/com/nitrostorage/DiskSqliteStore.kt +160 -43
- package/cpp/bindings/HybridStorage.cpp +12 -4
- package/cpp/core/SqliteDiskStore.cpp +191 -33
- package/cpp/core/SqliteDiskStore.hpp +11 -1
- package/docs/api-reference.md +11 -10
- package/docs/native-libraries.md +28 -0
- package/ios/IOSStorageAdapterCpp.mm +89 -22
- package/lib/commonjs/index.web.js +2 -1
- package/lib/commonjs/index.web.js.map +1 -1
- package/lib/commonjs/storage-core.js +26 -6
- package/lib/commonjs/storage-core.js.map +1 -1
- package/lib/commonjs/storage-runtime.js +1 -1
- package/lib/commonjs/storage-runtime.js.map +1 -1
- package/lib/module/index.web.js +2 -1
- package/lib/module/index.web.js.map +1 -1
- package/lib/module/storage-core.js +26 -6
- package/lib/module/storage-core.js.map +1 -1
- package/lib/module/storage-runtime.js +1 -1
- package/lib/module/storage-runtime.js.map +1 -1
- package/lib/typescript/index.d.ts.map +1 -1
- package/lib/typescript/index.web.d.ts.map +1 -1
- package/lib/typescript/storage-core.d.ts.map +1 -1
- package/lib/typescript/storage-runtime.d.ts +1 -1
- package/lib/typescript/storage-runtime.d.ts.map +1 -1
- package/lib/typescript/testing.d.ts.map +1 -1
- package/package.json +2 -1
- package/src/index.web.ts +5 -1
- package/src/storage-core.ts +26 -6
- package/src/storage-runtime.ts +2 -0
|
@@ -3,10 +3,12 @@
|
|
|
3
3
|
#include <sqlite3.h>
|
|
4
4
|
|
|
5
5
|
#include <algorithm>
|
|
6
|
+
#include <cerrno>
|
|
6
7
|
#include <cstdlib>
|
|
7
8
|
#include <filesystem>
|
|
8
9
|
#include <memory>
|
|
9
10
|
#include <stdexcept>
|
|
11
|
+
#include <system_error>
|
|
10
12
|
|
|
11
13
|
namespace NitroStorage {
|
|
12
14
|
namespace {
|
|
@@ -21,24 +23,43 @@ std::mutex& sharedMutex() {
|
|
|
21
23
|
return mutex;
|
|
22
24
|
}
|
|
23
25
|
|
|
26
|
+
const char* storageErrorTag(sqlite3* db, int code) {
|
|
27
|
+
switch (code & 0xff) {
|
|
28
|
+
case SQLITE_FULL:
|
|
29
|
+
return "[nitro-error:storage_full] ";
|
|
30
|
+
case SQLITE_CORRUPT:
|
|
31
|
+
case SQLITE_NOTADB:
|
|
32
|
+
return "[nitro-error:storage_corruption] ";
|
|
33
|
+
case SQLITE_IOERR: {
|
|
34
|
+
const int systemError = db != nullptr ? sqlite3_system_errno(db) : 0;
|
|
35
|
+
return systemError == ENOSPC || systemError == EDQUOT
|
|
36
|
+
? "[nitro-error:storage_full] "
|
|
37
|
+
: "";
|
|
38
|
+
}
|
|
39
|
+
default:
|
|
40
|
+
return "";
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
24
44
|
[[noreturn]] void throwSqlite(sqlite3* db, const char* operation, int code) {
|
|
25
45
|
const char* message = db != nullptr ? sqlite3_errmsg(db) : sqlite3_errstr(code);
|
|
26
46
|
throw std::runtime_error(
|
|
27
|
-
std::string("NitroStorage: Disk SQLite "
|
|
28
|
-
(message != nullptr ? message : "unknown error")
|
|
47
|
+
std::string(storageErrorTag(db, code)) + "NitroStorage: Disk SQLite " + operation +
|
|
48
|
+
" failed: " + (message != nullptr ? message : "unknown error")
|
|
29
49
|
);
|
|
30
50
|
}
|
|
31
51
|
|
|
32
52
|
void bindText(sqlite3_stmt* stmt, int index, const std::string& value) {
|
|
33
|
-
const int rc =
|
|
53
|
+
const int rc = sqlite3_bind_text64(
|
|
34
54
|
stmt,
|
|
35
55
|
index,
|
|
36
56
|
value.data(),
|
|
37
|
-
static_cast<
|
|
38
|
-
SQLITE_TRANSIENT
|
|
57
|
+
static_cast<sqlite3_uint64>(value.size()),
|
|
58
|
+
SQLITE_TRANSIENT,
|
|
59
|
+
SQLITE_UTF8
|
|
39
60
|
);
|
|
40
61
|
if (rc != SQLITE_OK) {
|
|
41
|
-
|
|
62
|
+
throwSqlite(sqlite3_db_handle(stmt), "bind", rc);
|
|
42
63
|
}
|
|
43
64
|
}
|
|
44
65
|
|
|
@@ -57,16 +78,23 @@ std::string escapeLikePrefix(const std::string& prefix) {
|
|
|
57
78
|
return escaped;
|
|
58
79
|
}
|
|
59
80
|
|
|
81
|
+
std::optional<std::string> prefixUpperBound(const std::string& prefix) {
|
|
82
|
+
std::string upper = prefix;
|
|
83
|
+
while (!upper.empty() && static_cast<unsigned char>(upper.back()) == 0xFF) {
|
|
84
|
+
upper.pop_back();
|
|
85
|
+
}
|
|
86
|
+
if (upper.empty()) {
|
|
87
|
+
return std::nullopt;
|
|
88
|
+
}
|
|
89
|
+
upper.back() = static_cast<char>(static_cast<unsigned char>(upper.back()) + 1);
|
|
90
|
+
return upper;
|
|
91
|
+
}
|
|
92
|
+
|
|
60
93
|
} // namespace
|
|
61
94
|
|
|
62
95
|
SqliteDiskStore::SqliteDiskStore(std::string path) : path_(std::move(path)) {
|
|
63
96
|
std::lock_guard<std::mutex> lock(mutex_);
|
|
64
|
-
|
|
65
|
-
openLocked();
|
|
66
|
-
} catch (...) {
|
|
67
|
-
closeLocked();
|
|
68
|
-
throw;
|
|
69
|
-
}
|
|
97
|
+
reopenLocked();
|
|
70
98
|
}
|
|
71
99
|
|
|
72
100
|
SqliteDiskStore::~SqliteDiskStore() {
|
|
@@ -83,6 +111,98 @@ SqliteDiskStore& SqliteDiskStore::shared(const std::string& path) {
|
|
|
83
111
|
return *store;
|
|
84
112
|
}
|
|
85
113
|
|
|
114
|
+
bool SqliteDiskStore::isValidUtf8(const std::string& value) {
|
|
115
|
+
const auto* bytes = reinterpret_cast<const unsigned char*>(value.data());
|
|
116
|
+
const size_t size = value.size();
|
|
117
|
+
size_t index = 0;
|
|
118
|
+
while (index < size) {
|
|
119
|
+
const unsigned char lead = bytes[index];
|
|
120
|
+
size_t length = 0;
|
|
121
|
+
unsigned int minimum = 0;
|
|
122
|
+
unsigned int codePoint = 0;
|
|
123
|
+
if (lead < 0x80) {
|
|
124
|
+
index += 1;
|
|
125
|
+
continue;
|
|
126
|
+
} else if ((lead & 0xE0) == 0xC0) {
|
|
127
|
+
length = 2;
|
|
128
|
+
minimum = 0x80;
|
|
129
|
+
codePoint = lead & 0x1Fu;
|
|
130
|
+
} else if ((lead & 0xF0) == 0xE0) {
|
|
131
|
+
length = 3;
|
|
132
|
+
minimum = 0x800;
|
|
133
|
+
codePoint = lead & 0x0Fu;
|
|
134
|
+
} else if ((lead & 0xF8) == 0xF0) {
|
|
135
|
+
length = 4;
|
|
136
|
+
minimum = 0x10000;
|
|
137
|
+
codePoint = lead & 0x07u;
|
|
138
|
+
} else {
|
|
139
|
+
return false;
|
|
140
|
+
}
|
|
141
|
+
if (size - index < length) {
|
|
142
|
+
return false;
|
|
143
|
+
}
|
|
144
|
+
for (size_t offset = 1; offset < length; ++offset) {
|
|
145
|
+
const unsigned char continuation = bytes[index + offset];
|
|
146
|
+
if ((continuation & 0xC0) != 0x80) {
|
|
147
|
+
return false;
|
|
148
|
+
}
|
|
149
|
+
codePoint = (codePoint << 6) | (continuation & 0x3Fu);
|
|
150
|
+
}
|
|
151
|
+
if (codePoint < minimum || codePoint > 0x10FFFF || (codePoint >= 0xD800 && codePoint <= 0xDFFF)) {
|
|
152
|
+
return false;
|
|
153
|
+
}
|
|
154
|
+
index += length;
|
|
155
|
+
}
|
|
156
|
+
return true;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
void SqliteDiskStore::recreateShared(const std::string& path) {
|
|
160
|
+
std::lock_guard<std::mutex> lock(sharedMutex());
|
|
161
|
+
auto& store = sharedStore();
|
|
162
|
+
if (store && store->path() == path) {
|
|
163
|
+
store->recreate();
|
|
164
|
+
return;
|
|
165
|
+
}
|
|
166
|
+
store.reset();
|
|
167
|
+
removeDatabaseFiles(path);
|
|
168
|
+
store = std::make_unique<SqliteDiskStore>(path);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
void SqliteDiskStore::removeDatabaseFiles(const std::string& path) {
|
|
172
|
+
for (const char* suffix : {"", "-wal", "-shm", "-journal"}) {
|
|
173
|
+
std::error_code error;
|
|
174
|
+
std::filesystem::remove(path + suffix, error);
|
|
175
|
+
if (error) {
|
|
176
|
+
throw std::runtime_error(
|
|
177
|
+
"NitroStorage: Disk SQLite clear failed: cannot delete the database file: " +
|
|
178
|
+
error.message()
|
|
179
|
+
);
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
void SqliteDiskStore::recreate() {
|
|
185
|
+
std::lock_guard<std::mutex> lock(mutex_);
|
|
186
|
+
closeLocked();
|
|
187
|
+
removeDatabaseFiles(path_);
|
|
188
|
+
reopenLocked();
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
void SqliteDiskStore::reopenLocked() {
|
|
192
|
+
try {
|
|
193
|
+
openLocked();
|
|
194
|
+
} catch (...) {
|
|
195
|
+
closeLocked();
|
|
196
|
+
throw;
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
void SqliteDiskStore::ensureOpenLocked() {
|
|
201
|
+
if (db_ == nullptr) {
|
|
202
|
+
reopenLocked();
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
86
206
|
void SqliteDiskStore::resetShared() {
|
|
87
207
|
std::lock_guard<std::mutex> lock(sharedMutex());
|
|
88
208
|
sharedStore().reset();
|
|
@@ -109,17 +229,22 @@ void SqliteDiskStore::openLocked() {
|
|
|
109
229
|
const int flags = SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE | SQLITE_OPEN_FULLMUTEX;
|
|
110
230
|
const int openRc = sqlite3_open_v2(path_.c_str(), &db, flags, nullptr);
|
|
111
231
|
if (openRc != SQLITE_OK) {
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
232
|
+
try {
|
|
233
|
+
throwSqlite(db, "open", openRc);
|
|
234
|
+
} catch (...) {
|
|
235
|
+
if (db != nullptr) {
|
|
236
|
+
sqlite3_close(db);
|
|
237
|
+
}
|
|
238
|
+
throw;
|
|
115
239
|
}
|
|
116
|
-
throwSqlite(nullptr, "open", code);
|
|
117
240
|
}
|
|
118
241
|
db_ = db;
|
|
119
242
|
sqlite3_busy_timeout(db_, 5000);
|
|
120
243
|
execLocked("PRAGMA journal_mode=WAL;");
|
|
121
244
|
execLocked("PRAGMA synchronous=NORMAL;");
|
|
122
245
|
execLocked("PRAGMA temp_store=MEMORY;");
|
|
246
|
+
execLocked("PRAGMA wal_autocheckpoint=1000;");
|
|
247
|
+
execLocked("PRAGMA journal_size_limit=32768;");
|
|
123
248
|
execLocked(
|
|
124
249
|
"CREATE TABLE IF NOT EXISTS kv ("
|
|
125
250
|
"key TEXT PRIMARY KEY NOT NULL,"
|
|
@@ -137,7 +262,9 @@ void SqliteDiskStore::openLocked() {
|
|
|
137
262
|
removeStmt_ = prepareLocked("DELETE FROM kv WHERE key = ?1;");
|
|
138
263
|
hasStmt_ = prepareLocked("SELECT 1 FROM kv WHERE key = ?1 LIMIT 1;");
|
|
139
264
|
keysStmt_ = prepareLocked("SELECT key FROM kv;");
|
|
140
|
-
|
|
265
|
+
prefixRangeStmt_ = prepareLocked("SELECT key FROM kv WHERE key >= ?1 AND key < ?2;");
|
|
266
|
+
prefixFromStmt_ = prepareLocked("SELECT key FROM kv WHERE key >= ?1;");
|
|
267
|
+
prefixLikeStmt_ = prepareLocked("SELECT key FROM kv WHERE key LIKE ?1 ESCAPE '\\';");
|
|
141
268
|
sizeStmt_ = prepareLocked("SELECT COUNT(*) FROM kv;");
|
|
142
269
|
clearStmt_ = prepareLocked("DELETE FROM kv;");
|
|
143
270
|
insertAbsentStmt_ = prepareLocked(
|
|
@@ -159,7 +286,9 @@ void SqliteDiskStore::closeLocked() {
|
|
|
159
286
|
finalize(removeStmt_);
|
|
160
287
|
finalize(hasStmt_);
|
|
161
288
|
finalize(keysStmt_);
|
|
162
|
-
finalize(
|
|
289
|
+
finalize(prefixRangeStmt_);
|
|
290
|
+
finalize(prefixFromStmt_);
|
|
291
|
+
finalize(prefixLikeStmt_);
|
|
163
292
|
finalize(sizeStmt_);
|
|
164
293
|
finalize(clearStmt_);
|
|
165
294
|
finalize(insertAbsentStmt_);
|
|
@@ -172,17 +301,24 @@ void SqliteDiskStore::closeLocked() {
|
|
|
172
301
|
}
|
|
173
302
|
|
|
174
303
|
void SqliteDiskStore::execLocked(const char* sql) {
|
|
175
|
-
|
|
176
|
-
const int rc = sqlite3_exec(db_, sql, nullptr, nullptr, &error);
|
|
304
|
+
const int rc = sqlite3_exec(db_, sql, nullptr, nullptr, nullptr);
|
|
177
305
|
if (rc != SQLITE_OK) {
|
|
178
|
-
|
|
179
|
-
sqlite3_free(error);
|
|
180
|
-
throw std::runtime_error(
|
|
181
|
-
std::string("NitroStorage: Disk SQLite exec failed: ") + message
|
|
182
|
-
);
|
|
306
|
+
throwSqlite(db_, "exec", rc);
|
|
183
307
|
}
|
|
184
308
|
}
|
|
185
309
|
|
|
310
|
+
void SqliteDiskStore::limitPageCountForTesting(int pages) {
|
|
311
|
+
std::lock_guard<std::mutex> lock(mutex_);
|
|
312
|
+
ensureOpenLocked();
|
|
313
|
+
execLocked(("PRAGMA max_page_count=" + std::to_string(pages) + ";").c_str());
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
void SqliteDiskStore::limitValueLengthForTesting(int bytes) {
|
|
317
|
+
std::lock_guard<std::mutex> lock(mutex_);
|
|
318
|
+
ensureOpenLocked();
|
|
319
|
+
sqlite3_limit(db_, SQLITE_LIMIT_LENGTH, bytes);
|
|
320
|
+
}
|
|
321
|
+
|
|
186
322
|
void SqliteDiskStore::beginLocked() {
|
|
187
323
|
execLocked("BEGIN IMMEDIATE;");
|
|
188
324
|
}
|
|
@@ -252,21 +388,25 @@ void SqliteDiskStore::removeLocked(const std::string& key) {
|
|
|
252
388
|
|
|
253
389
|
void SqliteDiskStore::set(const std::string& key, const std::string& value) {
|
|
254
390
|
std::lock_guard<std::mutex> lock(mutex_);
|
|
391
|
+
ensureOpenLocked();
|
|
255
392
|
setLocked(key, value);
|
|
256
393
|
}
|
|
257
394
|
|
|
258
395
|
std::optional<std::string> SqliteDiskStore::get(const std::string& key) {
|
|
259
396
|
std::lock_guard<std::mutex> lock(mutex_);
|
|
397
|
+
ensureOpenLocked();
|
|
260
398
|
return getLocked(key);
|
|
261
399
|
}
|
|
262
400
|
|
|
263
401
|
void SqliteDiskStore::remove(const std::string& key) {
|
|
264
402
|
std::lock_guard<std::mutex> lock(mutex_);
|
|
403
|
+
ensureOpenLocked();
|
|
265
404
|
removeLocked(key);
|
|
266
405
|
}
|
|
267
406
|
|
|
268
407
|
bool SqliteDiskStore::has(const std::string& key) {
|
|
269
408
|
std::lock_guard<std::mutex> lock(mutex_);
|
|
409
|
+
ensureOpenLocked();
|
|
270
410
|
sqlite3_reset(hasStmt_);
|
|
271
411
|
sqlite3_clear_bindings(hasStmt_);
|
|
272
412
|
bindText(hasStmt_, 1, key);
|
|
@@ -286,6 +426,7 @@ void SqliteDiskStore::setBatch(
|
|
|
286
426
|
const std::vector<std::string>& values
|
|
287
427
|
) {
|
|
288
428
|
std::lock_guard<std::mutex> lock(mutex_);
|
|
429
|
+
ensureOpenLocked();
|
|
289
430
|
beginLocked();
|
|
290
431
|
try {
|
|
291
432
|
const size_t count = std::min(keys.size(), values.size());
|
|
@@ -303,6 +444,7 @@ std::vector<std::optional<std::string>> SqliteDiskStore::getBatch(
|
|
|
303
444
|
const std::vector<std::string>& keys
|
|
304
445
|
) {
|
|
305
446
|
std::lock_guard<std::mutex> lock(mutex_);
|
|
447
|
+
ensureOpenLocked();
|
|
306
448
|
std::vector<std::optional<std::string>> results;
|
|
307
449
|
results.reserve(keys.size());
|
|
308
450
|
for (const auto& key : keys) {
|
|
@@ -313,6 +455,7 @@ std::vector<std::optional<std::string>> SqliteDiskStore::getBatch(
|
|
|
313
455
|
|
|
314
456
|
void SqliteDiskStore::removeBatch(const std::vector<std::string>& keys) {
|
|
315
457
|
std::lock_guard<std::mutex> lock(mutex_);
|
|
458
|
+
ensureOpenLocked();
|
|
316
459
|
beginLocked();
|
|
317
460
|
try {
|
|
318
461
|
for (const auto& key : keys) {
|
|
@@ -327,6 +470,7 @@ void SqliteDiskStore::removeBatch(const std::vector<std::string>& keys) {
|
|
|
327
470
|
|
|
328
471
|
std::vector<std::string> SqliteDiskStore::getAllKeys() {
|
|
329
472
|
std::lock_guard<std::mutex> lock(mutex_);
|
|
473
|
+
ensureOpenLocked();
|
|
330
474
|
sqlite3_reset(keysStmt_);
|
|
331
475
|
std::vector<std::string> keys;
|
|
332
476
|
while (true) {
|
|
@@ -351,21 +495,30 @@ std::vector<std::string> SqliteDiskStore::getAllKeys() {
|
|
|
351
495
|
|
|
352
496
|
std::vector<std::string> SqliteDiskStore::getKeysByPrefix(const std::string& prefix) {
|
|
353
497
|
std::lock_guard<std::mutex> lock(mutex_);
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
498
|
+
ensureOpenLocked();
|
|
499
|
+
const bool useRange = isValidUtf8(prefix);
|
|
500
|
+
const auto upperBound = useRange ? prefixUpperBound(prefix) : std::nullopt;
|
|
501
|
+
sqlite3_stmt* stmt = !useRange
|
|
502
|
+
? prefixLikeStmt_
|
|
503
|
+
: upperBound.has_value() ? prefixRangeStmt_ : prefixFromStmt_;
|
|
504
|
+
sqlite3_reset(stmt);
|
|
505
|
+
sqlite3_clear_bindings(stmt);
|
|
506
|
+
bindText(stmt, 1, useRange ? prefix : escapeLikePrefix(prefix));
|
|
507
|
+
if (upperBound.has_value()) {
|
|
508
|
+
bindText(stmt, 2, *upperBound);
|
|
509
|
+
}
|
|
357
510
|
std::vector<std::string> keys;
|
|
358
511
|
while (true) {
|
|
359
|
-
const int rc = sqlite3_step(
|
|
512
|
+
const int rc = sqlite3_step(stmt);
|
|
360
513
|
if (rc == SQLITE_DONE) {
|
|
361
514
|
break;
|
|
362
515
|
}
|
|
363
516
|
if (rc != SQLITE_ROW) {
|
|
364
|
-
sqlite3_reset(
|
|
517
|
+
sqlite3_reset(stmt);
|
|
365
518
|
throwSqlite(db_, "getKeysByPrefix", rc);
|
|
366
519
|
}
|
|
367
|
-
const unsigned char* text = sqlite3_column_text(
|
|
368
|
-
const int bytes = sqlite3_column_bytes(
|
|
520
|
+
const unsigned char* text = sqlite3_column_text(stmt, 0);
|
|
521
|
+
const int bytes = sqlite3_column_bytes(stmt, 0);
|
|
369
522
|
std::string key(
|
|
370
523
|
text != nullptr ? reinterpret_cast<const char*>(text) : "",
|
|
371
524
|
static_cast<size_t>(bytes)
|
|
@@ -374,12 +527,13 @@ std::vector<std::string> SqliteDiskStore::getKeysByPrefix(const std::string& pre
|
|
|
374
527
|
keys.push_back(std::move(key));
|
|
375
528
|
}
|
|
376
529
|
}
|
|
377
|
-
sqlite3_reset(
|
|
530
|
+
sqlite3_reset(stmt);
|
|
378
531
|
return keys;
|
|
379
532
|
}
|
|
380
533
|
|
|
381
534
|
size_t SqliteDiskStore::size() {
|
|
382
535
|
std::lock_guard<std::mutex> lock(mutex_);
|
|
536
|
+
ensureOpenLocked();
|
|
383
537
|
sqlite3_reset(sizeStmt_);
|
|
384
538
|
const int rc = sqlite3_step(sizeStmt_);
|
|
385
539
|
if (rc != SQLITE_ROW) {
|
|
@@ -393,6 +547,7 @@ size_t SqliteDiskStore::size() {
|
|
|
393
547
|
|
|
394
548
|
void SqliteDiskStore::clear() {
|
|
395
549
|
std::lock_guard<std::mutex> lock(mutex_);
|
|
550
|
+
ensureOpenLocked();
|
|
396
551
|
sqlite3_reset(clearStmt_);
|
|
397
552
|
const int rc = sqlite3_step(clearStmt_);
|
|
398
553
|
sqlite3_reset(clearStmt_);
|
|
@@ -451,6 +606,7 @@ void SqliteDiskStore::migrateIfAbsent(
|
|
|
451
606
|
return;
|
|
452
607
|
}
|
|
453
608
|
std::lock_guard<std::mutex> lock(mutex_);
|
|
609
|
+
ensureOpenLocked();
|
|
454
610
|
beginLocked();
|
|
455
611
|
try {
|
|
456
612
|
insertAbsentLocked(entries);
|
|
@@ -463,6 +619,7 @@ void SqliteDiskStore::migrateIfAbsent(
|
|
|
463
619
|
|
|
464
620
|
bool SqliteDiskStore::hasMigrationMarker(const std::string& name) {
|
|
465
621
|
std::lock_guard<std::mutex> lock(mutex_);
|
|
622
|
+
ensureOpenLocked();
|
|
466
623
|
return hasMigrationMarkerLocked(name);
|
|
467
624
|
}
|
|
468
625
|
|
|
@@ -471,6 +628,7 @@ void SqliteDiskStore::migrateOnce(
|
|
|
471
628
|
const std::vector<std::pair<std::string, std::string>>& entries
|
|
472
629
|
) {
|
|
473
630
|
std::lock_guard<std::mutex> lock(mutex_);
|
|
631
|
+
ensureOpenLocked();
|
|
474
632
|
beginLocked();
|
|
475
633
|
try {
|
|
476
634
|
if (hasMigrationMarkerLocked(name)) {
|
|
@@ -20,7 +20,10 @@ public:
|
|
|
20
20
|
|
|
21
21
|
static SqliteDiskStore& shared(const std::string& path);
|
|
22
22
|
static void resetShared();
|
|
23
|
+
static void recreateShared(const std::string& path);
|
|
24
|
+
static void removeDatabaseFiles(const std::string& path);
|
|
23
25
|
static std::string defaultPath();
|
|
26
|
+
static bool isValidUtf8(const std::string& value);
|
|
24
27
|
|
|
25
28
|
const std::string& path() const { return path_; }
|
|
26
29
|
|
|
@@ -44,9 +47,14 @@ public:
|
|
|
44
47
|
const std::string& name,
|
|
45
48
|
const std::vector<std::pair<std::string, std::string>>& entries
|
|
46
49
|
);
|
|
50
|
+
void recreate();
|
|
51
|
+
void limitPageCountForTesting(int pages);
|
|
52
|
+
void limitValueLengthForTesting(int bytes);
|
|
47
53
|
|
|
48
54
|
private:
|
|
49
55
|
void openLocked();
|
|
56
|
+
void reopenLocked();
|
|
57
|
+
void ensureOpenLocked();
|
|
50
58
|
void closeLocked();
|
|
51
59
|
void execLocked(const char* sql);
|
|
52
60
|
void beginLocked();
|
|
@@ -67,7 +75,9 @@ private:
|
|
|
67
75
|
sqlite3_stmt* removeStmt_ = nullptr;
|
|
68
76
|
sqlite3_stmt* hasStmt_ = nullptr;
|
|
69
77
|
sqlite3_stmt* keysStmt_ = nullptr;
|
|
70
|
-
sqlite3_stmt*
|
|
78
|
+
sqlite3_stmt* prefixRangeStmt_ = nullptr;
|
|
79
|
+
sqlite3_stmt* prefixFromStmt_ = nullptr;
|
|
80
|
+
sqlite3_stmt* prefixLikeStmt_ = nullptr;
|
|
71
81
|
sqlite3_stmt* sizeStmt_ = nullptr;
|
|
72
82
|
sqlite3_stmt* clearStmt_ = nullptr;
|
|
73
83
|
sqlite3_stmt* insertAbsentStmt_ = nullptr;
|
package/docs/api-reference.md
CHANGED
|
@@ -276,16 +276,17 @@ the native or web adapter. `isStorageError(error, code)` matches one exact code
|
|
|
276
276
|
without parsing platform message text. See [secure-storage.md](secure-storage.md)
|
|
277
277
|
for recovery semantics.
|
|
278
278
|
|
|
279
|
-
| Code | Raised when
|
|
280
|
-
| ----------------------------- |
|
|
281
|
-
| `keychain_locked` | The protected store is locked.
|
|
282
|
-
| `authentication_required` | The item needs user authentication, or the user cancelled the prompt.
|
|
283
|
-
| `key_invalidated` | The protecting key was invalidated, for example by a biometric enrolment change.
|
|
284
|
-
| `biometric_unavailable` | The requested biometric level is not available on this device or OS version.
|
|
285
|
-
| `storage_corruption` | Stored secure data could not be
|
|
286
|
-
| `storage_compensation_failed` | A multi-step write failed and restoring the previous state also failed.
|
|
287
|
-
| `unsupported` | The operation is not available on this platform or environment.
|
|
288
|
-
| `
|
|
279
|
+
| Code | Raised when |
|
|
280
|
+
| ----------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
281
|
+
| `keychain_locked` | The protected store is locked. |
|
|
282
|
+
| `authentication_required` | The item needs user authentication, or the user cancelled the prompt. |
|
|
283
|
+
| `key_invalidated` | The protecting key was invalidated, for example by a biometric enrolment change. |
|
|
284
|
+
| `biometric_unavailable` | The requested biometric level is not available on this device or OS version. |
|
|
285
|
+
| `storage_corruption` | Stored secure data could not be decoded, the Disk database is corrupt, or (Android) the Secure master key or store cannot be created. Do not retry. |
|
|
286
|
+
| `storage_compensation_failed` | A multi-step write failed and restoring the previous state also failed. |
|
|
287
|
+
| `unsupported` | The operation is not available on this platform or environment. |
|
|
288
|
+
| `storage_full` | The device or database is out of space, or the web storage quota is exceeded. Free space before retrying. |
|
|
289
|
+
| `invalid_key` | A storage key is empty. |
|
|
289
290
|
|
|
290
291
|
`StorageCompositeError` and `StorageCompensationError` describe errors that
|
|
291
292
|
carry a primary failure plus secondary failures from reconciliation or
|
package/docs/native-libraries.md
CHANGED
|
@@ -28,6 +28,34 @@ wipe leaves no pre-SQLite copy behind. Later Disk reads and writes use SQLite.
|
|
|
28
28
|
| RocksDB / LevelDB | Rejected. Heavy native footprint for small preference-sized values. |
|
|
29
29
|
| yyjson | Rejected for Disk. Typed TTL/JSON envelopes stay in JavaScript. |
|
|
30
30
|
|
|
31
|
+
## Limits and durability
|
|
32
|
+
|
|
33
|
+
- Disk writes wait up to 5 seconds for another connection that holds the
|
|
34
|
+
SQLite write lock. The library uses one connection per process, so this wait
|
|
35
|
+
only happens when another process (for example an app extension in the same
|
|
36
|
+
container) is writing. Reads do not wait in WAL mode. Five seconds gives the
|
|
37
|
+
other writer time to finish and stays below the iOS and Android
|
|
38
|
+
unresponsive-app limits.
|
|
39
|
+
- If the Disk database file is deleted while the app runs, the open connection
|
|
40
|
+
keeps reading and writing the deleted file, and the next launch starts with
|
|
41
|
+
an empty database. The library does not check for this on each call, because
|
|
42
|
+
the check costs a system call on every write. Do not delete
|
|
43
|
+
`nitro-storage-disk.sqlite` yourself; call
|
|
44
|
+
`storage.clear(StorageScope.Disk)`.
|
|
45
|
+
- Each platform keeps its system SQLite checkpoint settings. iOS checkpoints
|
|
46
|
+
every 1000 pages and truncates the WAL to 32 KiB; these values are set in
|
|
47
|
+
code so they do not depend on the SQLite build. Android checkpoints every
|
|
48
|
+
100 pages with a 512 KiB limit, applied by the Android framework to every
|
|
49
|
+
pooled connection.
|
|
50
|
+
- Android opens the Disk database in WAL mode. If that open fails for a reason
|
|
51
|
+
other than corruption (for example a full device while an older database
|
|
52
|
+
converts to WAL), it opens once without WAL and uses `synchronous=FULL`, so
|
|
53
|
+
reads keep working; the next launch tries WAL again. If WAL is active and the
|
|
54
|
+
device is so full that the WAL shared-memory file cannot be created, reads
|
|
55
|
+
fail with `storage_full` until space is free. iOS has the same limit.
|
|
56
|
+
- With WAL and `synchronous=NORMAL`, an app kill never loses a committed
|
|
57
|
+
write. A power loss can lose the most recent commits.
|
|
58
|
+
|
|
31
59
|
## Compatibility
|
|
32
60
|
|
|
33
61
|
- Public Disk APIs (`get`/`set`/`setBatch`/`getKeysByPrefix`/`import`) are
|