Skip to main content

vantage_sql/primitives/
concat.rs

1use std::fmt::{Debug, Display};
2
3use vantage_core::util::IntoVec;
4use vantage_expressions::{Expression, Expressive};
5
6/// Vendor-aware string concatenation.
7///
8/// Renders as:
9/// - **SQLite/PostgreSQL:** `a || b || c`
10/// - **MySQL:**             `CONCAT(a, b, c)`
11///
12/// # Examples
13///
14/// ```ignore
15/// use vantage_sql::concat_sql;
16///
17/// concat_sql!(
18///     ident("path").dot_of("dt"),
19///     " > ",
20///     ident("name").dot_of("d")
21/// )
22/// ```
23#[derive(Debug, Clone)]
24pub struct Concat<T: Debug + Display + Clone> {
25    parts: Vec<Expression<T>>,
26    separator: Option<Expression<T>>,
27}
28
29impl<T: Debug + Display + Clone> Concat<T> {
30    pub fn new(parts: impl IntoVec<Expression<T>>) -> Self {
31        Self {
32            parts: parts.into_vec(),
33            separator: None,
34        }
35    }
36
37    /// Use CONCAT_WS with a separator instead of plain CONCAT.
38    pub fn ws(mut self, separator: impl Expressive<T>) -> Self {
39        self.separator = Some(separator.expr());
40        self
41    }
42}
43
44/// Macro to create a `Concat` from mixed `Expressive` arguments.
45///
46/// Each argument has `.expr()` called on it automatically, so you can
47/// pass `Identifier`, `&str`, vendor expressions, etc. directly.
48///
49/// ```ignore
50/// concat_sql!(
51///     ident("path").dot_of("dt"),
52///     " > ",
53///     ident("name").dot_of("d")
54/// )
55/// ```
56#[macro_export]
57macro_rules! concat_ {
58    ($($part:expr),+ $(,)?) => {
59        $crate::primitives::concat::Concat::new(vec![
60            $({
61                #[allow(unused_imports)]
62                use vantage_expressions::Expressive;
63                ($part).expr()
64            }),+
65        ])
66    };
67}
68
69#[cfg(feature = "sqlite")]
70impl Expressive<crate::sqlite::types::AnySqliteType>
71    for Concat<crate::sqlite::types::AnySqliteType>
72{
73    fn expr(&self) -> Expression<crate::sqlite::types::AnySqliteType> {
74        if let Some(sep) = &self.separator {
75            let mut interleaved = Vec::with_capacity(self.parts.len() * 2 - 1);
76            for (i, part) in self.parts.iter().enumerate() {
77                if i > 0 {
78                    interleaved.push(sep.clone());
79                }
80                interleaved.push(part.clone());
81            }
82            Expression::from_vec(interleaved, " || ")
83        } else {
84            Expression::from_vec(self.parts.clone(), " || ")
85        }
86    }
87}
88
89#[cfg(feature = "mysql")]
90impl Expressive<crate::mysql::types::AnyMysqlType> for Concat<crate::mysql::types::AnyMysqlType> {
91    fn expr(&self) -> Expression<crate::mysql::types::AnyMysqlType> {
92        use vantage_expressions::ExpressiveEnum;
93
94        if let Some(sep) = &self.separator {
95            let mut all = vec![sep.clone()];
96            all.extend(self.parts.clone());
97            let args = Expression::from_vec(all, ", ");
98            Expression::new("CONCAT_WS({})", vec![ExpressiveEnum::Nested(args)])
99        } else {
100            let args = Expression::from_vec(self.parts.clone(), ", ");
101            Expression::new("CONCAT({})", vec![ExpressiveEnum::Nested(args)])
102        }
103    }
104}
105
106#[cfg(feature = "postgres")]
107impl Expressive<crate::postgres::types::AnyPostgresType>
108    for Concat<crate::postgres::types::AnyPostgresType>
109{
110    fn expr(&self) -> Expression<crate::postgres::types::AnyPostgresType> {
111        use vantage_expressions::ExpressiveEnum;
112
113        if let Some(sep) = &self.separator {
114            let mut all = vec![sep.clone()];
115            all.extend(self.parts.clone());
116            let args = Expression::from_vec(all, ", ");
117            Expression::new("CONCAT_WS({})", vec![ExpressiveEnum::Nested(args)])
118        } else {
119            Expression::from_vec(self.parts.clone(), " || ")
120        }
121    }
122}