#[cfg(any(feature = "cosmos", feature = "gremlin"))]
pub mod gremlin;
#[cfg(feature = "neo4j")]
pub mod neo4j;
pub mod no_database;
use crate::engine::context::RequestContext;
use crate::engine::objects::{Node, Rel};
use crate::engine::schema::Info;
use crate::engine::value::Value;
use crate::error::Error;
use async_trait::async_trait;
#[cfg(any(feature = "cosmos", feature = "gremlin"))]
use gremlin_client::GremlinClient;
#[cfg(feature = "neo4j")]
use mobc::Connection;
#[cfg(feature = "neo4j")]
use mobc_boltrs::BoltConnectionManager;
use std::collections::HashMap;
use std::convert::TryFrom;
#[cfg(any(feature = "cosmos", feature = "gremlin", feature = "neo4j"))]
use std::env::var_os;
use std::fmt::Debug;
#[cfg(feature = "gremlin")]
pub fn env_bool(var_name: &str) -> Result<bool, Error> {
Ok(env_string(var_name)?.parse::<bool>()?)
}
#[cfg(any(feature = "cosmos", feature = "gremlin", feature = "neo4j"))]
fn env_string(var_name: &str) -> Result<String, Error> {
var_os(var_name)
.map(|osstr| osstr.to_string_lossy().into_owned())
.ok_or_else(|| Error::EnvironmentVariableNotFound {
name: var_name.to_string(),
})
}
#[cfg(any(feature = "cosmos", feature = "gremlin", feature = "neo4j"))]
fn env_u16(var_name: &str) -> Result<u16, Error> {
Ok(env_string(var_name)?.parse::<u16>()?)
}
pub enum DatabaseClient {
#[cfg(any(feature = "cosmos", feature = "gremlin"))]
Gremlin(Box<GremlinClient>),
#[cfg(feature = "neo4j")]
Neo4j(Box<Connection<BoltConnectionManager>>),
NoDatabase,
}
#[async_trait]
pub trait DatabaseEndpoint {
type PoolType: DatabasePool;
async fn pool(&self) -> Result<Self::PoolType, Error>;
}
#[async_trait]
pub trait DatabasePool: Clone + Sync + Send {
type TransactionType: Transaction;
async fn transaction(&self) -> Result<Self::TransactionType, Error>;
async fn client(&self) -> Result<DatabaseClient, Error>;
}
#[async_trait]
pub trait Transaction: Send + Sync {
async fn begin(&mut self) -> Result<(), Error>;
async fn create_node<RequestCtx: RequestContext>(
&mut self,
node_var: &NodeQueryVar,
props: HashMap<String, Value>,
partition_key_opt: Option<&Value>,
info: &Info,
sg: &mut SuffixGenerator,
) -> Result<Node<RequestCtx>, Error>;
#[allow(clippy::too_many_arguments)]
async fn create_rels<RequestCtx: RequestContext>(
&mut self,
src_query_fragment: QueryFragment,
dst_query_fragment: QueryFragment,
rel_var: &RelQueryVar,
props: HashMap<String, Value>,
props_type_name: Option<&str>,
partition_key_opt: Option<&Value>,
sg: &mut SuffixGenerator,
) -> Result<Vec<Rel<RequestCtx>>, Error>;
fn node_read_by_ids_fragment<RequestCtx: RequestContext>(
&mut self,
node_var: &NodeQueryVar,
nodes: &[Node<RequestCtx>],
) -> Result<QueryFragment, Error>;
fn node_read_fragment(
&mut self,
rel_query_fragments: Vec<QueryFragment>,
node_var: &NodeQueryVar,
props: HashMap<String, Comparison>,
sg: &mut SuffixGenerator,
) -> Result<QueryFragment, Error>;
async fn read_nodes<RequestCtx: RequestContext>(
&mut self,
node_var: &NodeQueryVar,
query_fragment: QueryFragment,
partition_key_opt: Option<&Value>,
info: &Info,
) -> Result<Vec<Node<RequestCtx>>, Error>;
fn rel_read_by_ids_fragment<RequestCtx: RequestContext>(
&mut self,
rel_var: &RelQueryVar,
rels: &[Rel<RequestCtx>],
) -> Result<QueryFragment, Error>;
fn rel_read_fragment(
&mut self,
src_fragment_opt: Option<QueryFragment>,
dst_fragment_opt: Option<QueryFragment>,
rel_var: &RelQueryVar,
props: HashMap<String, Comparison>,
sg: &mut SuffixGenerator,
) -> Result<QueryFragment, Error>;
async fn read_rels<RequestCtx: RequestContext>(
&mut self,
query_fragment: QueryFragment,
rel_var: &RelQueryVar,
props_type_name: Option<&str>,
partition_key_opt: Option<&Value>,
) -> Result<Vec<Rel<RequestCtx>>, Error>;
async fn update_nodes<RequestCtx: RequestContext>(
&mut self,
query_fragment: QueryFragment,
node_var: &NodeQueryVar,
props: HashMap<String, Value>,
partition_key_opt: Option<&Value>,
info: &Info,
sg: &mut SuffixGenerator,
) -> Result<Vec<Node<RequestCtx>>, Error>;
async fn update_rels<RequestCtx: RequestContext>(
&mut self,
query_fragment: QueryFragment,
rel_var: &RelQueryVar,
props: HashMap<String, Value>,
props_type_name: Option<&str>,
partition_key_opt: Option<&Value>,
sg: &mut SuffixGenerator,
) -> Result<Vec<Rel<RequestCtx>>, Error>;
async fn delete_nodes(
&mut self,
query_fragment: QueryFragment,
node_var: &NodeQueryVar,
partition_key_opt: Option<&Value>,
) -> Result<i32, Error>;
async fn delete_rels(
&mut self,
query_fragment: QueryFragment,
rel_var: &RelQueryVar,
partition_key_opt: Option<&Value>,
) -> Result<i32, Error>;
async fn commit(&mut self) -> Result<(), Error>;
async fn rollback(&mut self) -> Result<(), Error>;
}
#[derive(Clone, Debug)]
pub enum CrudOperation {
ReadNode(String),
ReadRel(String, String),
CreateNode(String),
CreateRel(String, String),
UpdateNode(String),
UpdateRel(String, String),
DeleteNode(String),
DeleteRel(String, String),
None,
}
#[derive(Clone, Debug)]
#[allow(clippy::upper_case_acronyms)]
pub enum Operation {
EQ,
CONTAINS,
IN,
GT,
GTE,
LT,
LTE,
}
#[derive(Clone, Debug)]
pub struct Comparison {
operation: Operation,
operand: Value,
negated: bool,
}
impl Comparison {
pub fn new(operation: Operation, negated: bool, operand: Value) -> Self {
Comparison {
operation,
operand,
negated,
}
}
pub fn default(v: Value) -> Self {
Self::new(Operation::EQ, false, v)
}
}
impl TryFrom<Value> for Comparison {
type Error = Error;
fn try_from(v: Value) -> Result<Comparison, Error> {
Ok(match v {
Value::String(_) => Comparison::default(v),
Value::Int64(_) => Comparison::default(v),
Value::Float64(_) => Comparison::default(v),
Value::Bool(_) => Comparison::default(v),
Value::Map(m) => {
let (operation_str, operand) =
m.into_iter().next().ok_or(Error::InputItemNotFound {
name: "Comparison keys".to_string(),
})?;
Comparison::new(
match operation_str.as_ref() {
"EQ" => Operation::EQ,
"NOTEQ" => Operation::EQ,
"CONTAINS" => Operation::CONTAINS,
"NOTCONTAINS" => Operation::CONTAINS,
"IN" => Operation::IN,
"NOTIN" => Operation::IN,
"GT" => Operation::GT,
"GTE" => Operation::GTE,
"LT" => Operation::LT,
"LTE" => Operation::LTE,
_ => {
return Err(Error::TypeNotExpected {
details: Some(format!("comparison operation {}", operation_str)),
})
}
},
matches!(operation_str.as_ref(), "NOTEQ" | "NOTCONTAINS" | "NOTIN"),
operand,
)
}
_ => {
return Err(Error::TypeNotExpected {
details: Some(format!("comparison value: {:#?}", v)),
})
}
})
}
}
#[derive(Clone, Debug)]
pub struct QueryFragment {
match_fragment: String,
where_fragment: String,
params: HashMap<String, Value>,
}
impl QueryFragment {
#[cfg(any(feature = "cosmos", feature = "gremlin", feature = "neo4j"))]
pub(crate) fn new(
match_fragment: String,
where_fragment: String,
params: HashMap<String, Value>,
) -> QueryFragment {
QueryFragment {
match_fragment,
where_fragment,
params,
}
}
#[cfg(feature = "neo4j")]
pub(crate) fn match_fragment(&self) -> &str {
&self.match_fragment
}
#[cfg(any(feature = "cosmos", feature = "gremlin", feature = "neo4j"))]
pub(crate) fn where_fragment(&self) -> &str {
&self.where_fragment
}
#[cfg(any(feature = "cosmos", feature = "gremlin", feature = "neo4j"))]
pub(crate) fn params(self) -> HashMap<String, Value> {
self.params
}
}
#[derive(Clone, Debug)]
pub struct NodeQueryVar {
base: String,
suffix: String,
label: Option<String>,
name: String,
}
impl NodeQueryVar {
pub(crate) fn new(label: Option<String>, base: String, suffix: String) -> NodeQueryVar {
NodeQueryVar {
base: base.clone(),
suffix: suffix.clone(),
label,
name: base + &*suffix,
}
}
pub(crate) fn base(&self) -> &str {
&self.base
}
pub(crate) fn label(&self) -> Result<&str, Error> {
self.label.as_deref().ok_or(Error::LabelNotFound)
}
pub(crate) fn suffix(&self) -> &str {
&self.suffix
}
#[cfg(any(feature = "cosmos", feature = "gremlin", feature = "neo4j"))]
pub(crate) fn name(&self) -> &str {
&self.name
}
}
#[derive(Clone, Debug)]
pub struct RelQueryVar {
label: String,
suffix: String,
name: String,
src: NodeQueryVar,
dst: NodeQueryVar,
}
impl RelQueryVar {
pub(crate) fn new(
label: String,
suffix: String,
src: NodeQueryVar,
dst: NodeQueryVar,
) -> RelQueryVar {
RelQueryVar {
label,
suffix: suffix.clone(),
name: "rel".to_string() + &*suffix,
src,
dst,
}
}
pub(crate) fn label(&self) -> &str {
&self.label
}
#[cfg(any(feature = "neo4j"))]
pub(crate) fn name(&self) -> &str {
&self.name
}
pub(crate) fn src(&self) -> &NodeQueryVar {
&self.src
}
pub(crate) fn dst(&self) -> &NodeQueryVar {
&self.dst
}
}
#[derive(Debug, Default)]
pub struct SuffixGenerator {
seed: i32,
}
impl SuffixGenerator {
pub(crate) fn new() -> SuffixGenerator {
SuffixGenerator { seed: -1 }
}
pub(crate) fn suffix(&mut self) -> String {
self.seed += 1;
"_".to_string() + &*self.seed.to_string()
}
}