Skip to main content

sea_query/types/iden/
core.rs

1//! "Core", low-level identifier types.
2use std::{borrow::Cow, fmt::Debug};
3
4/// Identifier
5pub trait Iden {
6    /// Return the to-be sanitized version of the identifier.
7    ///
8    /// For example, for MySQL "hel`lo`" would have to be escaped as "hel``lo".
9    /// Note that this method doesn't do the actual escape,
10    /// as it's backend specific.
11    /// It only indicates whether the identifier needs to be escaped.
12    ///
13    /// If the identifier doesn't need to be escaped, return `'static str`.
14    /// This can be deduced at compile-time by the `Iden` macro,
15    /// or using the [`is_static_iden`] function.
16    ///
17    /// `Cow::Owned` would always be escaped.
18    fn quoted(&self) -> Cow<'static, str> {
19        Cow::Owned(self.to_string())
20    }
21
22    /// A shortcut for writing an [`unquoted`][Iden::unquoted]
23    /// identifier into a [`String`].
24    ///
25    /// We can't reuse [`ToString`] for this, because [`ToString`] uses
26    /// the [`Display`][std::fmt::Display] representation. But [`Iden`]
27    /// representation is distinct from [`Display`][std::fmt::Display]
28    /// and can be different.
29    fn to_string(&self) -> String {
30        self.unquoted().to_owned()
31    }
32
33    /// Write a raw identifier string without quotes.
34    ///
35    /// We intentionally don't reuse [`Display`][std::fmt::Display] for
36    /// this, because we want to allow it to have a different logic.
37    fn unquoted(&self) -> &str;
38}
39
40impl Iden for &'static str {
41    fn quoted(&self) -> Cow<'static, str> {
42        if is_static_iden(self) {
43            Cow::Borrowed(self)
44        } else {
45            Cow::Owned(String::from(*self))
46        }
47    }
48
49    fn unquoted(&self) -> &str {
50        self
51    }
52}
53
54#[cfg(feature = "thread-safe")]
55/// Identifier statically known at compile-time.
56pub trait IdenStatic: Iden + Copy + Send + Sync + 'static {
57    fn as_str(&self) -> &'static str;
58}
59
60#[cfg(not(feature = "thread-safe"))]
61/// Identifier statically known at compile-time.
62pub trait IdenStatic: Iden + Copy + 'static {
63    fn as_str(&self) -> &'static str;
64}
65
66/// A prepared (quoted) identifier string.
67///
68/// The naming is legacy and kept for compatibility.
69/// This used to be an alias for a `dyn Iden` object that's lazily rendered later.
70///
71/// Nowadays, it's an eagerly-rendered string.
72/// Most identifiers are static strings that aren't "rendered" at runtime anyway.
73#[derive(Debug, Clone, PartialEq, Eq, Hash)]
74pub struct DynIden(pub(crate) Cow<'static, str>);
75
76impl DynIden {
77    pub fn inner(&self) -> Cow<'static, str> {
78        self.0.clone()
79    }
80}
81
82impl std::fmt::Display for DynIden {
83    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
84        f.write_str(&self.0)
85    }
86}
87
88pub trait IntoIden: Into<DynIden> {
89    fn into_iden(self) -> DynIden;
90}
91
92impl<T> IntoIden for T
93where
94    T: Into<DynIden>,
95{
96    fn into_iden(self) -> DynIden {
97        self.into()
98    }
99}
100
101impl From<String> for DynIden {
102    fn from(value: String) -> Self {
103        DynIden(Cow::Owned(value))
104    }
105}
106
107impl<T> From<T> for DynIden
108where
109    T: Iden,
110{
111    fn from(iden: T) -> Self {
112        DynIden(iden.quoted())
113    }
114}
115
116/// An explicit wrapper for [`Iden`]s which are dynamic user-provided strings.
117///
118/// Nowadays, `&str` implements [`Iden`] and can be used directly.
119#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
120pub struct Alias(pub String);
121
122impl Alias {
123    pub fn new<T>(n: T) -> Self
124    where
125        T: Into<String>,
126    {
127        Self(n.into())
128    }
129}
130
131impl Iden for Alias {
132    fn quoted(&self) -> Cow<'static, str> {
133        Cow::Owned(self.0.clone())
134    }
135
136    fn unquoted(&self) -> &str {
137        &self.0
138    }
139}
140
141/// Null Alias
142#[derive(Default, Debug, Copy, Clone)]
143pub struct NullAlias;
144
145impl NullAlias {
146    pub fn new() -> Self {
147        Self
148    }
149}
150
151impl Iden for NullAlias {
152    fn unquoted(&self) -> &str {
153        ""
154    }
155}
156
157/// Asterisk ("*")
158///
159/// Express the asterisk without table prefix.
160///
161/// # Examples
162///
163/// ```
164/// use sea_query::{tests_cfg::*, *};
165///
166/// let query = Query::select()
167///     .column(Asterisk)
168///     .from(Char::Table)
169///     .to_owned();
170///
171/// assert_eq!(
172///     query.to_string(MysqlQueryBuilder),
173///     r#"SELECT * FROM `character`"#
174/// );
175/// assert_eq!(
176///     query.to_string(PostgresQueryBuilder),
177///     r#"SELECT * FROM "character""#
178/// );
179/// assert_eq!(
180///     query.to_string(SqliteQueryBuilder),
181///     r#"SELECT * FROM "character""#
182/// );
183/// ```
184///
185/// Express the asterisk with table prefix.
186///
187/// Examples
188///
189/// ```
190/// use sea_query::{tests_cfg::*, *};
191///
192/// let query = Query::select()
193///     .column((Char::Table, Asterisk))
194///     .from(Char::Table)
195///     .to_owned();
196///
197/// assert_eq!(
198///     query.to_string(MysqlQueryBuilder),
199///     r#"SELECT `character`.* FROM `character`"#
200/// );
201/// assert_eq!(
202///     query.to_string(PostgresQueryBuilder),
203///     r#"SELECT "character".* FROM "character""#
204/// );
205/// assert_eq!(
206///     query.to_string(SqliteQueryBuilder),
207///     r#"SELECT "character".* FROM "character""#
208/// );
209/// ```
210#[derive(Default, Debug, Clone, Copy)]
211pub struct Asterisk;
212
213/// Return whether this identifier needs to be escaped.
214/// Right now we're very safe and only return true for identifiers
215/// composed of `a-zA-Z0-9_`.
216///
217/// ```
218/// use sea_query::is_static_iden;
219///
220/// assert!(is_static_iden("abc"));
221/// assert!(is_static_iden("a_b_c"));
222/// assert!(!is_static_iden("a-b-c"));
223/// assert!(is_static_iden("abc123"));
224/// assert!(!is_static_iden("123abc"));
225/// assert!(!is_static_iden("a|b|c"));
226/// assert!(!is_static_iden("a'b'c"));
227/// ```
228pub const fn is_static_iden(string: &str) -> bool {
229    let bytes = string.as_bytes();
230    if bytes.is_empty() {
231        return true;
232    }
233
234    // can only begin with [a-z_]
235    if bytes[0] == b'_' || (bytes[0] as char).is_ascii_alphabetic() {
236        // good
237    } else {
238        return false;
239    }
240
241    let mut i = 1;
242    while i < bytes.len() {
243        if bytes[i] == b'_' || (bytes[i] as char).is_ascii_alphanumeric() {
244            // good
245        } else {
246            return false;
247        }
248        i += 1;
249    }
250
251    true
252}