use crate::query::BoolExpr;
use crate::{migrations::adb, Error, Result, SqlVal, SqlValRef};
use serde::{Deserialize, Serialize};
use std::borrow::Cow;
use std::fs;
use std::io::Write;
use std::ops::{Deref, DerefMut};
use std::path::Path;
mod connmethods;
mod helper;
mod macros;
#[cfg(feature = "pg")]
pub mod pg;
#[cfg(feature = "sqlite")]
pub mod sqlite;
#[cfg(feature = "r2d2")]
mod r2;
#[cfg(feature = "r2d2")]
pub use r2::ConnectionManager;
use crate::connection_method_wrapper;
pub use connmethods::{
BackendRow, BackendRows, Column, ConnectionMethods, QueryResult, RawQueryResult,
};
pub trait BackendConnection: ConnectionMethods + Send + 'static {
fn transaction(&mut self) -> Result<Transaction>;
fn backend(&self) -> Box<dyn Backend>;
fn backend_name(&self) -> &'static str;
fn is_closed(&self) -> bool;
}
pub struct Connection {
conn: Box<dyn BackendConnection>,
}
impl Connection {
pub fn execute(&mut self, sql: impl AsRef<str>) -> Result<()> {
self.conn.execute(sql.as_ref())
}
#[allow(clippy::unnecessary_wraps)]
fn wrapped_connection_methods(&self) -> Result<&dyn BackendConnection> {
Ok(self.conn.as_ref())
}
}
impl BackendConnection for Connection {
fn transaction(&mut self) -> Result<Transaction> {
self.conn.transaction()
}
fn backend(&self) -> Box<dyn Backend> {
self.conn.backend()
}
fn backend_name(&self) -> &'static str {
self.conn.backend_name()
}
fn is_closed(&self) -> bool {
self.conn.is_closed()
}
}
connection_method_wrapper!(Connection);
#[derive(Serialize, Deserialize)]
pub struct ConnectionSpec {
pub backend_name: String,
pub conn_str: String,
}
impl ConnectionSpec {
pub fn new(backend_name: impl Into<String>, conn_str: impl Into<String>) -> Self {
ConnectionSpec {
backend_name: backend_name.into(),
conn_str: conn_str.into(),
}
}
pub fn save(&self, path: &Path) -> Result<()> {
let path = conn_complete_if_dir(path);
let mut f = fs::File::create(path)?;
f.write_all(serde_json::to_string(self)?.as_bytes())
.map_err(|e| e.into())
}
pub fn load(path: impl AsRef<Path>) -> Result<Self> {
let path = conn_complete_if_dir(path.as_ref());
serde_json::from_reader(fs::File::open(path)?).map_err(|e| e.into())
}
pub fn get_backend(&self) -> Result<Box<dyn Backend>> {
match get_backend(&self.backend_name) {
Some(backend) => Ok(backend),
None => Err(crate::Error::UnknownBackend(self.backend_name.clone())),
}
}
}
fn conn_complete_if_dir(path: &Path) -> Cow<Path> {
if path.is_dir() {
Cow::from(path.join("connection.json"))
} else {
Cow::from(path)
}
}
pub trait Backend {
fn name(&self) -> &'static str;
fn create_migration_sql(&self, current: &adb::ADB, ops: Vec<adb::Operation>) -> Result<String>;
fn connect(&self, conn_str: &str) -> Result<Connection>;
}
impl Backend for Box<dyn Backend> {
fn name(&self) -> &'static str {
self.deref().name()
}
fn create_migration_sql(&self, current: &adb::ADB, ops: Vec<adb::Operation>) -> Result<String> {
self.deref().create_migration_sql(current, ops)
}
fn connect(&self, conn_str: &str) -> Result<Connection> {
self.deref().connect(conn_str)
}
}
pub fn get_backend(name: &str) -> Option<Box<dyn Backend>> {
match name {
#[cfg(feature = "sqlite")]
sqlite::BACKEND_NAME => Some(Box::new(sqlite::SQLiteBackend::new())),
#[cfg(feature = "pg")]
pg::BACKEND_NAME => Some(Box::new(pg::PgBackend::new())),
_ => None,
}
}
pub fn connect(spec: &ConnectionSpec) -> Result<Connection> {
get_backend(&spec.backend_name)
.ok_or_else(|| Error::UnknownBackend(spec.backend_name.clone()))?
.connect(&spec.conn_str)
}
trait BackendTransaction<'c>: ConnectionMethods {
fn commit(&mut self) -> Result<()>;
fn rollback(&mut self) -> Result<()>;
fn connection_methods(&self) -> &dyn ConnectionMethods;
fn connection_methods_mut(&mut self) -> &mut dyn ConnectionMethods;
}
pub struct Transaction<'c> {
trans: Box<dyn BackendTransaction<'c> + 'c>,
}
impl<'c> Transaction<'c> {
#[allow(unused)]
fn new(trans: Box<dyn BackendTransaction<'c> + 'c>) -> Self {
Transaction { trans }
}
pub fn commit(mut self) -> Result<()> {
self.trans.deref_mut().commit()
}
pub fn rollback(mut self) -> Result<()> {
self.trans.deref_mut().rollback()
}
#[allow(clippy::unnecessary_wraps)]
fn wrapped_connection_methods(&self) -> Result<&dyn ConnectionMethods> {
let a: &dyn BackendTransaction<'c> = self.trans.as_ref();
Ok(a.connection_methods())
}
}
connection_method_wrapper!(Transaction<'_>);