add (some) lang tests, implement "in" logic
parent
cb22756a47
commit
6aa804584d
|
@ -11,7 +11,7 @@ use std::borrow::Borrow;
|
|||
use std::convert::TryFrom;
|
||||
use std::str::FromStr;
|
||||
|
||||
#[derive(Debug)]
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub enum QueryComponent<T>
|
||||
where
|
||||
T: FromStr,
|
||||
|
@ -22,33 +22,33 @@ where
|
|||
Any,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub struct EntryQuery {
|
||||
pub entity: QueryComponent<Address>,
|
||||
pub attribute: QueryComponent<String>,
|
||||
pub value: QueryComponent<EntryValue>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub enum QueryPart {
|
||||
Matches(EntryQuery),
|
||||
Type(String),
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub enum QueryQualifier {
|
||||
And,
|
||||
Or,
|
||||
Not,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub struct MultiQuery {
|
||||
pub qualifier: QueryQualifier,
|
||||
pub queries: NonEmpty<Box<Query>>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub enum Query {
|
||||
SingleQuery(QueryPart),
|
||||
MultiQuery(MultiQuery),
|
||||
|
@ -291,12 +291,31 @@ impl Query {
|
|||
))?;
|
||||
|
||||
match first {
|
||||
EntryValue::Number(_) => todo!(),
|
||||
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(anyhow!("IN queries fidj oisdjfo isdjfoi jsdoifj sodijf oisdjf oij must not combine numeric and string values! ({v} is not a number)"))
|
||||
}
|
||||
})
|
||||
.collect::<Result<Vec<f64>>>()?,
|
||||
),
|
||||
)),
|
||||
_ => subqueries.push(Box::new(
|
||||
data::value_str.eq_any(
|
||||
q_values
|
||||
.iter()
|
||||
.map(|v| v.to_string())
|
||||
.map(|v| {
|
||||
if let EntryValue::Number(_) = v {
|
||||
Err(anyhow!("IN queries must not combine numeric and string values! (Found {v})"))
|
||||
} else {
|
||||
v.to_string()
|
||||
}
|
||||
})
|
||||
.collect::<Result<Vec<String>>>()?,
|
||||
),
|
||||
)),
|
||||
|
@ -363,3 +382,188 @@ impl Query {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[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()?;
|
||||
|
||||
let values = vec![
|
||||
EntryValue::String("FOO".to_string()),
|
||||
EntryValue::String("BAR".to_string()),
|
||||
];
|
||||
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
|
||||
let values = vec![
|
||||
EntryValue::String("FOO".to_string()),
|
||||
EntryValue::Number(1337.93),
|
||||
];
|
||||
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."))?;
|
||||
|
||||
let values = vec![
|
||||
EntryValue::Number(1337.93),
|
||||
EntryValue::String("FOO".to_string()),
|
||||
];
|
||||
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<()> {
|
||||
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(())
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue