Skip to content

Commit 5b8934e

Browse files
authored
Merge pull request #1906 from tursodatabase/lucio/build-unsafe
libsql: add `skip_safety_assert` builder option
2 parents e88c6b5 + e859a0d commit 5b8934e

File tree

3 files changed

+172
-20
lines changed

3 files changed

+172
-20
lines changed

libsql/src/database.rs

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@ enum DbType {
7474
path: String,
7575
flags: OpenFlags,
7676
encryption_config: Option<EncryptionConfig>,
77+
skip_saftey_assert: bool,
7778
},
7879
#[cfg(feature = "replication")]
7980
Sync {
@@ -146,6 +147,7 @@ cfg_core! {
146147
path: db_path.into(),
147148
flags,
148149
encryption_config: None,
150+
skip_saftey_assert: false,
149151
},
150152
max_write_replication_index: Default::default(),
151153
})
@@ -429,7 +431,7 @@ cfg_replication! {
429431
DbType::Sync { db, .. } => {
430432
let path = db.path().to_string();
431433
Ok(Database {
432-
db_type: DbType::File { path, flags: OpenFlags::default(), encryption_config: None},
434+
db_type: DbType::File { path, flags: OpenFlags::default(), encryption_config: None, skip_saftey_assert: false },
433435
max_write_replication_index: Default::default(),
434436
})
435437
}
@@ -550,10 +552,16 @@ impl Database {
550552
path,
551553
flags,
552554
encryption_config,
555+
skip_saftey_assert,
553556
} => {
554557
use crate::local::impls::LibsqlConnection;
555558

556-
let db = crate::local::Database::open(path, *flags)?;
559+
let db = if !skip_saftey_assert {
560+
crate::local::Database::open(path, *flags)?
561+
} else {
562+
unsafe { crate::local::Database::open_raw(path, *flags)? }
563+
};
564+
557565
let conn = db.connect()?;
558566

559567
if !cfg!(feature = "encryption") && encryption_config.is_some() {

libsql/src/database/builder.rs

Lines changed: 77 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ impl Builder<()> {
3939
path: path.as_ref().to_path_buf(),
4040
flags: crate::OpenFlags::default(),
4141
encryption_config: None,
42+
skip_safety_assert: false,
4243
},
4344
}
4445
}
@@ -64,7 +65,8 @@ impl Builder<()> {
6465
read_your_writes: true,
6566
sync_interval: None,
6667
http_request_callback: None,
67-
namespace: None
68+
namespace: None,
69+
skip_safety_assert: false,
6870
},
6971
}
7072
}
@@ -137,6 +139,7 @@ cfg_core! {
137139
path: std::path::PathBuf,
138140
flags: crate::OpenFlags,
139141
encryption_config: Option<EncryptionConfig>,
142+
skip_safety_assert: bool,
140143
}
141144

