From eec4f96293a6b40e0652544a1b2edf560872e3c5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Ml=C3=A1dek?= Date: Fri, 15 Apr 2022 20:35:26 +0200 Subject: [PATCH] reorganize query code in preparation for lang upgrade --- src/database/engine.rs | 189 ++++++++++++++++++++++++++++++ src/database/hierarchies.rs | 10 +- src/database/lang.rs | 221 ++++-------------------------------- src/database/mod.rs | 14 +-- 4 files changed, 221 insertions(+), 213 deletions(-) create mode 100644 src/database/engine.rs diff --git a/src/database/engine.rs b/src/database/engine.rs new file mode 100644 index 0000000..6ac5c4b --- /dev/null +++ b/src/database/engine.rs @@ -0,0 +1,189 @@ +use super::entry::EntryValue; +use super::inner::models::Entry; +use super::inner::schema::data; +use super::lang::{Query, QueryComponent, QueryPart, QueryQualifier}; +use crate::database::inner::models; +use crate::diesel::IntoSql; +use crate::diesel::RunQueryDsl; +use crate::diesel::{ExpressionMethods, TextExpressionMethods}; +use anyhow::Result; +use diesel::expression::grouped::Grouped; +use diesel::expression::operators::{And, Not, Or}; +use diesel::sql_types::Bool; +use diesel::sqlite::Sqlite; +use diesel::{debug_query, BoxableExpression, QueryDsl}; +use diesel::{ + r2d2::{ConnectionManager, PooledConnection}, + SqliteConnection, +}; +use log::trace; + +#[derive(Debug, Clone)] +pub struct QueryExecutionError(String); + +impl std::fmt::Display for QueryExecutionError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", self.0) + } +} + +impl std::error::Error for QueryExecutionError {} + +pub fn execute( + connection: &PooledConnection>, + query: Query, +) -> Result> { + use crate::database::inner::schema::data::dsl::*; + let db_query = data.filter(to_sqlite_predicates(query)?); + trace!("DB query: {}", debug_query(&db_query)); + db_query.load::(connection).map_err(anyhow::Error::from) +} + +type Predicate = dyn BoxableExpression; + +fn to_sqlite_predicates(query: Query) -> Result, QueryExecutionError> { + match query { + 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( + |e| QueryExecutionError(format!("failed producing sql: {e}")), + )?))) + } + 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(|e| { + QueryExecutionError(format!("failed producing sql: {e}")) + })?))) + } + 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(|e| { + QueryExecutionError(format!("failed producing sql: {e}")) + })?, + ))), + }, + QueryComponent::In(q_values) => { + let first = q_values.first().ok_or_else(|| { + QueryExecutionError( + "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(QueryExecutionError(format!("IN queries must not combine numeric and string values! ({v} is not a number)"))) + } + }) + .collect::, QueryExecutionError>>()?, + ), + )), + _ => subqueries.push(Box::new( + data::value_str.eq_any( + q_values + .iter() + .map(|v| { + if let EntryValue::Number(_) = v { + Err(QueryExecutionError(format!("IN queries must not combine numeric and string values! (Found {v})"))) + } else { + v.to_string().map_err(|e| QueryExecutionError(format!("failed producing sql: {e}"))) + } + }) + .collect::, QueryExecutionError>>()?, + ), + )), + } + } + 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>, QueryExecutionError> = mq + .queries + .into_iter() + .map(|sq| to_sqlite_predicates(*sq)) + .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(QueryExecutionError("NOT only takes one subquery.".into())) + } + }, + } + } + } +} diff --git a/src/database/hierarchies.rs b/src/database/hierarchies.rs index 0c446ff..896e8b9 100644 --- a/src/database/hierarchies.rs +++ b/src/database/hierarchies.rs @@ -11,7 +11,7 @@ use crate::database::constants::{ HIER_ADDR, HIER_HAS_ATTR, HIER_INVARIANT, IS_OF_TYPE_ATTR, LABEL_ATTR, TYPE_ADDR, TYPE_HAS_ATTR, }; use crate::database::entry::{Entry, EntryValue}; -use crate::database::lang::{EntryQuery, Query, QueryComponent, QueryPart}; +use crate::database::lang::{PatternQuery, Query, QueryComponent, QueryPart}; use super::UpEndConnection; @@ -95,7 +95,7 @@ impl PointerEntries for Vec { pub fn list_roots(connection: &UpEndConnection) -> Result> { let all_directories: Vec = - connection.query(Query::SingleQuery(QueryPart::Matches(EntryQuery { + connection.query(Query::SingleQuery(QueryPart::Matches(PatternQuery { entity: QueryComponent::Any, attribute: QueryComponent::Exact(IS_OF_TYPE_ATTR.into()), value: QueryComponent::Exact(HIER_ADDR.clone().into()), @@ -103,7 +103,7 @@ pub fn list_roots(connection: &UpEndConnection) -> Result> { // TODO: this is horrible let directories_with_parents: Vec
= connection - .query(Query::SingleQuery(QueryPart::Matches(EntryQuery { + .query(Query::SingleQuery(QueryPart::Matches(PatternQuery { entity: QueryComponent::Any, attribute: QueryComponent::Exact(HIER_HAS_ATTR.into()), value: QueryComponent::Any, @@ -141,7 +141,7 @@ pub fn fetch_or_create_dir( } let matching_directories = connection - .query(Query::SingleQuery(QueryPart::Matches(EntryQuery { + .query(Query::SingleQuery(QueryPart::Matches(PatternQuery { entity: QueryComponent::Any, attribute: QueryComponent::Exact(LABEL_ATTR.into()), value: QueryComponent::Exact(directory.as_ref().clone().into()), @@ -151,7 +151,7 @@ pub fn fetch_or_create_dir( let parent_has: Vec
= match parent.clone() { Some(parent) => connection - .query(Query::SingleQuery(QueryPart::Matches(EntryQuery { + .query(Query::SingleQuery(QueryPart::Matches(PatternQuery { entity: QueryComponent::Exact(parent), attribute: QueryComponent::Exact(HIER_HAS_ATTR.into()), value: QueryComponent::Any, diff --git a/src/database/lang.rs b/src/database/lang.rs index 0450a12..226baf1 100644 --- a/src/database/lang.rs +++ b/src/database/lang.rs @@ -1,18 +1,12 @@ 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); +pub struct Attribute(pub String); impl From<&str> for Attribute { fn from(str: &str) -> Self { @@ -20,7 +14,7 @@ impl From<&str> for Attribute { } } -#[derive(Debug, PartialEq)] +#[derive(Debug, Clone, PartialEq)] pub enum QueryComponent where T: TryFrom, @@ -31,8 +25,8 @@ where Any, } -#[derive(Debug, PartialEq)] -pub struct EntryQuery { +#[derive(Debug, Clone, PartialEq)] +pub struct PatternQuery { pub entity: QueryComponent
, pub attribute: QueryComponent, pub value: QueryComponent, @@ -94,26 +88,26 @@ impl TryFrom for EntryValue { } } -#[derive(Debug, PartialEq)] +#[derive(Debug, Clone, PartialEq)] pub enum QueryPart { - Matches(EntryQuery), + Matches(PatternQuery), Type(String), } -#[derive(Debug, PartialEq)] +#[derive(Debug, Clone, PartialEq)] pub enum QueryQualifier { And, Or, Not, } -#[derive(Debug, PartialEq)] +#[derive(Debug, Clone, PartialEq)] pub struct MultiQuery { pub qualifier: QueryQualifier, pub queries: NonEmpty>, } -#[derive(Debug, PartialEq)] +#[derive(Debug, Clone, PartialEq)] pub enum Query { SingleQuery(QueryPart), MultiQuery(MultiQuery), @@ -130,8 +124,6 @@ impl std::fmt::Display for QueryParseError { impl std::error::Error for QueryParseError {} -type Predicate = dyn BoxableExpression; - impl TryFrom<&lexpr::Value> for Query { type Error = QueryParseError; @@ -204,7 +196,7 @@ impl TryFrom<&lexpr::Value> for Query { let entity = parse_component::
(entity)?; let attribute = parse_component::(attribute)?; let value = parse_component::(value)?; - Ok(Query::SingleQuery(QueryPart::Matches(EntryQuery { + Ok(Query::SingleQuery(QueryPart::Matches(PatternQuery { entity, attribute, value, @@ -303,208 +295,55 @@ impl FromStr for Query { } } -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( - |e| QueryParseError(format!("failed producing sql: {e}")), - )?))) - } - 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( - |e| QueryParseError(format!("failed producing sql: {e}")), - )?))) - } - 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(|e| { - QueryParseError(format!("failed producing sql: {e}")) - })?, - ))), - }, - 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(|e| QueryParseError(format!("failed producing sql: {e}"))) - } - }) - .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}; + use anyhow::{Result}; #[test] fn test_matches() -> Result<()> { let query = "(matches ? ? ?)".parse::()?; assert_eq!( query, - Query::SingleQuery(QueryPart::Matches(EntryQuery { + Query::SingleQuery(QueryPart::Matches(PatternQuery { 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 { + Query::SingleQuery(QueryPart::Matches(PatternQuery { 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 { + Query::SingleQuery(QueryPart::Matches(PatternQuery { 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 { + Query::SingleQuery(QueryPart::Matches(PatternQuery { entity: QueryComponent::Any, attribute: QueryComponent::Any, value: QueryComponent::Exact(value) })) ); - query.to_sqlite_predicates()?; Ok(()) } @@ -514,37 +353,34 @@ mod test { let query = r#"(matches ? (in "FOO" "BAR") ?)"#.parse::()?; assert_eq!( query, - Query::SingleQuery(QueryPart::Matches(EntryQuery { + Query::SingleQuery(QueryPart::Matches(PatternQuery { 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 { + Query::SingleQuery(QueryPart::Matches(PatternQuery { 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 { + Query::SingleQuery(QueryPart::Matches(PatternQuery { 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)]; @@ -552,32 +388,24 @@ mod test { assert_eq!( query, - Query::SingleQuery(QueryPart::Matches(EntryQuery { + Query::SingleQuery(QueryPart::Matches(PatternQuery { 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 { + Query::SingleQuery(QueryPart::Matches(PatternQuery { entity: QueryComponent::Any, attribute: QueryComponent::Any, value: QueryComponent::In(values) })) ); - query - .to_sqlite_predicates() - .err() - .ok_or(anyhow!("Failed to reject mixed query."))?; Ok(()) } @@ -587,35 +415,32 @@ mod test { let query = r#"(matches (contains "foo") ? ?)"#.parse::()?; assert_eq!( query, - Query::SingleQuery(QueryPart::Matches(EntryQuery { + Query::SingleQuery(QueryPart::Matches(PatternQuery { 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 { + Query::SingleQuery(QueryPart::Matches(PatternQuery { 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 { + Query::SingleQuery(QueryPart::Matches(PatternQuery { entity: QueryComponent::Any, attribute: QueryComponent::Any, value: QueryComponent::Contains("foo".to_string()) })) ); - query.to_sqlite_predicates()?; Ok(()) } diff --git a/src/database/mod.rs b/src/database/mod.rs index c497439..aa98c14 100644 --- a/src/database/mod.rs +++ b/src/database/mod.rs @@ -4,6 +4,7 @@ mod macros; pub mod constants; +pub mod engine; pub mod entry; pub mod hierarchies; pub mod inner; @@ -13,6 +14,7 @@ use crate::addressing::{Address, Addressable}; use crate::database::constants::{ IS_OF_TYPE_ATTR, LABEL_ATTR, TYPE_ADDR, TYPE_HAS_ATTR, TYPE_INVARIANT, }; +use crate::database::engine::execute; use crate::database::entry::{Entry, EntryValue, ImmutableEntry}; use crate::database::inner::models; use crate::database::inner::schema::data; @@ -21,7 +23,6 @@ use crate::util::hash::Hash; use crate::util::LoggerSink; use anyhow::{anyhow, Result}; use chrono::NaiveDateTime; -use diesel::debug_query; use diesel::prelude::*; use diesel::r2d2::{self, ConnectionManager, PooledConnection}; use diesel::result::{DatabaseErrorKind, Error}; @@ -345,17 +346,10 @@ impl UpEndConnection { } pub fn query(&self, query: Query) -> Result> { - use crate::database::inner::schema::data::dsl::*; - trace!("Querying: {:?}", query); - let db_query = data.filter(query.to_sqlite_predicates()?); - - trace!("DB query: {}", debug_query(&db_query)); - - let matches = db_query.load::(&self.conn)?; - - let entries = matches + let entries = execute(&self.conn, query)?; + let entries = entries .iter() .map(Entry::try_from) .filter_map(Result::ok)