feat, wip: Discard (`_`) token in langs, results in Only-queries (#38)
ci/woodpecker/push/woodpecker Pipeline was successful Details

Tomáš Mládek 2023-07-14 08:37:49 +02:00
parent 7a9aafac5a
commit d0a2c545e8
11 changed files with 298 additions and 204 deletions

View File

@ -7,7 +7,7 @@ use std::convert::TryFrom;
use std::io::{Cursor, Write};
use url::Url;
#[derive(Debug, Clone, Serialize, Deserialize)]
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct Entry {
pub entity: Address,
pub attribute: String,

View File

@ -16,7 +16,7 @@ impl From<&str> for Attribute {
}
#[derive(Debug, Clone, PartialEq)]
pub enum QueryComponent<T>
pub enum PatternQueryComponent<T>
where
T: TryFrom<lexpr::Value>,
{
@ -24,13 +24,14 @@ where
In(Vec<T>),
Contains(String),
Variable(Option<String>),
Discard,
}
#[derive(Debug, Clone, PartialEq)]
pub struct PatternQuery {
pub entity: QueryComponent<Address>,
pub attribute: QueryComponent<Attribute>,
pub value: QueryComponent<EntryValue>,
pub entity: PatternQueryComponent<Address>,
pub attribute: PatternQueryComponent<Attribute>,
pub value: PatternQueryComponent<EntryValue>,
}
impl TryFrom<lexpr::Value> for Address {
@ -120,7 +121,7 @@ impl TryFrom<&lexpr::Value> for Query {
fn try_from(expression: &lexpr::Value) -> Result<Self, Self::Error> {
fn parse_component<T: TryFrom<lexpr::Value>>(
value: &lexpr::Value,
) -> Result<QueryComponent<T>, UpEndError>
) -> Result<PatternQueryComponent<T>, UpEndError>
where
UpEndError: From<<T as TryFrom<lexpr::Value>>::Error>,
{
@ -137,7 +138,7 @@ impl TryFrom<&lexpr::Value> for Query {
.map(|value| T::try_from(value.clone()))
.collect();
Ok(QueryComponent::In(values?))
Ok(PatternQueryComponent::In(values?))
} else {
Err(UpEndError::QueryParseError(
"Malformed expression: Inner value cannot be empty.".into(),
@ -150,7 +151,7 @@ impl TryFrom<&lexpr::Value> for Query {
2 => {
let value = cons_vec.remove(1);
if let lexpr::Value::String(str) = value {
Ok(QueryComponent::Contains(str.into_string()))
Ok(PatternQueryComponent::Contains(str.into_string()))
} else {
Err(UpEndError::QueryParseError("Malformed expression: 'Contains' argument must be a string.".into()))
}
@ -174,13 +175,16 @@ impl TryFrom<&lexpr::Value> for Query {
}
lexpr::Value::Symbol(symbol) if symbol.starts_with('?') => {
let var_name = symbol.strip_prefix('?').unwrap();
Ok(QueryComponent::Variable(if var_name.is_empty() {
Ok(PatternQueryComponent::Variable(if var_name.is_empty() {
None
} else {
Some(var_name.into())
}))
}
_ => Ok(QueryComponent::Exact(T::try_from(value.clone())?)),
lexpr::Value::Symbol(symbol) if symbol.starts_with('_') => {
Ok(PatternQueryComponent::Discard)
}
_ => Ok(PatternQueryComponent::Exact(T::try_from(value.clone())?)),
}
}
@ -309,9 +313,9 @@ mod test {
assert_eq!(
query,
Query::SingleQuery(QueryPart::Matches(PatternQuery {
entity: QueryComponent::Variable(None),
attribute: QueryComponent::Variable(None),
value: QueryComponent::Variable(None)
entity: PatternQueryComponent::Variable(None),
attribute: PatternQueryComponent::Variable(None),
value: PatternQueryComponent::Variable(None)
}))
);
@ -320,9 +324,9 @@ mod test {
assert_eq!(
query,
Query::SingleQuery(QueryPart::Matches(PatternQuery {
entity: QueryComponent::Exact(address),
attribute: QueryComponent::Variable(None),
value: QueryComponent::Variable(None)
entity: PatternQueryComponent::Exact(address),
attribute: PatternQueryComponent::Variable(None),
value: PatternQueryComponent::Variable(None)
}))
);
@ -330,9 +334,9 @@ mod test {
assert_eq!(
query,
Query::SingleQuery(QueryPart::Matches(PatternQuery {
entity: QueryComponent::Variable(None),
attribute: QueryComponent::Exact("FOO".into()),
value: QueryComponent::Variable(None)
entity: PatternQueryComponent::Variable(None),
attribute: PatternQueryComponent::Exact("FOO".into()),
value: PatternQueryComponent::Variable(None)
}))
);
@ -341,9 +345,9 @@ mod test {
assert_eq!(
query,
Query::SingleQuery(QueryPart::Matches(PatternQuery {
entity: QueryComponent::Variable(None),
attribute: QueryComponent::Variable(None),
value: QueryComponent::Exact(value)
entity: PatternQueryComponent::Variable(None),
attribute: PatternQueryComponent::Variable(None),
value: PatternQueryComponent::Exact(value)
}))
);
@ -356,9 +360,9 @@ mod test {
assert_eq!(
query,
Query::SingleQuery(QueryPart::Matches(PatternQuery {
entity: QueryComponent::Variable(Some("a".into())),
attribute: QueryComponent::Variable(Some("b".into())),
value: QueryComponent::Variable(None)
entity: PatternQueryComponent::Variable(Some("a".into())),
attribute: PatternQueryComponent::Variable(Some("b".into())),
value: PatternQueryComponent::Variable(None)
}))
);
@ -371,9 +375,9 @@ mod test {
assert_eq!(
query,
Query::SingleQuery(QueryPart::Matches(PatternQuery {
entity: QueryComponent::Variable(None),
attribute: QueryComponent::In(vec!("FOO".into(), "BAR".into())),
value: QueryComponent::Variable(None)
entity: PatternQueryComponent::Variable(None),
attribute: PatternQueryComponent::In(vec!("FOO".into(), "BAR".into())),
value: PatternQueryComponent::Variable(None)
}))
);
@ -382,9 +386,9 @@ mod test {
assert_eq!(
query,
Query::SingleQuery(QueryPart::Matches(PatternQuery {
entity: QueryComponent::Variable(None),
attribute: QueryComponent::Variable(None),
value: QueryComponent::In(values)
entity: PatternQueryComponent::Variable(None),
attribute: PatternQueryComponent::Variable(None),
value: PatternQueryComponent::In(values)
}))
);
@ -393,9 +397,9 @@ mod test {
assert_eq!(
query,
Query::SingleQuery(QueryPart::Matches(PatternQuery {
entity: QueryComponent::Variable(None),
attribute: QueryComponent::Variable(None),
value: QueryComponent::In(values)
entity: PatternQueryComponent::Variable(None),
attribute: PatternQueryComponent::Variable(None),
value: PatternQueryComponent::In(values)
}))
);
@ -406,9 +410,9 @@ mod test {
assert_eq!(
query,
Query::SingleQuery(QueryPart::Matches(PatternQuery {
entity: QueryComponent::Variable(None),
attribute: QueryComponent::Variable(None),
value: QueryComponent::In(values)
entity: PatternQueryComponent::Variable(None),
attribute: PatternQueryComponent::Variable(None),
value: PatternQueryComponent::In(values)
}))
);
@ -418,9 +422,9 @@ mod test {
assert_eq!(
query,
Query::SingleQuery(QueryPart::Matches(PatternQuery {
entity: QueryComponent::Variable(None),
attribute: QueryComponent::Variable(None),
value: QueryComponent::In(values)
entity: PatternQueryComponent::Variable(None),
attribute: PatternQueryComponent::Variable(None),
value: PatternQueryComponent::In(values)
}))
);
@ -433,9 +437,9 @@ mod test {
assert_eq!(
query,
Query::SingleQuery(QueryPart::Matches(PatternQuery {
entity: QueryComponent::Contains("foo".to_string()),
attribute: QueryComponent::Variable(None),
value: QueryComponent::Variable(None)
entity: PatternQueryComponent::Contains("foo".to_string()),
attribute: PatternQueryComponent::Variable(None),
value: PatternQueryComponent::Variable(None)
}))
);
@ -443,9 +447,9 @@ mod test {
assert_eq!(
query,
Query::SingleQuery(QueryPart::Matches(PatternQuery {
entity: QueryComponent::Variable(None),
attribute: QueryComponent::Contains("foo".to_string()),
value: QueryComponent::Variable(None),
entity: PatternQueryComponent::Variable(None),
attribute: PatternQueryComponent::Contains("foo".to_string()),
value: PatternQueryComponent::Variable(None),
}))
);
@ -453,9 +457,9 @@ mod test {
assert_eq!(
query,
Query::SingleQuery(QueryPart::Matches(PatternQuery {
entity: QueryComponent::Variable(None),
attribute: QueryComponent::Variable(None),
value: QueryComponent::Contains("foo".to_string())
entity: PatternQueryComponent::Variable(None),
attribute: PatternQueryComponent::Variable(None),
value: PatternQueryComponent::Contains("foo".to_string())
}))
);

View File

@ -134,7 +134,7 @@ impl Extractor for ID3Extractor {
}
let is_extracted = !connection
.query(format!("(matches @{} (contains \"ID3\") ?)", address).parse()?)?
.query::<Vec<Entry>>(format!("(matches @{} (contains \"ID3\") ?)", address).parse()?)?
.is_empty();
if is_extracted {

View File

@ -136,7 +136,9 @@ impl Extractor for MediaExtractor {
}
let is_extracted = !connection
.query(format!("(matches @{} (contains \"{}\") ?)", address, DURATION_KEY).parse()?)?
.query::<Vec<Entry>>(
format!("(matches @{} (contains \"{}\") ?)", address, DURATION_KEY).parse()?,
)?
.is_empty();
if is_extracted {

View File

@ -153,7 +153,7 @@ impl Extractor for ExifExtractor {
}
let is_extracted = !connection
.query(format!("(matches @{} (contains \"EXIF\") ?)", address).parse()?)?
.query::<Vec<Entry>>(format!("(matches @{} (contains \"EXIF\") ?)", address).parse()?)?
.is_empty();
if is_extracted {

View File

@ -99,7 +99,7 @@ impl Extractor for WebExtractor {
fn is_needed(&self, address: &Address, connection: &UpEndConnection) -> Result<bool> {
Ok(connection
.query(
.query::<Vec<Entry>>(
format!(r#"(matches @{address} (in "HTML_TITLE" "HTML_DESCRIPTION") ?)"#)
.parse()?,
)?

View File

@ -39,6 +39,7 @@ use upend_base::lang::Query;
use upend_db::hierarchies::{list_roots, resolve_path, UHierPath};
use upend_db::jobs;
use upend_db::stores::{Blob, UpStore};
use upend_db::QueryResult;
use upend_db::UpEndDatabase;
use url::Url;
@ -263,25 +264,41 @@ pub async fn get_query(state: web::Data<State>, query: String) -> Result<HttpRes
let connection = state.upend.connection().map_err(ErrorInternalServerError)?;
let in_query: Query = query.parse().map_err(ErrorBadRequest)?;
let entries = web::block(move || connection.query(in_query))
let query_result = web::block(move || connection.query::<QueryResult>(in_query))
.await
.map_err(ErrorInternalServerError)?
.map_err(ErrorInternalServerError)?;
let mut result: HashMap<String, Entry> = HashMap::new();
for entry in entries {
result.insert(
b58_encode(
entry
.address()
.map_err(ErrorInternalServerError)?
.encode()
.map_err(ErrorInternalServerError)?,
),
entry,
);
}
Ok(HttpResponse::Ok().json(&result))
let result = match query_result {
QueryResult::Entries(entries) => {
let mut result: HashMap<String, Entry> = HashMap::new();
for entry in entries {
result.insert(
b58_encode(
entry
.address()
.map_err(ErrorInternalServerError)?
.encode()
.map_err(ErrorInternalServerError)?,
),
entry,
);
}
json!({ "entries": result })
}
QueryResult::Entities(entities) => {
json!({ "entities": entities })
}
QueryResult::Attributes(attributes) => {
json!({ "attributes": attributes })
}
QueryResult::Values(values) => {
json!({ "values": values })
}
};
Ok(HttpResponse::Ok().json(result))
}
trait EntriesAsHash {
@ -566,8 +583,8 @@ pub async fn put_object_attribute(
let new_address = web::block(move || {
connection.transaction::<_, anyhow::Error, _>(|| {
let existing_attr_entries =
connection.query(format!(r#"(matches @{address} "{attribute}" ?)"#).parse()?)?;
let existing_attr_entries = connection
.query::<Vec<Entry>>(format!(r#"(matches @{address} "{attribute}" ?)"#).parse()?)?;
for eae in existing_attr_entries {
let _ = connection.remove_object(eae.address()?)?;
@ -679,42 +696,6 @@ pub async fn get_address(
Ok(response.json(format!("{}", address)))
}
#[get("/api/all/attributes")]
pub async fn get_all_attributes(state: web::Data<State>) -> Result<HttpResponse, Error> {
let connection = state.upend.connection().map_err(ErrorInternalServerError)?;
let attributes = web::block(move || connection.get_all_attributes())
.await?
.map_err(ErrorInternalServerError)?;
let connection = state.upend.connection().map_err(ErrorInternalServerError)?;
let result: serde_json::Value = attributes
.into_iter()
.map(|attribute| {
json!({
"name": attribute,
"labels": connection
.retrieve_object(&Address::Attribute(attribute))
.unwrap_or_else(|_| vec![])
.into_iter()
.filter_map(|e| {
if e.attribute == ATTR_LABEL {
if let EntryValue::String(label) = e.value {
Some(label)
} else {
None
}
} else {
None
}
})
.collect::<Vec<String>>(),
})
})
.collect();
Ok(HttpResponse::Ok().json(result))
}
#[routes]
#[get("/api/hier/{path:.*}")]
#[put("/api/hier/{path:.*}")]

View File

@ -56,7 +56,6 @@ where
.service(routes::put_object_attribute)
.service(routes::delete_object)
.service(routes::get_address)
.service(routes::get_all_attributes)
.service(routes::api_refresh)
.service(routes::list_hier)
.service(routes::list_hier_roots)

View File

@ -18,7 +18,14 @@ use diesel::{
use diesel::{BoxableExpression, QueryDsl};
use diesel::{ExpressionMethods, TextExpressionMethods};
use upend_base::entry::EntryValue;
use upend_base::lang::{PatternQuery, Query, QueryComponent, QueryPart, QueryQualifier};
use upend_base::lang::{PatternQuery, PatternQueryComponent, Query, QueryPart, QueryQualifier};
pub enum InnerQueryResult {
Entries(Vec<Entry>),
Entities(Vec<Vec<u8>>),
Attributes(Vec<String>),
Values(Vec<EntryValue>),
}
#[derive(Debug, Clone)]
pub struct QueryExecutionError(String);
@ -33,15 +40,51 @@ impl std::error::Error for QueryExecutionError {}
pub fn execute(
connection: &PooledConnection<ConnectionManager<SqliteConnection>>,
query: Query,
) -> Result<Vec<Entry>, QueryExecutionError> {
query: &Query,
) -> Result<InnerQueryResult, QueryExecutionError> {
use crate::inner::schema::data::dsl::*;
if let Some(predicates) = to_sqlite_predicates(query.clone())? {
let db_query = data.filter(predicates);
db_query
let entries = db_query
.load::<models::Entry>(connection)
.map_err(|e| QueryExecutionError(e.to_string()))
.map_err(|e| QueryExecutionError(e.to_string()))?;
match query {
Query::SingleQuery(query) => match query {
QueryPart::Matches(pattern)
if !matches!(pattern.entity, PatternQueryComponent::Discard)
&& matches!(pattern.attribute, PatternQueryComponent::Discard)
&& matches!(pattern.value, PatternQueryComponent::Discard) =>
{
Ok(InnerQueryResult::Entities(
entries.into_iter().map(|e| e.entity).collect(),
))
}
QueryPart::Matches(pattern)
if matches!(pattern.entity, PatternQueryComponent::Discard)
&& !matches!(pattern.attribute, PatternQueryComponent::Discard)
&& matches!(pattern.value, PatternQueryComponent::Discard) =>
{
let mut attributes: Vec<String> =
entries.into_iter().map(|e| e.attribute).collect();
attributes.sort_unstable();
attributes.dedup();
Ok(InnerQueryResult::Attributes(attributes))
}
QueryPart::Matches(pattern)
if matches!(pattern.entity, PatternQueryComponent::Discard)
&& matches!(pattern.attribute, PatternQueryComponent::Discard)
&& !matches!(pattern.value, PatternQueryComponent::Discard) =>
{
Err(QueryExecutionError(
"Only-value entries not yet implemented.".to_string(),
))
}
_ => Ok(InnerQueryResult::Entries(entries)),
},
_ => Ok(InnerQueryResult::Entries(entries)),
}
} else {
match query {
Query::SingleQuery(_) => Err(QueryExecutionError(
@ -57,27 +100,44 @@ pub fn execute(
let subquery_results = mq
.queries
.iter()
.map(|q| execute(connection, *q.clone()))
.map(|q| execute(connection, q))
.map(|q| {
if let Ok(InnerQueryResult::Entries(entries)) = q {
Ok(entries)
} else {
Err(QueryExecutionError(
"Multiquery must be composed of pattern queries.".to_string(),
))
}
})
.collect::<Result<Vec<Vec<Entry>>, QueryExecutionError>>()?;
match mq.qualifier {
QueryQualifier::Not => unreachable!(),
QueryQualifier::And => Ok(subquery_results
.into_iter()
.reduce(|acc, cur| {
acc.into_iter()
.filter(|e| {
cur.iter().map(|e| &e.identity).any(|x| x == &e.identity)
})
.collect()
})
.unwrap()), // TODO
QueryQualifier::Or => Ok(subquery_results.into_iter().flatten().collect()),
QueryQualifier::And => Ok(InnerQueryResult::Entries(
subquery_results
.into_iter()
.reduce(|acc, cur| {
acc.into_iter()
.filter(|e| {
cur.iter()
.map(|e| &e.identity)
.any(|x| x == &e.identity)
})
.collect()
})
.unwrap(),
)), // TODO
QueryQualifier::Or => Ok(InnerQueryResult::Entries(
subquery_results.into_iter().flatten().collect(),
)),
QueryQualifier::Join => {
let pattern_queries = mq
.queries
.clone()
.into_iter()
.map(|q| match *q {
Query::SingleQuery(QueryPart::Matches(pq)) => Some(pq),
Query::SingleQuery(QueryPart::Matches(pq)) => Some(pq.clone()),
_ => None,
})
.collect::<Option<Vec<_>>>();
@ -109,7 +169,9 @@ pub fn execute(
})
.unwrap(); // TODO
Ok(joined.into_iter().map(|ev| ev.entry).collect())
Ok(InnerQueryResult::Entries(
joined.into_iter().map(|ev| ev.entry).collect(),
))
} else {
Err(QueryExecutionError(
"Cannot join on non-atomic queries.".into(),
@ -132,18 +194,18 @@ impl EntryWithVars {
pub fn new(query: &PatternQuery, entry: Entry) -> Self {
let mut vars = HashMap::new();
if let QueryComponent::Variable(Some(var_name)) = &query.entity {
if let PatternQueryComponent::Variable(Some(var_name)) = &query.entity {
vars.insert(
var_name.clone(),
upend_base::hash::b58_encode(&entry.entity),
);
}
if let QueryComponent::Variable(Some(var_name)) = &query.attribute {
if let PatternQueryComponent::Variable(Some(var_name)) = &query.attribute {
vars.insert(var_name.clone(), entry.attribute.clone());
}
if let QueryComponent::Variable(Some(var_name)) = &query.value {
if let PatternQueryComponent::Variable(Some(var_name)) = &query.value {
if let Some(value_str) = &entry.value_str {
vars.insert(var_name.clone(), value_str.clone());
}
@ -164,38 +226,38 @@ fn to_sqlite_predicates(query: Query) -> Result<SqlResult, QueryExecutionError>
let mut subqueries: Vec<Box<SqlPredicate>> = vec![];
match &eq.entity {
QueryComponent::Exact(q_entity) => {
PatternQueryComponent::Exact(q_entity) => {
subqueries.push(Box::new(data::entity.eq(q_entity.encode().map_err(
|e| QueryExecutionError(format!("failed producing sql: {e}")),
)?)))
}
QueryComponent::In(q_entities) => {
PatternQueryComponent::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(|e| {
QueryExecutionError(format!("failed producing sql: {e}"))
})?)))
}
QueryComponent::Contains(q_entity) => subqueries.push(Box::new(
PatternQueryComponent::Contains(q_entity) => subqueries.push(Box::new(
data::entity_searchable.like(format!("%{}%", q_entity)),
)),
QueryComponent::Variable(_) => {}
PatternQueryComponent::Variable(_) | PatternQueryComponent::Discard => {}
};
match &eq.attribute {
QueryComponent::Exact(q_attribute) => {
PatternQueryComponent::Exact(q_attribute) => {
subqueries.push(Box::new(data::attribute.eq(q_attribute.0.clone())))
}
QueryComponent::In(q_attributes) => subqueries.push(Box::new(
PatternQueryComponent::In(q_attributes) => subqueries.push(Box::new(
data::attribute.eq_any(q_attributes.iter().map(|a| &a.0).cloned()),
)),
QueryComponent::Contains(q_attribute) => subqueries
PatternQueryComponent::Contains(q_attribute) => subqueries
.push(Box::new(data::attribute.like(format!("%{}%", q_attribute)))),
QueryComponent::Variable(_) => {}
PatternQueryComponent::Variable(_) | PatternQueryComponent::Discard => {}
};
match &eq.value {
QueryComponent::Exact(q_value) => match q_value {
PatternQueryComponent::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().map_err(|e| {
@ -203,7 +265,7 @@ fn to_sqlite_predicates(query: Query) -> Result<SqlResult, QueryExecutionError>
})?,
))),
},
QueryComponent::In(q_values) => {
PatternQueryComponent::In(q_values) => {
let first = q_values.first().ok_or_else(|| {
QueryExecutionError(
"Malformed expression: Inner value cannot be empty.".into(),
@ -251,10 +313,10 @@ fn to_sqlite_predicates(query: Query) -> Result<SqlResult, QueryExecutionError>
)),
}
}
QueryComponent::Contains(q_value) => {
PatternQueryComponent::Contains(q_value) => {
subqueries.push(Box::new(data::value_str.like(format!("S%{}%", q_value))))
}
QueryComponent::Variable(_) => {}
PatternQueryComponent::Variable(_) | PatternQueryComponent::Discard => {}
};
match subqueries.len() {

View File

@ -10,7 +10,7 @@ use upend_base::addressing::Address;
use upend_base::constants::ATTR_LABEL;
use upend_base::constants::{ATTR_IN, HIER_ROOT_ADDR, HIER_ROOT_INVARIANT};
use upend_base::entry::Entry;
use upend_base::lang::{PatternQuery, Query, QueryComponent, QueryPart};
use upend_base::lang::{PatternQuery, PatternQueryComponent, Query, QueryPart};
use super::UpEndConnection;
@ -77,15 +77,11 @@ impl std::fmt::Display for UHierPath {
}
pub fn list_roots(connection: &UpEndConnection) -> Result<Vec<Address>> {
Ok(connection
.query(Query::SingleQuery(QueryPart::Matches(PatternQuery {
entity: QueryComponent::Variable(None),
attribute: QueryComponent::Exact(ATTR_IN.into()),
value: QueryComponent::Exact((*HIER_ROOT_ADDR).clone().into()),
})))?
.into_iter()
.map(|e| e.entity)
.collect())
connection.query::<Vec<Address>>(Query::SingleQuery(QueryPart::Matches(PatternQuery {
entity: PatternQueryComponent::Variable(None),
attribute: PatternQueryComponent::Exact(ATTR_IN.into()),
value: PatternQueryComponent::Exact((*HIER_ROOT_ADDR).clone().into()),
})))
}
lazy_static! {
@ -108,29 +104,26 @@ pub fn fetch_or_create_dir(
_lock = FETCH_CREATE_LOCK.lock().unwrap();
}
let matching_directories = connection
.query(Query::SingleQuery(QueryPart::Matches(PatternQuery {
entity: QueryComponent::Variable(None),
attribute: QueryComponent::Exact(ATTR_LABEL.into()),
value: QueryComponent::Exact(String::from(directory.clone()).into()),
})))?
.into_iter()
.map(|e: Entry| e.entity);
let matching_directories =
connection.query::<Vec<Address>>(Query::SingleQuery(QueryPart::Matches(PatternQuery {
entity: PatternQueryComponent::Variable(None),
attribute: PatternQueryComponent::Exact(ATTR_LABEL.into()),
value: PatternQueryComponent::Exact(String::from(directory.clone()).into()),
})))?;
let parent_has: Vec<Address> = match parent.clone() {
Some(parent) => connection
.query(Query::SingleQuery(QueryPart::Matches(PatternQuery {
entity: QueryComponent::Variable(None),
attribute: QueryComponent::Exact(ATTR_IN.into()),
value: QueryComponent::Exact(parent.into()),
})))?
.into_iter()
.map(|e| e.entity)
.collect(),
Some(parent) => connection.query::<Vec<Address>>(Query::SingleQuery(
QueryPart::Matches(PatternQuery {
entity: PatternQueryComponent::Variable(None),
attribute: PatternQueryComponent::Exact(ATTR_IN.into()),
value: PatternQueryComponent::Exact(parent.into()),
}),
))?,
None => list_roots(connection)?,
};
let valid_directories: Vec<Address> = matching_directories
.into_iter()
.filter(|a| parent_has.contains(a))
.collect();

View File

@ -30,9 +30,10 @@ use diesel::prelude::*;
use diesel::r2d2::{self, ConnectionManager};
use diesel::result::{DatabaseErrorKind, Error};
use diesel::sqlite::SqliteConnection;
use engine::InnerQueryResult;
use hierarchies::initialize_hier;
use shadow_rs::is_release;
use std::convert::TryFrom;
use std::convert::{TryFrom, TryInto};
use std::fs;
use std::path::{Path, PathBuf};
use std::sync::{Arc, Mutex, RwLock};
@ -52,7 +53,7 @@ pub struct ConnectionOptions {
}
impl ConnectionOptions {
pub fn apply(&self, connection: &SqliteConnection) -> QueryResult<()> {
pub fn apply(&self, connection: &SqliteConnection) -> diesel::prelude::QueryResult<()> {
let _lock = self.mutex.lock().unwrap();
if let Some(duration) = self.busy_timeout {
@ -292,20 +293,20 @@ impl UpEndConnection {
Ok(diesel::delete(matches).execute(&conn)?)
}
pub fn query(&self, query: Query) -> Result<Vec<Entry>> {
pub fn query<T>(&self, query: Query) -> Result<T>
where
T: TryFrom<InnerQueryResult>,
<T as TryFrom<InnerQueryResult>>::Error: std::fmt::Display,
{
trace!("Querying: {:?}", query);
let _lock = self.lock.read().unwrap();
let conn = self.pool.get()?;
let entries = execute(&conn, query)?;
let entries = entries
.iter()
.map(Entry::try_from)
.filter_map(Result::ok)
.collect();
Ok(entries)
let result = execute(&conn, &query)?;
result
.try_into()
.map_err(|err| anyhow!("Could not convert to requested result type: {err}"))
}
pub fn insert_entry(&self, entry: Entry) -> Result<Address> {
@ -358,22 +359,6 @@ impl UpEndConnection {
Ok(result)
}
#[deprecated]
pub fn get_all_attributes(&self) -> Result<Vec<String>> {
use crate::inner::schema::data::dsl::*;
let _lock = self.lock.read().unwrap();
let conn = self.pool.get()?;
let result = data
.select(attribute)
.distinct()
.order_by(attribute)
.load::<String>(&conn)?;
Ok(result)
}
pub fn get_stats(&self) -> Result<serde_json::Value> {
use crate::inner::schema::data::dsl::*;
let _lock = self.lock.read().unwrap();
@ -430,6 +415,74 @@ impl UpEndConnection {
}
}
pub enum QueryResult {
Entries(Vec<Entry>),
Entities(Vec<Address>),
Attributes(Vec<String>),
Values(Vec<EntryValue>),
}
impl TryFrom<InnerQueryResult> for QueryResult {
type Error = anyhow::Error;
fn try_from(inner_result: InnerQueryResult) -> std::result::Result<Self, Self::Error> {
let result = match inner_result {
InnerQueryResult::Entries(entries) => QueryResult::Entries(
entries
.iter()
.map(Entry::try_from)
.collect::<Result<Vec<Entry>, _>>()?,
),
InnerQueryResult::Entities(entities) => QueryResult::Entities(
entities
.iter()
.map(|buf| Address::decode(buf))
.collect::<Result<Vec<Address>, _>>()?,
),
InnerQueryResult::Attributes(attributes) => QueryResult::Attributes(attributes),
InnerQueryResult::Values(values) => QueryResult::Values(values),
};
Ok(result)
}
}
impl TryFrom<InnerQueryResult> for Vec<Address> {
type Error = anyhow::Error;
fn try_from(inner_result: InnerQueryResult) -> std::result::Result<Self, Self::Error> {
let result = match inner_result {
InnerQueryResult::Entries(entries) => entries
.into_iter()
.map(|e| Address::decode(&e.entity))
.collect::<Result<Vec<Address>, _>>()?,
InnerQueryResult::Entities(entities) => entities
.into_iter()
.map(|buf| Address::decode(&buf))
.collect::<Result<Vec<Address>, _>>()?,
_ => Err(anyhow!("Insufficient information in query."))?,
};
Ok(result)
}
}
impl TryFrom<InnerQueryResult> for Vec<Entry> {
type Error = anyhow::Error;
fn try_from(inner_result: InnerQueryResult) -> std::result::Result<Self, Self::Error> {
let result = match inner_result {
InnerQueryResult::Entries(entries) => entries
.iter()
.map(Entry::try_from)
.collect::<Result<Vec<Entry>, _>>()?,
_ => Err(anyhow!("Insufficient information in query."))?,
};
Ok(result)
}
}
#[cfg(test)]
mod test {
use upend_base::constants::ATTR_LABEL;
@ -471,7 +524,7 @@ mod test {
let query = format!(r#"(matches @{random_entity} ? ?)"#)
.parse()
.unwrap();
let result = connection.query(query).unwrap();
let result = connection.query::<Vec<Entry>>(query).unwrap();
assert_eq!(result.len(), 2);
let other_entity = Address::Uuid(uuid::Uuid::new_v4());
@ -481,36 +534,36 @@ mod test {
let query = format!(r#"(matches (in @{random_entity} @{other_entity}) ? ?)"#)
.parse()
.unwrap();
let result = connection.query(query).unwrap();
let result = connection.query::<Vec<Entry>>(query).unwrap();
assert_eq!(result.len(), 4);
let query = r#"(matches ? (in "FLAVOUR" "CHARGE") ?)"#.parse().unwrap();
let result = connection.query(query).unwrap();
let result = connection.query::<Vec<Entry>>(query).unwrap();
assert_eq!(result.len(), 2);
let query = format!(r#"(matches ? "{ATTR_LABEL}" (in "FOOBAR" "BAZQUX"))"#)
.parse()
.unwrap();
let result = connection.query(query).unwrap();
let result = connection.query::<Vec<Entry>>(query).unwrap();
assert_eq!(result.len(), 2);
let query = format!(r#"(matches ? "{ATTR_LABEL}" (contains "OOBA"))"#)
.parse()
.unwrap();
let result = connection.query(query).unwrap();
let result = connection.query::<Vec<Entry>>(query).unwrap();
assert_eq!(result.len(), 1);
let query = r#"(or (matches ? ? (contains "OOBA")) (matches ? (contains "HARGE") ?) )"#
.parse()
.unwrap();
let result = connection.query(query).unwrap();
let result = connection.query::<Vec<Entry>>(query).unwrap();
assert_eq!(result.len(), 2);
let query =
format!(r#"(and (matches ? ? (contains "OOBA")) (matches ? "{ATTR_LABEL}" ?) )"#)
.parse()
.unwrap();
let result = connection.query(query).unwrap();
let result = connection.query::<Vec<Entry>>(query).unwrap();
assert_eq!(result.len(), 1);
let query = format!(
@ -524,7 +577,7 @@ mod test {
)
.parse()
.unwrap();
let result = connection.query(query).unwrap();
let result = connection.query::<Vec<Entry>>(query).unwrap();
assert_eq!(result.len(), 1);
let query = format!(
@ -535,7 +588,7 @@ mod test {
)
.parse()
.unwrap();
let result = connection.query(query).unwrap();
let result = connection.query::<Vec<Entry>>(query).unwrap();
assert_eq!(result.len(), 1);
assert_eq!(result[0].value, "STRANGE".into());
}