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
8 changes: 6 additions & 2 deletions cpp/DBHostObject.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -217,19 +217,23 @@ 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,
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, crsqlite_path, sqlite_vec_path,
db = opsqlite_open(db_name, path, readOnly, crsqlite_path, sqlite_vec_path,
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);
#else
db = opsqlite_open(db_name, path, crsqlite_path, sqlite_vec_path);
db = opsqlite_open(db_name, path, readOnly, crsqlite_path, sqlite_vec_path);
#endif
create_jsi_functions(rt);
};
Expand Down
2 changes: 1 addition & 1 deletion cpp/DBHostObject.h
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ 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, std::string &crsqlite_path,
std::string &path, bool readOnly, std::string &crsqlite_path,
std::string &sqlite_vec_path, std::string &encryption_key);

#ifdef OP_SQLITE_USE_LIBSQL
Expand Down
7 changes: 6 additions & 1 deletion cpp/OPSqlite.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ void install(jsi::Runtime &rt,
std::string path = std::string(_base_path);
std::string location;
std::string encryption_key;
bool readOnly = false;

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

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

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

std::shared_ptr<DBHostObject> db = std::make_shared<DBHostObject>(
rt, path, name, path, _crsqlite_path, _sqlite_vec_path, encryption_key);
rt, path, name, path, readOnly, _crsqlite_path, _sqlite_vec_path, encryption_key);
dbs.emplace_back(db);
return jsi::Object::createFromHostObject(rt, db);
});
Expand Down
11 changes: 8 additions & 3 deletions cpp/bridge.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -80,20 +80,25 @@

#ifdef OP_SQLITE_USE_SQLCIPHER
sqlite3 *opsqlite_open(std::string const &name, std::string const &path,
std::string const &crsqlite_path,
bool readOnly, std::string const &crsqlite_path,
std::string const &sqlite_vec_path,
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) {
#endif
std::string final_path = opsqlite_get_db_path(name, path);
char *errMsg;
sqlite3 *db;

int flags =
SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE | SQLITE_OPEN_FULLMUTEX;
int flags = SQLITE_OPEN_FULLMUTEX;
if (readOnly) {
flags |= SQLITE_OPEN_READONLY;
} else {
flags |= SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE;
}

int status = sqlite3_open_v2(final_path.c_str(), &db, flags, nullptr);

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

Check warning on line 335 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 335 in cpp/bridge.cpp

View workflow job for this annotation

GitHub Actions / ios-sqlcipher

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

Check warning on line 335 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 335 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 @@ -641,7 +646,7 @@
if (isFailed) {
throw std::runtime_error(
"[op-sqlite] SQLite error code: " + std::to_string(result) +
", description: " + std::string(errorMessage));

Check warning on line 649 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 649 in cpp/bridge.cpp

View workflow job for this annotation

GitHub Actions / ios-sqlcipher

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

Check warning on line 649 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 @@ -780,7 +785,7 @@
if (isFailed) {
throw std::runtime_error(
"[op-sqlite] SQLite error code: " + std::to_string(step) +
", description: " + std::string(errorMessage));

Check warning on line 788 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 788 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
3 changes: 2 additions & 1 deletion cpp/bridge.h
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,12 @@ std::string opsqlite_get_db_path(std::string const &db_name,

#ifdef OP_SQLITE_USE_SQLCIPHER
sqlite3 *opsqlite_open(std::string const &dbName, std::string const &path,
std::string const &crsqlite_path,
bool readOnly, std::string const &crsqlite_path,
std::string const &sqlite_vec_path,
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);
#endif
Expand Down
4 changes: 4 additions & 0 deletions cpp/turso_bridge.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -370,8 +370,12 @@ 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) {
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);
setup_turso_temp_dir(handle->path);
Expand Down
31 changes: 31 additions & 0 deletions example/src/tests/dbsetup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -231,6 +231,37 @@ describe("DB setup tests", () => {
expect(!!e).toBe(true);
}
});

it("Can open read-only databases", async () => {
function openReadOnly() {
return open({
name: 'ignored',
location: ":memory:",
readOnly: true,
});
}

if (isLibsql() || isTurso()) {
// libsql/turso C bindings don't expose a way to open read-only databases, so the option is not supported.
try {
openReadOnly();
throw new Error('should have failed');
} catch (e: any) {
expect(e.message).toContain('does not support read-only databases');
}

return;
}

const db = openReadOnly();
expect(!!db).toBe(true);

try {
await db.execute('CREATE TABLE foo (bar TEXT);');
} catch (e: any) {
expect(e.message).toContain('attempt to write a readonly database');
}
});
});

