use crate::addressing::Address; use crate::database::entry::EntryValue; use crate::database::inner::schema::data; use diesel::expression::grouped::Grouped; 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, Clone, PartialEq, Eq)] pub struct Attribute(String); impl From<&str> for Attribute { fn from(str: &str) -> Self { Self(str.to_string()) } } #[derive(Debug, PartialEq)] pub enum QueryComponent where T: TryFrom, { Exact(T), In(Vec), Contains(String), Any, } #[derive(Debug, PartialEq)] pub struct EntryQuery { pub entity: QueryComponent
, pub attribute: QueryComponent, pub value: QueryComponent, } impl TryFrom for Address { type Error = QueryParseError; fn try_from(value: lexpr::Value) -> Result { match value { lexpr::Value::Symbol(str) => { if let Some(address_str) = str.strip_prefix('@') { address_str .parse() .map_err(|e: anyhow::Error| QueryParseError(e.to_string())) } else { Err(QueryParseError( "Incorrect address format (use @address).".into(), )) } } _ => Err(QueryParseError( "Incorrect type for address (use @address).".into(), )), } } } impl TryFrom for Attribute { type Error = QueryParseError; fn try_from(value: lexpr::Value) -> Result { match value { lexpr::Value::String(str) => Ok(Attribute(str.to_string())), _ => Err(QueryParseError( "Can only convert to attribute from string.".into(), )), } } } impl TryFrom for EntryValue { type Error = QueryParseError; fn try_from(value: lexpr::Value) -> Result { match value { lexpr::Value::Number(num) => { Ok(EntryValue::Number(num.as_f64().ok_or_else(|| { QueryParseError(format!("Error processing number ({num:?}).")) })?)) } lexpr::Value::Char(chr) => Ok(EntryValue::String(chr.to_string())), lexpr::Value::String(str) => Ok(EntryValue::String(str.to_string())), lexpr::Value::Symbol(_) => Ok(EntryValue::Address(Address::try_from(value.clone())?)), _ => Err(QueryParseError( "Value can only be a string, number or address.".into(), )), } } } #[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), } #[derive(Debug, Clone)] pub struct QueryParseError(String); impl std::fmt::Display for QueryParseError { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "{}", self.0) } } impl std::error::Error for QueryParseError {} type Predicate = dyn BoxableExpression; impl TryFrom<&lexpr::Value> for Query { type Error = QueryParseError; fn try_from(expression: &lexpr::Value) -> Result { fn parse_component>( value: &lexpr::Value, ) -> Result, QueryParseError> where QueryParseError: From<>::Error>, { 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| T::try_from(value.clone())) .collect(); Ok(QueryComponent::In(values?)) } else { Err(QueryParseError( "Malformed expression: Inner value cannot be empty.".into(), )) } } "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(QueryParseError("Malformed expression: 'Contains' argument must be a string.".into())) } } _ => Err(QueryParseError( "Malformed expression: 'Contains' requires a single argument.".into() )), } } _ => Err(QueryParseError(format!( "Malformed expression: Unknowne symbol {}", symbol ))), } } else { Err(QueryParseError(format!( "Malformed expression: Inner value '{:?}' is not a symbol.", value ))) } } lexpr::Value::Symbol(symbol) if symbol.as_ref() == "?" => Ok(QueryComponent::Any), _ => Ok(QueryComponent::Exact(T::try_from(value.clone())?)), } } 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(QueryParseError( "Malformed expression: Wrong number of arguments to 'matches'." .into(), )) } } "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(QueryParseError( "Malformed expression: Type must be specified as a string." .into(), )) } } else { Err(QueryParseError( "Malformed expression: Wrong number of arguments to 'type'.".into(), )) } } "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::>, QueryParseError>>()?; if let Some(queries) = NonEmpty::from_vec(values) { Ok(Query::MultiQuery(MultiQuery { qualifier: match symbol.borrow() { "and" => QueryQualifier::And, _ => QueryQualifier::Or, }, queries, })) } else { Err(QueryParseError( "Malformed expression: sub-query list cannot be empty.".into(), )) } } "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::>, QueryParseError>>()?; if values.len() == 1 { Ok(Query::MultiQuery(MultiQuery { qualifier: QueryQualifier::Not, queries: NonEmpty::from_vec(values).unwrap(), })) } else { Err(QueryParseError( "Malformed expression: NOT takes exactly one parameter.".into(), )) } } _ => Err(QueryParseError(format!( "Malformed expression: Unknown symbol '{}'.", symbol ))), } } else { Err(QueryParseError(format!( "Malformed expression: Value '{:?}' is not a symbol.", value ))) } } else { Err(QueryParseError("Malformed expression: Not a list.".into())) } } } impl FromStr for Query { type Err = QueryParseError; fn from_str(s: &str) -> Result { let sexp = lexpr::from_str_custom(s, lexpr::parse::Options::new()) .map_err(|_| QueryParseError("???".into()))?; Query::try_from(&sexp) } } impl Query { pub(crate) fn to_sqlite_predicates(&self) -> Result, QueryParseError> { 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() .map_err(|_| QueryParseError("???".into()))?), )), QueryComponent::In(q_entities) => { let entities: Result, _> = q_entities.iter().map(|t| t.encode()).collect(); subqueries.push(Box::new( data::entity .eq_any(entities.map_err(|_| QueryParseError("???".into()))?), )) } 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.0.clone()))) } QueryComponent::In(q_attributes) => subqueries.push(Box::new( data::attribute.eq_any(q_attributes.iter().map(|a| &a.0).cloned()), )), 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() .map_err(|_| QueryParseError("???".into()))?), )), }, QueryComponent::In(q_values) => { let first = q_values.first().ok_or_else(|| { QueryParseError( "Malformed expression: Inner value cannot be empty.".into(), ) })?; 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(QueryParseError(format!("IN queries must not combine numeric and string values! ({v} is not a number)"))) } }) .collect::, QueryParseError>>()?, ), )), _ => subqueries.push(Box::new( data::value_str.eq_any( q_values .iter() .map(|v| { if let EntryValue::Number(_) = v { Err(QueryParseError(format!("IN queries must not combine numeric and string values! (Found {v})"))) } else { v.to_string().map_err(|_| QueryParseError("???".into())) } }) .collect::, QueryParseError>>()?, ), )), } } 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>, QueryParseError> = 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(Grouped(result))) } QueryQualifier::Or => { let mut result = 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(Grouped(result))) } QueryQualifier::Not => { Err(QueryParseError("NOT only takes one subquery.".into())) } }, } } } } } #[cfg(test)] mod test { use super::*; use anyhow::{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 = r#"(matches ? "FOO" ?)"#.parse::()?; assert_eq!( query, Query::SingleQuery(QueryPart::Matches(EntryQuery { entity: QueryComponent::Any, attribute: QueryComponent::Exact("FOO".into()), value: QueryComponent::Any })) ); query.to_sqlite_predicates()?; let value = EntryValue::Number(1337.93); let query = "(matches ? ? 1337.93)".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 = r#"(matches ? (in "FOO" "BAR") ?)"#.parse::()?; assert_eq!( query, Query::SingleQuery(QueryPart::Matches(EntryQuery { entity: QueryComponent::Any, attribute: QueryComponent::In(vec!("FOO".into(), "BAR".into())), value: QueryComponent::Any })) ); query.to_sqlite_predicates()?; let values: Vec = vec!["FOO".into(), "BAR".into()]; let query = r#"(matches ? ? (in "FOO" "BAR"))"#.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 = r#"(matches ? ? (in 1337.93 1968.12))"#.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 = r#"(matches ? ? (in "FOO" 1337.93))"#.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 = r#"(matches ? ? (in 1337.93 "FOO"))"#.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 = r#"(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 = r#"(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 = r#"(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(()) } }