toql_core/
sql.rs

1//! A raw SQL statement.
2
3use crate::sql_arg::SqlArg;
4
5///  A tuple to hold a raw SQL statement and the SQL arguments.
6/// `Sql` is the result from the [Resolver](crate::sql_expr::resolver::Resolver)
7/// and is ready to be sent to the database.
8#[derive(Debug)]
9pub struct Sql(pub String, pub Vec<SqlArg>);
10
11impl Sql {
12    /// Builds a string with all arguments inlined.
13    ///
14    /// While the string could technically be sent to a database
15    /// never do this, because of the risk of SQL injection!
16    /// The string is should only be used for debugging and logging purposes.
17    pub fn to_unsafe_string(&self) -> String {
18        fn parse_and_replace(position: &str, args: &[SqlArg], unsafe_string: &mut String) {
19            let pos: Result<usize, _> = position.parse();
20            match pos {
21                Ok(pos) => {
22                    let arg: Option<&SqlArg> = args.get(pos - 1); // Positional argument start from 1
23                    match arg {
24                        Some(v) => unsafe_string.push_str(&v.to_sql_string()),
25                        None => {
26                            unsafe_string.push('$');
27                            unsafe_string.push_str(&position);
28                        }
29                    }
30                }
31                _ => {
32                    unsafe_string.push('$');
33                    unsafe_string.push_str(&position);
34                }
35            }
36        }
37
38        let mut quoted = false;
39        // Replace every ? with param
40        // Replace every $1 with param 1
41        // Respect quoting incl. quoted quotes
42        // If params are missing they are not replaced
43        let mut params = self.1.iter();
44        let mut position_parsing = false;
45        let mut position = String::with_capacity(8);
46        let mut unsafe_string: String = String::new();
47
48        for c in self.0.chars() {
49            match c {
50                '\'' => {
51                    quoted = !quoted;
52                    unsafe_string.push('\'');
53                }
54                '?' if !quoted => {
55                    match params.next() {
56                        Some(p) => unsafe_string.push_str(&p.to_sql_string()),
57                        None => unsafe_string.push('?'),
58                    };
59                }
60                '$' if !quoted => {
61                    position_parsing = true;
62                }
63                ' ' if position_parsing => {
64                    parse_and_replace(&position, &self.1, &mut unsafe_string);
65                    unsafe_string.push(' ');
66                    position.clear();
67                    position_parsing = false;
68                }
69                _ if position_parsing => position.push(c),
70                _ => unsafe_string.push(c),
71            }
72        }
73
74        // If string ends with position parsing, process position
75        if position_parsing {
76            parse_and_replace(&position, &self.1, &mut unsafe_string);
77        }
78
79        unsafe_string
80    }
81    /// Add `Sql` at the end.
82    pub fn append(&mut self, sql: &Sql) {
83        self.0.push_str(&sql.0);
84        self.1.extend_from_slice(&sql.1);
85    }
86    /// Add a literal string at the end.
87    pub fn push_literal(&mut self, sql_lit: &str) {
88        self.0.push_str(&sql_lit);
89    }
90    /// Remove a number of characters from the end.
91    pub fn pop_literals(&mut self, count: u8) {
92        for _ in 0..count {
93            self.0.pop();
94        }
95    }
96    /// Create a new empty SQL statement
97    pub fn new() -> Self {
98        Sql(String::new(), Vec::new())
99    }
100    /// Returns true, if statement is empty
101    pub fn is_empty(&self) -> bool {
102        self.0.is_empty()
103    }
104}
105
106impl Default for Sql {
107    fn default() -> Self {
108        Self::new()
109    }
110}
111
112#[cfg(test)]
113mod test {
114    use super::{Sql, SqlArg};
115
116    #[test]
117    fn to_unsafe_string() {
118        assert_eq!(
119            Sql("SELECT ?".to_string(), vec![SqlArg::U64(1)]).to_unsafe_string(),
120            "SELECT 1"
121        );
122        assert_eq!(
123            Sql("SELECT ? FROM".to_string(), vec![SqlArg::U64(1)]).to_unsafe_string(),
124            "SELECT 1 FROM"
125        );
126        assert_eq!(
127            Sql("SELECT $1 FROM".to_string(), vec![SqlArg::U64(1)]).to_unsafe_string(),
128            "SELECT 1 FROM"
129        );
130        assert_eq!(
131            Sql("SELECT $1".to_string(), vec![SqlArg::U64(1)]).to_unsafe_string(),
132            "SELECT 1"
133        );
134        assert_eq!(
135            Sql("SELECT $1".to_string(), vec![]).to_unsafe_string(),
136            "SELECT $1"
137        );
138        assert_eq!(
139            Sql("SELECT $a".to_string(), vec![]).to_unsafe_string(),
140            "SELECT $a"
141        );
142        assert_eq!(
143            Sql("SELECT '?'".to_string(), vec![SqlArg::U64(1)]).to_unsafe_string(),
144            "SELECT '?'"
145        );
146        assert_eq!(
147            Sql("SELECT '''?'".to_string(), vec![SqlArg::U64(1)]).to_unsafe_string(),
148            "SELECT '''?'"
149        );
150    }
151
152    #[test]
153    fn build() {
154        assert_eq!(Sql::default().to_unsafe_string(), "");
155        assert_eq!(Sql::default().is_empty(), true);
156        assert_eq!(Sql::new().is_empty(), true);
157
158        let mut s = Sql::new();
159        s.push_literal("SELECT 1 FROM");
160        assert_eq!(s.to_unsafe_string(), "SELECT 1 FROM");
161        s.pop_literals(5);
162        assert_eq!(s.to_unsafe_string(), "SELECT 1");
163
164        let mut s1 = Sql("SELECT ?".to_string(), vec![SqlArg::U64(1)]);
165        let s2 = Sql(", ?".to_string(), vec![SqlArg::U64(2)]);
166        s1.append(&s2);
167        assert_eq!(s1.to_unsafe_string(), "SELECT 1, 2");
168    }
169}