use crate::addressing::Address; use crate::database::entry::EntryValue; use nonempty::NonEmpty; use std::borrow::Borrow; use std::convert::TryFrom; use std::str::FromStr; #[derive(Debug, Clone, PartialEq, Eq)] pub struct Attribute(pub String); impl From<&str> for Attribute { fn from(str: &str) -> Self { Self(str.to_string()) } } #[derive(Debug, Clone, PartialEq)] pub enum QueryComponent where T: TryFrom, { Exact(T), In(Vec), Contains(String), Variable(Option), } #[derive(Debug, Clone, PartialEq)] pub struct PatternQuery { 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, Clone, PartialEq)] pub enum QueryPart { Matches(PatternQuery), Type(String), } #[derive(Debug, Clone, PartialEq)] pub enum QueryQualifier { And, Or, Not, Join, } #[derive(Debug, Clone, PartialEq)] pub struct MultiQuery { pub qualifier: QueryQualifier, pub queries: NonEmpty>, } #[derive(Debug, Clone, 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 {} 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: Unknown symbol {}", symbol ))), } } else { Err(QueryParseError(format!( "Malformed expression: Inner value '{:?}' is not a symbol.", value ))) } } lexpr::Value::Symbol(symbol) if symbol.starts_with('?') => { let var_name = symbol.strip_prefix('?').unwrap(); Ok(QueryComponent::Variable(if var_name.is_empty() { None } else { Some(var_name.into()) })) } _ => 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(PatternQuery { 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" | "join" => { 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, "join" => QueryQualifier::Join, _ => 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(|e| QueryParseError(format!("failed to parse s-expression: {e}")))?; Query::try_from(&sexp) } } #[cfg(test)] mod test { use super::*; use anyhow::Result; use url::Url; #[test] fn test_matches() -> Result<()> { let query = "(matches ? ? ?)".parse::()?; assert_eq!( query, Query::SingleQuery(QueryPart::Matches(PatternQuery { entity: QueryComponent::Variable(None), attribute: QueryComponent::Variable(None), value: QueryComponent::Variable(None) })) ); let address = Address::Url(Url::parse("https://upend.dev").unwrap()); let query = format!("(matches @{address} ? ?)").parse::()?; assert_eq!( query, Query::SingleQuery(QueryPart::Matches(PatternQuery { entity: QueryComponent::Exact(address), attribute: QueryComponent::Variable(None), value: QueryComponent::Variable(None) })) ); let query = r#"(matches ? "FOO" ?)"#.parse::()?; assert_eq!( query, Query::SingleQuery(QueryPart::Matches(PatternQuery { entity: QueryComponent::Variable(None), attribute: QueryComponent::Exact("FOO".into()), value: QueryComponent::Variable(None) })) ); let value = EntryValue::Number(1337.93); let query = "(matches ? ? 1337.93)".parse::()?; assert_eq!( query, Query::SingleQuery(QueryPart::Matches(PatternQuery { entity: QueryComponent::Variable(None), attribute: QueryComponent::Variable(None), value: QueryComponent::Exact(value) })) ); Ok(()) } #[test] fn test_joins() -> Result<()> { let query = "(matches ?a ?b ?)".parse::()?; assert_eq!( query, Query::SingleQuery(QueryPart::Matches(PatternQuery { entity: QueryComponent::Variable(Some("a".into())), attribute: QueryComponent::Variable(Some("b".into())), value: QueryComponent::Variable(None) })) ); Ok(()) } #[test] fn test_in_parse() -> Result<()> { let query = r#"(matches ? (in "FOO" "BAR") ?)"#.parse::()?; assert_eq!( query, Query::SingleQuery(QueryPart::Matches(PatternQuery { entity: QueryComponent::Variable(None), attribute: QueryComponent::In(vec!("FOO".into(), "BAR".into())), value: QueryComponent::Variable(None) })) ); let values: Vec = vec!["FOO".into(), "BAR".into()]; let query = r#"(matches ? ? (in "FOO" "BAR"))"#.parse::()?; assert_eq!( query, Query::SingleQuery(QueryPart::Matches(PatternQuery { entity: QueryComponent::Variable(None), attribute: QueryComponent::Variable(None), value: QueryComponent::In(values) })) ); 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(PatternQuery { entity: QueryComponent::Variable(None), attribute: QueryComponent::Variable(None), value: QueryComponent::In(values) })) ); // 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(PatternQuery { entity: QueryComponent::Variable(None), attribute: QueryComponent::Variable(None), value: QueryComponent::In(values) })) ); 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(PatternQuery { entity: QueryComponent::Variable(None), attribute: QueryComponent::Variable(None), value: QueryComponent::In(values) })) ); Ok(()) } #[test] fn test_contains() -> Result<()> { let query = r#"(matches (contains "foo") ? ?)"#.parse::()?; assert_eq!( query, Query::SingleQuery(QueryPart::Matches(PatternQuery { entity: QueryComponent::Contains("foo".to_string()), attribute: QueryComponent::Variable(None), value: QueryComponent::Variable(None) })) ); let query = r#"(matches ? (contains "foo") ?)"#.parse::()?; assert_eq!( query, Query::SingleQuery(QueryPart::Matches(PatternQuery { entity: QueryComponent::Variable(None), attribute: QueryComponent::Contains("foo".to_string()), value: QueryComponent::Variable(None), })) ); let query = r#"(matches ? ? (contains "foo"))"#.parse::()?; assert_eq!( query, Query::SingleQuery(QueryPart::Matches(PatternQuery { entity: QueryComponent::Variable(None), attribute: QueryComponent::Variable(None), value: QueryComponent::Contains("foo".to_string()) })) ); Ok(()) } }