use crate::WriteSql;
pub enum JoinType {
Cross,
Inner,
Left,
Right,
Full,
}
impl JoinType {
pub const fn as_str(&self) -> &'static str {
match *self {
Self::Cross => "CROSS",
Self::Inner => "INNER",
Self::Left => "LEFT",
Self::Right => "RIGHT",
Self::Full => "FULL",
}
}
}
pub fn cross_join<Sql, Arg>(sql: &mut Sql, table: &str)
where
Sql: WriteSql<Arg>,
{
separator_optional(sql);
sql.push_cmd("CROSS JOIN ");
sql.push_cmd(table);
}
pub fn join<Sql, Arg>(sql: &mut Sql, typ: JoinType, table: &str, alias: Option<&str>)
where
Sql: WriteSql<Arg>,
{
separator_optional(sql);
sql.push_cmd(typ.as_str());
sql.push_cmd(" JOIN ");
sql.push_cmd(table);
if let Some(alias) = alias {
sql.push_cmd(" AS ");
sql.push_cmd(alias);
}
}
pub fn join_on<Sql, Arg>(sql: &mut Sql)
where
Sql: WriteSql<Arg>,
{
separator_optional(sql);
sql.push_cmd("ON");
}
pub fn join_using<'t, Sql, Arg, I>(sql: &mut Sql, columns: I)
where
Sql: WriteSql<Arg>,
I: IntoIterator<Item = &'t str>,
{
separator_optional(sql);
sql.push_cmd("USING (");
let mut tbls = columns.into_iter();
if let Some(tbl) = tbls.next() {
sql.push_cmd(tbl);
}
for tbl in tbls {
sql.push_cmd(", ");
sql.push_cmd(tbl);
}
sql.push_cmd(")");
}
#[macro_export]
macro_rules! static_join_using {
($first:literal) => {
concat!("USING (", $first, ")")
};
($first:literal, $($column:literal),* $(,)?) => {
concat!("USING (", $first, $(", ", $column),*, ")")
};
}
#[macro_export]
macro_rules! static_join {
(CROSS $table:literal) => {
concat!("CROSS JOIN ", $table)
};
(INNER $table:literal ON $a:literal $op:tt $b:literal $($logic_op:tt $ax:literal $opx:tt $bx:literal)*) => {
concat!(
"INNER JOIN ",
$table,
" ON ",
$crate::expr::static_condition!($a $op $b $($logic_op $ax $opx $bx)*)
)
};
(INNER $table:literal ON NOT $a:literal $op:tt $b:literal $($logic_op:tt $ax:literal $opx:tt $bx:literal)*) => {
concat!(
"INNER JOIN ",
$table,
" ON NOT ",
$crate::expr::static_condition!($a $op $b $($logic_op $ax $opx $bx)*)
)
};
(INNER $table:literal USING ($first:literal$(,)? $($column:literal),*)) => {
concat!(
"INNER JOIN ",
$table,
" ",
$crate::expr::static_join_using!($first, $($column),*),
)
};
(LEFT $table:literal ON $a:literal $op:tt $b:literal $($logic_op:tt $ax:literal $opx:tt $bx:literal)*) => {
concat!(
"LEFT JOIN ",
$table,
" ON ",
$crate::expr::static_condition!($a $op $b $($logic_op $ax $opx $bx)*)
)
};
(LEFT $table:literal ON NOT $a:literal $op:tt $b:literal $($logic_op:tt $ax:literal $opx:tt $bx:literal)*) => {
concat!(
"LEFT JOIN ",
$table,
" ON NOT ",
$crate::expr::static_condition!($a $op $b $($logic_op $ax $opx $bx)*)
)
};
(LEFT $table:literal USING ($first:literal$(,)? $($column:literal),*)) => {
concat!(
"LEFT JOIN ",
$table,
" ",
$crate::expr::static_join_using!($first, $($column),*),
)
};
(RIGHT $table:literal ON $a:literal $op:tt $b:literal $($logic_op:tt $ax:literal $opx:tt $bx:literal)*) => {
concat!(
"RIGHT JOIN ",
$table,
" ON ",
$crate::expr::static_condition!($a $op $b $($logic_op $ax $opx $bx)*)
)
};
(RIGHT $table:literal ON NOT $a:literal $op:tt $b:literal $($logic_op:tt $ax:literal $opx:tt $bx:literal)*) => {
concat!(
"RIGHT JOIN ",
$table,
" ON NOT ",
$crate::expr::static_condition!($a $op $b $($logic_op $ax $opx $bx)*)
)
};
(RIGHT $table:literal USING ($first:literal$(,)? $($column:literal),*)) => {
concat!(
"RIGHT JOIN ",
$table,
" ",
$crate::expr::static_join_using!($first, $($column),*),
)
};
(FULL $table:literal ON $a:literal $op:tt $b:literal $($logic_op:tt $ax:literal $opx:tt $bx:literal)*) => {
concat!(
"FULL JOIN ",
$table,
" ON ",
$crate::expr::static_condition!($a $op $b $($logic_op $ax $opx $bx)*)
)
};
(FULL $table:literal ON NOT $a:literal $op:tt $b:literal $($logic_op:tt $ax:literal $opx:tt $bx:literal)*) => {
concat!(
"FULL JOIN ",
$table,
" ON NOT ",
$crate::expr::static_condition!($a $op $b $($logic_op $ax $opx $bx)*)
)
};
(FULL $table:literal USING ($first:literal$(,)? $($column:literal),*)) => {
concat!(
"FULL JOIN ",
$table,
" ",
$crate::expr::static_join_using!($first, $($column),*)
)
};
}
pub use static_join;
pub use static_join_using;
use super::separator_optional;
#[cfg(test)]
mod test {
use crate::{expr::join_using, test::TestArgs, SqlCommand};
#[test]
fn join_using_single_column() {
let mut sql: SqlCommand<TestArgs> = SqlCommand::default();
join_using(&mut sql, ["id"]);
assert_eq!(sql.as_command(), "USING (id)");
}
#[test]
fn join_using_multiple_columns() {
let mut sql: SqlCommand<TestArgs> = SqlCommand::default();
join_using(&mut sql, ["id", "customer_id"]);
assert_eq!(sql.as_command(), "USING (id, customer_id)");
let mut sql: SqlCommand<TestArgs> = SqlCommand::default();
join_using(&mut sql, ["id", "sale_id", "customer_id"]);
assert_eq!(sql.as_command(), "USING (id, sale_id, customer_id)");
}
#[test]
fn static_using_macro() {
assert_eq!(static_join_using!("id"), "USING (id)");
assert_eq!(
static_join_using!("id", "customer_id"),
"USING (id, customer_id)"
);
assert_eq!(
static_join_using!("id", "sale_id", "customer_id",),
"USING (id, sale_id, customer_id)"
);
}
#[test]
fn static_join_macro() {
assert_eq!(static_join!(CROSS "user"), "CROSS JOIN user");
assert_eq!(
static_join!(INNER "user" USING ("id", "department")),
"INNER JOIN user USING (id, department)"
);
assert_eq!(
static_join!(INNER "user" ON "user.id" = "access_history.user_id"),
"INNER JOIN user ON user.id = access_history.user_id"
);
assert_eq!(
static_join!(INNER "user" ON NOT "user.id" = "access_history.user_id" AND_NOT "user.updated" > "access_history.created"),
"INNER JOIN user ON NOT user.id = access_history.user_id AND NOT user.updated > access_history.created"
);
assert_eq!(
static_join!(LEFT "user" USING ("id", "department")),
"LEFT JOIN user USING (id, department)"
);
assert_eq!(
static_join!(LEFT "user" ON "user.id" = "access_history.user_id"),
"LEFT JOIN user ON user.id = access_history.user_id"
);
assert_eq!(
static_join!(LEFT "user" ON NOT "user.id" = "access_history.user_id"),
"LEFT JOIN user ON NOT user.id = access_history.user_id"
);
assert_eq!(
static_join!(LEFT "user" ON "user.id" = "access_history.user_id" OR "user.updated" <= "access_history.created"),
"LEFT JOIN user ON user.id = access_history.user_id OR user.updated <= access_history.created"
);
assert_eq!(
static_join!(RIGHT "user" USING ("id", "department")),
"RIGHT JOIN user USING (id, department)"
);
assert_eq!(
static_join!(RIGHT "user" ON "user.id" = "access_history.user_id"),
"RIGHT JOIN user ON user.id = access_history.user_id"
);
assert_eq!(
static_join!(RIGHT "user" ON NOT "user.id" = "access_history.user_id" AND "user.updated" < "access_history.created"),
"RIGHT JOIN user ON NOT user.id = access_history.user_id AND user.updated < access_history.created"
);
assert_eq!(
static_join!(RIGHT "user" ON "user.id" = "access_history.user_id" AND_NOT "user.updated" >= "access_history.created"),
"RIGHT JOIN user ON user.id = access_history.user_id AND NOT user.updated >= access_history.created"
);
assert_eq!(
static_join!(FULL "user" USING ("id", "department")),
"FULL JOIN user USING (id, department)"
);
assert_eq!(
static_join!(FULL "user" ON "user.id" != "access_history.user_id"),
"FULL JOIN user ON user.id <> access_history.user_id"
);
assert_eq!(
static_join!(FULL "user" ON "user.id" = "access_history.user_id" OR "user.updated" < "access_history.created"),
"FULL JOIN user ON user.id = access_history.user_id OR user.updated < access_history.created"
);
assert_eq!(
static_join!(FULL "user" ON NOT "user.id" != "access_history.user_id" OR_NOT "user.updated" < "access_history.created"),
"FULL JOIN user ON NOT user.id <> access_history.user_id OR NOT user.updated < access_history.created"
);
}
}