Skip to content

Commit a74e32f

Browse files
chrispaderclaude
andcommitted
refactor: replace NitroSQLiteOnLoad with a platform context
A PlatformContext hybrid object now returns the database, temporary and legacy database directories. Kotlin reads them from the Android application context, and Objective-C++ applies the iOS Info.plist options that OnLoad.mm handled at launch. JavaScript passes them to the NitroSQLite constructor, so each root resolves databases against its own directories instead of static paths, and the Android JNI setter and the iOS +load hook go away. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
1 parent 9f1ee6e commit a74e32f

34 files changed

Lines changed: 565 additions & 352 deletions

‎packages/react-native-nitro-sqlite/Package.swift‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,9 @@ let sqlite = SQLiteBuildOptions.resolve()
1414

1515
addNitrogenTargets(
1616
to: package,
17-
// SwiftPM cannot build Objective-C++ in a Swift target, so OnLoad.mm builds with the C++ sources.
17+
// SwiftPM cannot build Objective-C++ in a Swift target, so HybridPlatformContext.mm builds with the C++ sources.
1818
swiftSources: [],
19-
cxxSources: ["cpp", "ios/OnLoad.mm"],
19+
cxxSources: ["cpp", "ios/HybridPlatformContext.mm"],
2020
cxxSettings: sqlite.defines.map { .define($0.name, to: $0.value) }
2121
)
2222

Lines changed: 0 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -1,45 +1,7 @@
1-
#include "HybridNitroSQLite.hpp"
21
#include <RNNitroSQLite/private/RNNitroSQLiteOnLoad.hpp>
3-
#include <cstdlib>
42
#include <fbjni/fbjni.h>
53
#include <jni.h>
6-
#include <jsi/jsi.h>
7-
#include <mutex>
8-
#include <typeinfo>
9-
10-
using namespace margelo::nitro::rnnitrosqlite;
114

125
JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm, void*) {
136
return facebook::jni::initialize(vm, [] { margelo::nitro::rnnitrosqlite::registerAllNatives(); });
147
}
15-
16-
extern "C" JNIEXPORT void JNICALL Java_com_margelo_rnnitrosqlite_DocPathSetter_setDocPathInJNI(JNIEnv* env, jclass clazz, jstring doc_path,
17-
jstring cache_path) {
18-
const char* docPath = env->GetStringUTFChars(doc_path, nullptr);
19-
if (docPath == nullptr) {
20-
return;
21-
}
22-
23-
const char* cachePath = env->GetStringUTFChars(cache_path, nullptr);
24-
if (cachePath == nullptr) {
25-
env->ReleaseStringUTFChars(doc_path, docPath);
26-
return;
27-
}
28-
29-
// SQLite caches this environment value during VFS initialization. Do not replace it on JS reload.
30-
static std::once_flag tempDirectoryOnce;
31-
static bool tempDirectoryConfigured = false;
32-
std::call_once(tempDirectoryOnce, [&] { tempDirectoryConfigured = setenv("SQLITE_TMPDIR", cachePath, 1) == 0; });
33-
34-
if (tempDirectoryConfigured) {
35-
HybridNitroSQLite::docPath = std::string(docPath);
36-
} else {
37-
jclass exception = env->FindClass("java/lang/IllegalStateException");
38-
if (exception != nullptr) {
39-
env->ThrowNew(exception, "Could not configure SQLite temporary directory");
40-
}
41-
}
42-
43-
env->ReleaseStringUTFChars(cache_path, cachePath);
44-
env->ReleaseStringUTFChars(doc_path, docPath);
45-
}

‎packages/react-native-nitro-sqlite/android/src/main/kotlin/com/margelo/nitro/rnnitrosqlite/HybridNitroSQLiteOnLoad.kt‎

