Skip to main content

mentat_sql/
lib.rs

1// Copyright 2016-2018 Mozilla
2//
3// Licensed under the Apache License, Version 2.0 (the "License"); you may not use
4// this file except in compliance with the License. You may obtain a copy of the
5// License at http://www.apache.org/licenses/LICENSE-2.0
6// Unless required by applicable law or agreed to in writing, software distributed
7// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
8// CONDITIONS OF ANY KIND, either express or implied. See the License for the
9// specific language governing permissions and limitations under the License.
10
11extern crate ordered_float;
12extern crate rusqlite;
13
14extern crate core_traits;
15extern crate mentat_core;
16extern crate sql_traits;
17
18use std::rc::Rc;
19
20use std::collections::HashMap;
21
22use ordered_float::OrderedFloat;
23
24use core_traits::TypedValue;
25
26use sql_traits::errors::{BuildQueryResult, SQLError};
27
28use mentat_core::{ToMicros, ValueRc};
29
30mod conn;
31pub use conn::{from_rusqlite, sql_error, sqlite, to_rusqlite, Param, RusqliteRow, SqliteConn};
32pub use sql_traits::conn::{Dialect, SqlConn, SqlConnExt, SqlError, SqlRow, SqlValue};
33
34/// We want to accumulate values that will later be substituted into a SQL statement execution.
35/// This struct encapsulates the generated string and the _initial_ argument list.
36/// Additional user-supplied argument bindings, with their placeholders accumulated via
37/// `push_bind_param`, will be appended to this argument list.
38pub struct SQLQuery {
39    pub sql: String,
40
41    /// These will eventually perhaps be rusqlite `ToSql` instances.
42    pub args: Vec<(String, Rc<rusqlite::types::Value>)>,
43}
44
45/// Gratefully based on Diesel's QueryBuilder trait:
46/// https://github.com/diesel-rs/diesel/blob/4885f61b8205f7f3c2cfa03837ed6714831abe6b/diesel/src/query_builder/mod.rs#L56
47pub trait QueryBuilder {
48    /// The SQL dialect being generated. SQLite unless the builder was told
49    /// otherwise; fragments branch on it only where DuckDB differs.
50    fn dialect(&self) -> Dialect {
51        Dialect::Sqlite
52    }
53    fn push_sql(&mut self, sql: &str);
54    fn push_identifier(&mut self, identifier: &str) -> BuildQueryResult;
55    fn push_typed_value(&mut self, value: &TypedValue) -> BuildQueryResult;
56    fn push_bind_param(&mut self, name: &str) -> BuildQueryResult;
57    fn finish(self) -> SQLQuery;
58}
59
60pub trait QueryFragment {
61    fn push_sql(&self, out: &mut dyn QueryBuilder) -> BuildQueryResult;
62}
63
64impl QueryFragment for Box<dyn QueryFragment> {
65    fn push_sql(&self, out: &mut dyn QueryBuilder) -> BuildQueryResult {
66        QueryFragment::push_sql(&**self, out)
67    }
68}
69
70impl QueryFragment for &dyn QueryFragment {
71    fn push_sql(&self, out: &mut dyn QueryBuilder) -> BuildQueryResult {
72        QueryFragment::push_sql(&**self, out)
73    }
74}
75
76impl QueryFragment for () {
77    fn push_sql(&self, _out: &mut dyn QueryBuilder) -> BuildQueryResult {
78        Ok(())
79    }
80}
81
82/// A QueryBuilder that implements SQLite's specific escaping rules.
83#[derive(Default)]
84pub struct SQLiteQueryBuilder {
85    pub sql: String,
86
87    arg_prefix: String,
88    arg_counter: i64,
89
90    // We can't just use an InternSet on the rusqlite::types::Value instances, because that
91    // includes f64, so it's not Hash or Eq.
92    // Instead, we track the byte and String arguments separately, mapping them to their argument name,
93    // in order to dedupe. We'll add these to the regular argument vector later.
94    byte_args: HashMap<Vec<u8>, String>, // From value to argument name.
95    string_args: HashMap<ValueRc<String>, String>, // From value to argument name.
96    args: Vec<(String, Rc<rusqlite::types::Value>)>, // (arg, value).
97
98    dialect: Option<Dialect>,
99}
100
101impl SQLiteQueryBuilder {
102    pub fn new() -> Self {
103        SQLiteQueryBuilder::with_prefix("$v".to_string())
104    }
105
106    pub fn with_prefix(prefix: String) -> Self {
107        SQLiteQueryBuilder {
108            sql: String::new(),
109            arg_prefix: prefix,
110            arg_counter: 0,
111
112            byte_args: HashMap::default(),
113            string_args: HashMap::default(),
114            args: vec![],
115            dialect: None,
116        }
117    }
118
119    /// Generate SQL for `dialect` (identifiers, and the few fragments that
120    /// differ). The default is SQLite.
121    pub fn set_dialect(&mut self, dialect: Dialect) {
122        self.dialect = Some(dialect);
123    }
124
125    fn next_argument_name(&mut self) -> String {
126        let arg = format!("{}{}", self.arg_prefix, self.arg_counter);
127        self.arg_counter += 1;
128        arg
129    }
130
131    fn push_static_arg(&mut self, val: Rc<rusqlite::types::Value>) {
132        // TODO: intern these, too.
133        let arg = self.next_argument_name();
134        self.push_named_arg(arg.as_str());
135        self.args.push((arg, val));
136    }
137
138    fn push_named_arg(&mut self, arg: &str) {
139        self.push_sql(arg);
140    }
141}
142
143impl QueryBuilder for SQLiteQueryBuilder {
144    fn dialect(&self) -> Dialect {
145        self.dialect.unwrap_or(Dialect::Sqlite)
146    }
147
148    fn push_sql(&mut self, sql: &str) {
149        self.sql.push_str(sql);
150    }
151
152    fn push_identifier(&mut self, identifier: &str) -> BuildQueryResult {
153        if self.dialect() == Dialect::DuckDb {
154            // DuckDB rejects backticks; standard double quotes.
155            self.push_sql("\"");
156            self.push_sql(&identifier.replace('"', "\"\""));
157            self.push_sql("\"");
158            return Ok(());
159        }
160        self.push_sql("`");
161        self.push_sql(&identifier.replace("`", "``"));
162        self.push_sql("`");
163        Ok(())
164    }
165
166    fn push_typed_value(&mut self, value: &TypedValue) -> BuildQueryResult {
167        use crate::TypedValue::*;
168        match value {
169            Ref(entid) => self.push_sql(entid.to_string().as_str()),
170            Boolean(v) => self.push_sql(if *v { "1" } else { "0" }),
171            Long(v) => self.push_sql(v.to_string().as_str()),
172            Double(OrderedFloat(v)) => {
173                // Rust's floats print without a trailing '.' in some cases.
174                // https://github.com/rust-lang/rust/issues/30967
175                // We format with 'e' -- scientific notation -- so that SQLite treats them as
176                // floats and not integers. This is most noticeable for fulltext scores, which
177                // will currently (2017-06) always be 0, and need to round-trip as doubles.
178                self.push_sql(format!("{:e}", v).as_str());
179            }
180            Instant(dt) => {
181                self.push_sql(format!("{}", dt.to_micros()).as_str()); // TODO: argument instead?
182            }
183            Uuid(ref u) => {
184                let bytes = u.as_bytes();
185                if let Some(arg) = self.byte_args.get(bytes.as_ref()).cloned() {
186                    // Why, borrow checker, why?!
187                    self.push_named_arg(arg.as_str());
188                } else {
189                    let arg = self.next_argument_name();
190                    self.push_named_arg(arg.as_str());
191                    self.byte_args.insert(bytes.clone().to_vec(), arg);
192                }
193            }
194            // These are both `Rc`. Unfortunately, we can't use that fact when
195            // turning these into rusqlite Values.
196            // However, we can check to see whether there's an existing var that matches…
197            String(ref s) => {
198                if let Some(arg) = self.string_args.get(s).cloned() {
199                    self.push_named_arg(arg.as_str());
200                } else {
201                    let arg = self.next_argument_name();
202                    self.push_named_arg(arg.as_str());
203                    self.string_args.insert(s.clone(), arg);
204                }
205            }
206            Keyword(ref s) => {
207                // TODO: intern.
208                let v = Rc::new(rusqlite::types::Value::Text(s.as_ref().to_string()));
209                self.push_static_arg(v);
210            }
211            Bytes(b) => {
212                let bytes = b.to_vec();
213                if let Some(arg) = self.byte_args.get(&bytes).cloned() {
214                    // Why, borrow checker, why?!
215                    self.push_named_arg(arg.as_str());
216                } else {
217                    let arg = self.next_argument_name();
218                    self.push_named_arg(arg.as_str());
219                    self.byte_args.insert(bytes, arg);
220                }
221            }
222        }
223        Ok(())
224    }
225
226    /// Our bind parameters will be interleaved with pushed `TypedValue` instances. That means we
227    /// need to use named parameters, not positional parameters.
228    /// The `name` argument to this method is expected to be alphanumeric. If not, this method
229    /// returns an `InvalidParameterName` error result.
230    /// Callers should make sure that the name doesn't overlap with generated parameter names. If
231    /// it does, `BindParamCouldBeGenerated` is the error.
232    fn push_bind_param(&mut self, name: &str) -> BuildQueryResult {
233        // Do some validation first.
234        // This is not free, but it's probably worth it for now.
235        if !name.chars().all(|c| char::is_alphanumeric(c) || c == '_') {
236            return Err(SQLError::InvalidParameterName(name.to_string()));
237        }
238
239        if name.starts_with(self.arg_prefix.as_str())
240            && name
241                .chars()
242                .skip(self.arg_prefix.len())
243                .all(char::is_numeric)
244        {
245            return Err(SQLError::BindParamCouldBeGenerated(name.to_string()));
246        }
247
248        self.push_sql("$");
249        self.push_sql(name);
250        Ok(())
251    }
252
253    fn finish(self) -> SQLQuery {
254        // We collected string and byte arguments into separate maps so that we could
255        // dedupe them. Now we need to turn them into rusqlite Values.
256        let mut args = self.args;
257        let string_args = self.string_args.into_iter().map(|(val, arg)| {
258            (
259                arg,
260                Rc::new(rusqlite::types::Value::Text(val.as_ref().clone())),
261            )
262        });
263        let byte_args = self
264            .byte_args
265            .into_iter()
266            .map(|(val, arg)| (arg, Rc::new(rusqlite::types::Value::Blob(val))));
267
268        args.extend(string_args);
269        args.extend(byte_args);
270
271        // Get the args in the right order -- $v0, $v1…
272        args.sort_by(|(k1, _), (k2, _)| k1.cmp(k2));
273        SQLQuery {
274            sql: self.sql,
275            args,
276        }
277    }
278}
279
280#[cfg(test)]
281mod tests {
282    use super::*;
283
284    fn string_arg(s: &str) -> Rc<rusqlite::types::Value> {
285        Rc::new(rusqlite::types::Value::Text(s.to_string()))
286    }
287
288    #[test]
289    fn test_sql() {
290        let mut s = SQLiteQueryBuilder::new();
291        s.push_sql("SELECT ");
292        s.push_identifier("foo").unwrap();
293        s.push_sql(" WHERE ");
294        s.push_identifier("bar").unwrap();
295        s.push_sql(" = ");
296        s.push_static_arg(string_arg("frobnicate"));
297        s.push_sql(" OR ");
298        s.push_static_arg(string_arg("swoogle"));
299        s.push_sql(" OR ");
300        s.push_identifier("bar").unwrap();
301        s.push_sql(" = ");
302        s.push_typed_value(&TypedValue::Double(1.0.into())).unwrap();
303        let q = s.finish();
304
305        assert_eq!(
306            q.sql.as_str(),
307            "SELECT `foo` WHERE `bar` = $v0 OR $v1 OR `bar` = 1e0"
308        );
309        assert_eq!(
310            q.args,
311            vec![
312                ("$v0".to_string(), string_arg("frobnicate")),
313                ("$v1".to_string(), string_arg("swoogle"))
314            ]
315        );
316    }
317}