refactoring: remove query_entries(), from_sexp into TryFrom, query_to_sqlite is a method

feat/vaults
Tomáš Mládek 2021-02-20 12:39:03 +01:00 committed by Tomáš Mládek
parent 4c1034ee4b
commit 5096e1eece
3 changed files with 199 additions and 239 deletions

View File

@ -11,6 +11,7 @@ use diesel::r2d2::{self, ConnectionManager};
use diesel::sql_types::Bool; use diesel::sql_types::Bool;
use diesel::sqlite::{Sqlite, SqliteConnection}; use diesel::sqlite::{Sqlite, SqliteConnection};
use lexpr::value::Value::Symbol; use lexpr::value::Value::Symbol;
use lexpr::Value;
use lexpr::Value::Cons; use lexpr::Value::Cons;
use log::{debug, trace}; use log::{debug, trace};
use nonempty::NonEmpty; use nonempty::NonEmpty;
@ -275,84 +276,12 @@ pub enum Query {
MultiQuery(MultiQuery), MultiQuery(MultiQuery),
} }
impl Query { type Predicate = dyn BoxableExpression<data::table, Sqlite, SqlType = Bool>;
pub fn from_sexp(expression: &lexpr::Value) -> Result<Self> {
if let Cons(value) = expression {
if let Symbol(symbol) = value.car() {
match symbol.borrow() {
"matches" => {
let (cons_vec, _) = value.clone().into_vec();
if let [_, entity, attribute, value] = &cons_vec[..] {
let entity = Query::parse_component::<Address>(entity)?;
let attribute = Query::parse_component::<String>(attribute)?;
let value = Query::parse_component::<EntryValue>(value)?;
Ok(Query::SingleQuery(QueryPart::Matches(EntryQuery {
entity,
attribute,
value,
})))
} else {
Err(anyhow!(
"Malformed expression: Wrong number of arguments to 'matches'."
))
}
}
"type" => {
let (cons_vec, _) = value.clone().into_vec();
if let [_, type_name] = &cons_vec[..] {
if let lexpr::Value::String(type_name_str) = type_name {
Ok(Query::SingleQuery(QueryPart::Type(
type_name_str.to_string(),
)))
} else {
Err(anyhow!(
"Malformed expression: Type must be specified as a string."
))
}
} else {
Err(anyhow!(
"Malformed expression: Wrong number of arguments to 'type'."
))
}
}
"and" | "or" => {
let (cons_vec, _) = value.clone().into_vec();
let sub_expressions = &cons_vec[1..];
let values: Result<Vec<Box<Query>>> = sub_expressions
.iter()
.map(|value| Ok(Box::new(Query::from_sexp(value)?)))
.collect();
if let Some(queries) = NonEmpty::from_vec(values?) { impl TryFrom<&lexpr::Value> for Query {
Ok(Query::MultiQuery(MultiQuery { type Error = anyhow::Error;
qualifier: match symbol.borrow() {
"and" => QueryQualifier::AND,
_ => QueryQualifier::OR,
},
queries,
}))
} else {
Err(anyhow!(
"Malformed expression: sub-query list cannot be empty.",
))
}
}
_ => Err(anyhow!(format!(
"Malformed expression: Unknown symbol '{}'.",
symbol
))),
}
} else {
Err(anyhow!(format!(
"Malformed expression: Value '{:?}' is not a symbol.",
value
)))
}
} else {
Err(anyhow!("Malformed expression: Not a list."))
}
}
fn try_from(expression: &Value) -> Result<Self> {
fn parse_component<T: FromStr>(value: &lexpr::Value) -> Result<QueryComponent<T>> fn parse_component<T: FromStr>(value: &lexpr::Value) -> Result<QueryComponent<T>>
where where
<T as FromStr>::Err: std::fmt::Debug, <T as FromStr>::Err: std::fmt::Debug,
@ -430,29 +359,87 @@ impl Query {
)), )),
} }
} }
if let Cons(value) = expression {
if let Symbol(symbol) = value.car() {
match symbol.borrow() {
"matches" => {
let (cons_vec, _) = value.clone().into_vec();
if let [_, entity, attribute, value] = &cons_vec[..] {
let entity = parse_component::<Address>(entity)?;
let attribute = parse_component::<String>(attribute)?;
let value = parse_component::<EntryValue>(value)?;
Ok(Query::SingleQuery(QueryPart::Matches(EntryQuery {
entity,
attribute,
value,
})))
} else {
Err(anyhow!(
"Malformed expression: Wrong number of arguments to 'matches'."
))
} }
}
pub fn query<C: Connection<Backend = Sqlite>>(connection: &C, query: Query) -> Result<Vec<Entry>> { "type" => {
use crate::schema::data::dsl::*; let (cons_vec, _) = value.clone().into_vec();
if let [_, type_name] = &cons_vec[..] {
let db_query = data.filter(query_to_sqlite(&query)?); if let lexpr::Value::String(type_name_str) = type_name {
Ok(Query::SingleQuery(QueryPart::Type(
trace!("Querying: {}", debug_query(&db_query)); type_name_str.to_string(),
)))
let matches = db_query.load::<models::Entry>(connection)?; } else {
Err(anyhow!(
let entries = matches "Malformed expression: Type must be specified as a string."
))
}
} else {
Err(anyhow!(
"Malformed expression: Wrong number of arguments to 'type'."
))
}
}
"and" | "or" => {
let (cons_vec, _) = value.clone().into_vec();
let sub_expressions = &cons_vec[1..];
let values: Result<Vec<Box<Query>>> = sub_expressions
.iter() .iter()
.map(Entry::try_from) .map(|value| Ok(Box::new(Query::try_from(value)?)))
.filter_map(Result::ok)
.collect(); .collect();
Ok(entries) if let Some(queries) = NonEmpty::from_vec(values?) {
Ok(Query::MultiQuery(MultiQuery {
qualifier: match symbol.borrow() {
"and" => QueryQualifier::AND,
_ => QueryQualifier::OR,
},
queries,
}))
} else {
Err(anyhow!(
"Malformed expression: sub-query list cannot be empty.",
))
}
}
_ => Err(anyhow!(format!(
"Malformed expression: Unknown symbol '{}'.",
symbol
))),
}
} else {
Err(anyhow!(format!(
"Malformed expression: Value '{:?}' is not a symbol.",
value
)))
}
} else {
Err(anyhow!("Malformed expression: Not a list."))
}
}
} }
type Predicate = dyn BoxableExpression<data::table, Sqlite, SqlType = Bool>;
fn query_to_sqlite(query: &Query) -> Result<Box<Predicate>> { impl Query {
match query { fn to_sqlite_predicates(&self) -> Result<Box<Predicate>> {
match self {
Query::SingleQuery(qp) => match qp { Query::SingleQuery(qp) => match qp {
QueryPart::Matches(eq) => { QueryPart::Matches(eq) => {
let mut subqueries: Vec<Box<Predicate>> = vec![]; let mut subqueries: Vec<Box<Predicate>> = vec![];
@ -516,8 +503,11 @@ fn query_to_sqlite(query: &Query) -> Result<Box<Predicate>> {
QueryPart::Type(_) => unimplemented!("Type queries are not yet implemented."), QueryPart::Type(_) => unimplemented!("Type queries are not yet implemented."),
}, },
Query::MultiQuery(mq) => { Query::MultiQuery(mq) => {
let subqueries: Result<Vec<Box<Predicate>>> = let subqueries: Result<Vec<Box<Predicate>>> = mq
mq.queries.iter().map(|sq| query_to_sqlite(sq)).collect(); .queries
.iter()
.map(|sq| sq.to_sqlite_predicates())
.collect();
let mut subqueries: Vec<Box<Predicate>> = subqueries?; let mut subqueries: Vec<Box<Predicate>> = subqueries?;
match subqueries.len() { match subqueries.len() {
0 => Ok(Box::new(true.into_sql::<Bool>())), 0 => Ok(Box::new(true.into_sql::<Bool>())),
@ -544,47 +534,16 @@ fn query_to_sqlite(query: &Query) -> Result<Box<Predicate>> {
} }
} }
} }
}
pub fn query_entries<C: Connection<Backend = Sqlite>>( pub fn query<C: Connection<Backend = Sqlite>>(connection: &C, query: Query) -> Result<Vec<Entry>> {
connection: &C,
entry_query: EntryQuery,
) -> Result<Vec<Entry>> {
use crate::schema::data::dsl::*; use crate::schema::data::dsl::*;
let mut query = data.into_boxed(); let db_query = data.filter(query.to_sqlite_predicates()?);
query = match entry_query.entity { trace!("Querying: {}", debug_query(&db_query));
QueryComponent::Exact(q_target) => query.filter(target.eq(q_target.encode()?)),
QueryComponent::In(q_targets) => {
let targets: Result<Vec<_>, _> = q_targets.into_iter().map(|t| t.encode()).collect();
query.filter(target.eq_any(targets?))
}
QueryComponent::Contains(_) => return Err(anyhow!("Cannot query Address alike.")),
QueryComponent::Any => query,
};
query = match entry_query.attribute { let matches = db_query.load::<models::Entry>(connection)?;
QueryComponent::Exact(q_key) => query.filter(key.eq(q_key)),
QueryComponent::In(q_keys) => query.filter(key.eq_any(q_keys)),
QueryComponent::Contains(q_key) => query.filter(key.like(format!("%{}%", q_key))),
QueryComponent::Any => query,
};
query = match entry_query.value {
QueryComponent::Exact(q_value) => query.filter(value.eq(q_value.to_string()?)),
QueryComponent::In(q_values) => {
let values: Result<Vec<_>, _> = q_values.into_iter().map(|v| v.to_string()).collect();
query.filter(value.eq_any(values?))
}
QueryComponent::Contains(q_value_string) => {
query.filter(value.like(format!("%{}%", q_value_string)))
}
QueryComponent::Any => query,
};
trace!("Querying: {}", debug_query(&query));
let matches = query.load::<models::Entry>(connection)?;
let entries = matches let entries = matches
.iter() .iter()

View File

@ -1,7 +1,7 @@
use crate::addressing::Address; use crate::addressing::Address;
use crate::database::{ use crate::database::{
bulk_retrieve_objects, file_set_valid, insert_entry, insert_file, query_entries, bulk_retrieve_objects, file_set_valid, insert_entry, insert_file, query, retrieve_all_files,
retrieve_all_files, DbPool, Entry, EntryQuery, EntryValue, QueryComponent, DATABASE_FILENAME, DbPool, Entry, EntryQuery, EntryValue, Query, QueryComponent, QueryPart, DATABASE_FILENAME,
}; };
use crate::hash::Hashable; use crate::hash::Hashable;
use crate::models; use crate::models;
@ -129,22 +129,22 @@ impl EntryList for Vec<Entry> {
} }
pub fn list_roots<C: Connection<Backend = Sqlite>>(connection: &C) -> Result<Vec<Entry>> { pub fn list_roots<C: Connection<Backend = Sqlite>>(connection: &C) -> Result<Vec<Entry>> {
let all_directories: Vec<Entry> = query_entries( let all_directories: Vec<Entry> = query(
connection, connection,
EntryQuery { Query::SingleQuery(QueryPart::Matches(EntryQuery {
entity: QueryComponent::Any, entity: QueryComponent::Any,
attribute: QueryComponent::Exact(DIR_KEY.to_string()), attribute: QueryComponent::Exact(DIR_KEY.to_string()),
value: QueryComponent::Any, value: QueryComponent::Any,
}, })),
)?; )?;
let directories_with_parents: Vec<Address> = query_entries( let directories_with_parents: Vec<Address> = query(
connection, connection,
EntryQuery { Query::SingleQuery(QueryPart::Matches(EntryQuery {
entity: QueryComponent::Any, entity: QueryComponent::Any,
attribute: QueryComponent::Exact(DIR_HAS_KEY.to_string()), attribute: QueryComponent::Exact(DIR_HAS_KEY.to_string()),
value: QueryComponent::Any, value: QueryComponent::Any,
}, })),
)? )?
.extract_addresses(); .extract_addresses();
@ -167,13 +167,13 @@ pub async fn list_directory<C: Connection<Backend = Sqlite>>(
let resolved_path: Vec<Address> = resolve_path(connection, path, false)?; let resolved_path: Vec<Address> = resolve_path(connection, path, false)?;
let last = resolved_path.last().unwrap(); let last = resolved_path.last().unwrap();
query_entries( query(
connection, connection,
EntryQuery { Query::SingleQuery(QueryPart::Matches(EntryQuery {
entity: QueryComponent::Exact(last.clone()), entity: QueryComponent::Exact(last.clone()),
attribute: QueryComponent::Exact(DIR_HAS_KEY.to_string()), attribute: QueryComponent::Exact(DIR_HAS_KEY.to_string()),
value: QueryComponent::Any, value: QueryComponent::Any,
}, })),
)? )?
.extract_addresses() .extract_addresses()
} }
@ -197,13 +197,13 @@ pub fn fetch_or_create_dir<C: Connection<Backend = Sqlite>>(
} }
let dir_value = EntryValue::Value(Value::String(directory.name)); let dir_value = EntryValue::Value(Value::String(directory.name));
let directories: Vec<Address> = query_entries( let directories: Vec<Address> = query(
connection, connection,
EntryQuery { Query::SingleQuery(QueryPart::Matches(EntryQuery {
entity: QueryComponent::Any, entity: QueryComponent::Any,
attribute: QueryComponent::Exact(String::from(DIR_KEY)), attribute: QueryComponent::Exact(String::from(DIR_KEY)),
value: QueryComponent::Exact(dir_value.clone()), value: QueryComponent::Exact(dir_value.clone()),
}, })),
)? )?
.into_iter() .into_iter()
.map(|e: Entry| e.target) .map(|e: Entry| e.target)
@ -211,13 +211,13 @@ pub fn fetch_or_create_dir<C: Connection<Backend = Sqlite>>(
let valid_directories: Vec<Address> = match parent.clone() { let valid_directories: Vec<Address> = match parent.clone() {
Some(address) => { Some(address) => {
let parent_has: Vec<Address> = query_entries( let parent_has: Vec<Address> = query(
connection, connection,
EntryQuery { Query::SingleQuery(QueryPart::Matches(EntryQuery {
entity: QueryComponent::Exact(address), entity: QueryComponent::Exact(address),
attribute: QueryComponent::Exact(String::from(DIR_HAS_KEY)), attribute: QueryComponent::Exact(String::from(DIR_HAS_KEY)),
value: QueryComponent::Any, value: QueryComponent::Any,
}, })),
)? )?
.extract_addresses(); .extract_addresses();

View File

@ -12,6 +12,7 @@ use futures_util::StreamExt;
use log::debug; use log::debug;
use serde::Deserialize; use serde::Deserialize;
use std::collections::HashMap; use std::collections::HashMap;
use std::convert::TryFrom;
use std::path::PathBuf; use std::path::PathBuf;
#[derive(Clone)] #[derive(Clone)]
@ -55,7 +56,7 @@ pub async fn get_query(
let connection = state.db_pool.get().map_err(ErrorInternalServerError)?; let connection = state.db_pool.get().map_err(ErrorInternalServerError)?;
let sexp = lexpr::from_str(info.query.as_str()).map_err(ErrorInternalServerError)?; let sexp = lexpr::from_str(info.query.as_str()).map_err(ErrorInternalServerError)?;
let in_query = Query::from_sexp(&sexp).map_err(ErrorInternalServerError)?; let in_query = Query::try_from(&sexp).map_err(ErrorInternalServerError)?;
let result = query(&connection, in_query).map_err(ErrorInternalServerError)?; let result = query(&connection, in_query).map_err(ErrorInternalServerError)?;
Ok(HttpResponse::Ok().json(result)) Ok(HttpResponse::Ok().json(result))