Lines changed: 0 additions & 20 deletions
This file was deleted.
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
package com.margelo.nitro.rnnitrosqlite
2+
3+
import androidx.annotation.Keep
4+
import com.facebook.proguard.annotations.DoNotStrip
5+
import com.margelo.nitro.NitroModules
6+
7+
/** Android directories for NitroSQLite, read from the application context. */
8+
@Keep
9+
@DoNotStrip
10+
class HybridPlatformContext : HybridPlatformContextSpec() {
11+
companion object : HybridPlatformContextSpec.Companion {
12+
override val databaseDirectory: String
13+
get() = context.filesDir.absolutePath
14+
15+
override val temporaryDirectory: String?
16+
get() = context.cacheDir.absolutePath
17+
18+
override val legacyDatabaseDirectory: String?
19+
get() = null
20+
21+
private val context
22+
get() = NitroModules.applicationContext
23+
?: throw IllegalStateException("NitroSQLite needs the React Native application context to find its database directory")
24+
}
25+
}

‎packages/react-native-nitro-sqlite/android/src/main/kotlin/com/margelo/rnnitrosqlite/DocPathSetter.kt‎

Lines changed: 0 additions & 14 deletions
This file was deleted.

‎packages/react-native-nitro-sqlite/cpp/hybridObjects/HybridNitroSQLite.cpp‎

Lines changed: 38 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -7,18 +7,39 @@
77
#include "../NitroSQLiteMacros.hpp"
88
#include "../NitroSQLiteOperations.hpp"
99
#include "HybridPreparedStatement.hpp"
10+
#include <cstdlib>
1011
#include <exception>
1112
#include <filesystem>
1213
#include <iostream>
1314
#include <map>
15+
#include <mutex>
1416
#include <optional>
17+
#include <stdexcept>
1518
#include <string>
1619
#include <utility>
1720
#include <variant>
1821
#include <vector>
1922

