use super::{iden::DynIden, table_ref::TableRef};
use crate::expr::{Condition, IntoCondition};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum JoinType {
Join,
InnerJoin,
LeftJoin,
RightJoin,
FullOuterJoin,
CrossJoin,
}
impl JoinType {
#[must_use]
pub fn as_str(&self) -> &'static str {
match self {
Self::Join => "JOIN",
Self::InnerJoin => "INNER JOIN",
Self::LeftJoin => "LEFT JOIN",
Self::RightJoin => "RIGHT JOIN",
Self::FullOuterJoin => "FULL OUTER JOIN",
Self::CrossJoin => "CROSS JOIN",
}
}
}
#[derive(Debug, Clone)]
pub enum JoinOn {
Columns(ColumnPair),
Condition(Condition),
Using(Vec<DynIden>),
}
#[derive(Debug, Clone)]
pub struct ColumnPair {
pub left: ColumnSpec,
pub right: ColumnSpec,
}
#[derive(Debug, Clone)]
pub enum ColumnSpec {
Column(DynIden),
TableColumn(DynIden, DynIden),
}
impl ColumnSpec {
pub fn column<I: super::iden::IntoIden>(column: I) -> Self {
Self::Column(column.into_iden())
}
pub fn table_column<T: super::iden::IntoIden, C: super::iden::IntoIden>(
table: T,
column: C,
) -> Self {
Self::TableColumn(table.into_iden(), column.into_iden())
}
}
#[derive(Debug, Clone)]
pub struct JoinExpr {
pub join: JoinType,
pub table: TableRef,
pub on: Option<JoinOn>,
}
impl JoinExpr {
pub fn new(table: TableRef) -> Self {
Self {
join: JoinType::InnerJoin,
table,
on: None,
}
}
#[must_use]
pub fn join_type(mut self, join: JoinType) -> Self {
self.join = join;
self
}
#[must_use]
pub fn on(mut self, condition: JoinOn) -> Self {
self.on = Some(condition);
self
}
#[must_use]
pub fn on_columns(mut self, left: ColumnSpec, right: ColumnSpec) -> Self {
self.on = Some(JoinOn::Columns(ColumnPair { left, right }));
self
}
#[must_use]
pub fn on_condition<C: IntoCondition>(mut self, condition: C) -> Self {
self.on = Some(JoinOn::Condition(condition.into_condition()));
self
}
#[must_use]
pub fn using_columns<I, C>(mut self, columns: I) -> Self
where
I: IntoIterator<Item = C>,
C: super::iden::IntoIden,
{
let cols: Vec<DynIden> = columns.into_iter().map(|c| c.into_iden()).collect();
self.on = Some(JoinOn::Using(cols));
self
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::types::iden::IntoIden;
use rstest::rstest;
#[rstest]
#[case(JoinType::Join, "JOIN")]
#[case(JoinType::InnerJoin, "INNER JOIN")]
#[case(JoinType::LeftJoin, "LEFT JOIN")]
#[case(JoinType::RightJoin, "RIGHT JOIN")]
#[case(JoinType::FullOuterJoin, "FULL OUTER JOIN")]
#[case(JoinType::CrossJoin, "CROSS JOIN")]
fn test_join_type_as_str(#[case] join_type: JoinType, #[case] expected: &str) {
assert_eq!(join_type.as_str(), expected);
}
#[rstest]
fn test_column_spec_simple() {
let _spec = ColumnSpec::column("id");
}
#[rstest]
fn test_column_spec_qualified() {
let _spec = ColumnSpec::table_column("users", "id");
}
#[rstest]
fn test_join_expr_builder() {
use crate::types::Alias;
let table = TableRef::Table(Alias::new("posts").into_iden());
let join = JoinExpr::new(table)
.join_type(JoinType::LeftJoin)
.on_columns(
ColumnSpec::table_column("users", "id"),
ColumnSpec::table_column("posts", "user_id"),
);
assert_eq!(join.join, JoinType::LeftJoin);
assert!(join.on.is_some());
}
}