Dedupe SQL arguments.
This isn't perfect -- we still need to clone in a couple of cases -- but it avoids us passing duplicate strings down into SQLite whenever the same value is mentioned more than once in a query.
This commit is contained in:
parent
a88375fc15
commit
7812a01ffe
2 changed files with 57 additions and 12 deletions
|
@ -381,7 +381,7 @@ fn test_complex_or_join() {
|
||||||
`datoms` AS `datoms03` \
|
`datoms` AS `datoms03` \
|
||||||
WHERE `datoms02`.a = 95 \
|
WHERE `datoms02`.a = 95 \
|
||||||
AND `datoms03`.a = 96 \
|
AND `datoms03`.a = 96 \
|
||||||
AND `datoms03`.v = $v2 \
|
AND `datoms03`.v = $v1 \
|
||||||
AND `datoms02`.v = `datoms03`.e) AS `c00`, \
|
AND `datoms02`.v = `datoms03`.e) AS `c00`, \
|
||||||
`datoms` AS `datoms04`, \
|
`datoms` AS `datoms04`, \
|
||||||
`datoms` AS `datoms05` \
|
`datoms` AS `datoms05` \
|
||||||
|
@ -391,8 +391,7 @@ fn test_complex_or_join() {
|
||||||
AND `c00`.`?page` = `datoms05`.e \
|
AND `c00`.`?page` = `datoms05`.e \
|
||||||
LIMIT 1");
|
LIMIT 1");
|
||||||
assert_eq!(args, vec![make_arg("$v0", "http://foo.com/"),
|
assert_eq!(args, vec![make_arg("$v0", "http://foo.com/"),
|
||||||
make_arg("$v1", "Foo"),
|
make_arg("$v1", "Foo")]);
|
||||||
make_arg("$v2", "Foo")]);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
|
@ -17,11 +17,14 @@ extern crate mentat_core;
|
||||||
|
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
|
|
||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
use ordered_float::OrderedFloat;
|
use ordered_float::OrderedFloat;
|
||||||
|
|
||||||
use mentat_core::{
|
use mentat_core::{
|
||||||
ToMicros,
|
ToMicros,
|
||||||
TypedValue,
|
TypedValue,
|
||||||
|
Uuid,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub use rusqlite::types::Value;
|
pub use rusqlite::types::Value;
|
||||||
|
@ -95,7 +98,14 @@ pub struct SQLiteQueryBuilder {
|
||||||
|
|
||||||
arg_prefix: String,
|
arg_prefix: String,
|
||||||
arg_counter: i64,
|
arg_counter: i64,
|
||||||
args: Vec<(String, Rc<rusqlite::types::Value>)>,
|
|
||||||
|
// We can't just use an InternSet on the rusqlite::types::Value instances, because that
|
||||||
|
// includes f64, so it's not Hash or Eq.
|
||||||
|
// Instead we track UUID and String arguments separately, mapping them to their argument name,
|
||||||
|
// in order to dedupe. We'll add these to the regular argument vector later.
|
||||||
|
uuid_args: HashMap<Rc<Uuid>, String>, // From value to argument name.
|
||||||
|
string_args: HashMap<Rc<String>, String>, // From value to argument name.
|
||||||
|
args: Vec<(String, Rc<rusqlite::types::Value>)>, // (arg, value).
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SQLiteQueryBuilder {
|
impl SQLiteQueryBuilder {
|
||||||
|
@ -108,13 +118,22 @@ impl SQLiteQueryBuilder {
|
||||||
sql: String::new(),
|
sql: String::new(),
|
||||||
arg_prefix: prefix,
|
arg_prefix: prefix,
|
||||||
arg_counter: 0,
|
arg_counter: 0,
|
||||||
|
|
||||||
|
uuid_args: HashMap::default(),
|
||||||
|
string_args: HashMap::default(),
|
||||||
args: vec![],
|
args: vec![],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn push_static_arg(&mut self, val: Rc<rusqlite::types::Value>) {
|
fn next_argument_name(&mut self) -> String {
|
||||||
let arg = format!("{}{}", self.arg_prefix, self.arg_counter);
|
let arg = format!("{}{}", self.arg_prefix, self.arg_counter);
|
||||||
self.arg_counter = self.arg_counter + 1;
|
self.arg_counter = self.arg_counter + 1;
|
||||||
|
arg
|
||||||
|
}
|
||||||
|
|
||||||
|
fn push_static_arg(&mut self, val: Rc<rusqlite::types::Value>) {
|
||||||
|
// TODO: intern these, too.
|
||||||
|
let arg = self.next_argument_name();
|
||||||
self.push_named_arg(arg.as_str());
|
self.push_named_arg(arg.as_str());
|
||||||
self.args.push((arg, val));
|
self.args.push((arg, val));
|
||||||
}
|
}
|
||||||
|
@ -147,18 +166,28 @@ impl QueryBuilder for SQLiteQueryBuilder {
|
||||||
self.push_sql(format!("{}", dt.to_micros()).as_str()); // TODO: argument instead?
|
self.push_sql(format!("{}", dt.to_micros()).as_str()); // TODO: argument instead?
|
||||||
},
|
},
|
||||||
&Uuid(ref u) => {
|
&Uuid(ref u) => {
|
||||||
// Get a byte array.
|
if let Some(arg) = self.uuid_args.get(u).cloned() { // Why, borrow checker, why?!
|
||||||
let bytes = u.as_bytes().clone();
|
self.push_named_arg(arg.as_str());
|
||||||
let v = Rc::new(rusqlite::types::Value::Blob(bytes.to_vec()));
|
} else {
|
||||||
self.push_static_arg(v);
|
let arg = self.next_argument_name();
|
||||||
|
self.push_named_arg(arg.as_str());
|
||||||
|
self.uuid_args.insert(Rc::new(u.clone()), arg);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
// These are both `Rc`. Unfortunately, we can't use that fact when
|
// These are both `Rc`. Unfortunately, we can't use that fact when
|
||||||
// turning these into rusqlite Values.
|
// turning these into rusqlite Values.
|
||||||
|
// However, we can check to see whether there's an existing var that matches…
|
||||||
&String(ref s) => {
|
&String(ref s) => {
|
||||||
let v = Rc::new(rusqlite::types::Value::Text(s.as_ref().clone()));
|
if let Some(arg) = self.string_args.get(s).cloned() {
|
||||||
self.push_static_arg(v);
|
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);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
&Keyword(ref s) => {
|
&Keyword(ref s) => {
|
||||||
|
// TODO: intern.
|
||||||
let v = Rc::new(rusqlite::types::Value::Text(s.as_ref().to_string()));
|
let v = Rc::new(rusqlite::types::Value::Text(s.as_ref().to_string()));
|
||||||
self.push_static_arg(v);
|
self.push_static_arg(v);
|
||||||
},
|
},
|
||||||
|
@ -190,9 +219,26 @@ impl QueryBuilder for SQLiteQueryBuilder {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn finish(self) -> SQLQuery {
|
fn finish(self) -> SQLQuery {
|
||||||
|
// We collected string and UUID arguments into separate maps so that we could
|
||||||
|
// 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())))
|
||||||
|
});
|
||||||
|
let uuid_args = self.uuid_args.into_iter().map(|(val, arg)| {
|
||||||
|
// Get a byte array.
|
||||||
|
let bytes = val.as_bytes().clone();
|
||||||
|
(arg, Rc::new(rusqlite::types::Value::Blob(bytes.to_vec())))
|
||||||
|
});
|
||||||
|
|
||||||
|
args.extend(string_args);
|
||||||
|
args.extend(uuid_args);
|
||||||
|
|
||||||
|
// Get the args in the right order -- $v0, $v1…
|
||||||
|
args.sort_by(|&(ref k1, _), &(ref k2, _)| k1.cmp(k2));
|
||||||
SQLQuery {
|
SQLQuery {
|
||||||
sql: self.sql,
|
sql: self.sql,
|
||||||
args: self.args,
|
args: args,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue