2017-02-16 23:07:52 +00:00
|
|
|
// Copyright 2016 Mozilla
|
|
|
|
//
|
|
|
|
// Licensed under the Apache License, Version 2.0 (the "License"); you may not use
|
|
|
|
// this file except in compliance with the License. You may obtain a copy of the
|
|
|
|
// License at http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
// Unless required by applicable law or agreed to in writing, software distributed
|
|
|
|
// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
|
|
|
// CONDITIONS OF ANY KIND, either express or implied. See the License for the
|
|
|
|
// specific language governing permissions and limitations under the License.
|
2018-06-01 02:30:25 +00:00
|
|
|
extern crate failure;
|
2017-02-16 23:07:52 +00:00
|
|
|
|
2018-06-01 02:30:25 +00:00
|
|
|
#[macro_use] extern crate failure_derive;
|
2017-02-22 02:47:06 +00:00
|
|
|
extern crate ordered_float;
|
2017-04-29 03:11:55 +00:00
|
|
|
extern crate rusqlite;
|
|
|
|
|
2017-02-22 02:47:06 +00:00
|
|
|
extern crate mentat_core;
|
|
|
|
|
Use Rc for TypedValue, Variable, and query Ident keywords. (#395) r=nalexander
Part 1, core: use Rc for String and Keyword.
Part 2, query: use Rc for Variable.
Part 3, sql: use Rc for args in SQLiteQueryBuilder.
Part 4, query-algebrizer: use Rc.
Part 5, db: use Rc.
Part 6, query-parser: use Rc.
Part 7, query-projector: use Rc.
Part 8, query-translator: use Rc.
Part 9, top level: use Rc.
Part 10: intern Ident and IdentOrKeyword.
2017-03-29 20:18:17 +00:00
|
|
|
use std::rc::Rc;
|
|
|
|
|
2017-06-07 01:21:07 +00:00
|
|
|
use std::collections::HashMap;
|
|
|
|
|
2017-02-22 02:47:06 +00:00
|
|
|
use ordered_float::OrderedFloat;
|
|
|
|
|
2017-04-29 03:11:55 +00:00
|
|
|
use mentat_core::{
|
|
|
|
ToMicros,
|
|
|
|
TypedValue,
|
2018-04-25 21:23:27 +00:00
|
|
|
ValueRc,
|
2017-04-29 03:11:55 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
pub use rusqlite::types::Value;
|
2017-02-22 02:47:06 +00:00
|
|
|
|
2018-06-01 02:30:25 +00:00
|
|
|
#[derive(Debug, Fail)]
|
|
|
|
pub enum SQLError {
|
|
|
|
#[fail(display = "invalid parameter name: {}", _0)]
|
|
|
|
InvalidParameterName(String),
|
2017-02-16 23:07:52 +00:00
|
|
|
|
2018-06-01 02:30:25 +00:00
|
|
|
#[fail(display = "parameter name could be generated: '{}'", _0)]
|
|
|
|
BindParamCouldBeGenerated(String)
|
2017-02-22 02:47:06 +00:00
|
|
|
}
|
|
|
|
|
2018-06-01 02:30:25 +00:00
|
|
|
pub type BuildQueryResult = Result<(), SQLError>;
|
2017-02-24 03:52:17 +00:00
|
|
|
|
2017-02-22 02:47:06 +00:00
|
|
|
/// We want to accumulate values that will later be substituted into a SQL statement execution.
|
|
|
|
/// This struct encapsulates the generated string and the _initial_ argument list.
|
|
|
|
/// Additional user-supplied argument bindings, with their placeholders accumulated via
|
|
|
|
/// `push_bind_param`, will be appended to this argument list.
|
|
|
|
pub struct SQLQuery {
|
|
|
|
pub sql: String,
|
|
|
|
|
|
|
|
/// These will eventually perhaps be rusqlite `ToSql` instances.
|
2017-04-29 03:11:55 +00:00
|
|
|
pub args: Vec<(String, Rc<rusqlite::types::Value>)>,
|
2017-02-22 02:47:06 +00:00
|
|
|
}
|
|
|
|
|
2017-02-16 23:07:52 +00:00
|
|
|
/// Gratefully based on Diesel's QueryBuilder trait:
|
|
|
|
/// https://github.com/diesel-rs/diesel/blob/4885f61b8205f7f3c2cfa03837ed6714831abe6b/diesel/src/query_builder/mod.rs#L56
|
|
|
|
pub trait QueryBuilder {
|
|
|
|
fn push_sql(&mut self, sql: &str);
|
|
|
|
fn push_identifier(&mut self, identifier: &str) -> BuildQueryResult;
|
2017-02-22 02:47:06 +00:00
|
|
|
fn push_typed_value(&mut self, value: &TypedValue) -> BuildQueryResult;
|
2017-02-24 03:52:17 +00:00
|
|
|
fn push_bind_param(&mut self, name: &str) -> BuildQueryResult;
|
2017-02-22 02:47:06 +00:00
|
|
|
fn finish(self) -> SQLQuery;
|
2017-02-16 23:07:52 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
pub trait QueryFragment {
|
|
|
|
fn push_sql(&self, out: &mut QueryBuilder) -> BuildQueryResult;
|
|
|
|
}
|
|
|
|
|
|
|
|
impl QueryFragment for Box<QueryFragment> {
|
|
|
|
fn push_sql(&self, out: &mut QueryBuilder) -> BuildQueryResult {
|
|
|
|
QueryFragment::push_sql(&**self, out)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl<'a> QueryFragment for &'a QueryFragment {
|
|
|
|
fn push_sql(&self, out: &mut QueryBuilder) -> BuildQueryResult {
|
|
|
|
QueryFragment::push_sql(&**self, out)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl QueryFragment for () {
|
|
|
|
fn push_sql(&self, _out: &mut QueryBuilder) -> BuildQueryResult {
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// A QueryBuilder that implements SQLite's specific escaping rules.
|
|
|
|
pub struct SQLiteQueryBuilder {
|
|
|
|
pub sql: String,
|
2017-02-22 02:47:06 +00:00
|
|
|
|
|
|
|
arg_prefix: String,
|
|
|
|
arg_counter: i64,
|
2017-06-07 01:21:07 +00:00
|
|
|
|
|
|
|
// We can't just use an InternSet on the rusqlite::types::Value instances, because that
|
|
|
|
// includes f64, so it's not Hash or Eq.
|
2017-06-07 18:55:05 +00:00
|
|
|
// Instead we track byte and String arguments separately, mapping them to their argument name,
|
2017-06-07 01:21:07 +00:00
|
|
|
// in order to dedupe. We'll add these to the regular argument vector later.
|
2017-06-07 18:55:05 +00:00
|
|
|
byte_args: HashMap<Vec<u8>, String>, // From value to argument name.
|
2018-04-25 21:23:27 +00:00
|
|
|
string_args: HashMap<ValueRc<String>, String>, // From value to argument name.
|
2017-06-07 01:21:07 +00:00
|
|
|
args: Vec<(String, Rc<rusqlite::types::Value>)>, // (arg, value).
|
2017-02-16 23:07:52 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
impl SQLiteQueryBuilder {
|
|
|
|
pub fn new() -> Self {
|
2017-02-22 02:47:06 +00:00
|
|
|
SQLiteQueryBuilder::with_prefix("$v".to_string())
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn with_prefix(prefix: String) -> Self {
|
2017-02-16 23:07:52 +00:00
|
|
|
SQLiteQueryBuilder {
|
|
|
|
sql: String::new(),
|
2017-02-22 02:47:06 +00:00
|
|
|
arg_prefix: prefix,
|
|
|
|
arg_counter: 0,
|
2017-06-07 01:21:07 +00:00
|
|
|
|
2017-06-07 18:55:05 +00:00
|
|
|
byte_args: HashMap::default(),
|
2017-06-07 01:21:07 +00:00
|
|
|
string_args: HashMap::default(),
|
2017-02-22 02:47:06 +00:00
|
|
|
args: vec![],
|
2017-02-16 23:07:52 +00:00
|
|
|
}
|
|
|
|
}
|
2017-02-22 02:47:06 +00:00
|
|
|
|
2017-06-07 01:21:07 +00:00
|
|
|
fn next_argument_name(&mut self) -> String {
|
2017-02-22 02:47:06 +00:00
|
|
|
let arg = format!("{}{}", self.arg_prefix, self.arg_counter);
|
|
|
|
self.arg_counter = self.arg_counter + 1;
|
2017-06-07 01:21:07 +00:00
|
|
|
arg
|
|
|
|
}
|
|
|
|
|
|
|
|
fn push_static_arg(&mut self, val: Rc<rusqlite::types::Value>) {
|
|
|
|
// TODO: intern these, too.
|
|
|
|
let arg = self.next_argument_name();
|
2017-02-22 02:47:06 +00:00
|
|
|
self.push_named_arg(arg.as_str());
|
|
|
|
self.args.push((arg, val));
|
|
|
|
}
|
|
|
|
|
|
|
|
fn push_named_arg(&mut self, arg: &str) {
|
|
|
|
self.push_sql(arg);
|
|
|
|
}
|
2017-02-16 23:07:52 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
impl QueryBuilder for SQLiteQueryBuilder {
|
|
|
|
fn push_sql(&mut self, sql: &str) {
|
|
|
|
self.sql.push_str(sql);
|
|
|
|
}
|
|
|
|
|
|
|
|
fn push_identifier(&mut self, identifier: &str) -> BuildQueryResult {
|
|
|
|
self.push_sql("`");
|
|
|
|
self.push_sql(&identifier.replace("`", "``"));
|
|
|
|
self.push_sql("`");
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
2017-02-22 02:47:06 +00:00
|
|
|
fn push_typed_value(&mut self, value: &TypedValue) -> BuildQueryResult {
|
|
|
|
use TypedValue::*;
|
|
|
|
match value {
|
|
|
|
&Ref(entid) => self.push_sql(entid.to_string().as_str()),
|
|
|
|
&Boolean(v) => self.push_sql(if v { "1" } else { "0" }),
|
|
|
|
&Long(v) => self.push_sql(v.to_string().as_str()),
|
2017-06-14 00:26:53 +00:00
|
|
|
&Double(OrderedFloat(v)) => {
|
|
|
|
// Rust's floats print without a trailing '.' in some cases.
|
|
|
|
// https://github.com/rust-lang/rust/issues/30967
|
|
|
|
// We format with 'e' -- scientific notation -- so that SQLite treats them as
|
|
|
|
// floats and not integers. This is most noticeable for fulltext scores, which
|
|
|
|
// will currently (2017-06) always be 0, and need to round-trip as doubles.
|
|
|
|
self.push_sql(format!("{:e}", v).as_str());
|
|
|
|
},
|
2017-04-29 03:11:55 +00:00
|
|
|
&Instant(dt) => {
|
|
|
|
self.push_sql(format!("{}", dt.to_micros()).as_str()); // TODO: argument instead?
|
|
|
|
},
|
|
|
|
&Uuid(ref u) => {
|
2017-06-07 18:55:05 +00:00
|
|
|
let bytes = u.as_bytes();
|
|
|
|
if let Some(arg) = self.byte_args.get(bytes.as_ref()).cloned() { // Why, borrow checker, why?!
|
2017-06-07 01:21:07 +00:00
|
|
|
self.push_named_arg(arg.as_str());
|
|
|
|
} else {
|
|
|
|
let arg = self.next_argument_name();
|
|
|
|
self.push_named_arg(arg.as_str());
|
2017-06-07 18:55:05 +00:00
|
|
|
self.byte_args.insert(bytes.clone().to_vec(), arg);
|
2017-06-07 01:21:07 +00:00
|
|
|
}
|
2017-04-29 03:11:55 +00:00
|
|
|
},
|
|
|
|
// These are both `Rc`. Unfortunately, we can't use that fact when
|
|
|
|
// turning these into rusqlite Values.
|
2017-06-07 01:21:07 +00:00
|
|
|
// However, we can check to see whether there's an existing var that matches…
|
2017-04-29 03:11:55 +00:00
|
|
|
&String(ref s) => {
|
2017-06-07 01:21:07 +00:00
|
|
|
if let Some(arg) = self.string_args.get(s).cloned() {
|
|
|
|
self.push_named_arg(arg.as_str());
|
|
|
|
} else {
|
|
|
|
let arg = self.next_argument_name();
|
|
|
|
self.push_named_arg(arg.as_str());
|
|
|
|
self.string_args.insert(s.clone(), arg);
|
|
|
|
}
|
2017-04-29 03:11:55 +00:00
|
|
|
},
|
|
|
|
&Keyword(ref s) => {
|
2017-06-07 01:21:07 +00:00
|
|
|
// TODO: intern.
|
2017-04-29 03:11:55 +00:00
|
|
|
let v = Rc::new(rusqlite::types::Value::Text(s.as_ref().to_string()));
|
|
|
|
self.push_static_arg(v);
|
|
|
|
},
|
2017-02-22 02:47:06 +00:00
|
|
|
}
|
|
|
|
Ok(())
|
2017-02-16 23:07:52 +00:00
|
|
|
}
|
|
|
|
|
2017-02-22 02:47:06 +00:00
|
|
|
/// Our bind parameters will be interleaved with pushed `TypedValue` instances. That means we
|
|
|
|
/// need to use named parameters, not positional parameters.
|
|
|
|
/// The `name` argument to this method is expected to be alphanumeric. If not, this method
|
|
|
|
/// returns an `InvalidParameterName` error result.
|
|
|
|
/// Callers should make sure that the name doesn't overlap with generated parameter names. If
|
|
|
|
/// it does, `BindParamCouldBeGenerated` is the error.
|
2017-02-24 03:52:17 +00:00
|
|
|
fn push_bind_param(&mut self, name: &str) -> BuildQueryResult {
|
2017-02-22 02:47:06 +00:00
|
|
|
// Do some validation first.
|
|
|
|
// This is not free, but it's probably worth it for now.
|
2017-04-19 23:16:19 +00:00
|
|
|
if !name.chars().all(|c| char::is_alphanumeric(c) || c == '_') {
|
2018-06-01 02:30:25 +00:00
|
|
|
return Err(SQLError::InvalidParameterName(name.to_string()))
|
2017-02-22 02:47:06 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if name.starts_with(self.arg_prefix.as_str()) &&
|
|
|
|
name.chars().skip(self.arg_prefix.len()).all(char::is_numeric) {
|
2018-06-01 02:30:25 +00:00
|
|
|
return Err(SQLError::BindParamCouldBeGenerated(name.to_string()))
|
2017-02-22 02:47:06 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
self.push_sql("$");
|
|
|
|
self.push_sql(name);
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
|
|
|
fn finish(self) -> SQLQuery {
|
2017-06-07 18:55:05 +00:00
|
|
|
// We collected string and byte arguments into separate maps so that we could
|
2017-06-07 01:21:07 +00:00
|
|
|
// dedupe them. Now we need to turn them into rusqlite Values.
|
|
|
|
let mut args = self.args;
|
|
|
|
let string_args = self.string_args.into_iter().map(|(val, arg)| {
|
|
|
|
(arg, Rc::new(rusqlite::types::Value::Text(val.as_ref().clone())))
|
|
|
|
});
|
2017-06-07 18:55:05 +00:00
|
|
|
let byte_args = self.byte_args.into_iter().map(|(val, arg)| {
|
|
|
|
(arg, Rc::new(rusqlite::types::Value::Blob(val)))
|
2017-06-07 01:21:07 +00:00
|
|
|
});
|
|
|
|
|
|
|
|
args.extend(string_args);
|
2017-06-07 18:55:05 +00:00
|
|
|
args.extend(byte_args);
|
2017-06-07 01:21:07 +00:00
|
|
|
|
|
|
|
// Get the args in the right order -- $v0, $v1…
|
|
|
|
args.sort_by(|&(ref k1, _), &(ref k2, _)| k1.cmp(k2));
|
2017-02-22 02:47:06 +00:00
|
|
|
SQLQuery {
|
|
|
|
sql: self.sql,
|
2017-06-07 01:21:07 +00:00
|
|
|
args: args,
|
2017-02-22 02:47:06 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#[cfg(test)]
|
|
|
|
mod tests {
|
|
|
|
use super::*;
|
|
|
|
|
2017-04-29 03:11:55 +00:00
|
|
|
fn string_arg(s: &str) -> Rc<rusqlite::types::Value> {
|
|
|
|
Rc::new(rusqlite::types::Value::Text(s.to_string()))
|
|
|
|
}
|
|
|
|
|
2017-02-22 02:47:06 +00:00
|
|
|
#[test]
|
|
|
|
fn test_sql() {
|
|
|
|
let mut s = SQLiteQueryBuilder::new();
|
|
|
|
s.push_sql("SELECT ");
|
|
|
|
s.push_identifier("foo").unwrap();
|
|
|
|
s.push_sql(" WHERE ");
|
|
|
|
s.push_identifier("bar").unwrap();
|
|
|
|
s.push_sql(" = ");
|
2017-04-29 03:11:55 +00:00
|
|
|
s.push_static_arg(string_arg("frobnicate"));
|
2017-02-22 02:47:06 +00:00
|
|
|
s.push_sql(" OR ");
|
2017-04-29 03:11:55 +00:00
|
|
|
s.push_static_arg(string_arg("swoogle"));
|
2017-06-14 00:26:53 +00:00
|
|
|
s.push_sql(" OR ");
|
|
|
|
s.push_identifier("bar").unwrap();
|
|
|
|
s.push_sql(" = ");
|
|
|
|
s.push_typed_value(&TypedValue::Double(1.0.into())).unwrap();
|
2017-02-22 02:47:06 +00:00
|
|
|
let q = s.finish();
|
|
|
|
|
2017-06-14 00:26:53 +00:00
|
|
|
assert_eq!(q.sql.as_str(), "SELECT `foo` WHERE `bar` = $v0 OR $v1 OR `bar` = 1e0");
|
2017-02-22 02:47:06 +00:00
|
|
|
assert_eq!(q.args,
|
2017-04-29 03:11:55 +00:00
|
|
|
vec![("$v0".to_string(), string_arg("frobnicate")),
|
|
|
|
("$v1".to_string(), string_arg("swoogle"))]);
|
2017-02-16 23:07:52 +00:00
|
|
|
}
|
|
|
|
}
|