query lang - addresses are denoted by @, values need not match db deserialization format
e.g.` (matches @address ? ?)`, and `(matches ? ? "foo")` instead of "Sfoo"
This commit is contained in:
parent
fd72034571
commit
8c60a617a2
9 changed files with 233 additions and 193 deletions
|
@ -97,7 +97,7 @@ pub fn list_roots(connection: &UpEndConnection) -> Result<Vec<Address>> {
|
|||
let all_directories: Vec<Entry> =
|
||||
connection.query(Query::SingleQuery(QueryPart::Matches(EntryQuery {
|
||||
entity: QueryComponent::Any,
|
||||
attribute: QueryComponent::Exact(IS_OF_TYPE_ATTR.to_string()),
|
||||
attribute: QueryComponent::Exact(IS_OF_TYPE_ATTR.into()),
|
||||
value: QueryComponent::Exact(HIER_ADDR.clone().into()),
|
||||
})))?;
|
||||
|
||||
|
@ -105,7 +105,7 @@ pub fn list_roots(connection: &UpEndConnection) -> Result<Vec<Address>> {
|
|||
let directories_with_parents: Vec<Address> = connection
|
||||
.query(Query::SingleQuery(QueryPart::Matches(EntryQuery {
|
||||
entity: QueryComponent::Any,
|
||||
attribute: QueryComponent::Exact(HIER_HAS_ATTR.to_string()),
|
||||
attribute: QueryComponent::Exact(HIER_HAS_ATTR.into()),
|
||||
value: QueryComponent::Any,
|
||||
})))?
|
||||
.extract_pointers()
|
||||
|
@ -143,7 +143,7 @@ pub fn fetch_or_create_dir(
|
|||
let matching_directories = connection
|
||||
.query(Query::SingleQuery(QueryPart::Matches(EntryQuery {
|
||||
entity: QueryComponent::Any,
|
||||
attribute: QueryComponent::Exact(String::from(LABEL_ATTR)),
|
||||
attribute: QueryComponent::Exact(LABEL_ATTR.into()),
|
||||
value: QueryComponent::Exact(directory.as_ref().clone().into()),
|
||||
})))?
|
||||
.into_iter()
|
||||
|
@ -153,7 +153,7 @@ pub fn fetch_or_create_dir(
|
|||
Some(parent) => connection
|
||||
.query(Query::SingleQuery(QueryPart::Matches(EntryQuery {
|
||||
entity: QueryComponent::Exact(parent),
|
||||
attribute: QueryComponent::Exact(String::from(HIER_HAS_ATTR)),
|
||||
attribute: QueryComponent::Exact(HIER_HAS_ATTR.into()),
|
||||
value: QueryComponent::Any,
|
||||
})))?
|
||||
.extract_pointers()
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
use crate::addressing::Address;
|
||||
use crate::database::entry::EntryValue;
|
||||
use crate::database::inner::schema::data;
|
||||
use anyhow::{anyhow, Result};
|
||||
use diesel::expression::operators::{And, Not, Or};
|
||||
use diesel::sql_types::Bool;
|
||||
use diesel::sqlite::Sqlite;
|
||||
|
@ -11,10 +10,19 @@ use std::borrow::Borrow;
|
|||
use std::convert::TryFrom;
|
||||
use std::str::FromStr;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct Attribute(String);
|
||||
|
||||
impl From<&str> for Attribute {
|
||||
fn from(str: &str) -> Self {
|
||||
Self(str.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub enum QueryComponent<T>
|
||||
where
|
||||
T: FromStr,
|
||||
T: TryFrom<lexpr::Value>,
|
||||
{
|
||||
Exact(T),
|
||||
In(Vec<T>),
|
||||
|
@ -25,10 +33,66 @@ where
|
|||
#[derive(Debug, PartialEq)]
|
||||
pub struct EntryQuery {
|
||||
pub entity: QueryComponent<Address>,
|
||||
pub attribute: QueryComponent<String>,
|
||||
pub attribute: QueryComponent<Attribute>,
|
||||
pub value: QueryComponent<EntryValue>,
|
||||
}
|
||||
|
||||
impl TryFrom<lexpr::Value> for Address {
|
||||
type Error = QueryParseError;
|
||||
|
||||
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()
|
||||
.map_err(|e: anyhow::Error| QueryParseError(e.to_string()))
|
||||
} else {
|
||||
Err(QueryParseError(
|
||||
"Incorrect address format (use @address).".into(),
|
||||
))
|
||||
}
|
||||
}
|
||||
_ => Err(QueryParseError(
|
||||
"Incorrect type for address (use @address).".into(),
|
||||
)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<lexpr::Value> for Attribute {
|
||||
type Error = QueryParseError;
|
||||
|
||||
fn try_from(value: lexpr::Value) -> Result<Self, Self::Error> {
|
||||
match value {
|
||||
lexpr::Value::String(str) => Ok(Attribute(str.to_string())),
|
||||
_ => Err(QueryParseError(
|
||||
"Can only convert to attribute from string.".into(),
|
||||
)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<lexpr::Value> for EntryValue {
|
||||
type Error = QueryParseError;
|
||||
|
||||
fn try_from(value: lexpr::Value) -> Result<Self, Self::Error> {
|
||||
match value {
|
||||
lexpr::Value::Number(num) => {
|
||||
Ok(EntryValue::Number(num.as_f64().ok_or_else(|| {
|
||||
QueryParseError(format!("Error processing number ({num:?})."))
|
||||
})?))
|
||||
}
|
||||
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())?)),
|
||||
_ => Err(QueryParseError(
|
||||
"Value can only be a string, number or address.".into(),
|
||||
)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub enum QueryPart {
|
||||
Matches(EntryQuery),
|
||||
|
@ -54,15 +118,28 @@ pub enum Query {
|
|||
MultiQuery(MultiQuery),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct QueryParseError(String);
|
||||
|
||||
impl std::fmt::Display for QueryParseError {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "{}", self.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl std::error::Error for QueryParseError {}
|
||||
|
||||
type Predicate = dyn BoxableExpression<data::table, Sqlite, SqlType = Bool>;
|
||||
|
||||
impl TryFrom<&lexpr::Value> for Query {
|
||||
type Error = anyhow::Error;
|
||||
type Error = QueryParseError;
|
||||
|
||||
fn try_from(expression: &lexpr::Value) -> Result<Self> {
|
||||
fn parse_component<T: FromStr>(value: &lexpr::Value) -> Result<QueryComponent<T>>
|
||||
fn try_from(expression: &lexpr::Value) -> Result<Self, Self::Error> {
|
||||
fn parse_component<T: TryFrom<lexpr::Value>>(
|
||||
value: &lexpr::Value,
|
||||
) -> Result<QueryComponent<T>, QueryParseError>
|
||||
where
|
||||
<T as FromStr>::Err: std::fmt::Debug,
|
||||
QueryParseError: From<<T as TryFrom<lexpr::Value>>::Error>,
|
||||
{
|
||||
match value {
|
||||
lexpr::Value::Cons(cons) => {
|
||||
|
@ -72,22 +149,16 @@ impl TryFrom<&lexpr::Value> for Query {
|
|||
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();
|
||||
let values: Result<Vec<T>, _> = args
|
||||
.iter()
|
||||
.map(|value| T::try_from(value.clone()))
|
||||
.collect();
|
||||
|
||||
Ok(QueryComponent::In(values?))
|
||||
} else {
|
||||
Err(anyhow!(
|
||||
"Malformed expression: Inner value cannot be empty."
|
||||
))
|
||||
Err(QueryParseError(
|
||||
"Malformed expression: Inner value cannot be empty.".into(),
|
||||
))
|
||||
}
|
||||
}
|
||||
"contains" => {
|
||||
|
@ -98,43 +169,28 @@ impl TryFrom<&lexpr::Value> for Query {
|
|||
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(QueryParseError("Malformed expression: 'Contains' argument must be a string.".into()))
|
||||
}
|
||||
}
|
||||
_ => Err(anyhow!(
|
||||
"Malformed expression: 'Contains' requires a single argument."
|
||||
_ => Err(QueryParseError(
|
||||
"Malformed expression: 'Contains' requires a single argument.".into()
|
||||
)),
|
||||
}
|
||||
}
|
||||
_ => Err(anyhow!(format!(
|
||||
"Malformed expression: Unknown symbol {}",
|
||||
symbol
|
||||
))),
|
||||
_ => Err(QueryParseError(format!(
|
||||
"Malformed expression: Unknowne symbol {}",
|
||||
symbol
|
||||
))),
|
||||
}
|
||||
} else {
|
||||
Err(anyhow!(format!(
|
||||
"Malformed expression: Inner value '{:?}' is not a symbol.",
|
||||
value
|
||||
)))
|
||||
Err(QueryParseError(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 '?'."
|
||||
)),
|
||||
lexpr::Value::Symbol(symbol) if symbol.as_ref() == "?" => Ok(QueryComponent::Any),
|
||||
_ => Ok(QueryComponent::Exact(T::try_from(value.clone())?)),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -145,7 +201,7 @@ impl TryFrom<&lexpr::Value> for Query {
|
|||
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 attribute = parse_component::<Attribute>(attribute)?;
|
||||
let value = parse_component::<EntryValue>(value)?;
|
||||
Ok(Query::SingleQuery(QueryPart::Matches(EntryQuery {
|
||||
entity,
|
||||
|
@ -153,8 +209,9 @@ impl TryFrom<&lexpr::Value> for Query {
|
|||
value,
|
||||
})))
|
||||
} else {
|
||||
Err(anyhow!(
|
||||
Err(QueryParseError(
|
||||
"Malformed expression: Wrong number of arguments to 'matches'."
|
||||
.into(),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
@ -166,13 +223,14 @@ impl TryFrom<&lexpr::Value> for Query {
|
|||
type_name_str.to_string(),
|
||||
)))
|
||||
} else {
|
||||
Err(anyhow!(
|
||||
Err(QueryParseError(
|
||||
"Malformed expression: Type must be specified as a string."
|
||||
.into(),
|
||||
))
|
||||
}
|
||||
} else {
|
||||
Err(anyhow!(
|
||||
"Malformed expression: Wrong number of arguments to 'type'."
|
||||
Err(QueryParseError(
|
||||
"Malformed expression: Wrong number of arguments to 'type'.".into(),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
@ -182,7 +240,7 @@ impl TryFrom<&lexpr::Value> for Query {
|
|||
let values = sub_expressions
|
||||
.iter()
|
||||
.map(|value| Ok(Box::new(Query::try_from(value)?)))
|
||||
.collect::<Result<Vec<Box<Query>>>>()?;
|
||||
.collect::<Result<Vec<Box<Query>>, QueryParseError>>()?;
|
||||
|
||||
if let Some(queries) = NonEmpty::from_vec(values) {
|
||||
Ok(Query::MultiQuery(MultiQuery {
|
||||
|
@ -193,8 +251,8 @@ impl TryFrom<&lexpr::Value> for Query {
|
|||
queries,
|
||||
}))
|
||||
} else {
|
||||
Err(anyhow!(
|
||||
"Malformed expression: sub-query list cannot be empty.",
|
||||
Err(QueryParseError(
|
||||
"Malformed expression: sub-query list cannot be empty.".into(),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
@ -204,7 +262,7 @@ impl TryFrom<&lexpr::Value> for Query {
|
|||
let values = sub_expressions
|
||||
.iter()
|
||||
.map(|value| Ok(Box::new(Query::try_from(value)?)))
|
||||
.collect::<Result<Vec<Box<Query>>>>()?;
|
||||
.collect::<Result<Vec<Box<Query>>, QueryParseError>>()?;
|
||||
|
||||
if values.len() == 1 {
|
||||
Ok(Query::MultiQuery(MultiQuery {
|
||||
|
@ -212,85 +270,96 @@ impl TryFrom<&lexpr::Value> for Query {
|
|||
queries: NonEmpty::from_vec(values).unwrap(),
|
||||
}))
|
||||
} else {
|
||||
Err(anyhow!(
|
||||
"Malformed expression: NOT takes exactly one parameter."
|
||||
Err(QueryParseError(
|
||||
"Malformed expression: NOT takes exactly one parameter.".into(),
|
||||
))
|
||||
}
|
||||
}
|
||||
_ => Err(anyhow!(format!(
|
||||
_ => Err(QueryParseError(format!(
|
||||
"Malformed expression: Unknown symbol '{}'.",
|
||||
symbol
|
||||
))),
|
||||
}
|
||||
} else {
|
||||
Err(anyhow!(format!(
|
||||
Err(QueryParseError(format!(
|
||||
"Malformed expression: Value '{:?}' is not a symbol.",
|
||||
value
|
||||
)))
|
||||
}
|
||||
} else {
|
||||
Err(anyhow!("Malformed expression: Not a list."))
|
||||
Err(QueryParseError("Malformed expression: Not a list.".into()))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for Query {
|
||||
type Err = anyhow::Error;
|
||||
type Err = QueryParseError;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
let sexp = lexpr::from_str_custom(s, lexpr::parse::Options::new())?;
|
||||
let sexp = lexpr::from_str_custom(s, lexpr::parse::Options::new())
|
||||
.map_err(|_| QueryParseError("???".into()))?;
|
||||
Query::try_from(&sexp)
|
||||
}
|
||||
}
|
||||
|
||||
impl Query {
|
||||
pub(crate) fn to_sqlite_predicates(&self) -> Result<Box<Predicate>> {
|
||||
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![];
|
||||
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()?)))
|
||||
match &eq.entity {
|
||||
QueryComponent::Exact(q_entity) => subqueries.push(Box::new(
|
||||
data::entity.eq(q_entity
|
||||
.encode()
|
||||
.map_err(|_| QueryParseError("???".into()))?),
|
||||
)),
|
||||
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(|_| QueryParseError("???".into()))?),
|
||||
))
|
||||
}
|
||||
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)))
|
||||
}
|
||||
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?)))
|
||||
}
|
||||
QueryComponent::Contains(q_entity) => subqueries.push(Box::new(
|
||||
data::entity_searchable.like(format!("%{}%", q_entity)),
|
||||
_ => subqueries.push(Box::new(
|
||||
data::value_str.eq(q_value
|
||||
.to_string()
|
||||
.map_err(|_| QueryParseError("???".into()))?),
|
||||
)),
|
||||
QueryComponent::Any => {}
|
||||
};
|
||||
},
|
||||
QueryComponent::In(q_values) => {
|
||||
let first = q_values.first().ok_or_else(|| {
|
||||
QueryParseError(
|
||||
"Malformed expression: Inner value cannot be empty.".into(),
|
||||
)
|
||||
})?;
|
||||
|
||||
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 {
|
||||
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()?))),
|
||||
},
|
||||
QueryComponent::In(q_values) => {
|
||||
let first = q_values.first().ok_or(anyhow!(
|
||||
"Malformed expression: Inner value cannot be empty."
|
||||
))?;
|
||||
|
||||
match first {
|
||||
match first {
|
||||
EntryValue::Number(_) => subqueries.push(Box::new(
|
||||
data::value_num.eq_any(
|
||||
q_values
|
||||
|
@ -299,10 +368,10 @@ impl Query {
|
|||
if let EntryValue::Number(n) = v {
|
||||
Ok(*n)
|
||||
} else {
|
||||
Err(anyhow!("IN queries must not combine numeric and string values! ({v} is not a number)"))
|
||||
Err(QueryParseError(format!("IN queries must not combine numeric and string values! ({v} is not a number)")))
|
||||
}
|
||||
})
|
||||
.collect::<Result<Vec<f64>>>()?,
|
||||
.collect::<Result<Vec<f64>, QueryParseError>>()?,
|
||||
),
|
||||
)),
|
||||
_ => subqueries.push(Box::new(
|
||||
|
@ -311,39 +380,38 @@ impl Query {
|
|||
.iter()
|
||||
.map(|v| {
|
||||
if let EntryValue::Number(_) = v {
|
||||
Err(anyhow!("IN queries must not combine numeric and string values! (Found {v})"))
|
||||
Err(QueryParseError(format!("IN queries must not combine numeric and string values! (Found {v})")))
|
||||
} else {
|
||||
v.to_string()
|
||||
v.to_string().map_err(|_| QueryParseError("???".into()))
|
||||
}
|
||||
})
|
||||
.collect::<Result<Vec<String>>>()?,
|
||||
.collect::<Result<Vec<String>, QueryParseError>>()?,
|
||||
),
|
||||
)),
|
||||
}
|
||||
}
|
||||
QueryComponent::Contains(q_value) => subqueries
|
||||
.push(Box::new(data::value_str.like(format!("S%{}%", q_value)))),
|
||||
QueryComponent::Any => {}
|
||||
};
|
||||
}
|
||||
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))
|
||||
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."),
|
||||
}
|
||||
}
|
||||
QueryPart::Type(_) => unimplemented!("Type queries are not yet implemented."),
|
||||
},
|
||||
Query::MultiQuery(mq) => {
|
||||
let subqueries: Result<Vec<Box<Predicate>>> = mq
|
||||
let subqueries: Result<Vec<Box<Predicate>>, QueryParseError> = mq
|
||||
.queries
|
||||
.iter()
|
||||
.map(|sq| sq.to_sqlite_predicates())
|
||||
|
@ -375,7 +443,9 @@ impl Query {
|
|||
}
|
||||
Ok(Box::new(result))
|
||||
}
|
||||
QueryQualifier::Not => Err(anyhow!("NOT only takes one subquery.")),
|
||||
QueryQualifier::Not => {
|
||||
Err(QueryParseError("NOT only takes one subquery.".into()))
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
@ -386,7 +456,7 @@ impl Query {
|
|||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
use anyhow::Result;
|
||||
use anyhow::{anyhow, Result};
|
||||
|
||||
#[test]
|
||||
fn test_matches() -> Result<()> {
|
||||
|
@ -402,7 +472,7 @@ mod test {
|
|||
query.to_sqlite_predicates()?;
|
||||
|
||||
let address = Address::Url(String::from("https://upendproject.net"));
|
||||
let query = format!("(matches \"{address}\" ? ?)").parse::<Query>()?;
|
||||
let query = format!("(matches @{address} ? ?)").parse::<Query>()?;
|
||||
assert_eq!(
|
||||
query,
|
||||
Query::SingleQuery(QueryPart::Matches(EntryQuery {
|
||||
|
@ -413,19 +483,19 @@ mod test {
|
|||
);
|
||||
query.to_sqlite_predicates()?;
|
||||
|
||||
let query = "(matches ? \"FOO\" ?)".parse::<Query>()?;
|
||||
let query = r#"(matches ? "FOO" ?)"#.parse::<Query>()?;
|
||||
assert_eq!(
|
||||
query,
|
||||
Query::SingleQuery(QueryPart::Matches(EntryQuery {
|
||||
entity: QueryComponent::Any,
|
||||
attribute: QueryComponent::Exact(String::from("FOO")),
|
||||
attribute: QueryComponent::Exact("FOO".into()),
|
||||
value: QueryComponent::Any
|
||||
}))
|
||||
);
|
||||
query.to_sqlite_predicates()?;
|
||||
|
||||
let value = EntryValue::Number(1337.93);
|
||||
let query = format!("(matches ? ? \"{}\")", value.to_string().unwrap()).parse::<Query>()?;
|
||||
let query = "(matches ? ? 1337.93)".parse::<Query>()?;
|
||||
assert_eq!(
|
||||
query,
|
||||
Query::SingleQuery(QueryPart::Matches(EntryQuery {
|
||||
|
@ -441,27 +511,19 @@ mod test {
|
|||
|
||||
#[test]
|
||||
fn test_in_parse() -> Result<()> {
|
||||
let query = "(matches ? (in \"FOO\" \"BAR\") ?)".parse::<Query>()?;
|
||||
let query = r#"(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())),
|
||||
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 = format!(
|
||||
"(matches ? ? (in {}))",
|
||||
values
|
||||
.iter()
|
||||
.map(|v| format!("\"{}\"", v.to_string().unwrap()))
|
||||
.collect::<Vec<String>>()
|
||||
.join(" ")
|
||||
)
|
||||
.parse::<Query>()?;
|
||||
let query = r#"(matches ? ? (in "FOO" "BAR"))"#.parse::<Query>()?;
|
||||
assert_eq!(
|
||||
query,
|
||||
Query::SingleQuery(QueryPart::Matches(EntryQuery {
|
||||
|
@ -473,15 +535,7 @@ mod test {
|
|||
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>()?;
|
||||
let query = r#"(matches ? ? (in 1337.93 1968.12))"#.parse::<Query>()?;
|
||||
assert_eq!(
|
||||
query,
|
||||
Query::SingleQuery(QueryPart::Matches(EntryQuery {
|
||||
|
@ -494,15 +548,8 @@ mod test {
|
|||
|
||||
// Invalid queries
|
||||
let values: Vec<EntryValue> = vec!["FOO".into(), EntryValue::Number(1337.93)];
|
||||
let query = format!(
|
||||
"(matches ? ? (in {}))",
|
||||
values
|
||||
.iter()
|
||||
.map(|v| format!("\"{}\"", v.to_string().unwrap()))
|
||||
.collect::<Vec<String>>()
|
||||
.join(" ")
|
||||
)
|
||||
.parse::<Query>()?;
|
||||
let query = r#"(matches ? ? (in "FOO" 1337.93))"#.parse::<Query>()?;
|
||||
|
||||
assert_eq!(
|
||||
query,
|
||||
Query::SingleQuery(QueryPart::Matches(EntryQuery {
|
||||
|
@ -517,15 +564,8 @@ mod test {
|
|||
.ok_or(anyhow!("Failed to reject mixed query."))?;
|
||||
|
||||
let values = vec![EntryValue::Number(1337.93), "FOO".into()];
|
||||
let query = format!(
|
||||
"(matches ? ? (in {}))",
|
||||
values
|
||||
.iter()
|
||||
.map(|v| format!("\"{}\"", v.to_string().unwrap()))
|
||||
.collect::<Vec<String>>()
|
||||
.join(" ")
|
||||
)
|
||||
.parse::<Query>()?;
|
||||
let query = r#"(matches ? ? (in 1337.93 "FOO"))"#.parse::<Query>()?;
|
||||
|
||||
assert_eq!(
|
||||
query,
|
||||
Query::SingleQuery(QueryPart::Matches(EntryQuery {
|
||||
|
@ -544,7 +584,7 @@ mod test {
|
|||
|
||||
#[test]
|
||||
fn test_contains() -> Result<()> {
|
||||
let query = "(matches (contains \"foo\") ? ?)".parse::<Query>()?;
|
||||
let query = r#"(matches (contains "foo") ? ?)"#.parse::<Query>()?;
|
||||
assert_eq!(
|
||||
query,
|
||||
Query::SingleQuery(QueryPart::Matches(EntryQuery {
|
||||
|
@ -555,7 +595,7 @@ mod test {
|
|||
);
|
||||
query.to_sqlite_predicates()?;
|
||||
|
||||
let query = "(matches ? (contains \"foo\") ?)".parse::<Query>()?;
|
||||
let query = r#"(matches ? (contains "foo") ?)"#.parse::<Query>()?;
|
||||
assert_eq!(
|
||||
query,
|
||||
Query::SingleQuery(QueryPart::Matches(EntryQuery {
|
||||
|
@ -566,7 +606,7 @@ mod test {
|
|||
);
|
||||
query.to_sqlite_predicates()?;
|
||||
|
||||
let query = "(matches ? ? (contains \"foo\"))".parse::<Query>()?;
|
||||
let query = r#"(matches ? ? (contains "foo"))"#.parse::<Query>()?;
|
||||
assert_eq!(
|
||||
query,
|
||||
Query::SingleQuery(QueryPart::Matches(EntryQuery {
|
||||
|
|
|
@ -458,7 +458,7 @@ mod test {
|
|||
upend_insert_val!(connection, random_entity, LABEL_ATTR, "FOOBAR").unwrap();
|
||||
upend_insert_val!(connection, random_entity, "FLAVOUR", "STRANGE").unwrap();
|
||||
|
||||
let query = format!(r#"(matches "{random_entity}" ? ?)"#)
|
||||
let query = format!(r#"(matches @{random_entity} ? ?)"#)
|
||||
.parse()
|
||||
.unwrap();
|
||||
let result = connection.query(query).unwrap();
|
||||
|
@ -468,7 +468,7 @@ mod test {
|
|||
upend_insert_val!(connection, random_entity, LABEL_ATTR, "BAZQUX").unwrap();
|
||||
upend_insert_val!(connection, random_entity, "CHARGE", "POSITIVE").unwrap();
|
||||
|
||||
let query = format!(r#"(matches (in "{random_entity}" "{other_entity}") ? ?)"#)
|
||||
let query = format!(r#"(matches (in @{random_entity} @{other_entity}) ? ?)"#)
|
||||
.parse()
|
||||
.unwrap();
|
||||
let result = connection.query(query).unwrap();
|
||||
|
@ -478,11 +478,11 @@ mod test {
|
|||
let result = connection.query(query).unwrap();
|
||||
assert_eq!(result.len(), 2);
|
||||
|
||||
// let query = format!("(matches ? \"{LABEL_ATTR}\" (in \"FOOBAR\" \"BAZQUX\"))")
|
||||
// .parse()
|
||||
// .unwrap();
|
||||
// let result = connection.query(query).unwrap();
|
||||
// assert_eq!(result.len(), 2);
|
||||
let query = format!(r#"(matches ? "{LABEL_ATTR}" (in "FOOBAR" "BAZQUX"))"#)
|
||||
.parse()
|
||||
.unwrap();
|
||||
let result = connection.query(query).unwrap();
|
||||
assert_eq!(result.len(), 2);
|
||||
|
||||
let query = format!(r#"(matches ? "{LABEL_ATTR}" (contains "OOBA"))"#)
|
||||
.parse()
|
||||
|
|
|
@ -72,7 +72,7 @@ impl Extractor for WebExtractor {
|
|||
) -> Result<bool> {
|
||||
Ok(connection
|
||||
.query(
|
||||
format!("(matches \"{address}\" (in \"HTML_TITLE\" \"HTML_DESCRIPTION\") ?)")
|
||||
format!(r#"(matches @{address} (in "HTML_TITLE" "HTML_DESCRIPTION") ?)"#)
|
||||
.parse()?,
|
||||
)?
|
||||
.is_empty())
|
||||
|
|
|
@ -576,7 +576,7 @@ pub async fn put_object_attribute(
|
|||
let new_address = web::block(move || {
|
||||
connection.transaction::<_, anyhow::Error, _>(|| {
|
||||
let existing_attr_entries =
|
||||
connection.query(format!("(matches \"{address}\" \"{attribute}\" ?)").parse()?)?;
|
||||
connection.query(format!(r#"(matches @{address} "{attribute}" ?)"#).parse()?)?;
|
||||
|
||||
for eae in existing_attr_entries {
|
||||
let _ = connection.remove_object(eae.address()?)?;
|
||||
|
|
|
@ -45,7 +45,7 @@
|
|||
|
||||
$: allTypeEntries = query(
|
||||
`(matches (in ${allTypeAddresses
|
||||
.map((addr) => `"${addr}"`)
|
||||
.map((addr) => `@${addr}`)
|
||||
.join(" ")}) ? ?)`
|
||||
).result;
|
||||
|
||||
|
|
|
@ -45,7 +45,7 @@
|
|||
}
|
||||
});
|
||||
|
||||
const addressesString = addresses.map((addr) => `"${addr}"`).join(" ");
|
||||
const addressesString = addresses.map((addr) => `@${addr}`).join(" ");
|
||||
|
||||
labelListing = query(`(matches (in ${addressesString}) "LBL" ? )`).result;
|
||||
}
|
||||
|
|
|
@ -79,7 +79,7 @@
|
|||
}
|
||||
});
|
||||
|
||||
const addressesString = addresses.map((addr) => `"${addr}"`).join(" ");
|
||||
const addressesString = addresses.map((addr) => `@${addr}`).join(" ");
|
||||
|
||||
labelListing = query(`(matches (in ${addressesString}) "LBL" ? )`).result;
|
||||
}
|
||||
|
|
|
@ -38,7 +38,7 @@
|
|||
|
||||
let exactHits: string[] = [];
|
||||
$: {
|
||||
const addressesString = objects.map((e) => `"${e.entity}"`).join(" ");
|
||||
const addressesString = objects.map((e) => `@${e.entity}`).join(" ");
|
||||
queryOnce(`(matches (in ${addressesString}) "LBL" ? )`).then(
|
||||
(labelListing) => {
|
||||
exactHits = labelListing.entries
|
||||
|
|
Loading…
Reference in a new issue