extern crate ordered_float;
extern crate rusqlite;
extern crate core_traits;
extern crate mentat_core;
extern crate sql_traits;
use std::rc::Rc;
use std::collections::HashMap;
use ordered_float::OrderedFloat;
use core_traits::TypedValue;
use sql_traits::errors::{BuildQueryResult, SQLError};
use mentat_core::{ToMicros, ValueRc};
mod conn;
pub use conn::{from_rusqlite, sql_error, sqlite, to_rusqlite, Param, RusqliteRow, SqliteConn};
pub use sql_traits::conn::{Dialect, SqlConn, SqlConnExt, SqlError, SqlRow, SqlValue};
pub struct SQLQuery {
pub sql: String,
pub args: Vec<(String, Rc<rusqlite::types::Value>)>,
}
pub trait QueryBuilder {
fn dialect(&self) -> Dialect {
Dialect::Sqlite
}
fn push_sql(&mut self, sql: &str);
fn push_identifier(&mut self, identifier: &str) -> BuildQueryResult;
fn push_typed_value(&mut self, value: &TypedValue) -> BuildQueryResult;
fn push_bind_param(&mut self, name: &str) -> BuildQueryResult;
fn finish(self) -> SQLQuery;
}
pub trait QueryFragment {
fn push_sql(&self, out: &mut dyn QueryBuilder) -> BuildQueryResult;
}
impl QueryFragment for Box<dyn QueryFragment> {
fn push_sql(&self, out: &mut dyn QueryBuilder) -> BuildQueryResult {
QueryFragment::push_sql(&**self, out)
}
}
impl QueryFragment for &dyn QueryFragment {
fn push_sql(&self, out: &mut dyn QueryBuilder) -> BuildQueryResult {
QueryFragment::push_sql(&**self, out)
}
}
impl QueryFragment for () {
fn push_sql(&self, _out: &mut dyn QueryBuilder) -> BuildQueryResult {
Ok(())
}
}
#[derive(Default)]
pub struct SQLiteQueryBuilder {
pub sql: String,
arg_prefix: String,
arg_counter: i64,
byte_args: HashMap<Vec<u8>, String>, string_args: HashMap<ValueRc<String>, String>, args: Vec<(String, Rc<rusqlite::types::Value>)>,
dialect: Option<Dialect>,
}
impl SQLiteQueryBuilder {
pub fn new() -> Self {
SQLiteQueryBuilder::with_prefix("$v".to_string())
}
pub fn with_prefix(prefix: String) -> Self {
SQLiteQueryBuilder {
sql: String::new(),
arg_prefix: prefix,
arg_counter: 0,
byte_args: HashMap::default(),
string_args: HashMap::default(),
args: vec![],
dialect: None,
}
}
pub fn set_dialect(&mut self, dialect: Dialect) {
self.dialect = Some(dialect);
}
fn next_argument_name(&mut self) -> String {
let arg = format!("{}{}", self.arg_prefix, self.arg_counter);
self.arg_counter += 1;
arg
}
fn push_static_arg(&mut self, val: Rc<rusqlite::types::Value>) {
let arg = self.next_argument_name();
self.push_named_arg(arg.as_str());
self.args.push((arg, val));
}
fn push_named_arg(&mut self, arg: &str) {
self.push_sql(arg);
}
}
impl QueryBuilder for SQLiteQueryBuilder {
fn dialect(&self) -> Dialect {
self.dialect.unwrap_or(Dialect::Sqlite)
}
fn push_sql(&mut self, sql: &str) {
self.sql.push_str(sql);
}
fn push_identifier(&mut self, identifier: &str) -> BuildQueryResult {
if self.dialect() == Dialect::DuckDb {
self.push_sql("\"");
self.push_sql(&identifier.replace('"', "\"\""));
self.push_sql("\"");
return Ok(());
}
self.push_sql("`");
self.push_sql(&identifier.replace("`", "``"));
self.push_sql("`");
Ok(())
}
fn push_typed_value(&mut self, value: &TypedValue) -> BuildQueryResult {
use crate::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()),
Double(OrderedFloat(v)) => {
self.push_sql(format!("{:e}", v).as_str());
}
Instant(dt) => {
self.push_sql(format!("{}", dt.to_micros()).as_str()); }
Uuid(ref u) => {
let bytes = u.as_bytes();
if let Some(arg) = self.byte_args.get(bytes.as_ref()).cloned() {
self.push_named_arg(arg.as_str());
} else {
let arg = self.next_argument_name();
self.push_named_arg(arg.as_str());
self.byte_args.insert(bytes.clone().to_vec(), arg);
}
}
String(ref s) => {
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);
}
}
Keyword(ref s) => {
let v = Rc::new(rusqlite::types::Value::Text(s.as_ref().to_string()));
self.push_static_arg(v);
}
Bytes(b) => {
let bytes = b.to_vec();
if let Some(arg) = self.byte_args.get(&bytes).cloned() {
self.push_named_arg(arg.as_str());
} else {
let arg = self.next_argument_name();
self.push_named_arg(arg.as_str());
self.byte_args.insert(bytes, arg);
}
}
}
Ok(())
}
fn push_bind_param(&mut self, name: &str) -> BuildQueryResult {
if !name.chars().all(|c| char::is_alphanumeric(c) || c == '_') {
return Err(SQLError::InvalidParameterName(name.to_string()));
}
if name.starts_with(self.arg_prefix.as_str())
&& name
.chars()
.skip(self.arg_prefix.len())
.all(char::is_numeric)
{
return Err(SQLError::BindParamCouldBeGenerated(name.to_string()));
}
self.push_sql("$");
self.push_sql(name);
Ok(())
}
fn finish(self) -> SQLQuery {
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 byte_args = self
.byte_args
.into_iter()
.map(|(val, arg)| (arg, Rc::new(rusqlite::types::Value::Blob(val))));
args.extend(string_args);
args.extend(byte_args);
args.sort_by(|(k1, _), (k2, _)| k1.cmp(k2));
SQLQuery {
sql: self.sql,
args,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
fn string_arg(s: &str) -> Rc<rusqlite::types::Value> {
Rc::new(rusqlite::types::Value::Text(s.to_string()))
}
#[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(" = ");
s.push_static_arg(string_arg("frobnicate"));
s.push_sql(" OR ");
s.push_static_arg(string_arg("swoogle"));
s.push_sql(" OR ");
s.push_identifier("bar").unwrap();
s.push_sql(" = ");
s.push_typed_value(&TypedValue::Double(1.0.into())).unwrap();
let q = s.finish();
assert_eq!(
q.sql.as_str(),
"SELECT `foo` WHERE `bar` = $v0 OR $v1 OR `bar` = 1e0"
);
assert_eq!(
q.args,
vec![
("$v0".to_string(), string_arg("frobnicate")),
("$v1".to_string(), string_arg("swoogle"))
]
);
}
}