Skip to content
Merged
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: 4 additions & 0 deletions android/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,10 @@ if(useLibsql && useTurso) {
throw new GradleException("[OP-SQLITE] Error: libsql and turso backends are mutually exclusive.")
}

if(useLibsql && useCRSQLite) {
throw new GradleException("[OP-SQLITE] Error: You cannot use crsqlite with libsql.")
}

if(useSQLCipher) {
println "[OP-SQLITE] using sqlcipher."
} else if(useTurso) {
Expand Down
11 changes: 4 additions & 7 deletions cpp/DBHostObject.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -217,23 +217,20 @@ DBHostObject::DBHostObject(jsi::Runtime &rt, std::string &db_name,

DBHostObject::DBHostObject(jsi::Runtime &rt, std::string &base_path,
std::string &db_name, std::string &path,
bool readOnly,
std::string &crsqlite_path,
std::string &sqlite_vec_path,
bool readOnly, bool failOnCreate,
std::string &encryption_key)
: base_path(base_path), db_name(db_name), delete_db_name(db_name) {
thread_pool = std::make_shared<ThreadPool>();

#ifdef OP_SQLITE_USE_SQLCIPHER
db = opsqlite_open(db_name, path, readOnly, crsqlite_path, sqlite_vec_path,
encryption_key);
db = opsqlite_open(db_name, path, readOnly, failOnCreate, encryption_key);
#elif OP_SQLITE_USE_LIBSQL
if (readOnly) {
throw std::runtime_error("libsql does not support read-only databases.");
}
db = opsqlite_libsql_open(db_name, path, crsqlite_path);
db = opsqlite_libsql_open(db_name, path, failOnCreate);
#else
db = opsqlite_open(db_name, path, readOnly, crsqlite_path, sqlite_vec_path);
db = opsqlite_open(db_name, path, readOnly, failOnCreate);
#endif
create_jsi_functions(rt);
};
Expand Down
4 changes: 2 additions & 2 deletions cpp/DBHostObject.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -45,8 +45,8 @@ class JSI_EXPORT DBHostObject : public jsi::HostObject {
public:
// Normal constructor shared between all backends
DBHostObject(jsi::Runtime &rt, std::string &base_path, std::string &db_name,
std::string &path, bool readOnly, std::string &crsqlite_path,
std::string &sqlite_vec_path, std::string &encryption_key);
std::string &path, bool readOnly, bool failOnCreate,
std::string &encryption_key);

#ifdef OP_SQLITE_USE_LIBSQL
// Constructor for remoteOpen, purely for remote databases
Expand Down
9 changes: 7 additions & 2 deletions cpp/OPSqlite.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ void install(jsi::Runtime &rt,
std::string location;
std::string encryption_key;
bool readOnly = false;
bool failOnCreate = false;

if (options.hasProperty(rt, "location")) {
location = options.getProperty(rt, "location").asString(rt).utf8(rt);
Expand All @@ -75,6 +76,10 @@ void install(jsi::Runtime &rt,
readOnly = options.getProperty(rt, "readOnly").asBool();
}

if (options.hasProperty(rt, "failOnCreate")) {
failOnCreate = options.getProperty(rt, "failOnCreate").asBool();
}

if (!location.empty()) {
if (location == ":memory:") {
path = ":memory:";
Expand All @@ -86,7 +91,7 @@ void install(jsi::Runtime &rt,
}

std::shared_ptr<DBHostObject> db = std::make_shared<DBHostObject>(
rt, path, name, path, readOnly, _crsqlite_path, _sqlite_vec_path, encryption_key);
rt, path, name, path, readOnly, failOnCreate, encryption_key);
dbs.emplace_back(db);
return jsi::Object::createFromHostObject(rt, db);
});
Expand Down Expand Up @@ -228,7 +233,7 @@ void expoUpdatesWorkaround(const char *base_path) {
std::string path = std::string(base_path);
// Open a DB before anything else so that expo-updates does not mess up the
// configuration
opsqlite_libsql_open("__dummy", path, "");
opsqlite_libsql_open("__dummy", path, false);
#endif
}

Expand Down
16 changes: 8 additions & 8 deletions cpp/bridge.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -81,14 +81,11 @@

#ifdef OP_SQLITE_USE_SQLCIPHER
sqlite3 *opsqlite_open(std::string const &name, std::string const &path,
bool readOnly, std::string const &crsqlite_path,
std::string const &sqlite_vec_path,
bool readOnly, bool failOnCreate,
std::string const &encryption_key) {
#else
sqlite3 *opsqlite_open(std::string const &name, std::string const &path,
bool readOnly,
[[maybe_unused]] std::string const &crsqlite_path,
[[maybe_unused]] std::string const &sqlite_vec_path) {
bool readOnly, bool failOnCreate) {
#endif
std::string final_path = opsqlite_get_db_path(name, path);
char *errMsg;
Expand All @@ -98,7 +95,10 @@
if (readOnly) {
flags |= SQLITE_OPEN_READONLY;
} else {
flags |= SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE;
flags |= SQLITE_OPEN_READWRITE;
if (!failOnCreate) {
flags |= SQLITE_OPEN_CREATE;
}
}

int status = sqlite3_open_v2(final_path.c_str(), &db, flags, nullptr);
Expand Down Expand Up @@ -135,7 +135,7 @@
#ifdef OP_SQLITE_USE_CRSQLITE
const char *crsqliteEntryPoint = "sqlite3_crsqlite_init";

sqlite3_load_extension(db, crsqlite_path.c_str(), crsqliteEntryPoint,
sqlite3_load_extension(db, _crsqlite_path.c_str(), crsqliteEntryPoint,
&errMsg);

if (errMsg != nullptr) {
Expand All @@ -146,7 +146,7 @@
#ifdef OP_SQLITE_USE_SQLITE_VEC
const char *vec_entry_point = "sqlite3_vec_init";

sqlite3_load_extension(db, sqlite_vec_path.c_str(), vec_entry_point, &errMsg);
sqlite3_load_extension(db, _sqlite_vec_path.c_str(), vec_entry_point, &errMsg);

if (errMsg != nullptr) {
throw std::runtime_error(errMsg);
Expand Down Expand Up @@ -333,7 +333,7 @@
if (isFailed) {
throw std::runtime_error(
"[op-sqlite] SQLite code: " + std::to_string(result) +
" execution error: " + std::string(errorMessage));

Check warning on line 336 in cpp/bridge.cpp

View workflow job for this annotation

GitHub Actions / ios-embedded

variable 'errorMessage' may be uninitialized when used here [-Wconditional-uninitialized]

Check warning on line 336 in cpp/bridge.cpp

View workflow job for this annotation

GitHub Actions / ios-embedded

variable 'errorMessage' may be uninitialized when used here [-Wconditional-uninitialized]

Check warning on line 336 in cpp/bridge.cpp

View workflow job for this annotation

GitHub Actions / ios

variable 'errorMessage' may be uninitialized when used here [-Wconditional-uninitialized]

Check warning on line 336 in cpp/bridge.cpp

View workflow job for this annotation

GitHub Actions / ios-sqlcipher

variable 'errorMessage' may be uninitialized when used here [-Wconditional-uninitialized]
}

int changedRowCount = sqlite3_changes(db);
Expand Down Expand Up @@ -654,7 +654,7 @@
if (isFailed) {
throw std::runtime_error(
"[op-sqlite] SQLite error code: " + std::to_string(result) +
", description: " + std::string(errorMessage));

Check warning on line 657 in cpp/bridge.cpp

View workflow job for this annotation

GitHub Actions / ios-embedded

variable 'errorMessage' may be uninitialized when used here [-Wconditional-uninitialized]
}

int changedRowCount = sqlite3_changes(db);
Expand Down Expand Up @@ -793,7 +793,7 @@
if (isFailed) {
throw std::runtime_error(
"[op-sqlite] SQLite error code: " + std::to_string(step) +
", description: " + std::string(errorMessage));

Check warning on line 796 in cpp/bridge.cpp

View workflow job for this annotation

GitHub Actions / ios-embedded

variable 'errorMessage' may be uninitialized when used here [-Wconditional-uninitialized]
}

int changedRowCount = sqlite3_changes(db);
Expand Down
13 changes: 8 additions & 5 deletions cpp/bridge.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -22,19 +22,22 @@ typedef std::function<void(std::string dbName, std::string tableName,
typedef std::function<void(std::string dbName)> CommitCallback;
typedef std::function<void(std::string dbName)> RollbackCallback;

// Paths to the optional loadable extensions, set once in install() and
// shared by every subsequent open() call instead of being threaded through
// as parameters.
extern std::string _crsqlite_path;
extern std::string _sqlite_vec_path;

std::string opsqlite_get_db_path(std::string const &db_name,
std::string const &location);

#ifdef OP_SQLITE_USE_SQLCIPHER
sqlite3 *opsqlite_open(std::string const &dbName, std::string const &path,
bool readOnly, std::string const &crsqlite_path,
std::string const &sqlite_vec_path,
bool readOnly, bool failOnCreate,
std::string const &encryption_key);
#else
sqlite3 *opsqlite_open(std::string const &name, std::string const &path,
bool readOnly,
[[maybe_unused]] std::string const &crsqlite_path,
std::string const &sqlite_vec_path);
bool readOnly, bool failOnCreate);
#endif

#ifdef OP_SQLITE_USE_TURSO
Expand Down
23 changes: 8 additions & 15 deletions cpp/libsql/bridge.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@
.remote_encryption_key = remote_encryption_key.empty()
? nullptr
: remote_encryption_key.c_str(),
.sync_interval = sync_interval,

Check warning on line 63 in cpp/libsql/bridge.cpp

View workflow job for this annotation

GitHub Actions / ios-libsql

ISO C++ requires field designators to be specified in declaration order; field 'remote_encryption_key' will be initialized after field 'sync_interval' [-Wreorder-init-list]

Check warning on line 63 in cpp/libsql/bridge.cpp

View workflow job for this annotation

GitHub Actions / ios-libsql

ISO C++ requires field designators to be specified in declaration order; field 'remote_encryption_key' will be initialized after field 'sync_interval' [-Wreorder-init-list]
.with_webpki = '1',
.offline = offline,
};
Expand All @@ -80,9 +80,16 @@
}

DB opsqlite_libsql_open(std::string const &name, std::string const &last_path,
std::string const &crsqlitePath) {
bool failOnCreate) {
std::string path = opsqlite_get_db_path(name, last_path);

// libsql_open_file always creates the database file if it is missing,
// there is no "open existing only" flag, so failOnCreate is enforced up
// front by checking for the file's existence.
if (failOnCreate && path != ":memory:" && !std::filesystem::exists(path)) {
throw std::runtime_error("unable to open database file: " + path);
}

int status;
libsql_database_t db;
libsql_connection_t c;
Expand All @@ -100,20 +107,6 @@
throw std::runtime_error(err);
}

#ifdef OP_SQLITE_USE_CRSQLITE
const char *errMsg;
const char *crsqliteEntryPoint = "sqlite3_crsqlite_init";

status = libsql_load_extension(c, crsqlitePath.c_str(), crsqliteEntryPoint,
&errMsg);

if (status != 0) {
throw std::runtime_error(errMsg);
} else {
LOGI("Loaded CRSQlite successfully");
}
#endif

return {.db = db, .c = c};
}

Expand Down
2 changes: 1 addition & 1 deletion cpp/libsql/bridge.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ std::string opsqlite_get_db_path(std::string const &name,
std::string const &location);

DB opsqlite_libsql_open(std::string const &name, std::string const &path,
std::string const &crsqlitePath);
bool failOnCreate);

DB opsqlite_libsql_open_remote(std::string const &url,
std::string const &auth_token);
Expand Down
15 changes: 12 additions & 3 deletions cpp/turso_bridge.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -370,14 +370,23 @@ std::string opsqlite_get_db_path(std::string const &db_name,
}

sqlite3 *opsqlite_open(std::string const &name, std::string const &path,
bool readOnly,
[[maybe_unused]] std::string const &crsqlite_path,
[[maybe_unused]] std::string const &sqlite_vec_path) {
bool readOnly, bool failOnCreate) {
if (readOnly) {
throw std::runtime_error("turso does not support read-only databases.");
}
auto *handle = new TursoDbHandle();
handle->path = opsqlite_get_db_path(name, path);

// Turso's API always creates the database file on open, there is no
// "open existing only" flag, so failOnCreate is enforced up front by
// checking for the file's existence.
if (failOnCreate && handle->path != ":memory:" &&
!std::filesystem::exists(handle->path)) {
std::string missing_path = handle->path;
delete handle;
throw std::runtime_error("unable to open database file: " + missing_path);
}

setup_turso_temp_dir(handle->path);

turso_database_config_t db_config = {
Expand Down
17 changes: 17 additions & 0 deletions docs/docs/api.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,23 @@ export const db = open({

If you want to read more about securely storing your encryption key, [read this article](https://ospfranco.com/react-native-security-guide/). Again: **DO NOT OPEN MORE THAN ONE CONNECTION PER DATABASE**. Just export one single db connection for your entire application and reuse it everywhere.

### Open Existing Only (failOnCreate)

By default, `open()` creates the database file if it doesn't already exist. Pass `failOnCreate: true` to require the file to already exist; if it doesn't, the call throws instead of creating it. This is supported across all backends (plain SQLite3, SQLCipher, libsql and Turso).

```tsx
import { open } from '@op-engineering/op-sqlite';

try {
const db = open({
name: 'myDb.sqlite',
failOnCreate: true,
});
} catch (e) {
// The database file did not exist and was not created
}
```

### Remote and Sync Open (Libsql/Turso)

For remote/sync scenarios, enable either the `libsql` or `turso` backend in your package configuration, then use `openRemote` or `openSync`.
Expand Down
10 changes: 10 additions & 0 deletions docs/docs/changelog.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
---
sidebar_position: 11
---

# API Changes

## 17.2.0

- Added `failOnCreate` option to `open()`. When set to `true`, the database file must already exist; if it doesn't, `open()` throws instead of creating it. Implemented natively across all backends (plain SQLite3, SQLCipher, libsql and Turso). See the [Open Existing Only (failOnCreate)](./api.md#open-existing-only-failoncreate) section for usage.
- Removed support for combining `crsqlite` with `libsql`. Enabling both in `package.json` now fails the build (iOS podspec and Android Gradle) with a clear error instead of silently loading the extension. If you relied on this combination, drop one of the two flags.
Loading
Loading