2021-07-26 21:00:05 +02:00
|
|
|
use crate::addressing::Address;
|
|
|
|
use crate::database::entry::EntryValue;
|
2021-07-26 21:14:12 +02:00
|
|
|
use crate::database::inner::schema::data;
|
2021-07-26 21:00:05 +02:00
|
|
|
use anyhow::{anyhow, Result};
|
2021-12-21 11:50:46 +01:00
|
|
|
use diesel::expression::operators::{And, Not, Or};
|
2021-07-26 21:00:05 +02:00
|
|
|
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;
|
|
|
|
|
2022-02-02 17:13:11 +01:00
|
|
|
#[derive(Debug, PartialEq)]
|
2021-07-26 21:00:05 +02:00
|
|
|
pub enum QueryComponent<T>
|
|
|
|
where
|
|
|
|
T: FromStr,
|
|
|
|
{
|
|
|
|
Exact(T),
|
|
|
|
In(Vec<T>),
|
|
|
|
Contains(String),
|
|
|
|
Any,
|
|
|
|
}
|
|
|
|
|
2022-02-02 17:13:11 +01:00
|
|
|
#[derive(Debug, PartialEq)]
|
2021-07-26 21:00:05 +02:00
|
|
|
pub struct EntryQuery {
|
|
|
|
pub entity: QueryComponent<Address>,
|
|
|
|
pub attribute: QueryComponent<String>,
|
|
|
|
pub value: QueryComponent<EntryValue>,
|
|
|
|
}
|
|
|
|
|
2022-02-02 17:13:11 +01:00
|
|
|
#[derive(Debug, PartialEq)]
|
2021-07-26 21:00:05 +02:00
|
|
|
pub enum QueryPart {
|
|
|
|
Matches(EntryQuery),
|
|
|
|
Type(String),
|
|
|
|
}
|
|
|
|
|
2022-02-02 17:13:11 +01:00
|
|
|
#[derive(Debug, PartialEq)]
|
2021-07-26 21:00:05 +02:00
|
|
|
pub enum QueryQualifier {
|
|
|
|
And,
|
|
|
|
Or,
|
2021-12-21 11:50:46 +01:00
|
|
|
Not,
|
2021-07-26 21:00:05 +02:00
|
|
|
}
|
|
|
|
|
2022-02-02 17:13:11 +01:00
|
|
|
#[derive(Debug, PartialEq)]
|
2021-07-26 21:00:05 +02:00
|
|
|
pub struct MultiQuery {
|
|
|
|
pub qualifier: QueryQualifier,
|
|
|
|
pub queries: NonEmpty<Box<Query>>,
|
|
|
|
}
|
|
|
|
|
2022-02-02 17:13:11 +01:00
|
|
|
#[derive(Debug, PartialEq)]
|
2021-07-26 21:00:05 +02:00
|
|
|
pub enum Query {
|
|
|
|
SingleQuery(QueryPart),
|
|
|
|
MultiQuery(MultiQuery),
|
|
|
|
}
|
|
|
|
|
|
|
|
type Predicate = dyn BoxableExpression<data::table, Sqlite, SqlType = Bool>;
|
|
|
|
|
|
|
|
impl TryFrom<&lexpr::Value> for Query {
|
|
|
|
type Error = anyhow::Error;
|
|
|
|
|
|
|
|
fn try_from(expression: &lexpr::Value) -> Result<Self> {
|
|
|
|
fn parse_component<T: FromStr>(value: &lexpr::Value) -> Result<QueryComponent<T>>
|
|
|
|
where
|
|
|
|
<T as FromStr>::Err: std::fmt::Debug,
|
|
|
|
{
|
|
|
|
match value {
|
|
|
|
lexpr::Value::Cons(cons) => {
|
|
|
|
if let lexpr::Value::Symbol(symbol) = cons.car() {
|
|
|
|
match symbol.borrow() {
|
|
|
|
"in" => {
|
|
|
|
let (cons_vec, _) = cons.clone().into_vec();
|
|
|
|
if let Some(split) = cons_vec.split_first() {
|
|
|
|
let args = split.1;
|
|
|
|
let values: Result<Vec<T>, _> = args.iter().map(|value| {
|
|
|
|
if let lexpr::Value::String(str) = value {
|
|
|
|
match T::from_str(str.borrow()) {
|
|
|
|
Ok(value) => Ok(value),
|
|
|
|
Err(error) => Err(anyhow!(format!("Malformed expression: Conversion of inner value '{}' from string failed: {:#?}",str, error))),
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
Err(anyhow!("Malformed expression: Inner value list must be comprised of strings."))
|
|
|
|
}
|
|
|
|
}).collect();
|
|
|
|
|
|
|
|
Ok(QueryComponent::In(values?))
|
|
|
|
} else {
|
|
|
|
Err(anyhow!(
|
|
|
|
"Malformed expression: Inner value cannot be empty."
|
|
|
|
))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
"contains" => {
|
|
|
|
let (mut cons_vec, _) = cons.clone().into_vec();
|
|
|
|
match cons_vec.len() {
|
|
|
|
2 => {
|
|
|
|
let value = cons_vec.remove(1);
|
|
|
|
if let lexpr::Value::String(str) = value {
|
|
|
|
Ok(QueryComponent::Contains(str.into_string()))
|
|
|
|
} else {
|
|
|
|
Err(anyhow!("Malformed expression: 'Contains' argument must be a string."))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
_ => Err(anyhow!(
|
|
|
|
"Malformed expression: 'Contains' requires a single argument."
|
|
|
|
)),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
_ => Err(anyhow!(format!(
|
|
|
|
"Malformed expression: Unknown symbol {}",
|
|
|
|
symbol
|
|
|
|
))),
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
Err(anyhow!(format!(
|
|
|
|
"Malformed expression: Inner value '{:?}' is not a symbol.",
|
|
|
|
value
|
|
|
|
)))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
lexpr::Value::String(str) => match T::from_str(str.borrow()) {
|
|
|
|
Ok(value) => Ok(QueryComponent::Exact(value)),
|
|
|
|
Err(error) => Err(anyhow!(format!(
|
|
|
|
"Malformed expression: Conversion of inner value '{}' from string failed: {:#?}",
|
|
|
|
str, error
|
|
|
|
))),
|
|
|
|
},
|
|
|
|
lexpr::Value::Symbol(symbol) => match symbol.borrow() {
|
|
|
|
"?" => Ok(QueryComponent::Any),
|
|
|
|
_ => Err(anyhow!(format!(
|
|
|
|
"Malformed expression: Unknown symbol {}",
|
|
|
|
symbol
|
|
|
|
))),
|
|
|
|
},
|
|
|
|
_ => Err(anyhow!(
|
|
|
|
"Malformed expression: Inner value not a string, list or '?'."
|
|
|
|
)),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if let lexpr::Value::Cons(value) = expression {
|
|
|
|
if let lexpr::Value::Symbol(symbol) = value.car() {
|
|
|
|
match symbol.borrow() {
|
|
|
|
"matches" => {
|
|
|
|
let (cons_vec, _) = value.clone().into_vec();
|
|
|
|
if let [_, entity, attribute, value] = &cons_vec[..] {
|
|
|
|
let entity = parse_component::<Address>(entity)?;
|
|
|
|
let attribute = parse_component::<String>(attribute)?;
|
|
|
|
let value = parse_component::<EntryValue>(value)?;
|
|
|
|
Ok(Query::SingleQuery(QueryPart::Matches(EntryQuery {
|
|
|
|
entity,
|
|
|
|
attribute,
|
|
|
|
value,
|
|
|
|
})))
|
|
|
|
} else {
|
|
|
|
Err(anyhow!(
|
|
|
|
"Malformed expression: Wrong number of arguments to 'matches'."
|
|
|
|
))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
"type" => {
|
|
|
|
let (cons_vec, _) = value.clone().into_vec();
|
|
|
|
if let [_, type_name] = &cons_vec[..] {
|
|
|
|
if let lexpr::Value::String(type_name_str) = type_name {
|
|
|
|
Ok(Query::SingleQuery(QueryPart::Type(
|
|
|
|
type_name_str.to_string(),
|
|
|
|
)))
|
|
|
|
} else {
|
|
|
|
Err(anyhow!(
|
|
|
|
"Malformed expression: Type must be specified as a string."
|
|
|
|
))
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
Err(anyhow!(
|
|
|
|
"Malformed expression: Wrong number of arguments to 'type'."
|
|
|
|
))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
"and" | "or" => {
|
|
|
|
let (cons_vec, _) = value.clone().into_vec();
|
|
|
|
let sub_expressions = &cons_vec[1..];
|
2021-12-21 11:50:46 +01:00
|
|
|
let values = sub_expressions
|
2021-07-26 21:00:05 +02:00
|
|
|
.iter()
|
|
|
|
.map(|value| Ok(Box::new(Query::try_from(value)?)))
|
2021-12-21 11:50:46 +01:00
|
|
|
.collect::<Result<Vec<Box<Query>>>>()?;
|
2021-07-26 21:00:05 +02:00
|
|
|
|
2021-12-21 11:50:46 +01:00
|
|
|
if let Some(queries) = NonEmpty::from_vec(values) {
|
2021-07-26 21:00:05 +02:00
|
|
|
Ok(Query::MultiQuery(MultiQuery {
|
|
|
|
qualifier: match symbol.borrow() {
|
|
|
|
"and" => QueryQualifier::And,
|
|
|
|
_ => QueryQualifier::Or,
|
|
|
|
},
|
|
|
|
queries,
|
|
|
|
}))
|
|
|
|
} else {
|
|
|
|
Err(anyhow!(
|
|
|
|
"Malformed expression: sub-query list cannot be empty.",
|
|
|
|
))
|
|
|
|
}
|
|
|
|
}
|
2021-12-21 11:50:46 +01:00
|
|
|
"not" => {
|
|
|
|
let (cons_vec, _) = value.clone().into_vec();
|
|
|
|
let sub_expressions = &cons_vec[1..];
|
|
|
|
let values = sub_expressions
|
|
|
|
.iter()
|
|
|
|
.map(|value| Ok(Box::new(Query::try_from(value)?)))
|
|
|
|
.collect::<Result<Vec<Box<Query>>>>()?;
|
|
|
|
|
|
|
|
if values.len() == 1 {
|
|
|
|
Ok(Query::MultiQuery(MultiQuery {
|
|
|
|
qualifier: QueryQualifier::Not,
|
|
|
|
queries: NonEmpty::from_vec(values).unwrap(),
|
|
|
|
}))
|
|
|
|
} else {
|
|
|
|
Err(anyhow!(
|
|
|
|
"Malformed expression: NOT takes exactly one parameter."
|
|
|
|
))
|
|
|
|
}
|
|
|
|
}
|
2021-07-26 21:00:05 +02:00
|
|
|
_ => Err(anyhow!(format!(
|
|
|
|
"Malformed expression: Unknown symbol '{}'.",
|
|
|
|
symbol
|
|
|
|
))),
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
Err(anyhow!(format!(
|
|
|
|
"Malformed expression: Value '{:?}' is not a symbol.",
|
|
|
|
value
|
|
|
|
)))
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
Err(anyhow!("Malformed expression: Not a list."))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-12-21 12:01:40 +01:00
|
|
|
impl FromStr for Query {
|
|
|
|
type Err = anyhow::Error;
|
|
|
|
|
|
|
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
2022-02-04 23:01:44 +01:00
|
|
|
let sexp = lexpr::from_str_custom(s, lexpr::parse::Options::new())?;
|
2021-12-21 12:01:40 +01:00
|
|
|
Query::try_from(&sexp)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-07-26 21:00:05 +02:00
|
|
|
impl Query {
|
|
|
|
pub(crate) fn to_sqlite_predicates(&self) -> Result<Box<Predicate>> {
|
|
|
|
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()?)))
|
|
|
|
}
|
|
|
|
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?)))
|
|
|
|
}
|
2022-02-07 18:33:57 +01:00
|
|
|
QueryComponent::Contains(q_entity) => subqueries.push(Box::new(
|
|
|
|
data::entity_searchable.like(format!("%{}%", q_entity)),
|
|
|
|
)),
|
2021-07-26 21:00:05 +02:00
|
|
|
QueryComponent::Any => {}
|
|
|
|
};
|
|
|
|
|
|
|
|
match &eq.attribute {
|
|
|
|
QueryComponent::Exact(q_attribute) => {
|
|
|
|
subqueries.push(Box::new(data::attribute.eq(q_attribute.clone())))
|
|
|
|
}
|
|
|
|
QueryComponent::In(q_attributes) => subqueries
|
|
|
|
.push(Box::new(data::attribute.eq_any(q_attributes.clone()))),
|
|
|
|
QueryComponent::Contains(q_attribute) => subqueries
|
|
|
|
.push(Box::new(data::attribute.like(format!("%{}%", q_attribute)))),
|
|
|
|
QueryComponent::Any => {}
|
|
|
|
};
|
|
|
|
|
|
|
|
match &eq.value {
|
2022-01-28 18:17:14 +01:00
|
|
|
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()?))),
|
|
|
|
},
|
2021-07-26 21:00:05 +02:00
|
|
|
QueryComponent::In(q_values) => {
|
2022-01-28 18:17:14 +01:00
|
|
|
let first = q_values.first().ok_or(anyhow!(
|
|
|
|
"Malformed expression: Inner value cannot be empty."
|
|
|
|
))?;
|
|
|
|
|
|
|
|
match first {
|
2022-02-02 17:13:11 +01:00
|
|
|
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 {
|
2022-03-28 20:06:00 +02:00
|
|
|
Err(anyhow!("IN queries must not combine numeric and string values! ({v} is not a number)"))
|
2022-02-02 17:13:11 +01:00
|
|
|
}
|
|
|
|
})
|
|
|
|
.collect::<Result<Vec<f64>>>()?,
|
|
|
|
),
|
|
|
|
)),
|
2022-01-28 18:17:14 +01:00
|
|
|
_ => subqueries.push(Box::new(
|
|
|
|
data::value_str.eq_any(
|
|
|
|
q_values
|
|
|
|
.iter()
|
2022-02-02 17:13:11 +01:00
|
|
|
.map(|v| {
|
|
|
|
if let EntryValue::Number(_) = v {
|
|
|
|
Err(anyhow!("IN queries must not combine numeric and string values! (Found {v})"))
|
|
|
|
} else {
|
|
|
|
v.to_string()
|
|
|
|
}
|
|
|
|
})
|
2022-01-28 18:17:14 +01:00
|
|
|
.collect::<Result<Vec<String>>>()?,
|
|
|
|
),
|
|
|
|
)),
|
|
|
|
}
|
2021-07-26 21:00:05 +02:00
|
|
|
}
|
|
|
|
QueryComponent::Contains(q_value) => subqueries
|
2022-01-30 16:20:02 +01:00
|
|
|
.push(Box::new(data::value_str.like(format!("S%{}%", q_value)))),
|
2021-07-26 21:00:05 +02:00
|
|
|
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>>> = 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>())),
|
2021-12-21 11:50:46 +01:00
|
|
|
1 => {
|
|
|
|
if let QueryQualifier::Not = mq.qualifier {
|
|
|
|
Ok(Box::new(Not::new(subqueries.remove(0))))
|
|
|
|
} else {
|
|
|
|
Ok(subqueries.remove(0))
|
|
|
|
}
|
|
|
|
}
|
2021-07-26 21:00:05 +02:00
|
|
|
_ => 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(result))
|
|
|
|
}
|
|
|
|
QueryQualifier::Or => {
|
|
|
|
let mut result: Box<Or<Box<Predicate>, Box<Predicate>>> =
|
|
|
|
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(result))
|
|
|
|
}
|
2021-12-21 11:50:46 +01:00
|
|
|
QueryQualifier::Not => Err(anyhow!("NOT only takes one subquery.")),
|
2021-07-26 21:00:05 +02:00
|
|
|
},
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2022-02-02 17:13:11 +01:00
|
|
|
|
|
|
|
#[cfg(test)]
|
|
|
|
mod test {
|
|
|
|
use super::*;
|
|
|
|
use anyhow::Result;
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_matches() -> Result<()> {
|
|
|
|
let query = "(matches ? ? ?)".parse::<Query>()?;
|
|
|
|
assert_eq!(
|
|
|
|
query,
|
|
|
|
Query::SingleQuery(QueryPart::Matches(EntryQuery {
|
|
|
|
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 {
|
|
|
|
entity: QueryComponent::Exact(address),
|
|
|
|
attribute: QueryComponent::Any,
|
|
|
|
value: QueryComponent::Any
|
|
|
|
}))
|
|
|
|
);
|
|
|
|
query.to_sqlite_predicates()?;
|
|
|
|
|
|
|
|
let query = "(matches ? \"FOO\" ?)".parse::<Query>()?;
|
|
|
|
assert_eq!(
|
|
|
|
query,
|
|
|
|
Query::SingleQuery(QueryPart::Matches(EntryQuery {
|
|
|
|
entity: QueryComponent::Any,
|
|
|
|
attribute: QueryComponent::Exact(String::from("FOO")),
|
|
|
|
value: QueryComponent::Any
|
|
|
|
}))
|
|
|
|
);
|
|
|
|
query.to_sqlite_predicates()?;
|
|
|
|
|
|
|
|
let value = EntryValue::Number(1337.93);
|
|
|
|
let query = format!("(matches ? ? \"{}\")", value.to_string().unwrap()).parse::<Query>()?;
|
|
|
|
assert_eq!(
|
|
|
|
query,
|
|
|
|
Query::SingleQuery(QueryPart::Matches(EntryQuery {
|
|
|
|
entity: QueryComponent::Any,
|
|
|
|
attribute: QueryComponent::Any,
|
|
|
|
value: QueryComponent::Exact(value)
|
|
|
|
}))
|
|
|
|
);
|
|
|
|
query.to_sqlite_predicates()?;
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_in_parse() -> Result<()> {
|
|
|
|
let query = "(matches ? (in \"FOO\" \"BAR\") ?)".parse::<Query>()?;
|
|
|
|
assert_eq!(
|
|
|
|
query,
|
|
|
|
Query::SingleQuery(QueryPart::Matches(EntryQuery {
|
|
|
|
entity: QueryComponent::Any,
|
|
|
|
attribute: QueryComponent::In(vec!("FOO".to_string(), "BAR".to_string())),
|
|
|
|
value: QueryComponent::Any
|
|
|
|
}))
|
|
|
|
);
|
|
|
|
query.to_sqlite_predicates()?;
|
|
|
|
|
2022-02-13 12:37:16 +01:00
|
|
|
let values: Vec<EntryValue> = vec!["FOO".into(), "BAR".into()];
|
2022-02-02 17:13:11 +01:00
|
|
|
let query = format!(
|
|
|
|
"(matches ? ? (in {}))",
|
|
|
|
values
|
|
|
|
.iter()
|
|
|
|
.map(|v| format!("\"{}\"", v.to_string().unwrap()))
|
|
|
|
.collect::<Vec<String>>()
|
|
|
|
.join(" ")
|
|
|
|
)
|
|
|
|
.parse::<Query>()?;
|
|
|
|
assert_eq!(
|
|
|
|
query,
|
|
|
|
Query::SingleQuery(QueryPart::Matches(EntryQuery {
|
|
|
|
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 = format!(
|
|
|
|
"(matches ? ? (in {}))",
|
|
|
|
values
|
|
|
|
.iter()
|
|
|
|
.map(|v| format!("\"{}\"", v.to_string().unwrap()))
|
|
|
|
.collect::<Vec<String>>()
|
|
|
|
.join(" ")
|
|
|
|
)
|
|
|
|
.parse::<Query>()?;
|
|
|
|
assert_eq!(
|
|
|
|
query,
|
|
|
|
Query::SingleQuery(QueryPart::Matches(EntryQuery {
|
|
|
|
entity: QueryComponent::Any,
|
|
|
|
attribute: QueryComponent::Any,
|
|
|
|
value: QueryComponent::In(values)
|
|
|
|
}))
|
|
|
|
);
|
|
|
|
query.to_sqlite_predicates()?;
|
|
|
|
|
|
|
|
// Invalid queries
|
2022-02-13 12:37:16 +01:00
|
|
|
let values: Vec<EntryValue> = vec!["FOO".into(), EntryValue::Number(1337.93)];
|
2022-02-02 17:13:11 +01:00
|
|
|
let query = format!(
|
|
|
|
"(matches ? ? (in {}))",
|
|
|
|
values
|
|
|
|
.iter()
|
|
|
|
.map(|v| format!("\"{}\"", v.to_string().unwrap()))
|
|
|
|
.collect::<Vec<String>>()
|
|
|
|
.join(" ")
|
|
|
|
)
|
|
|
|
.parse::<Query>()?;
|
|
|
|
assert_eq!(
|
|
|
|
query,
|
|
|
|
Query::SingleQuery(QueryPart::Matches(EntryQuery {
|
|
|
|
entity: QueryComponent::Any,
|
|
|
|
attribute: QueryComponent::Any,
|
|
|
|
value: QueryComponent::In(values)
|
|
|
|
}))
|
|
|
|
);
|
|
|
|
query
|
|
|
|
.to_sqlite_predicates()
|
|
|
|
.err()
|
|
|
|
.ok_or(anyhow!("Failed to reject mixed query."))?;
|
|
|
|
|
2022-02-13 12:37:16 +01:00
|
|
|
let values = vec![EntryValue::Number(1337.93), "FOO".into()];
|
2022-02-02 17:13:11 +01:00
|
|
|
let query = format!(
|
|
|
|
"(matches ? ? (in {}))",
|
|
|
|
values
|
|
|
|
.iter()
|
|
|
|
.map(|v| format!("\"{}\"", v.to_string().unwrap()))
|
|
|
|
.collect::<Vec<String>>()
|
|
|
|
.join(" ")
|
|
|
|
)
|
|
|
|
.parse::<Query>()?;
|
|
|
|
assert_eq!(
|
|
|
|
query,
|
|
|
|
Query::SingleQuery(QueryPart::Matches(EntryQuery {
|
|
|
|
entity: QueryComponent::Any,
|
|
|
|
attribute: QueryComponent::Any,
|
|
|
|
value: QueryComponent::In(values)
|
|
|
|
}))
|
|
|
|
);
|
|
|
|
query
|
|
|
|
.to_sqlite_predicates()
|
|
|
|
.err()
|
|
|
|
.ok_or(anyhow!("Failed to reject mixed query."))?;
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_contains() -> Result<()> {
|
2022-02-07 18:33:57 +01:00
|
|
|
let query = "(matches (contains \"foo\") ? ?)".parse::<Query>()?;
|
|
|
|
assert_eq!(
|
|
|
|
query,
|
|
|
|
Query::SingleQuery(QueryPart::Matches(EntryQuery {
|
|
|
|
entity: QueryComponent::Contains("foo".to_string()),
|
|
|
|
attribute: QueryComponent::Any,
|
|
|
|
value: QueryComponent::Any
|
|
|
|
}))
|
|
|
|
);
|
|
|
|
query.to_sqlite_predicates()?;
|
|
|
|
|
|
|
|
let query = "(matches ? (contains \"foo\") ?)".parse::<Query>()?;
|
|
|
|
assert_eq!(
|
|
|
|
query,
|
|
|
|
Query::SingleQuery(QueryPart::Matches(EntryQuery {
|
|
|
|
entity: QueryComponent::Any,
|
|
|
|
attribute: QueryComponent::Contains("foo".to_string()),
|
|
|
|
value: QueryComponent::Any,
|
|
|
|
}))
|
|
|
|
);
|
|
|
|
query.to_sqlite_predicates()?;
|
|
|
|
|
2022-02-02 17:13:11 +01:00
|
|
|
let query = "(matches ? ? (contains \"foo\"))".parse::<Query>()?;
|
|
|
|
assert_eq!(
|
|
|
|
query,
|
|
|
|
Query::SingleQuery(QueryPart::Matches(EntryQuery {
|
|
|
|
entity: QueryComponent::Any,
|
|
|
|
attribute: QueryComponent::Any,
|
|
|
|
value: QueryComponent::Contains("foo".to_string())
|
|
|
|
}))
|
|
|
|
);
|
|
|
|
query.to_sqlite_predicates()?;
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
}
|