reorganize query code in preparation for lang upgrade
parent
cc57ea7a78
commit
eec4f96293
|
@ -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()))
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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,
|
||||
|
|
|
@ -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(())
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
|
Loading…
Reference in New Issue