2023
namespace margelo::nitro::rnnitrosqlite {
2124

25+
// SQLite caches SQLITE_TMPDIR when it initializes its VFS, so only the first root sets it.
26+
static void configureTemporaryDirectory(const std::string& temporaryDirectory) {
27+
static std::once_flag configureOnce;
28+
static bool configured = false;
29+
std::call_once(configureOnce, [&] { configured = setenv("SQLITE_TMPDIR", temporaryDirectory.c_str(), 1) == 0; });
30+
if (!configured) {
31+
throw std::runtime_error("Could not configure SQLite temporary directory");
32+
}
33+
}
34+
35+
HybridNitroSQLite::HybridNitroSQLite(std::string databaseDirectory, const std::optional<std::string>& temporaryDirectory,
36+
const std::optional<std::string>& legacyDatabaseDirectory)
37+
: _databaseDirectory(std::move(databaseDirectory)), _legacyDatabaseDirectory(legacyDatabaseDirectory.value_or("")) {
38+
if (temporaryDirectory) {
39+
configureTemporaryDirectory(*temporaryDirectory);
40+
}
41+
}
42+
2243
// Copy any JS-backed ArrayBuffers on the JS thread so they can be safely
2344
// accessed from the connection's background worker.
2445
static std::optional<SQLiteQueryParams> copyArrayBufferParamsForBackground(const std::optional<SQLiteQueryParams>& params) {
@@ -90,34 +111,35 @@ static Promise<Result> enqueueConnectionOperation(const SQLiteConnectionPtr& con
90111
return promise;
91112
}
92113

93-
const std::string getDocPath(const std::optional<std::string>& location) {
114+
const std::string getDocPath(const std::string& databaseDirectory, const std::optional<std::string>& location) {
94115
if (location && location->find('\0') != std::string::npos) {
95116
throw NitroSQLiteException(NitroSQLiteExceptionType::DatabaseCannotBeOpened, "Database location contains a NUL byte");
96117
}
97-
std::string tempDocPath = std::string(HybridNitroSQLite::docPath);
118+
std::string tempDocPath = databaseDirectory;
98119
if (location) {
99120
tempDocPath = tempDocPath + "/" + *location;
100121
}
101122

102123
return tempDocPath;
103124
}
104125

105-
const std::string getOldDocPath(const std::optional<std::string>& location) {
106-
std::string oldDocPath = HybridNitroSQLite::migrationDocPath;
126+
const std::string getOldDocPath(const std::string& legacyDatabaseDirectory, const std::optional<std::string>& location) {
127+
std::string oldDocPath = legacyDatabaseDirectory;
107128
if (location) {
108129
oldDocPath = oldDocPath + "/" + *location;
109130
}
110131

111132
return oldDocPath;
112133
}
113134

114-
const std::string getMigratedDocPath(DatabaseConnections& connections, const std::string& dbName,
135+
const std::string getMigratedDocPath(DatabaseConnections& connections, const std::string& databaseDirectory,
136+
const std::string& legacyDatabaseDirectory, const std::string& dbName,
115137
const std::optional<std::string>& location, bool readOnly = false) {
116-
const auto currentDocPath = getDocPath(location);
117-
if (HybridNitroSQLite::migrationDocPath.empty()) {
138+
const auto currentDocPath = getDocPath(databaseDirectory, location);
139+
if (legacyDatabaseDirectory.empty()) {
118140
return currentDocPath;
119141
}
120-
const auto oldDocPath = getOldDocPath(location);
142+
const auto oldDocPath = getOldDocPath(legacyDatabaseDirectory, location);
121143
std::string selectedPath;
122144
connections.withConnectionsLocked([&]() {
123145
const auto oldPath = std::filesystem::path(oldDocPath) / dbName;
@@ -140,7 +162,8 @@ void HybridNitroSQLite::open(const std::string& dbName, const std::optional<std:
140162
if (_connections.isOpen(dbName)) {
141163
throw NitroSQLiteException::DatabaseAlreadyOpen(dbName);
142164
}
143-
const auto docPath = getMigratedDocPath(_connections, dbName, location, readOnly.value_or(false));
165+
const auto docPath =
166+
getMigratedDocPath(_connections, _databaseDirectory, _legacyDatabaseDirectory, dbName, location, readOnly.value_or(false));
144167
sqliteOpenDb(_connections, dbName, docPath, readOnly.value_or(false), encryptionKey);
145168
}
146169

@@ -152,7 +175,8 @@ std::string HybridNitroSQLite::openConnection(const std::string& dbName, const s
152175
"Independent connections require a thread-safe SQLite build");
153176
}
154177
std::lock_guard lock(_connections.lifecycleMutex);
155-
const auto docPath = getMigratedDocPath(_connections, dbName, location, readOnly.value_or(false));
178+
const auto docPath =
179+
getMigratedDocPath(_connections, _databaseDirectory, _legacyDatabaseDirectory, dbName, location, readOnly.value_or(false));
156180
return sqliteOpenConnection(_connections, dbName, docPath, readOnly.value_or(false), encryptionKey);
157181
}
158182

@@ -168,13 +192,13 @@ void HybridNitroSQLite::drop(const std::string& dbName, const std::optional<std:
168192
const std::optional<std::string>& connectionId) {
169193
validateDatabaseName(dbName);
170194
std::lock_guard lock(_connections.lifecycleMutex);
171-
const auto currentDocPath = getDocPath(location);
172-
if (migrationDocPath.empty()) {
195+
const auto currentDocPath = getDocPath(_databaseDirectory, location);
196+
if (_legacyDatabaseDirectory.empty()) {
173197
sqliteRemoveDb(_connections, dbName, currentDocPath, connectionId);
174198
return;
175199
}
176200

177-
const auto oldDocPath = getOldDocPath(location);
201+
const auto oldDocPath = getOldDocPath(_legacyDatabaseDirectory, location);
178202
const auto preferredPath = _connections.physicalPathForKey(connectionId.value_or(dbName));
179203
const auto currentPath = std::filesystem::path(currentDocPath) / dbName;
180204
std::error_code ec;
@@ -199,7 +223,7 @@ void HybridNitroSQLite::attach(const std::string& mainDbName, const std::string&
199223
if (_connections.get(mainDbName)->readOnly) {
200224
throw NitroSQLiteException(NitroSQLiteExceptionType::UnableToAttachToDatabase, "Cannot attach a database to a read-only connection");
201225
}
202-
const auto attachedDocPath = getMigratedDocPath(_connections, dbNameToAttach, location);
226+
const auto attachedDocPath = getMigratedDocPath(_connections, _databaseDirectory, _legacyDatabaseDirectory, dbNameToAttach, location);
203227
sqliteAttachDb(_connections.get(mainDbName), attachedDocPath, dbNameToAttach, alias);
204228
};
205229

‎packages/react-native-nitro-sqlite/cpp/hybridObjects/HybridNitroSQLite.hpp‎

Lines changed: 13 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -14,17 +14,22 @@ namespace margelo::nitro::rnnitrosqlite {
1414
*/
1515
class HybridNitroSQLite : public HybridNitroSQLiteSpec {
1616
public:
17-
HybridNitroSQLite() = default;
18-
19-
public:
20-
static std::string docPath;
21-
// Directory databases were stored in by previous app versions, when the platform layer has
22-
// relocated docPath (e.g. iOS with RNNitroSQLite_DatabaseLocation set to "ApplicationSupport").
23-
// When non-empty, databases found there are resolved as they are opened, attached, or dropped.
24-
static std::string migrationDocPath;
17+
/** Create a root that stores its databases in @p databaseDirectory.
18+
* @param temporaryDirectory SQLite's temporary directory. SQLite reads it once per process,
19+
* so the first root that passes one sets it.
20+
* @param legacyDatabaseDirectory Directory that previous app versions stored databases in,
21+
* for example Documents when iOS moves them to Application Support. Databases found there
22+
* are moved as they are opened, attached, or dropped.
23+
* @throws std::runtime_error If SQLite's temporary directory can't be configured.
24+
*/
25+
HybridNitroSQLite(std::string databaseDirectory, const std::optional<std::string>& temporaryDirectory,
26+
const std::optional<std::string>& legacyDatabaseDirectory);
2527

2628
private:
2729
DatabaseConnections _connections;
30+
std::string _databaseDirectory;
31+
// Empty when databases have never been stored anywhere else.
32+
std::string _legacyDatabaseDirectory;
2833

2934
public:
3035
// Methods
@@ -99,7 +104,4 @@ class HybridNitroSQLite : public HybridNitroSQLiteSpec {
99104
Promise<FileLoadResult> loadFileAsync(const std::string& dbName, const std::string& location) override;
100105
};
101106

102-
inline std::string HybridNitroSQLite::docPath = "";
103-
inline std::string HybridNitroSQLite::migrationDocPath = "";
104-
105107
} // namespace margelo::nitro::rnnitrosqlite
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
#pragma once
2+
3+
#include <optional>
4+
#include <string>
5+
6+
namespace margelo::nitro::rnnitrosqlite {
7+
8+
/** Apple platform directories for NitroSQLite, implemented in ios/HybridPlatformContext.mm.
9+
* PlatformContext only has static members, so this class doesn't derive from its generated spec.
10+
* Android implements PlatformContext in Kotlin instead.
11+
*/
12+
class HybridPlatformContext {
13+
public:
14+
/** Database directory selected by the RNNitroSQLite_AppGroup and RNNitroSQLite_DatabaseLocation Info.plist keys.
15+
* @throws std::runtime_error If the app group container doesn't exist or the directory can't be created.
16+
*/
17+
static std::string getDatabaseDirectory();
18+
19+
/** Apple platforms keep SQLite's default temporary directory, so this is always empty. */
20+
static std::optional<std::string> getTemporaryDirectory();
21+
22+
/** The Documents directory when RNNitroSQLite_DatabaseLocation moves databases to Application Support. */
23+
static std::optional<std::string> getLegacyDatabaseDirectory();
24+
};
25+
26+
} // namespace margelo::nitro::rnnitrosqlite

0 commit comments

Comments
 (0)