#![allow(clippy::use_self)]
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::utils::{string_from_array, OptionalQueryString};
use crate::undefined_record::UndefinedRecord;
use crate::{DatabaseAccess, Error, Record};
use serde::{Deserialize, Serialize};
use serde_json::Value;
use std::collections::HashMap;
use std::fmt::{self, Display, Formatter};
pub use {
comparison::Comparison, comparison::ComparisonBuilder, filter::Filter,
query_cursor::QueryCursor, query_result::QueryResult,
};
mod comparison;
mod filter;
mod graph_query;
mod operations;
mod query_cursor;
mod query_id_helper;
mod query_result;
mod utils;
#[macro_export]
macro_rules! query {
($collection:expr) => {
$crate::query::Query::new($collection)
};
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub enum SortDirection {
Asc,
Desc,
}
impl Display for SortDirection {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
write!(
f,
"{}",
match self {
Self::Asc => "ASC",
Self::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,
pub bind_vars: HashMap<String, Value>,
}
impl Query {
#[inline]
#[must_use]
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,
bind_vars: HashMap::default(),
}
}
#[must_use]
#[inline]
pub fn bind_var(mut self, var: &str, value: impl Into<Value>) -> Self {
self.bind_vars.insert(var.to_owned(), value.into());
self
}
pub fn try_bind_var(mut self, var: &str, value: impl serde::Serialize) -> Result<Self, Error> {
self.bind_vars
.insert(var.to_owned(), serde_json::to_value(value)?);
Ok(self)
}
#[inline]
#[must_use]
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)
}
}
#[inline]
#[must_use]
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)
}
}
#[inline]
#[must_use]
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)
}
}
#[inline]
#[must_use]
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)
}
}
#[inline]
#[must_use]
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)
}
}
#[inline]
#[must_use]
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: Self,
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.aql_str());
self
}
#[inline]
#[must_use]
pub fn join_outbound(self, min: u16, max: u16, named_graph: bool, query: Self) -> Self {
self.join(min, max, query, GraphQueryDirection::Outbound, named_graph)
}
#[inline]
#[must_use]
pub fn join_inbound(self, min: u16, max: u16, named_graph: bool, query: Self) -> Self {
self.join(min, max, query, GraphQueryDirection::Inbound, named_graph)
}
#[inline]
#[must_use]
pub fn join_any(self, min: u16, max: u16, named_graph: bool, query: Self) -> Self {
self.join(min, max, query, GraphQueryDirection::Any, named_graph)
}
#[inline]
#[must_use]
pub fn with_collections(mut self, collections: &[&str]) -> Self {
self.with_collections =
OptionalQueryString(Some(format!("WITH {} ", string_from_array(collections))));
self
}
#[inline]
#[must_use]
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
}
#[inline]
#[must_use]
pub fn filter(mut self, filter: Filter) -> Self {
self.operations.0.push(AqlOperation::Filter(filter));
self
}
#[inline]
#[must_use]
pub fn prune(mut self, filter: Filter) -> Self {
self.operations.0.push(AqlOperation::Prune(filter));
self
}
#[inline]
#[must_use]
pub fn limit(mut self, limit: u32, skip: Option<u32>) -> Self {
self.operations.0.push(AqlOperation::Limit { skip, limit });
self
}
#[inline]
#[must_use]
pub const fn distinct(mut self) -> Self {
self.distinct = true;
self
}
#[inline]
#[must_use]
#[deprecated(since = "0.17.0", note = "use `aql_str` instead")]
pub fn to_aql(&self) -> String {
self.aql_str()
}
#[inline]
#[must_use]
pub fn aql_str(&self) -> String {
let collection_id = get_str_identifier(self.item_identifier);
let mut res = self.with_collections.to_string();
if let Some(graph_data) = &self.graph_data {
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.aql_str(&collection_id));
}
if let Some(sub_query) = &self.sub_query {
res = format!("{} {}", res, sub_query);
} else {
res = format!(
"{} return {}{}",
res,
if self.distinct { "DISTINCT " } else { "" },
&collection_id
);
}
res
}
#[maybe_async::maybe_async]
pub async fn raw_call<D>(&self, db_accessor: &D) -> Result<QueryResult<UndefinedRecord>, Error>
where
D: DatabaseAccess + ?Sized,
{
db_accessor.query(self).await
}
#[maybe_async::maybe_async]
pub async fn call<D, T>(&self, db_accessor: &D) -> Result<QueryResult<T>, Error>
where
D: DatabaseAccess + ?Sized,
T: Record + Send,
{
T::get(self, db_accessor).await
}
#[maybe_async::maybe_async]
pub async fn raw_call_in_batches<D>(
&self,
db_accessor: &D,
batch_size: u32,
) -> Result<QueryCursor<UndefinedRecord>, Error>
where
D: DatabaseAccess + ?Sized,
{
db_accessor.query_in_batches(self, batch_size).await
}
#[maybe_async::maybe_async]
pub async fn call_in_batches<D, T>(
&self,
db_accessor: &D,
batch_size: u32,
) -> Result<QueryCursor<T>, Error>
where
D: DatabaseAccess + ?Sized,
T: Record + Send,
{
T::get_in_batches(self, db_accessor, batch_size).await
}
}
impl Display for Query {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.aql_str())
}
}