use super::expr::Expr;
use crate::core::condition::SqlValue;
#[derive(Debug, Clone, Copy)]
pub struct Column<T, V> {
pub(crate) table: &'static str,
pub(crate) name: &'static str,
_table: std::marker::PhantomData<T>,
_value: std::marker::PhantomData<V>,
}
impl<T, V> Column<T, V> {
pub const fn new(table: &'static str, name: &'static str) -> Self {
Self {
table,
name,
_table: std::marker::PhantomData,
_value: std::marker::PhantomData,
}
}
pub fn qualified(&self) -> String {
format!("\"{}\".\"{}\"", self.table, self.name)
}
pub fn name(&self) -> &'static str {
self.name
}
}
impl<T, V: Into<SqlValue>> Column<T, V> {
pub fn eq(self, val: impl Into<SqlValue>) -> Expr {
Expr::Eq(self.qualified(), val.into())
}
pub fn ne(self, val: impl Into<SqlValue>) -> Expr {
Expr::Ne(self.qualified(), val.into())
}
pub fn gt(self, val: impl Into<SqlValue>) -> Expr {
Expr::Gt(self.qualified(), val.into())
}
pub fn gte(self, val: impl Into<SqlValue>) -> Expr {
Expr::Gte(self.qualified(), val.into())
}
pub fn lt(self, val: impl Into<SqlValue>) -> Expr {
Expr::Lt(self.qualified(), val.into())
}
pub fn lte(self, val: impl Into<SqlValue>) -> Expr {
Expr::Lte(self.qualified(), val.into())
}
pub fn like(self, pattern: impl Into<SqlValue>) -> Expr {
Expr::Like(self.qualified(), pattern.into())
}
pub fn not_like(self, pattern: impl Into<SqlValue>) -> Expr {
Expr::NotLike(self.qualified(), pattern.into())
}
pub fn ilike(self, pattern: impl Into<SqlValue>) -> Expr {
Expr::ILike(self.qualified(), pattern.into())
}
pub fn in_(self, vals: impl IntoIterator<Item = impl Into<SqlValue>>) -> Expr {
Expr::In(self.qualified(), vals.into_iter().map(Into::into).collect())
}
pub fn not_in(self, vals: impl IntoIterator<Item = impl Into<SqlValue>>) -> Expr {
Expr::NotIn(self.qualified(), vals.into_iter().map(Into::into).collect())
}
pub fn eq_any(self, vals: impl IntoIterator<Item = impl Into<SqlValue>>) -> Expr {
Expr::EqAny(self.qualified(), vals.into_iter().map(Into::into).collect())
}
pub fn between(self, lo: impl Into<SqlValue>, hi: impl Into<SqlValue>) -> Expr {
Expr::Between(self.qualified(), lo.into(), hi.into())
}
pub fn not_between(self, lo: impl Into<SqlValue>, hi: impl Into<SqlValue>) -> Expr {
Expr::NotBetween(self.qualified(), lo.into(), hi.into())
}
}
impl<T, V> Column<T, V> {
pub fn is_null(self) -> Expr {
Expr::IsNull(self.qualified())
}
pub fn is_not_null(self) -> Expr {
Expr::IsNotNull(self.qualified())
}
pub fn asc(self) -> OrderExpr {
OrderExpr {
col: self.qualified(),
dir: OrderDir::Asc,
nulls: NullsOrder::Default,
}
}
pub fn desc(self) -> OrderExpr {
OrderExpr {
col: self.qualified(),
dir: OrderDir::Desc,
nulls: NullsOrder::Default,
}
}
pub fn asc_nulls_last(self) -> OrderExpr {
OrderExpr {
col: self.qualified(),
dir: OrderDir::Asc,
nulls: NullsOrder::Last,
}
}
pub fn asc_nulls_first(self) -> OrderExpr {
OrderExpr {
col: self.qualified(),
dir: OrderDir::Asc,
nulls: NullsOrder::First,
}
}
pub fn desc_nulls_last(self) -> OrderExpr {
OrderExpr {
col: self.qualified(),
dir: OrderDir::Desc,
nulls: NullsOrder::Last,
}
}
pub fn desc_nulls_first(self) -> OrderExpr {
OrderExpr {
col: self.qualified(),
dir: OrderDir::Desc,
nulls: NullsOrder::First,
}
}
pub fn references<T2, V2>(self, other: Column<T2, V2>) -> Expr {
Expr::ColEq(self.qualified(), other.qualified())
}
pub fn eq_col<T2, V2>(self, other: Column<T2, V2>) -> Expr {
Expr::ColEq(self.qualified(), other.qualified())
}
pub fn in_subquery(self, subquery_sql: impl Into<String>) -> Expr {
Expr::InSubquery(self.qualified(), subquery_sql.into())
}
pub fn not_in_subquery(self, subquery_sql: impl Into<String>) -> Expr {
Expr::NotInSubquery(self.qualified(), subquery_sql.into())
}
}
impl<T, V> Column<T, V> {
pub fn lower(self) -> FnExpr {
FnExpr::new(format!("LOWER({})", self.qualified()))
}
pub fn upper(self) -> FnExpr {
FnExpr::new(format!("UPPER({})", self.qualified()))
}
pub fn length(self) -> FnExpr {
FnExpr::new(format!("LENGTH({})", self.qualified()))
}
pub fn trim(self) -> FnExpr {
FnExpr::new(format!("TRIM({})", self.qualified()))
}
pub fn coalesce(self, fallback_sql: impl Into<String>) -> FnExpr {
FnExpr::new(format!(
"COALESCE({}, {})",
self.qualified(),
fallback_sql.into()
))
}
pub fn cast_as(self, type_sql: impl Into<String>) -> FnExpr {
FnExpr::new(format!("CAST({} AS {})", self.qualified(), type_sql.into()))
}
pub fn date_trunc(self, unit: impl Into<String>) -> FnExpr {
FnExpr::new(format!(
"DATE_TRUNC('{}', {})",
unit.into(),
self.qualified()
))
}
pub fn extract(self, part: impl Into<String>) -> FnExpr {
FnExpr::new(format!(
"EXTRACT({} FROM {})",
part.into(),
self.qualified()
))
}
}
impl<T, V> Column<T, V> {
pub fn count(self) -> AggExpr {
AggExpr::new(format!("COUNT({})", self.qualified()))
}
pub fn count_distinct(self) -> AggExpr {
AggExpr::new(format!("COUNT(DISTINCT {})", self.qualified()))
}
pub fn sum(self) -> AggExpr {
AggExpr::new(format!("SUM({})", self.qualified()))
}
pub fn avg(self) -> AggExpr {
AggExpr::new(format!("AVG({})", self.qualified()))
}
pub fn min(self) -> AggExpr {
AggExpr::new(format!("MIN({})", self.qualified()))
}
pub fn max(self) -> AggExpr {
AggExpr::new(format!("MAX({})", self.qualified()))
}
}
#[derive(Debug, Clone)]
pub struct AggExpr {
pub(crate) sql: String,
alias: Option<String>,
}
impl AggExpr {
pub(crate) fn new(sql: String) -> Self {
Self { sql, alias: None }
}
#[must_use]
pub fn alias(mut self, name: impl Into<String>) -> Self {
self.alias = Some(name.into());
self
}
pub fn to_projection_sql(&self) -> String {
match &self.alias {
Some(a) => format!("{} AS \"{}\"", self.sql, a),
None => self.sql.clone(),
}
}
pub fn gt(self, val: impl Into<SqlValue>) -> Expr {
Expr::AggCmp(self.sql, ">", val.into())
}
pub fn gte(self, val: impl Into<SqlValue>) -> Expr {
Expr::AggCmp(self.sql, ">=", val.into())
}
pub fn lt(self, val: impl Into<SqlValue>) -> Expr {
Expr::AggCmp(self.sql, "<", val.into())
}
pub fn lte(self, val: impl Into<SqlValue>) -> Expr {
Expr::AggCmp(self.sql, "<=", val.into())
}
pub fn eq(self, val: impl Into<SqlValue>) -> Expr {
Expr::AggCmp(self.sql, "=", val.into())
}
pub fn ne(self, val: impl Into<SqlValue>) -> Expr {
Expr::AggCmp(self.sql, "!=", val.into())
}
}
#[derive(Debug, Clone)]
pub struct OrderExpr {
pub(crate) col: String,
pub(crate) dir: OrderDir,
pub(crate) nulls: NullsOrder,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum OrderDir {
Asc,
Desc,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum NullsOrder {
Default,
First,
Last,
}
impl OrderExpr {
pub fn to_sql(&self) -> String {
let dir = match self.dir {
OrderDir::Asc => "ASC",
OrderDir::Desc => "DESC",
};
let nulls = match self.nulls {
NullsOrder::Default => String::new(),
NullsOrder::First => " NULLS FIRST".to_string(),
NullsOrder::Last => " NULLS LAST".to_string(),
};
format!("{} {}{}", self.col, dir, nulls)
}
}
#[derive(Debug, Clone)]
pub struct FnExpr {
pub(crate) sql: String,
alias: Option<String>,
}
impl FnExpr {
pub(crate) fn new(sql: String) -> Self {
Self { sql, alias: None }
}
#[must_use]
pub fn alias(mut self, name: impl Into<String>) -> Self {
self.alias = Some(name.into());
self
}
pub fn to_projection_sql(&self) -> String {
match &self.alias {
Some(a) => format!("{} AS \"{}\"", self.sql, a),
None => self.sql.clone(),
}
}
pub fn eq(self, val: impl Into<crate::core::condition::SqlValue>) -> super::expr::Expr {
super::expr::Expr::AggCmp(self.sql, "=", val.into())
}
pub fn ne(self, val: impl Into<crate::core::condition::SqlValue>) -> super::expr::Expr {
super::expr::Expr::AggCmp(self.sql, "!=", val.into())
}
pub fn like(self, pattern: impl Into<crate::core::condition::SqlValue>) -> super::expr::Expr {
super::expr::Expr::AggCmp(self.sql, "LIKE", pattern.into())
}
pub fn ilike(self, pattern: impl Into<crate::core::condition::SqlValue>) -> super::expr::Expr {
super::expr::Expr::AggCmp(self.sql, "ILIKE", pattern.into())
}
pub fn gt(self, val: impl Into<crate::core::condition::SqlValue>) -> super::expr::Expr {
super::expr::Expr::AggCmp(self.sql, ">", val.into())
}
pub fn lt(self, val: impl Into<crate::core::condition::SqlValue>) -> super::expr::Expr {
super::expr::Expr::AggCmp(self.sql, "<", val.into())
}
}