it("Can attach/dettach database", () => {
Expand Down
13 changes: 3 additions & 10 deletions src/functions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import type {
BatchQueryResult,
DB,
DBParams,
OpenOptions,
OPSQLiteProxy,
QueryResult,
Scalar,
Expand Down Expand Up @@ -363,11 +364,7 @@ export const openRemote = (params: { url: string; authToken: string }): DB => {
* Open a connection to a local sqlite or sqlcipher database
* If you want libsql remote or sync connections, use openSync or openRemote
*/
export const open = (params: {
name: string;
location?: string;
encryptionKey?: string;
}): DB => {
export const open = (params: OpenOptions): DB => {
if (params.location?.startsWith("file://")) {
console.warn(
"[op-sqlite] You are passing a path with 'file://' prefix, it's automatically removed",
Expand All @@ -385,11 +382,7 @@ export const open = (params: {
* Async wrapper around open().
* Useful for cross-platform code that also targets web where openAsync() is required.
*/
export const openAsync = async (params: {
name: string;
location?: string;
encryptionKey?: string;
}): Promise<DB> => {
export const openAsync = async (params: OpenOptions): Promise<DB> => {
return open(params);
};

Expand Down
16 changes: 5 additions & 11 deletions src/functions.web.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import type {
DB,
DBParams,
FileLoadResult,
OpenOptions,
OPSQLiteProxy,
PreparedStatement,
QueryResult,
Expand Down Expand Up @@ -313,11 +314,7 @@ function enhanceWebDb(db: _InternalDB, options: { name?: string; location?: stri
return enhancedDb;
}

async function createWebDb(params: {
name: string;
location?: string;
encryptionKey?: string;
}): Promise<_InternalDB> {
async function createWebDb(params: OpenOptions): Promise<_InternalDB> {
if (params.encryptionKey) {
throw new Error("[op-sqlite] SQLCipher is not supported on web.");
}
Expand All @@ -328,6 +325,7 @@ async function createWebDb(params: {
const filename = `file:${params.name}?vfs=opfs`;
const opened = await promiser("open", {
filename,
flags: params.readOnly ? 'r': 'c'
});

const dbId = opened?.dbId || opened?.result?.dbId;
Expand Down Expand Up @@ -444,16 +442,12 @@ async function createWebDb(params: {
* Open a connection to a local sqlite database on web.
* Web is async-only: use openAsync() and async methods like execute().
*/
export const openAsync = async (params: {
name: string;
location?: string;
encryptionKey?: string;
}): Promise<DB> => {
export const openAsync = async (params: OpenOptions): Promise<DB> => {
const db = await createWebDb(params);
return enhanceWebDb(db, params);
};

export const open = (_params: { name: string; location?: string; encryptionKey?: string }): DB => {
export const open = (_params:OpenOptions): DB => {
throwSyncApiError("open");
};

Expand Down
22 changes: 22 additions & 0 deletions src/types.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,27 @@
export type Scalar = string | number | boolean | null | ArrayBuffer | ArrayBufferView;

export interface OpenOptions {
/**
* The file name of the database to open.
*/
name: string;
/**
* A directory prefix for the database file.
*
* When set to `:memory:`, the name is ignored and an in-memory database is opened instead.
*/
location?: string;
encryptionKey?: string;
/**
* When set to true, the database is opened in read-only mode and any statement attempting to write to the database
* will fail.
*
* This option is only supported for plain SQLite3 and SQLCipher. When enabling this option with libsql enabled,
* opening databases will throw.
*/
readOnly?: boolean;
}

/**
* Object returned by SQL Query executions {
* insertId: Represent the auto-generated row id if applicable
Expand Down
Loading