parent
7a9aafac5a
commit
d0a2c545e8
|
@ -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,
|
||||
|
|
100
base/src/lang.rs
100
base/src/lang.rs
|
@ -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())
|
||||
}))
|
||||
);
|
||||
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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()?,
|
||||
)?
|
||||
|
|
|
@ -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:.*}")]
|
||||
|
|
|
@ -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)
|
||||
|
|
130
db/src/engine.rs
130
db/src/engine.rs
|
@ -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() {
|
||||
|
|
|
@ -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();
|
||||
|
||||
|
|
125
db/src/lib.rs
125
db/src/lib.rs
|
@ -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());
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue