Skip to main content

use_sql_table/
lib.rs

1#![forbid(unsafe_code)]
2#![doc = include_str!("../README.md")]
3
4use core::{fmt, str::FromStr};
5use std::error::Error;
6
7use use_sql_ident::{SqlIdentifier, SqlIdentifierError};
8use use_sql_schema::SqlSchemaName;
9
10/// SQL table name primitive.
11#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
12pub struct SqlTableName(SqlIdentifier);
13
14impl SqlTableName {
15    /// Creates a table name.
16    ///
17    /// # Errors
18    ///
19    /// Returns [`SqlTableError`] when identifier validation fails.
20    pub fn new(input: impl AsRef<str>) -> Result<Self, SqlTableError> {
21        SqlIdentifier::new(input)
22            .map(Self)
23            .map_err(SqlTableError::Identifier)
24    }
25
26    /// Returns the table name text.
27    #[must_use]
28    pub fn as_str(&self) -> &str {
29        self.0.as_str()
30    }
31}
32
33impl AsRef<str> for SqlTableName {
34    fn as_ref(&self) -> &str {
35        self.as_str()
36    }
37}
38
39impl fmt::Display for SqlTableName {
40    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
41        self.0.fmt(formatter)
42    }
43}
44
45impl FromStr for SqlTableName {
46    type Err = SqlTableError;
47
48    fn from_str(input: &str) -> Result<Self, Self::Err> {
49        Self::new(input)
50    }
51}
52
53/// SQL table alias primitive.
54#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
55pub struct SqlTableAlias(SqlIdentifier);
56
57impl SqlTableAlias {
58    /// Creates a table alias.
59    ///
60    /// # Errors
61    ///
62    /// Returns [`SqlTableError`] when identifier validation fails.
63    pub fn new(input: impl AsRef<str>) -> Result<Self, SqlTableError> {
64        SqlIdentifier::new(input)
65            .map(Self)
66            .map_err(SqlTableError::Identifier)
67    }
68
69    /// Returns the alias text.
70    #[must_use]
71    pub fn as_str(&self) -> &str {
72        self.0.as_str()
73    }
74}
75
76impl fmt::Display for SqlTableAlias {
77    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
78        self.0.fmt(formatter)
79    }
80}
81
82/// SQL table reference metadata.
83#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
84pub struct SqlTableRef {
85    schema: Option<SqlSchemaName>,
86    name: SqlTableName,
87    alias: Option<SqlTableAlias>,
88}
89
90impl SqlTableRef {
91    /// Creates a table reference from a table name.
92    #[must_use]
93    pub const fn new(name: SqlTableName) -> Self {
94        Self {
95            schema: None,
96            name,
97            alias: None,
98        }
99    }
100
101    /// Adds schema qualification.
102    #[must_use]
103    pub fn with_schema(mut self, schema: SqlSchemaName) -> Self {
104        self.schema = Some(schema);
105        self
106    }
107
108    /// Adds a table alias.
109    #[must_use]
110    pub fn with_alias(mut self, alias: SqlTableAlias) -> Self {
111        self.alias = Some(alias);
112        self
113    }
114
115    /// Returns the optional schema name.
116    #[must_use]
117    pub const fn schema(&self) -> Option<&SqlSchemaName> {
118        self.schema.as_ref()
119    }
120
121    /// Returns the table name.
122    #[must_use]
123    pub const fn name(&self) -> &SqlTableName {
124        &self.name
125    }
126
127    /// Returns the optional alias.
128    #[must_use]
129    pub const fn alias(&self) -> Option<&SqlTableAlias> {
130        self.alias.as_ref()
131    }
132}
133
134impl fmt::Display for SqlTableRef {
135    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
136        if let Some(schema) = &self.schema {
137            write!(formatter, "{schema}.")?;
138        }
139        write!(formatter, "{}", self.name)?;
140        if let Some(alias) = &self.alias {
141            write!(formatter, " AS {alias}")?;
142        }
143        Ok(())
144    }
145}
146
147/// Error returned when SQL table metadata is invalid.
148#[derive(Clone, Debug, Eq, PartialEq)]
149pub enum SqlTableError {
150    Identifier(SqlIdentifierError),
151}
152
153impl fmt::Display for SqlTableError {
154    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
155        match self {
156            Self::Identifier(error) => write!(formatter, "invalid SQL table identifier: {error}"),
157        }
158    }
159}
160
161impl Error for SqlTableError {}
162
163#[cfg(test)]
164mod tests {
165    use super::{SqlTableAlias, SqlTableError, SqlTableName, SqlTableRef};
166    use use_sql_schema::SqlSchemaName;
167
168    #[test]
169    fn creates_table_references() -> Result<(), Box<dyn std::error::Error>> {
170        let table = SqlTableRef::new(SqlTableName::new("users")?)
171            .with_schema(SqlSchemaName::new("public")?)
172            .with_alias(SqlTableAlias::new("u")?);
173
174        assert_eq!(table.to_string(), "public.users AS u");
175        Ok(())
176    }
177
178    #[test]
179    fn rejects_invalid_table_names() {
180        assert!(matches!(
181            SqlTableName::new(""),
182            Err(SqlTableError::Identifier(_))
183        ));
184    }
185}