add join
queries to the language (fixes #3)
This commit is contained in:
parent
459eede174
commit
6fdc3e2f48
4 changed files with 279 additions and 111 deletions
|
@ -1,7 +1,10 @@
|
||||||
|
use std::collections::HashMap;
|
||||||
|
use std::iter::zip;
|
||||||
|
|
||||||
use super::entry::EntryValue;
|
use super::entry::EntryValue;
|
||||||
use super::inner::models::Entry;
|
use super::inner::models::Entry;
|
||||||
use super::inner::schema::data;
|
use super::inner::schema::data;
|
||||||
use super::lang::{Query, QueryComponent, QueryPart, QueryQualifier};
|
use super::lang::{PatternQuery, Query, QueryComponent, QueryPart, QueryQualifier};
|
||||||
use crate::database::inner::models;
|
use crate::database::inner::models;
|
||||||
use crate::diesel::IntoSql;
|
use crate::diesel::IntoSql;
|
||||||
use crate::diesel::RunQueryDsl;
|
use crate::diesel::RunQueryDsl;
|
||||||
|
@ -11,12 +14,11 @@ use diesel::expression::grouped::Grouped;
|
||||||
use diesel::expression::operators::{And, Not, Or};
|
use diesel::expression::operators::{And, Not, Or};
|
||||||
use diesel::sql_types::Bool;
|
use diesel::sql_types::Bool;
|
||||||
use diesel::sqlite::Sqlite;
|
use diesel::sqlite::Sqlite;
|
||||||
use diesel::{debug_query, BoxableExpression, QueryDsl};
|
|
||||||
use diesel::{
|
use diesel::{
|
||||||
r2d2::{ConnectionManager, PooledConnection},
|
r2d2::{ConnectionManager, PooledConnection},
|
||||||
SqliteConnection,
|
SqliteConnection,
|
||||||
};
|
};
|
||||||
use log::trace;
|
use diesel::{BoxableExpression, QueryDsl};
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct QueryExecutionError(String);
|
pub struct QueryExecutionError(String);
|
||||||
|
@ -32,20 +34,134 @@ impl std::error::Error for QueryExecutionError {}
|
||||||
pub fn execute(
|
pub fn execute(
|
||||||
connection: &PooledConnection<ConnectionManager<SqliteConnection>>,
|
connection: &PooledConnection<ConnectionManager<SqliteConnection>>,
|
||||||
query: Query,
|
query: Query,
|
||||||
) -> Result<Vec<Entry>> {
|
) -> Result<Vec<Entry>, QueryExecutionError> {
|
||||||
use crate::database::inner::schema::data::dsl::*;
|
use crate::database::inner::schema::data::dsl::*;
|
||||||
let db_query = data.filter(to_sqlite_predicates(query)?);
|
|
||||||
trace!("DB query: {}", debug_query(&db_query));
|
if let Some(predicates) = to_sqlite_predicates(query.clone())? {
|
||||||
db_query.load::<models::Entry>(connection).map_err(anyhow::Error::from)
|
let db_query = data.filter(predicates);
|
||||||
|
db_query
|
||||||
|
.load::<models::Entry>(connection)
|
||||||
|
.map_err(|e| QueryExecutionError(e.to_string()))
|
||||||
|
} else {
|
||||||
|
match query {
|
||||||
|
Query::SingleQuery(_) => Err(QueryExecutionError(
|
||||||
|
"Forced manual evaluation of an atomic query, this should never happen.".into(),
|
||||||
|
)),
|
||||||
|
Query::MultiQuery(mq) => match mq.qualifier {
|
||||||
|
QueryQualifier::Not => Err(QueryExecutionError(
|
||||||
|
"Stopped manual evaluation at NOT sub-query due to performance limits. Please \
|
||||||
|
rework your query."
|
||||||
|
.into(),
|
||||||
|
)),
|
||||||
|
_ => {
|
||||||
|
let subquery_results = mq
|
||||||
|
.queries
|
||||||
|
.iter()
|
||||||
|
.map(|q| execute(connection, *q.clone()))
|
||||||
|
.collect::<Result<Vec<Vec<Entry>>, QueryExecutionError>>()?;
|
||||||
|
match mq.qualifier {
|
||||||
|
QueryQualifier::Not => unreachable!(),
|
||||||
|
QueryQualifier::And => Ok(subquery_results
|
||||||
|
.into_iter()
|
||||||
|
.reduce(|acc, cur| {
|
||||||
|
acc.into_iter()
|
||||||
|
.filter(|e| {
|
||||||
|
cur.iter().map(|e| &e.identity).any(|x| x == &e.identity)
|
||||||
|
})
|
||||||
|
.collect()
|
||||||
|
})
|
||||||
|
.unwrap()), // TODO
|
||||||
|
QueryQualifier::Or => Ok(subquery_results.into_iter().flatten().collect()),
|
||||||
|
QueryQualifier::Join => {
|
||||||
|
let pattern_queries = mq
|
||||||
|
.queries
|
||||||
|
.into_iter()
|
||||||
|
.map(|q| match *q {
|
||||||
|
Query::SingleQuery(QueryPart::Matches(pq)) => Some(pq),
|
||||||
|
_ => None,
|
||||||
|
})
|
||||||
|
.collect::<Option<Vec<_>>>();
|
||||||
|
|
||||||
|
if let Some(pattern_queries) = pattern_queries {
|
||||||
|
let entries = zip(pattern_queries, subquery_results)
|
||||||
|
.into_iter()
|
||||||
|
.map(|(query, results)| {
|
||||||
|
results
|
||||||
|
.into_iter()
|
||||||
|
.map(|e| EntryWithVars::new(&query, e))
|
||||||
|
.collect::<Vec<EntryWithVars>>()
|
||||||
|
});
|
||||||
|
|
||||||
|
let joined = entries
|
||||||
|
.reduce(|acc, cur| {
|
||||||
|
acc.into_iter()
|
||||||
|
.filter(|tested_entry| {
|
||||||
|
tested_entry.vars.iter().any(|(k1, v1)| {
|
||||||
|
cur.iter().any(|other_entry| {
|
||||||
|
other_entry
|
||||||
|
.vars
|
||||||
|
.iter()
|
||||||
|
.any(|(k2, v2)| k1 == k2 && v1 == v2)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.collect()
|
||||||
|
})
|
||||||
|
.unwrap(); // TODO
|
||||||
|
|
||||||
|
Ok(joined.into_iter().map(|ev| ev.entry).collect())
|
||||||
|
} else {
|
||||||
|
Err(QueryExecutionError(
|
||||||
|
"Cannot join on non-atomic queries.".into(),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type Predicate = dyn BoxableExpression<data::table, Sqlite, SqlType = Bool>;
|
struct EntryWithVars {
|
||||||
|
entry: Entry,
|
||||||
|
vars: HashMap<String, String>,
|
||||||
|
}
|
||||||
|
|
||||||
fn to_sqlite_predicates(query: Query) -> Result<Box<Predicate>, QueryExecutionError> {
|
impl EntryWithVars {
|
||||||
|
pub fn new(query: &PatternQuery, entry: Entry) -> Self {
|
||||||
|
let mut vars = HashMap::new();
|
||||||
|
|
||||||
|
if let QueryComponent::Variable(Some(var_name)) = &query.entity {
|
||||||
|
vars.insert(
|
||||||
|
var_name.clone(),
|
||||||
|
crate::util::hash::b58_encode(&entry.entity),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if let QueryComponent::Variable(Some(var_name)) = &query.attribute {
|
||||||
|
vars.insert(var_name.clone(), entry.attribute.clone());
|
||||||
|
}
|
||||||
|
|
||||||
|
if let QueryComponent::Variable(Some(var_name)) = &query.value {
|
||||||
|
if let Some(value_str) = &entry.value_str {
|
||||||
|
vars.insert(var_name.clone(), value_str.clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
EntryWithVars { entry, vars }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type SqlPredicate = dyn BoxableExpression<data::table, Sqlite, SqlType = Bool>;
|
||||||
|
|
||||||
|
type SqlResult = Option<Box<SqlPredicate>>;
|
||||||
|
|
||||||
|
fn to_sqlite_predicates(query: Query) -> Result<SqlResult, QueryExecutionError> {
|
||||||
match query {
|
match query {
|
||||||
Query::SingleQuery(qp) => match qp {
|
Query::SingleQuery(qp) => match qp {
|
||||||
QueryPart::Matches(eq) => {
|
QueryPart::Matches(eq) => {
|
||||||
let mut subqueries: Vec<Box<Predicate>> = vec![];
|
let mut subqueries: Vec<Box<SqlPredicate>> = vec![];
|
||||||
|
|
||||||
match &eq.entity {
|
match &eq.entity {
|
||||||
QueryComponent::Exact(q_entity) => {
|
QueryComponent::Exact(q_entity) => {
|
||||||
|
@ -63,7 +179,7 @@ fn to_sqlite_predicates(query: Query) -> Result<Box<Predicate>, QueryExecutionEr
|
||||||
QueryComponent::Contains(q_entity) => subqueries.push(Box::new(
|
QueryComponent::Contains(q_entity) => subqueries.push(Box::new(
|
||||||
data::entity_searchable.like(format!("%{}%", q_entity)),
|
data::entity_searchable.like(format!("%{}%", q_entity)),
|
||||||
)),
|
)),
|
||||||
QueryComponent::Any => {}
|
QueryComponent::Variable(_) => {}
|
||||||
};
|
};
|
||||||
|
|
||||||
match &eq.attribute {
|
match &eq.attribute {
|
||||||
|
@ -75,7 +191,7 @@ fn to_sqlite_predicates(query: Query) -> Result<Box<Predicate>, QueryExecutionEr
|
||||||
)),
|
)),
|
||||||
QueryComponent::Contains(q_attribute) => subqueries
|
QueryComponent::Contains(q_attribute) => subqueries
|
||||||
.push(Box::new(data::attribute.like(format!("%{}%", q_attribute)))),
|
.push(Box::new(data::attribute.like(format!("%{}%", q_attribute)))),
|
||||||
QueryComponent::Any => {}
|
QueryComponent::Variable(_) => {}
|
||||||
};
|
};
|
||||||
|
|
||||||
match &eq.value {
|
match &eq.value {
|
||||||
|
@ -95,94 +211,111 @@ fn to_sqlite_predicates(query: Query) -> Result<Box<Predicate>, QueryExecutionEr
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
match first {
|
match first {
|
||||||
EntryValue::Number(_) => subqueries.push(Box::new(
|
EntryValue::Number(_) => subqueries.push(Box::new(
|
||||||
data::value_num.eq_any(
|
data::value_num.eq_any(
|
||||||
q_values
|
q_values
|
||||||
.iter()
|
.iter()
|
||||||
.map(|v| {
|
.map(|v| {
|
||||||
if let EntryValue::Number(n) = v {
|
if let EntryValue::Number(n) = v {
|
||||||
Ok(*n)
|
Ok(*n)
|
||||||
} else {
|
} else {
|
||||||
Err(QueryExecutionError(format!("IN queries must not combine numeric and string values! ({v} is not a number)")))
|
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<f64>, QueryExecutionError>>()?,
|
}
|
||||||
),
|
})
|
||||||
)),
|
.collect::<Result<Vec<String>, 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) => {
|
QueryComponent::Contains(q_value) => {
|
||||||
subqueries.push(Box::new(data::value_str.like(format!("S%{}%", q_value))))
|
subqueries.push(Box::new(data::value_str.like(format!("S%{}%", q_value))))
|
||||||
}
|
}
|
||||||
QueryComponent::Any => {}
|
QueryComponent::Variable(_) => {}
|
||||||
};
|
};
|
||||||
|
|
||||||
match subqueries.len() {
|
match subqueries.len() {
|
||||||
0 => Ok(Box::new(true.into_sql::<Bool>())),
|
0 => Ok(Some(Box::new(true.into_sql::<Bool>()))),
|
||||||
1 => Ok(subqueries.remove(0)),
|
1 => Ok(Some(subqueries.remove(0))),
|
||||||
_ => {
|
_ => {
|
||||||
let mut result: Box<And<Box<Predicate>, Box<Predicate>>> =
|
let mut result: Box<And<Box<SqlPredicate>, Box<SqlPredicate>>> =
|
||||||
Box::new(And::new(subqueries.remove(0), subqueries.remove(0)));
|
Box::new(And::new(subqueries.remove(0), subqueries.remove(0)));
|
||||||
while !subqueries.is_empty() {
|
while !subqueries.is_empty() {
|
||||||
result = Box::new(And::new(result, subqueries.remove(0)));
|
result = Box::new(And::new(result, subqueries.remove(0)));
|
||||||
}
|
}
|
||||||
Ok(Box::new(result))
|
Ok(Some(Box::new(result)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
QueryPart::Type(_) => unimplemented!("Type queries are not yet implemented."),
|
QueryPart::Type(_) => unimplemented!("Type queries are not yet implemented."),
|
||||||
},
|
},
|
||||||
Query::MultiQuery(mq) => {
|
Query::MultiQuery(mq) => {
|
||||||
let subqueries: Result<Vec<Box<Predicate>>, QueryExecutionError> = mq
|
let mq_result = mq
|
||||||
.queries
|
.queries
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|sq| to_sqlite_predicates(*sq))
|
.map(|sq| to_sqlite_predicates(*sq))
|
||||||
.collect();
|
.collect::<Result<Vec<SqlResult>, QueryExecutionError>>()?;
|
||||||
let mut subqueries: Vec<Box<Predicate>> = subqueries?;
|
|
||||||
match subqueries.len() {
|
let mq_result: Option<Vec<Box<SqlPredicate>>> = mq_result.into_iter().collect();
|
||||||
0 => Ok(Box::new(true.into_sql::<Bool>())),
|
|
||||||
1 => {
|
if let Some(mut subqueries) = mq_result {
|
||||||
if let QueryQualifier::Not = mq.qualifier {
|
match subqueries.len() {
|
||||||
Ok(Box::new(Not::new(subqueries.remove(0))))
|
0 => Ok(Some(Box::new(true.into_sql::<Bool>()))),
|
||||||
} else {
|
1 => {
|
||||||
Ok(subqueries.remove(0))
|
if let QueryQualifier::Not = mq.qualifier {
|
||||||
|
Ok(Some(Box::new(Not::new(subqueries.remove(0)))))
|
||||||
|
} else {
|
||||||
|
Ok(Some(subqueries.remove(0)))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
_ => match mq.qualifier {
|
||||||
|
QueryQualifier::Join => Ok(None),
|
||||||
|
QueryQualifier::And => {
|
||||||
|
let mut result: Box<And<Box<SqlPredicate>, Box<SqlPredicate>>> =
|
||||||
|
Box::new(And::new(subqueries.remove(0), subqueries.remove(0)));
|
||||||
|
while !subqueries.is_empty() {
|
||||||
|
result = Box::new(And::new(result, subqueries.remove(0)));
|
||||||
|
}
|
||||||
|
Ok(Some(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(Some(Box::new(Grouped(result))))
|
||||||
|
}
|
||||||
|
QueryQualifier::Not => {
|
||||||
|
Err(QueryExecutionError("NOT only takes one subquery.".into()))
|
||||||
|
}
|
||||||
|
},
|
||||||
}
|
}
|
||||||
_ => match mq.qualifier {
|
} else {
|
||||||
QueryQualifier::And => {
|
Ok(None)
|
||||||
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()))
|
|
||||||
}
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -96,7 +96,7 @@ impl PointerEntries for Vec<Entry> {
|
||||||
pub fn list_roots(connection: &UpEndConnection) -> Result<Vec<Address>> {
|
pub fn list_roots(connection: &UpEndConnection) -> Result<Vec<Address>> {
|
||||||
let all_directories: Vec<Entry> =
|
let all_directories: Vec<Entry> =
|
||||||
connection.query(Query::SingleQuery(QueryPart::Matches(PatternQuery {
|
connection.query(Query::SingleQuery(QueryPart::Matches(PatternQuery {
|
||||||
entity: QueryComponent::Any,
|
entity: QueryComponent::Variable(None),
|
||||||
attribute: QueryComponent::Exact(IS_OF_TYPE_ATTR.into()),
|
attribute: QueryComponent::Exact(IS_OF_TYPE_ATTR.into()),
|
||||||
value: QueryComponent::Exact(HIER_ADDR.clone().into()),
|
value: QueryComponent::Exact(HIER_ADDR.clone().into()),
|
||||||
})))?;
|
})))?;
|
||||||
|
@ -104,9 +104,9 @@ pub fn list_roots(connection: &UpEndConnection) -> Result<Vec<Address>> {
|
||||||
// TODO: this is horrible
|
// TODO: this is horrible
|
||||||
let directories_with_parents: Vec<Address> = connection
|
let directories_with_parents: Vec<Address> = connection
|
||||||
.query(Query::SingleQuery(QueryPart::Matches(PatternQuery {
|
.query(Query::SingleQuery(QueryPart::Matches(PatternQuery {
|
||||||
entity: QueryComponent::Any,
|
entity: QueryComponent::Variable(None),
|
||||||
attribute: QueryComponent::Exact(HIER_HAS_ATTR.into()),
|
attribute: QueryComponent::Exact(HIER_HAS_ATTR.into()),
|
||||||
value: QueryComponent::Any,
|
value: QueryComponent::Variable(None),
|
||||||
})))?
|
})))?
|
||||||
.extract_pointers()
|
.extract_pointers()
|
||||||
.into_iter()
|
.into_iter()
|
||||||
|
@ -142,7 +142,7 @@ pub fn fetch_or_create_dir(
|
||||||
|
|
||||||
let matching_directories = connection
|
let matching_directories = connection
|
||||||
.query(Query::SingleQuery(QueryPart::Matches(PatternQuery {
|
.query(Query::SingleQuery(QueryPart::Matches(PatternQuery {
|
||||||
entity: QueryComponent::Any,
|
entity: QueryComponent::Variable(None),
|
||||||
attribute: QueryComponent::Exact(LABEL_ATTR.into()),
|
attribute: QueryComponent::Exact(LABEL_ATTR.into()),
|
||||||
value: QueryComponent::Exact(directory.as_ref().clone().into()),
|
value: QueryComponent::Exact(directory.as_ref().clone().into()),
|
||||||
})))?
|
})))?
|
||||||
|
@ -154,7 +154,7 @@ pub fn fetch_or_create_dir(
|
||||||
.query(Query::SingleQuery(QueryPart::Matches(PatternQuery {
|
.query(Query::SingleQuery(QueryPart::Matches(PatternQuery {
|
||||||
entity: QueryComponent::Exact(parent),
|
entity: QueryComponent::Exact(parent),
|
||||||
attribute: QueryComponent::Exact(HIER_HAS_ATTR.into()),
|
attribute: QueryComponent::Exact(HIER_HAS_ATTR.into()),
|
||||||
value: QueryComponent::Any,
|
value: QueryComponent::Variable(None),
|
||||||
})))?
|
})))?
|
||||||
.extract_pointers()
|
.extract_pointers()
|
||||||
.into_iter()
|
.into_iter()
|
||||||
|
|
|
@ -22,7 +22,7 @@ where
|
||||||
Exact(T),
|
Exact(T),
|
||||||
In(Vec<T>),
|
In(Vec<T>),
|
||||||
Contains(String),
|
Contains(String),
|
||||||
Any,
|
Variable(Option<String>),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq)]
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
|
@ -99,6 +99,7 @@ pub enum QueryQualifier {
|
||||||
And,
|
And,
|
||||||
Or,
|
Or,
|
||||||
Not,
|
Not,
|
||||||
|
Join,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq)]
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
|
@ -182,7 +183,14 @@ impl TryFrom<&lexpr::Value> for Query {
|
||||||
)))
|
)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
lexpr::Value::Symbol(symbol) if symbol.as_ref() == "?" => Ok(QueryComponent::Any),
|
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())?)),
|
_ => Ok(QueryComponent::Exact(T::try_from(value.clone())?)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -227,7 +235,7 @@ impl TryFrom<&lexpr::Value> for Query {
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
"and" | "or" => {
|
"and" | "or" | "join" => {
|
||||||
let (cons_vec, _) = value.clone().into_vec();
|
let (cons_vec, _) = value.clone().into_vec();
|
||||||
let sub_expressions = &cons_vec[1..];
|
let sub_expressions = &cons_vec[1..];
|
||||||
let values = sub_expressions
|
let values = sub_expressions
|
||||||
|
@ -239,6 +247,7 @@ impl TryFrom<&lexpr::Value> for Query {
|
||||||
Ok(Query::MultiQuery(MultiQuery {
|
Ok(Query::MultiQuery(MultiQuery {
|
||||||
qualifier: match symbol.borrow() {
|
qualifier: match symbol.borrow() {
|
||||||
"and" => QueryQualifier::And,
|
"and" => QueryQualifier::And,
|
||||||
|
"join" => QueryQualifier::Join,
|
||||||
_ => QueryQualifier::Or,
|
_ => QueryQualifier::Or,
|
||||||
},
|
},
|
||||||
queries,
|
queries,
|
||||||
|
@ -295,11 +304,10 @@ impl FromStr for Query {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
use super::*;
|
use super::*;
|
||||||
use anyhow::{Result};
|
use anyhow::Result;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_matches() -> Result<()> {
|
fn test_matches() -> Result<()> {
|
||||||
|
@ -307,9 +315,9 @@ mod test {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
query,
|
query,
|
||||||
Query::SingleQuery(QueryPart::Matches(PatternQuery {
|
Query::SingleQuery(QueryPart::Matches(PatternQuery {
|
||||||
entity: QueryComponent::Any,
|
entity: QueryComponent::Variable(None),
|
||||||
attribute: QueryComponent::Any,
|
attribute: QueryComponent::Variable(None),
|
||||||
value: QueryComponent::Any
|
value: QueryComponent::Variable(None)
|
||||||
}))
|
}))
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -319,8 +327,8 @@ mod test {
|
||||||
query,
|
query,
|
||||||
Query::SingleQuery(QueryPart::Matches(PatternQuery {
|
Query::SingleQuery(QueryPart::Matches(PatternQuery {
|
||||||
entity: QueryComponent::Exact(address),
|
entity: QueryComponent::Exact(address),
|
||||||
attribute: QueryComponent::Any,
|
attribute: QueryComponent::Variable(None),
|
||||||
value: QueryComponent::Any
|
value: QueryComponent::Variable(None)
|
||||||
}))
|
}))
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -328,9 +336,9 @@ mod test {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
query,
|
query,
|
||||||
Query::SingleQuery(QueryPart::Matches(PatternQuery {
|
Query::SingleQuery(QueryPart::Matches(PatternQuery {
|
||||||
entity: QueryComponent::Any,
|
entity: QueryComponent::Variable(None),
|
||||||
attribute: QueryComponent::Exact("FOO".into()),
|
attribute: QueryComponent::Exact("FOO".into()),
|
||||||
value: QueryComponent::Any
|
value: QueryComponent::Variable(None)
|
||||||
}))
|
}))
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -339,8 +347,8 @@ mod test {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
query,
|
query,
|
||||||
Query::SingleQuery(QueryPart::Matches(PatternQuery {
|
Query::SingleQuery(QueryPart::Matches(PatternQuery {
|
||||||
entity: QueryComponent::Any,
|
entity: QueryComponent::Variable(None),
|
||||||
attribute: QueryComponent::Any,
|
attribute: QueryComponent::Variable(None),
|
||||||
value: QueryComponent::Exact(value)
|
value: QueryComponent::Exact(value)
|
||||||
}))
|
}))
|
||||||
);
|
);
|
||||||
|
@ -348,15 +356,30 @@ mod test {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_joins() -> Result<()> {
|
||||||
|
let query = "(matches ?a ?b ?)".parse::<Query>()?;
|
||||||
|
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]
|
#[test]
|
||||||
fn test_in_parse() -> Result<()> {
|
fn test_in_parse() -> Result<()> {
|
||||||
let query = r#"(matches ? (in "FOO" "BAR") ?)"#.parse::<Query>()?;
|
let query = r#"(matches ? (in "FOO" "BAR") ?)"#.parse::<Query>()?;
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
query,
|
query,
|
||||||
Query::SingleQuery(QueryPart::Matches(PatternQuery {
|
Query::SingleQuery(QueryPart::Matches(PatternQuery {
|
||||||
entity: QueryComponent::Any,
|
entity: QueryComponent::Variable(None),
|
||||||
attribute: QueryComponent::In(vec!("FOO".into(), "BAR".into())),
|
attribute: QueryComponent::In(vec!("FOO".into(), "BAR".into())),
|
||||||
value: QueryComponent::Any
|
value: QueryComponent::Variable(None)
|
||||||
}))
|
}))
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -365,8 +388,8 @@ mod test {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
query,
|
query,
|
||||||
Query::SingleQuery(QueryPart::Matches(PatternQuery {
|
Query::SingleQuery(QueryPart::Matches(PatternQuery {
|
||||||
entity: QueryComponent::Any,
|
entity: QueryComponent::Variable(None),
|
||||||
attribute: QueryComponent::Any,
|
attribute: QueryComponent::Variable(None),
|
||||||
value: QueryComponent::In(values)
|
value: QueryComponent::In(values)
|
||||||
}))
|
}))
|
||||||
);
|
);
|
||||||
|
@ -376,8 +399,8 @@ mod test {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
query,
|
query,
|
||||||
Query::SingleQuery(QueryPart::Matches(PatternQuery {
|
Query::SingleQuery(QueryPart::Matches(PatternQuery {
|
||||||
entity: QueryComponent::Any,
|
entity: QueryComponent::Variable(None),
|
||||||
attribute: QueryComponent::Any,
|
attribute: QueryComponent::Variable(None),
|
||||||
value: QueryComponent::In(values)
|
value: QueryComponent::In(values)
|
||||||
}))
|
}))
|
||||||
);
|
);
|
||||||
|
@ -389,8 +412,8 @@ mod test {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
query,
|
query,
|
||||||
Query::SingleQuery(QueryPart::Matches(PatternQuery {
|
Query::SingleQuery(QueryPart::Matches(PatternQuery {
|
||||||
entity: QueryComponent::Any,
|
entity: QueryComponent::Variable(None),
|
||||||
attribute: QueryComponent::Any,
|
attribute: QueryComponent::Variable(None),
|
||||||
value: QueryComponent::In(values)
|
value: QueryComponent::In(values)
|
||||||
}))
|
}))
|
||||||
);
|
);
|
||||||
|
@ -401,8 +424,8 @@ mod test {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
query,
|
query,
|
||||||
Query::SingleQuery(QueryPart::Matches(PatternQuery {
|
Query::SingleQuery(QueryPart::Matches(PatternQuery {
|
||||||
entity: QueryComponent::Any,
|
entity: QueryComponent::Variable(None),
|
||||||
attribute: QueryComponent::Any,
|
attribute: QueryComponent::Variable(None),
|
||||||
value: QueryComponent::In(values)
|
value: QueryComponent::In(values)
|
||||||
}))
|
}))
|
||||||
);
|
);
|
||||||
|
@ -417,8 +440,8 @@ mod test {
|
||||||
query,
|
query,
|
||||||
Query::SingleQuery(QueryPart::Matches(PatternQuery {
|
Query::SingleQuery(QueryPart::Matches(PatternQuery {
|
||||||
entity: QueryComponent::Contains("foo".to_string()),
|
entity: QueryComponent::Contains("foo".to_string()),
|
||||||
attribute: QueryComponent::Any,
|
attribute: QueryComponent::Variable(None),
|
||||||
value: QueryComponent::Any
|
value: QueryComponent::Variable(None)
|
||||||
}))
|
}))
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -426,9 +449,9 @@ mod test {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
query,
|
query,
|
||||||
Query::SingleQuery(QueryPart::Matches(PatternQuery {
|
Query::SingleQuery(QueryPart::Matches(PatternQuery {
|
||||||
entity: QueryComponent::Any,
|
entity: QueryComponent::Variable(None),
|
||||||
attribute: QueryComponent::Contains("foo".to_string()),
|
attribute: QueryComponent::Contains("foo".to_string()),
|
||||||
value: QueryComponent::Any,
|
value: QueryComponent::Variable(None),
|
||||||
}))
|
}))
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -436,8 +459,8 @@ mod test {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
query,
|
query,
|
||||||
Query::SingleQuery(QueryPart::Matches(PatternQuery {
|
Query::SingleQuery(QueryPart::Matches(PatternQuery {
|
||||||
entity: QueryComponent::Any,
|
entity: QueryComponent::Variable(None),
|
||||||
attribute: QueryComponent::Any,
|
attribute: QueryComponent::Variable(None),
|
||||||
value: QueryComponent::Contains("foo".to_string())
|
value: QueryComponent::Contains("foo".to_string())
|
||||||
}))
|
}))
|
||||||
);
|
);
|
||||||
|
|
|
@ -510,5 +510,17 @@ mod test {
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let result = connection.query(query).unwrap();
|
let result = connection.query(query).unwrap();
|
||||||
assert_eq!(result.len(), 1);
|
assert_eq!(result.len(), 1);
|
||||||
|
|
||||||
|
let query = format!(
|
||||||
|
r#"(join
|
||||||
|
(matches ?a "FLAVOUR" ?)
|
||||||
|
(matches ?a "{LABEL_ATTR}" "FOOBAR")
|
||||||
|
)"#
|
||||||
|
)
|
||||||
|
.parse()
|
||||||
|
.unwrap();
|
||||||
|
let result = connection.query(query).unwrap();
|
||||||
|
assert_eq!(result.len(), 1);
|
||||||
|
assert_eq!(result[0].value, "STRANGE".into());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue