reorganize query code in preparation for lang upgrade

feat/vaults
Tomáš Mládek 2022-04-15 20:35:26 +02:00
parent cc57ea7a78
commit eec4f96293
No known key found for this signature in database
GPG Key ID: 65E225C8B3E2ED8A
4 changed files with 221 additions and 213 deletions

189
src/database/engine.rs Normal file
View File

@ -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<ConnectionManager<SqliteConnection>>,
query: Query,
) -> Result<Vec<Entry>> {
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::<models::Entry>(connection).map_err(anyhow::Error::from)
}
type Predicate = dyn BoxableExpression<data::table, Sqlite, SqlType = Bool>;
fn to_sqlite_predicates(query: Query) -> Result<Box<Predicate>, QueryExecutionError> {
match query {
Query::SingleQuery(qp) => match qp {
QueryPart::Matches(eq) => {
let mut subqueries: Vec<Box<Predicate>> = 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<Vec<_>, _> =
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::<Result<Vec<f64>, 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::<Result<Vec<String>, 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::<Bool>())),
1 => Ok(subqueries.remove(0)),
_ => {
let mut result: Box<And<Box<Predicate>, Box<Predicate>>> =
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<Vec<Box<Predicate>>, QueryExecutionError> = mq
.queries
.into_iter()
.map(|sq| to_sqlite_predicates(*sq))
.collect();
let mut subqueries: Vec<Box<Predicate>> = subqueries?;
match subqueries.len() {
0 => Ok(Box::new(true.into_sql::<Bool>())),
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<And<Box<Predicate>, Box<Predicate>>> =
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()))
}
},
}
}
}
}

View File

@ -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<Entry> {
pub fn list_roots(connection: &UpEndConnection) -> Result<Vec<Address>> {
let all_directories: Vec<Entry> =
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<Vec<Address>> {
// TODO: this is horrible
let directories_with_parents: Vec<Address> = 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<Address> = 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,

View File

@ -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<T>
where
T: TryFrom<lexpr::Value>,
@ -31,8 +25,8 @@ where
Any,
}
#[derive(Debug, PartialEq)]
pub struct EntryQuery {
#[derive(Debug, Clone, PartialEq)]
pub struct PatternQuery {
pub entity: QueryComponent<Address>,
pub attribute: QueryComponent<Attribute>,
pub value: QueryComponent<EntryValue>,
@ -94,26 +88,26 @@ impl TryFrom<lexpr::Value> 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<Box<Query>>,
}
#[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<data::table, Sqlite, SqlType = Bool>;
impl TryFrom<&lexpr::Value> for Query {
type Error = QueryParseError;
@ -204,7 +196,7 @@ impl TryFrom<&lexpr::Value> for Query {
let entity = parse_component::<Address>(entity)?;
let attribute = parse_component::<Attribute>(attribute)?;
let value = parse_component::<EntryValue>(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<Box<Predicate>, QueryParseError> {
match self {
Query::SingleQuery(qp) => match qp {
QueryPart::Matches(eq) => {
let mut subqueries: Vec<Box<Predicate>> = 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<Vec<_>, _> =
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::<Result<Vec<f64>, 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::<Result<Vec<String>, 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::<Bool>())),
1 => Ok(subqueries.remove(0)),
_ => {
let mut result: Box<And<Box<Predicate>, Box<Predicate>>> =
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<Vec<Box<Predicate>>, QueryParseError> = mq
.queries
.iter()
.map(|sq| sq.to_sqlite_predicates())
.collect();
let mut subqueries: Vec<Box<Predicate>> = subqueries?;
match subqueries.len() {
0 => Ok(Box::new(true.into_sql::<Bool>())),
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<And<Box<Predicate>, Box<Predicate>>> =
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::<Query>()?;
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::<Query>()?;
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::<Query>()?;
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::<Query>()?;
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::<Query>()?;
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<EntryValue> = vec!["FOO".into(), "BAR".into()];
let query = r#"(matches ? ? (in "FOO" "BAR"))"#.parse::<Query>()?;
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::<Query>()?;
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<EntryValue> = 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::<Query>()?;
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::<Query>()?;
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::<Query>()?;
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::<Query>()?;
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(())
}

View File

@ -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<Vec<Entry>> {
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::<models::Entry>(&self.conn)?;
let entries = matches
let entries = execute(&self.conn, query)?;
let entries = entries
.iter()
.map(Entry::try_from)
.filter_map(Result::ok)