use std::fmt::{self, Display, Formatter};
#[cfg(feature = "open-api")]
use paperclip::actix::Apiv2Schema;
use serde::{Deserialize, Serialize};
use crate::query::graph_query::{GraphQueryData, GraphQueryDirection};
use crate::query::operations::{AqlOperation, OperationContainer};
use crate::query::query_id_helper::get_str_identifier;
use crate::query::query_result::JsonQueryResult;
use crate::query::{string_from_array, Filter, OptionalQueryString};
use crate::{DatabaseAccess, ServiceError};
#[macro_export]
macro_rules! query {
($collection:expr) => {
$crate::query::Query::new($collection)
};
}
#[derive(Clone, Debug, Serialize, Deserialize)]
#[cfg_attr(feature = "open-api", derive(Apiv2Schema))]
pub enum SortDirection {
Asc,
Desc,
}
impl Display for SortDirection {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
write!(
f,
"{}",
match self {
SortDirection::Asc => "ASC",
SortDirection::Desc => "DESC",
}
)
}
}
#[derive(Clone, Debug)]
pub struct Query {
with_collections: OptionalQueryString,
collection: String,
graph_data: Option<GraphQueryData>,
operations: OperationContainer,
distinct: bool,
sub_query: Option<String>,
item_identifier: usize,
}
impl Query {
pub fn new(collection_name: &str) -> Self {
Self {
with_collections: OptionalQueryString(None),
collection: String::from(collection_name),
graph_data: None,
operations: OperationContainer(vec![]),
distinct: false,
sub_query: None,
item_identifier: 0,
}
}
pub fn outbound(min: u16, max: u16, edge_collection: &str, vertex: &str) -> Self {
Self {
graph_data: Some(GraphQueryData {
direction: GraphQueryDirection::Outbound,
start_vertex: format!(r#"'{}'"#, vertex),
min,
max,
named_graph: false,
}),
..Self::new(edge_collection)
}
}
pub fn outbound_graph(min: u16, max: u16, named_graph: &str, vertex: &str) -> Self {
Self {
graph_data: Some(GraphQueryData {
direction: GraphQueryDirection::Outbound,
start_vertex: format!(r#"'{}'"#, vertex),
min,
max,
named_graph: true,
}),
..Self::new(named_graph)
}
}
pub fn any(min: u16, max: u16, edge_collection: &str, vertex: &str) -> Self {
Self {
graph_data: Some(GraphQueryData {
direction: GraphQueryDirection::Any,
start_vertex: format!(r#"'{}'"#, vertex),
min,
max,
named_graph: false,
}),
..Self::new(edge_collection)
}
}
pub fn any_graph(min: u16, max: u16, named_graph: &str, vertex: &str) -> Self {
Self {
graph_data: Some(GraphQueryData {
direction: GraphQueryDirection::Any,
start_vertex: format!(r#"'{}'"#, vertex),
min,
max,
named_graph: true,
}),
..Self::new(named_graph)
}
}
pub fn inbound(min: u16, max: u16, edge_collection: &str, vertex: &str) -> Self {
Self {
graph_data: Some(GraphQueryData {
direction: GraphQueryDirection::Inbound,
start_vertex: format!(r#"'{}'"#, vertex),
min,
max,
named_graph: false,
}),
..Self::new(edge_collection)
}
}
pub fn inbound_graph(min: u16, max: u16, named_graph: &str, vertex: &str) -> Self {
Self {
graph_data: Some(GraphQueryData {
direction: GraphQueryDirection::Inbound,
start_vertex: format!(r#"'{}'"#, vertex),
min,
max,
named_graph: true,
}),
..Self::new(named_graph)
}
}
fn join(
mut self,
min: u16,
max: u16,
mut query: Query,
direction: GraphQueryDirection,
named_graph: bool,
) -> Self {
self.item_identifier = query.item_identifier + 1;
query.graph_data = Some(GraphQueryData {
direction,
start_vertex: get_str_identifier(self.item_identifier),
min,
max,
named_graph,
});
self.sub_query = Some(query.to_aql());
self
}
pub fn join_outbound(self, min: u16, max: u16, named_graph: bool, query: Query) -> Self {
self.join(min, max, query, GraphQueryDirection::Outbound, named_graph)
}
pub fn join_inbound(self, min: u16, max: u16, named_graph: bool, query: Query) -> Self {
self.join(min, max, query, GraphQueryDirection::Inbound, named_graph)
}
pub fn join_any(self, min: u16, max: u16, named_graph: bool, query: Query) -> Self {
self.join(min, max, query, GraphQueryDirection::Any, named_graph)
}
pub fn with_collections(mut self, collections: &[&str]) -> Self {
self.with_collections =
OptionalQueryString(Some(format!("WITH {} ", string_from_array(collections))));
self
}
pub fn sort(mut self, field: &str, direction: Option<SortDirection>) -> Self {
self.operations.0.push(AqlOperation::Sort {
field: field.to_string(),
direction: direction.unwrap_or(SortDirection::Asc),
});
self
}
pub fn filter(mut self, filter: Filter) -> Self {
self.operations.0.push(AqlOperation::Filter(filter));
self
}
pub fn prune(mut self, filter: Filter) -> Self {
self.operations.0.push(AqlOperation::Prune(filter));
self
}
pub fn limit(mut self, limit: u32, skip: Option<u32>) -> Self {
self.operations.0.push(AqlOperation::Limit { skip, limit });
self
}
pub fn distinct(mut self) -> Self {
self.distinct = true;
self
}
pub fn to_aql(&self) -> String {
let collection_id = get_str_identifier(self.item_identifier);
let mut res = self.with_collections.to_string();
if self.graph_data.is_some() {
let graph_data = self.graph_data.as_ref().unwrap();
res = format!(
"{}FOR {} in {}..{} {} {} {}{}",
res,
collection_id,
graph_data.min,
graph_data.max,
graph_data.direction,
&graph_data.start_vertex,
if graph_data.named_graph { "GRAPH " } else { "" },
&self.collection
);
} else {
res = format!("{}FOR {} in {}", res, collection_id, &self.collection);
}
if !self.operations.0.is_empty() {
res = format!("{} {}", res, self.operations.to_aql(&collection_id));
}
if self.sub_query.is_some() {
res = format!("{} {}", res, self.sub_query.as_ref().unwrap())
} else {
res = format!(
"{} return {}{}",
res,
if self.distinct { "DISTINCT " } else { "" },
&collection_id
);
}
res
}
#[maybe_async::maybe_async]
pub async fn call<D>(self, db_pool: &D) -> Result<JsonQueryResult, ServiceError>
where
D: DatabaseAccess,
{
db_pool.aql_get(&self.to_aql()).await
}
}
impl Display for Query {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.to_aql())
}
}