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> =
|
let all_directories: Vec<Entry> =
|
||||||
connection.query(Query::SingleQuery(QueryPart::Matches(EntryQuery {
|
connection.query(Query::SingleQuery(QueryPart::Matches(EntryQuery {
|
||||||
entity: QueryComponent::Any,
|
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()),
|
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
|
let directories_with_parents: Vec<Address> = connection
|
||||||
.query(Query::SingleQuery(QueryPart::Matches(EntryQuery {
|
.query(Query::SingleQuery(QueryPart::Matches(EntryQuery {
|
||||||
entity: QueryComponent::Any,
|
entity: QueryComponent::Any,
|
||||||
attribute: QueryComponent::Exact(HIER_HAS_ATTR.to_string()),
|
attribute: QueryComponent::Exact(HIER_HAS_ATTR.into()),
|
||||||
value: QueryComponent::Any,
|
value: QueryComponent::Any,
|
||||||
})))?
|
})))?
|
||||||
.extract_pointers()
|
.extract_pointers()
|
||||||
|
@ -143,7 +143,7 @@ pub fn fetch_or_create_dir(
|
||||||
let matching_directories = connection
|
let matching_directories = connection
|
||||||
.query(Query::SingleQuery(QueryPart::Matches(EntryQuery {
|
.query(Query::SingleQuery(QueryPart::Matches(EntryQuery {
|
||||||
entity: QueryComponent::Any,
|
entity: QueryComponent::Any,
|
||||||
attribute: QueryComponent::Exact(String::from(LABEL_ATTR)),
|
attribute: QueryComponent::Exact(LABEL_ATTR.into()),
|
||||||
value: QueryComponent::Exact(directory.as_ref().clone().into()),
|
value: QueryComponent::Exact(directory.as_ref().clone().into()),
|
||||||
})))?
|
})))?
|
||||||
.into_iter()
|
.into_iter()
|
||||||
|
@ -153,7 +153,7 @@ pub fn fetch_or_create_dir(
|
||||||
Some(parent) => connection
|
Some(parent) => connection
|
||||||
.query(Query::SingleQuery(QueryPart::Matches(EntryQuery {
|
.query(Query::SingleQuery(QueryPart::Matches(EntryQuery {
|
||||||
entity: QueryComponent::Exact(parent),
|
entity: QueryComponent::Exact(parent),
|
||||||
attribute: QueryComponent::Exact(String::from(HIER_HAS_ATTR)),
|
attribute: QueryComponent::Exact(HIER_HAS_ATTR.into()),
|
||||||
value: QueryComponent::Any,
|
value: QueryComponent::Any,
|
||||||
})))?
|
})))?
|
||||||
.extract_pointers()
|
.extract_pointers()
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
use crate::addressing::Address;
|
use crate::addressing::Address;
|
||||||
use crate::database::entry::EntryValue;
|
use crate::database::entry::EntryValue;
|
||||||
use crate::database::inner::schema::data;
|
use crate::database::inner::schema::data;
|
||||||
use anyhow::{anyhow, Result};
|
|
||||||
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;
|
||||||
|
@ -11,10 +10,19 @@ use std::borrow::Borrow;
|
||||||
use std::convert::TryFrom;
|
use std::convert::TryFrom;
|
||||||
use std::str::FromStr;
|
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)]
|
#[derive(Debug, PartialEq)]
|
||||||
pub enum QueryComponent<T>
|
pub enum QueryComponent<T>
|
||||||
where
|
where
|
||||||
T: FromStr,
|
T: TryFrom<lexpr::Value>,
|
||||||
{
|
{
|
||||||
Exact(T),
|
Exact(T),
|
||||||
In(Vec<T>),
|
In(Vec<T>),
|
||||||
|
@ -25,10 +33,66 @@ where
|
||||||
#[derive(Debug, PartialEq)]
|
#[derive(Debug, PartialEq)]
|
||||||
pub struct EntryQuery {
|
pub struct EntryQuery {
|
||||||
pub entity: QueryComponent<Address>,
|
pub entity: QueryComponent<Address>,
|
||||||
pub attribute: QueryComponent<String>,
|
pub attribute: QueryComponent<Attribute>,
|
||||||
pub value: QueryComponent<EntryValue>,
|
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)]
|
#[derive(Debug, PartialEq)]
|
||||||
pub enum QueryPart {
|
pub enum QueryPart {
|
||||||
Matches(EntryQuery),
|
Matches(EntryQuery),
|
||||||
|
@ -54,15 +118,28 @@ pub enum Query {
|
||||||
MultiQuery(MultiQuery),
|
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>;
|
type Predicate = dyn BoxableExpression<data::table, Sqlite, SqlType = Bool>;
|
||||||
|
|
||||||
impl TryFrom<&lexpr::Value> for Query {
|
impl TryFrom<&lexpr::Value> for Query {
|
||||||
type Error = anyhow::Error;
|
type Error = QueryParseError;
|
||||||
|
|
||||||
fn try_from(expression: &lexpr::Value) -> Result<Self> {
|
fn try_from(expression: &lexpr::Value) -> Result<Self, Self::Error> {
|
||||||
fn parse_component<T: FromStr>(value: &lexpr::Value) -> Result<QueryComponent<T>>
|
fn parse_component<T: TryFrom<lexpr::Value>>(
|
||||||
|
value: &lexpr::Value,
|
||||||
|
) -> Result<QueryComponent<T>, QueryParseError>
|
||||||
where
|
where
|
||||||
<T as FromStr>::Err: std::fmt::Debug,
|
QueryParseError: From<<T as TryFrom<lexpr::Value>>::Error>,
|
||||||
{
|
{
|
||||||
match value {
|
match value {
|
||||||
lexpr::Value::Cons(cons) => {
|
lexpr::Value::Cons(cons) => {
|
||||||
|
@ -72,22 +149,16 @@ impl TryFrom<&lexpr::Value> for Query {
|
||||||
let (cons_vec, _) = cons.clone().into_vec();
|
let (cons_vec, _) = cons.clone().into_vec();
|
||||||
if let Some(split) = cons_vec.split_first() {
|
if let Some(split) = cons_vec.split_first() {
|
||||||
let args = split.1;
|
let args = split.1;
|
||||||
let values: Result<Vec<T>, _> = args.iter().map(|value| {
|
let values: Result<Vec<T>, _> = args
|
||||||
if let lexpr::Value::String(str) = value {
|
.iter()
|
||||||
match T::from_str(str.borrow()) {
|
.map(|value| T::try_from(value.clone()))
|
||||||
Ok(value) => Ok(value),
|
.collect();
|
||||||
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?))
|
Ok(QueryComponent::In(values?))
|
||||||
} else {
|
} else {
|
||||||
Err(anyhow!(
|
Err(QueryParseError(
|
||||||
"Malformed expression: Inner value cannot be empty."
|
"Malformed expression: Inner value cannot be empty.".into(),
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
"contains" => {
|
"contains" => {
|
||||||
|
@ -98,43 +169,28 @@ impl TryFrom<&lexpr::Value> for Query {
|
||||||
if let lexpr::Value::String(str) = value {
|
if let lexpr::Value::String(str) = value {
|
||||||
Ok(QueryComponent::Contains(str.into_string()))
|
Ok(QueryComponent::Contains(str.into_string()))
|
||||||
} else {
|
} else {
|
||||||
Err(anyhow!("Malformed expression: 'Contains' argument must be a string."))
|
Err(QueryParseError("Malformed expression: 'Contains' argument must be a string.".into()))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_ => Err(anyhow!(
|
_ => Err(QueryParseError(
|
||||||
"Malformed expression: 'Contains' requires a single argument."
|
"Malformed expression: 'Contains' requires a single argument.".into()
|
||||||
)),
|
)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_ => Err(anyhow!(format!(
|
_ => Err(QueryParseError(format!(
|
||||||
"Malformed expression: Unknown symbol {}",
|
"Malformed expression: Unknowne symbol {}",
|
||||||
symbol
|
symbol
|
||||||
))),
|
))),
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
Err(anyhow!(format!(
|
Err(QueryParseError(format!(
|
||||||
"Malformed expression: Inner value '{:?}' is not a symbol.",
|
"Malformed expression: Inner value '{:?}' is not a symbol.",
|
||||||
value
|
value
|
||||||
)))
|
)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
lexpr::Value::String(str) => match T::from_str(str.borrow()) {
|
lexpr::Value::Symbol(symbol) if symbol.as_ref() == "?" => Ok(QueryComponent::Any),
|
||||||
Ok(value) => Ok(QueryComponent::Exact(value)),
|
_ => Ok(QueryComponent::Exact(T::try_from(value.clone())?)),
|
||||||
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 '?'."
|
|
||||||
)),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -145,7 +201,7 @@ impl TryFrom<&lexpr::Value> for Query {
|
||||||
let (cons_vec, _) = value.clone().into_vec();
|
let (cons_vec, _) = value.clone().into_vec();
|
||||||
if let [_, entity, attribute, value] = &cons_vec[..] {
|
if let [_, entity, attribute, value] = &cons_vec[..] {
|
||||||
let entity = parse_component::<Address>(entity)?;
|
let entity = parse_component::<Address>(entity)?;
|
||||||
let attribute = parse_component::<String>(attribute)?;
|
let attribute = parse_component::<Attribute>(attribute)?;
|
||||||
let value = parse_component::<EntryValue>(value)?;
|
let value = parse_component::<EntryValue>(value)?;
|
||||||
Ok(Query::SingleQuery(QueryPart::Matches(EntryQuery {
|
Ok(Query::SingleQuery(QueryPart::Matches(EntryQuery {
|
||||||
entity,
|
entity,
|
||||||
|
@ -153,8 +209,9 @@ impl TryFrom<&lexpr::Value> for Query {
|
||||||
value,
|
value,
|
||||||
})))
|
})))
|
||||||
} else {
|
} else {
|
||||||
Err(anyhow!(
|
Err(QueryParseError(
|
||||||
"Malformed expression: Wrong number of arguments to 'matches'."
|
"Malformed expression: Wrong number of arguments to 'matches'."
|
||||||
|
.into(),
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -166,13 +223,14 @@ impl TryFrom<&lexpr::Value> for Query {
|
||||||
type_name_str.to_string(),
|
type_name_str.to_string(),
|
||||||
)))
|
)))
|
||||||
} else {
|
} else {
|
||||||
Err(anyhow!(
|
Err(QueryParseError(
|
||||||
"Malformed expression: Type must be specified as a string."
|
"Malformed expression: Type must be specified as a string."
|
||||||
|
.into(),
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
Err(anyhow!(
|
Err(QueryParseError(
|
||||||
"Malformed expression: Wrong number of arguments to 'type'."
|
"Malformed expression: Wrong number of arguments to 'type'.".into(),
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -182,7 +240,7 @@ impl TryFrom<&lexpr::Value> for Query {
|
||||||
let values = sub_expressions
|
let values = sub_expressions
|
||||||
.iter()
|
.iter()
|
||||||
.map(|value| Ok(Box::new(Query::try_from(value)?)))
|
.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) {
|
if let Some(queries) = NonEmpty::from_vec(values) {
|
||||||
Ok(Query::MultiQuery(MultiQuery {
|
Ok(Query::MultiQuery(MultiQuery {
|
||||||
|
@ -193,8 +251,8 @@ impl TryFrom<&lexpr::Value> for Query {
|
||||||
queries,
|
queries,
|
||||||
}))
|
}))
|
||||||
} else {
|
} else {
|
||||||
Err(anyhow!(
|
Err(QueryParseError(
|
||||||
"Malformed expression: sub-query list cannot be empty.",
|
"Malformed expression: sub-query list cannot be empty.".into(),
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -204,7 +262,7 @@ impl TryFrom<&lexpr::Value> for Query {
|
||||||
let values = sub_expressions
|
let values = sub_expressions
|
||||||
.iter()
|
.iter()
|
||||||
.map(|value| Ok(Box::new(Query::try_from(value)?)))
|
.map(|value| Ok(Box::new(Query::try_from(value)?)))
|
||||||
.collect::<Result<Vec<Box<Query>>>>()?;
|
.collect::<Result<Vec<Box<Query>>, QueryParseError>>()?;
|
||||||
|
|
||||||
if values.len() == 1 {
|
if values.len() == 1 {
|
||||||
Ok(Query::MultiQuery(MultiQuery {
|
Ok(Query::MultiQuery(MultiQuery {
|
||||||
|
@ -212,85 +270,96 @@ impl TryFrom<&lexpr::Value> for Query {
|
||||||
queries: NonEmpty::from_vec(values).unwrap(),
|
queries: NonEmpty::from_vec(values).unwrap(),
|
||||||
}))
|
}))
|
||||||
} else {
|
} else {
|
||||||
Err(anyhow!(
|
Err(QueryParseError(
|
||||||
"Malformed expression: NOT takes exactly one parameter."
|
"Malformed expression: NOT takes exactly one parameter.".into(),
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_ => Err(anyhow!(format!(
|
_ => Err(QueryParseError(format!(
|
||||||
"Malformed expression: Unknown symbol '{}'.",
|
"Malformed expression: Unknown symbol '{}'.",
|
||||||
symbol
|
symbol
|
||||||
))),
|
))),
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
Err(anyhow!(format!(
|
Err(QueryParseError(format!(
|
||||||
"Malformed expression: Value '{:?}' is not a symbol.",
|
"Malformed expression: Value '{:?}' is not a symbol.",
|
||||||
value
|
value
|
||||||
)))
|
)))
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
Err(anyhow!("Malformed expression: Not a list."))
|
Err(QueryParseError("Malformed expression: Not a list.".into()))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FromStr for Query {
|
impl FromStr for Query {
|
||||||
type Err = anyhow::Error;
|
type Err = QueryParseError;
|
||||||
|
|
||||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
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)
|
Query::try_from(&sexp)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Query {
|
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 {
|
match self {
|
||||||
Query::SingleQuery(qp) => {
|
Query::SingleQuery(qp) => match qp {
|
||||||
match qp {
|
QueryPart::Matches(eq) => {
|
||||||
QueryPart::Matches(eq) => {
|
let mut subqueries: Vec<Box<Predicate>> = vec![];
|
||||||
let mut subqueries: Vec<Box<Predicate>> = vec![];
|
|
||||||
|
|
||||||
match &eq.entity {
|
match &eq.entity {
|
||||||
QueryComponent::Exact(q_entity) => {
|
QueryComponent::Exact(q_entity) => subqueries.push(Box::new(
|
||||||
subqueries.push(Box::new(data::entity.eq(q_entity.encode()?)))
|
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) => {
|
_ => subqueries.push(Box::new(
|
||||||
let entities: Result<Vec<_>, _> =
|
data::value_str.eq(q_value
|
||||||
q_entities.iter().map(|t| t.encode()).collect();
|
.to_string()
|
||||||
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 => {}
|
},
|
||||||
};
|
QueryComponent::In(q_values) => {
|
||||||
|
let first = q_values.first().ok_or_else(|| {
|
||||||
|
QueryParseError(
|
||||||
|
"Malformed expression: Inner value cannot be empty.".into(),
|
||||||
|
)
|
||||||
|
})?;
|
||||||
|
|
||||||
match &eq.attribute {
|
match first {
|
||||||
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 {
|
|
||||||
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
|
||||||
|
@ -299,10 +368,10 @@ impl Query {
|
||||||
if let EntryValue::Number(n) = v {
|
if let EntryValue::Number(n) = v {
|
||||||
Ok(*n)
|
Ok(*n)
|
||||||
} else {
|
} 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(
|
_ => subqueries.push(Box::new(
|
||||||
|
@ -311,39 +380,38 @@ impl Query {
|
||||||
.iter()
|
.iter()
|
||||||
.map(|v| {
|
.map(|v| {
|
||||||
if let EntryValue::Number(_) = 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 {
|
} 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
|
QueryComponent::Contains(q_value) => subqueries
|
||||||
.push(Box::new(data::value_str.like(format!("S%{}%", q_value)))),
|
.push(Box::new(data::value_str.like(format!("S%{}%", q_value)))),
|
||||||
QueryComponent::Any => {}
|
QueryComponent::Any => {}
|
||||||
};
|
};
|
||||||
|
|
||||||
match subqueries.len() {
|
match subqueries.len() {
|
||||||
0 => Ok(Box::new(true.into_sql::<Bool>())),
|
0 => Ok(Box::new(true.into_sql::<Bool>())),
|
||||||
1 => Ok(subqueries.remove(0)),
|
1 => Ok(subqueries.remove(0)),
|
||||||
_ => {
|
_ => {
|
||||||
let mut result: Box<And<Box<Predicate>, Box<Predicate>>> =
|
let mut result: Box<And<Box<Predicate>, Box<Predicate>>> =
|
||||||
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(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>>> = mq
|
let subqueries: Result<Vec<Box<Predicate>>, QueryParseError> = mq
|
||||||
.queries
|
.queries
|
||||||
.iter()
|
.iter()
|
||||||
.map(|sq| sq.to_sqlite_predicates())
|
.map(|sq| sq.to_sqlite_predicates())
|
||||||
|
@ -375,7 +443,9 @@ impl Query {
|
||||||
}
|
}
|
||||||
Ok(Box::new(result))
|
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)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
use super::*;
|
use super::*;
|
||||||
use anyhow::Result;
|
use anyhow::{anyhow, Result};
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_matches() -> Result<()> {
|
fn test_matches() -> Result<()> {
|
||||||
|
@ -402,7 +472,7 @@ mod test {
|
||||||
query.to_sqlite_predicates()?;
|
query.to_sqlite_predicates()?;
|
||||||
|
|
||||||
let address = Address::Url(String::from("https://upendproject.net"));
|
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!(
|
assert_eq!(
|
||||||
query,
|
query,
|
||||||
Query::SingleQuery(QueryPart::Matches(EntryQuery {
|
Query::SingleQuery(QueryPart::Matches(EntryQuery {
|
||||||
|
@ -413,19 +483,19 @@ mod test {
|
||||||
);
|
);
|
||||||
query.to_sqlite_predicates()?;
|
query.to_sqlite_predicates()?;
|
||||||
|
|
||||||
let query = "(matches ? \"FOO\" ?)".parse::<Query>()?;
|
let query = r#"(matches ? "FOO" ?)"#.parse::<Query>()?;
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
query,
|
query,
|
||||||
Query::SingleQuery(QueryPart::Matches(EntryQuery {
|
Query::SingleQuery(QueryPart::Matches(EntryQuery {
|
||||||
entity: QueryComponent::Any,
|
entity: QueryComponent::Any,
|
||||||
attribute: QueryComponent::Exact(String::from("FOO")),
|
attribute: QueryComponent::Exact("FOO".into()),
|
||||||
value: QueryComponent::Any
|
value: QueryComponent::Any
|
||||||
}))
|
}))
|
||||||
);
|
);
|
||||||
query.to_sqlite_predicates()?;
|
query.to_sqlite_predicates()?;
|
||||||
|
|
||||||
let value = EntryValue::Number(1337.93);
|
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!(
|
assert_eq!(
|
||||||
query,
|
query,
|
||||||
Query::SingleQuery(QueryPart::Matches(EntryQuery {
|
Query::SingleQuery(QueryPart::Matches(EntryQuery {
|
||||||
|
@ -441,27 +511,19 @@ mod test {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_in_parse() -> Result<()> {
|
fn test_in_parse() -> Result<()> {
|
||||||
let query = "(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(EntryQuery {
|
Query::SingleQuery(QueryPart::Matches(EntryQuery {
|
||||||
entity: QueryComponent::Any,
|
entity: QueryComponent::Any,
|
||||||
attribute: QueryComponent::In(vec!("FOO".to_string(), "BAR".to_string())),
|
attribute: QueryComponent::In(vec!("FOO".into(), "BAR".into())),
|
||||||
value: QueryComponent::Any
|
value: QueryComponent::Any
|
||||||
}))
|
}))
|
||||||
);
|
);
|
||||||
query.to_sqlite_predicates()?;
|
query.to_sqlite_predicates()?;
|
||||||
|
|
||||||
let values: Vec<EntryValue> = vec!["FOO".into(), "BAR".into()];
|
let values: Vec<EntryValue> = vec!["FOO".into(), "BAR".into()];
|
||||||
let query = format!(
|
let query = r#"(matches ? ? (in "FOO" "BAR"))"#.parse::<Query>()?;
|
||||||
"(matches ? ? (in {}))",
|
|
||||||
values
|
|
||||||
.iter()
|
|
||||||
.map(|v| format!("\"{}\"", v.to_string().unwrap()))
|
|
||||||
.collect::<Vec<String>>()
|
|
||||||
.join(" ")
|
|
||||||
)
|
|
||||||
.parse::<Query>()?;
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
query,
|
query,
|
||||||
Query::SingleQuery(QueryPart::Matches(EntryQuery {
|
Query::SingleQuery(QueryPart::Matches(EntryQuery {
|
||||||
|
@ -473,15 +535,7 @@ mod test {
|
||||||
query.to_sqlite_predicates()?;
|
query.to_sqlite_predicates()?;
|
||||||
|
|
||||||
let values = vec![EntryValue::Number(1337.93), EntryValue::Number(1968.12)];
|
let values = vec![EntryValue::Number(1337.93), EntryValue::Number(1968.12)];
|
||||||
let query = format!(
|
let query = r#"(matches ? ? (in 1337.93 1968.12))"#.parse::<Query>()?;
|
||||||
"(matches ? ? (in {}))",
|
|
||||||
values
|
|
||||||
.iter()
|
|
||||||
.map(|v| format!("\"{}\"", v.to_string().unwrap()))
|
|
||||||
.collect::<Vec<String>>()
|
|
||||||
.join(" ")
|
|
||||||
)
|
|
||||||
.parse::<Query>()?;
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
query,
|
query,
|
||||||
Query::SingleQuery(QueryPart::Matches(EntryQuery {
|
Query::SingleQuery(QueryPart::Matches(EntryQuery {
|
||||||
|
@ -494,15 +548,8 @@ mod test {
|
||||||
|
|
||||||
// Invalid queries
|
// Invalid queries
|
||||||
let values: Vec<EntryValue> = vec!["FOO".into(), EntryValue::Number(1337.93)];
|
let values: Vec<EntryValue> = vec!["FOO".into(), EntryValue::Number(1337.93)];
|
||||||
let query = format!(
|
let query = r#"(matches ? ? (in "FOO" 1337.93))"#.parse::<Query>()?;
|
||||||
"(matches ? ? (in {}))",
|
|
||||||
values
|
|
||||||
.iter()
|
|
||||||
.map(|v| format!("\"{}\"", v.to_string().unwrap()))
|
|
||||||
.collect::<Vec<String>>()
|
|
||||||
.join(" ")
|
|
||||||
)
|
|
||||||
.parse::<Query>()?;
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
query,
|
query,
|
||||||
Query::SingleQuery(QueryPart::Matches(EntryQuery {
|
Query::SingleQuery(QueryPart::Matches(EntryQuery {
|
||||||
|
@ -517,15 +564,8 @@ mod test {
|
||||||
.ok_or(anyhow!("Failed to reject mixed query."))?;
|
.ok_or(anyhow!("Failed to reject mixed query."))?;
|
||||||
|
|
||||||
let values = vec![EntryValue::Number(1337.93), "FOO".into()];
|
let values = vec![EntryValue::Number(1337.93), "FOO".into()];
|
||||||
let query = format!(
|
let query = r#"(matches ? ? (in 1337.93 "FOO"))"#.parse::<Query>()?;
|
||||||
"(matches ? ? (in {}))",
|
|
||||||
values
|
|
||||||
.iter()
|
|
||||||
.map(|v| format!("\"{}\"", v.to_string().unwrap()))
|
|
||||||
.collect::<Vec<String>>()
|
|
||||||
.join(" ")
|
|
||||||
)
|
|
||||||
.parse::<Query>()?;
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
query,
|
query,
|
||||||
Query::SingleQuery(QueryPart::Matches(EntryQuery {
|
Query::SingleQuery(QueryPart::Matches(EntryQuery {
|
||||||
|
@ -544,7 +584,7 @@ mod test {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_contains() -> Result<()> {
|
fn test_contains() -> Result<()> {
|
||||||
let query = "(matches (contains \"foo\") ? ?)".parse::<Query>()?;
|
let query = r#"(matches (contains "foo") ? ?)"#.parse::<Query>()?;
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
query,
|
query,
|
||||||
Query::SingleQuery(QueryPart::Matches(EntryQuery {
|
Query::SingleQuery(QueryPart::Matches(EntryQuery {
|
||||||
|
@ -555,7 +595,7 @@ mod test {
|
||||||
);
|
);
|
||||||
query.to_sqlite_predicates()?;
|
query.to_sqlite_predicates()?;
|
||||||
|
|
||||||
let query = "(matches ? (contains \"foo\") ?)".parse::<Query>()?;
|
let query = r#"(matches ? (contains "foo") ?)"#.parse::<Query>()?;
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
query,
|
query,
|
||||||
Query::SingleQuery(QueryPart::Matches(EntryQuery {
|
Query::SingleQuery(QueryPart::Matches(EntryQuery {
|
||||||
|
@ -566,7 +606,7 @@ mod test {
|
||||||
);
|
);
|
||||||
query.to_sqlite_predicates()?;
|
query.to_sqlite_predicates()?;
|
||||||
|
|
||||||
let query = "(matches ? ? (contains \"foo\"))".parse::<Query>()?;
|
let query = r#"(matches ? ? (contains "foo"))"#.parse::<Query>()?;
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
query,
|
query,
|
||||||
Query::SingleQuery(QueryPart::Matches(EntryQuery {
|
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, LABEL_ATTR, "FOOBAR").unwrap();
|
||||||
upend_insert_val!(connection, random_entity, "FLAVOUR", "STRANGE").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()
|
.parse()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let result = connection.query(query).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, LABEL_ATTR, "BAZQUX").unwrap();
|
||||||
upend_insert_val!(connection, random_entity, "CHARGE", "POSITIVE").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()
|
.parse()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let result = connection.query(query).unwrap();
|
let result = connection.query(query).unwrap();
|
||||||
|
@ -478,11 +478,11 @@ mod test {
|
||||||
let result = connection.query(query).unwrap();
|
let result = connection.query(query).unwrap();
|
||||||
assert_eq!(result.len(), 2);
|
assert_eq!(result.len(), 2);
|
||||||
|
|
||||||
// let query = format!("(matches ? \"{LABEL_ATTR}\" (in \"FOOBAR\" \"BAZQUX\"))")
|
let query = format!(r#"(matches ? "{LABEL_ATTR}" (in "FOOBAR" "BAZQUX"))"#)
|
||||||
// .parse()
|
.parse()
|
||||||
// .unwrap();
|
.unwrap();
|
||||||
// let result = connection.query(query).unwrap();
|
let result = connection.query(query).unwrap();
|
||||||
// assert_eq!(result.len(), 2);
|
assert_eq!(result.len(), 2);
|
||||||
|
|
||||||
let query = format!(r#"(matches ? "{LABEL_ATTR}" (contains "OOBA"))"#)
|
let query = format!(r#"(matches ? "{LABEL_ATTR}" (contains "OOBA"))"#)
|
||||||
.parse()
|
.parse()
|
||||||
|
|
|
@ -72,7 +72,7 @@ impl Extractor for WebExtractor {
|
||||||
) -> Result<bool> {
|
) -> Result<bool> {
|
||||||
Ok(connection
|
Ok(connection
|
||||||
.query(
|
.query(
|
||||||
format!("(matches \"{address}\" (in \"HTML_TITLE\" \"HTML_DESCRIPTION\") ?)")
|
format!(r#"(matches @{address} (in "HTML_TITLE" "HTML_DESCRIPTION") ?)"#)
|
||||||
.parse()?,
|
.parse()?,
|
||||||
)?
|
)?
|
||||||
.is_empty())
|
.is_empty())
|
||||||
|
|
|
@ -576,7 +576,7 @@ pub async fn put_object_attribute(
|
||||||
let new_address = web::block(move || {
|
let new_address = web::block(move || {
|
||||||
connection.transaction::<_, anyhow::Error, _>(|| {
|
connection.transaction::<_, anyhow::Error, _>(|| {
|
||||||
let existing_attr_entries =
|
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 {
|
for eae in existing_attr_entries {
|
||||||
let _ = connection.remove_object(eae.address()?)?;
|
let _ = connection.remove_object(eae.address()?)?;
|
||||||
|
|
|
@ -45,7 +45,7 @@
|
||||||
|
|
||||||
$: allTypeEntries = query(
|
$: allTypeEntries = query(
|
||||||
`(matches (in ${allTypeAddresses
|
`(matches (in ${allTypeAddresses
|
||||||
.map((addr) => `"${addr}"`)
|
.map((addr) => `@${addr}`)
|
||||||
.join(" ")}) ? ?)`
|
.join(" ")}) ? ?)`
|
||||||
).result;
|
).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;
|
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;
|
labelListing = query(`(matches (in ${addressesString}) "LBL" ? )`).result;
|
||||||
}
|
}
|
||||||
|
|
|
@ -38,7 +38,7 @@
|
||||||
|
|
||||||
let exactHits: string[] = [];
|
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(
|
queryOnce(`(matches (in ${addressesString}) "LBL" ? )`).then(
|
||||||
(labelListing) => {
|
(labelListing) => {
|
||||||
exactHits = labelListing.entries
|
exactHits = labelListing.entries
|
||||||
|
|
Loading…
Reference in a new issue