use vantage_expressions::{Expression, Expressive};
#[derive(Debug, Clone)]
pub struct Identifier {
parts: Vec<String>,
alias: Option<String>,
}
impl Identifier {
pub fn new(name: impl Into<String>) -> Self {
Self {
parts: vec![name.into()],
alias: None,
}
}
pub fn dot_of(mut self, prefix: impl Into<String>) -> Self {
self.parts.insert(0, prefix.into());
self
}
pub fn with_alias(mut self, alias: impl Into<String>) -> Self {
self.alias = Some(alias.into());
self
}
pub fn name(&self) -> String {
self.parts.join(".")
}
pub fn alias(&self) -> Option<&str> {
self.alias.as_deref()
}
fn render_with(&self, q: char) -> String {
let quote = |p: &str| format!("{q}{}{q}", p.replace(q, &format!("{q}{q}")));
let base = self
.parts
.iter()
.map(|p| quote(p))
.collect::<Vec<_>>()
.join(".");
match &self.alias {
Some(alias) => format!("{base} AS {}", quote(alias)),
None => base,
}
}
}
pub fn ident(name: impl Into<String>) -> Identifier {
Identifier::new(name)
}
#[cfg(feature = "sqlite")]
impl Expressive<crate::sqlite::types::AnySqliteType> for Identifier {
fn expr(&self) -> Expression<crate::sqlite::types::AnySqliteType> {
Expression::new(self.render_with('"'), vec![])
}
}
#[cfg(feature = "sqlite")]
impl From<Identifier> for Expression<crate::sqlite::types::AnySqliteType> {
fn from(id: Identifier) -> Self {
id.expr()
}
}
#[cfg(feature = "postgres")]
impl Expressive<crate::postgres::types::AnyPostgresType> for Identifier {
fn expr(&self) -> Expression<crate::postgres::types::AnyPostgresType> {
Expression::new(self.render_with('"'), vec![])
}
}
#[cfg(feature = "postgres")]
impl From<Identifier> for Expression<crate::postgres::types::AnyPostgresType> {
fn from(id: Identifier) -> Self {
id.expr()
}
}
#[cfg(feature = "mysql")]
impl Expressive<crate::mysql::types::AnyMysqlType> for Identifier {
fn expr(&self) -> Expression<crate::mysql::types::AnyMysqlType> {
Expression::new(self.render_with('`'), vec![])
}
}
#[cfg(feature = "mysql")]
impl From<Identifier> for Expression<crate::mysql::types::AnyMysqlType> {
fn from(id: Identifier) -> Self {
id.expr()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn parts_and_alias_quote_normally() {
let id = ident("name").dot_of("u").with_alias("n");
assert_eq!(id.render_with('"'), r#""u"."name" AS "n""#);
}
#[test]
fn embedded_quotes_are_doubled_not_escaped_out_of() {
let id = ident(r#"x" ; DROP TABLE users --"#);
assert_eq!(id.render_with('"'), r#""x"" ; DROP TABLE users --""#);
let id = ident("a`b");
assert_eq!(id.render_with('`'), "`a``b`");
}
#[test]
fn alias_is_escaped_too() {
let id = ident("col").with_alias(r#"a"b"#);
assert_eq!(id.render_with('"'), r#""col" AS "a""b""#);
}
}