siderite_backends/sqlite/
group.rs1use super::gate::WriteGate;
23use super::run_write;
24use crate::shared::map_error;
25use crate::sql::CompiledQuery;
26use siderite_orm::{BackendError, ExecResult, OrmError};
27use sqlx::TransactionManager;
28use sqlx::sqlite::{SqliteConnection, SqlitePool, SqliteTransactionManager};
29use tokio::sync::{mpsc, oneshot};
30
31#[derive(Debug, Clone, Copy, PartialEq, Eq)]
34pub struct GroupCommit {
35 pub max_batch: usize,
37 pub queue: usize,
39}
40
41impl Default for GroupCommit {
42 fn default() -> Self {
43 Self {
44 max_batch: 64,
45 queue: 1024,
46 }
47 }
48}
49
50type Reply = Result<ExecResult, OrmError>;
51
52struct Job {
53 compiled: CompiledQuery,
54 returning: bool,
55 reply: oneshot::Sender<Reply>,
56}
57
58#[derive(Debug, Clone)]
60pub(super) struct Committer {
61 jobs: mpsc::Sender<Job>,
62}
63
64impl Committer {
65 pub(super) fn spawn(pool: SqlitePool, gate: WriteGate, config: GroupCommit) -> Self {
67 let max_batch = config.max_batch.max(1);
68 let (jobs, queue) = mpsc::channel(config.queue.max(1));
69 tokio::spawn(run(pool, gate, queue, max_batch));
70 Self { jobs }
71 }
72
73 pub(super) async fn submit(&self, compiled: CompiledQuery, returning: bool) -> Reply {
75 let (reply, answer) = oneshot::channel();
76 let job = Job {
77 compiled,
78 returning,
79 reply,
80 };
81 self.jobs.send(job).await.map_err(|_| stopped())?;
82 answer.await.map_err(|_| stopped())?
83 }
84}
85
86fn stopped() -> OrmError {
87 BackendError::Database("SQLite group committer stopped".into()).into()
88}
89
90async fn run(pool: SqlitePool, gate: WriteGate, mut queue: mpsc::Receiver<Job>, max: usize) {
91 let mut batch = Vec::with_capacity(max);
92 while queue.recv_many(&mut batch, max).await > 0 {
93 batch.retain(|job| !job.reply.is_closed());
96 if batch.is_empty() {
97 continue;
98 }
99 let replies = match gate.acquire().await {
100 Ok(_permit) => match pool.acquire().await {
101 Ok(mut conn) => write(&mut conn, &mut batch).await,
102 Err(err) => fail_all(batch.len(), &OrmError::from(map_error(err))),
103 },
104 Err(err) => fail_all(batch.len(), &err),
105 };
106 for (job, reply) in batch.drain(..).zip(replies) {
107 drop(job.reply.send(reply));
108 }
109 }
110}
111
112async fn write(conn: &mut SqliteConnection, batch: &mut [Job]) -> Vec<Reply> {
113 if let [job] = batch {
114 let compiled = take(&mut job.compiled);
115 return vec![run_write(&mut *conn, compiled, job.returning).await];
116 }
117 if let Err(err) = SqliteTransactionManager::begin(conn, Some("BEGIN IMMEDIATE".into())).await {
118 return fail_all(batch.len(), &OrmError::from(map_error(err)));
119 }
120 let mut replies = Vec::with_capacity(batch.len());
121 for job in batch.iter_mut() {
122 if let Err(err) = savepoint(conn, "SAVEPOINT siderite_group").await {
123 return abort(conn, batch.len(), &err).await;
124 }
125 let compiled = take(&mut job.compiled);
126 let reply = run_write(&mut *conn, compiled, job.returning).await;
127 let close = if reply.is_ok() {
128 savepoint(conn, "RELEASE siderite_group").await
129 } else {
130 match savepoint(conn, "ROLLBACK TO siderite_group").await {
132 Ok(()) => savepoint(conn, "RELEASE siderite_group").await,
133 Err(err) => Err(err),
134 }
135 };
136 if let Err(err) = close {
137 return abort(conn, batch.len(), &err).await;
138 }
139 replies.push(reply);
140 }
141 match SqliteTransactionManager::commit(conn).await {
142 Ok(()) => replies,
143 Err(err) => abort(conn, batch.len(), &OrmError::from(map_error(err))).await,
144 }
145}
146
147async fn abort(conn: &mut SqliteConnection, len: usize, err: &OrmError) -> Vec<Reply> {
149 drop(SqliteTransactionManager::rollback(conn).await);
150 fail_all(len, err)
151}
152
153fn fail_all(len: usize, err: &OrmError) -> Vec<Reply> {
154 let message = format!("SQLite group commit failed: {err}");
155 (0..len)
156 .map(|_| Err(BackendError::Database(message.clone()).into()))
157 .collect()
158}
159
160async fn savepoint(conn: &mut SqliteConnection, sql: &str) -> Result<(), OrmError> {
161 sqlx::Executor::execute(&mut *conn, sql)
162 .await
163 .map(|_| ())
164 .map_err(|e| map_error(e).into())
165}
166
167fn take(compiled: &mut CompiledQuery) -> CompiledQuery {
168 CompiledQuery {
169 sql: std::mem::take(&mut compiled.sql),
170 params: std::mem::take(&mut compiled.params),
171 }
172}