use crate::column::ColumnMarker;
use crate::error::{Error, Result};
use crate::expr::Expr;
#[derive(Debug, Clone, PartialEq)]
pub enum SelectItem {
Column(ColumnMarker),
Computed {
expr: Expr,
alias: String,
},
}
impl SelectItem {
pub fn column(marker: ColumnMarker) -> Self {
SelectItem::Column(marker)
}
pub fn computed(expr: Expr, alias: impl Into<String>) -> Self {
SelectItem::Computed {
expr,
alias: alias.into(),
}
}
}
impl From<ColumnMarker> for SelectItem {
fn from(marker: ColumnMarker) -> Self {
SelectItem::Column(marker)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum OrderDir {
Asc,
Desc,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct OrderBy {
pub column: String,
pub direction: OrderDir,
}
impl OrderBy {
pub fn new(column: impl Into<String>, direction: OrderDir) -> Self {
OrderBy {
column: column.into(),
direction,
}
}
pub fn asc(column: impl Into<String>) -> Self {
OrderBy::new(column, OrderDir::Asc)
}
pub fn desc(column: impl Into<String>) -> Self {
OrderBy::new(column, OrderDir::Desc)
}
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
pub struct SelectCapacity {
pub items: usize,
pub joins: usize,
pub order_by_columns: usize,
pub order_by_exprs: usize,
pub group_by: usize,
pub distinct: usize,
}
#[derive(Debug, Clone, PartialEq)]
pub enum OrderByItem {
Column(OrderBy),
Expr(Expr, OrderDir),
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum JoinType {
Inner,
Left,
}
#[derive(Debug, Clone, PartialEq)]
pub struct JoinClause {
pub join_type: JoinType,
pub table: String,
pub on: Expr,
pub items: Vec<SelectItem>,
}
impl JoinClause {
pub fn new(
join_type: JoinType,
table: impl Into<String>,
on: Expr,
items: Vec<SelectItem>,
) -> Self {
Self {
join_type,
table: table.into(),
on,
items,
}
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct Select {
pub table: String,
pub items: Vec<SelectItem>,
pub joins: Vec<JoinClause>,
pub filter: Option<Expr>,
pub order_by: Vec<OrderBy>,
pub take: Option<i32>,
pub skip: Option<u32>,
pub group_by: Vec<ColumnMarker>,
pub distinct: Vec<String>,
pub having: Option<Expr>,
pub order_by_items: Vec<OrderByItem>,
pub order_by_exprs: Vec<(Expr, OrderDir)>,
}
impl Select {
pub fn from_table(table: impl Into<String>) -> SelectBuilder {
SelectBuilder {
table: table.into(),
items: Vec::new(),
joins: Vec::new(),
filter: None,
order_by: Vec::new(),
take: None,
skip: None,
group_by: Vec::new(),
distinct: Vec::new(),
having: None,
order_by_items: Vec::new(),
order_by_exprs: Vec::new(),
}
}
}
#[derive(Debug, Clone)]
pub struct SelectBuilder {
table: String,
items: Vec<SelectItem>,
joins: Vec<JoinClause>,
filter: Option<Expr>,
order_by: Vec<OrderBy>,
take: Option<i32>,
skip: Option<u32>,
group_by: Vec<ColumnMarker>,
distinct: Vec<String>,
having: Option<Expr>,
order_by_items: Vec<OrderByItem>,
order_by_exprs: Vec<(Expr, OrderDir)>,
}
impl SelectBuilder {
#[must_use]
pub fn with_capacity(mut self, capacity: SelectCapacity) -> Self {
self.items.reserve(capacity.items);
self.joins.reserve(capacity.joins);
self.order_by.reserve(capacity.order_by_columns);
self.group_by.reserve(capacity.group_by);
self.distinct.reserve(capacity.distinct);
self.order_by_items
.reserve(capacity.order_by_columns + capacity.order_by_exprs);
self.order_by_exprs.reserve(capacity.order_by_exprs);
self
}
#[must_use]
pub fn items(mut self, items: Vec<SelectItem>) -> Self {
self.items = items;
self
}
#[must_use]
pub fn item(mut self, item: SelectItem) -> Self {
self.items.push(item);
self
}
#[must_use]
pub fn computed(mut self, expr: Expr, alias: impl Into<String>) -> Self {
self.items.push(SelectItem::computed(expr, alias));
self
}
#[must_use]
pub fn filter(mut self, expr: Expr) -> Self {
self.filter = Some(expr);
self
}
#[must_use]
pub fn order_by(mut self, column: impl Into<String>, direction: OrderDir) -> Self {
let order = OrderBy::new(column, direction);
self.order_by.push(order.clone());
self.order_by_items.push(OrderByItem::Column(order));
self
}
#[must_use]
pub fn order_by_asc(mut self, column: impl Into<String>) -> Self {
let order = OrderBy::asc(column);
self.order_by.push(order.clone());
self.order_by_items.push(OrderByItem::Column(order));
self
}
#[must_use]
pub fn order_by_desc(mut self, column: impl Into<String>) -> Self {
let order = OrderBy::desc(column);
self.order_by.push(order.clone());
self.order_by_items.push(OrderByItem::Column(order));
self
}
#[must_use]
pub fn take(mut self, n: i32) -> Self {
self.take = Some(n);
self
}
#[must_use]
pub fn skip(mut self, n: u32) -> Self {
self.skip = Some(n);
self
}
#[must_use]
pub fn join(mut self, clause: JoinClause) -> Self {
self.joins.push(clause);
self
}
#[must_use]
pub fn inner_join(self, table: impl Into<String>, on: Expr, items: Vec<SelectItem>) -> Self {
self.join(JoinClause::new(JoinType::Inner, table, on, items))
}
#[must_use]
pub fn left_join(self, table: impl Into<String>, on: Expr, items: Vec<SelectItem>) -> Self {
self.join(JoinClause::new(JoinType::Left, table, on, items))
}
#[must_use]
pub fn group_by_column(mut self, column: ColumnMarker) -> Self {
self.group_by.push(column);
self
}
#[must_use]
pub fn group_by(mut self, columns: Vec<ColumnMarker>) -> Self {
self.group_by.extend(columns);
self
}
#[must_use]
pub fn having(mut self, expr: Expr) -> Self {
self.having = Some(expr);
self
}
#[must_use]
pub fn order_by_expr(mut self, expr: Expr, direction: OrderDir) -> Self {
self.order_by_exprs.push((expr.clone(), direction));
self.order_by_items.push(OrderByItem::Expr(expr, direction));
self
}
#[must_use]
pub fn distinct(mut self, columns: Vec<String>) -> Self {
self.distinct = columns;
self
}
pub fn build(self) -> Result<Select> {
if self.table.is_empty() {
return Err(Error::MissingField("table".to_string()));
}
Ok(Select {
table: self.table,
items: self.items,
joins: self.joins,
filter: self.filter,
order_by: self.order_by,
take: self.take,
skip: self.skip,
group_by: self.group_by,
distinct: self.distinct,
having: self.having,
order_by_items: self.order_by_items,
order_by_exprs: self.order_by_exprs,
})
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::expr::Expr;
#[test]
fn test_order_by() {
let asc = OrderBy::asc("id");
assert_eq!(asc.column, "id");
assert_eq!(asc.direction, OrderDir::Asc);
let desc = OrderBy::desc("created_at");
assert_eq!(desc.column, "created_at");
assert_eq!(desc.direction, OrderDir::Desc);
}
#[test]
fn test_simple_select() {
let query = Select::from_table("users").build().unwrap();
assert_eq!(query.table, "users");
assert!(query.items.is_empty());
assert!(query.joins.is_empty());
assert!(query.filter.is_none());
assert!(query.order_by.is_empty());
assert!(query.take.is_none());
assert!(query.skip.is_none());
}
#[test]
fn test_select_with_columns() {
let query = Select::from_table("users")
.item(SelectItem::from(ColumnMarker::new("users", "id")))
.item(SelectItem::from(ColumnMarker::new("users", "email")))
.build()
.unwrap();
assert_eq!(query.items.len(), 2);
if let SelectItem::Column(col) = &query.items[0] {
assert_eq!(col.table, "users");
assert_eq!(col.name, "id");
}
if let SelectItem::Column(col) = &query.items[1] {
assert_eq!(col.table, "users");
assert_eq!(col.name, "email");
}
}
#[test]
fn test_select_with_filter() {
let filter = Expr::column("age").ge(Expr::param(18i64));
let query = Select::from_table("users")
.filter(filter.clone())
.build()
.unwrap();
assert_eq!(query.filter, Some(filter));
}
#[test]
fn test_select_with_order_by() {
let query = Select::from_table("users")
.order_by_desc("created_at")
.order_by_asc("email")
.build()
.unwrap();
assert_eq!(query.order_by.len(), 2);
assert_eq!(query.order_by[0].column, "created_at");
assert_eq!(query.order_by[0].direction, OrderDir::Desc);
assert_eq!(query.order_by[1].column, "email");
assert_eq!(query.order_by[1].direction, OrderDir::Asc);
}
#[test]
fn test_select_with_take_and_skip() {
let query = Select::from_table("users")
.take(10)
.skip(20)
.build()
.unwrap();
assert_eq!(query.take, Some(10));
assert_eq!(query.skip, Some(20));
}
#[test]
fn test_complex_select() {
let filter = Expr::column("age")
.ge(Expr::param(18i64))
.and(Expr::column("email").like(Expr::param("%@gmail.com")));
let query = Select::from_table("users")
.items(vec![
SelectItem::from(ColumnMarker::new("users", "id")),
SelectItem::from(ColumnMarker::new("users", "email")),
SelectItem::from(ColumnMarker::new("users", "age")),
])
.filter(filter)
.order_by_desc("id")
.take(10)
.build()
.unwrap();
assert_eq!(query.table, "users");
assert_eq!(query.items.len(), 3);
assert!(query.filter.is_some());
assert_eq!(query.order_by.len(), 1);
assert_eq!(query.take, Some(10));
}
#[test]
fn test_select_with_inner_join() {
let on = Expr::column("users__id").eq(Expr::column("posts__user_id"));
let query = Select::from_table("users")
.item(SelectItem::from(ColumnMarker::new("users", "id")))
.inner_join(
"posts",
on.clone(),
vec![
SelectItem::from(ColumnMarker::new("posts", "id")),
SelectItem::from(ColumnMarker::new("posts", "title")),
],
)
.build()
.unwrap();
assert_eq!(query.joins.len(), 1);
assert_eq!(query.joins[0].join_type, JoinType::Inner);
assert_eq!(query.joins[0].table, "posts");
assert_eq!(query.joins[0].on, on);
assert_eq!(query.joins[0].items.len(), 2);
}
#[test]
fn test_select_with_left_join() {
let on = Expr::column("users__id").eq(Expr::column("posts__user_id"));
let query = Select::from_table("users")
.item(SelectItem::from(ColumnMarker::new("users", "id")))
.left_join(
"posts",
on,
vec![SelectItem::from(ColumnMarker::new("posts", "title"))],
)
.build()
.unwrap();
assert_eq!(query.joins.len(), 1);
assert_eq!(query.joins[0].join_type, JoinType::Left);
assert_eq!(query.joins[0].table, "posts");
assert_eq!(query.joins[0].items.len(), 1);
}
#[test]
fn test_select_with_multiple_joins() {
let query = Select::from_table("users")
.inner_join(
"posts",
Expr::column("users__id").eq(Expr::column("posts__user_id")),
vec![SelectItem::from(ColumnMarker::new("posts", "title"))],
)
.left_join(
"comments",
Expr::column("posts__id").eq(Expr::column("comments__post_id")),
vec![SelectItem::from(ColumnMarker::new("comments", "body"))],
)
.build()
.unwrap();
assert_eq!(query.joins.len(), 2);
assert_eq!(query.joins[0].join_type, JoinType::Inner);
assert_eq!(query.joins[0].table, "posts");
assert_eq!(query.joins[1].join_type, JoinType::Left);
assert_eq!(query.joins[1].table, "comments");
}
}