Skip to content

Commit 1e6af39

Browse files
authored
Merge pull request #1932 from levydsa/remote_writes
Remote writes for offline databases
2 parents 09715e4 + 3bd9ef9 commit 1e6af39

File tree

14 files changed

+461
-159
lines changed

14 files changed

+461
-159
lines changed

libsql/Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,8 @@ sync = [
9999
"parser",
100100
"serde",
101101
"stream",
102+
"remote",
103+
"replication",
102104
"dep:tower",
103105
"dep:hyper",
104106
"dep:http",

libsql/src/database.rs

Lines changed: 45 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@ pub use libsql_sys::{Cipher, EncryptionConfig};
1010
use crate::{Connection, Result};
1111
use std::fmt;
1212
use std::sync::atomic::AtomicU64;
13-
use std::sync::Arc;
1413

1514
cfg_core! {
1615
bitflags::bitflags! {
@@ -82,7 +81,14 @@ enum DbType {
8281
encryption_config: Option<EncryptionConfig>,
8382
},
8483
#[cfg(feature = "sync")]
85-
Offline { db: crate::local::Database },
84+
Offline {
85+
db: crate::local::Database,
86+
remote_writes: bool,
87+
read_your_writes: bool,
88+
url: String,
89+
auth_token: String,
90+
connector: crate::util::ConnectorService,
91+
},
8692
#[cfg(feature = "remote")]
8793
Remote {
8894
url: String,
@@ -117,7 +123,7 @@ pub struct Database {
117123
db_type: DbType,
118124
/// The maximum replication index returned from a write performed using any connection created using this Database object.
119125
#[allow(dead_code)]
120-
max_write_replication_index: Arc<AtomicU64>,
126+
max_write_replication_index: std::sync::Arc<AtomicU64>,
121127
}
122128

123129
cfg_core! {
@@ -375,7 +381,7 @@ cfg_replication! {
375381
#[cfg(feature = "replication")]
376382
DbType::Sync { db, encryption_config: _ } => db.sync().await,
377383
#[cfg(feature = "sync")]
378-
DbType::Offline { db } => db.sync_offline().await,
384+
DbType::Offline { db, .. } => db.sync_offline().await,
379385
_ => Err(Error::SyncNotSupported(format!("{:?}", self.db_type))),
380386
}
381387
}
@@ -642,13 +648,42 @@ impl Database {
642648
}
643649

644650
#[cfg(feature = "sync")]
645-
DbType::Offline { db } => {
646-
use crate::local::impls::LibsqlConnection;
647-
648-
let conn = db.connect()?;
649-
650-
let conn = std::sync::Arc::new(LibsqlConnection { conn });
651+
DbType::Offline {
652+
db,
653+
remote_writes,
654+
read_your_writes,
655+
url,
656+
auth_token,
657+
connector,
658+
} => {
659+
use crate::{
660+
hrana::{connection::HttpConnection, hyper::HttpSender},
661+
local::impls::LibsqlConnection,
662+
replication::connection::State,
663+
sync::connection::SyncedConnection,
664+
};
665+
use tokio::sync::Mutex;
666+
667+
let local = db.connect()?;
668+
669+
if *remote_writes {
670+
let synced = SyncedConnection {
671+
local,
672+
remote: HttpConnection::new(
673+
url.clone(),
674+
auth_token.clone(),
675+
HttpSender::new(connector.clone(), None),
676+
),
677+
read_your_writes: *read_your_writes,
678+
context: db.sync_ctx.clone().unwrap(),
679+
state: std::sync::Arc::new(Mutex::new(State::Init)),
680+
};
681+
682+
let conn = std::sync::Arc::new(synced);
683+
return Ok(Connection { conn });
684+
}
651685

686+
let conn = std::sync::Arc::new(LibsqlConnection { conn: local });
652687
Ok(Connection { conn })
653688
}
654689

libsql/src/database/builder.rs

Lines changed: 28 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,9 @@ impl Builder<()> {
102102
connector: None,
103103
version: None,
104104
},
105-
connector:None,
105+
connector: None,
106+
read_your_writes: true,
107+
remote_writes: false,
106108
},
107109
}
108110
}
@@ -463,6 +465,8 @@ cfg_sync! {
463465
flags: crate::OpenFlags,
464466
remote: Remote,
465467
connector: Option<crate::util::ConnectorService>,
468+
remote_writes: bool,
469+
read_your_writes: bool,
466470
}
467471

468472
impl Builder<SyncedDatabase> {
@@ -472,6 +476,16 @@ cfg_sync! {
472476
self
473477
}
474478

479+
pub fn read_your_writes(mut self, v: bool) -> Builder<SyncedDatabase> {
480+
self.inner.read_your_writes = v;
481+
self
482+
}
483+
484+
pub fn remote_writes(mut self, v: bool) -> Builder<SyncedDatabase> {
485+
self.inner.remote_writes = v;
486+
self
487+
}
488+
475489
/// Provide a custom http connector that will be used to create http connections.
476490
pub fn connector<C>(mut self, connector: C) -> Builder<SyncedDatabase>
477491
where
@@ -497,6 +511,8 @@ cfg_sync! {
497511
version: _,
498512
},
499513
connector,
514+
remote_writes,
515+
read_your_writes,
500516
} = self.inner;
501517

502518
let path = path.to_str().ok_or(crate::Error::InvalidUTF8Path)?.to_owned();
@@ -515,16 +531,23 @@ cfg_sync! {
515531
let connector = crate::util::ConnectorService::new(svc);
516532

517533
let db = crate::local::Database::open_local_with_offline_writes(
518-
connector,
534+
connector.clone(),
519535
path,
520536
flags,
521-
url,
522-
auth_token,
537+
url.clone(),
538+
auth_token.clone(),
523539
)
524540
.await?;
525541

526542
Ok(Database {
527-
db_type: DbType::Offline { db },
543+
db_type: DbType::Offline {
544+
db,
545+
remote_writes,
546+
read_your_writes,
547+
url,
548+
auth_token,
549+
connector,
550+
},
528551
max_write_replication_index: Default::default(),
529552
})
530553
}

libsql/src/hrana/hyper.rs

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -305,14 +305,17 @@ impl Conn for HranaStream<HttpSender> {
305305
let parse = crate::parser::Statement::parse(sql);
306306
for s in parse {
307307
let s = s?;
308-
if s.kind == crate::parser::StmtKind::TxnBegin
309-
|| s.kind == crate::parser::StmtKind::TxnBeginReadOnly
310-
|| s.kind == crate::parser::StmtKind::TxnEnd
311-
{
308+
309+
use crate::parser::StmtKind;
310+
if matches!(
311+
s.kind,
312+
StmtKind::TxnBegin | StmtKind::TxnBeginReadOnly | StmtKind::TxnEnd
313+
) {
312314
return Err(Error::TransactionalBatchError(
313315
"Transactions forbidden inside transactional batch".to_string(),
314316
));
315317
}
318+
316319
stmts.push(Stmt::new(s.stmt, false));
317320
}
318321
let res = self

libsql/src/hrana/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
pub mod connection;
44

55
cfg_remote! {
6-
mod hyper;
6+
pub mod hyper;
77
}
88

99
mod cursor;

libsql/src/local/database.rs

Lines changed: 6 additions & 132 deletions
Original file line numberDiff line numberDiff line change
@@ -20,10 +20,11 @@ cfg_replication!(
2020

2121
cfg_sync! {
2222
use crate::sync::SyncContext;
23+
use tokio::sync::Mutex;
24+
use std::sync::Arc;
2325
}
2426

25-
use crate::{database::OpenFlags, local::connection::Connection};
26-
use crate::{Error::ConnectionFailed, Result};
27+
use crate::{database::OpenFlags, local::connection::Connection, Error::ConnectionFailed, Result};
2728
use libsql_sys::ffi;
2829

2930
// A libSQL database.
@@ -33,7 +34,7 @@ pub struct Database {
3334
#[cfg(feature = "replication")]
3435
pub replication_ctx: Option<ReplicationContext>,
3536
#[cfg(feature = "sync")]
36-
pub sync_ctx: Option<tokio::sync::Mutex<SyncContext>>,
37+
pub sync_ctx: Option<Arc<Mutex<SyncContext>>>,
3738
}
3839

3940
impl Database {
@@ -222,7 +223,7 @@ impl Database {
222223

223224
let sync_ctx =
224225
SyncContext::new(connector, db_path.into(), endpoint, Some(auth_token)).await?;
225-
db.sync_ctx = Some(tokio::sync::Mutex::new(sync_ctx));
226+
db.sync_ctx = Some(Arc::new(Mutex::new(sync_ctx)));
226227

227228
Ok(db)
228229
}
@@ -463,137 +464,10 @@ impl Database {
463464
#[cfg(feature = "sync")]
464465
/// Sync WAL frames to remote.
465466
pub async fn sync_offline(&self) -> Result<crate::database::Replicated> {
466-
use crate::sync::SyncError;
467-
use crate::Error;
468-
469467
let mut sync_ctx = self.sync_ctx.as_ref().unwrap().lock().await;
470468
let conn = self.connect()?;
471469

472-
let durable_frame_no = sync_ctx.durable_frame_num();
473-
let max_frame_no = conn.wal_frame_count();
474-
475-
if max_frame_no > durable_frame_no {
476-
match self.try_push(&mut sync_ctx, &conn).await {
477-
Ok(rep) => Ok(rep),
478-
Err(Error::Sync(err)) => {
479-
// Retry the sync because we are ahead of the server and we need to push some older
480-
// frames.
481-
if let Some(SyncError::InvalidPushFrameNoLow(_, _)) = err.downcast_ref() {
482-
tracing::debug!("got InvalidPushFrameNo, retrying push");
483-
self.try_push(&mut sync_ctx, &conn).await
484-
} else {
485-
Err(Error::Sync(err))
486-
}
487-
}
488-
Err(e) => Err(e),
489-
}
490-
} else {
491-
self.try_pull(&mut sync_ctx, &conn).await
492-
}
493-
.or_else(|err| {
494-
let Error::Sync(err) = err else {
495-
return Err(err);
496-
};
497-
498-
// TODO(levy): upcasting should be done *only* at the API boundary, doing this in
499-
// internal code just sucks.
500-
let Some(SyncError::HttpDispatch(_)) = err.downcast_ref() else {
501-
return Err(Error::Sync(err));
502-
};
503-
504-
Ok(crate::database::Replicated {
505-
frame_no: None,
506-
frames_synced: 0,
507-
})
508-
})
509-
}
510-
511-
#[cfg(feature = "sync")]
512-
async fn try_push(
513-
&self,
514-
sync_ctx: &mut SyncContext,
515-
conn: &Connection,
516-
) -> Result<crate::database::Replicated> {
517-
let page_size = {
518-
let rows = conn
519-
.query("PRAGMA page_size", crate::params::Params::None)?
520-
.unwrap();
521-
let row = rows.next()?.unwrap();
522-
let page_size = row.get::<u32>(0)?;
523-
page_size
524-
};
525-
526-
let max_frame_no = conn.wal_frame_count();
527-
if max_frame_no == 0 {
528-
return Ok(crate::database::Replicated {
529-
frame_no: None,
530-
frames_synced: 0,
531-
});
532-
}
533-
534-
let generation = sync_ctx.generation(); // TODO: Probe from WAL.
535-
let start_frame_no = sync_ctx.durable_frame_num() + 1;
536-
let end_frame_no = max_frame_no;
537-
538-
let mut frame_no = start_frame_no;
539-
while frame_no <= end_frame_no {
540-
let frame = conn.wal_get_frame(frame_no, page_size)?;
541-
542-
// The server returns its maximum frame number. To avoid resending
543-
// frames the server already knows about, we need to update the
544-
// frame number to the one returned by the server.
545-
let max_frame_no = sync_ctx
546-
.push_one_frame(frame.freeze(), generation, frame_no)
547-
.await?;
548-
549-
if max_frame_no > frame_no {
550-
frame_no = max_frame_no;
551-
}
552-
frame_no += 1;
553-
}
554-
555-
sync_ctx.write_metadata().await?;
556-
557-
// TODO(lucio): this can underflow if the server previously returned a higher max_frame_no
558-
// than what we have stored here.
559-
let frame_count = end_frame_no - start_frame_no + 1;
560-
Ok(crate::database::Replicated {
561-
frame_no: None,
562-
frames_synced: frame_count as usize,
563-
})
564-
}
565-
566-
#[cfg(feature = "sync")]
567-
async fn try_pull(
568-
&self,
569-
sync_ctx: &mut SyncContext,
570-
conn: &Connection,
571-
) -> Result<crate::database::Replicated> {
572-
let generation = sync_ctx.generation();
573-
let mut frame_no = sync_ctx.durable_frame_num() + 1;
574-
575-
let insert_handle = conn.wal_insert_handle()?;
576-
577-
loop {
578-
match sync_ctx.pull_one_frame(generation, frame_no).await {
579-
Ok(Some(frame)) => {
580-
insert_handle.insert(&frame)?;
581-
frame_no += 1;
582-
}
583-
Ok(None) => {
584-
sync_ctx.write_metadata().await?;
585-
return Ok(crate::database::Replicated {
586-
frame_no: None,
587-
frames_synced: 1,
588-
});
589-
}
590-
Err(err) => {
591-
tracing::debug!("pull_one_frame error: {:?}", err);
592-
sync_ctx.write_metadata().await?;
593-
return Err(err);
594-
}
595-
}
596-
}
470+
crate::sync::sync_offline(&mut sync_ctx, &conn).await
597471
}
598472

599473
pub(crate) fn path(&self) -> &str {

libsql/src/local/impls.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,7 @@ impl Drop for LibsqlConnection {
9191
}
9292
}
9393

94-
pub(crate) struct LibsqlStmt(pub(super) crate::local::Statement);
94+
pub(crate) struct LibsqlStmt(pub crate::local::Statement);
9595

9696
#[async_trait::async_trait]
9797
impl Stmt for LibsqlStmt {

libsql/src/params.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,13 @@ impl IntoParams for Params {
141141
}
142142
}
143143

144+
impl Sealed for &Params {}
145+
impl IntoParams for &Params {
146+
fn into_params(self) -> Result<Params> {
147+
Ok(self.clone())
148+
}
149+
}
150+
144151
impl<T: IntoValue> Sealed for Vec<T> {}
145152
impl<T: IntoValue> IntoParams for Vec<T> {
146153
fn into_params(self) -> Result<Params> {

0 commit comments

Comments
 (0)