react-native-nitro-sqlite 9.8.2 → 10.0.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.
Files changed (143) hide show
  1. package/RNNitroSQLite.podspec +7 -3
  2. package/android/CMakeLists.txt +2 -2
  3. package/cpp/NitroSQLiteDatabaseConnections.cpp +306 -0
  4. package/cpp/NitroSQLiteDatabaseConnections.hpp +86 -0
  5. package/cpp/{databaseMigration.cpp → NitroSQLiteDatabaseMigration.cpp} +2 -2
  6. package/cpp/{sqliteExecuteBatch.cpp → NitroSQLiteExecuteBatch.cpp} +4 -4
  7. package/cpp/{sqliteExecuteBatch.hpp → NitroSQLiteExecuteBatch.hpp} +3 -6
  8. package/cpp/{importSqlFile.cpp → NitroSQLiteImportSqlFile.cpp} +4 -4
  9. package/cpp/{importSqlFile.hpp → NitroSQLiteImportSqlFile.hpp} +3 -3
  10. package/cpp/{macros.hpp → NitroSQLiteMacros.hpp} +3 -3
  11. package/cpp/NitroSQLiteOperations.cpp +396 -0
  12. package/cpp/NitroSQLiteOperations.hpp +72 -0
  13. package/cpp/{types.hpp → NitroSQLiteTypes.hpp} +3 -6
  14. package/cpp/{utils.hpp → NitroSQLiteUtils.hpp} +2 -2
  15. package/cpp/hybridObjects/HybridNitroSQLite.cpp +108 -22
  16. package/cpp/hybridObjects/HybridNitroSQLite.hpp +49 -5
  17. package/cpp/hybridObjects/HybridNitroSQLitePreparedStatement.cpp +59 -0
  18. package/cpp/hybridObjects/HybridNitroSQLitePreparedStatement.hpp +33 -0
  19. package/cpp/hybridObjects/HybridNitroSQLiteQueryResult.hpp +6 -4
  20. package/ios/OnLoad.mm +38 -2
  21. package/lib/commonjs/DatabaseQueue.js +52 -16
  22. package/lib/commonjs/DatabaseQueue.js.map +1 -1
  23. package/lib/commonjs/NitroSQLiteError.js +6 -7
  24. package/lib/commonjs/NitroSQLiteError.js.map +1 -1
  25. package/lib/commonjs/OnLoad.js +1 -1
  26. package/lib/commonjs/index.js +8 -3
  27. package/lib/commonjs/index.js.map +1 -1
  28. package/lib/commonjs/operations/execute.js +19 -4
  29. package/lib/commonjs/operations/execute.js.map +1 -1
  30. package/lib/commonjs/operations/executeBatch.js +19 -6
  31. package/lib/commonjs/operations/executeBatch.js.map +1 -1
  32. package/lib/commonjs/operations/prepare.js +54 -0
  33. package/lib/commonjs/operations/prepare.js.map +1 -0
  34. package/lib/commonjs/operations/session.js +106 -30
  35. package/lib/commonjs/operations/session.js.map +1 -1
  36. package/lib/commonjs/operations/transaction.js +30 -5
  37. package/lib/commonjs/operations/transaction.js.map +1 -1
  38. package/lib/commonjs/specs/NitroSQLitePreparedStatement.nitro.js +6 -0
  39. package/lib/commonjs/specs/NitroSQLitePreparedStatement.nitro.js.map +1 -0
  40. package/lib/commonjs/typeORM.js +10 -3
  41. package/lib/commonjs/typeORM.js.map +1 -1
  42. package/lib/commonjs/types.js +35 -9
  43. package/lib/commonjs/types.js.map +1 -1
  44. package/lib/module/DatabaseQueue.js +52 -16
  45. package/lib/module/DatabaseQueue.js.map +1 -1
  46. package/lib/module/NitroSQLiteError.js +6 -7
  47. package/lib/module/NitroSQLiteError.js.map +1 -1
  48. package/lib/module/OnLoad.js +1 -1
  49. package/lib/module/index.js +8 -3
  50. package/lib/module/index.js.map +1 -1
  51. package/lib/module/operations/execute.js +20 -5
  52. package/lib/module/operations/execute.js.map +1 -1
  53. package/lib/module/operations/executeBatch.js +20 -7
  54. package/lib/module/operations/executeBatch.js.map +1 -1
  55. package/lib/module/operations/prepare.js +49 -0
  56. package/lib/module/operations/prepare.js.map +1 -0
  57. package/lib/module/operations/session.js +107 -31
  58. package/lib/module/operations/session.js.map +1 -1
  59. package/lib/module/operations/transaction.js +30 -5
  60. package/lib/module/operations/transaction.js.map +1 -1
  61. package/lib/module/specs/NitroSQLitePreparedStatement.nitro.js +4 -0
  62. package/lib/module/specs/NitroSQLitePreparedStatement.nitro.js.map +1 -0
  63. package/lib/module/typeORM.js +11 -3
  64. package/lib/module/typeORM.js.map +1 -1
  65. package/lib/module/types.js +48 -9
  66. package/lib/module/types.js.map +1 -1
  67. package/lib/typescript/commonjs/DatabaseQueue.d.ts +15 -7
  68. package/lib/typescript/commonjs/DatabaseQueue.d.ts.map +1 -1
  69. package/lib/typescript/commonjs/NitroSQLiteError.d.ts +6 -7
  70. package/lib/typescript/commonjs/NitroSQLiteError.d.ts.map +1 -1
  71. package/lib/typescript/commonjs/index.d.ts +9 -2
  72. package/lib/typescript/commonjs/index.d.ts.map +1 -1
  73. package/lib/typescript/commonjs/operations/execute.d.ts +17 -2
  74. package/lib/typescript/commonjs/operations/execute.d.ts.map +1 -1
  75. package/lib/typescript/commonjs/operations/executeBatch.d.ts +15 -2
  76. package/lib/typescript/commonjs/operations/executeBatch.d.ts.map +1 -1
  77. package/lib/typescript/commonjs/operations/prepare.d.ts +13 -0
  78. package/lib/typescript/commonjs/operations/prepare.d.ts.map +1 -0
  79. package/lib/typescript/commonjs/operations/session.d.ts +8 -0
  80. package/lib/typescript/commonjs/operations/session.d.ts.map +1 -1
  81. package/lib/typescript/commonjs/operations/transaction.d.ts +11 -1
  82. package/lib/typescript/commonjs/operations/transaction.d.ts.map +1 -1
  83. package/lib/typescript/commonjs/specs/NitroSQLite.nitro.d.ts +83 -2
  84. package/lib/typescript/commonjs/specs/NitroSQLite.nitro.d.ts.map +1 -1
  85. package/lib/typescript/commonjs/specs/NitroSQLitePreparedStatement.nitro.d.ts +26 -0
  86. package/lib/typescript/commonjs/specs/NitroSQLitePreparedStatement.nitro.d.ts.map +1 -0
  87. package/lib/typescript/commonjs/specs/NitroSQLiteQueryResult.nitro.d.ts +9 -15
  88. package/lib/typescript/commonjs/specs/NitroSQLiteQueryResult.nitro.d.ts.map +1 -1
  89. package/lib/typescript/commonjs/typeORM.d.ts +14 -3
  90. package/lib/typescript/commonjs/typeORM.d.ts.map +1 -1
  91. package/lib/typescript/commonjs/types.d.ts +124 -21
  92. package/lib/typescript/commonjs/types.d.ts.map +1 -1
  93. package/lib/typescript/module/DatabaseQueue.d.ts +15 -7
  94. package/lib/typescript/module/DatabaseQueue.d.ts.map +1 -1
  95. package/lib/typescript/module/NitroSQLiteError.d.ts +6 -7
  96. package/lib/typescript/module/NitroSQLiteError.d.ts.map +1 -1
  97. package/lib/typescript/module/index.d.ts +9 -2
  98. package/lib/typescript/module/index.d.ts.map +1 -1
  99. package/lib/typescript/module/operations/execute.d.ts +17 -2
  100. package/lib/typescript/module/operations/execute.d.ts.map +1 -1
  101. package/lib/typescript/module/operations/executeBatch.d.ts +15 -2
  102. package/lib/typescript/module/operations/executeBatch.d.ts.map +1 -1
  103. package/lib/typescript/module/operations/prepare.d.ts +13 -0
  104. package/lib/typescript/module/operations/prepare.d.ts.map +1 -0
  105. package/lib/typescript/module/operations/session.d.ts +8 -0
  106. package/lib/typescript/module/operations/session.d.ts.map +1 -1
  107. package/lib/typescript/module/operations/transaction.d.ts +11 -1
  108. package/lib/typescript/module/operations/transaction.d.ts.map +1 -1
  109. package/lib/typescript/module/specs/NitroSQLite.nitro.d.ts +83 -2
  110. package/lib/typescript/module/specs/NitroSQLite.nitro.d.ts.map +1 -1
  111. package/lib/typescript/module/specs/NitroSQLitePreparedStatement.nitro.d.ts +26 -0
  112. package/lib/typescript/module/specs/NitroSQLitePreparedStatement.nitro.d.ts.map +1 -0
  113. package/lib/typescript/module/specs/NitroSQLiteQueryResult.nitro.d.ts +9 -15
  114. package/lib/typescript/module/specs/NitroSQLiteQueryResult.nitro.d.ts.map +1 -1
  115. package/lib/typescript/module/typeORM.d.ts +14 -3
  116. package/lib/typescript/module/typeORM.d.ts.map +1 -1
  117. package/lib/typescript/module/types.d.ts +124 -21
  118. package/lib/typescript/module/types.d.ts.map +1 -1
  119. package/nitrogen/generated/android/RNNitroSQLite+autolinking.cmake +1 -0
  120. package/nitrogen/generated/shared/c++/HybridNitroSQLitePreparedStatementSpec.cpp +24 -0
  121. package/nitrogen/generated/shared/c++/HybridNitroSQLitePreparedStatementSpec.hpp +73 -0
  122. package/nitrogen/generated/shared/c++/HybridNitroSQLiteSpec.cpp +3 -0
  123. package/nitrogen/generated/shared/c++/HybridNitroSQLiteSpec.hpp +8 -2
  124. package/package.json +4 -2
  125. package/src/DatabaseQueue.ts +81 -25
  126. package/src/NitroSQLiteError.ts +6 -7
  127. package/src/OnLoad.ts +1 -1
  128. package/src/index.ts +7 -3
  129. package/src/operations/execute.ts +22 -3
  130. package/src/operations/executeBatch.ts +20 -5
  131. package/src/operations/prepare.ts +69 -0
  132. package/src/operations/session.ts +136 -45
  133. package/src/operations/transaction.ts +37 -4
  134. package/src/specs/NitroSQLite.nitro.ts +84 -2
  135. package/src/specs/NitroSQLitePreparedStatement.nitro.ts +28 -0
  136. package/src/specs/NitroSQLiteQueryResult.nitro.ts +9 -15
  137. package/src/typeORM.ts +14 -3
  138. package/src/types.ts +135 -21
  139. package/cpp/operations.cpp +0 -358
  140. package/cpp/operations.hpp +0 -56
  141. /package/android/{cpp-adapter.cpp → RNNitroSQLiteAdapter.cpp} +0 -0
  142. /package/cpp/{databaseMigration.hpp → NitroSQLiteDatabaseMigration.hpp} +0 -0
  143. /package/cpp/{logs.hpp → NitroSQLiteLogs.hpp} +0 -0
