react-native-nitro-sqlite 9.7.0 → 9.8.1

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.
Files changed (45) hide show
  1. package/RNNitroSQLite.podspec +44 -12
  2. package/cpp/NitroSQLiteException.hpp +5 -0
  3. package/cpp/databaseMigration.cpp +123 -0
  4. package/cpp/databaseMigration.hpp +13 -0
  5. package/cpp/hybridObjects/HybridNitroSQLite.cpp +65 -16
  6. package/cpp/hybridObjects/HybridNitroSQLite.hpp +5 -0
  7. package/cpp/importSqlFile.cpp +10 -5
  8. package/cpp/importSqlFile.hpp +5 -1
  9. package/cpp/operations.cpp +116 -35
  10. package/cpp/operations.hpp +29 -0
  11. package/cpp/sqliteExecuteBatch.cpp +9 -4
  12. package/cpp/sqliteExecuteBatch.hpp +4 -0
  13. package/ios/OnLoad.mm +26 -3
  14. package/lib/commonjs/DatabaseQueue.js +4 -17
  15. package/lib/commonjs/DatabaseQueue.js.map +1 -1
  16. package/lib/commonjs/operations/execute.js +28 -3
  17. package/lib/commonjs/operations/execute.js.map +1 -1
  18. package/lib/commonjs/operations/session.js +34 -9
  19. package/lib/commonjs/operations/session.js.map +1 -1
  20. package/lib/commonjs/operations/transaction.js +5 -5
  21. package/lib/commonjs/operations/transaction.js.map +1 -1
  22. package/lib/module/DatabaseQueue.js +4 -15
  23. package/lib/module/DatabaseQueue.js.map +1 -1
  24. package/lib/module/operations/execute.js +24 -4
  25. package/lib/module/operations/execute.js.map +1 -1
  26. package/lib/module/operations/session.js +36 -11
  27. package/lib/module/operations/session.js.map +1 -1
  28. package/lib/module/operations/transaction.js +6 -6
  29. package/lib/module/operations/transaction.js.map +1 -1
  30. package/lib/typescript/commonjs/DatabaseQueue.d.ts +1 -3
  31. package/lib/typescript/commonjs/DatabaseQueue.d.ts.map +1 -1
  32. package/lib/typescript/commonjs/operations/execute.d.ts +6 -0
  33. package/lib/typescript/commonjs/operations/execute.d.ts.map +1 -1
  34. package/lib/typescript/commonjs/operations/session.d.ts.map +1 -1
  35. package/lib/typescript/module/DatabaseQueue.d.ts +1 -3
  36. package/lib/typescript/module/DatabaseQueue.d.ts.map +1 -1
  37. package/lib/typescript/module/operations/execute.d.ts +6 -0
  38. package/lib/typescript/module/operations/execute.d.ts.map +1 -1
  39. package/lib/typescript/module/operations/session.d.ts.map +1 -1
  40. package/nitrogen/generated/android/kotlin/com/margelo/nitro/rnnitrosqlite/HybridNitroSQLiteOnLoadSpec.kt +2 -0
  41. package/package.json +25 -5
  42. package/src/DatabaseQueue.ts +9 -19
  43. package/src/operations/execute.ts +52 -4
  44. package/src/operations/session.ts +57 -11
  45. package/src/operations/transaction.ts +6 -6
@@ -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
- optimizedCflags = '$(inherited) -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'
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
- if performance_mode == 1
39
- log_message.call("Thread unsafe (1) performance mode enabled. Use only transactions! 🚀🚀")
40
- other_cflags = optimizedCflags + ' -DSQLITE_THREADSAFE=0 '
41
- elsif performance_mode == 2
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 = getDocPath(location);
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 docPath = getDocPath(location);
79
- sqliteRemoveDb(dbName, docPath);
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
- std::string tempDocPath = std::string(docPath);
85
- if (location) {
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
- [=, this]() -> std::shared_ptr<HybridNitroSQLiteQueryResultSpec> {
107
- auto result = sqliteExecute(dbName, query, copiedParams);
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([=, this]() -> BatchQueryResult {
127
- auto result = sqliteExecuteBatch(dbName, copiedCommands);
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
- return Promise<FileLoadResult>::async([=, this]() -> FileLoadResult {
139
- auto result = loadFile(dbName, location);
140
- return result;
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
@@ -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(dbName, "BEGIN EXCLUSIVE TRANSACTION");
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(dbName, line);
29
+ SQLiteOperationResult result = sqliteExecuteCommand(connection, line);
25
30
  rowsAffected += result.rowsAffected;
26
31
  commands++;
27
32
  } catch (NitroSQLiteException& e) {
28
- sqliteExecuteCommand(dbName, "ROLLBACK");
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(dbName, "COMMIT");
41
+ sqliteExecuteCommand(connection, "COMMIT");
37
42
  return {.rowsAffected = rowsAffected, .commands = commands};
38
43
  } catch (...) {
39
44
  sqFile.close();
40
- sqliteExecuteCommand(dbName, "ROLLBACK");
45
+ sqliteExecuteCommand(connection, "ROLLBACK");
41
46
  throw NitroSQLiteException(NitroSQLiteExceptionType::UnknownError, "Unexpected error. Transaction was rolled back");
42
47
  }
43
48
  } else {
@@ -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
@@ -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
- std::map<std::string, sqlite3*> dbMap = std::map<std::string, sqlite3*>();
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* db;
45
- int exit = 0;
46
- exit = sqlite3_open_v2(dbPath.c_str(), &db, sqlOpenFlags, nullptr);
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
- if (exit != SQLITE_OK) {
49
- throw NitroSQLiteException(NitroSQLiteExceptionType::DatabaseCannotBeOpened, sqlite3_errmsg(db));
50
- } else {
51
- dbMap[dbName] = db;
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
- if (dbMap.count(dbName) == 0) {
58
- throw NitroSQLiteException::DatabaseNotOpen(dbName);
105
+ connection = std::move(iterator->second);
106
+ dbMap.erase(iterator);
59
107
  }
60
108
 
61
- sqlite3* db = dbMap[dbName];
62
-
63
- sqlite3_close_v2(db);
64
-
65
- dbMap.erase(dbName);
109
+ connection->close();
66
110
  }
67
111
 
68
112
  void sqliteCloseAll() {
69
- for (auto const& x : dbMap) {
70
- // In certain cases, this will return SQLITE_OK, mark the database connection as an unusable "zombie",
71
- // and deallocate the connection later.
72
- sqlite3_close_v2(x.second);
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
- if (dbMap.count(dbName) == 1) {
109
- sqliteCloseDb(dbName);
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
- auto db = getOpenDatabase(dbName);
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
- auto db = getOpenDatabase(dbName);
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