add upend query lang -> sqlite conversion; first working prototype
parent
f950e02113
commit
98a9c88173
|
@ -1304,6 +1304,12 @@ dependencies = [
|
|||
"version_check 0.1.5",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nonempty"
|
||||
version = "0.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7fa586da3e43cc7df44aae0e21ed2e743218b876de3f38035683d30bd8a3828e"
|
||||
|
||||
[[package]]
|
||||
name = "num-integer"
|
||||
version = "0.1.43"
|
||||
|
@ -2036,6 +2042,7 @@ dependencies = [
|
|||
"lexpr",
|
||||
"libsqlite3-sys",
|
||||
"log",
|
||||
"nonempty",
|
||||
"rayon",
|
||||
"serde",
|
||||
"serde_json",
|
||||
|
|
|
@ -42,3 +42,5 @@ walkdir = "2"
|
|||
dotenv = "0.15.0"
|
||||
webbrowser = "0.5.5"
|
||||
xdg = "^2.1"
|
||||
|
||||
nonempty = "0.6.0"
|
||||
|
|
133
src/database.rs
133
src/database.rs
|
@ -1,15 +1,19 @@
|
|||
use crate::addressing::Address;
|
||||
use crate::hash::{decode, hash, Hash, Hashable};
|
||||
use crate::models;
|
||||
use crate::schema::data;
|
||||
use crate::util::LoggerSink;
|
||||
use anyhow::{anyhow, Result};
|
||||
use diesel::debug_query;
|
||||
use diesel::expression::operators::{And, Or};
|
||||
use diesel::prelude::*;
|
||||
use diesel::r2d2::{self, ConnectionManager};
|
||||
use diesel::sql_types::Bool;
|
||||
use diesel::sqlite::{Sqlite, SqliteConnection};
|
||||
use lexpr::value::Value::Symbol;
|
||||
use lexpr::Value::Cons;
|
||||
use log::{debug, trace};
|
||||
use nonempty::NonEmpty;
|
||||
use serde::export::Formatter;
|
||||
use serde_json::json;
|
||||
use std::borrow::Borrow;
|
||||
|
@ -288,7 +292,7 @@ pub enum QueryQualifier {
|
|||
#[derive(Debug)]
|
||||
pub struct MultiQuery {
|
||||
pub qualifier: QueryQualifier,
|
||||
pub queries: Vec<Box<Query>>,
|
||||
pub queries: NonEmpty<Box<Query>>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
|
@ -339,18 +343,19 @@ impl Query {
|
|||
}
|
||||
"and" | "or" => {
|
||||
let (cons_vec, _) = value.clone().into_vec();
|
||||
if let Some(split) = cons_vec.split_first() {
|
||||
let sub_expressions = split.1;
|
||||
let values: Result<Vec<Query>> = sub_expressions
|
||||
.into_iter()
|
||||
.map(|value| Query::from_sexp(value))
|
||||
.collect();
|
||||
let sub_expressions = &cons_vec[1..];
|
||||
let values: Result<Vec<Box<Query>>> = sub_expressions
|
||||
.into_iter()
|
||||
.map(|value| Ok(Box::new(Query::from_sexp(value)?)))
|
||||
.collect();
|
||||
|
||||
if let Some(queries) = NonEmpty::from_vec(values?) {
|
||||
Ok(Query::MultiQuery(MultiQuery {
|
||||
qualifier: match symbol.borrow() {
|
||||
"and" => QueryQualifier::AND,
|
||||
_ => QueryQualifier::OR,
|
||||
},
|
||||
queries: values?.into_iter().map(|sq| Box::new(sq)).collect(),
|
||||
queries,
|
||||
}))
|
||||
} else {
|
||||
Err(anyhow!(
|
||||
|
@ -438,6 +443,116 @@ impl Query {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn query<C: Connection<Backend = Sqlite>>(connection: &C, query: Query) -> Result<Vec<Entry>> {
|
||||
use crate::schema::data::dsl::*;
|
||||
|
||||
let mut db_query = data.into_boxed();
|
||||
|
||||
if let Some(predicate) = query_to_sqlite(&query)? {
|
||||
db_query = db_query.filter(predicate);
|
||||
}
|
||||
|
||||
trace!("Querying: {}", debug_query(&db_query));
|
||||
|
||||
let matches = db_query.load::<models::Entry>(connection)?;
|
||||
|
||||
let entries = matches
|
||||
.into_iter()
|
||||
.map(Entry::try_from)
|
||||
.filter_map(Result::ok)
|
||||
.collect();
|
||||
|
||||
Ok(entries)
|
||||
}
|
||||
type Predicate = dyn BoxableExpression<data::table, Sqlite, SqlType = Bool>;
|
||||
|
||||
fn query_to_sqlite(query: &Query) -> Result<Option<Box<Predicate>>> {
|
||||
match query {
|
||||
Query::SingleQuery(qp) => match qp {
|
||||
QueryPart::Matches(eq) => {
|
||||
let mut subqueries: Vec<Box<Predicate>> = vec![];
|
||||
|
||||
match &eq.entity {
|
||||
QueryComponent::Exact(q_target) => {
|
||||
subqueries.push(Box::new(data::target.eq(q_target.encode()?)))
|
||||
}
|
||||
QueryComponent::In(q_targets) => {
|
||||
let targets: Result<Vec<_>, _> =
|
||||
q_targets.into_iter().map(|t| t.encode()).collect();
|
||||
subqueries.push(Box::new(data::target.eq_any(targets?)))
|
||||
}
|
||||
QueryComponent::Any => {}
|
||||
};
|
||||
|
||||
match &eq.attribute {
|
||||
QueryComponent::Exact(q_key) => {
|
||||
subqueries.push(Box::new(data::key.eq(q_key.clone())))
|
||||
}
|
||||
QueryComponent::In(q_keys) => {
|
||||
subqueries.push(Box::new(data::key.eq_any(q_keys.clone())))
|
||||
}
|
||||
QueryComponent::Any => {}
|
||||
};
|
||||
|
||||
match &eq.value {
|
||||
QueryComponent::Exact(q_value) => {
|
||||
subqueries.push(Box::new(data::value.eq(q_value.to_str()?)))
|
||||
}
|
||||
QueryComponent::In(q_values) => {
|
||||
let values: Result<Vec<_>, _> =
|
||||
q_values.into_iter().map(|v| v.to_str()).collect();
|
||||
subqueries.push(Box::new(data::value.eq_any(values?)))
|
||||
}
|
||||
QueryComponent::Any => {}
|
||||
};
|
||||
|
||||
match subqueries.len() {
|
||||
0 => Ok(None),
|
||||
1 => Ok(Some(subqueries.remove(0))),
|
||||
_ => {
|
||||
let mut result: Box<And<Box<Predicate>, Box<Predicate>>> =
|
||||
Box::new(And::new(subqueries.remove(0), subqueries.remove(0)));
|
||||
while subqueries.len() > 0 {
|
||||
result = Box::new(And::new(result, subqueries.remove(0)));
|
||||
}
|
||||
Ok(Some(Box::new(result)))
|
||||
}
|
||||
}
|
||||
}
|
||||
QueryPart::Type(_) => unimplemented!("Type queries are not yet implemented."),
|
||||
},
|
||||
Query::MultiQuery(mq) => {
|
||||
// TODO: Handle the "OR TRUE" case as well
|
||||
let subqueries: Result<Vec<Option<Box<Predicate>>>> =
|
||||
mq.queries.iter().map(|sq| query_to_sqlite(sq)).collect();
|
||||
let mut subqueries: Vec<Box<Predicate>> =
|
||||
subqueries?.into_iter().filter_map(|sq| sq).collect();
|
||||
match subqueries.len() {
|
||||
0 => Ok(None),
|
||||
1 => Ok(Some(subqueries.remove(0))),
|
||||
_ => match mq.qualifier {
|
||||
QueryQualifier::AND => {
|
||||
let mut result: Box<And<Box<Predicate>, Box<Predicate>>> =
|
||||
Box::new(And::new(subqueries.remove(0), subqueries.remove(0)));
|
||||
while subqueries.len() > 0 {
|
||||
result = Box::new(And::new(result, subqueries.remove(0)));
|
||||
}
|
||||
Ok(Some(Box::new(result)))
|
||||
}
|
||||
QueryQualifier::OR => {
|
||||
let mut result: Box<Or<Box<Predicate>, Box<Predicate>>> =
|
||||
Box::new(Or::new(subqueries.remove(0), subqueries.remove(0)));
|
||||
while subqueries.len() > 0 {
|
||||
result = Box::new(Or::new(result, subqueries.remove(0)));
|
||||
}
|
||||
Ok(Some(Box::new(result)))
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn query_entries<C: Connection<Backend = Sqlite>>(
|
||||
connection: &C,
|
||||
entry_query: EntryQuery,
|
||||
|
@ -495,8 +610,6 @@ pub fn insert_entry<C: Connection<Backend = Sqlite>>(
|
|||
connection: &C,
|
||||
entry: InnerEntry,
|
||||
) -> Result<usize> {
|
||||
use crate::schema::data;
|
||||
|
||||
debug!("Inserting: {}", entry);
|
||||
|
||||
let insert_entry = models::Entry {
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
use crate::addressing::Address;
|
||||
use crate::database::{remove_object, retrieve_file, retrieve_object, DbPool, Entry, Query};
|
||||
use crate::database::{query, remove_object, retrieve_file, retrieve_object, DbPool, Entry, Query};
|
||||
use crate::filesystem::{list_directory, lookup_by_filename, UPath};
|
||||
use crate::hash::{decode, encode};
|
||||
use actix_files::NamedFile;
|
||||
|
@ -128,10 +128,16 @@ pub async fn get_query(
|
|||
) -> Result<HttpResponse, Error> {
|
||||
let connection = state.db_pool.get().map_err(ErrorInternalServerError)?;
|
||||
|
||||
let result = lexpr::from_str(info.query.as_str()).map_err(ErrorInternalServerError)?;
|
||||
let query = Query::from_sexp(&result).map_err(ErrorInternalServerError)?;
|
||||
let sexp = lexpr::from_str(info.query.as_str()).map_err(ErrorInternalServerError)?;
|
||||
let in_query = Query::from_sexp(&sexp).map_err(ErrorInternalServerError)?;
|
||||
let result = query(&connection, in_query).map_err(ErrorInternalServerError)?;
|
||||
|
||||
Ok(HttpResponse::Ok().json(format!("{:?}", query)))
|
||||
Ok(HttpResponse::Ok().json(
|
||||
result
|
||||
.iter()
|
||||
.map(Entry::as_json)
|
||||
.collect::<serde_json::Value>(),
|
||||
))
|
||||
}
|
||||
|
||||
#[post("/api/refresh")]
|
||||
|
|
Loading…
Reference in New Issue