use crate::types::error::CruxError;
use edn_rs::Serialize;
use std::collections::BTreeSet;
use std::convert::TryFrom;
#[derive(Clone, Debug)]
pub struct Query {
find: Find,
where_: Option<Where>,
args: Option<Args>,
order_by: Option<OrderBy>,
limit: Option<Limit>,
offset: Option<Offset>,
full_results: bool,
}
#[derive(Clone, Debug)]
struct Find(Vec<String>);
#[derive(Clone, Debug)]
struct Where(Vec<String>);
#[derive(Clone, Debug)]
struct Args(Vec<String>);
#[derive(Clone, Debug)]
struct OrderBy(Vec<String>);
#[derive(Clone, Debug)]
struct Limit(usize);
#[derive(Clone, Debug)]
struct Offset(usize);
impl Query {
pub fn find(find: Vec<&str>) -> Result<Self, CruxError> {
if find.iter().any(|e| !e.starts_with("?")) {
let error = find.iter().find(|e| !e.starts_with("?")).unwrap();
return Err(CruxError::QueryFormatError(format!(
"All elements of find clause should start with '?', element '{}' doesn't conform",
error
)));
}
Ok(Self {
find: Find {
0: find.into_iter().map(String::from).collect::<Vec<String>>(),
},
where_: None,
args: None,
order_by: None,
limit: None,
offset: None,
full_results: false,
})
}
pub fn where_clause(mut self, where_: Vec<&str>) -> Result<Self, CruxError> {
if self.find.0.iter().any(|e| !where_.join(" ").contains(e)) {
let error = self
.find
.0
.iter()
.find(|e| !where_.join(" ").contains(*e))
.unwrap();
return Err(CruxError::QueryFormatError(format!(
"Not all element of find, {}, are present in the where clause, {} is missing",
self.find.0.join(", "),
error
)));
}
let w = where_
.iter()
.map(|s| s.replace("[", "").replace("]", ""))
.collect::<Vec<String>>();
self.where_ = Some(Where { 0: w });
Ok(self)
}
pub fn args(mut self, args: Vec<&str>) -> Result<Self, CruxError> {
let where_ = self.where_.clone().unwrap().0.join(" ");
self.args = Some(Args::try_from((args, where_))?);
Ok(self)
}
pub fn order_by(mut self, order_by: Vec<&str>) -> Result<Self, CruxError> {
let f = self.find.0.join(" ");
if !order_by
.iter()
.map(|e| e.split(" ").collect::<Vec<&str>>())
.map(|e| e[1])
.all(|e| e.to_lowercase() == ":asc" || e.to_lowercase() == ":desc")
{
return Err(CruxError::QueryFormatError(
"Order element should be ':asc' or ':desc'".to_string(),
));
}
if !order_by
.iter()
.map(|e| e.split(" ").collect::<Vec<&str>>())
.map(|e| e[0])
.all(|e| f.contains(e))
{
let error = order_by
.iter()
.map(|e| e.split(" ").collect::<Vec<&str>>())
.map(|e| e[0])
.find(|e| !f.contains(e))
.unwrap();
return Err(CruxError::QueryFormatError(format!(
"All elements to be ordered should be present in find clause, {} not present",
error
)));
}
let o = order_by
.iter()
.map(|s| s.replace("[", "").replace("]", ""))
.collect::<Vec<String>>();
self.order_by = Some(OrderBy { 0: o });
Ok(self)
}
pub fn limit(mut self, limit: usize) -> Self {
self.limit = Some(Limit { 0: limit });
self
}
pub fn offset(mut self, offset: usize) -> Self {
self.offset = Some(Offset { 0: offset });
self
}
pub fn with_full_results(mut self) -> Self {
self.full_results = true;
self
}
pub fn build(self) -> Result<Self, CruxError> {
if self.where_.is_none() {
Err(CruxError::QueryFormatError(String::from(
"Where clause is required",
)))
} else {
Ok(self)
}
}
}
impl Serialize for Query {
fn serialize(self) -> String {
let mut q = String::from("{:query\n {");
q.push_str(&edn_rs::to_string(self.find));
q.push_str(&edn_rs::to_string(self.where_.unwrap()));
if self.args.is_some() {
q.push_str(&edn_rs::to_string(self.args.unwrap()));
}
if self.order_by.is_some() {
q.push_str(&edn_rs::to_string(self.order_by.unwrap()));
}
if self.limit.is_some() {
q.push_str(&edn_rs::to_string(self.limit.unwrap()));
}
if self.offset.is_some() {
q.push_str(&edn_rs::to_string(self.offset.unwrap()));
}
if self.full_results == true {
q.push_str(" :full-results? true\n")
}
q.push_str("}}");
q
}
}
impl Serialize for Find {
fn serialize(self) -> String {
let mut q = String::from(":find [");
q.push_str(&self.0.join(" "));
q.push_str("]\n");
q
}
}
impl Serialize for Where {
fn serialize(self) -> String {
let mut q = String::from(":where [[");
q.push_str(&self.0.join("]\n["));
q.push_str("]]\n");
q
}
}
impl Serialize for Args {
fn serialize(self) -> String {
let mut q = String::from(":args [{");
q.push_str(&self.0.join("}\n{"));
q.push_str("}]\n");
q
}
}
impl Serialize for OrderBy {
fn serialize(self) -> String {
let mut q = String::from(":order-by [[");
q.push_str(&self.0.join("]\n["));
q.push_str("]]\n");
q
}
}
impl Serialize for Limit {
fn serialize(self) -> String {
let mut q = String::from(":limit ");
q.push_str(&self.0.to_string());
q.push_str("\n");
q
}
}
impl Serialize for Offset {
fn serialize(self) -> String {
let mut q = String::from(":offset ");
q.push_str(&self.0.to_string());
q.push_str("\n");
q
}
}
type RawArgsWithWhere<'a> = (Vec<&'a str>, String);
impl TryFrom<RawArgsWithWhere<'_>> for Args {
type Error = CruxError;
fn try_from(value: RawArgsWithWhere) -> Result<Self, Self::Error> {
let (args, where_) = value;
let args_key_set = args_key_bset(&args);
let all_elements_in_where = args_key_set.iter().any(|e| !where_.contains(e));
let has_question = args_key_set.iter().any(|e| !e.starts_with("?"));
match (all_elements_in_where, has_question) {
(true, false) => Err(CruxError::QueryFormatError("All elements should be present in where clause".to_string())),
(false, true) => Err(CruxError::QueryFormatError("All elements should start with '?'".to_string())),
(true, true) => Err(CruxError::QueryFormatError("All elements should be present in where clause and all elements should start with '?'".to_string())),
(false, false) => Ok(Args{0: args.iter().map(|s| s.replace("{", "").replace("}", "")).collect::<Vec<String>>()}),
}
}
}
fn args_key_bset(args: &Vec<&str>) -> BTreeSet<String> {
let args_without_inst = args.join(" ").replace("#inst", "");
args_without_inst
.split(" ")
.filter(|i| !i.is_empty())
.enumerate()
.filter(|(i, _)| i % 2 == 0)
.map(|(_, s)| s.to_owned())
.collect::<BTreeSet<String>>()
}
#[cfg(test)]
mod test {
use super::Query;
use crate::client::Crux;
#[test]
fn query_with_find_and_where() {
let expected =
"{:query\n {:find [?p1]\n:where [[?p1 :first-name n]\n[?p1 :last-name \"Jorge\"]]\n}}";
let q = Query::find(vec!["?p1"])
.unwrap()
.where_clause(vec!["?p1 :first-name n", "?p1 :last-name \"Jorge\""])
.unwrap()
.build();
assert_eq!(edn_rs::to_string(q.unwrap()), expected);
}
#[test]
#[should_panic(expected = "Where clause is required")]
fn expect_query_format_error() {
let client = Crux::new("", "").http_client();
let query_where_is_none = Query::find(vec!["?p1", "?n"]).unwrap().build().unwrap();
let _ = client.query(query_where_is_none).unwrap();
}
#[test]
fn query_with_order() {
let expected =
"{:query\n {:find [?p1]\n:where [[?p1 :first-name n]\n[?p1 :last-name \"Jorge\"]]\n:order-by [[?p1 :asc]]\n}}";
let q = Query::find(vec!["?p1"])
.unwrap()
.where_clause(vec!["?p1 :first-name n", "?p1 :last-name \"Jorge\""])
.unwrap()
.order_by(vec!["?p1 :asc"])
.unwrap()
.build();
assert_eq!(edn_rs::to_string(q.unwrap()), expected);
}
#[test]
fn query_with_args() {
let expected =
"{:query\n {:find [?p1]\n:where [[?p1 :first-name n]\n[?p1 :last-name ?n]]\n:args [{?n \"Jorge\"}]\n}}";
let q = Query::find(vec!["?p1"])
.unwrap()
.where_clause(vec!["?p1 :first-name n", "?p1 :last-name ?n"])
.unwrap()
.args(vec!["?n \"Jorge\""])
.unwrap()
.build();
assert_eq!(edn_rs::to_string(q.unwrap()), expected);
}
#[test]
fn query_with_limit_and_offset() {
let expected =
"{:query\n {:find [?p1]\n:where [[?p1 :first-name n]\n[?p1 :last-name n]]\n:limit 5\n:offset 10\n}}";
let q = Query::find(vec!["?p1"])
.unwrap()
.where_clause(vec!["?p1 :first-name n", "?p1 :last-name n"])
.unwrap()
.limit(5)
.offset(10)
.build();
assert_eq!(edn_rs::to_string(q.unwrap()), expected);
}
#[test]
fn full_query() {
let expected =
"{:query\n {:find [?p1]\n:where [[?p1 :first-name n]\n[?p1 :last-name ?n]]\n:args [{?n \"Jorge\"}]\n:order-by [[?p1 :Asc]]\n:limit 5\n:offset 10\n}}";
let q = Query::find(vec!["?p1"])
.unwrap()
.where_clause(vec!["?p1 :first-name n", "?p1 :last-name ?n"])
.unwrap()
.args(vec!["?n \"Jorge\""])
.unwrap()
.order_by(vec!["?p1 :Asc"])
.unwrap()
.limit(5)
.offset(10)
.build();
assert_eq!(edn_rs::to_string(q.unwrap()), expected);
}
#[test]
#[should_panic(
expected = "Not all element of find, ?p1, ?n, ?s, are present in the where clause, ?n is missing"
)]
fn where_query_format_error() {
let _query = Query::find(vec!["?p1", "?n", "?s"])
.unwrap()
.where_clause(vec!["?p1 :name ?g", "?p1 :is-sql ?s", "?p1 :is-sql true"])
.unwrap()
.build();
}
#[test]
#[should_panic(expected = "Order element should be \\\':asc\\\' or \\\':desc\\\'")]
fn order_should_panic_for_unknow_order_element() {
let _query = Query::find(vec!["?p1", "?n", "?s"])
.unwrap()
.where_clause(vec!["?p1 :name ?n", "?p1 :is-sql ?s", "?p1 :is-sql true"])
.unwrap()
.order_by(vec!["?p1 :asc", "?n :desc", "?s :eq"])
.unwrap()
.build();
}
#[test]
#[should_panic(
expected = "All elements to be ordered should be present in find clause, ?g not present"
)]
fn order_element_should_be_present_in_find_clause() {
let _query = Query::find(vec!["?p1", "?n", "?s"])
.unwrap()
.where_clause(vec!["?p1 :name ?n", "?p1 :is-sql ?s", "?p1 :is-sql true"])
.unwrap()
.order_by(vec!["?p1 :asc", "?n :desc", "?g :asc"])
.unwrap()
.build();
}
#[test]
#[should_panic(expected = "All elements should be present in where clause")]
fn all_args_present_in_where() {
let _query = Query::find(vec!["?p1", "?n"])
.unwrap()
.where_clause(vec!["?p1 :name ?n", "?p1 :is-sql ?s", "?p1 :is-sql true"])
.unwrap()
.args(vec!["?s true ?x 1243"])
.unwrap()
.build();
}
#[test]
#[should_panic(expected = "All elements should start with \\\'?\\\'")]
fn all_args_should_start_with_question() {
let _query = Query::find(vec!["?p1", "?n"])
.unwrap()
.where_clause(vec!["?p1 :name ?n", "?p1 :is-sql s", "?p1 :is-sql true"])
.unwrap()
.args(vec!["s true"])
.unwrap()
.build();
}
#[test]
fn query_with_full_results() {
let expected =
"{:query\n {:find [?p1]\n:where [[?p1 :first-name n]\n[?p1 :last-name \"Jorge\"]]\n :full-results? true\n}}";
let q = Query::find(vec!["?p1"])
.unwrap()
.where_clause(vec!["?p1 :first-name n", "?p1 :last-name \"Jorge\""])
.unwrap()
.with_full_results()
.build();
assert_eq!(edn_rs::to_string(q.unwrap()), expected);
}
}