142145
impl Builder<Local> {
@@ -155,10 +158,29 @@ cfg_core! {
155158
self
156159
}
157160

161+
/// Skip the saftey assert used to ensure that sqlite3 is configured correctly for the way
162+
/// that libsql uses the ffi code. By default, libsql will try to use the SERIALIZED
163+
/// threadsafe mode for sqlite3. This allows us to implement Send/Sync for all the types to
164+
/// allow them to move between threads safely. Due to the fact that sqlite3 has a global
165+
/// config this may conflict with other sqlite3 connections in the same process.
166+
///
167+
/// Using this setting is very UNSAFE and you are expected to use the libsql in adherence
168+
/// with the sqlite3 threadsafe rules or else you WILL create undefined behavior. Use at
169+
/// your own risk.
170+
pub unsafe fn skip_saftey_assert(mut self, skip: bool) -> Builder<Local> {
171+
self.inner.skip_safety_assert = skip;
172+
self
173+
}
174+
158175
/// Build the local database.
159176
pub async fn build(self) -> Result<Database> {
160177
let db = if self.inner.path == std::path::Path::new(":memory:") {
161-
let db = crate::local::Database::open(":memory:", crate::OpenFlags::default())?;
178+
let db = if !self.inner.skip_safety_assert {
179+
crate::local::Database::open(":memory:", crate::OpenFlags::default())?
180+
} else {
181+
unsafe { crate::local::Database::open_raw(":memory:", crate::OpenFlags::default())? }
182+
};
183+
162184
Database {
163185
db_type: DbType::Memory { db } ,
164186
max_write_replication_index: Default::default(),
@@ -176,6 +198,7 @@ cfg_core! {
176198
path,
177199
flags: self.inner.flags,
178200
encryption_config: self.inner.encryption_config,
201+
skip_saftey_assert: self.inner.skip_safety_assert
179202
},
180203
max_write_replication_index: Default::default(),
181204
}
@@ -196,6 +219,7 @@ cfg_replication! {
196219
sync_interval: Option<std::time::Duration>,
197220
http_request_callback: Option<crate::util::HttpRequestCallback>,
198221
namespace: Option<String>,
222+
skip_safety_assert: bool,
199223
}
200224

201225
/// Local replica configuration type in [`Builder`].
@@ -270,6 +294,20 @@ cfg_replication! {
270294
self
271295
}
272296

297+
/// Skip the saftey assert used to ensure that sqlite3 is configured correctly for the way
298+
/// that libsql uses the ffi code. By default, libsql will try to use the SERIALIZED
299+
/// threadsafe mode for sqlite3. This allows us to implement Send/Sync for all the types to
300+
/// allow them to move between threads safely. Due to the fact that sqlite3 has a global
301+
/// config this may conflict with other sqlite3 connections in the same process.
302+
///
303+
/// Using this setting is very UNSAFE and you are expected to use the libsql in adherence
304+
/// with the sqlite3 threadsafe rules or else you WILL create undefined behavior. Use at
305+
/// your own risk.
306+
pub unsafe fn skip_saftey_assert(mut self, skip: bool) -> Builder<RemoteReplica> {
307+
self.inner.skip_safety_assert = skip;
308+
self
309+
}
310+
273311
/// Build the remote embedded replica database.
274312
pub async fn build(self) -> Result<Database> {
275313
let RemoteReplica {
@@ -285,7 +323,8 @@ cfg_replication! {
285323
read_your_writes,
286324
sync_interval,
287325
http_request_callback,
288-
namespace
326+
namespace,
327+
skip_safety_assert
289328
} = self.inner;
290329

291330
let connector = if let Some(connector) = connector {
@@ -303,19 +342,41 @@ cfg_replication! {
303342

304343
let path = path.to_str().ok_or(crate::Error::InvalidUTF8Path)?.to_owned();
305344

306-
let db = crate::local::Database::open_http_sync_internal(
307-
connector,
308-
path,
309-
url,
310-
auth_token,
311-
version,
312-
read_your_writes,
313-
encryption_config.clone(),
314-
sync_interval,
315-
http_request_callback,
316-
namespace,
317-
)
318-
.await?;
345+
let db = if !skip_safety_assert {
346+
crate::local::Database::open_http_sync_internal(
347+
connector,
348+
path,
349+
url,
350+
auth_token,
351+
version,
352+
read_your_writes,
353+
encryption_config.clone(),
354+
sync_interval,
355+
http_request_callback,
356+
namespace,
357+
)
358+
.await?
359+
} else {
360+
// SAFETY: this can only be enabled via the unsafe config function
361+
// `skip_safety_assert`.
362+
unsafe {
363+
crate::local::Database::open_http_sync_internal2(
364+
connector,
365+
path,
366+
url,
367+
auth_token,
368+
version,
369+
read_your_writes,
370+
encryption_config.clone(),
371+
sync_interval,
372+
http_request_callback,
373+
namespace,
374+
)
375+
.await?
376+
}
377+
378+
};
379+
319380

320381
Ok(Database {
321382
db_type: DbType::Sync { db, encryption_config },

libsql/src/local/database.rs

Lines changed: 85 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,30 @@ impl Database {
5353
}
5454
}
5555

56+
/// Safety: this is like `open` but does not enfoce that sqlite_config has THREADSAFE set to
57+
/// `SQLITE_CONFIG_SERIALIZED`, calling
58+
pub unsafe fn open_raw<S: Into<String>>(db_path: S, flags: OpenFlags) -> Result<Database> {
59+
let db_path = db_path.into();
60+
61+
if db_path.starts_with("libsql:")
62+
|| db_path.starts_with("http:")
63+
|| db_path.starts_with("https:")
64+
{
65+
Err(ConnectionFailed(format!(
66+
"Unable to open local database {db_path} with Database::open()"
67+
)))
68+
} else {
69+
Ok(Database {
70+
db_path,
71+
flags,
72+
#[cfg(feature = "replication")]
73+
replication_ctx: None,
74+
#[cfg(feature = "sync")]
75+
sync_ctx: None,
76+
})
77+
}
78+
}
79+
5680
#[cfg(feature = "replication")]
5781
pub async fn open_http_sync(
5882
connector: crate::util::ConnectorService,
@@ -128,6 +152,57 @@ impl Database {
128152
Ok(db)
129153
}
130154

155+
#[cfg(feature = "replication")]
156+
#[doc(hidden)]
157+
pub async unsafe fn open_http_sync_internal2(
158+
connector: crate::util::ConnectorService,
159+
db_path: String,
160+
endpoint: String,
161+
auth_token: String,
162+
version: Option<String>,
163+
read_your_writes: bool,
164+
encryption_config: Option<EncryptionConfig>,
165+
sync_interval: Option<std::time::Duration>,
166+
http_request_callback: Option<crate::util::HttpRequestCallback>,
167+
namespace: Option<String>,
168+
) -> Result<Database> {
169+
use std::path::PathBuf;
170+
171+
use crate::util::coerce_url_scheme;
172+
173+
let mut db = Database::open_raw(&db_path, OpenFlags::default())?;
174+
175+
let endpoint = coerce_url_scheme(endpoint);
176+
let remote = crate::replication::client::Client::new(
177+
connector.clone(),
178+
endpoint
179+
.as_str()
180+
.try_into()
181+
.map_err(|e: InvalidUri| crate::Error::Replication(e.into()))?,
182+
auth_token.clone(),
183+
version.as_deref(),
184+
http_request_callback.clone(),
185+
namespace,
186+
)
187+
.map_err(|e| crate::Error::Replication(e.into()))?;
188+
let path = PathBuf::from(db_path);
189+
let client = RemoteClient::new(remote.clone(), &path)
190+
.await
191+
.map_err(|e| crate::errors::Error::ConnectionFailed(e.to_string()))?;
192+
193+
let replicator =
194+
EmbeddedReplicator::with_remote(client, path, 1000, encryption_config, sync_interval)
195+
.await?;
196+
197+
db.replication_ctx = Some(ReplicationContext {
198+
replicator,
199+
client: Some(remote),
200+
read_your_writes,
201+
});
202+
203+
Ok(db)
204+
}
205+
131206
#[cfg(feature = "sync")]
132207
#[doc(hidden)]
133208
pub async fn open_local_with_offline_writes(
@@ -420,7 +495,11 @@ impl Database {
420495
}
421496

422497
#[cfg(feature = "sync")]
423-
async fn try_push(&self, sync_ctx: &mut SyncContext, conn: &Connection) -> Result<crate::database::Replicated> {
498+
async fn try_push(
499+
&self,
500+
sync_ctx: &mut SyncContext,
501+
conn: &Connection,
502+
) -> Result<crate::database::Replicated> {
424503
let page_size = {
425504
let rows = conn
426505
.query("PRAGMA page_size", crate::params::Params::None)?
@@ -471,7 +550,11 @@ impl Database {
471550
}
472551

473552
#[cfg(feature = "sync")]
474-
async fn try_pull(&self, sync_ctx: &mut SyncContext, conn: &Connection) -> Result<crate::database::Replicated> {
553+
async fn try_pull(
554+
&self,
555+
sync_ctx: &mut SyncContext,
556+
conn: &Connection,
557+
) -> Result<crate::database::Replicated> {
475558
let generation = sync_ctx.generation();
476559
let mut frame_no = sync_ctx.durable_frame_num() + 1;
477560
conn.wal_insert_begin()?;

0 commit comments

Comments
 (0)