2021-07-26 21:00:05 +02:00
|
|
|
use crate::addressing::Address;
|
2023-06-25 15:29:52 +02:00
|
|
|
use crate::entry::EntryValue;
|
2023-06-27 21:11:10 +02:00
|
|
|
use crate::error::UpEndError;
|
2021-07-26 21:00:05 +02:00
|
|
|
use nonempty::NonEmpty;
|
|
|
|
use std::borrow::Borrow;
|
|
|
|
use std::convert::TryFrom;
|
|
|
|
use std::str::FromStr;
|
|
|
|
|
2022-03-30 10:20:27 +02:00
|
|
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
2022-04-15 20:35:26 +02:00
|
|
|
pub struct Attribute(pub String);
|
2022-03-30 10:20:27 +02:00
|
|
|
|
|
|
|
impl From<&str> for Attribute {
|
|
|
|
fn from(str: &str) -> Self {
|
|
|
|
Self(str.to_string())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-04-15 20:35:26 +02:00
|
|
|
#[derive(Debug, Clone, PartialEq)]
|
2021-07-26 21:00:05 +02:00
|
|
|
pub enum QueryComponent<T>
|
|
|
|
where
|
2022-03-30 10:20:27 +02:00
|
|
|
T: TryFrom<lexpr::Value>,
|
2021-07-26 21:00:05 +02:00
|
|
|
{
|
|
|
|
Exact(T),
|
|
|
|
In(Vec<T>),
|
|
|
|
Contains(String),
|
2022-04-16 00:55:09 +02:00
|
|
|
Variable(Option<String>),
|
2021-07-26 21:00:05 +02:00
|
|
|
}
|
|
|
|
|
2022-04-15 20:35:26 +02:00
|
|
|
#[derive(Debug, Clone, PartialEq)]
|
|
|
|
pub struct PatternQuery {
|
2021-07-26 21:00:05 +02:00
|
|
|
pub entity: QueryComponent<Address>,
|
2022-03-30 10:20:27 +02:00
|
|
|
pub attribute: QueryComponent<Attribute>,
|
2021-07-26 21:00:05 +02:00
|
|
|
pub value: QueryComponent<EntryValue>,
|
|
|
|
}
|
|
|
|
|
2022-03-30 10:20:27 +02:00
|
|
|
impl TryFrom<lexpr::Value> for Address {
|
2023-06-29 12:58:06 +02:00
|
|
|
type Error = UpEndError;
|
2022-03-30 10:20:27 +02:00
|
|
|
|
|
|
|
fn try_from(value: lexpr::Value) -> Result<Self, Self::Error> {
|
|
|
|
match value {
|
|
|
|
lexpr::Value::Symbol(str) => {
|
|
|
|
if let Some(address_str) = str.strip_prefix('@') {
|
|
|
|
address_str
|
|
|
|
.parse()
|
2023-06-29 12:58:06 +02:00
|
|
|
.map_err(|e: UpEndError| UpEndError::QueryParseError(e.to_string()))
|
2022-03-30 10:20:27 +02:00
|
|
|
} else {
|
2023-06-29 12:58:06 +02:00
|
|
|
Err(UpEndError::QueryParseError(
|
2022-03-30 10:20:27 +02:00
|
|
|
"Incorrect address format (use @address).".into(),
|
|
|
|
))
|
|
|
|
}
|
|
|
|
}
|
2023-06-29 12:58:06 +02:00
|
|
|
_ => Err(UpEndError::QueryParseError(
|
2022-03-30 10:20:27 +02:00
|
|
|
"Incorrect type for address (use @address).".into(),
|
|
|
|
)),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl TryFrom<lexpr::Value> for EntryValue {
|
2023-06-29 12:58:06 +02:00
|
|
|
type Error = UpEndError;
|
2022-03-30 10:20:27 +02:00
|
|
|
|
|
|
|
fn try_from(value: lexpr::Value) -> Result<Self, Self::Error> {
|
|
|
|
match value {
|
2023-06-29 12:58:06 +02:00
|
|
|
lexpr::Value::Number(num) => Ok(EntryValue::Number(num.as_f64().ok_or_else(|| {
|
|
|
|
UpEndError::QueryParseError(format!("Error processing number ({num:?})."))
|
|
|
|
})?)),
|
2022-03-30 10:20:27 +02:00
|
|
|
lexpr::Value::Char(chr) => Ok(EntryValue::String(chr.to_string())),
|
|
|
|
lexpr::Value::String(str) => Ok(EntryValue::String(str.to_string())),
|
|
|
|
lexpr::Value::Symbol(_) => Ok(EntryValue::Address(Address::try_from(value.clone())?)),
|
2023-06-29 12:58:06 +02:00
|
|
|
_ => Err(UpEndError::QueryParseError(
|
2022-03-30 10:20:27 +02:00
|
|
|
"Value can only be a string, number or address.".into(),
|
|
|
|
)),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-06-25 15:29:52 +02:00
|
|
|
impl TryFrom<lexpr::Value> for Attribute {
|
2023-06-29 12:58:06 +02:00
|
|
|
type Error = UpEndError;
|
2023-06-25 15:29:52 +02:00
|
|
|
|
|
|
|
fn try_from(value: lexpr::Value) -> Result<Self, Self::Error> {
|
|
|
|
match value {
|
|
|
|
lexpr::Value::String(str) => Ok(Attribute(str.to_string())),
|
2023-06-29 12:58:06 +02:00
|
|
|
_ => Err(UpEndError::QueryParseError(
|
2023-06-25 15:29:52 +02:00
|
|
|
"Can only convert to attribute from string.".into(),
|
|
|
|
)),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-04-15 20:35:26 +02:00
|
|
|
#[derive(Debug, Clone, PartialEq)]
|
2021-07-26 21:00:05 +02:00
|
|
|
pub enum QueryPart {
|
2022-04-15 20:35:26 +02:00
|
|
|
Matches(PatternQuery),
|
2021-07-26 21:00:05 +02:00
|
|
|
Type(String),
|
|
|
|
}
|
|
|
|
|
2022-04-15 20:35:26 +02:00
|
|
|
#[derive(Debug, Clone, PartialEq)]
|
2021-07-26 21:00:05 +02:00
|
|
|
pub enum QueryQualifier {
|
|
|
|
And,
|
|
|
|
Or,
|
2021-12-21 11:50:46 +01:00
|
|
|
Not,
|
2022-04-16 00:55:09 +02:00
|
|
|
Join,
|
2021-07-26 21:00:05 +02:00
|
|
|
}
|
|
|
|
|
2022-04-15 20:35:26 +02:00
|
|
|
#[derive(Debug, Clone, PartialEq)]
|
2021-07-26 21:00:05 +02:00
|
|
|
pub struct MultiQuery {
|
|
|
|
pub qualifier: QueryQualifier,
|
|
|
|
pub queries: NonEmpty<Box<Query>>,
|
|
|
|
}
|
|
|
|
|
2023-05-21 21:48:21 +02:00
|
|
|
#[allow(clippy::large_enum_variant)]
|
2022-04-15 20:35:26 +02:00
|
|
|
#[derive(Debug, Clone, PartialEq)]
|
2021-07-26 21:00:05 +02:00
|
|
|
pub enum Query {
|
|
|
|
SingleQuery(QueryPart),
|
|
|
|
MultiQuery(MultiQuery),
|
|
|
|
}
|
|
|
|
|
|
|
|
impl TryFrom<&lexpr::Value> for Query {
|
2023-06-29 12:58:06 +02:00
|
|
|
type Error = UpEndError;
|
2021-07-26 21:00:05 +02:00
|
|
|
|
2022-03-30 10:20:27 +02:00
|
|
|
fn try_from(expression: &lexpr::Value) -> Result<Self, Self::Error> {
|
|
|
|
fn parse_component<T: TryFrom<lexpr::Value>>(
|
|
|
|
value: &lexpr::Value,
|
2023-06-29 12:58:06 +02:00
|
|
|
) -> Result<QueryComponent<T>, UpEndError>
|
2021-07-26 21:00:05 +02:00
|
|
|
where
|
2023-06-29 12:58:06 +02:00
|
|
|
UpEndError: From<<T as TryFrom<lexpr::Value>>::Error>,
|
2021-07-26 21:00:05 +02:00
|
|
|
{
|
|
|
|
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;
|
2022-03-30 10:20:27 +02:00
|
|
|
let values: Result<Vec<T>, _> = args
|
|
|
|
.iter()
|
|
|
|
.map(|value| T::try_from(value.clone()))
|
|
|
|
.collect();
|
2021-07-26 21:00:05 +02:00
|
|
|
|
|
|
|
Ok(QueryComponent::In(values?))
|
|
|
|
} else {
|
2023-06-29 12:58:06 +02:00
|
|
|
Err(UpEndError::QueryParseError(
|
2022-03-30 10:20:27 +02:00
|
|
|
"Malformed expression: Inner value cannot be empty.".into(),
|
|
|
|
))
|
2021-07-26 21:00:05 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
"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 {
|
2023-06-29 12:58:06 +02:00
|
|
|
Err(UpEndError::QueryParseError("Malformed expression: 'Contains' argument must be a string.".into()))
|
2021-07-26 21:00:05 +02:00
|
|
|
}
|
|
|
|
}
|
2023-06-29 12:58:06 +02:00
|
|
|
_ => Err(UpEndError::QueryParseError(
|
2022-03-30 10:20:27 +02:00
|
|
|
"Malformed expression: 'Contains' requires a single argument.".into()
|
2021-07-26 21:00:05 +02:00
|
|
|
)),
|
|
|
|
}
|
|
|
|
}
|
2023-06-29 12:58:06 +02:00
|
|
|
_ => Err(UpEndError::QueryParseError(format!(
|
2022-09-19 22:58:07 +02:00
|
|
|
"Malformed expression: Unknown symbol {}",
|
2022-03-30 10:20:27 +02:00
|
|
|
symbol
|
|
|
|
))),
|
2021-07-26 21:00:05 +02:00
|
|
|
}
|
|
|
|
} else {
|
2023-06-29 12:58:06 +02:00
|
|
|
Err(UpEndError::QueryParseError(format!(
|
2022-03-30 10:20:27 +02:00
|
|
|
"Malformed expression: Inner value '{:?}' is not a symbol.",
|
|
|
|
value
|
|
|
|
)))
|
2021-07-26 21:00:05 +02:00
|
|
|
}
|
|
|
|
}
|
2022-04-16 00:55:09 +02:00
|
|
|
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())
|
|
|
|
}))
|
|
|
|
}
|
2022-03-30 10:20:27 +02:00
|
|
|
_ => Ok(QueryComponent::Exact(T::try_from(value.clone())?)),
|
2021-07-26 21:00:05 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
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)?;
|
2022-03-30 10:20:27 +02:00
|
|
|
let attribute = parse_component::<Attribute>(attribute)?;
|
2021-07-26 21:00:05 +02:00
|
|
|
let value = parse_component::<EntryValue>(value)?;
|
2022-04-15 20:35:26 +02:00
|
|
|
Ok(Query::SingleQuery(QueryPart::Matches(PatternQuery {
|
2021-07-26 21:00:05 +02:00
|
|
|
entity,
|
|
|
|
attribute,
|
|
|
|
value,
|
|
|
|
})))
|
|
|
|
} else {
|
2023-06-29 12:58:06 +02:00
|
|
|
Err(UpEndError::QueryParseError(
|
2021-07-26 21:00:05 +02:00
|
|
|
"Malformed expression: Wrong number of arguments to 'matches'."
|
2022-03-30 10:20:27 +02:00
|
|
|
.into(),
|
2021-07-26 21:00:05 +02:00
|
|
|
))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
"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 {
|
2023-06-29 12:58:06 +02:00
|
|
|
Err(UpEndError::QueryParseError(
|
2021-07-26 21:00:05 +02:00
|
|
|
"Malformed expression: Type must be specified as a string."
|
2022-03-30 10:20:27 +02:00
|
|
|
.into(),
|
2021-07-26 21:00:05 +02:00
|
|
|
))
|
|
|
|
}
|
|
|
|
} else {
|
2023-06-29 12:58:06 +02:00
|
|
|
Err(UpEndError::QueryParseError(
|
2022-03-30 10:20:27 +02:00
|
|
|
"Malformed expression: Wrong number of arguments to 'type'.".into(),
|
2021-07-26 21:00:05 +02:00
|
|
|
))
|
|
|
|
}
|
|
|
|
}
|
2022-04-16 00:55:09 +02:00
|
|
|
"and" | "or" | "join" => {
|
2021-07-26 21:00:05 +02:00
|
|
|
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)?)))
|
2023-06-29 12:58:06 +02:00
|
|
|
.collect::<Result<Vec<Box<Query>>, UpEndError>>()?;
|
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,
|
2022-04-16 00:55:09 +02:00
|
|
|
"join" => QueryQualifier::Join,
|
2021-07-26 21:00:05 +02:00
|
|
|
_ => QueryQualifier::Or,
|
|
|
|
},
|
|
|
|
queries,
|
|
|
|
}))
|
|
|
|
} else {
|
2023-06-29 12:58:06 +02:00
|
|
|
Err(UpEndError::QueryParseError(
|
2022-03-30 10:20:27 +02:00
|
|
|
"Malformed expression: sub-query list cannot be empty.".into(),
|
2021-07-26 21:00:05 +02:00
|
|
|
))
|
|
|
|
}
|
|
|
|
}
|
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)?)))
|
2023-06-29 12:58:06 +02:00
|
|
|
.collect::<Result<Vec<Box<Query>>, UpEndError>>()?;
|
2021-12-21 11:50:46 +01:00
|
|
|
|
|
|
|
if values.len() == 1 {
|
|
|
|
Ok(Query::MultiQuery(MultiQuery {
|
|
|
|
qualifier: QueryQualifier::Not,
|
|
|
|
queries: NonEmpty::from_vec(values).unwrap(),
|
|
|
|
}))
|
|
|
|
} else {
|
2023-06-29 12:58:06 +02:00
|
|
|
Err(UpEndError::QueryParseError(
|
2022-03-30 10:20:27 +02:00
|
|
|
"Malformed expression: NOT takes exactly one parameter.".into(),
|
2021-12-21 11:50:46 +01:00
|
|
|
))
|
|
|
|
}
|
|
|
|
}
|
2023-06-29 12:58:06 +02:00
|
|
|
_ => Err(UpEndError::QueryParseError(format!(
|
2021-07-26 21:00:05 +02:00
|
|
|
"Malformed expression: Unknown symbol '{}'.",
|
|
|
|
symbol
|
|
|
|
))),
|
|
|
|
}
|
|
|
|
} else {
|
2023-06-29 12:58:06 +02:00
|
|
|
Err(UpEndError::QueryParseError(format!(
|
2021-07-26 21:00:05 +02:00
|
|
|
"Malformed expression: Value '{:?}' is not a symbol.",
|
|
|
|
value
|
|
|
|
)))
|
|
|
|
}
|
|
|
|
} else {
|
2023-06-29 12:58:06 +02:00
|
|
|
Err(UpEndError::QueryParseError(
|
|
|
|
"Malformed expression: Not a list.".into(),
|
|
|
|
))
|
2021-07-26 21:00:05 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-12-21 12:01:40 +01:00
|
|
|
impl FromStr for Query {
|
2023-06-29 12:58:06 +02:00
|
|
|
type Err = UpEndError;
|
2021-12-21 12:01:40 +01:00
|
|
|
|
|
|
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
2023-06-29 12:58:06 +02:00
|
|
|
let sexp = lexpr::from_str_custom(s, lexpr::parse::Options::new()).map_err(|e| {
|
|
|
|
UpEndError::QueryParseError(format!("failed to parse s-expression: {e}"))
|
|
|
|
})?;
|
2021-12-21 12:01:40 +01:00
|
|
|
Query::try_from(&sexp)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-02-02 17:13:11 +01:00
|
|
|
#[cfg(test)]
|
|
|
|
mod test {
|
2023-06-27 21:11:10 +02:00
|
|
|
use crate::error::UpEndError;
|
|
|
|
|
2022-02-02 17:13:11 +01:00
|
|
|
use super::*;
|
2023-05-19 17:30:09 +02:00
|
|
|
use url::Url;
|
2022-02-02 17:13:11 +01:00
|
|
|
|
|
|
|
#[test]
|
2023-06-27 21:11:10 +02:00
|
|
|
fn test_matches() -> Result<(), UpEndError> {
|
2022-02-02 17:13:11 +01:00
|
|
|
let query = "(matches ? ? ?)".parse::<Query>()?;
|
|
|
|
assert_eq!(
|
|
|
|
query,
|
2022-04-15 20:35:26 +02:00
|
|
|
Query::SingleQuery(QueryPart::Matches(PatternQuery {
|
2022-04-16 00:55:09 +02:00
|
|
|
entity: QueryComponent::Variable(None),
|
|
|
|
attribute: QueryComponent::Variable(None),
|
|
|
|
value: QueryComponent::Variable(None)
|
2022-02-02 17:13:11 +01:00
|
|
|
}))
|
|
|
|
);
|
|
|
|
|
2023-05-19 17:30:09 +02:00
|
|
|
let address = Address::Url(Url::parse("https://upend.dev").unwrap());
|
2022-03-30 10:20:27 +02:00
|
|
|
let query = format!("(matches @{address} ? ?)").parse::<Query>()?;
|
2022-02-02 17:13:11 +01:00
|
|
|
assert_eq!(
|
|
|
|
query,
|
2022-04-15 20:35:26 +02:00
|
|
|
Query::SingleQuery(QueryPart::Matches(PatternQuery {
|
2022-02-02 17:13:11 +01:00
|
|
|
entity: QueryComponent::Exact(address),
|
2022-04-16 00:55:09 +02:00
|
|
|
attribute: QueryComponent::Variable(None),
|
|
|
|
value: QueryComponent::Variable(None)
|
2022-02-02 17:13:11 +01:00
|
|
|
}))
|
|
|
|
);
|
|
|
|
|
2022-03-30 10:20:27 +02:00
|
|
|
let query = r#"(matches ? "FOO" ?)"#.parse::<Query>()?;
|
2022-02-02 17:13:11 +01:00
|
|
|
assert_eq!(
|
|
|
|
query,
|
2022-04-15 20:35:26 +02:00
|
|
|
Query::SingleQuery(QueryPart::Matches(PatternQuery {
|
2022-04-16 00:55:09 +02:00
|
|
|
entity: QueryComponent::Variable(None),
|
2022-03-30 10:20:27 +02:00
|
|
|
attribute: QueryComponent::Exact("FOO".into()),
|
2022-04-16 00:55:09 +02:00
|
|
|
value: QueryComponent::Variable(None)
|
2022-02-02 17:13:11 +01:00
|
|
|
}))
|
|
|
|
);
|
|
|
|
|
|
|
|
let value = EntryValue::Number(1337.93);
|
2022-03-30 10:20:27 +02:00
|
|
|
let query = "(matches ? ? 1337.93)".parse::<Query>()?;
|
2022-02-02 17:13:11 +01:00
|
|
|
assert_eq!(
|
|
|
|
query,
|
2022-04-15 20:35:26 +02:00
|
|
|
Query::SingleQuery(QueryPart::Matches(PatternQuery {
|
2022-04-16 00:55:09 +02:00
|
|
|
entity: QueryComponent::Variable(None),
|
|
|
|
attribute: QueryComponent::Variable(None),
|
2022-02-02 17:13:11 +01:00
|
|
|
value: QueryComponent::Exact(value)
|
|
|
|
}))
|
|
|
|
);
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
2022-04-16 00:55:09 +02:00
|
|
|
#[test]
|
2023-06-29 12:58:06 +02:00
|
|
|
fn test_joins() -> Result<(), UpEndError> {
|
2022-04-16 00:55:09 +02:00
|
|
|
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(())
|
|
|
|
}
|
|
|
|
|
2022-02-02 17:13:11 +01:00
|
|
|
#[test]
|
2023-06-29 12:58:06 +02:00
|
|
|
fn test_in_parse() -> Result<(), UpEndError> {
|
2022-03-30 10:20:27 +02:00
|
|
|
let query = r#"(matches ? (in "FOO" "BAR") ?)"#.parse::<Query>()?;
|
2022-02-02 17:13:11 +01:00
|
|
|
assert_eq!(
|
|
|
|
query,
|
2022-04-15 20:35:26 +02:00
|
|
|
Query::SingleQuery(QueryPart::Matches(PatternQuery {
|
2022-04-16 00:55:09 +02:00
|
|
|
entity: QueryComponent::Variable(None),
|
2022-03-30 10:20:27 +02:00
|
|
|
attribute: QueryComponent::In(vec!("FOO".into(), "BAR".into())),
|
2022-04-16 00:55:09 +02:00
|
|
|
value: QueryComponent::Variable(None)
|
2022-02-02 17:13:11 +01:00
|
|
|
}))
|
|
|
|
);
|
|
|
|
|
2022-02-13 12:37:16 +01:00
|
|
|
let values: Vec<EntryValue> = vec!["FOO".into(), "BAR".into()];
|
2022-03-30 10:20:27 +02:00
|
|
|
let query = r#"(matches ? ? (in "FOO" "BAR"))"#.parse::<Query>()?;
|
2022-02-02 17:13:11 +01:00
|
|
|
assert_eq!(
|
|
|
|
query,
|
2022-04-15 20:35:26 +02:00
|
|
|
Query::SingleQuery(QueryPart::Matches(PatternQuery {
|
2022-04-16 00:55:09 +02:00
|
|
|
entity: QueryComponent::Variable(None),
|
|
|
|
attribute: QueryComponent::Variable(None),
|
2022-02-02 17:13:11 +01:00
|
|
|
value: QueryComponent::In(values)
|
|
|
|
}))
|
|
|
|
);
|
|
|
|
|
|
|
|
let values = vec![EntryValue::Number(1337.93), EntryValue::Number(1968.12)];
|
2022-03-30 10:20:27 +02:00
|
|
|
let query = r#"(matches ? ? (in 1337.93 1968.12))"#.parse::<Query>()?;
|
2022-02-02 17:13:11 +01:00
|
|
|
assert_eq!(
|
|
|
|
query,
|
2022-04-15 20:35:26 +02:00
|
|
|
Query::SingleQuery(QueryPart::Matches(PatternQuery {
|
2022-04-16 00:55:09 +02:00
|
|
|
entity: QueryComponent::Variable(None),
|
|
|
|
attribute: QueryComponent::Variable(None),
|
2022-02-02 17:13:11 +01:00
|
|
|
value: QueryComponent::In(values)
|
|
|
|
}))
|
|
|
|
);
|
|
|
|
|
|
|
|
// Invalid queries
|
2022-02-13 12:37:16 +01:00
|
|
|
let values: Vec<EntryValue> = vec!["FOO".into(), EntryValue::Number(1337.93)];
|
2022-03-30 10:20:27 +02:00
|
|
|
let query = r#"(matches ? ? (in "FOO" 1337.93))"#.parse::<Query>()?;
|
|
|
|
|
2022-02-02 17:13:11 +01:00
|
|
|
assert_eq!(
|
|
|
|
query,
|
2022-04-15 20:35:26 +02:00
|
|
|
Query::SingleQuery(QueryPart::Matches(PatternQuery {
|
2022-04-16 00:55:09 +02:00
|
|
|
entity: QueryComponent::Variable(None),
|
|
|
|
attribute: QueryComponent::Variable(None),
|
2022-02-02 17:13:11 +01:00
|
|
|
value: QueryComponent::In(values)
|
|
|
|
}))
|
|
|
|
);
|
|
|
|
|
2022-02-13 12:37:16 +01:00
|
|
|
let values = vec![EntryValue::Number(1337.93), "FOO".into()];
|
2022-03-30 10:20:27 +02:00
|
|
|
let query = r#"(matches ? ? (in 1337.93 "FOO"))"#.parse::<Query>()?;
|
|
|
|
|
2022-02-02 17:13:11 +01:00
|
|
|
assert_eq!(
|
|
|
|
query,
|
2022-04-15 20:35:26 +02:00
|
|
|
Query::SingleQuery(QueryPart::Matches(PatternQuery {
|
2022-04-16 00:55:09 +02:00
|
|
|
entity: QueryComponent::Variable(None),
|
|
|
|
attribute: QueryComponent::Variable(None),
|
2022-02-02 17:13:11 +01:00
|
|
|
value: QueryComponent::In(values)
|
|
|
|
}))
|
|
|
|
);
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
2023-06-29 12:58:06 +02:00
|
|
|
fn test_contains() -> Result<(), UpEndError> {
|
2022-03-30 10:20:27 +02:00
|
|
|
let query = r#"(matches (contains "foo") ? ?)"#.parse::<Query>()?;
|
2022-02-07 18:33:57 +01:00
|
|
|
assert_eq!(
|
|
|
|
query,
|
2022-04-15 20:35:26 +02:00
|
|
|
Query::SingleQuery(QueryPart::Matches(PatternQuery {
|
2022-02-07 18:33:57 +01:00
|
|
|
entity: QueryComponent::Contains("foo".to_string()),
|
2022-04-16 00:55:09 +02:00
|
|
|
attribute: QueryComponent::Variable(None),
|
|
|
|
value: QueryComponent::Variable(None)
|
2022-02-07 18:33:57 +01:00
|
|
|
}))
|
|
|
|
);
|
|
|
|
|
2022-03-30 10:20:27 +02:00
|
|
|
let query = r#"(matches ? (contains "foo") ?)"#.parse::<Query>()?;
|
2022-02-07 18:33:57 +01:00
|
|
|
assert_eq!(
|
|
|
|
query,
|
2022-04-15 20:35:26 +02:00
|
|
|
Query::SingleQuery(QueryPart::Matches(PatternQuery {
|
2022-04-16 00:55:09 +02:00
|
|
|
entity: QueryComponent::Variable(None),
|
2022-02-07 18:33:57 +01:00
|
|
|
attribute: QueryComponent::Contains("foo".to_string()),
|
2022-04-16 00:55:09 +02:00
|
|
|
value: QueryComponent::Variable(None),
|
2022-02-07 18:33:57 +01:00
|
|
|
}))
|
|
|
|
);
|
|
|
|
|
2022-03-30 10:20:27 +02:00
|
|
|
let query = r#"(matches ? ? (contains "foo"))"#.parse::<Query>()?;
|
2022-02-02 17:13:11 +01:00
|
|
|
assert_eq!(
|
|
|
|
query,
|
2022-04-15 20:35:26 +02:00
|
|
|
Query::SingleQuery(QueryPart::Matches(PatternQuery {
|
2022-04-16 00:55:09 +02:00
|
|
|
entity: QueryComponent::Variable(None),
|
|
|
|
attribute: QueryComponent::Variable(None),
|
2022-02-02 17:13:11 +01:00
|
|
|
value: QueryComponent::Contains("foo".to_string())
|
|
|
|
}))
|
|
|
|
);
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
}
|