use tokio::sync::{mpsc, oneshot};
fn create_tables(conn: &sqlite::Connection) -> sqlite::Result<()> {
conn.execute("CREATE TABLE IF NOT EXISTS names (name TEXT)")?;
conn.execute(
"CREATE TABLE IF NOT EXISTS preferences (
better INTEGER,
worse INTEGER,
FOREIGN KEY (better) REFERENCES names(rowid) ON DELETE CASCADE,
FOREIGN KEY (worse) REFERENCES names(rowid) ON DELETE CASCADE,
UNIQUE(better,worse))",
)?;
Ok(())
}
struct AsyncConnection(
mpsc::Sender<Box<dyn for<'a> FnOnce(&'a sqlite::Connection) + Send>>,
);
impl AsyncConnection {
pub async fn open<T: AsRef<std::path::Path> + Send + 'static>(
path: T,
) -> Result<Self, Box<dyn std::error::Error>> {
let (tx, mut rx): (
_,
mpsc::Receiver<Box<dyn for<'a> FnOnce(&sqlite::Connection) + Send>>,
) = mpsc::channel(1);
std::thread::Builder::new()
.name(format!("database thread: {}", path.as_ref().display()))
.spawn(move || {
let conn = sqlite::open(path).unwrap();
while let Some(cb) = rx.blocking_recv() {
cb(&conn);
}
})?;
let result = Self(tx);
result.post(create_tables).await??;
Ok(result)
}
async fn post<
F: FnOnce(&sqlite::Connection) -> U + Send + 'static,
U: Send + 'static,
>(
&self,
cb: F,
) -> Result<U, Box<dyn std::error::Error>> {
let (sc, rc): (_, oneshot::Receiver<U>) = oneshot::channel();
self.0
.send(Box::new(move |conn: &sqlite::Connection| {
sc.send(cb(conn)).unwrap_or(());
})
as Box<dyn for<'a> FnOnce(&'a sqlite::Connection) + Send>)
.await
.map_err(|mpsc::error::SendError(_)| mpsc::error::SendError(()))?;
Ok(rc.await?)
}
}