Skip to content

Commit

Permalink
libsql: add unsafe Database::connect_raw
Browse files Browse the repository at this point in the history
This adds a `connect_raw` function that can be called in an unsafe
manner. This will bypass `libsql`'s safety checks that enforce that
sqlite3's serialized thread safety mode is enabled. This is a use at
your own risk type of function.
  • Loading branch information
LucioFranco committed Dec 23, 2024
1 parent e10af22 commit 8a83816
Show file tree
Hide file tree
Showing 2 changed files with 153 additions and 2 deletions.
85 changes: 85 additions & 0 deletions libsql/src/database.rs
Original file line number Diff line number Diff line change
Expand Up @@ -666,6 +666,91 @@ impl Database {
_ => unreachable!("no database type set"),
}
}

/// Connect to a database without safety checks. Using `connect` will enforce that sqlite is
/// compiled and configured correctly. This can conflict if you have other sqlite3 processes
/// due to sqlite3's global config. This function allows a user to bypass those checks in
/// exchanged for the user enforcing that sqlite3 is compiled AND enabled for serailized thread
/// saftey mode.
///
/// SAFTEY: Users must ensure that sqlite3 has SERIALIZED thread safe mode enabled or else this
/// will produce UBs.
#[allow(unreachable_patterns)]
pub unsafe fn connect_raw(&self) -> Result<Connection> {
match &self.db_type {
#[cfg(feature = "core")]
DbType::Memory { db: _ } => {
unimplemented!("memory dbs are not supported in connect_raw")
}

#[cfg(feature = "core")]
DbType::File {
path,
flags,
encryption_config,
} => {
use crate::local::impls::LibsqlConnection;

let db = crate::local::Database::open_raw(path, *flags)?;
let conn = db.connect()?;

if !cfg!(feature = "encryption") && encryption_config.is_some() {
return Err(crate::Error::Misuse(
"Encryption is not enabled: enable the `encryption` feature in order to enable encryption-at-rest".to_string(),
));
}

#[cfg(feature = "encryption")]
if let Some(cfg) = encryption_config {
if unsafe {
libsql_sys::connection::set_encryption_cipher(conn.raw, cfg.cipher_id())
} == -1
{
return Err(crate::Error::Misuse(
"failed to set encryption cipher".to_string(),
));
}
if unsafe {
libsql_sys::connection::set_encryption_key(conn.raw, &cfg.encryption_key)
} != crate::ffi::SQLITE_OK
{
return Err(crate::Error::Misuse(
"failed to set encryption key".to_string(),
));
}
}

let conn = std::sync::Arc::new(LibsqlConnection { conn });

Ok(Connection { conn })
}

#[cfg(feature = "replication")]
DbType::Sync {
db: _,
encryption_config: _,
} => {
unimplemented!("embedded replica is not supported in connect_raw")
}

#[cfg(feature = "sync")]
DbType::Offline { db } => {
unimplemented!("offline sync not supported in connect_raw")
}

#[cfg(feature = "remote")]
DbType::Remote {
url: _,
auth_token: _,
connector: _,
version: _,
} => {
unimplemented!("remote connections are not supported in connect_raw")
}

_ => unreachable!("no database type set"),
}
}
}

#[cfg(any(
Expand Down
70 changes: 68 additions & 2 deletions libsql/src/local/database.rs
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,30 @@ impl Database {
}
}

/// Safety: this is like `open` but does not enfoce that sqlite_config has THREADSAFE set to
/// `SQLITE_CONFIG_SERIALIZED`, calling
pub unsafe fn open_raw<S: Into<String>>(db_path: S, flags: OpenFlags) -> Result<Database> {
let db_path = db_path.into();

if db_path.starts_with("libsql:")
|| db_path.starts_with("http:")
|| db_path.starts_with("https:")
{
Err(ConnectionFailed(format!(
"Unable to open local database {db_path} with Database::open()"
)))
} else {
Ok(Database {
db_path,
flags,
#[cfg(feature = "replication")]
replication_ctx: None,
#[cfg(feature = "sync")]
sync_ctx: None,
})
}
}

#[cfg(feature = "replication")]
pub async fn open_http_sync(
connector: crate::util::ConnectorService,
Expand Down Expand Up @@ -263,6 +287,40 @@ impl Database {
}
}

pub unsafe fn new2(db_path: String, flags: OpenFlags) -> Database {
static LIBSQL_INIT: Once = Once::new();

LIBSQL_INIT.call_once(|| {
// Ensure that we are configured with the correct threading model
// if this config is not set correctly the entire api is unsafe.
unsafe {
assert_eq!(
ffi::sqlite3_config(ffi::SQLITE_CONFIG_SERIALIZED),
ffi::SQLITE_OK,
"libsql was configured with an incorrect threading configuration and
the api is not safe to use. Please check that no multi-thread options have
been set. If nothing was configured then please open an issue at:
https://github.com/libsql/libsql"
);

assert_eq!(
ffi::sqlite3_initialize(),
ffi::SQLITE_OK,
"libsql failed to initialize"
);
}
});

Database {
db_path,
flags,
#[cfg(feature = "replication")]
replication_ctx: None,
#[cfg(feature = "sync")]
sync_ctx: None,
}
}

pub fn connect(&self) -> Result<Connection> {
Connection::connect(self)
}
Expand Down Expand Up @@ -420,7 +478,11 @@ impl Database {
}

#[cfg(feature = "sync")]
async fn try_push(&self, sync_ctx: &mut SyncContext, conn: &Connection) -> Result<crate::database::Replicated> {
async fn try_push(
&self,
sync_ctx: &mut SyncContext,
conn: &Connection,
) -> Result<crate::database::Replicated> {
let page_size = {
let rows = conn
.query("PRAGMA page_size", crate::params::Params::None)?
Expand Down Expand Up @@ -471,7 +533,11 @@ impl Database {
}

#[cfg(feature = "sync")]
async fn try_pull(&self, sync_ctx: &mut SyncContext, conn: &Connection) -> Result<crate::database::Replicated> {
async fn try_pull(
&self,
sync_ctx: &mut SyncContext,
conn: &Connection,
) -> Result<crate::database::Replicated> {
let generation = sync_ctx.generation();
let mut frame_no = sync_ctx.durable_frame_num() + 1;
conn.wal_insert_begin()?;
Expand Down

0 comments on commit 8a83816

Please sign in to comment.