@@ -1,25 +1,27 @@
1
1
  #include "HybridNitroSQLite.hpp"
2
+ #include "../NitroSQLiteDatabaseMigration.hpp"
3
+ #include "../NitroSQLiteException.hpp"
4
+ #include "../NitroSQLiteExecuteBatch.hpp"
5
+ #include "../NitroSQLiteImportSqlFile.hpp"
6
+ #include "../NitroSQLiteLogs.hpp"
7
+ #include "../NitroSQLiteMacros.hpp"
8
+ #include "../NitroSQLiteOperations.hpp"
9
+ #include "HybridNitroSQLitePreparedStatement.hpp"
2
10
  #include "HybridNitroSQLiteQueryResult.hpp"
3
- #include "NitroSQLiteException.hpp"
4
- #include "databaseMigration.hpp"
5
- #include "importSqlFile.hpp"
6
- #include "logs.hpp"
7
- #include "macros.hpp"
8
- #include "operations.hpp"
9
- #include "sqliteExecuteBatch.hpp"
10
11
  #include <exception>
11
12
  #include <filesystem>
12
13
  #include <iostream>
13
14
  #include <map>
14
15
  #include <optional>
15
16
  #include <string>
17
+ #include <utility>
16
18
  #include <variant>
17
19
  #include <vector>
