Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions packages/react-native-nitro-sqlite/Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,9 @@ let sqlite = SQLiteBuildOptions.resolve()

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

Expand Down
Original file line number Diff line number Diff line change
@@ -1,45 +1,7 @@
#include "HybridNitroSQLite.hpp"
#include <RNNitroSQLite/private/RNNitroSQLiteOnLoad.hpp>
#include <cstdlib>
#include <fbjni/fbjni.h>
#include <jni.h>
#include <jsi/jsi.h>
#include <mutex>
#include <typeinfo>

using namespace margelo::nitro::rnnitrosqlite;

JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm, void*) {
return facebook::jni::initialize(vm, [] { margelo::nitro::rnnitrosqlite::registerAllNatives(); });
}

extern "C" JNIEXPORT void JNICALL Java_com_margelo_rnnitrosqlite_DocPathSetter_setDocPathInJNI(JNIEnv* env, jclass clazz, jstring doc_path,
jstring cache_path) {
const char* docPath = env->GetStringUTFChars(doc_path, nullptr);
if (docPath == nullptr) {
return;
}

const char* cachePath = env->GetStringUTFChars(cache_path, nullptr);
if (cachePath == nullptr) {
env->ReleaseStringUTFChars(doc_path, docPath);
return;
}

// SQLite caches this environment value during VFS initialization. Do not replace it on JS reload.
static std::once_flag tempDirectoryOnce;
static bool tempDirectoryConfigured = false;
std::call_once(tempDirectoryOnce, [&] { tempDirectoryConfigured = setenv("SQLITE_TMPDIR", cachePath, 1) == 0; });

if (tempDirectoryConfigured) {
HybridNitroSQLite::docPath = std::string(docPath);
} else {
jclass exception = env->FindClass("java/lang/IllegalStateException");
if (exception != nullptr) {
env->ThrowNew(exception, "Could not configure SQLite temporary directory");
}
}

env->ReleaseStringUTFChars(cache_path, cachePath);
env->ReleaseStringUTFChars(doc_path, docPath);
}

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package com.margelo.nitro.rnnitrosqlite

import androidx.annotation.Keep
import com.facebook.proguard.annotations.DoNotStrip
import com.margelo.nitro.NitroModules

/** Android directories for NitroSQLite, read from the application context. */
@Keep
@DoNotStrip
class HybridPlatformContext : HybridPlatformContextSpec() {
companion object : HybridPlatformContextSpec.Companion {
override val databaseDirectory: String
get() = context.filesDir.absolutePath

override val temporaryDirectory: String?
get() = context.cacheDir.absolutePath

override val legacyDatabaseDirectory: String?
get() = null

private val context
get() = NitroModules.applicationContext
?: throw IllegalStateException("NitroSQLite needs the React Native application context to find its database directory")
}
}

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -7,18 +7,39 @@
#include "../NitroSQLiteMacros.hpp"
#include "../NitroSQLiteOperations.hpp"
#include "HybridPreparedStatement.hpp"
#include <cstdlib>
#include <exception>
#include <filesystem>
#include <iostream>
#include <map>
#include <mutex>
#include <optional>
#include <stdexcept>
#include <string>
#include <utility>
#include <variant>
#include <vector>

