use crate::addressing::Address; use crate::database::entry::EntryValue; use crate::database::inner::schema::data; use anyhow::{anyhow, Result}; use diesel::expression::operators::{And, Not, Or}; use diesel::sql_types::Bool; use diesel::sqlite::Sqlite; use diesel::{BoxableExpression, ExpressionMethods, IntoSql, TextExpressionMethods}; use nonempty::NonEmpty; use std::borrow::Borrow; use std::convert::TryFrom; use std::str::FromStr; #[derive(Debug, PartialEq)] pub enum QueryComponent where T: FromStr, { Exact(T), In(Vec), Contains(String), Any, } #[derive(Debug, PartialEq)] pub struct EntryQuery { pub entity: QueryComponent
, pub attribute: QueryComponent, pub value: QueryComponent, } #[derive(Debug, PartialEq)] pub enum QueryPart { Matches(EntryQuery), Type(String), } #[derive(Debug, PartialEq)] pub enum QueryQualifier { And, Or, Not, } #[derive(Debug, PartialEq)] pub struct MultiQuery { pub qualifier: QueryQualifier, pub queries: NonEmpty>, } #[derive(Debug, PartialEq)] pub enum Query { SingleQuery(QueryPart), MultiQuery(MultiQuery), } type Predicate = dyn BoxableExpression; impl TryFrom<&lexpr::Value> for Query { type Error = anyhow::Error; fn try_from(expression: &lexpr::Value) -> Result { fn parse_component(value: &lexpr::Value) -> Result> where ::Err: std::fmt::Debug, { match value { lexpr::Value::Cons(cons) => { if let lexpr::Value::Symbol(symbol) = cons.car() { match symbol.borrow() { "in" => { let (cons_vec, _) = cons.clone().into_vec(); if let Some(split) = cons_vec.split_first() { let args = split.1; let values: Result, _> = args.iter().map(|value| { if let lexpr::Value::String(str) = value { match T::from_str(str.borrow()) { Ok(value) => Ok(value), Err(error) => Err(anyhow!(format!("Malformed expression: Conversion of inner value '{}' from string failed: {:#?}",str, error))), } } else { Err(anyhow!("Malformed expression: Inner value list must be comprised of strings.")) } }).collect(); Ok(QueryComponent::In(values?)) } else { Err(anyhow!( "Malformed expression: Inner value cannot be empty." )) } } "contains" => { let (mut cons_vec, _) = cons.clone().into_vec(); match cons_vec.len() { 2 => { let value = cons_vec.remove(1); if let lexpr::Value::String(str) = value { Ok(QueryComponent::Contains(str.into_string())) } else { Err(anyhow!("Malformed expression: 'Contains' argument must be a string.")) } } _ => Err(anyhow!( "Malformed expression: 'Contains' requires a single argument." )), } } _ => Err(anyhow!(format!( "Malformed expression: Unknown symbol {}", symbol ))), } } else { Err(anyhow!(format!( "Malformed expression: Inner value '{:?}' is not a symbol.", value ))) } } lexpr::Value::String(str) => match T::from_str(str.borrow()) { Ok(value) => Ok(QueryComponent::Exact(value)), Err(error) => Err(anyhow!(format!( "Malformed expression: Conversion of inner value '{}' from string failed: {:#?}", str, error ))), }, lexpr::Value::Symbol(symbol) => match symbol.borrow() { "?" => Ok(QueryComponent::Any), _ => Err(anyhow!(format!( "Malformed expression: Unknown symbol {}", symbol ))), }, _ => Err(anyhow!( "Malformed expression: Inner value not a string, list or '?'." )), } } if let lexpr::Value::Cons(value) = expression { if let lexpr::Value::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::
(entity)?; let attribute = parse_component::(attribute)?; let value = parse_component::(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 = sub_expressions .iter() .map(|value| Ok(Box::new(Query::try_from(value)?))) .collect::>>>()?; 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.", )) } } "not" => { let (cons_vec, _) = value.clone().into_vec(); let sub_expressions = &cons_vec[1..]; let values = sub_expressions .iter() .map(|value| Ok(Box::new(Query::try_from(value)?))) .collect::>>>()?; if values.len() == 1 { Ok(Query::MultiQuery(MultiQuery { qualifier: QueryQualifier::Not, queries: NonEmpty::from_vec(values).unwrap(), })) } else { Err(anyhow!( "Malformed expression: NOT takes exactly one parameter." )) } } _ => 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.")) } } } impl FromStr for Query { type Err = anyhow::Error; fn from_str(s: &str) -> Result { let sexp = lexpr::from_str_custom(s, lexpr::parse::Options::new())?; Query::try_from(&sexp) } } impl Query { pub(crate) fn to_sqlite_predicates(&self) -> Result> { match self { Query::SingleQuery(qp) => { match qp { QueryPart::Matches(eq) => { let mut subqueries: Vec> = vec![]; match &eq.entity { QueryComponent::Exact(q_entity) => { subqueries.push(Box::new(data::entity.eq(q_entity.encode()?))) } QueryComponent::In(q_entities) => { let entities: Result, _> = q_entities.iter().map(|t| t.encode()).collect(); subqueries.push(Box::new(data::entity.eq_any(entities?))) } QueryComponent::Contains(q_entity) => subqueries.push(Box::new( data::entity_searchable.like(format!("%{}%", q_entity)), )), QueryComponent::Any => {} }; match &eq.attribute { QueryComponent::Exact(q_attribute) => { subqueries.push(Box::new(data::attribute.eq(q_attribute.clone()))) } QueryComponent::In(q_attributes) => subqueries .push(Box::new(data::attribute.eq_any(q_attributes.clone()))), QueryComponent::Contains(q_attribute) => subqueries .push(Box::new(data::attribute.like(format!("%{}%", q_attribute)))), QueryComponent::Any => {} }; match &eq.value { QueryComponent::Exact(q_value) => match q_value { EntryValue::Number(n) => { subqueries.push(Box::new(data::value_num.eq(*n))) } _ => subqueries .push(Box::new(data::value_str.eq(q_value.to_string()?))), }, QueryComponent::In(q_values) => { let first = q_values.first().ok_or(anyhow!( "Malformed expression: Inner value cannot be empty." ))?; match first { EntryValue::Number(_) => subqueries.push(Box::new( data::value_num.eq_any( q_values .iter() .map(|v| { if let EntryValue::Number(n) = v { Ok(*n) } else { Err(anyhow!("IN queries must not combine numeric and string values! ({v} is not a number)")) } }) .collect::>>()?, ), )), _ => subqueries.push(Box::new( data::value_str.eq_any( q_values .iter() .map(|v| { if let EntryValue::Number(_) = v { Err(anyhow!("IN queries must not combine numeric and string values! (Found {v})")) } else { v.to_string() } }) .collect::>>()?, ), )), } } QueryComponent::Contains(q_value) => subqueries .push(Box::new(data::value_str.like(format!("S%{}%", q_value)))), QueryComponent::Any => {} }; match subqueries.len() { 0 => Ok(Box::new(true.into_sql::())), 1 => Ok(subqueries.remove(0)), _ => { let mut result: Box, Box>> = Box::new(And::new(subqueries.remove(0), subqueries.remove(0))); while !subqueries.is_empty() { result = Box::new(And::new(result, subqueries.remove(0))); } Ok(Box::new(result)) } } } QueryPart::Type(_) => unimplemented!("Type queries are not yet implemented."), } } Query::MultiQuery(mq) => { let subqueries: Result>> = mq .queries .iter() .map(|sq| sq.to_sqlite_predicates()) .collect(); let mut subqueries: Vec> = subqueries?; match subqueries.len() { 0 => Ok(Box::new(true.into_sql::())), 1 => { if let QueryQualifier::Not = mq.qualifier { Ok(Box::new(Not::new(subqueries.remove(0)))) } else { Ok(subqueries.remove(0)) } } _ => match mq.qualifier { QueryQualifier::And => { let mut result: Box, Box>> = Box::new(And::new(subqueries.remove(0), subqueries.remove(0))); while !subqueries.is_empty() { result = Box::new(And::new(result, subqueries.remove(0))); } Ok(Box::new(result)) } QueryQualifier::Or => { let mut result: Box, Box>> = Box::new(Or::new(subqueries.remove(0), subqueries.remove(0))); while !subqueries.is_empty() { result = Box::new(Or::new(result, subqueries.remove(0))); } Ok(Box::new(result)) } QueryQualifier::Not => Err(anyhow!("NOT only takes one subquery.")), }, } } } } } #[cfg(test)] mod test { use super::*; use anyhow::Result; #[test] fn test_matches() -> Result<()> { let query = "(matches ? ? ?)".parse::()?; assert_eq!( query, Query::SingleQuery(QueryPart::Matches(EntryQuery { entity: QueryComponent::Any, attribute: QueryComponent::Any, value: QueryComponent::Any })) ); query.to_sqlite_predicates()?; let address = Address::Url(String::from("https://upendproject.net")); let query = format!("(matches \"{address}\" ? ?)").parse::()?; assert_eq!( query, Query::SingleQuery(QueryPart::Matches(EntryQuery { entity: QueryComponent::Exact(address), attribute: QueryComponent::Any, value: QueryComponent::Any })) ); query.to_sqlite_predicates()?; let query = "(matches ? \"FOO\" ?)".parse::()?; assert_eq!( query, Query::SingleQuery(QueryPart::Matches(EntryQuery { entity: QueryComponent::Any, attribute: QueryComponent::Exact(String::from("FOO")), value: QueryComponent::Any })) ); query.to_sqlite_predicates()?; let value = EntryValue::Number(1337.93); let query = format!("(matches ? ? \"{}\")", value.to_string().unwrap()).parse::()?; assert_eq!( query, Query::SingleQuery(QueryPart::Matches(EntryQuery { entity: QueryComponent::Any, attribute: QueryComponent::Any, value: QueryComponent::Exact(value) })) ); query.to_sqlite_predicates()?; Ok(()) } #[test] fn test_in_parse() -> Result<()> { let query = "(matches ? (in \"FOO\" \"BAR\") ?)".parse::()?; assert_eq!( query, Query::SingleQuery(QueryPart::Matches(EntryQuery { entity: QueryComponent::Any, attribute: QueryComponent::In(vec!("FOO".to_string(), "BAR".to_string())), value: QueryComponent::Any })) ); query.to_sqlite_predicates()?; let values: Vec = vec!["FOO".into(), "BAR".into()]; let query = format!( "(matches ? ? (in {}))", values .iter() .map(|v| format!("\"{}\"", v.to_string().unwrap())) .collect::>() .join(" ") ) .parse::()?; assert_eq!( query, Query::SingleQuery(QueryPart::Matches(EntryQuery { entity: QueryComponent::Any, attribute: QueryComponent::Any, value: QueryComponent::In(values) })) ); query.to_sqlite_predicates()?; let values = vec![EntryValue::Number(1337.93), EntryValue::Number(1968.12)]; let query = format!( "(matches ? ? (in {}))", values .iter() .map(|v| format!("\"{}\"", v.to_string().unwrap())) .collect::>() .join(" ") ) .parse::()?; assert_eq!( query, Query::SingleQuery(QueryPart::Matches(EntryQuery { entity: QueryComponent::Any, attribute: QueryComponent::Any, value: QueryComponent::In(values) })) ); query.to_sqlite_predicates()?; // Invalid queries let values: Vec = vec!["FOO".into(), EntryValue::Number(1337.93)]; let query = format!( "(matches ? ? (in {}))", values .iter() .map(|v| format!("\"{}\"", v.to_string().unwrap())) .collect::>() .join(" ") ) .parse::()?; assert_eq!( query, Query::SingleQuery(QueryPart::Matches(EntryQuery { entity: QueryComponent::Any, attribute: QueryComponent::Any, value: QueryComponent::In(values) })) ); query .to_sqlite_predicates() .err() .ok_or(anyhow!("Failed to reject mixed query."))?; let values = vec![EntryValue::Number(1337.93), "FOO".into()]; let query = format!( "(matches ? ? (in {}))", values .iter() .map(|v| format!("\"{}\"", v.to_string().unwrap())) .collect::>() .join(" ") ) .parse::()?; assert_eq!( query, Query::SingleQuery(QueryPart::Matches(EntryQuery { entity: QueryComponent::Any, attribute: QueryComponent::Any, value: QueryComponent::In(values) })) ); query .to_sqlite_predicates() .err() .ok_or(anyhow!("Failed to reject mixed query."))?; Ok(()) } #[test] fn test_contains() -> Result<()> { let query = "(matches (contains \"foo\") ? ?)".parse::()?; assert_eq!( query, Query::SingleQuery(QueryPart::Matches(EntryQuery { entity: QueryComponent::Contains("foo".to_string()), attribute: QueryComponent::Any, value: QueryComponent::Any })) ); query.to_sqlite_predicates()?; let query = "(matches ? (contains \"foo\") ?)".parse::()?; assert_eq!( query, Query::SingleQuery(QueryPart::Matches(EntryQuery { entity: QueryComponent::Any, attribute: QueryComponent::Contains("foo".to_string()), value: QueryComponent::Any, })) ); query.to_sqlite_predicates()?; let query = "(matches ? ? (contains \"foo\"))".parse::()?; assert_eq!( query, Query::SingleQuery(QueryPart::Matches(EntryQuery { entity: QueryComponent::Any, attribute: QueryComponent::Any, value: QueryComponent::Contains("foo".to_string()) })) ); query.to_sqlite_predicates()?; Ok(()) } }