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}