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// -- SQLite: a || b || c or a || sep || b || sep || c -------------------------
70
71#[cfg(feature = "sqlite")]
72impl Expressive<crate::sqlite::types::AnySqliteType>
73    for Concat<crate::sqlite::types::AnySqliteType>
74{
75    fn expr(&self) -> Expression<crate::sqlite::types::AnySqliteType> {
76        if let Some(sep) = &self.separator {
77            // Interleave parts with separator: a || sep || b || sep || c
78            let mut interleaved = Vec::with_capacity(self.parts.len() * 2 - 1);
79            for (i, part) in self.parts.iter().enumerate() {
80                if i > 0 {
81                    interleaved.push(sep.clone());
82                }
83                interleaved.push(part.clone());
84            }
85            Expression::from_vec(interleaved, " || ")
86        } else {
87            Expression::from_vec(self.parts.clone(), " || ")
88        }
89    }
90}
91
92// -- MySQL: CONCAT(a, b, c) or CONCAT_WS(sep, a, b, c) ----------------------
93
94#[cfg(feature = "mysql")]
95impl Expressive<crate::mysql::types::AnyMysqlType> for Concat<crate::mysql::types::AnyMysqlType> {
96    fn expr(&self) -> Expression<crate::mysql::types::AnyMysqlType> {
97        use vantage_expressions::ExpressiveEnum;
98
99        if let Some(sep) = &self.separator {
100            let mut all = vec![sep.clone()];
101            all.extend(self.parts.clone());
102            let args = Expression::from_vec(all, ", ");
103            Expression::new("CONCAT_WS({})", vec![ExpressiveEnum::Nested(args)])
104        } else {
105            let args = Expression::from_vec(self.parts.clone(), ", ");
106            Expression::new("CONCAT({})", vec![ExpressiveEnum::Nested(args)])
107        }
108    }
109}
110
111// -- PostgreSQL: a || b || c or CONCAT_WS(sep, a, b, c) ----------------------
112
113#[cfg(feature = "postgres")]
114impl Expressive<crate::postgres::types::AnyPostgresType>
115    for Concat<crate::postgres::types::AnyPostgresType>
116{
117    fn expr(&self) -> Expression<crate::postgres::types::AnyPostgresType> {
118        use vantage_expressions::ExpressiveEnum;
119
120        if let Some(sep) = &self.separator {
121            let mut all = vec![sep.clone()];
122            all.extend(self.parts.clone());
123            let args = Expression::from_vec(all, ", ");
124            Expression::new("CONCAT_WS({})", vec![ExpressiveEnum::Nested(args)])
125        } else {
126            Expression::from_vec(self.parts.clone(), " || ")
127        }
128    }
129}