18
20
 
19
21
  namespace margelo::nitro::rnnitrosqlite {
20
22
 
21
23
  // Copy any JS-backed ArrayBuffers on the JS thread so they can be safely
22
- // accessed from the background thread used by Promise::async.
24
+ // accessed from the connection's background worker.
23
25
  static std::optional<SQLiteQueryParams> copyArrayBufferParamsForBackground(const std::optional<SQLiteQueryParams>& params) {
24
26
  if (!params) {
25
27
  return std::nullopt;
@@ -59,7 +61,40 @@ static std::vector<BatchQuery> copyArrayBufferParamsForBackground(const std::vec
59
61
  return copiedCommands;
60
62
  }
61
63
 
64
+ template <typename Result, typename Operation>
65
+ static std::shared_ptr<Promise<Result>> enqueueConnectionOperation(const SQLiteConnectionPtr& connection, Operation&& operation) {
66
+ auto promise = Promise<Result>::create();
67
+ try {
68
+ connection->enqueueAsync([promise, operation = std::forward<Operation>(operation)]() mutable {
69
+ std::optional<Result> result;
70
+ try {
71
+ result.emplace(operation());
72
+ } catch (...) {
73
+ promise->reject(std::current_exception());
74
+ return;
75
+ }
76
+ // Resolving may dispatch to JavaScript and throw after the native promise
77
+ // has settled. Do not try to reject that same promise again.
78
+ try {
79
+ promise->resolve(std::move(*result));
80
+ } catch (...) {
81
+ if (promise->isPending()) {
82
+ promise->reject(std::current_exception());
83
+ return;
84
+ }
85
+ throw;
86
+ }
87
+ });
88
+ } catch (...) {
89
+ promise->reject(std::current_exception());
90
+ }
91
+ return promise;
92
+ }
93
+
62
94
  const std::string getDocPath(const std::optional<std::string>& location) {
95
+ if (location && location->find('\0') != std::string::npos) {
96
+ throw NitroSQLiteException(NitroSQLiteExceptionType::DatabaseCannotBeOpened, "Database location contains a NUL byte");
97
+ }
63
98
  std::string tempDocPath = std::string(HybridNitroSQLite::docPath);
64
99
  if (location) {
65
100
  tempDocPath = tempDocPath + "/" + *location;
@@ -77,45 +112,92 @@ const std::string getOldDocPath(const std::optional<std::string>& location) {
77
112
  return oldDocPath;
78
113
  }
79
114
 
80
- const std::string getMigratedDocPath(const std::string& dbName, const std::optional<std::string>& location) {
115
+ const std::string getMigratedDocPath(const std::string& dbName, const std::optional<std::string>& location, bool readOnly = false) {
81
116
  const auto currentDocPath = getDocPath(location);
82
117
  if (HybridNitroSQLite::migrationDocPath.empty()) {
83
118
  return currentDocPath;
84
119
  }
120
+ const auto oldDocPath = getOldDocPath(location);
121
+ std::string selectedPath;
122
+ databaseConnections().withConnectionsLocked([&]() {
123
+ const auto oldPath = std::filesystem::path(oldDocPath) / dbName;
124
+ const auto livePath = databaseConnections().findLivePath(oldPath, std::filesystem::path(currentDocPath) / dbName);
125
+ if (livePath) {
126
+ selectedPath = *livePath == oldPath ? oldDocPath : currentDocPath;
127
+ } else if (readOnly) {
128
+ selectedPath = std::filesystem::exists(std::filesystem::path(oldDocPath) / dbName) ? oldDocPath : currentDocPath;
129
+ } else {
130
+ selectedPath = migrateDatabase(dbName, oldDocPath, currentDocPath).string();
131
+ }
132
+ });
133
+ return selectedPath;
134
+ }
85
135
 
86
- return migrateDatabase(dbName, getOldDocPath(location), currentDocPath).string();
136
+ void HybridNitroSQLite::open(const std::string& dbName, const std::optional<std::string>& location, std::optional<bool> readOnly) {
137
+ validateDatabaseName(dbName);
138
+ std::lock_guard lock(databaseConnections().lifecycleMutex);
139
+ if (databaseConnections().isOpen(dbName)) {
140
+ throw NitroSQLiteException::DatabaseAlreadyOpen(dbName);
141
+ }
142
+ const auto docPath = getMigratedDocPath(dbName, location, readOnly.value_or(false));
143
+ sqliteOpenDb(dbName, docPath, readOnly.value_or(false));
87
144
  }
88
145
 
89
- void HybridNitroSQLite::open(const std::string& dbName, const std::optional<std::string>& location) {
90
- const auto docPath = getMigratedDocPath(dbName, location);
91
- sqliteOpenDb(dbName, docPath);
146
+ std::string HybridNitroSQLite::openConnection(const std::string& dbName, const std::optional<std::string>& location,
147
+ std::optional<bool> readOnly) {
148
+ validateDatabaseName(dbName);
149
+ if (sqlite3_threadsafe() == 0) {
150
+ throw NitroSQLiteException(NitroSQLiteExceptionType::DatabaseCannotBeOpened,
151
+ "Independent connections require a thread-safe SQLite build");
152
+ }
153
+ std::lock_guard lock(databaseConnections().lifecycleMutex);
154
+ const auto docPath = getMigratedDocPath(dbName, location, readOnly.value_or(false));
155
+ return sqliteOpenConnection(dbName, docPath, readOnly.value_or(false));
92
156
  }
93
157
 
94
158
  void HybridNitroSQLite::close(const std::string& dbName) {
95
159
  sqliteCloseDb(dbName);
96
160
  };
97
161
 
98
- void HybridNitroSQLite::drop(const std::string& dbName, const std::optional<std::string>& location) {
162
+ bool HybridNitroSQLite::isConnectionOpen(const std::string& connectionId) {
163
+ return databaseConnections().isOpen(connectionId);
164
+ }
165
+
166
+ void HybridNitroSQLite::drop(const std::string& dbName, const std::optional<std::string>& location,
167
+ const std::optional<std::string>& connectionId) {
168
+ validateDatabaseName(dbName);
169
+ std::lock_guard lock(databaseConnections().lifecycleMutex);
99
170
  const auto currentDocPath = getDocPath(location);
100
171
  if (migrationDocPath.empty()) {
101
- sqliteRemoveDb(dbName, currentDocPath);
172
+ sqliteRemoveDb(dbName, currentDocPath, connectionId);
102
173
  return;
103
174
  }
104
175
 
105
176
  const auto oldDocPath = getOldDocPath(location);
177
+ const auto preferredPath = databaseConnections().physicalPathForKey(connectionId.value_or(dbName));
178
+ const auto currentPath = std::filesystem::path(currentDocPath) / dbName;
106
179
  std::error_code ec;
107
180
  const bool oldDatabaseExists = std::filesystem::exists(std::filesystem::path(oldDocPath) / dbName, ec);
108
181
  if (ec) {
109
182
  LOGW("Failed to inspect database %s in its old location: %s", dbName.c_str(), ec.message().c_str());
110
183
  }
111
184
 
112
- sqliteRemoveDb(dbName, oldDatabaseExists || ec ? oldDocPath : currentDocPath);
113
- removeDatabaseFiles(dbName, oldDocPath);
114
- removeDatabaseFiles(dbName, currentDocPath);
185
+ // A stale copy in the old directory must not override the actual target of a live or
186
+ // recently closed independent connection.
187
+ std::error_code equivalentError;
188
+ const bool prefersCurrent = preferredPath && (std::filesystem::equivalent(*preferredPath, currentPath, equivalentError) ||
189
+ canonicalDatabasePath(*preferredPath) == canonicalDatabasePath(currentPath));
190
+ const bool useOldPath = !prefersCurrent && (preferredPath || oldDatabaseExists || ec);
191
+ sqliteRemoveDb(dbName, useOldPath ? oldDocPath : currentDocPath, connectionId, useOldPath ? currentDocPath : oldDocPath);
115
192
  };
116
193
 
117
194
  void HybridNitroSQLite::attach(const std::string& mainDbName, const std::string& dbNameToAttach, const std::string& alias,
118
195
  const std::optional<std::string>& location) {
196
+ validateDatabaseName(dbNameToAttach);
197
+ std::lock_guard lock(databaseConnections().lifecycleMutex);
198
+ if (databaseConnections().get(mainDbName)->readOnly) {
199
+ throw NitroSQLiteException(NitroSQLiteExceptionType::UnableToAttachToDatabase, "Cannot attach a database to a read-only connection");
200
+ }
119
201
  const auto attachedDocPath = getMigratedDocPath(dbNameToAttach, location);
120
202
  sqliteAttachDb(mainDbName, attachedDocPath, dbNameToAttach, alias);
121
203
  };
@@ -139,13 +221,17 @@ HybridNitroSQLite::executeAsync(const std::string& dbName, const std::string& qu
139
221
  return Promise<std::shared_ptr<HybridNitroSQLiteQueryResultSpec>>::rejected(std::current_exception());
140
222
  }
141
223
 
142
- return Promise<std::shared_ptr<HybridNitroSQLiteQueryResultSpec>>::async(
143
- [connection, query, copiedParams]() -> std::shared_ptr<HybridNitroSQLiteQueryResultSpec> {
224
+ return enqueueConnectionOperation<std::shared_ptr<HybridNitroSQLiteQueryResultSpec>>(
225
+ connection, [connection, query, copiedParams]() -> std::shared_ptr<HybridNitroSQLiteQueryResultSpec> {
144
226
  auto result = sqliteExecute(connection, query, copiedParams);
145
227
  return result;
146
228
  });
147
229
  };
148
230
 
231
+ std::shared_ptr<HybridNitroSQLitePreparedStatementSpec> HybridNitroSQLite::prepare(const std::string& dbName, const std::string& query) {
232
+ return std::make_shared<HybridNitroSQLitePreparedStatement>(sqlitePrepare(dbName, query));
233
+ }
234
+
149
235
  BatchQueryResult HybridNitroSQLite::executeBatch(const std::string& dbName, const std::vector<BatchQueryCommand>& batchParams) {
150
236
  const auto commands = batchParamsToCommands(batchParams);
151
237
 
@@ -166,7 +252,7 @@ std::shared_ptr<Promise<BatchQueryResult>> HybridNitroSQLite::executeBatchAsync(
166
252
  return Promise<BatchQueryResult>::rejected(std::current_exception());
167
253
  }
168
254
 
169
- return Promise<BatchQueryResult>::async([connection, copiedCommands]() -> BatchQueryResult {
255
+ return enqueueConnectionOperation<BatchQueryResult>(connection, [connection, copiedCommands]() -> BatchQueryResult {
170
256
  auto result = sqliteExecuteBatch(connection, copiedCommands);
171
257
  return BatchQueryResult(result.rowsAffected);
172
258
  });
@@ -184,7 +270,7 @@ std::shared_ptr<Promise<FileLoadResult>> HybridNitroSQLite::loadFileAsync(const
184
270
  } catch (...) {
185
271
  return Promise<FileLoadResult>::rejected(std::current_exception());
186
272
  }
187
- return Promise<FileLoadResult>::async([connection, location]() -> FileLoadResult {
273
+ return enqueueConnectionOperation<FileLoadResult>(connection, [connection, location]() -> FileLoadResult {
188
274
  const auto result = importSqlFile(connection, location);
189
275
  return FileLoadResult(result.commands, result.rowsAffected);
190
276
  });
@@ -1,13 +1,13 @@
1
1
  #pragma once
2
2
 
3
+ #include "../NitroSQLiteTypes.hpp"
4
+ #include "HybridNitroSQLitePreparedStatementSpec.hpp"
3
5
  #include "HybridNitroSQLiteQueryResultSpec.hpp"
4
6
  #include "HybridNitroSQLiteSpec.hpp"
5
- #include "types.hpp"
6
-
7
- using namespace margelo::rnnitrosqlite;
8
7
 
9
8
  namespace margelo::nitro::rnnitrosqlite {
10
9
 
10
+ /** Native database operations exposed to JavaScript through the Nitro hybrid object. */
11
11
  class HybridNitroSQLite : public HybridNitroSQLiteSpec {
12
12
  public:
13
13
  HybridNitroSQLite() : HybridObject(TAG) {}
@@ -21,28 +21,72 @@ public:
21
21
 
22
22
  public:
23
23
  // Methods
24
- void open(const std::string& dbName, const std::optional<std::string>& location) override;
24
+ /** Open the default connection, requiring an existing file in read-only mode. */
25
+ void open(const std::string& dbName, const std::optional<std::string>& location, std::optional<bool> readOnly) override;
26
+
27
+ /** Open a separate native handle and return an opaque ID for later calls. */
28
+ std::string openConnection(const std::string& dbName, const std::optional<std::string>& location, std::optional<bool> readOnly) override;
25
29
 
30
+ /** Close the named native database handle. */
26
31
  void close(const std::string& dbName) override;
27
32
 
28
- void drop(const std::string& dbName, const std::optional<std::string>& location) override;
33
+ /** Check whether a connection name or ID still identifies an open handle. */
34
+ bool isConnectionOpen(const std::string& connectionId) override;
29
35
 
36
+ /** Delete a database, closing the specified connection when it is open. */
37
+ void drop(const std::string& dbName, const std::optional<std::string>& location, const std::optional<std::string>& connectionId) override;
38
+
39
+ /** Attach a database file to an open database.
40
+ * @param mainDbName Name of the open main database.
41
+ * @param dbNameToAttach File name of the database to attach.
42
+ * @param alias SQL schema name for the attached database.
43
+ * @param location Optional directory relative to the platform database root.
44
+ */
30
45
  void attach(const std::string& mainDbName, const std::string& dbNameToAttach, const std::string& alias,
31
46
  const std::optional<std::string>& location) override;
32
47
 
48
+ /** Detach the database identified by @p alias from @p mainDbName. */
33
49
  void detach(const std::string& mainDbName, const std::string& alias) override;
34
50
 
51
+ /** Execute one SQL statement on the calling thread.
52
+ * @param dbName Name of an open database.
53
+ * @param query SQL statement with optional positional placeholders.
54
+ * @param params Optional values bound to the placeholders.
55
+ * @return Native rows, affected row count, insert ID, and metadata.
56
+ */
35
57
  std::shared_ptr<HybridNitroSQLiteQueryResultSpec> execute(const std::string& dbName, const std::string& query,
36
58
  const std::optional<SQLiteQueryParams>& params) override;
37
59
 
60
+ /** Execute one SQL statement on a background thread.
61
+ * @return A promise of the native query result.
62
+ */
38
63
  std::shared_ptr<Promise<std::shared_ptr<HybridNitroSQLiteQueryResultSpec>>>
39
64
  executeAsync(const std::string& dbName, const std::string& query, const std::optional<SQLiteQueryParams>& params) override;
40
65
 
66
+ /** Prepare one SQL statement for repeated execution on an open connection. */
67
+ std::shared_ptr<HybridNitroSQLitePreparedStatementSpec> prepare(const std::string& dbName, const std::string& query) override;
68
+
69
+ /** Run a nonempty batch in one exclusive transaction on the calling thread.
70
+ * A failed command rolls back the batch.
71
+ * @return Total affected row count.
72
+ */
41
73
  BatchQueryResult executeBatch(const std::string& dbName, const std::vector<BatchQueryCommand>& commands) override;
74
+ /** Run a nonempty batch in one exclusive transaction on a background thread.
75
+ * @return A promise of the total affected row count.
76
+ */
42
77
  std::shared_ptr<Promise<BatchQueryResult>> executeBatchAsync(const std::string& dbName,
43
78
  const std::vector<BatchQueryCommand>& commands) override;
44
79
 
80
+ /** Import a SQL file in one exclusive transaction on the calling thread.
81
+ * Each nonempty line is interpreted as one statement.
82
+ * @param location Path to the SQL file.
83
+ * @return Number of executed commands and affected rows.
84
+ */
45
85
  FileLoadResult loadFile(const std::string& dbName, const std::string& location) override;
86
+ /** Import a SQL file on a background thread.
87
+ * @param location Path to the SQL file.
88
+ * @return A promise of the command and affected row counts.
89
+ */
46
90
  std::shared_ptr<Promise<FileLoadResult>> loadFileAsync(const std::string& dbName, const std::string& location) override;
47
91
  };
48
92
 
@@ -0,0 +1,59 @@
1
+ #include "HybridNitroSQLitePreparedStatement.hpp"
2
+ #include "HybridNitroSQLiteQueryResult.hpp"
3
+ #include <NitroModules/ArrayBuffer.hpp>
4
+
5
+ namespace margelo::nitro::rnnitrosqlite {
6
+
7
+ namespace {
8
+
9
+ std::optional<SQLiteQueryParams> copyArrayBufferParamsForBackground(const std::optional<SQLiteQueryParams>& params) {
10
+ if (!params) {
11
+ return std::nullopt;
12
+ }
13
+
14
+ SQLiteQueryParams copiedParams;
15
+ copiedParams.reserve(params->size());
16
+
17
+ for (const auto& value : *params) {
18
+ if (std::holds_alternative<std::shared_ptr<ArrayBuffer>>(value)) {
19
+ copiedParams.push_back(ArrayBuffer::copy(std::get<std::shared_ptr<ArrayBuffer>>(value)));
20
+ } else {
21
+ copiedParams.push_back(value);
22
+ }
23
+ }
24
+
25
+ return copiedParams;
26
+ }
27
+
28
+ } // namespace
29
+
30
+ HybridNitroSQLitePreparedStatement::HybridNitroSQLitePreparedStatement(std::shared_ptr<SQLitePreparedStatement> statement)
31
+ : HybridObject(TAG), _statement(std::move(statement)) {}
32
+
33
+ std::shared_ptr<HybridNitroSQLiteQueryResultSpec>
34
+ HybridNitroSQLitePreparedStatement::execute(const std::optional<SQLiteQueryParams>& params) {
35
+ return _statement->execute(params);
36
+ }
37
+
38
+ std::shared_ptr<Promise<std::shared_ptr<HybridNitroSQLiteQueryResultSpec>>>
39
+ HybridNitroSQLitePreparedStatement::executeAsync(const std::optional<SQLiteQueryParams>& params) {
40
+ const auto copiedParams = copyArrayBufferParamsForBackground(params);
41
+ const auto statement = _statement;
42
+
43
+ return Promise<std::shared_ptr<HybridNitroSQLiteQueryResultSpec>>::async(
44
+ [statement, copiedParams]() -> std::shared_ptr<HybridNitroSQLiteQueryResultSpec> { return statement->execute(copiedParams); });
45
+ }
46
+
47
+ void HybridNitroSQLitePreparedStatement::finalize() {
48
+ _statement->finalize();
49
+ }
50
+
51
+ bool HybridNitroSQLitePreparedStatement::getIsFinalized() {
52
+ return _statement->isFinalized();
53
+ }
54
+
55
+ size_t HybridNitroSQLitePreparedStatement::getExternalMemorySize() noexcept {
56
+ return sizeof(*this) + _statement->getExternalMemorySize();
57
+ }
58
+
59
+ } // namespace margelo::nitro::rnnitrosqlite
@@ -0,0 +1,33 @@
1
+ #pragma once
2
+
3
+ #include "../NitroSQLiteOperations.hpp"
4
+ #include "../NitroSQLiteTypes.hpp"
5
+ #include "HybridNitroSQLitePreparedStatementSpec.hpp"
6
+ #include <memory>
7
+
8
+ namespace margelo::nitro::rnnitrosqlite {
9
+
10
+ /** Nitro hybrid object for a reusable native SQLite statement. */
11
+ class HybridNitroSQLitePreparedStatement : public HybridNitroSQLitePreparedStatementSpec {
12
+ public:
13
+ /** Wrap a prepared statement for calls from JavaScript. */
14
+ explicit HybridNitroSQLitePreparedStatement(std::shared_ptr<SQLitePreparedStatement> statement);
15
+
16
+ /** Execute with new bindings on the calling thread. */
17
+ std::shared_ptr<HybridNitroSQLiteQueryResultSpec> execute(const std::optional<SQLiteQueryParams>& params) override;
18
+ /** Execute with new bindings on a background thread. */
19
+ std::shared_ptr<Promise<std::shared_ptr<HybridNitroSQLiteQueryResultSpec>>>
20
+ executeAsync(const std::optional<SQLiteQueryParams>& params) override;
21
+ /** Release the native statement. */
22
+ void finalize() override;
23
+ /** Check whether the native statement has been released. */
24
+ bool getIsFinalized() override;
25
+
26
+ /** Report native memory used by this hybrid object. */
27
+ size_t getExternalMemorySize() noexcept override;
28
+
29
+ private:
30
+ std::shared_ptr<SQLitePreparedStatement> _statement;
31
+ };
32
+
33
+ } // namespace margelo::nitro::rnnitrosqlite
@@ -1,13 +1,12 @@
1
1
  #pragma once
2
2
 
3
+ #include "../NitroSQLiteTypes.hpp"
3
4
  #include "HybridNitroSQLiteQueryResultSpec.hpp"
4
- #include "types.hpp"
5
5
  #include <map>
6
6
 
7
- using namespace margelo::rnnitrosqlite;
8
-
9
7
  namespace margelo::nitro::rnnitrosqlite {
10
8
 
9
+ /** Native result object returned by a single SQL execution. */
11
10
  class HybridNitroSQLiteQueryResult : public HybridNitroSQLiteQueryResultSpec {
12
11
  public:
13
12
  HybridNitroSQLiteQueryResult() : HybridObject(TAG) {}
@@ -22,10 +21,13 @@ private:
22
21
  std::optional<SQLiteQueryTableMetadata> _metadata;
23
22
 
24
23
  public:
25
- // Properties
24
+ /** Return the connection's last insert row ID, which may be from an earlier statement. */
26
25
  std::optional<double> getInsertId() override;
26
+ /** Return SQLite's latest row change count, which may be stale for a read-only query. */
27
27
  double getRowsAffected() override;
28
+ /** Return result rows keyed by column name. */
28
29
  SQLiteQueryResults getResults() override;
30
+ /** Return column metadata when available. */
29
31
  std::optional<SQLiteQueryTableMetadata> getMetadata() override;
30
32
 
31
33
  /**
package/ios/OnLoad.mm CHANGED
@@ -1,7 +1,39 @@
1
1
  #import <Foundation/Foundation.h>
2
+ #import <TargetConditionals.h>
2
3
  #import "RNNitroSQLite-Swift-Cxx-Umbrella.hpp"
3
4
  #import "HybridNitroSQLite.hpp"
4
5
 
6
+ #if TARGET_OS_OSX
7
+ static NSString *macOSDatabaseDirectory(void) {
8
+ NSFileManager *fileManager = [NSFileManager defaultManager];
9
+ NSError *error = nil;
10
+ NSURL *applicationSupportURL = [fileManager URLForDirectory:NSApplicationSupportDirectory
11
+ inDomain:NSUserDomainMask
12
+ appropriateForURL:nil
13
+ create:YES
14
+ error:&error];
15
+ if (applicationSupportURL == nil) {
16
+ @throw [NSException exceptionWithName:@"SQLiteInitializationException"
17
+ reason:[NSString stringWithFormat:@"Could not find the Application Support directory: %@", error]
18
+ userInfo:nil];
19
+ }
20
+
21
+ NSString *bundleIdentifier = [[NSBundle mainBundle] bundleIdentifier];
22
+ if (bundleIdentifier == nil) {
23
+ bundleIdentifier = [[NSProcessInfo processInfo] processName];
24
+ }
25
+
26
+ NSURL *databaseURL = [applicationSupportURL URLByAppendingPathComponent:bundleIdentifier isDirectory:YES];
27
+ if (![fileManager createDirectoryAtURL:databaseURL withIntermediateDirectories:YES attributes:nil error:&error]) {
28
+ @throw [NSException exceptionWithName:@"SQLiteInitializationException"
29
+ reason:[NSString stringWithFormat:@"Could not create the database directory: %@", error]
30
+ userInfo:nil];
31
+ }
32
+
33
+ return [databaseURL path];
34
+ }
35
+ #endif
36
+
5
37
  @interface OnLoad : NSObject
6
38
  @end
7
39
 
@@ -11,7 +43,7 @@ using namespace margelo::nitro;
11
43
  using namespace margelo::nitro::rnnitrosqlite;
12
44
 
13
45
  + (void)load {
14
- // Get appGroupID value from Info.plist using key "AppGroup"
46
+ // Get appGroupID value from Info.plist using key "RNNitroSQLite_AppGroup".
15
47
  NSString *appGroupID = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"RNNitroSQLite_AppGroup"];
16
48
  NSString *documentPath;
17
49
 
@@ -21,7 +53,7 @@ using namespace margelo::nitro::rnnitrosqlite;
21
53
  NSURL *storeUrl = [fileManager containerURLForSecurityApplicationGroupIdentifier:appGroupID];
22
54
 
23
55
  if (storeUrl == nil) {
24
- NSLog(@"Invalid AppGroup ID provided (%@). Check the value of \"AppGroup\" in your Info.plist file", appGroupID);
56
+ NSLog(@"Invalid App Group ID provided (%@). Check the value of \"RNNitroSQLite_AppGroup\" in your Info.plist file", appGroupID);
25
57
  @throw [NSException exceptionWithName:@"SQLiteInitializationException"
26
58
  reason:@"Error while initializing SQLite database (AppGroup)"
27
59
  userInfo:nil];
@@ -30,6 +62,9 @@ using namespace margelo::nitro::rnnitrosqlite;
30
62
 
31
63
  documentPath = [storeUrl path];
32
64
  } else {
65
+ #if TARGET_OS_OSX
66
+ documentPath = macOSDatabaseDirectory();
67
+ #else
33
68
  NSString *databaseLocation = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"RNNitroSQLite_DatabaseLocation"];
34
69
 
35
70
  if ([databaseLocation isEqualToString:@"ApplicationSupport"]) {
@@ -56,6 +91,7 @@ using namespace margelo::nitro::rnnitrosqlite;
56
91
  NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, true);
57
92
  documentPath = [paths objectAtIndex:0];
58
93
  }
94
+ #endif
59
95
  }
60
96
 
61
97
  HybridNitroSQLite::docPath = [documentPath UTF8String];
@@ -8,24 +8,31 @@ exports.getDatabaseQueue = getDatabaseQueue;
8
8
  exports.isDatabaseOpen = isDatabaseOpen;
9
9
  exports.openDatabaseQueue = openDatabaseQueue;
10
10
  exports.queueOperationAsync = queueOperationAsync;
11
+ exports.queueStatementAsync = queueStatementAsync;
11
12
  exports.startOperationSync = startOperationSync;
12
13
  exports.throwIfDatabaseIsNotOpen = throwIfDatabaseIsNotOpen;
13
14
  var _NitroSQLiteError = _interopRequireDefault(require("./NitroSQLiteError.js"));
14
15
  function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
16
+ /** Identity of a managed connection's operation queue. Application code should
17
+ * omit the optional queue key on package helpers; connections choose it for you.
18
+ */
19
+
15
20
  const databaseQueues = new Map();
16
21
  function openDatabaseQueue(dbName) {
17
22
  if (isDatabaseOpen(dbName)) {
18
- throw new _NitroSQLiteError.default(`Database ${dbName} is already open. There is already a connection to the database.`);
23
+ throw new _NitroSQLiteError.default(`Database ${String(dbName)} is already open. There is already a connection to the database.`);
19
24
  }
20
25
  databaseQueues.set(dbName, {
21
26
  queue: [],
22
- inProgress: false
27
+ inProgress: false,
28
+ activeStatements: 0,
29
+ draining: false
23
30
  });
24
31
  }
25
32
  function closeDatabaseQueue(dbName) {
26
33
  const databaseQueue = getDatabaseQueue(dbName);
27
34
  if (databaseQueue.inProgress || databaseQueue.queue.length > 0) {
28
- throw new _NitroSQLiteError.default(`Cannot close database ${dbName}. The database is busy with another operation.`);
35
+ throw new _NitroSQLiteError.default(`Cannot close database ${String(dbName)}. The database is busy with another operation.`);
29
36
  }
30
37
  databaseQueues.delete(dbName);
31
38
  }
@@ -33,7 +40,7 @@ function isDatabaseOpen(dbName) {
33
40
  return databaseQueues.has(dbName);
34
41
  }
35
42
  function throwIfDatabaseIsNotOpen(dbName) {
36
- if (!isDatabaseOpen(dbName)) throw new _NitroSQLiteError.default(`Database ${dbName} is not open. There is no connection to the database.`);
43
+ if (!isDatabaseOpen(dbName)) throw new _NitroSQLiteError.default(`Database ${String(dbName)} is not open. There is no connection to the database.`);
37
44
  }
38
45
  function getDatabaseQueue(dbName) {
39
46
  throwIfDatabaseIsNotOpen(dbName);
@@ -41,6 +48,12 @@ function getDatabaseQueue(dbName) {
41
48
  return queue;
42
49
  }
43
50
  function queueOperationAsync(dbName, callback) {
51
+ return enqueueOperation(dbName, 'exclusive', callback);
52
+ }
53
+ function queueStatementAsync(dbName, callback) {
54
+ return enqueueOperation(dbName, 'statement', callback);
55
+ }
56
+ function enqueueOperation(dbName, kind, callback) {
44
57
  const databaseQueue = getDatabaseQueue(dbName);
45
58
  return new Promise((resolve, reject) => {
46
59
  async function start() {
@@ -50,34 +63,56 @@ function queueOperationAsync(dbName, callback) {
50
63
  } catch (error) {
51
64
  reject(error);
52
65
  } finally {
53
- databaseQueue.inProgress = false;
54
- startOperationAsync(databaseQueue);
66
+ if (kind === 'statement') {
67
+ databaseQueue.activeStatements--;
68
+ if (databaseQueue.activeStatements === 0) {
69
+ databaseQueue.inProgress = false;
70
+ }
71
+ } else {
72
+ databaseQueue.inProgress = false;
73
+ }
74
+ startNextOperations(databaseQueue);
55
75
  }
56
76
  }
57
77
  const operation = {
78
+ kind,
58
79
  start
59
80
  };
60
81
  databaseQueue.queue.push(operation);
61
- startOperationAsync(databaseQueue);
82
+ startNextOperations(databaseQueue);
62
83
  });
63
84
  }
64
- function startOperationAsync(queue) {
65
- // Queue is empty or in progress. Bail out.
66
- if (queue.inProgress || queue.queue.length === 0) {
85
+ function startNextOperations(queue) {
86
+ if (queue.draining || queue.inProgress && queue.activeStatements === 0) {
67
87
  return;
68
88
  }
69
- queue.inProgress = true;
70
- const operation = queue.queue.shift();
71
- setImmediate(() => {
72
- operation.start();
73
- });
89
+ queue.draining = true;
90
+ try {
91
+ while (queue.queue.length > 0) {
92
+ const exclusiveIndex = queue.queue.findIndex(operation => operation.kind === 'exclusive');
93
+ const statementCount = exclusiveIndex === -1 ? queue.queue.length : exclusiveIndex;
94
+ if (statementCount > 0) {
95
+ const statements = queue.queue.splice(0, statementCount);
96
+ queue.inProgress = true;
97
+ queue.activeStatements += statements.length;
98
+ for (const statement of statements) statement.start();
99
+ continue;
100
+ }
101
+ if (queue.activeStatements > 0) return;
102
+ queue.inProgress = true;
103
+ queue.queue.shift().start();
104
+ return;
105
+ }
106
+ } finally {
107
+ queue.draining = false;
108
+ }
74
109
  }
75
110
  function startOperationSync(dbName, callback) {
76
111
  const databaseQueue = getDatabaseQueue(dbName);
77
112
 
78
113
  // Database is busy - cannot execute synchronously
79
114
  if (databaseQueue.inProgress || databaseQueue.queue.length > 0) {
80
- throw new _NitroSQLiteError.default(`Cannot run synchronous operation on database. Database ${dbName} is busy with another operation.`);
115
+ throw new _NitroSQLiteError.default(`Cannot run synchronous operation on database. Database ${String(dbName)} is busy with another operation.`);
81
116
  }
82
117
 
83
118
  // Execute synchronously
@@ -86,6 +121,7 @@ function startOperationSync(dbName, callback) {
86
121
  return callback();
87
122
  } finally {
88
123
  databaseQueue.inProgress = false;
124
+ startNextOperations(databaseQueue);
89
125
  }
90
126
  }
91
127
  //# sourceMappingURL=DatabaseQueue.js.map