#![doc = include_str!("../README.MD")]
#![warn(missing_docs)]
mod create_edges;
mod create_nodes;
mod edge_template;
mod expression;
mod key_or_template;
mod node_template;
pub mod prelude;
mod utils;
mod variable;
mod variables;
use std::{borrow::Borrow, convert::Infallible};
pub use expression::expression_builder;
pub use graphcore::{ValueMap, array, labels, value_map};
use crate::prelude::*;
#[derive(thiserror::Error, Debug)]
#[allow(missing_docs)]
#[non_exhaustive]
pub enum Error
{
#[error("{0}")]
GraphCore(#[from] graphcore::Error),
#[error("Askama: {0}")]
AskamaError(#[from] askama::Error),
#[error("Unknwon node.")]
UnknownNode,
#[error("Unknwon edge.")]
UnknownEdge,
#[error("QueryExecutionError {0}.")]
QueryExecutionError(String),
#[error("MissingTableInResult: query results did not produce a table")]
MissingTableInResult,
#[error("MissingEntryInTable: query did not produce the expected entry")]
MissingEntryInTable,
#[error("Infaillible")]
Infaillible,
#[error("MissingGraphName")]
MissingGraphName,
#[error("GenericError {0}")]
GenericError(Box<dyn std::error::Error + Send + Sync>),
}
impl From<Infallible> for Error
{
fn from(_: Infallible) -> Self
{
Error::Infaillible
}
}
type Result<T, E = Error> = std::result::Result<T, E>;
use askama::Template;
pub use {
edge_template::{
CreateEdgeTemplate, IntoCreateEdgeTemplate, IntoMatchEdgeTemplate, MatchEdgeTemplate,
},
key_or_template::KeyOrTemplate,
node_template::{IntoCreateNodeTemplate, IntoMatchNodeTemplate, NodeTemplate},
variable::{TypedVariable, Variable},
};
use {
create_edges::CreateEdges,
create_nodes::CreateNodes,
variables::{TypedVariables, Variables},
};
pub(crate) use variable::FromVariable;
mod templates
{
use askama::Template;
use crate::Variable;
#[derive(Template)]
#[template(path = "oc/node_pattern.oc", escape = "none")]
pub(super) struct NodePattern<'a>
{
pub var: &'a Variable,
pub labels: &'a Vec<String>,
pub has_properties: bool,
pub properties_binding: &'a String,
}
#[derive(Template)]
#[template(path = "oc/edge_pattern.oc", escape = "none")]
pub(super) struct EdgePattern<'a>
{
pub path_variable: &'a Option<Variable>,
pub source: &'a Variable,
pub var: &'a Variable,
pub labels: &'a Vec<String>,
pub has_properties: bool,
pub properties_binding: &'a String,
pub destination: &'a Variable,
}
#[derive(Template)]
#[template(path = "oc/delete.oc", escape = "none")]
pub(super) struct Delete<'a>
{
pub variables: &'a Vec<Variable>,
}
#[derive(Template)]
#[template(path = "oc/detach_delete.oc", escape = "none")]
pub(super) struct DetachDelete<'a>
{
pub variables: &'a Vec<Variable>,
}
}
#[derive(Debug)]
enum MatchCreateFragment
{
Node
{
variable: Variable,
node: graphcore::Node,
},
Edge
{
path_variable: Option<Variable>,
source: Variable,
edge_variable: Variable,
destination: Variable,
edge: graphcore::Edge,
},
}
#[derive(Debug, Default)]
struct CreateStatement
{
fragments: Vec<MatchCreateFragment>,
}
#[derive(Debug, Default)]
struct MatchStatement
{
fragments: Vec<MatchCreateFragment>,
}
#[derive(Debug)]
struct WhereStatement
{
expression: Box<Expression>,
}
#[derive(Debug, Default)]
struct ReturnStatement
{
values: Vec<(Expression, String)>,
}
#[derive(Debug, Default)]
struct DeleteStatement
{
variables: Vec<Variable>,
}
#[derive(Debug, Default)]
struct DetachDeleteStatement
{
variables: Vec<Variable>,
}
#[derive(Debug)]
enum SetExpression
{
Assignment
{
lhs: Expression, rhs: Expression
},
}
#[derive(Debug, Default)]
struct SetStatement
{
expressions: Vec<SetExpression>,
}
#[derive(Debug)]
struct UseGraphStatement
{
name: String,
}
#[derive(Debug)]
struct CreateGraphStatement
{
name: String,
if_not_exists: bool,
}
#[derive(Debug)]
struct DropGraphStatement
{
name: String,
if_exists: bool,
}
#[derive(Debug)]
enum Statement
{
Create(CreateStatement),
Match(MatchStatement),
Where(WhereStatement),
Return(ReturnStatement),
Delete(DeleteStatement),
DetachDelete(DetachDeleteStatement),
Set(SetStatement),
UseGraph(UseGraphStatement),
CreateGraph(CreateGraphStatement),
DropGraph(DropGraphStatement),
}
#[derive(Debug, Default)]
pub struct Builder
{
statements: Vec<Statement>,
variables_count: usize,
}
macro_rules! last_statement {
($fname: ident, $statement: ty, $ename: path) => {
fn $fname(&mut self) -> &mut $statement
{
let needs_push = match self.statements.last()
{
Some($ename(_)) => false,
_ => true,
};
if needs_push
{
self.statements.push($ename(<$statement>::default()));
}
match self.statements.last_mut().unwrap()
{
$ename(statement) => statement,
_ => unreachable!(), }
}
};
}
impl Builder
{
last_statement! {last_create_statement, CreateStatement, Statement::Create}
last_statement! {last_match_statement, MatchStatement, Statement::Match}
last_statement! {last_return_statement, ReturnStatement, Statement::Return}
last_statement! {last_delete_statement, DeleteStatement, Statement::Delete}
last_statement! {last_detach_delete_statement, DetachDeleteStatement, Statement::DetachDelete}
last_statement! {last_set_statement, SetStatement, Statement::Set}
fn next_variable(&mut self, suffix: &'static str) -> Variable
{
self.variables_count += 1;
Variable {
suffix,
id: self.variables_count,
}
}
pub fn node_variable(&mut self) -> Variable
{
self.next_variable("n")
}
fn edge_variable(&mut self) -> Variable
{
self.next_variable("e")
}
pub fn create_node<TIntoCreateNodeTemplate>(
&mut self,
node_template: TIntoCreateNodeTemplate,
) -> TIntoCreateNodeTemplate::VariableType
where
TIntoCreateNodeTemplate: IntoCreateNodeTemplate,
{
let template = node_template.into_node_template();
let var = self.node_variable();
let cs = self.last_create_statement();
cs.fragments.push(MatchCreateFragment::Node {
variable: var,
node: graphcore::Node::new(
None,
Default::default(),
template.labels,
template.properties,
),
});
TIntoCreateNodeTemplate::VariableType::from_variable(var)
}
pub fn create_nodes<T>(&mut self, t: T) -> T::Output
where
T: CreateNodes,
{
t.fill(self)
}
pub fn create_edge<TIntoCreateEdgeTemplate>(
&mut self,
edge_template: TIntoCreateEdgeTemplate,
) -> TIntoCreateEdgeTemplate::VariableType
where
TIntoCreateEdgeTemplate: IntoCreateEdgeTemplate,
{
let template = edge_template.into_edge_template(self);
let var = self.next_variable("p");
let evar = self.next_variable("e");
let cs = self.last_create_statement();
cs.fragments.push(MatchCreateFragment::Edge {
path_variable: Some(var),
edge_variable: evar,
source: template.source,
destination: template.destination,
edge: graphcore::Edge::new(
None,
Default::default(),
template.labels,
template.properties,
),
});
TIntoCreateEdgeTemplate::VariableType::from_variable(var)
}
pub fn create_edges<T>(&mut self, t: T) -> T::Output
where
T: CreateEdges,
{
t.fill(self)
}
pub fn match_node<TNodeTemplate: IntoMatchNodeTemplate>(
&mut self,
node_template: TNodeTemplate,
) -> TNodeTemplate::VariableType
{
match node_template.into_node_template()
{
KeyOrTemplate::Key(key) =>
{
let variable = self.node_variable();
let ss = self.last_match_statement();
ss.fragments.push(MatchCreateFragment::Node {
variable,
node: Default::default(),
});
self.where_statement(expression_builder::equal(
expression_builder::function_call("id", (variable,)),
key.into(),
));
TNodeTemplate::VariableType::from_variable(variable)
}
KeyOrTemplate::Template(template) =>
{
let variable = self.node_variable();
let ss = self.last_match_statement();
ss.fragments.push(MatchCreateFragment::Node {
variable,
node: graphcore::Node::new(
None,
Default::default(),
template.labels,
template.properties,
),
});
TNodeTemplate::VariableType::from_variable(variable)
}
}
}
pub fn match_edge<TIntoMatchEdgeTemplate: IntoMatchEdgeTemplate>(
&mut self,
edge_template: TIntoMatchEdgeTemplate,
) -> TIntoMatchEdgeTemplate::VariableType
{
match edge_template.into_edge_template(self)
{
KeyOrTemplate::Key(key) =>
{
let source = self.node_variable();
let destination = self.node_variable();
let edge_variable = self.edge_variable();
let ss = self.last_match_statement();
ss.fragments.push(MatchCreateFragment::Edge {
path_variable: None,
source,
edge_variable,
destination,
edge: Default::default(),
});
self.where_statement(expression_builder::equal(
expression_builder::function_call("id", (edge_variable,)),
key.into(),
));
TIntoMatchEdgeTemplate::VariableType::from_variable(edge_variable)
}
KeyOrTemplate::Template(template) =>
{
let edge_variable = self.edge_variable();
let source = template.source.unwrap_or_else(|| self.node_variable());
let destination = template.destination.unwrap_or_else(|| self.node_variable());
let ss = self.last_match_statement();
ss.fragments.push(MatchCreateFragment::Edge {
path_variable: None,
source,
edge_variable,
destination,
edge: graphcore::Edge::new(
None,
Default::default(),
template.labels,
template.properties,
),
});
TIntoMatchEdgeTemplate::VariableType::from_variable(edge_variable)
}
}
}
pub fn match_path<TIntoMatchEdgeTemplate: IntoMatchEdgeTemplate>(
&mut self,
edge_template: TIntoMatchEdgeTemplate,
) -> TIntoMatchEdgeTemplate::VariableType
{
match edge_template.into_edge_template(self)
{
KeyOrTemplate::Key(key) =>
{
let variable = self.node_variable();
let source = self.node_variable();
let destination = self.node_variable();
let edge_variable = self.edge_variable();
let ss = self.last_match_statement();
ss.fragments.push(MatchCreateFragment::Edge {
path_variable: None,
source,
edge_variable,
destination,
edge: Default::default(),
});
self.where_statement(expression_builder::equal(
expression_builder::function_call("id", (variable,)),
key.into(),
));
TIntoMatchEdgeTemplate::VariableType::from_variable(variable)
}
KeyOrTemplate::Template(template) =>
{
let path_variable = self.next_variable("p");
let edge_variable = self.edge_variable();
let source = template.source.unwrap_or_else(|| self.node_variable());
let destination = template.destination.unwrap_or_else(|| self.node_variable());
let ss = self.last_match_statement();
ss.fragments.push(MatchCreateFragment::Edge {
path_variable: Some(path_variable),
source,
edge_variable,
destination,
edge: graphcore::Edge::new(
None,
Default::default(),
template.labels,
template.properties,
),
});
TIntoMatchEdgeTemplate::VariableType::from_variable(path_variable)
}
}
}
pub fn set_assignment<I, S>(&mut self, variable: Variable, path: I, expression: Expression)
where
I: IntoIterator<Item = S>,
S: Borrow<str> + std::fmt::Display,
{
use expression_builder as eb;
self
.last_set_statement()
.expressions
.push(SetExpression::Assignment {
lhs: eb::get(
variable.into(),
path.into_iter().map(|s| s.to_string()).collect(),
),
rhs: expression,
});
}
pub fn where_statement(&mut self, expression: Expression)
{
let expression = Box::new(expression);
self
.statements
.push(Statement::Where(WhereStatement { expression }));
}
pub fn return_variable(&mut self, variable: Variable, name: impl Into<String>)
{
self.return_expression(variable.into(), name.into());
}
pub fn return_variables(&mut self, variables: impl Variables)
{
let mut vec = Default::default();
variables.fill(&mut vec);
for (idx, var) in vec.into_iter().enumerate()
{
self.return_variable(var, format!("v{idx}"));
}
}
pub fn return_property<I, S>(&mut self, variable: Variable, path: I, name: impl Into<String>)
where
I: IntoIterator<Item = S>,
S: Borrow<str> + std::fmt::Display,
{
use expression_builder as eb;
self.return_expression(
eb::get(
variable.into(),
path.into_iter().map(|s| s.to_string()).collect(),
),
name.into(),
);
}
pub fn return_expression(&mut self, expression: Expression, name: impl Into<String>)
{
self
.last_return_statement()
.values
.push((expression, name.into()));
}
pub fn delete(&mut self, variables: impl Variables)
{
let delete_statement = self.last_delete_statement();
variables.fill(&mut delete_statement.variables);
}
pub fn detach_delete(&mut self, variables: impl Variables)
{
let delete_statement = self.last_detach_delete_statement();
variables.fill(&mut delete_statement.variables);
}
pub fn use_graph(&mut self, graphname: impl Into<String>)
{
self.statements.push(Statement::UseGraph(UseGraphStatement {
name: graphname.into(),
}));
}
pub fn create_graph(&mut self, graphname: impl Into<String>, if_not_exists: bool)
{
self
.statements
.push(Statement::CreateGraph(CreateGraphStatement {
name: graphname.into(),
if_not_exists,
}));
}
pub fn drop_graph(&mut self, graphname: impl Into<String>, if_exists: bool)
{
self
.statements
.push(Statement::DropGraph(DropGraphStatement {
name: graphname.into(),
if_exists,
}));
}
pub fn into_oc_query(self) -> Result<(String, graphcore::ValueMap)>
{
let mut q = String::new();
let mut bindings = graphcore::ValueMap::new();
let mut space = false;
for st in self.statements
{
if space
{
q += " ";
}
else
{
space = true;
}
match st
{
Statement::Create(create) =>
{
q += "CREATE ";
let mut comma = false;
for fragment in create.fragments
{
if comma
{
q += ", "
}
else
{
comma = true;
}
match fragment
{
MatchCreateFragment::Node { variable, node } =>
{
let properties_binding = format!("$b{}", bindings.len());
q += templates::NodePattern {
var: &variable,
labels: node.labels(),
has_properties: !node.properties().is_empty(),
properties_binding: &properties_binding,
}
.render()
.unwrap()
.as_str();
if !node.properties().is_empty()
{
bindings.insert(properties_binding, node.properties().to_owned().into());
}
}
MatchCreateFragment::Edge {
path_variable,
source,
edge_variable,
destination,
edge,
} =>
{
let properties_binding = format!("$b{}", bindings.len());
q += templates::EdgePattern {
path_variable: &path_variable,
var: &edge_variable,
source: &source,
destination: &destination,
has_properties: !edge.properties().is_empty(),
labels: edge.labels(),
properties_binding: &properties_binding,
}
.render()
.unwrap()
.as_str();
if !edge.properties().is_empty()
{
bindings.insert(properties_binding, edge.properties().to_owned().into());
}
}
}
}
}
Statement::Match(select) =>
{
q += "MATCH ";
let mut comma = false;
for fragment in select.fragments
{
if comma
{
q += ", "
}
else
{
comma = true;
}
match fragment
{
MatchCreateFragment::Node { variable, node } =>
{
let properties_binding = format!("$b{}", bindings.len());
q += templates::NodePattern {
var: &variable,
labels: node.labels(),
has_properties: !node.properties().is_empty(),
properties_binding: &properties_binding,
}
.render()
.unwrap()
.as_str();
if !node.properties().is_empty()
{
bindings.insert(properties_binding, node.properties().to_owned().into());
}
}
MatchCreateFragment::Edge {
path_variable,
source,
edge_variable,
destination,
edge,
} =>
{
let properties_binding = format!("$b{}", bindings.len());
q += templates::EdgePattern {
path_variable: &path_variable,
var: &edge_variable,
source: &source,
destination: &destination,
labels: edge.labels(),
has_properties: !edge.properties().is_empty(),
properties_binding: &properties_binding,
}
.render()
.unwrap()
.as_str();
if !edge.properties().is_empty()
{
bindings.insert(properties_binding, edge.properties().to_owned().into());
}
}
}
}
}
Statement::Set(set_statement) =>
{
q += &format!(
"SET {}",
set_statement
.expressions
.into_iter()
.map(|x| match x
{
SetExpression::Assignment { lhs, rhs } =>
{
format!(
"{} = {}",
lhs.into_oc_query(&mut bindings),
rhs.into_oc_query(&mut bindings)
)
}
})
.collect::<Vec<_>>()
.join(", ")
)
}
Statement::Where(where_statment) =>
{
q += &format!(
"WHERE {}",
where_statment.expression.into_oc_query(&mut bindings)
)
}
Statement::Return(return_statement) =>
{
q += "RETURN ";
let mut comma = false;
for (expr, name) in return_statement.values.into_iter()
{
if comma
{
q += ", "
}
else
{
comma = true;
}
q += &format!("{} AS {}", expr.into_oc_query(&mut bindings), name);
}
}
Statement::Delete(delete_statement) =>
{
q += templates::Delete {
variables: &delete_statement.variables,
}
.render()
.unwrap()
.as_str();
}
Statement::DetachDelete(delete_statement) =>
{
q += templates::DetachDelete {
variables: &delete_statement.variables,
}
.render()
.unwrap()
.as_str();
}
Statement::UseGraph(graph) => q += &format!("USE {}", graph.name),
Statement::CreateGraph(graph) =>
{
if graph.if_not_exists
{
q += &format!("CREATE GRAPH IF NOT EXISTS {}", graph.name)
}
else
{
q += &format!("CREATE GRAPH {}", graph.name)
}
}
Statement::DropGraph(graph) =>
{
if graph.if_exists
{
q += &format!("DROP GRAPH IF EXISTS {}", graph.name)
}
else
{
q += &format!("DROP GRAPH {}", graph.name)
}
}
}
}
Ok((q, bindings))
}
pub fn execute_on(
self,
connection: &impl graphcore::OpenCypherQueryExecutor,
) -> Result<Option<graphcore::Table>>
{
let (query, parameters) = self.into_oc_query()?;
connection
.execute_oc_query(&query, parameters)
.map_err(|e| Error::QueryExecutionError(e.to_string()))
}
pub fn execute_and_return<TTypedVariables>(
mut self,
connection: &impl graphcore::OpenCypherQueryExecutor,
variables: TTypedVariables,
) -> Result<Vec<TTypedVariables::Output>>
where
TTypedVariables: TypedVariables,
{
self.return_variables(variables);
let table = self
.execute_on(connection)?
.ok_or(Error::MissingTableInResult)?;
table
.into_row_iter()
.map(|x| TTypedVariables::from_value_vector(x))
.collect::<Result<_>>()
}
pub fn execute_and_return_one<TTypedVariables>(
mut self,
connection: &impl graphcore::OpenCypherQueryExecutor,
variables: TTypedVariables,
) -> Result<TTypedVariables::Output>
where
TTypedVariables: TypedVariables,
{
self.return_variables(variables);
let table = self
.execute_on(connection)?
.ok_or(Error::MissingTableInResult)?;
if dbg!(&table).rows() != 1
{
return Err(Error::MissingEntryInTable);
}
table
.into_row_iter()
.map(|x| TTypedVariables::from_value_vector(x))
.next()
.unwrap()
}
}
pub fn build_execute(
connection: &impl graphcore::OpenCypherQueryExecutor,
f: impl FnOnce(&mut Builder),
) -> Result<Option<graphcore::Table>>
{
let mut builder = Builder::default();
f(&mut builder);
builder.execute_on(connection)
}
pub fn build_execute_and_return<TTypedVariables>(
connection: &impl graphcore::OpenCypherQueryExecutor,
f: impl FnOnce(&mut Builder) -> TTypedVariables,
) -> Result<Vec<TTypedVariables::Output>>
where
TTypedVariables: TypedVariables,
{
let mut builder = Builder::default();
let variables = f(&mut builder);
builder.execute_and_return(connection, variables)
}
pub fn build_execute_and_return_one<TTypedVariables>(
connection: &impl graphcore::OpenCypherQueryExecutor,
f: impl FnOnce(&mut Builder) -> TTypedVariables,
) -> Result<TTypedVariables::Output>
where
TTypedVariables: TypedVariables,
{
let mut builder = Builder::default();
let variables = f(&mut builder);
builder.execute_and_return_one(connection, variables)
}
#[cfg(test)]
mod test
{
use crate::prelude::*;
use graphcore::*;
#[test]
fn test_create()
{
let mut b = Builder::default();
let n1 = b.create_node((labels!("a"), ValueMap::default()));
let (n2, n3) = b.create_nodes((
(labels!("b"), ValueMap::default()),
(labels!("c"), ValueMap::default()),
));
let _ = b.create_edge((n1, labels!("d"), ValueMap::default(), n2));
let (_, _) = b.create_edges((
(n1, labels!("e"), ValueMap::default(), n3),
(n2, labels!("f"), ValueMap::default(), n3),
));
let (q, b) = b.into_oc_query().unwrap();
assert_eq!(
q,
"CREATE (n1:a), (n2:b), (n3:c), p4 = (n1)-[e5:d]->(n2), p6 = (n1)-[e7:e]->(n3), p8 = (n2)-[e9:f]->(n3)"
.to_string()
);
assert_eq!(b, value_map!());
}
#[test]
fn test_create_no_label()
{
let connection = gqlitedb::Connection::builder().create().unwrap();
let mut b = Builder::default();
b.create_node((labels!(), value_map!()));
let (q, b) = b.into_oc_query().unwrap();
assert_eq!(q, "CREATE (n1)");
assert_eq!(b, value_map!());
connection.execute_oc_query(q, b).unwrap();
let mut b = Builder::default();
b.create_node((labels!(), value_map!("a" => 1)));
let (q, b) = b.into_oc_query().unwrap();
assert_eq!(q, "CREATE (n1 $b0)");
assert_eq!(b, value_map!("$b0" => value_map!("a" => 1)));
connection.execute_oc_query(q, b).unwrap();
let mut b = Builder::default();
let n1 = b.create_node((labels!(), value_map!()));
let n2 = b.create_node((labels!(), value_map!()));
b.create_edge((n1, labels!("a"), value_map!(), n2));
let (q, b) = b.into_oc_query().unwrap();
assert_eq!(q, "CREATE (n1), (n2), p3 = (n1)-[e4:a]->(n2)");
assert_eq!(b, value_map!());
connection.execute_oc_query(q, b).unwrap();
let mut b = Builder::default();
let n1 = b.create_node((labels!(), value_map!()));
let n2 = b.create_node((labels!(), value_map!()));
b.create_edge((n1, labels!("b"), value_map!("a" => 1), n2));
let (q, b) = b.into_oc_query().unwrap();
assert_eq!(q, "CREATE (n1), (n2), p3 = (n1)-[e4:b $b0]->(n2)");
assert_eq!(b, value_map!("$b0" => value_map!("a" => 1)));
connection.execute_oc_query(q, b).unwrap();
}
#[test]
fn test_to_oc_query()
{
let connection = gqlitedb::Connection::builder().create().unwrap();
let mut b = Builder::default();
let (n2, n3) = b.create_nodes((
(labels!("b"), value_map!("id" => 3)),
(labels!("c"), ValueMap::default()),
));
b.create_edge((n2, labels!("d"), ValueMap::default(), n3));
b.create_edge((n2, labels!("e"), value_map!("id" => 5), n3));
let (query, parameters) = b.into_oc_query().unwrap();
connection.execute_oc_query(query, parameters).unwrap();
let r = connection
.execute_oc_query("MATCH (a:b) RETURN a", Default::default())
.unwrap();
let r: Table = r.try_into().unwrap();
let r: &Node = r.get(0, 0).unwrap();
assert_eq!(*r.labels(), labels!("b"));
assert_eq!(*r.properties(), value_map!("id" => 3));
let r = connection
.execute_oc_query("MATCH ()-[a:e]->() RETURN a", Default::default())
.unwrap();
let r: Table = r.try_into().unwrap();
let r: &Edge = r.get(0, 0).unwrap();
assert_eq!(*r.labels(), labels!("e"));
assert_eq!(*r.properties(), value_map!("id" => 5));
}
#[test]
fn test_select()
{
let mut b = Builder::default();
let n1 = b.match_node((labels!("a"), value_map!("id" => 3)));
let n2 = b.node_variable();
let e1 = b.match_edge((n1, labels!("b"), value_map!("id" => 2), n2));
let _p1 = b.match_path((n1, labels!("b"), value_map!("id" => 2), n2));
b.return_variable(n1, "n1");
b.return_variable(e1, "e1");
b.return_variable(n2, "n2");
let (q, b) = b.into_oc_query().unwrap();
assert_eq!(
q,
"MATCH (n1:a $b0), (n1)-[e3:b $b1]->(n2), p4 = (n1)-[e5:b $b2]->(n2) RETURN n1 AS n1, e3 AS e1, n2 AS n2".to_string()
);
assert_eq!(
b,
value_map!("$b0" => value_map!("id" => 3), "$b1" => value_map!("id" => 2), "$b2" => value_map!("id" => 2) )
);
let connection = gqlitedb::Connection::builder().create().unwrap();
connection.execute_oc_query(q, b).unwrap();
}
#[test]
fn test_delete()
{
let mut b = Builder::default();
let n1 = b.node_variable();
let n2 = b.node_variable();
let n3 = b.node_variable();
let n4 = b.node_variable();
let n5 = b.node_variable();
b.delete(n1);
b.delete((n2, n3));
b.delete(vec![n4, n5]);
let (q, b) = b.into_oc_query().unwrap();
assert_eq!(q, "DELETE n1, n2, n3, n4, n5".to_string());
assert_eq!(b, value_map!());
}
#[test]
fn test_where()
{
use crate::expression_builder as eb;
let vid = graphcore::Key::default();
let mut b = Builder::default();
let v1 = b.match_node((labels!(), value_map!()));
b.where_statement(eb::equal(eb::function_call("id", (v1,)), vid.into()));
let (q, b) = b.into_oc_query().unwrap();
assert_eq!(q, "MATCH (n1) WHERE (id(n1)) = ($l0)".to_string());
assert_eq!(b, value_map!("$l0" => vid));
}
#[test]
fn test_set()
{
let connection = gqlitedb::Connection::builder().create().unwrap();
let mut b = Builder::default();
let var = b.create_node((labels!["a"], value_map!()));
b.set_assignment(var, vec!["bob"], 1.into());
b.return_property(var, vec!["bob"], "bob");
let (q, b) = b.into_oc_query().unwrap();
assert_eq!(q, "CREATE (n1:a) SET n1.bob = $l0 RETURN n1.bob AS bob");
assert_eq!(b, value_map!("$l0" => 1));
let t = connection
.execute_oc_query(q, b)
.unwrap()
.try_into_table()
.unwrap();
assert_eq!(t.rows(), 1);
assert_eq!(*t.get::<i64>(0, 0).unwrap(), 1);
}
#[test]
fn test_graph_management()
{
let connection = gqlitedb::Connection::builder().create().unwrap();
let mut b = Builder::default();
b.create_graph("Hello", false);
b.create_node((labels!("a"), value_map!()));
b.use_graph("default");
b.create_node((labels!("b"), value_map!()));
let (q, b) = b.into_oc_query().unwrap();
connection.execute_oc_query(q, b).unwrap();
let mut b = Builder::default();
let n1 = b.match_node((labels!(), value_map!()));
b.use_graph("Hello");
let n2 = b.match_node((labels!(), value_map!()));
b.return_variable(n1, "n1");
b.return_variable(n2, "n2");
let (q, b) = b.into_oc_query().unwrap();
let r = connection
.execute_oc_query(q, b)
.unwrap()
.try_into_table()
.unwrap();
assert_eq!(r.columns(), 2);
assert_eq!(r.rows(), 1);
assert_eq!(*r.get::<Node>(0, 0).unwrap().labels(), labels!("b"));
assert_eq!(*r.get::<Node>(0, 1).unwrap().labels(), labels!("a"));
let mut b = Builder::default();
b.drop_graph("Hello", false);
b.use_graph("Hello");
let (q, b) = b.into_oc_query().unwrap();
let _ = connection.execute_oc_query(q, b).expect_err("should fail");
}
#[derive(Clone, Debug, PartialEq, Eq)]
struct Person
{
name: String,
age: i64,
}
impl IntoCreateNodeTemplate for Person
{
type VariableType = TypedVariable<Person>;
fn into_node_template(self) -> NodeTemplate
{
NodeTemplate {
labels: labels!("Person"),
properties: value_map!("name" => self.name.to_owned(), "age" => self.age),
}
}
}
match_node_template!(Person);
impl TryFrom<graphcore::Value> for Person
{
type Error = graphcore::Error;
fn try_from(value: graphcore::Value) -> Result<Self, Self::Error>
{
let node: &graphcore::Node = value.try_into_ref()?;
Ok(Person {
name: node.properties().get("name").unwrap().try_into().unwrap(),
age: node.properties().get("age").unwrap().try_into().unwrap(),
})
}
}
impl TryFrom<&graphcore::Node> for Person
{
type Error = graphcore::Error;
fn try_from(node: &graphcore::Node) -> Result<Self, Self::Error>
{
Ok(Person {
name: node.properties().get("name").unwrap().try_into().unwrap(),
age: node.properties().get("age").unwrap().try_into().unwrap(),
})
}
}
#[derive(Debug)]
struct FriendOf;
impl IntoMatchEdgeTemplate for FriendOf
{
type VariableType = TypedVariable<FriendOf>;
fn into_edge_template(self, _: &mut Builder) -> KeyOrTemplate<MatchEdgeTemplate>
{
KeyOrTemplate::Template(MatchEdgeTemplate {
source: None,
destination: None,
labels: labels!("friend_of"),
properties: value_map!(),
})
}
}
impl TryFrom<graphcore::Value> for FriendOf
{
type Error = graphcore::Error;
fn try_from(_: graphcore::Value) -> Result<Self, Self::Error>
{
Ok(FriendOf)
}
}
struct FriendOfPath((Person, FriendOf, Person));
impl IntoCreateEdgeTemplate for (Person, FriendOf, Person)
{
type VariableType = TypedVariable<FriendOfPath>;
fn into_edge_template(self, builder: &mut Builder) -> crate::CreateEdgeTemplate
{
let source = builder.match_node(self.0);
let destination = builder.match_node(self.2);
crate::CreateEdgeTemplate {
source: source.into(),
labels: labels!("friend_of"),
properties: value_map!(),
destination: destination.into(),
}
}
}
impl TryFrom<graphcore::Value> for FriendOfPath
{
type Error = graphcore::Error;
fn try_from(value: graphcore::Value) -> Result<Self, Self::Error>
{
let path: graphcore::SinglePath = value.try_into()?;
Ok(FriendOfPath((
path.source().try_into()?,
FriendOf,
path.destination().try_into()?,
)))
}
}
match_edge_template_for_alias!(FriendOfPath, (Person, FriendOf, Person));
#[test]
fn test_templates()
{
let connection = gqlitedb::Connection::builder().create().unwrap();
let george = build_execute_and_return_one(&connection, |b| {
b.create_node(Person {
name: "George".into(),
age: 12,
})
})
.unwrap();
assert_eq!(george.name, "George");
assert_eq!(george.age, 12);
let (anne, marie) = build_execute_and_return_one(&connection, |b| {
b.create_nodes((
Person {
name: "Anne".into(),
age: 23,
},
Person {
name: "Marie".into(),
age: 34,
},
))
})
.unwrap();
assert_eq!(anne.name, "Anne");
assert_eq!(anne.age, 23);
assert_eq!(marie.name, "Marie");
assert_eq!(marie.age, 34);
let first_friend = build_execute_and_return_one(&connection, |b| {
b.create_edge((george.clone(), FriendOf, anne.clone()))
})
.unwrap();
assert_eq!(first_friend.0.0, george);
assert_eq!(first_friend.0.2, anne);
let (second_friend, third_friend) = build_execute_and_return_one(&connection, |b| {
b.create_edges((
(george.clone(), FriendOf, marie.clone()),
(anne.clone(), FriendOf, marie.clone()),
))
})
.unwrap();
assert_eq!(second_friend.0.0, george);
assert_eq!(second_friend.0.2, marie);
assert_eq!(third_friend.0.0, anne);
assert_eq!(third_friend.0.2, marie);
let all_friends = build_execute_and_return(&connection, |b| b.match_edge(FriendOf)).unwrap();
assert_eq!(all_friends.len(), 3);
let first_friend = build_execute_and_return_one(&connection, |b| {
b.match_path(FriendOfPath((george.clone(), FriendOf, anne.clone())))
})
.unwrap();
assert_eq!(first_friend.0.0, george);
assert_eq!(first_friend.0.2, anne);
}
}