#![warn(warnings)]
#![doc(html_logo_url = "https://elephantry.github.io/logo.png")]
#![cfg_attr(docsrs, feature(doc_cfg))]
pub mod config;
pub mod connection;
pub mod entity;
pub mod from_sql;
#[cfg(feature = "inspect")]
pub mod inspect;
pub mod pq;
#[cfg(feature = "r2d2")]
pub mod r2d2;
#[cfg(feature = "rocket")]
#[doc(hidden)]
pub mod rocket;
pub mod to_sql;
pub mod transaction;
mod r#async;
mod errors;
mod from_text;
mod model;
mod notify;
mod pager;
mod pool;
mod projectable;
mod projection;
mod rows;
mod sql;
mod structure;
mod to_text;
mod tuple;
mod r#where;
pub use crate::config::Config;
pub use r#async::*;
pub use connection::Connection;
pub use elephantry_derive::*;
pub use entity::Entity;
pub use errors::*;
pub use from_sql::FromSql;
pub use from_text::*;
pub use model::*;
pub use notify::Notify;
pub use pager::*;
pub use pool::*;
pub use projectable::*;
pub use projection::*;
pub use rows::*;
pub use sql::*;
pub use structure::*;
pub use to_sql::ToSql;
pub use to_text::*;
pub use transaction::Transaction;
pub use tuple::*;
pub use r#where::Where;
macro_rules! regex {
($regex:literal) => {{
static REGEX: std::sync::LazyLock<regex::Regex> =
std::sync::LazyLock::new(|| regex::Regex::new($regex).unwrap());
®EX
}};
}
pub(crate) use regex;
#[macro_export]
macro_rules! pk {
($($pk:ident),+ $(,)?) => {
$crate::pk!($(
$pk => $pk,
)*)
};
($($key:expr => $value:expr),+ $(,)?) => {{
let mut hash = std::collections::HashMap::new();
$(
hash.insert(stringify!($key), &$value as &dyn $crate::ToSql);
)*
hash
}}
}
#[macro_export]
macro_rules! values {
($($pk:ident),+ $(,)?) => {
$crate::values!($(
$pk => $pk,
)*)
};
($($key:expr => $value:expr),+ $(,)?) => {{
let mut hash = std::collections::HashMap::new();
$(
hash.insert(stringify!($key).to_string(), &$value as &dyn $crate::ToSql);
)*
hash
}}
}
#[cfg(test)]
mod test {
use std::collections::HashMap;
#[macro_export]
macro_rules! sql_test_from {
($sql_type:ident, $rust_type:ty, $tests:expr) => {
#[test]
fn from_text() -> $crate::Result {
$crate::test::from_text::<$rust_type>(stringify!($sql_type), &$tests)
}
#[test]
fn from_binary() -> $crate::Result {
$crate::test::from_binary::<$rust_type>(stringify!($sql_type), &$tests)
}
};
}
pub(crate) fn from_text<T>(sql_type: &str, tests: &[(&str, T)]) -> crate::Result
where
T: crate::FromSql + crate::ToSql + PartialEq + std::fmt::Debug,
{
let conn = crate::test::new_conn()?;
for (value, expected) in tests {
let result = conn.execute(&format!("select {value}::{sql_type} as actual"))?;
assert_eq!(result.get(0).get::<T>("actual"), *expected, "from_text");
}
Ok(())
}
pub(crate) fn from_binary<T>(sql_type: &str, tests: &[(&str, T)]) -> crate::Result
where
T: crate::FromSql + crate::ToSql + PartialEq + std::fmt::Debug,
{
let conn = crate::test::new_conn()?;
for (value, expected) in tests {
let result = conn.query::<HashMap<String, T>>(
&format!("select {value}::{sql_type} as actual"),
&[],
)?;
assert_eq!(
result.get(0).get("actual").unwrap(),
expected,
"from_binary"
);
}
Ok(())
}
#[macro_export]
macro_rules! sql_test_to {
($sql_type:ident, $rust_type:ty, $tests:expr) => {
#[test]
fn to_text() -> $crate::Result {
$crate::test::to_text::<$rust_type>(stringify!($sql_type), &$tests)
}
#[test]
fn to_binary() -> $crate::Result {
$crate::test::to_binary::<$rust_type>(stringify!($sql_type), &$tests)
}
};
}
pub(crate) fn to_text<T>(sql_type: &str, tests: &[(&str, T)]) -> crate::Result
where
T: crate::Entity + crate::ToSql + PartialEq + std::fmt::Debug,
{
let conn = crate::test::new_conn()?;
for (_, value) in tests {
let result = conn.query::<T>(&format!("select $1::{sql_type}"), &[value]);
assert!(dbg!(&result).is_ok());
assert_eq!(&result.unwrap().get(0), value, "to_text");
}
Ok(())
}
pub(crate) fn to_binary<T>(sql_type: &str, tests: &[(&str, T)]) -> crate::Result
where
T: crate::Entity + crate::ToSql + PartialEq + std::fmt::Debug,
{
let conn = crate::test::new_conn()?;
for (_, value) in tests {
let result: crate::pq::Result = conn
.connection
.lock()
.map_err(|e| crate::Error::Mutex(e.to_string()))?
.exec_params(
&format!("select $1::{sql_type}"),
&[value.ty().oid],
&[value.to_binary()?.as_deref()],
&[crate::pq::Format::Binary],
crate::pq::Format::Binary,
)
.try_into()?;
let rows: crate::Rows<T> = result.into();
assert_eq!(&rows.get(0), value, "to_binary");
}
Ok(())
}
#[macro_export]
macro_rules! sql_test {
($sql_type:ident, $rust_type:ty, $tests:expr) => {
mod $sql_type {
$crate::sql_test_from!($sql_type, $rust_type, $tests);
$crate::sql_test_to!($sql_type, $rust_type, $tests);
}
};
}
pub fn dsn() -> String {
std::env::var("DATABASE_URL").unwrap_or_else(|_| "host=localhost".to_string())
}
pub fn new_conn() -> crate::Result<&'static crate::Connection> {
static INIT: std::sync::Once = std::sync::Once::new();
INIT.call_once(|| {
env_logger::init();
});
static POOL: std::sync::LazyLock<crate::Result<crate::Pool>> =
std::sync::LazyLock::new(|| {
let pool = crate::Pool::new(&dsn())?;
pool.execute("create extension if not exists hstore")?;
pool.execute("create extension if not exists ltree")?;
pool.execute("set lc_monetary to 'en_US.UTF-8';")?;
pool.execute(
"
do $$
begin
if not exists (select 1 from pg_type where typname = 'compfoo')
then
create type compfoo as (f1 int, f2 text);
end if;
if not exists (select 1 from pg_type where typname = 'mood')
then
create type mood as enum ('Sad', 'Ok', 'Happy');
end if;
if not exists (select 1 from pg_type where typname = 'us_postal_code')
then
create domain us_postal_code as text
check(
value ~ '^\\d{5}$'
or value ~ '^\\d{5}-\\d{4}$'
);
end if;
end$$;
",
)?;
Ok(pool)
});
Ok(POOL.as_ref().unwrap())
}
#[test]
fn test_pk_one() {
let uuid = "1234";
let pk = crate::pk!(uuid);
assert_eq!(pk.len(), 1);
assert!(pk.contains_key("uuid"));
}
#[test]
fn test_pk_multi() {
let uuid = "1234";
let name = "name";
let pk = crate::pk![uuid, name,];
assert_eq!(pk.len(), 2);
assert!(pk.contains_key("uuid"));
assert!(pk.contains_key("name"));
}
#[test]
fn test_pk_hash() {
let pk = crate::pk! {
uuid => "1234",
name => "name",
};
assert_eq!(pk.len(), 2);
assert!(pk.contains_key("uuid"));
assert!(pk.contains_key("name"));
}
#[allow(dead_code)]
#[derive(elephantry_derive::Entity)]
#[elephantry(model = "Model", structure = "Structure", relation = "entity")]
pub struct Entity {
#[elephantry(pk, column = "employee_id")]
pub id: i32,
pub first_name: String,
#[elephantry(default)]
pub last_name: String,
}
}