use crate::Error;
use anyhow::Result;
use libsql::{Builder, Database as LibsqlDatabase, Rows};
use serde::{Deserialize, Serialize};
use tracing::debug;
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct DatabaseConfig {
pub mode: TursoMode,
pub local_db_path: String,
pub db_url: String,
pub db_token: String,
}
impl DatabaseConfig {
pub fn new(mode: TursoMode, local_db_path: String, db_url: String, db_token: String) -> Self {
Self {
mode,
local_db_path,
db_url,
db_token,
}
}
pub fn memory() -> Self {
Self {
mode: TursoMode::Memory,
local_db_path: String::new(),
db_url: String::new(),
db_token: String::new(),
}
}
pub fn local(db_path: impl Into<String>) -> Self {
Self {
mode: TursoMode::Local,
local_db_path: db_path.into(),
db_url: String::new(),
db_token: String::new(),
}
}
pub fn remote(db_url: impl Into<String>, db_token: impl Into<String>) -> Self {
Self {
mode: TursoMode::Remote,
local_db_path: String::new(),
db_url: db_url.into(),
db_token: db_token.into(),
}
}
pub fn sync(
local_db_path: impl Into<String>,
db_url: impl Into<String>,
db_token: impl Into<String>,
) -> Self {
Self {
mode: TursoMode::Sync,
local_db_path: local_db_path.into(),
db_url: db_url.into(),
db_token: db_token.into(),
}
}
pub fn embed(
local_db_path: impl Into<String>,
db_url: impl Into<String>,
db_token: impl Into<String>,
) -> Self {
Self {
mode: TursoMode::Embed,
local_db_path: local_db_path.into(),
db_url: db_url.into(),
db_token: db_token.into(),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub enum TursoMode {
Memory,
Local,
Sync,
Remote,
Embed,
}
#[derive(Debug)]
pub struct Database {
pub db: libsql::Database,
pub conn: libsql::Connection,
pub mode: TursoMode,
}
impl TursoMode {
pub fn from_env() -> Self {
dotenv::dotenv().ok();
match std::env::var("TURSO_MODE")
.unwrap_or_else(|_| "local".to_string())
.to_lowercase()
.as_str()
{
"local" => TursoMode::Local,
"sync" => TursoMode::Sync,
"remote" => TursoMode::Remote,
"embed" => TursoMode::Embed,
_ => TursoMode::Local, }
}
}
impl Database {
pub async fn init(config: DatabaseConfig) -> Result<Self> {
let db = Self::client(config.clone()).await?;
let conn = db.connect().map_err(|e| Error::Connection(e))?;
let mode = config.mode;
conn.execute("PRAGMA foreign_keys = ON", ())
.await
.map_err(|e| Error::Connection(e))?;
debug!("Turso database connection established with foreign keys enabled");
Ok(Self { db, conn, mode })
}
async fn client(config: DatabaseConfig) -> Result<LibsqlDatabase, Error> {
let local_db_path = config.local_db_path;
let db_url = config.db_url;
let db_token = config.db_token;
let mode = config.mode;
let db = match mode {
TursoMode::Memory => Builder::new_local(":memory:")
.build()
.await
.map_err(|e| Error::Connection(e))?,
TursoMode::Local => Builder::new_local(&local_db_path)
.build()
.await
.map_err(|e| Error::Connection(e))?,
TursoMode::Sync => Builder::new_synced_database(local_db_path, db_url, db_token)
.build()
.await
.map_err(|e| Error::Connection(e))?,
TursoMode::Remote => Builder::new_remote(db_url, db_token)
.build()
.await
.map_err(|e| Error::Connection(e))?,
TursoMode::Embed => Builder::new_remote_replica(local_db_path, db_url, db_token)
.build()
.await
.map_err(|e| Error::Connection(e))?,
};
Ok(db)
}
pub async fn sync(&self) -> Result<()> {
if self.mode == TursoMode::Sync {
self.db.sync().await.map_err(|e| Error::Connection(e))?;
debug!("Turso database synced successfully");
}
Ok(())
}
pub async fn query(
&self,
sql: &str,
params: Vec<libsql::Value>,
) -> Result<Rows, libsql::Error> {
self.conn.query(sql, params).await
}
pub async fn execute(&self, sql: &str) -> Result<u64, libsql::Error> {
self.conn.execute(sql, ()).await
}
}