use crate::error::CandyError;
use postgres::{Client, NoTls, Row};
use std::collections::HashMap;
pub struct PostgresConn {
pub(crate) client: Client,
}
pub struct PostgresResult {
pub(crate) rows: Vec<Row>,
}
pub fn postgres_connect(
host: &str,
user: &str,
pass: &str,
db: &str,
) -> Result<PostgresConn, CandyError> {
let url = format!(
"host={} user={} password={} dbname={}",
host, user, pass, db
);
postgres_connect_url(&url)
}
pub fn postgres_connect_url(url: &str) -> Result<PostgresConn, CandyError> {
let client = Client::connect(url, NoTls).map_err(|e| CandyError::Connection(e.to_string()))?;
Ok(PostgresConn { client })
}
pub fn postgres_query(conn: &mut PostgresConn, sql: &str) -> Result<PostgresResult, CandyError> {
let rows = conn
.client
.query(sql, &[])
.map_err(|e| CandyError::Query(e.to_string()))?;
Ok(PostgresResult { rows })
}
pub fn postgres_fetch_all(res: PostgresResult) -> Result<Vec<HashMap<String, String>>, CandyError> {
res.rows.iter().map(row_to_map).collect()
}
pub fn postgres_fetch_one(res: PostgresResult) -> Result<HashMap<String, String>, CandyError> {
match res.rows.first() {
Some(row) => row_to_map(row),
None => Err(CandyError::Fetch("Result set is empty".into())),
}
}
pub fn postgres_exec(conn: &mut PostgresConn, sql: &str) -> Result<u64, CandyError> {
let n = conn
.client
.execute(sql, &[])
.map_err(|e| CandyError::Query(e.to_string()))?;
Ok(n)
}
pub fn postgres_transaction(conn: &mut PostgresConn, queries: &[&str]) -> Result<(), CandyError> {
let mut tx = conn
.client
.transaction()
.map_err(|e| CandyError::Transaction(e.to_string()))?;
for sql in queries {
if let Err(e) = tx.execute(*sql, &[]) {
let _ = tx.rollback();
return Err(CandyError::Transaction(e.to_string()));
}
}
tx.commit()
.map_err(|e| CandyError::Transaction(e.to_string()))
}
fn row_to_map(row: &Row) -> Result<HashMap<String, String>, CandyError> {
let mut map = HashMap::new();
for (i, col) in row.columns().iter().enumerate() {
let s = pg_col_to_string(row, i, col.type_()).map_err(|e| CandyError::Fetch(e))?;
map.insert(col.name().to_string(), s);
}
Ok(map)
}
fn pg_col_to_string(row: &Row, idx: usize, ty: &postgres::types::Type) -> Result<String, String> {
use postgres::types::Type;
macro_rules! try_get {
($t:ty) => {{
let v: Option<$t> = row.try_get(idx).map_err(|e| e.to_string())?;
return Ok(v
.map(|x| x.to_string())
.unwrap_or_else(|| "NULL".to_string()));
}};
}
match *ty {
Type::BOOL => try_get!(bool),
Type::INT2 => try_get!(i16),
Type::INT4 => try_get!(i32),
Type::INT8 => try_get!(i64),
Type::FLOAT4 => try_get!(f32),
Type::FLOAT8 => try_get!(f64),
Type::TEXT | Type::VARCHAR | Type::BPCHAR | Type::NAME => try_get!(String),
Type::BYTEA => {
let v: Option<Vec<u8>> = row.try_get(idx).map_err(|e| e.to_string())?;
Ok(v.map(|b| format!("{:?}", b))
.unwrap_or_else(|| "NULL".to_string()))
}
_ => {
let v: Option<String> = row.try_get(idx).ok().flatten();
Ok(v.unwrap_or_else(|| "NULL".to_string()))
}
}
}