sql_builder/
name.rs

1use crate::{baquote, brquote, dquote, quote};
2
3/// Make safe name of identifier if it contains unsafe characters.
4///
5/// # Examples
6/// ```
7/// #[macro_use] extern crate sql_builder;
8/// # use anyhow::Result;
9/// use sql_builder::{SqlBuilder, SqlName};
10///
11/// # fn main() -> Result<()> {
12/// let sql = SqlBuilder::select_from(name!("public", "BOOKS"; "b"))
13///     .field(name!("b", "title"))
14///     .field(name!("s", "total"))
15///     .left()
16///     .join(name!("shops"; "s"))
17///     .on_eq(name!("b", "id"), name!("s", "book"))
18///     .sql()?;
19///
20/// assert_eq!("SELECT b.title, s.total FROM `public`.`BOOKS` AS b LEFT JOIN shops AS s ON b.id = s.book;", &sql);
21/// # Ok(())
22/// # }
23/// ```
24#[macro_export]
25macro_rules! name {
26    ( $n:expr ) => {
27        {
28            SqlName::new( $n ).safe()
29        }
30    };
31    ( $n:expr, $( $x:expr ),* ) => {
32        {
33            SqlName::new( $n )
34            $(
35                .add( $x )
36            )*
37            .safe()
38        }
39    };
40    ( $n:expr; $a:expr ) => {
41        {
42            SqlName::new( $n ).alias( $a ).safe()
43        }
44    };
45    ( $n:expr, $( $x:expr ),*; $a:expr ) => {
46        {
47            SqlName::new( $n )
48            $(
49                .add( $x )
50            )*
51            .alias( $a )
52            .safe()
53        }
54    };
55}
56
57/// Make quoted name of identifier.
58///
59/// # Examples
60///
61/// ```
62/// #[macro_use] extern crate sql_builder;
63/// # use anyhow::Result;
64/// use sql_builder::{SqlBuilder, SqlName};
65///
66/// # fn main() -> Result<()> {
67/// let sql = SqlBuilder::select_from(qname!("public", "BOOKS"; "b"))
68///     .field(qname!("b", "title"))
69///     .field(qname!("s", "total"))
70///     .left()
71///     .join(qname!("shops"; "s"))
72///     .on_eq(qname!("b", "id"), qname!("s", "book"))
73///     .sql()?;
74///
75/// assert_eq!("SELECT 'b'.'title', 's'.'total' FROM 'public'.'BOOKS' AS b LEFT JOIN 'shops' AS s ON 'b'.'id' = 's'.'book';", &sql);
76/// # Ok(())
77/// # }
78/// ```
79#[macro_export]
80macro_rules! qname {
81    ( $n:expr ) => {
82        {
83            SqlName::new( $n ).quoted()
84        }
85    };
86    ( $n:expr, $( $x:expr ),* ) => {
87        {
88            SqlName::new( $n )
89            $(
90                .add( $x )
91            )*
92            .quoted()
93        }
94    };
95    ( $n:expr; $a:expr ) => {
96        {
97            SqlName::new( $n ).alias( $a ).quoted()
98        }
99    };
100    ( $n:expr, $( $x:expr ),*; $a:expr ) => {
101        {
102            SqlName::new( $n )
103            $(
104                .add( $x )
105            )*
106            .alias( $a )
107            .quoted()
108        }
109    };
110}
111
112/// Make backquoted name of identifier.
113///
114/// # Examples
115///
116/// ```
117/// #[macro_use] extern crate sql_builder;
118/// # use anyhow::Result;
119/// use sql_builder::{SqlBuilder, SqlName};
120///
121/// # fn main() -> Result<()> {
122/// let sql = SqlBuilder::select_from(baname!("public", "BOOKS"; "b"))
123///     .field(baname!("b", "title"))
124///     .field(baname!("s", "total"))
125///     .left()
126///     .join(baname!("shops"; "s"))
127///     .on_eq(baname!("b", "id"), baname!("s", "book"))
128///     .sql()?;
129///
130/// assert_eq!("SELECT `b`.`title`, `s`.`total` FROM `public`.`BOOKS` AS b LEFT JOIN `shops` AS s ON `b`.`id` = `s`.`book`;", &sql);
131/// # Ok(())
132/// # }
133/// ```
134#[macro_export]
135macro_rules! baname {
136    ( $n:expr ) => {
137        {
138            SqlName::new( $n ).baquoted()
139        }
140    };
141    ( $n:expr, $( $x:expr ),* ) => {
142        {
143            SqlName::new( $n )
144            $(
145                .add( $x )
146            )*
147            .baquoted()
148        }
149    };
150    ( $n:expr; $a:expr ) => {
151        {
152            SqlName::new( $n ).alias( $a ).baquoted()
153        }
154    };
155    ( $n:expr, $( $x:expr ),*; $a:expr ) => {
156        {
157            SqlName::new( $n )
158            $(
159                .add( $x )
160            )*
161            .alias( $a )
162            .baquoted()
163        }
164    };
165}
166
167/// Make brackets quoted name of identifier.
168///
169/// # Examples
170///
171/// ```
172/// #[macro_use] extern crate sql_builder;
173/// # use anyhow::Result;
174/// use sql_builder::{SqlBuilder, SqlName};
175///
176/// # fn main() -> Result<()> {
177/// let sql = SqlBuilder::select_from(brname!("public", "BOOKS"; "b"))
178///     .field(brname!("b", "title"))
179///     .field(brname!("s", "total"))
180///     .left()
181///     .join(brname!("shops"; "s"))
182///     .on_eq(brname!("b", "id"), brname!("s", "book"))
183///     .sql()?;
184///
185/// assert_eq!("SELECT [b].[title], [s].[total] FROM [public].[BOOKS] AS b LEFT JOIN [shops] AS s ON [b].[id] = [s].[book];", &sql);
186/// # Ok(())
187/// # }
188/// ```
189#[macro_export]
190macro_rules! brname {
191    ( $n:expr ) => {
192        {
193            SqlName::new( $n ).brquoted()
194        }
195    };
196    ( $n:expr, $( $x:expr ),* ) => {
197        {
198            SqlName::new( $n )
199            $(
200                .add( $x )
201            )*
202            .brquoted()
203        }
204    };
205    ( $n:expr; $a:expr ) => {
206        {
207            SqlName::new( $n ).alias( $a ).brquoted()
208        }
209    };
210    ( $n:expr, $( $x:expr ),*; $a:expr ) => {
211        {
212            SqlName::new( $n )
213            $(
214                .add( $x )
215            )*
216            .alias( $a )
217            .brquoted()
218        }
219    };
220}
221
222/// Make double quoted name of identifier.
223///
224/// # Examples
225///
226/// ```
227/// #[macro_use] extern crate sql_builder;
228/// # use anyhow::Result;
229/// use sql_builder::{SqlBuilder, SqlName};
230///
231/// # fn main() -> Result<()> {
232/// let sql = SqlBuilder::select_from(dname!("public", "BOOKS"; "b"))
233///     .field(dname!("b", "title"))
234///     .field(dname!("s", "total"))
235///     .left()
236///     .join(dname!("shops"; "s"))
237///     .on_eq(dname!("b", "id"), dname!("s", "book"))
238///     .sql()?;
239///
240/// assert_eq!("SELECT \"b\".\"title\", \"s\".\"total\" FROM \"public\".\"BOOKS\" AS b LEFT JOIN \"shops\" AS s ON \"b\".\"id\" = \"s\".\"book\";", &sql);
241/// # Ok(())
242/// # }
243/// ```
244#[macro_export]
245macro_rules! dname {
246    ( $n:expr ) => {
247        {
248            SqlName::new( $n ).dquoted()
249        }
250    };
251    ( $n:expr, $( $x:expr ),* ) => {
252        {
253            SqlName::new( $n )
254            $(
255                .add( $x )
256            )*
257            .dquoted()
258        }
259    };
260    ( $n:expr; $a:expr ) => {
261        {
262            SqlName::new( $n ).alias( $a ).dquoted()
263        }
264    };
265    ( $n:expr, $( $x:expr ),*; $a:expr ) => {
266        {
267            SqlName::new( $n )
268            $(
269                .add( $x )
270            )*
271            .alias( $a )
272            .dquoted()
273        }
274    };
275}
276
277/// Create safe name of identifier
278///
279/// # Examples
280/// ```
281/// #[macro_use] extern crate sql_builder;
282/// # use anyhow::Result;
283/// use sql_builder::{SqlBuilder, SqlName};
284///
285/// # fn main() -> Result<()> {
286/// let sql = SqlBuilder::select_from(SqlName::new("public").add("BOOKS").alias("b").baquoted())
287///     .field(SqlName::new("b").add("title").baquoted())
288///     .field(SqlName::new("s").add("total").baquoted())
289///     .left()
290///     .join(SqlName::new("shops").alias("s").baquoted())
291///     .on_eq(SqlName::new("b").add("id").baquoted(), SqlName::new("s").add("book").baquoted())
292///     .sql()?;
293///
294/// assert_eq!("SELECT `b`.`title`, `s`.`total` FROM `public`.`BOOKS` AS b LEFT JOIN `shops` AS s ON `b`.`id` = `s`.`book`;", &sql);
295/// # Ok(())
296/// # }
297/// ```
298///
299/// ```
300/// #[macro_use] extern crate sql_builder;
301/// # use anyhow::Result;
302/// use sql_builder::{SqlBuilder, SqlName};
303///
304/// # fn main() -> Result<()> {
305/// let sql = SqlBuilder::select_from(baname!("public", "BOOKS"; "b"))
306///     .field(baname!("b", "title"))
307///     .field(baname!("s", "total"))
308///     .left()
309///     .join(baname!("shops"; "s"))
310///     .on_eq(baname!("b", "id"), baname!("s", "book"))
311///     .sql()?;
312///
313/// assert_eq!("SELECT `b`.`title`, `s`.`total` FROM `public`.`BOOKS` AS b LEFT JOIN `shops` AS s ON `b`.`id` = `s`.`book`;", &sql);
314/// # Ok(())
315/// # }
316/// ```
317#[derive(Clone)]
318pub struct SqlName {
319    parts: Vec<String>,
320    alias: Option<String>,
321}
322
323impl SqlName {
324    /// Name of identifier
325    pub fn new<S: ToString>(name: S) -> Self {
326        Self {
327            parts: vec![name.to_string()],
328            alias: None,
329        }
330    }
331
332    /// Add additional part of identifier
333    pub fn add<S: ToString>(&mut self, name: S) -> &mut Self {
334        self.parts.push(name.to_string());
335        self
336    }
337
338    /// Set an alias for identifier
339    pub fn alias<S: ToString>(&mut self, alias: S) -> &mut Self {
340        self.alias = Some(alias.to_string());
341        self
342    }
343
344    /// Make safe identifier
345    pub fn safe(&self) -> String {
346        let safe_name = self.make_safe_parts().join(".");
347        self.join_with_alias(safe_name)
348    }
349
350    /// Make quoted identifier
351    pub fn quoted(&self) -> String {
352        let safe_name = self
353            .parts
354            .iter()
355            .map(quote)
356            .collect::<Vec<String>>()
357            .join(".");
358        self.join_with_alias(safe_name)
359    }
360
361    /// Make backquoted identifier
362    pub fn baquoted(&self) -> String {
363        let safe_name = self
364            .parts
365            .iter()
366            .map(baquote)
367            .collect::<Vec<String>>()
368            .join(".");
369        self.join_with_alias(safe_name)
370    }
371
372    /// Make bracket-quoted identifier
373    pub fn brquoted(&self) -> String {
374        let safe_name = self
375            .parts
376            .iter()
377            .map(brquote)
378            .collect::<Vec<String>>()
379            .join(".");
380        self.join_with_alias(safe_name)
381    }
382
383    /// Make double quoted identifier
384    pub fn dquoted(&self) -> String {
385        let safe_name = self
386            .parts
387            .iter()
388            .map(dquote)
389            .collect::<Vec<String>>()
390            .join(".");
391        self.join_with_alias(safe_name)
392    }
393
394    /// Join safe name with safe alias
395    fn join_with_alias(&self, safe_name: String) -> String {
396        match &self.alias {
397            Some(alias) => {
398                let safe_alias = Self::make_safe_name(&alias);
399                format!("{} AS {}", safe_name, safe_alias)
400            }
401            None => safe_name,
402        }
403    }
404
405    /// Convert all parts into safe form
406    fn make_safe_parts(&self) -> Vec<String> {
407        if self.all_is_safe() {
408            self.parts.clone()
409        } else {
410            self.parts.iter().map(baquote).collect()
411        }
412    }
413
414    /// Convert name into safe form
415    fn make_safe_name(name: &str) -> String {
416        if Self::is_safe(&name) {
417            name.to_string()
418        } else {
419            baquote(name)
420        }
421    }
422
423    /// Check if name is safe for injection
424    fn is_safe(name: &str) -> bool {
425        name.chars()
426            .all(|c| matches!(c, 'a'..='z' | '0'..='9' | '_'))
427    }
428
429    /// Check if all parts is safe for injection
430    fn all_is_safe(&self) -> bool {
431        self.parts.iter().all(|name| Self::is_safe(&name))
432    }
433}
434
435#[cfg(test)]
436mod tests {
437    use super::*;
438    use anyhow::Result;
439
440    #[test]
441    fn test_simple_name() -> Result<()> {
442        let name = SqlName::new("safe_name").safe();
443        assert_eq!(&name, "safe_name");
444
445        let name = SqlName::new("safe_name").alias("sn").safe();
446        assert_eq!(&name, "safe_name AS sn");
447
448        let name = name!("safe_name");
449        assert_eq!(&name, "safe_name");
450
451        let name = name!("safe_name"; "sn");
452        assert_eq!(&name, "safe_name AS sn");
453
454        Ok(())
455    }
456
457    #[test]
458    fn test_spaced_name() -> Result<()> {
459        let name = SqlName::new("spaced name").safe();
460        assert_eq!(&name, "`spaced name`");
461
462        let name = SqlName::new("spaced name").alias("s n").safe();
463        assert_eq!(&name, "`spaced name` AS `s n`");
464
465        let name = name!("spaced name");
466        assert_eq!(&name, "`spaced name`");
467
468        let name = name!("spaced name"; "s n");
469        assert_eq!(&name, "`spaced name` AS `s n`");
470
471        Ok(())
472    }
473
474    #[test]
475    fn test_quoted_name() -> Result<()> {
476        let name = SqlName::new("some 'awesome' name").quoted();
477        assert_eq!(&name, "'some ''awesome'' name'");
478
479        let name = SqlName::new("some 'awesome' name")
480            .alias("awesome name")
481            .quoted();
482        assert_eq!(&name, "'some ''awesome'' name' AS `awesome name`");
483
484        let name = SqlName::new("some 'awesome' name")
485            .add("sub")
486            .alias("awesome name")
487            .quoted();
488        assert_eq!(&name, "'some ''awesome'' name'.'sub' AS `awesome name`");
489
490        let name = qname!("some 'awesome' name");
491        assert_eq!(&name, "'some ''awesome'' name'");
492
493        let name = qname!("some 'awesome' name"; "awesome name");
494        assert_eq!(&name, "'some ''awesome'' name' AS `awesome name`");
495
496        let name = qname!("some 'awesome' name", "sub"; "awesome name");
497        assert_eq!(&name, "'some ''awesome'' name'.'sub' AS `awesome name`");
498
499        Ok(())
500    }
501
502    #[test]
503    fn test_baquoted_name() -> Result<()> {
504        let name = SqlName::new("safe_name").baquoted();
505        assert_eq!(&name, "`safe_name`");
506
507        let name = SqlName::new("safe_name").alias("sn").baquoted();
508        assert_eq!(&name, "`safe_name` AS sn");
509
510        let name = SqlName::new("safe_name").add("sub").alias("sn").baquoted();
511        assert_eq!(&name, "`safe_name`.`sub` AS sn");
512
513        let name = baname!("safe_name");
514        assert_eq!(&name, "`safe_name`");
515
516        let name = baname!("safe_name"; "sn");
517        assert_eq!(&name, "`safe_name` AS sn");
518
519        let name = baname!("safe_name", "sub"; "sn");
520        assert_eq!(&name, "`safe_name`.`sub` AS sn");
521
522        Ok(())
523    }
524
525    #[test]
526    fn test_brquoted_name() -> Result<()> {
527        let name = SqlName::new("safe_name").brquoted();
528        assert_eq!(&name, "[safe_name]");
529
530        let name = SqlName::new("safe_name").alias("sn").brquoted();
531        assert_eq!(&name, "[safe_name] AS sn");
532
533        let name = SqlName::new("safe_name").add("sub").alias("sn").brquoted();
534        assert_eq!(&name, "[safe_name].[sub] AS sn");
535
536        let name = brname!("safe_name");
537        assert_eq!(&name, "[safe_name]");
538
539        let name = brname!("safe_name"; "sn");
540        assert_eq!(&name, "[safe_name] AS sn");
541
542        let name = brname!("safe_name", "sub"; "sn");
543        assert_eq!(&name, "[safe_name].[sub] AS sn");
544
545        Ok(())
546    }
547
548    #[test]
549    fn test_dquoted_name() -> Result<()> {
550        let name = SqlName::new("safe_name").dquoted();
551        assert_eq!(&name, "\"safe_name\"");
552
553        let name = SqlName::new("safe_name").alias("sn").dquoted();
554        assert_eq!(&name, "\"safe_name\" AS sn");
555
556        let name = SqlName::new("safe_name").add("sub").alias("sn").dquoted();
557        assert_eq!(&name, "\"safe_name\".\"sub\" AS sn");
558
559        let name = dname!("safe_name");
560        assert_eq!(&name, "\"safe_name\"");
561
562        let name = dname!("safe_name"; "sn");
563        assert_eq!(&name, "\"safe_name\" AS sn");
564
565        let name = dname!("safe_name", "sub"; "sn");
566        assert_eq!(&name, "\"safe_name\".\"sub\" AS sn");
567
568        Ok(())
569    }
570}