react-native-nitro-sqlite 9.7.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 +44 -12
- 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/importSqlFile.cpp +10 -5
- package/cpp/importSqlFile.hpp +5 -1
- package/cpp/operations.cpp +116 -35
- package/cpp/operations.hpp +29 -0
- package/cpp/sqliteExecuteBatch.cpp +9 -4
- 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 -3
- 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 -4
- 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/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/nitrogen/generated/android/kotlin/com/margelo/nitro/rnnitrosqlite/HybridNitroSQLiteOnLoadSpec.kt +2 -0
- package/package.json +7 -6
- package/src/DatabaseQueue.ts +9 -19
- package/src/operations/execute.ts +52 -4
- package/src/operations/session.ts +57 -11
- package/src/operations/transaction.ts +6 -6
package/RNNitroSQLite.podspec
CHANGED
|
@@ -1,15 +1,49 @@
|
|
|
1
1
|
require "json"
|
|
2
2
|
|
|
3
3
|
package = JSON.parse(File.read(File.join(__dir__, "package.json")))
|
|
4
|
+
app_package_json_path = File.expand_path("../package.json", Pod::Config.instance.installation_root)
|
|
5
|
+
app_package = File.exist?(app_package_json_path) ? JSON.parse(File.read(app_package_json_path)) : {}
|
|
6
|
+
app_config = app_package.fetch("nitroSQLite", {})
|
|
7
|
+
|
|
8
|
+
unless app_config.is_a?(Hash)
|
|
9
|
+
raise "nitroSQLite in package.json must be an object"
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
if ENV.key?("NITRO_SQLITE_THREADSAFE")
|
|
13
|
+
thread_safe_value = ENV["NITRO_SQLITE_THREADSAFE"]
|
|
14
|
+
unless %w[true false 1 0].include?(thread_safe_value)
|
|
15
|
+
raise "NITRO_SQLITE_THREADSAFE must be true, false, 1, or 0"
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
sqlite_threadsafe = %w[true 1].include?(thread_safe_value) ? "1" : "0"
|
|
19
|
+
else
|
|
20
|
+
thread_safe_value = app_config.fetch("threadSafe", true)
|
|
21
|
+
unless [true, false].include?(thread_safe_value)
|
|
22
|
+
raise "nitroSQLite.threadSafe in package.json must be true or false"
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
sqlite_threadsafe = thread_safe_value ? "1" : "0"
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
if ENV.key?("NITRO_SQLITE_PERFORMANCE_MODE")
|
|
29
|
+
performance_mode_value = ENV["NITRO_SQLITE_PERFORMANCE_MODE"]
|
|
30
|
+
unless %w[true false 1 0].include?(performance_mode_value)
|
|
31
|
+
raise "NITRO_SQLITE_PERFORMANCE_MODE must be true, false, 1, or 0"
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
performance_mode = %w[true 1].include?(performance_mode_value)
|
|
35
|
+
else
|
|
36
|
+
performance_mode = app_config.fetch("performanceMode", true)
|
|
37
|
+
|
|
38
|
+
unless [true, false].include?(performance_mode)
|
|
39
|
+
raise "nitroSQLite.performanceMode in package.json must be true or false"
|
|
40
|
+
end
|
|
41
|
+
end
|
|
4
42
|
folly_compiler_flags = '-DFOLLY_NO_CONFIG -DFOLLY_MOBILE=1 -DFOLLY_USE_LIBCPP=1'
|
|
5
43
|
log_message = lambda do |message|
|
|
6
44
|
puts "\e[34m#{message}\e[0m"
|
|
7
45
|
end
|
|
8
46
|
|
|
9
|
-
# TODO: Should be customizable in package.json.
|
|
10
|
-
# Used to create comparable benchmark results
|
|
11
|
-
performance_mode = 1
|
|
12
|
-
|
|
13
47
|
Pod::Spec.new do |s|
|
|
14
48
|
s.name = "RNNitroSQLite"
|
|
15
49
|
s.version = package["version"]
|
|
@@ -33,15 +67,13 @@ Pod::Spec.new do |s|
|
|
|
33
67
|
"cpp/**/*.{h,hpp,c,cpp}"
|
|
34
68
|
]
|
|
35
69
|
|
|
36
|
-
|
|
70
|
+
inherited_cflags = '$(inherited)'
|
|
71
|
+
optimized_cflags = '-DSQLITE_DQS=0 -DSQLITE_DEFAULT_MEMSTATUS=0 -DSQLITE_DEFAULT_WAL_SYNCHRONOUS=1 -DSQLITE_LIKE_DOESNT_MATCH_BLOBS=1 -DSQLITE_MAX_EXPR_DEPTH=0 -DSQLITE_OMIT_DEPRECATED=1 -DSQLITE_OMIT_PROGRESS_CALLBACK=1 -DSQLITE_OMIT_SHARED_CACHE=1 -DSQLITE_USE_ALLOCA=1'
|
|
37
72
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
log_message.call("Thread safe (2) performance mode enabled 🚀")
|
|
43
|
-
other_cflags = optimizedCflags + ' -DSQLITE_THREADSAFE=1 '
|
|
44
|
-
end
|
|
73
|
+
log_message.call("SQLite thread safety: SQLITE_THREADSAFE=#{sqlite_threadsafe}")
|
|
74
|
+
log_message.call("SQLite performance mode: #{performance_mode ? "enabled" : "disabled"}")
|
|
75
|
+
performance_cflags = performance_mode ? " #{optimized_cflags}" : ""
|
|
76
|
+
other_cflags = "#{inherited_cflags}#{performance_cflags} -DSQLITE_THREADSAFE=#{sqlite_threadsafe} "
|
|
45
77
|
|
|
46
78
|
s.pod_target_xcconfig = {
|
|
47
79
|
:GCC_PREPROCESSOR_DEFINITIONS => "HAVE_FULLFSYNC=1",
|
|
@@ -46,6 +46,11 @@ public:
|
|
|
46
46
|
return this->_exceptionString.c_str();
|
|
47
47
|
}
|
|
48
48
|
|
|
49
|
+
static NitroSQLiteException DatabaseAlreadyOpen(const std::string& dbName) {
|
|
50
|
+
return NitroSQLiteException(NitroSQLiteExceptionType::DatabaseCannotBeOpened,
|
|
51
|
+
"Database " + dbName + " is already open. There is already a connection to the database.");
|
|
52
|
+
}
|
|
53
|
+
|
|
49
54
|
static NitroSQLiteException DatabaseNotOpen(const std::string& dbName) {
|
|
50
55
|
return NitroSQLiteException(NitroSQLiteExceptionType::UnableToAttachToDatabase, dbName + " is not open");
|
|
51
56
|
}
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
#include "databaseMigration.hpp"
|
|
2
|
+
#include "logs.hpp"
|
|
3
|
+
#include <array>
|
|
4
|
+
#include <system_error>
|
|
5
|
+
|
|
6
|
+
namespace margelo::nitro::rnnitrosqlite {
|
|
7
|
+
|
|
8
|
+
namespace fs = std::filesystem;
|
|
9
|
+
|
|
10
|
+
namespace {
|
|
11
|
+
|
|
12
|
+
constexpr std::size_t kDatabaseFileCount = 4;
|
|
13
|
+
using DatabaseFiles = std::array<std::string, kDatabaseFileCount>;
|
|
14
|
+
|
|
15
|
+
DatabaseFiles getDatabaseFiles(const std::string& dbName);
|
|
16
|
+
bool copyDatabaseFiles(const DatabaseFiles& files, const fs::path& fromDirectory, const fs::path& toDirectory);
|
|
17
|
+
void removeAuxiliaryDatabaseFiles(const DatabaseFiles& files, const fs::path& directory);
|
|
18
|
+
|
|
19
|
+
} // namespace
|
|
20
|
+
|
|
21
|
+
fs::path migrateDatabase(const std::string& dbName, const fs::path& fromDirectory, const fs::path& toDirectory) {
|
|
22
|
+
const auto files = getDatabaseFiles(dbName);
|
|
23
|
+
std::error_code ec;
|
|
24
|
+
const bool sourceExists = fs::exists(fromDirectory / dbName, ec);
|
|
25
|
+
|
|
26
|
+
if (ec) {
|
|
27
|
+
LOGW("Failed to inspect database %s in its old location: %s", dbName.c_str(), ec.message().c_str());
|
|
28
|
+
return fromDirectory;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
if (!sourceExists) {
|
|
32
|
+
// A completed migration may have been interrupted after deleting the database but before
|
|
33
|
+
// deleting its journals. The destination is already authoritative in that state.
|
|
34
|
+
removeAuxiliaryDatabaseFiles(files, fromDirectory);
|
|
35
|
+
return toDirectory;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// A database in the old directory is the live copy. Clear every database generation file at
|
|
39
|
+
// the destination before copying so SQLite never pairs the source with a stale journal.
|
|
40
|
+
if (!removeDatabaseFiles(dbName, toDirectory)) {
|
|
41
|
+
return fromDirectory;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
fs::create_directories(toDirectory, ec);
|
|
45
|
+
if (ec) {
|
|
46
|
+
LOGW("Failed to create database migration directory %s: %s", toDirectory.string().c_str(), ec.message().c_str());
|
|
47
|
+
return fromDirectory;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
if (!copyDatabaseFiles(files, fromDirectory, toDirectory)) {
|
|
51
|
+
return fromDirectory;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// Delete the database first. If this fails, every source journal must remain beside it so the
|
|
55
|
+
// caller can safely keep using the old location. Leftover journals after a successful database
|
|
56
|
+
// deletion are harmless and are removed on the next migration attempt.
|
|
57
|
+
if (!fs::remove(fromDirectory / dbName, ec) || ec) {
|
|
58
|
+
LOGW("Failed to remove migrated database %s from its old location: %s", dbName.c_str(), ec.message().c_str());
|
|
59
|
+
return fromDirectory;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
removeAuxiliaryDatabaseFiles(files, fromDirectory);
|
|
63
|
+
return toDirectory;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
bool removeDatabaseFiles(const std::string& dbName, const fs::path& directory) {
|
|
67
|
+
const auto files = getDatabaseFiles(dbName);
|
|
68
|
+
|
|
69
|
+
for (const auto& file : files) {
|
|
70
|
+
std::error_code ec;
|
|
71
|
+
fs::remove(directory / file, ec);
|
|
72
|
+
if (ec) {
|
|
73
|
+
LOGW("Failed to remove database file %s: %s", file.c_str(), ec.message().c_str());
|
|
74
|
+
return false;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
return true;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
namespace {
|
|
82
|
+
|
|
83
|
+
DatabaseFiles getDatabaseFiles(const std::string& dbName) {
|
|
84
|
+
return {dbName, dbName + "-journal", dbName + "-wal", dbName + "-shm"};
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
bool copyDatabaseFiles(const DatabaseFiles& files, const fs::path& fromDirectory, const fs::path& toDirectory) {
|
|
88
|
+
for (const auto& file : files) {
|
|
89
|
+
std::error_code ec;
|
|
90
|
+
const bool sourceExists = fs::exists(fromDirectory / file, ec);
|
|
91
|
+
|
|
92
|
+
if (ec) {
|
|
93
|
+
LOGW("Failed to inspect database file %s: %s", file.c_str(), ec.message().c_str());
|
|
94
|
+
return false;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
if (!sourceExists) {
|
|
98
|
+
continue;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
if (!fs::copy_file(fromDirectory / file, toDirectory / file, ec) || ec) {
|
|
102
|
+
LOGW("Failed to migrate database file %s: %s", file.c_str(), ec.message().c_str());
|
|
103
|
+
return false;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
return true;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
void removeAuxiliaryDatabaseFiles(const DatabaseFiles& files, const fs::path& directory) {
|
|
111
|
+
for (std::size_t index = 1; index < files.size(); index++) {
|
|
112
|
+
const auto& file = files[index];
|
|
113
|
+
std::error_code ec;
|
|
114
|
+
fs::remove(directory / file, ec);
|
|
115
|
+
if (ec) {
|
|
116
|
+
LOGW("Failed to remove database file %s: %s", file.c_str(), ec.message().c_str());
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
} // namespace
|
|
122
|
+
|
|
123
|
+
} // namespace margelo::nitro::rnnitrosqlite
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
#pragma once
|
|
2
|
+
|
|
3
|
+
#include <filesystem>
|
|
4
|
+
#include <string>
|
|
5
|
+
|
|
6
|
+
namespace margelo::nitro::rnnitrosqlite {
|
|
7
|
+
|
|
8
|
+
std::filesystem::path migrateDatabase(const std::string& dbName, const std::filesystem::path& fromDirectory,
|
|
9
|
+
const std::filesystem::path& toDirectory);
|
|
10
|
+
|
|
11
|
+
bool removeDatabaseFiles(const std::string& dbName, const std::filesystem::path& directory);
|
|
12
|
+
|
|
13
|
+
} // namespace margelo::nitro::rnnitrosqlite
|
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
#include "HybridNitroSQLite.hpp"
|
|
2
2
|
#include "HybridNitroSQLiteQueryResult.hpp"
|
|
3
3
|
#include "NitroSQLiteException.hpp"
|
|
4
|
+
#include "databaseMigration.hpp"
|
|
4
5
|
#include "importSqlFile.hpp"
|
|
5
6
|
#include "logs.hpp"
|
|
6
7
|
#include "macros.hpp"
|
|
7
8
|
#include "operations.hpp"
|
|
8
9
|
#include "sqliteExecuteBatch.hpp"
|
|
10
|
+
#include <exception>
|
|
11
|
+
#include <filesystem>
|
|
9
12
|
#include <iostream>
|
|
10
13
|
#include <map>
|
|
11
14
|
#include <optional>
|
|
@@ -65,8 +68,26 @@ const std::string getDocPath(const std::optional<std::string>& location) {
|
|
|
65
68
|
return tempDocPath;
|
|
66
69
|
}
|
|
67
70
|
|
|
71
|
+
const std::string getOldDocPath(const std::optional<std::string>& location) {
|
|
72
|
+
std::string oldDocPath = HybridNitroSQLite::migrationDocPath;
|
|
73
|
+
if (location) {
|
|
74
|
+
oldDocPath = oldDocPath + "/" + *location;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
return oldDocPath;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const std::string getMigratedDocPath(const std::string& dbName, const std::optional<std::string>& location) {
|
|
81
|
+
const auto currentDocPath = getDocPath(location);
|
|
82
|
+
if (HybridNitroSQLite::migrationDocPath.empty()) {
|
|
83
|
+
return currentDocPath;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
return migrateDatabase(dbName, getOldDocPath(location), currentDocPath).string();
|
|
87
|
+
}
|
|
88
|
+
|
|
68
89
|
void HybridNitroSQLite::open(const std::string& dbName, const std::optional<std::string>& location) {
|
|
69
|
-
const auto docPath =
|
|
90
|
+
const auto docPath = getMigratedDocPath(dbName, location);
|
|
70
91
|
sqliteOpenDb(dbName, docPath);
|
|
71
92
|
}
|
|
72
93
|
|
|
@@ -75,18 +96,28 @@ void HybridNitroSQLite::close(const std::string& dbName) {
|
|
|
75
96
|
};
|
|
76
97
|
|
|
77
98
|
void HybridNitroSQLite::drop(const std::string& dbName, const std::optional<std::string>& location) {
|
|
78
|
-
const auto
|
|
79
|
-
|
|
99
|
+
const auto currentDocPath = getDocPath(location);
|
|
100
|
+
if (migrationDocPath.empty()) {
|
|
101
|
+
sqliteRemoveDb(dbName, currentDocPath);
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
const auto oldDocPath = getOldDocPath(location);
|
|
106
|
+
std::error_code ec;
|
|
107
|
+
const bool oldDatabaseExists = std::filesystem::exists(std::filesystem::path(oldDocPath) / dbName, ec);
|
|
108
|
+
if (ec) {
|
|
109
|
+
LOGW("Failed to inspect database %s in its old location: %s", dbName.c_str(), ec.message().c_str());
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
sqliteRemoveDb(dbName, oldDatabaseExists || ec ? oldDocPath : currentDocPath);
|
|
113
|
+
removeDatabaseFiles(dbName, oldDocPath);
|
|
114
|
+
removeDatabaseFiles(dbName, currentDocPath);
|
|
80
115
|
};
|
|
81
116
|
|
|
82
117
|
void HybridNitroSQLite::attach(const std::string& mainDbName, const std::string& dbNameToAttach, const std::string& alias,
|
|
83
118
|
const std::optional<std::string>& location) {
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
tempDocPath = tempDocPath + "/" + *location;
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
sqliteAttachDb(mainDbName, tempDocPath, dbNameToAttach, alias);
|
|
119
|
+
const auto attachedDocPath = getMigratedDocPath(dbNameToAttach, location);
|
|
120
|
+
sqliteAttachDb(mainDbName, attachedDocPath, dbNameToAttach, alias);
|
|
90
121
|
};
|
|
91
122
|
|
|
92
123
|
void HybridNitroSQLite::detach(const std::string& mainDbName, const std::string& alias) {
|
|
@@ -101,10 +132,16 @@ std::shared_ptr<HybridNitroSQLiteQueryResultSpec> HybridNitroSQLite::execute(con
|
|
|
101
132
|
std::shared_ptr<Promise<std::shared_ptr<HybridNitroSQLiteQueryResultSpec>>>
|
|
102
133
|
HybridNitroSQLite::executeAsync(const std::string& dbName, const std::string& query, const std::optional<SQLiteQueryParams>& params) {
|
|
103
134
|
const auto copiedParams = copyArrayBufferParamsForBackground(params);
|
|
135
|
+
SQLiteConnectionPtr connection;
|
|
136
|
+
try {
|
|
137
|
+
connection = sqliteGetOpenDatabase(dbName);
|
|
138
|
+
} catch (...) {
|
|
139
|
+
return Promise<std::shared_ptr<HybridNitroSQLiteQueryResultSpec>>::rejected(std::current_exception());
|
|
140
|
+
}
|
|
104
141
|
|
|
105
142
|
return Promise<std::shared_ptr<HybridNitroSQLiteQueryResultSpec>>::async(
|
|
106
|
-
[
|
|
107
|
-
auto result = sqliteExecute(
|
|
143
|
+
[connection, query, copiedParams]() -> std::shared_ptr<HybridNitroSQLiteQueryResultSpec> {
|
|
144
|
+
auto result = sqliteExecute(connection, query, copiedParams);
|
|
108
145
|
return result;
|
|
109
146
|
});
|
|
110
147
|
};
|
|
@@ -122,9 +159,15 @@ std::shared_ptr<Promise<BatchQueryResult>> HybridNitroSQLite::executeBatchAsync(
|
|
|
122
159
|
// ArrayBuffers into native buffers before going off-thread.
|
|
123
160
|
const auto commands = batchParamsToCommands(batchParams);
|
|
124
161
|
const auto copiedCommands = copyArrayBufferParamsForBackground(commands);
|
|
162
|
+
SQLiteConnectionPtr connection;
|
|
163
|
+
try {
|
|
164
|
+
connection = sqliteGetOpenDatabase(dbName);
|
|
165
|
+
} catch (...) {
|
|
166
|
+
return Promise<BatchQueryResult>::rejected(std::current_exception());
|
|
167
|
+
}
|
|
125
168
|
|
|
126
|
-
return Promise<BatchQueryResult>::async([
|
|
127
|
-
auto result = sqliteExecuteBatch(
|
|
169
|
+
return Promise<BatchQueryResult>::async([connection, copiedCommands]() -> BatchQueryResult {
|
|
170
|
+
auto result = sqliteExecuteBatch(connection, copiedCommands);
|
|
128
171
|
return BatchQueryResult(result.rowsAffected);
|
|
129
172
|
});
|
|
130
173
|
};
|
|
@@ -135,9 +178,15 @@ FileLoadResult HybridNitroSQLite::loadFile(const std::string& dbName, const std:
|
|
|
135
178
|
};
|
|
136
179
|
|
|
137
180
|
std::shared_ptr<Promise<FileLoadResult>> HybridNitroSQLite::loadFileAsync(const std::string& dbName, const std::string& location) {
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
181
|
+
SQLiteConnectionPtr connection;
|
|
182
|
+
try {
|
|
183
|
+
connection = sqliteGetOpenDatabase(dbName);
|
|
184
|
+
} catch (...) {
|
|
185
|
+
return Promise<FileLoadResult>::rejected(std::current_exception());
|
|
186
|
+
}
|
|
187
|
+
return Promise<FileLoadResult>::async([connection, location]() -> FileLoadResult {
|
|
188
|
+
const auto result = importSqlFile(connection, location);
|
|
189
|
+
return FileLoadResult(result.commands, result.rowsAffected);
|
|
141
190
|
});
|
|
142
191
|
};
|
|
143
192
|
|
|
@@ -14,6 +14,10 @@ public:
|
|
|
14
14
|
|
|
15
15
|
public:
|
|
16
16
|
static std::string docPath;
|
|
17
|
+
// Directory databases were stored in by previous app versions, when the platform layer has
|
|
18
|
+
// relocated docPath (e.g. iOS with RNNitroSQLite_DatabaseLocation set to "ApplicationSupport").
|
|
19
|
+
// When non-empty, databases found there are resolved as they are opened, attached, or dropped.
|
|
20
|
+
static std::string migrationDocPath;
|
|
17
21
|
|
|
18
22
|
public:
|
|
19
23
|
// Methods
|
|
@@ -43,5 +47,6 @@ public:
|
|
|
43
47
|
};
|
|
44
48
|
|
|
45
49
|
inline std::string HybridNitroSQLite::docPath = "";
|
|
50
|
+
inline std::string HybridNitroSQLite::migrationDocPath = "";
|
|
46
51
|
|
|
47
52
|
} // namespace margelo::nitro::rnnitrosqlite
|
package/cpp/importSqlFile.cpp
CHANGED
|
@@ -11,21 +11,26 @@
|
|
|
11
11
|
namespace margelo::rnnitrosqlite {
|
|
12
12
|
|
|
13
13
|
SQLiteOperationResult importSqlFile(const std::string& dbName, const std::string& fileLocation) {
|
|
14
|
+
return importSqlFile(sqliteGetOpenDatabase(dbName), fileLocation);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
SQLiteOperationResult importSqlFile(const SQLiteConnectionPtr& connection, const std::string& fileLocation) {
|
|
18
|
+
std::lock_guard lock(connection->mutex);
|
|
14
19
|
std::string line;
|
|
15
20
|
std::ifstream sqFile(fileLocation);
|
|
16
21
|
if (sqFile.is_open()) {
|
|
17
22
|
try {
|
|
18
23
|
int rowsAffected = 0;
|
|
19
24
|
int commands = 0;
|
|
20
|
-
sqliteExecuteCommand(
|
|
25
|
+
sqliteExecuteCommand(connection, "BEGIN EXCLUSIVE TRANSACTION");
|
|
21
26
|
while (std::getline(sqFile, line, '\n')) {
|
|
22
27
|
if (!line.empty()) {
|
|
23
28
|
try {
|
|
24
|
-
SQLiteOperationResult result = sqliteExecuteCommand(
|
|
29
|
+
SQLiteOperationResult result = sqliteExecuteCommand(connection, line);
|
|
25
30
|
rowsAffected += result.rowsAffected;
|
|
26
31
|
commands++;
|
|
27
32
|
} catch (NitroSQLiteException& e) {
|
|
28
|
-
sqliteExecuteCommand(
|
|
33
|
+
sqliteExecuteCommand(connection, "ROLLBACK");
|
|
29
34
|
sqFile.close();
|
|
30
35
|
throw NitroSQLiteException::CouldNotLoadFile(fileLocation, "Transaction was rolled back");
|
|
31
36
|
}
|
|
@@ -33,11 +38,11 @@ SQLiteOperationResult importSqlFile(const std::string& dbName, const std::string
|
|
|
33
38
|
}
|
|
34
39
|
|
|
35
40
|
sqFile.close();
|
|
36
|
-
sqliteExecuteCommand(
|
|
41
|
+
sqliteExecuteCommand(connection, "COMMIT");
|
|
37
42
|
return {.rowsAffected = rowsAffected, .commands = commands};
|
|
38
43
|
} catch (...) {
|
|
39
44
|
sqFile.close();
|
|
40
|
-
sqliteExecuteCommand(
|
|
45
|
+
sqliteExecuteCommand(connection, "ROLLBACK");
|
|
41
46
|
throw NitroSQLiteException(NitroSQLiteExceptionType::UnknownError, "Unexpected error. Transaction was rolled back");
|
|
42
47
|
}
|
|
43
48
|
} else {
|
package/cpp/importSqlFile.hpp
CHANGED
|
@@ -7,9 +7,13 @@
|
|
|
7
7
|
#pragma once
|
|
8
8
|
|
|
9
9
|
#include "types.hpp"
|
|
10
|
+
#include <memory>
|
|
10
11
|
|
|
11
12
|
namespace margelo::rnnitrosqlite {
|
|
12
13
|
|
|
14
|
+
struct SQLiteConnection;
|
|
15
|
+
|
|
13
16
|
SQLiteOperationResult importSqlFile(const std::string& dbName, const std::string& fileLocation);
|
|
17
|
+
SQLiteOperationResult importSqlFile(const std::shared_ptr<SQLiteConnection>& connection, const std::string& fileLocation);
|
|
14
18
|
|
|
15
|
-
}
|
|
19
|
+
} // namespace margelo::rnnitrosqlite
|
package/cpp/operations.cpp
CHANGED
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
#include <limits>
|
|
11
11
|
#include <map>
|
|
12
12
|
#include <memory>
|
|
13
|
+
#include <mutex>
|
|
13
14
|
#include <optional>
|
|
14
15
|
#include <sqlite3.h>
|
|
15
16
|
#include <sstream>
|
|
@@ -29,9 +30,39 @@ namespace margelo::rnnitrosqlite {
|
|
|
29
30
|
static constexpr double kInt64MinAsDouble = static_cast<double>(std::numeric_limits<int64_t>::min());
|
|
30
31
|
static constexpr double kInt64UpperBoundAsDouble = -kInt64MinAsDouble;
|
|
31
32
|
|
|
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
|
+
}
|
|
33
56
|
|
|
34
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
|
+
|
|
35
66
|
#ifdef NITRO_SQLITE_VEC
|
|
36
67
|
// Register before opening so the connection exposes vec0 + vec_*.
|
|
37
68
|
margelo::rnnitrosqlitevec::registerVectorExtensions();
|
|
@@ -41,37 +72,54 @@ void sqliteOpenDb(const std::string& dbName, const std::string& docPath) {
|
|
|
41
72
|
|
|
42
73
|
int sqlOpenFlags = SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE | SQLITE_OPEN_FULLMUTEX;
|
|
43
74
|
|
|
44
|
-
sqlite3*
|
|
45
|
-
int
|
|
46
|
-
|
|
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
|
+
}
|
|
47
83
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
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
|
+
}
|
|
52
92
|
}
|
|
53
93
|
}
|
|
54
94
|
|
|
55
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
|
+
}
|
|
56
104
|
|
|
57
|
-
|
|
58
|
-
|
|
105
|
+
connection = std::move(iterator->second);
|
|
106
|
+
dbMap.erase(iterator);
|
|
59
107
|
}
|
|
60
108
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
sqlite3_close_v2(db);
|
|
64
|
-
|
|
65
|
-
dbMap.erase(dbName);
|
|
109
|
+
connection->close();
|
|
66
110
|
}
|
|
67
111
|
|
|
68
112
|
void sqliteCloseAll() {
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
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();
|
|
73
122
|
}
|
|
74
|
-
dbMap.clear();
|
|
75
123
|
}
|
|
76
124
|
|
|
77
125
|
void sqliteAttachDb(const std::string& mainDBName, const std::string& docPath, const std::string& databaseToAttach,
|
|
@@ -105,15 +153,26 @@ void sqliteDetachDb(const std::string& mainDBName, const std::string& alias) {
|
|
|
105
153
|
}
|
|
106
154
|
|
|
107
155
|
void sqliteRemoveDb(const std::string& dbName, const std::string& docPath) {
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
std::string dbFilePath = get_db_path(dbName, docPath);
|
|
156
|
+
std::lock_guard lifecycleLock(dbLifecycleMutex);
|
|
157
|
+
const std::string dbFilePath = get_db_path(dbName, docPath);
|
|
113
158
|
if (!file_exists(dbFilePath)) {
|
|
114
159
|
throw NitroSQLiteException::DatabaseFileNotFound(dbFilePath);
|
|
115
160
|
}
|
|
116
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
|
+
|
|
117
176
|
remove(dbFilePath.c_str());
|
|
118
177
|
}
|
|
119
178
|
|
|
@@ -155,14 +214,6 @@ namespace {
|
|
|
155
214
|
|
|
156
215
|
using SQLiteStatement = std::unique_ptr<sqlite3_stmt, SQLiteStatementFinalizer>;
|
|
157
216
|
|
|
158
|
-
sqlite3* getOpenDatabase(const std::string& dbName) {
|
|
159
|
-
if (dbMap.count(dbName) == 0) {
|
|
160
|
-
throw NitroSQLiteException::DatabaseNotOpen(dbName);
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
return dbMap[dbName];
|
|
164
|
-
}
|
|
165
|
-
|
|
166
217
|
SQLiteStatement prepareStatement(sqlite3* db, const std::string& query, const std::optional<SQLiteQueryParams>& params) {
|
|
167
218
|
sqlite3_stmt* rawStatement = nullptr;
|
|
168
219
|
int statementStatus = sqlite3_prepare_v2(db, query.c_str(), -1, &rawStatement, nullptr);
|
|
@@ -199,9 +250,29 @@ namespace {
|
|
|
199
250
|
|
|
200
251
|
} // namespace
|
|
201
252
|
|
|
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);
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
return iterator->second;
|
|
261
|
+
}
|
|
262
|
+
|
|
202
263
|
std::shared_ptr<HybridNitroSQLiteQueryResult> sqliteExecute(const std::string& dbName, const std::string& query,
|
|
203
264
|
const std::optional<SQLiteQueryParams>& params) {
|
|
204
|
-
|
|
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);
|
|
274
|
+
}
|
|
275
|
+
|
|
205
276
|
auto statement = prepareStatement(db, query, params);
|
|
206
277
|
SQLiteQueryResults results;
|
|
207
278
|
|
|
@@ -265,7 +336,17 @@ std::shared_ptr<HybridNitroSQLiteQueryResult> sqliteExecute(const std::string& d
|
|
|
265
336
|
|
|
266
337
|
SQLiteOperationResult sqliteExecuteCommand(const std::string& dbName, const std::string& query,
|
|
267
338
|
const std::optional<SQLiteQueryParams>& params) {
|
|
268
|
-
|
|
339
|
+
return sqliteExecuteCommand(sqliteGetOpenDatabase(dbName), query, params);
|
|
340
|
+
}
|
|
341
|
+
|
|
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);
|
|
348
|
+
}
|
|
349
|
+
|
|
269
350
|
auto statement = prepareStatement(db, query, params);
|
|
270
351
|
bool isReadOnly = sqlite3_stmt_readonly(statement.get()) != 0;
|
|
271
352
|
|