Skip to main content

vantage_sql/primitives/
identifier.rs

1use vantage_expressions::{Expression, Expressive};
2
3/// SQL identifier with optional qualification and alias.
4///
5/// Quoting is determined by the `Expressive<T>` impl — each backend
6/// renders with its own quote style (`"` for PostgreSQL/SQLite,
7/// `` ` `` for MySQL). This means `Identifier` is quote-agnostic;
8/// the quoting happens only when `.expr()` is called for a specific type.
9///
10/// **Warning:** Identifier names are not escaped for embedded quote characters.
11/// Do not pass untrusted user input as identifier names — this is intended
12/// for code-defined table/column names only.
13///
14/// # Examples
15///
16/// ```ignore
17/// use vantage_sql::primitives::identifier::ident;
18///
19/// // Simple column — quoting depends on which Expressive<T> is used
20/// let expr = mysql_expr!("SELECT {} FROM {}", (ident("name")), (ident("product")));
21///
22/// // Qualified (table.column)
23/// let expr = mysql_expr!("SELECT {}", (ident("name").dot_of("u")));
24///
25/// // With alias
26/// let expr = mysql_expr!("SELECT {}", (ident("name").with_alias("n")));
27/// ```
28#[derive(Debug, Clone)]
29pub struct Identifier {
30    parts: Vec<String>,
31    alias: Option<String>,
32}
33
34impl Identifier {
35    /// Single identifier: `name`.
36    pub fn new(name: impl Into<String>) -> Self {
37        Self {
38            parts: vec![name.into()],
39            alias: None,
40        }
41    }
42
43    /// Prepends a qualifier: `ident("name").dot_of("u")` → `u.name`.
44    /// Chaining adds further left: `ident("col").dot_of("t").dot_of("s")` → `s.t.col`.
45    pub fn dot_of(mut self, prefix: impl Into<String>) -> Self {
46        self.parts.insert(0, prefix.into());
47        self
48    }
49
50    /// Adds an AS alias.
51    pub fn with_alias(mut self, alias: impl Into<String>) -> Self {
52        self.alias = Some(alias.into());
53        self
54    }
55
56    /// Returns the identifier name (parts joined with dots, no quotes).
57    pub fn name(&self) -> String {
58        self.parts.join(".")
59    }
60
61    /// Returns the alias, if any.
62    pub fn alias(&self) -> Option<&str> {
63        self.alias.as_deref()
64    }
65
66    /// Render with a given quote character. Used by backend `Expressive` impls.
67    fn render_with(&self, q: char) -> String {
68        let base = self
69            .parts
70            .iter()
71            .map(|p| format!("{q}{p}{q}"))
72            .collect::<Vec<_>>()
73            .join(".");
74        match &self.alias {
75            Some(alias) => format!("{base} AS {q}{alias}{q}"),
76            None => base,
77        }
78    }
79}
80
81/// Shorthand for `Identifier::new(name)`.
82pub fn ident(name: impl Into<String>) -> Identifier {
83    Identifier::new(name)
84}
85
86// Each backend impl owns its quoting style.
87
88#[cfg(feature = "sqlite")]
89impl Expressive<crate::sqlite::types::AnySqliteType> for Identifier {
90    fn expr(&self) -> Expression<crate::sqlite::types::AnySqliteType> {
91        Expression::new(self.render_with('"'), vec![])
92    }
93}
94
95#[cfg(feature = "sqlite")]
96impl From<Identifier> for Expression<crate::sqlite::types::AnySqliteType> {
97    fn from(id: Identifier) -> Self {
98        id.expr()
99    }
100}
101
102#[cfg(feature = "postgres")]
103impl Expressive<crate::postgres::types::AnyPostgresType> for Identifier {
104    fn expr(&self) -> Expression<crate::postgres::types::AnyPostgresType> {
105        Expression::new(self.render_with('"'), vec![])
106    }
107}
108
109#[cfg(feature = "postgres")]
110impl From<Identifier> for Expression<crate::postgres::types::AnyPostgresType> {
111    fn from(id: Identifier) -> Self {
112        id.expr()
113    }
114}
115
116#[cfg(feature = "mysql")]
117impl Expressive<crate::mysql::types::AnyMysqlType> for Identifier {
118    fn expr(&self) -> Expression<crate::mysql::types::AnyMysqlType> {
119        Expression::new(self.render_with('`'), vec![])
120    }
121}
122
123#[cfg(feature = "mysql")]
124impl From<Identifier> for Expression<crate::mysql::types::AnyMysqlType> {
125    fn from(id: Identifier) -> Self {
126        id.expr()
127    }
128}