namespace margelo::nitro::rnnitrosqlite {

// SQLite caches SQLITE_TMPDIR when it initializes its VFS, so only the first root sets it.
static void configureTemporaryDirectory(const std::string& temporaryDirectory) {
static std::once_flag configureOnce;
static bool configured = false;
std::call_once(configureOnce, [&] { configured = setenv("SQLITE_TMPDIR", temporaryDirectory.c_str(), 1) == 0; });
if (!configured) {
throw std::runtime_error("Could not configure SQLite temporary directory");
}
}

HybridNitroSQLite::HybridNitroSQLite(std::string databaseDirectory, const std::optional<std::string>& temporaryDirectory,
const std::optional<std::string>& legacyDatabaseDirectory)
: _databaseDirectory(std::move(databaseDirectory)), _legacyDatabaseDirectory(legacyDatabaseDirectory.value_or("")) {
if (temporaryDirectory) {
configureTemporaryDirectory(*temporaryDirectory);
}
}

// Copy any JS-backed ArrayBuffers on the JS thread so they can be safely
// accessed from the connection's background worker.
static std::optional<SQLiteQueryParams> copyArrayBufferParamsForBackground(const std::optional<SQLiteQueryParams>& params) {
Expand Down Expand Up @@ -90,34 +111,35 @@ static Promise<Result> enqueueConnectionOperation(const SQLiteConnectionPtr& con
return promise;
}

const std::string getDocPath(const std::optional<std::string>& location) {
const std::string getDocPath(const std::string& databaseDirectory, const std::optional<std::string>& location) {
if (location && location->find('\0') != std::string::npos) {
throw NitroSQLiteException(NitroSQLiteExceptionType::DatabaseCannotBeOpened, "Database location contains a NUL byte");
}
std::string tempDocPath = std::string(HybridNitroSQLite::docPath);
std::string tempDocPath = databaseDirectory;
if (location) {
tempDocPath = tempDocPath + "/" + *location;
}

return tempDocPath;
}

const std::string getOldDocPath(const std::optional<std::string>& location) {
std::string oldDocPath = HybridNitroSQLite::migrationDocPath;
const std::string getOldDocPath(const std::string& legacyDatabaseDirectory, const std::optional<std::string>& location) {
std::string oldDocPath = legacyDatabaseDirectory;
if (location) {
oldDocPath = oldDocPath + "/" + *location;
}

return oldDocPath;
}

const std::string getMigratedDocPath(DatabaseConnections& connections, const std::string& dbName,
const std::string getMigratedDocPath(DatabaseConnections& connections, const std::string& databaseDirectory,
const std::string& legacyDatabaseDirectory, const std::string& dbName,
const std::optional<std::string>& location, bool readOnly = false) {
const auto currentDocPath = getDocPath(location);
if (HybridNitroSQLite::migrationDocPath.empty()) {
const auto currentDocPath = getDocPath(databaseDirectory, location);
if (legacyDatabaseDirectory.empty()) {
return currentDocPath;
}
const auto oldDocPath = getOldDocPath(location);
const auto oldDocPath = getOldDocPath(legacyDatabaseDirectory, location);
std::string selectedPath;
connections.withConnectionsLocked([&]() {
const auto oldPath = std::filesystem::path(oldDocPath) / dbName;
Expand All @@ -140,7 +162,8 @@ void HybridNitroSQLite::open(const std::string& dbName, const std::optional<std:
if (_connections.isOpen(dbName)) {
throw NitroSQLiteException::DatabaseAlreadyOpen(dbName);
}
const auto docPath = getMigratedDocPath(_connections, dbName, location, readOnly.value_or(false));
const auto docPath =
getMigratedDocPath(_connections, _databaseDirectory, _legacyDatabaseDirectory, dbName, location, readOnly.value_or(false));
sqliteOpenDb(_connections, dbName, docPath, readOnly.value_or(false), encryptionKey);
}

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

Expand All @@ -168,13 +192,13 @@ void HybridNitroSQLite::drop(const std::string& dbName, const std::optional<std:
const std::optional<std::string>& connectionId) {
validateDatabaseName(dbName);
std::lock_guard lock(_connections.lifecycleMutex);
const auto currentDocPath = getDocPath(location);
if (migrationDocPath.empty()) {
const auto currentDocPath = getDocPath(_databaseDirectory, location);
if (_legacyDatabaseDirectory.empty()) {
sqliteRemoveDb(_connections, dbName, currentDocPath, connectionId);
return;
}

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

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,17 +14,22 @@ namespace margelo::nitro::rnnitrosqlite {
*/
class HybridNitroSQLite : public HybridNitroSQLiteSpec {
public:
HybridNitroSQLite() = default;

public:
static std::string docPath;
// Directory databases were stored in by previous app versions, when the platform layer has
// relocated docPath (e.g. iOS with RNNitroSQLite_DatabaseLocation set to "ApplicationSupport").
// When non-empty, databases found there are resolved as they are opened, attached, or dropped.
static std::string migrationDocPath;
/** Create a root that stores its databases in @p databaseDirectory.
* @param temporaryDirectory SQLite's temporary directory. SQLite reads it once per process,
* so the first root that passes one sets it.
* @param legacyDatabaseDirectory Directory that previous app versions stored databases in,
* for example Documents when iOS moves them to Application Support. Databases found there
* are moved as they are opened, attached, or dropped.
* @throws std::runtime_error If SQLite's temporary directory can't be configured.
*/
HybridNitroSQLite(std::string databaseDirectory, const std::optional<std::string>& temporaryDirectory,
const std::optional<std::string>& legacyDatabaseDirectory);

private:
DatabaseConnections _connections;
std::string _databaseDirectory;
// Empty when databases have never been stored anywhere else.
std::string _legacyDatabaseDirectory;

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

inline std::string HybridNitroSQLite::docPath = "";
inline std::string HybridNitroSQLite::migrationDocPath = "";

} // namespace margelo::nitro::rnnitrosqlite
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
#pragma once

#include <optional>
#include <string>

namespace margelo::nitro::rnnitrosqlite {

/** Apple platform directories for NitroSQLite, implemented in ios/HybridPlatformContext.mm.
* PlatformContext only has static members, so this class doesn't derive from its generated spec.
* Android implements PlatformContext in Kotlin instead.
*/
class HybridPlatformContext {
public:
/** Database directory selected by the RNNitroSQLite_AppGroup and RNNitroSQLite_DatabaseLocation Info.plist keys.
* @throws std::runtime_error If the app group container doesn't exist or the directory can't be created.
*/
static std::string getDatabaseDirectory();

/** Apple platforms keep SQLite's default temporary directory, so this is always empty. */
static std::optional<std::string> getTemporaryDirectory();

/** The Documents directory when RNNitroSQLite_DatabaseLocation moves databases to Application Support. */
static std::optional<std::string> getLegacyDatabaseDirectory();
};

} // namespace margelo::nitro::rnnitrosqlite
Loading
Loading