#[cfg(any(
feature = "apoc-create",
feature = "apoc-refactor",
feature = "apoc-cypher"
))]
use crate::error::Error;
use crate::error::Result;
use crate::reader::GraphReader;
use crate::value::Value;
#[cfg(any(
feature = "apoc-create",
feature = "apoc-refactor",
feature = "apoc-cypher"
))]
use crate::writer::GraphWriter;
#[cfg(any(feature = "apoc-create", feature = "apoc-refactor"))]
use meshdb_core::Edge;
#[cfg(feature = "apoc-create")]
use meshdb_core::Node;
use meshdb_core::Property;
use std::collections::{BTreeSet, HashMap};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ProcType {
String,
Integer,
Float,
Number,
Boolean,
Any,
}
impl ProcType {
pub fn parse(s: &str) -> Self {
let trimmed = s.trim().trim_end_matches('?').trim();
match trimmed.to_ascii_uppercase().as_str() {
"STRING" => ProcType::String,
"INTEGER" | "INT" => ProcType::Integer,
"FLOAT" => ProcType::Float,
"NUMBER" | "NUMERIC" => ProcType::Number,
"BOOLEAN" | "BOOL" => ProcType::Boolean,
_ => ProcType::Any,
}
}
pub fn accepts(&self, value: &Value) -> bool {
if matches!(value, Value::Null) {
return true;
}
match (self, value) {
(ProcType::Any, _) => true,
(ProcType::String, Value::Property(Property::String(_))) => true,
(ProcType::Integer, Value::Property(Property::Int64(_))) => true,
(ProcType::Float, Value::Property(Property::Float64(_))) => true,
(ProcType::Float, Value::Property(Property::Int64(_))) => true,
(ProcType::Number, Value::Property(Property::Int64(_))) => true,
(ProcType::Number, Value::Property(Property::Float64(_))) => true,
(ProcType::Boolean, Value::Property(Property::Bool(_))) => true,
_ => false,
}
}
}
#[derive(Debug, Clone)]
pub struct ProcArgSpec {
pub name: String,
pub ty: ProcType,
}
#[derive(Debug, Clone)]
pub struct ProcOutSpec {
pub name: String,
pub ty: ProcType,
}
#[derive(Debug, Clone)]
pub struct Procedure {
pub qualified_name: Vec<String>,
pub inputs: Vec<ProcArgSpec>,
pub outputs: Vec<ProcOutSpec>,
pub rows: Vec<ProcRow>,
pub builtin: Option<BuiltinProc>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum BuiltinProc {
DbLabels,
DbRelationshipTypes,
DbPropertyKeys,
DbConstraints,
#[cfg(feature = "apoc-create")]
ApocCreateNode,
#[cfg(feature = "apoc-create")]
ApocCreateRelationship,
#[cfg(feature = "apoc-create")]
ApocCreateAddLabels,
#[cfg(feature = "apoc-create")]
ApocCreateRemoveLabels,
#[cfg(feature = "apoc-create")]
ApocCreateSetLabels,
#[cfg(feature = "apoc-create")]
ApocCreateSetProperty,
#[cfg(feature = "apoc-create")]
ApocCreateSetRelProperty,
#[cfg(feature = "apoc-refactor")]
ApocRefactorSetType,
#[cfg(feature = "apoc-meta")]
ApocMetaSchema,
#[cfg(feature = "apoc-path")]
ApocPathExpand,
#[cfg(feature = "apoc-path")]
ApocPathExpandConfig,
#[cfg(feature = "apoc-path")]
ApocPathSubgraphNodes,
#[cfg(feature = "apoc-path")]
ApocPathSubgraphAll,
#[cfg(feature = "apoc-path")]
ApocPathSpanningTree,
#[cfg(feature = "apoc-cypher")]
ApocCypherRun,
#[cfg(feature = "apoc-cypher")]
ApocCypherDoIt,
#[cfg(feature = "apoc-load")]
ApocLoadJson,
#[cfg(feature = "apoc-load")]
ApocLoadCsv,
#[cfg(feature = "apoc-export")]
ApocExportCsvAll,
#[cfg(feature = "apoc-export")]
ApocExportCsvQuery,
#[cfg(feature = "apoc-export")]
ApocExportJsonAll,
#[cfg(feature = "apoc-export")]
ApocExportJsonQuery,
#[cfg(feature = "apoc-export")]
ApocExportCypherAll,
#[cfg(feature = "apoc-export")]
ApocExportCypherQuery,
#[cfg(feature = "apoc-trigger")]
ApocTriggerInstall,
#[cfg(feature = "apoc-trigger")]
ApocTriggerDrop,
#[cfg(feature = "apoc-trigger")]
ApocTriggerList,
#[cfg(feature = "apoc-trigger")]
ApocTriggerStart,
#[cfg(feature = "apoc-trigger")]
ApocTriggerStop,
}
pub type ProcRow = HashMap<String, Value>;
pub enum ProcRows {
Eager(Vec<ProcRow>),
Streaming(Box<dyn ProcCursor>),
}
impl std::fmt::Debug for ProcRows {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
ProcRows::Eager(rows) => f
.debug_tuple("Eager")
.field(&format_args!("{} rows", rows.len()))
.finish(),
ProcRows::Streaming(_) => f.debug_struct("Streaming").finish(),
}
}
}
pub trait ProcCursor {
fn advance(&mut self, reader: &dyn GraphReader) -> Result<Option<ProcRow>>;
}
impl Procedure {
pub fn row_matches(&self, row: &ProcRow, args: &[Value]) -> bool {
for (spec, arg) in self.inputs.iter().zip(args.iter()) {
let cell = row.get(&spec.name).unwrap_or(&Value::Null);
if !values_equal_for_procedure(cell, arg) {
return false;
}
}
true
}
pub fn is_write_builtin(&self) -> bool {
match self.builtin {
#[cfg(feature = "apoc-create")]
Some(
BuiltinProc::ApocCreateNode
| BuiltinProc::ApocCreateRelationship
| BuiltinProc::ApocCreateAddLabels
| BuiltinProc::ApocCreateRemoveLabels
| BuiltinProc::ApocCreateSetLabels
| BuiltinProc::ApocCreateSetProperty
| BuiltinProc::ApocCreateSetRelProperty,
) => true,
#[cfg(feature = "apoc-refactor")]
Some(BuiltinProc::ApocRefactorSetType) => true,
#[cfg(feature = "apoc-cypher")]
Some(BuiltinProc::ApocCypherRun | BuiltinProc::ApocCypherDoIt) => true,
#[cfg(feature = "apoc-trigger")]
Some(
BuiltinProc::ApocTriggerInstall
| BuiltinProc::ApocTriggerDrop
| BuiltinProc::ApocTriggerStart
| BuiltinProc::ApocTriggerStop,
) => true,
_ => false,
}
}
pub fn resolve_rows(
&self,
reader: &dyn GraphReader,
args: &[Value],
procedures: &ProcedureRegistry,
) -> Result<ProcRows> {
let _ = args;
let _ = procedures;
match self.builtin {
None => Ok(ProcRows::Eager(self.rows.clone())),
Some(BuiltinProc::DbLabels) => builtin_db_labels(reader).map(ProcRows::Eager),
Some(BuiltinProc::DbRelationshipTypes) => {
builtin_db_relationship_types(reader).map(ProcRows::Eager)
}
Some(BuiltinProc::DbPropertyKeys) => {
builtin_db_property_keys(reader).map(ProcRows::Eager)
}
Some(BuiltinProc::DbConstraints) => builtin_db_constraints(reader).map(ProcRows::Eager),
#[cfg(feature = "apoc-meta")]
Some(BuiltinProc::ApocMetaSchema) => {
builtin_apoc_meta_schema(reader).map(ProcRows::Eager)
}
#[cfg(feature = "apoc-path")]
Some(BuiltinProc::ApocPathExpand) => {
let cfg = crate::apoc_path::config_from_expand_args(args)?;
Ok(ProcRows::Streaming(Box::new(
crate::apoc_path::ExpandCursor::new(cfg),
)))
}
#[cfg(feature = "apoc-path")]
Some(BuiltinProc::ApocPathExpandConfig) => {
let cfg = crate::apoc_path::config_from_expand_config_args(args)?;
Ok(ProcRows::Streaming(Box::new(
crate::apoc_path::ExpandCursor::new(cfg),
)))
}
#[cfg(feature = "apoc-path")]
Some(BuiltinProc::ApocPathSubgraphNodes) => {
let cfg = crate::apoc_path::config_from_expand_config_args(args)?;
Ok(ProcRows::Streaming(Box::new(
crate::apoc_path::SubgraphNodesCursor::new(cfg),
)))
}
#[cfg(feature = "apoc-path")]
Some(BuiltinProc::ApocPathSubgraphAll) => {
let cfg = crate::apoc_path::config_from_expand_config_args(args)?;
Ok(ProcRows::Streaming(Box::new(
crate::apoc_path::SubgraphAllCursor::new(cfg),
)))
}
#[cfg(feature = "apoc-path")]
Some(BuiltinProc::ApocPathSpanningTree) => {
let cfg = crate::apoc_path::config_from_expand_config_args(args)?;
Ok(ProcRows::Streaming(Box::new(
crate::apoc_path::SpanningTreeCursor::new(cfg),
)))
}
#[cfg(feature = "apoc-create")]
Some(BuiltinProc::ApocCreateNode) => Err(Error::Procedure(
"apoc.create.node is a write procedure — call via resolve_write_rows".into(),
)),
#[cfg(feature = "apoc-create")]
Some(BuiltinProc::ApocCreateRelationship) => Err(Error::Procedure(
"apoc.create.relationship is a write procedure — call via resolve_write_rows"
.into(),
)),
#[cfg(feature = "apoc-create")]
Some(
BuiltinProc::ApocCreateAddLabels
| BuiltinProc::ApocCreateRemoveLabels
| BuiltinProc::ApocCreateSetLabels
| BuiltinProc::ApocCreateSetProperty
| BuiltinProc::ApocCreateSetRelProperty,
) => Err(Error::Procedure(
"apoc.create.* mutator is a write procedure — call via resolve_write_rows".into(),
)),
#[cfg(feature = "apoc-refactor")]
Some(BuiltinProc::ApocRefactorSetType) => Err(Error::Procedure(
"apoc.refactor.setType is a write procedure — call via resolve_write_rows".into(),
)),
#[cfg(feature = "apoc-cypher")]
Some(BuiltinProc::ApocCypherRun | BuiltinProc::ApocCypherDoIt) => {
Err(Error::Procedure(
"apoc.cypher.* is dispatched through resolve_write_rows — caller bug".into(),
))
}
#[cfg(feature = "apoc-load")]
Some(BuiltinProc::ApocLoadJson) => {
let input =
crate::apoc_load::expect_source_arg(&args[0], "first argument (urlOrPath)")?;
let pointer = if args.len() > 1 {
crate::apoc_load::expect_optional_string(&args[1], "second argument (path)")?
} else {
None
};
let cfg = crate::apoc_load::import_config_from_registry(procedures);
Ok(ProcRows::Streaming(Box::new(
crate::apoc_load::LoadJsonCursor::new(cfg, input, pointer),
)))
}
#[cfg(feature = "apoc-load")]
Some(BuiltinProc::ApocLoadCsv) => {
let input =
crate::apoc_load::expect_source_arg(&args[0], "first argument (urlOrPath)")?;
let csv_cfg = if args.len() > 1 {
crate::apoc_load::expect_optional_config_map(&args[1])?
} else {
None
};
let cfg = crate::apoc_load::import_config_from_registry(procedures);
Ok(ProcRows::Streaming(Box::new(
crate::apoc_load::LoadCsvCursor::new(cfg, input, csv_cfg.as_ref()),
)))
}
#[cfg(feature = "apoc-export")]
Some(BuiltinProc::ApocExportCsvAll) => {
let file = crate::apoc_export::expect_all_args(args)?;
let cfg = crate::apoc_load::import_config_from_registry(procedures);
crate::apoc_export::export_csv_all(reader, &cfg, &file).map(ProcRows::Eager)
}
#[cfg(feature = "apoc-export")]
Some(BuiltinProc::ApocExportCsvQuery) => {
let (query, file) = crate::apoc_export::expect_query_args(args)?;
let cfg = crate::apoc_load::import_config_from_registry(procedures);
crate::apoc_export::export_csv_query(reader, &cfg, procedures, &query, &file)
.map(ProcRows::Eager)
}
#[cfg(feature = "apoc-export")]
Some(BuiltinProc::ApocExportJsonAll) => {
let file = crate::apoc_export::expect_all_args(args)?;
let cfg = crate::apoc_load::import_config_from_registry(procedures);
crate::apoc_export::export_json_all(reader, &cfg, &file).map(ProcRows::Eager)
}
#[cfg(feature = "apoc-export")]
Some(BuiltinProc::ApocExportJsonQuery) => {
let (query, file) = crate::apoc_export::expect_query_args(args)?;
let cfg = crate::apoc_load::import_config_from_registry(procedures);
crate::apoc_export::export_json_query(reader, &cfg, procedures, &query, &file)
.map(ProcRows::Eager)
}
#[cfg(feature = "apoc-export")]
Some(BuiltinProc::ApocExportCypherAll) => {
let file = crate::apoc_export::expect_all_args(args)?;
let cfg = crate::apoc_load::import_config_from_registry(procedures);
crate::apoc_export::export_cypher_all(reader, &cfg, &file).map(ProcRows::Eager)
}
#[cfg(feature = "apoc-export")]
Some(BuiltinProc::ApocExportCypherQuery) => {
let (query, file) = crate::apoc_export::expect_query_args(args)?;
let cfg = crate::apoc_load::import_config_from_registry(procedures);
crate::apoc_export::export_cypher_query(reader, &cfg, procedures, &query, &file)
.map(ProcRows::Eager)
}
#[cfg(feature = "apoc-trigger")]
Some(
BuiltinProc::ApocTriggerInstall
| BuiltinProc::ApocTriggerDrop
| BuiltinProc::ApocTriggerStart
| BuiltinProc::ApocTriggerStop,
) => Err(Error::Procedure(
"apoc.trigger.install/drop/start/stop are write builtins — must go through resolve_write_rows"
.into(),
)),
#[cfg(feature = "apoc-trigger")]
Some(BuiltinProc::ApocTriggerList) => {
let registry = procedures.trigger_registry().ok_or_else(|| {
Error::Procedure(
"apoc.trigger.* not available — server did not attach a trigger registry"
.into(),
)
})?;
crate::apoc_trigger::list_call(registry).map(ProcRows::Eager)
}
}
}
#[cfg(any(
feature = "apoc-create",
feature = "apoc-refactor",
feature = "apoc-cypher"
))]
pub fn resolve_write_rows(
&self,
reader: &dyn GraphReader,
writer: &dyn GraphWriter,
args: &[Value],
procedures: &ProcedureRegistry,
) -> Result<Vec<ProcRow>> {
let _ = procedures;
match self.builtin {
#[cfg(feature = "apoc-create")]
Some(BuiltinProc::ApocCreateNode) => apoc_create_node(writer, args),
#[cfg(feature = "apoc-create")]
Some(BuiltinProc::ApocCreateRelationship) => apoc_create_relationship(writer, args),
#[cfg(feature = "apoc-create")]
Some(BuiltinProc::ApocCreateAddLabels) => {
apoc_label_mutator(reader, writer, args, LabelMode::Add)
}
#[cfg(feature = "apoc-create")]
Some(BuiltinProc::ApocCreateRemoveLabels) => {
apoc_label_mutator(reader, writer, args, LabelMode::Remove)
}
#[cfg(feature = "apoc-create")]
Some(BuiltinProc::ApocCreateSetLabels) => {
apoc_label_mutator(reader, writer, args, LabelMode::Set)
}
#[cfg(feature = "apoc-create")]
Some(BuiltinProc::ApocCreateSetProperty) => {
apoc_set_node_property(reader, writer, args)
}
#[cfg(feature = "apoc-create")]
Some(BuiltinProc::ApocCreateSetRelProperty) => {
apoc_set_rel_property(reader, writer, args)
}
#[cfg(feature = "apoc-refactor")]
Some(BuiltinProc::ApocRefactorSetType) => apoc_refactor_set_type(reader, writer, args),
#[cfg(feature = "apoc-cypher")]
Some(BuiltinProc::ApocCypherRun) => {
crate::apoc_cypher::run_cypher(reader, writer, args, procedures, false)
}
#[cfg(feature = "apoc-cypher")]
Some(BuiltinProc::ApocCypherDoIt) => {
crate::apoc_cypher::run_cypher(reader, writer, args, procedures, true)
}
#[cfg(feature = "apoc-trigger")]
Some(BuiltinProc::ApocTriggerInstall) => {
crate::apoc_trigger::install_call(writer, args)
}
#[cfg(feature = "apoc-trigger")]
Some(BuiltinProc::ApocTriggerDrop) => crate::apoc_trigger::drop_call(writer, args),
#[cfg(feature = "apoc-trigger")]
Some(BuiltinProc::ApocTriggerStart) => {
let registry = procedures.trigger_registry().ok_or_else(|| {
Error::Procedure("apoc.trigger.start: no trigger registry attached".into())
})?;
crate::apoc_trigger::start_call(registry, writer, args)
}
#[cfg(feature = "apoc-trigger")]
Some(BuiltinProc::ApocTriggerStop) => {
let registry = procedures.trigger_registry().ok_or_else(|| {
Error::Procedure("apoc.trigger.stop: no trigger registry attached".into())
})?;
crate::apoc_trigger::stop_call(registry, writer, args)
}
_ => Err(Error::Procedure("procedure is not a write builtin".into())),
}
}
}
fn str_row(column: &str, value: String) -> ProcRow {
let mut row = HashMap::new();
row.insert(column.to_string(), Value::Property(Property::String(value)));
row
}
fn builtin_db_labels(reader: &dyn GraphReader) -> Result<Vec<ProcRow>> {
let mut labels: BTreeSet<String> = BTreeSet::new();
for id in reader.all_node_ids()? {
if let Some(n) = reader.get_node(id)? {
for l in n.labels {
labels.insert(l);
}
}
}
Ok(labels.into_iter().map(|l| str_row("label", l)).collect())
}
fn builtin_db_relationship_types(reader: &dyn GraphReader) -> Result<Vec<ProcRow>> {
let mut types: BTreeSet<String> = BTreeSet::new();
for id in reader.all_node_ids()? {
for (edge_id, _) in reader.outgoing(id)? {
if let Some(e) = reader.get_edge(edge_id)? {
types.insert(e.edge_type);
}
}
}
Ok(types
.into_iter()
.map(|t| str_row("relationshipType", t))
.collect())
}
fn builtin_db_constraints(reader: &dyn GraphReader) -> Result<Vec<ProcRow>> {
let specs = reader.list_property_constraints()?;
Ok(specs
.into_iter()
.map(|spec| {
let mut row: ProcRow = HashMap::new();
row.insert("name".into(), Value::Property(Property::String(spec.name)));
let (scope_tag, target) = match spec.scope {
meshdb_storage::ConstraintScope::Node(l) => ("NODE", l),
meshdb_storage::ConstraintScope::Relationship(t) => ("RELATIONSHIP", t),
};
row.insert(
"scope".into(),
Value::Property(Property::String(scope_tag.into())),
);
row.insert("label".into(), Value::Property(Property::String(target)));
let props: Vec<Property> = spec.properties.into_iter().map(Property::String).collect();
row.insert("properties".into(), Value::Property(Property::List(props)));
row.insert(
"type".into(),
Value::Property(Property::String(spec.kind.as_string())),
);
row
})
.collect())
}
fn builtin_db_property_keys(reader: &dyn GraphReader) -> Result<Vec<ProcRow>> {
let mut keys: BTreeSet<String> = BTreeSet::new();
for id in reader.all_node_ids()? {
if let Some(n) = reader.get_node(id)? {
for k in n.properties.keys() {
keys.insert(k.clone());
}
for (edge_id, _) in reader.outgoing(id)? {
if let Some(e) = reader.get_edge(edge_id)? {
for k in e.properties.keys() {
keys.insert(k.clone());
}
}
}
}
}
Ok(keys
.into_iter()
.map(|k| str_row("propertyKey", k))
.collect())
}
#[cfg(feature = "apoc-meta")]
fn builtin_apoc_meta_schema(reader: &dyn GraphReader) -> Result<Vec<ProcRow>> {
use std::collections::HashSet;
let mut label_count: HashMap<String, i64> = HashMap::new();
let mut label_props: HashMap<String, HashMap<String, HashSet<&'static str>>> = HashMap::new();
let mut edge_count: HashMap<String, i64> = HashMap::new();
let mut edge_props: HashMap<String, HashMap<String, HashSet<&'static str>>> = HashMap::new();
for id in reader.all_node_ids()? {
let node = match reader.get_node(id)? {
Some(n) => n,
None => continue,
};
for label in &node.labels {
*label_count.entry(label.clone()).or_insert(0) += 1;
let per_label = label_props.entry(label.clone()).or_default();
for (k, v) in &node.properties {
per_label
.entry(k.clone())
.or_default()
.insert(apoc_schema_type(v));
}
}
for (edge_id, _) in reader.outgoing(id)? {
let edge = match reader.get_edge(edge_id)? {
Some(e) => e,
None => continue,
};
*edge_count.entry(edge.edge_type.clone()).or_insert(0) += 1;
let per_type = edge_props.entry(edge.edge_type.clone()).or_default();
for (k, v) in &edge.properties {
per_type
.entry(k.clone())
.or_default()
.insert(apoc_schema_type(v));
}
}
}
let mut node_indexed: HashMap<String, HashSet<String>> = HashMap::new();
for (label, props) in reader.list_property_indexes()? {
let entry = node_indexed.entry(label).or_default();
for p in props {
entry.insert(p);
}
}
let mut edge_indexed: HashMap<String, HashSet<String>> = HashMap::new();
for (edge_type, props) in reader.list_edge_property_indexes()? {
let entry = edge_indexed.entry(edge_type).or_default();
for p in props {
entry.insert(p);
}
}
let mut schema: HashMap<String, Property> = HashMap::new();
for (label, count) in label_count {
let props = label_props.remove(&label).unwrap_or_default();
let indexed = node_indexed.remove(&label).unwrap_or_default();
schema.insert(
label,
Property::Map(schema_entry(count, "node", props, Some(&indexed))),
);
}
for (edge_type, count) in edge_count {
let props = edge_props.remove(&edge_type).unwrap_or_default();
let indexed = edge_indexed.remove(&edge_type).unwrap_or_default();
schema.insert(
edge_type,
Property::Map(schema_entry(count, "relationship", props, Some(&indexed))),
);
}
let mut row = HashMap::new();
row.insert("value".to_string(), Value::Property(Property::Map(schema)));
Ok(vec![row])
}
#[cfg(feature = "apoc-meta")]
fn schema_entry(
count: i64,
type_tag: &str,
props: HashMap<String, std::collections::HashSet<&'static str>>,
indexed: Option<&std::collections::HashSet<String>>,
) -> HashMap<String, Property> {
let mut out: HashMap<String, Property> = HashMap::new();
out.insert("type".into(), Property::String(type_tag.into()));
out.insert("count".into(), Property::Int64(count));
let mut properties: HashMap<String, Property> = HashMap::new();
for (k, types) in props {
let mut sorted: Vec<&'static str> = types.into_iter().collect();
sorted.sort_unstable();
let ty = sorted.join("|");
let mut info: HashMap<String, Property> = HashMap::new();
info.insert("type".into(), Property::String(ty));
if let Some(idx) = indexed {
info.insert("indexed".into(), Property::Bool(idx.contains(&k)));
}
properties.insert(k, Property::Map(info));
}
out.insert("properties".into(), Property::Map(properties));
out
}
#[cfg(feature = "apoc-meta")]
fn apoc_schema_type(p: &Property) -> &'static str {
match p {
Property::Null => "NULL",
Property::String(_) => "STRING",
Property::Int64(_) => "INTEGER",
Property::Float64(_) => "FLOAT",
Property::Bool(_) => "BOOLEAN",
Property::List(_) => "LIST",
Property::Map(_) => "MAP",
Property::DateTime { .. } => "DATE_TIME",
Property::LocalDateTime(_) => "LOCAL_DATE_TIME",
Property::Date(_) => "DATE",
Property::Time { .. } => "TIME",
Property::Duration(_) => "DURATION",
Property::Point(_) => "POINT",
}
}
#[cfg(feature = "apoc-create")]
fn apoc_create_node(writer: &dyn GraphWriter, args: &[Value]) -> Result<Vec<ProcRow>> {
let labels = match &args[0] {
Value::Null | Value::Property(Property::Null) => Vec::new(),
Value::List(items) => items
.iter()
.map(|v| match v {
Value::Property(Property::String(s)) => Ok(s.clone()),
other => Err(Error::Procedure(format!(
"apoc.create.node: labels must be strings, got {other:?}"
))),
})
.collect::<Result<Vec<_>>>()?,
Value::Property(Property::List(items)) => items
.iter()
.map(|p| match p {
Property::String(s) => Ok(s.clone()),
other => Err(Error::Procedure(format!(
"apoc.create.node: labels must be strings, got {other:?}"
))),
})
.collect::<Result<Vec<_>>>()?,
other => {
return Err(Error::Procedure(format!(
"apoc.create.node: first argument must be a list of strings, got {other:?}"
)));
}
};
let props: HashMap<String, Property> = match &args[1] {
Value::Null | Value::Property(Property::Null) => HashMap::new(),
Value::Map(pairs) => pairs
.iter()
.map(|(k, v)| Ok((k.clone(), value_to_storable_property(v)?)))
.collect::<Result<HashMap<_, _>>>()?,
Value::Property(Property::Map(entries)) => entries
.iter()
.map(|(k, p)| (k.clone(), p.clone()))
.collect(),
other => {
return Err(Error::Procedure(format!(
"apoc.create.node: second argument must be a map, got {other:?}"
)));
}
};
let mut node = Node::new();
node.labels = labels;
for (k, p) in props {
if !matches!(p, Property::Null) {
node.properties.insert(k, p);
}
}
writer.put_node(&node)?;
let mut row = HashMap::new();
row.insert("node".to_string(), Value::Node(node));
Ok(vec![row])
}
#[cfg(feature = "apoc-create")]
fn apoc_create_relationship(writer: &dyn GraphWriter, args: &[Value]) -> Result<Vec<ProcRow>> {
let from = expect_node_id(&args[0], "first argument (from)")?;
let rel_type = match &args[1] {
Value::Property(Property::String(s)) => s.clone(),
Value::Null | Value::Property(Property::Null) => {
return Err(Error::Procedure(
"apoc.create.relationship: relationship type must not be null".into(),
));
}
other => {
return Err(Error::Procedure(format!(
"apoc.create.relationship: relationship type must be a string, got {other:?}"
)));
}
};
let props: HashMap<String, Property> = match &args[2] {
Value::Null | Value::Property(Property::Null) => HashMap::new(),
Value::Map(pairs) => pairs
.iter()
.map(|(k, v)| Ok((k.clone(), value_to_storable_property(v)?)))
.collect::<Result<HashMap<_, _>>>()?,
Value::Property(Property::Map(entries)) => entries
.iter()
.map(|(k, p)| (k.clone(), p.clone()))
.collect(),
other => {
return Err(Error::Procedure(format!(
"apoc.create.relationship: third argument must be a map, got {other:?}"
)));
}
};
let to = expect_node_id(&args[3], "fourth argument (to)")?;
let mut edge = Edge::new(rel_type, from, to);
for (k, p) in props {
if !matches!(p, Property::Null) {
edge.properties.insert(k, p);
}
}
writer.put_edge(&edge)?;
let mut row = HashMap::new();
row.insert("rel".to_string(), Value::Edge(edge));
Ok(vec![row])
}
#[cfg(feature = "apoc-refactor")]
fn apoc_refactor_set_type(
reader: &dyn GraphReader,
writer: &dyn GraphWriter,
args: &[Value],
) -> Result<Vec<ProcRow>> {
let old_id = match &args[0] {
Value::Edge(e) => e.id,
Value::Null | Value::Property(Property::Null) => {
return Err(Error::Procedure(
"apoc.refactor.setType: relationship argument must not be null".into(),
));
}
other => {
return Err(Error::Procedure(format!(
"apoc.refactor.setType: first argument must be a relationship, got {other:?}"
)));
}
};
let new_type = match &args[1] {
Value::Property(Property::String(s)) => s.clone(),
Value::Null | Value::Property(Property::Null) => {
return Err(Error::Procedure(
"apoc.refactor.setType: new type must not be null".into(),
));
}
other => {
return Err(Error::Procedure(format!(
"apoc.refactor.setType: new type must be a string, got {other:?}"
)));
}
};
let old = reader
.get_edge(old_id)?
.ok_or_else(|| Error::Procedure(format!("edge {old_id:?} no longer exists")))?;
if old.edge_type == new_type {
let mut row = HashMap::new();
row.insert("rel".to_string(), Value::Edge(old));
return Ok(vec![row]);
}
let mut new_edge = Edge::new(new_type, old.source, old.target);
new_edge.properties = old.properties.clone();
writer.delete_edge(old_id)?;
writer.put_edge(&new_edge)?;
let mut row = HashMap::new();
row.insert("rel".to_string(), Value::Edge(new_edge));
Ok(vec![row])
}
#[cfg(feature = "apoc-create")]
#[derive(Debug, Clone, Copy)]
enum LabelMode {
Add,
Remove,
Set,
}
#[cfg(feature = "apoc-create")]
fn apoc_label_mutator(
reader: &dyn GraphReader,
writer: &dyn GraphWriter,
args: &[Value],
mode: LabelMode,
) -> Result<Vec<ProcRow>> {
let node_ids = expect_node_or_node_list(&args[0], "first argument")?;
let labels = expect_string_list(&args[1], "second argument (labels)")?;
let mut out: Vec<ProcRow> = Vec::with_capacity(node_ids.len());
for nid in node_ids {
let mut node = reader
.get_node(nid)?
.ok_or_else(|| Error::Procedure(format!("node {nid:?} no longer exists")))?;
match mode {
LabelMode::Add => {
for l in &labels {
if !node.labels.iter().any(|existing| existing == l) {
node.labels.push(l.clone());
}
}
}
LabelMode::Remove => {
node.labels
.retain(|existing| !labels.iter().any(|l| l == existing));
}
LabelMode::Set => {
node.labels = labels.clone();
}
}
writer.put_node(&node)?;
let mut row = HashMap::new();
row.insert("node".to_string(), Value::Node(node));
out.push(row);
}
Ok(out)
}
#[cfg(feature = "apoc-create")]
fn apoc_set_node_property(
reader: &dyn GraphReader,
writer: &dyn GraphWriter,
args: &[Value],
) -> Result<Vec<ProcRow>> {
let node_ids = expect_node_or_node_list(&args[0], "first argument")?;
let key = expect_string(&args[1], "second argument (key)")?;
let value = value_to_storable_property(&args[2])?;
let mut out: Vec<ProcRow> = Vec::with_capacity(node_ids.len());
for nid in node_ids {
let mut node = reader
.get_node(nid)?
.ok_or_else(|| Error::Procedure(format!("node {nid:?} no longer exists")))?;
if matches!(value, Property::Null) {
node.properties.remove(&key);
} else {
node.properties.insert(key.clone(), value.clone());
}
writer.put_node(&node)?;
let mut row = HashMap::new();
row.insert("node".to_string(), Value::Node(node));
out.push(row);
}
Ok(out)
}
#[cfg(feature = "apoc-create")]
fn apoc_set_rel_property(
reader: &dyn GraphReader,
writer: &dyn GraphWriter,
args: &[Value],
) -> Result<Vec<ProcRow>> {
let edge_ids = expect_edge_or_edge_list(&args[0], "first argument")?;
let key = expect_string(&args[1], "second argument (key)")?;
let value = value_to_storable_property(&args[2])?;
let mut out: Vec<ProcRow> = Vec::with_capacity(edge_ids.len());
for eid in edge_ids {
let mut edge = reader
.get_edge(eid)?
.ok_or_else(|| Error::Procedure(format!("edge {eid:?} no longer exists")))?;
if matches!(value, Property::Null) {
edge.properties.remove(&key);
} else {
edge.properties.insert(key.clone(), value.clone());
}
writer.put_edge(&edge)?;
let mut row = HashMap::new();
row.insert("rel".to_string(), Value::Edge(edge));
out.push(row);
}
Ok(out)
}
#[cfg(feature = "apoc-create")]
fn expect_node_or_node_list(v: &Value, position: &str) -> Result<Vec<meshdb_core::NodeId>> {
let mut ids: Vec<meshdb_core::NodeId> = Vec::new();
collect_node_ids(v, position, &mut ids)?;
if ids.is_empty() {
return Err(Error::Procedure(format!(
"apoc.create.*: {position} resolved to zero nodes"
)));
}
Ok(ids)
}
#[cfg(feature = "apoc-create")]
fn collect_node_ids(v: &Value, position: &str, out: &mut Vec<meshdb_core::NodeId>) -> Result<()> {
match v {
Value::Node(n) => {
out.push(n.id);
Ok(())
}
Value::List(items) => {
for item in items {
collect_node_ids(item, position, out)?;
}
Ok(())
}
Value::Null | Value::Property(Property::Null) => Ok(()),
other => Err(Error::Procedure(format!(
"apoc.create.*: {position} must be a node or list of nodes, got {other:?}"
))),
}
}
#[cfg(feature = "apoc-create")]
fn expect_edge_or_edge_list(v: &Value, position: &str) -> Result<Vec<meshdb_core::EdgeId>> {
let mut ids: Vec<meshdb_core::EdgeId> = Vec::new();
collect_edge_ids(v, position, &mut ids)?;
if ids.is_empty() {
return Err(Error::Procedure(format!(
"apoc.create.*: {position} resolved to zero relationships"
)));
}
Ok(ids)
}
#[cfg(feature = "apoc-create")]
fn collect_edge_ids(v: &Value, position: &str, out: &mut Vec<meshdb_core::EdgeId>) -> Result<()> {
match v {
Value::Edge(e) => {
out.push(e.id);
Ok(())
}
Value::List(items) => {
for item in items {
collect_edge_ids(item, position, out)?;
}
Ok(())
}
Value::Null | Value::Property(Property::Null) => Ok(()),
other => Err(Error::Procedure(format!(
"apoc.create.*: {position} must be a relationship or list of relationships, got {other:?}"
))),
}
}
#[cfg(feature = "apoc-create")]
fn expect_string_list(v: &Value, position: &str) -> Result<Vec<String>> {
match v {
Value::Null | Value::Property(Property::Null) => Ok(Vec::new()),
Value::List(items) => items
.iter()
.map(|item| match item {
Value::Property(Property::String(s)) => Ok(s.clone()),
other => Err(Error::Procedure(format!(
"apoc.create.*: {position} must contain strings, got {other:?}"
))),
})
.collect(),
Value::Property(Property::List(items)) => items
.iter()
.map(|p| match p {
Property::String(s) => Ok(s.clone()),
other => Err(Error::Procedure(format!(
"apoc.create.*: {position} must contain strings, got {other:?}"
))),
})
.collect(),
other => Err(Error::Procedure(format!(
"apoc.create.*: {position} must be a list of strings, got {other:?}"
))),
}
}
#[cfg(feature = "apoc-create")]
fn expect_string(v: &Value, position: &str) -> Result<String> {
match v {
Value::Property(Property::String(s)) => Ok(s.clone()),
other => Err(Error::Procedure(format!(
"apoc.create.*: {position} must be a string, got {other:?}"
))),
}
}
#[cfg(feature = "apoc-create")]
fn expect_node_id(v: &Value, position: &str) -> Result<meshdb_core::NodeId> {
match v {
Value::Node(n) => Ok(n.id),
Value::Null | Value::Property(Property::Null) => Err(Error::Procedure(format!(
"apoc.create.relationship: {position} must be a node, got null"
))),
other => Err(Error::Procedure(format!(
"apoc.create.relationship: {position} must be a node, got {other:?}"
))),
}
}
#[cfg(feature = "apoc-create")]
fn value_to_storable_property(v: &Value) -> Result<Property> {
match v {
Value::Property(p) => Ok(p.clone()),
Value::Null => Ok(Property::Null),
Value::List(items) => {
let props = items
.iter()
.map(value_to_storable_property)
.collect::<Result<Vec<_>>>()?;
Ok(Property::List(props))
}
Value::Map(_) | Value::Node(_) | Value::Edge(_) | Value::Path { .. } => {
Err(Error::Procedure(
"apoc.create.node: property values can't be graph elements or graph-aware maps"
.into(),
))
}
}
}
fn values_equal_for_procedure(a: &Value, b: &Value) -> bool {
match (a, b) {
(Value::Null, Value::Null) => true,
(Value::Null, _) | (_, Value::Null) => false,
(Value::Property(Property::Int64(x)), Value::Property(Property::Int64(y))) => x == y,
(Value::Property(Property::Float64(x)), Value::Property(Property::Float64(y))) => x == y,
(Value::Property(Property::Int64(i)), Value::Property(Property::Float64(f)))
| (Value::Property(Property::Float64(f)), Value::Property(Property::Int64(i))) => {
*f == (*i as f64)
}
(Value::Property(Property::String(x)), Value::Property(Property::String(y))) => x == y,
(Value::Property(Property::Bool(x)), Value::Property(Property::Bool(y))) => x == y,
_ => a == b,
}
}
#[derive(Debug, Clone, Default)]
pub struct ProcedureRegistry {
procs: HashMap<String, Procedure>,
#[cfg(feature = "apoc-load")]
import_config: Option<crate::apoc_load::ImportConfig>,
#[cfg(feature = "apoc-trigger")]
trigger_registry: Option<crate::apoc_trigger::TriggerRegistry>,
}
impl ProcedureRegistry {
pub fn new() -> Self {
Self::default()
}
pub fn register(&mut self, proc: Procedure) {
let key = proc.qualified_name.join(".");
self.procs.insert(key, proc);
}
pub fn get(&self, qualified_name: &[String]) -> Option<&Procedure> {
self.procs.get(&qualified_name.join("."))
}
#[cfg(feature = "apoc-load")]
pub fn set_import_config(&mut self, cfg: crate::apoc_load::ImportConfig) {
self.import_config = Some(cfg);
}
#[cfg(feature = "apoc-load")]
pub fn import_config(&self) -> Option<&crate::apoc_load::ImportConfig> {
self.import_config.as_ref()
}
#[cfg(feature = "apoc-trigger")]
pub fn set_trigger_registry(&mut self, registry: crate::apoc_trigger::TriggerRegistry) {
self.trigger_registry = Some(registry);
}
#[cfg(feature = "apoc-trigger")]
pub fn trigger_registry(&self) -> Option<&crate::apoc_trigger::TriggerRegistry> {
self.trigger_registry.as_ref()
}
pub fn register_defaults(&mut self) {
self.register(Procedure {
qualified_name: vec!["db".into(), "labels".into()],
inputs: Vec::new(),
outputs: vec![ProcOutSpec {
name: "label".into(),
ty: ProcType::String,
}],
rows: Vec::new(),
builtin: Some(BuiltinProc::DbLabels),
});
self.register(Procedure {
qualified_name: vec!["db".into(), "relationshipTypes".into()],
inputs: Vec::new(),
outputs: vec![ProcOutSpec {
name: "relationshipType".into(),
ty: ProcType::String,
}],
rows: Vec::new(),
builtin: Some(BuiltinProc::DbRelationshipTypes),
});
self.register(Procedure {
qualified_name: vec!["db".into(), "propertyKeys".into()],
inputs: Vec::new(),
outputs: vec![ProcOutSpec {
name: "propertyKey".into(),
ty: ProcType::String,
}],
rows: Vec::new(),
builtin: Some(BuiltinProc::DbPropertyKeys),
});
self.register(Procedure {
qualified_name: vec!["db".into(), "constraints".into()],
inputs: Vec::new(),
outputs: vec![
ProcOutSpec {
name: "name".into(),
ty: ProcType::String,
},
ProcOutSpec {
name: "scope".into(),
ty: ProcType::String,
},
ProcOutSpec {
name: "label".into(),
ty: ProcType::String,
},
ProcOutSpec {
name: "properties".into(),
ty: ProcType::Any,
},
ProcOutSpec {
name: "type".into(),
ty: ProcType::String,
},
],
rows: Vec::new(),
builtin: Some(BuiltinProc::DbConstraints),
});
#[cfg(feature = "apoc-create")]
self.register(Procedure {
qualified_name: vec!["apoc".into(), "create".into(), "node".into()],
inputs: vec![
ProcArgSpec {
name: "labels".into(),
ty: ProcType::Any,
},
ProcArgSpec {
name: "props".into(),
ty: ProcType::Any,
},
],
outputs: vec![ProcOutSpec {
name: "node".into(),
ty: ProcType::Any,
}],
rows: Vec::new(),
builtin: Some(BuiltinProc::ApocCreateNode),
});
#[cfg(feature = "apoc-create")]
self.register(Procedure {
qualified_name: vec!["apoc".into(), "create".into(), "relationship".into()],
inputs: vec![
ProcArgSpec {
name: "from".into(),
ty: ProcType::Any,
},
ProcArgSpec {
name: "relType".into(),
ty: ProcType::String,
},
ProcArgSpec {
name: "props".into(),
ty: ProcType::Any,
},
ProcArgSpec {
name: "to".into(),
ty: ProcType::Any,
},
],
outputs: vec![ProcOutSpec {
name: "rel".into(),
ty: ProcType::Any,
}],
rows: Vec::new(),
builtin: Some(BuiltinProc::ApocCreateRelationship),
});
#[cfg(feature = "apoc-create")]
for (name, builtin) in [
("addLabels", BuiltinProc::ApocCreateAddLabels),
("removeLabels", BuiltinProc::ApocCreateRemoveLabels),
("setLabels", BuiltinProc::ApocCreateSetLabels),
] {
self.register(Procedure {
qualified_name: vec!["apoc".into(), "create".into(), name.into()],
inputs: vec![
ProcArgSpec {
name: "nodes".into(),
ty: ProcType::Any,
},
ProcArgSpec {
name: "labels".into(),
ty: ProcType::Any,
},
],
outputs: vec![ProcOutSpec {
name: "node".into(),
ty: ProcType::Any,
}],
rows: Vec::new(),
builtin: Some(builtin),
});
}
#[cfg(feature = "apoc-create")]
self.register(Procedure {
qualified_name: vec!["apoc".into(), "create".into(), "setProperty".into()],
inputs: vec![
ProcArgSpec {
name: "nodes".into(),
ty: ProcType::Any,
},
ProcArgSpec {
name: "key".into(),
ty: ProcType::String,
},
ProcArgSpec {
name: "value".into(),
ty: ProcType::Any,
},
],
outputs: vec![ProcOutSpec {
name: "node".into(),
ty: ProcType::Any,
}],
rows: Vec::new(),
builtin: Some(BuiltinProc::ApocCreateSetProperty),
});
#[cfg(feature = "apoc-create")]
self.register(Procedure {
qualified_name: vec!["apoc".into(), "create".into(), "setRelProperty".into()],
inputs: vec![
ProcArgSpec {
name: "rels".into(),
ty: ProcType::Any,
},
ProcArgSpec {
name: "key".into(),
ty: ProcType::String,
},
ProcArgSpec {
name: "value".into(),
ty: ProcType::Any,
},
],
outputs: vec![ProcOutSpec {
name: "rel".into(),
ty: ProcType::Any,
}],
rows: Vec::new(),
builtin: Some(BuiltinProc::ApocCreateSetRelProperty),
});
#[cfg(feature = "apoc-meta")]
self.register(Procedure {
qualified_name: vec!["apoc".into(), "meta".into(), "schema".into()],
inputs: Vec::new(),
outputs: vec![ProcOutSpec {
name: "value".into(),
ty: ProcType::Any,
}],
rows: Vec::new(),
builtin: Some(BuiltinProc::ApocMetaSchema),
});
#[cfg(feature = "apoc-refactor")]
self.register(Procedure {
qualified_name: vec!["apoc".into(), "refactor".into(), "setType".into()],
inputs: vec![
ProcArgSpec {
name: "rel".into(),
ty: ProcType::Any,
},
ProcArgSpec {
name: "newType".into(),
ty: ProcType::String,
},
],
outputs: vec![ProcOutSpec {
name: "rel".into(),
ty: ProcType::Any,
}],
rows: Vec::new(),
builtin: Some(BuiltinProc::ApocRefactorSetType),
});
#[cfg(feature = "apoc-path")]
self.register(Procedure {
qualified_name: vec!["apoc".into(), "path".into(), "expand".into()],
inputs: vec![
ProcArgSpec {
name: "startNode".into(),
ty: ProcType::Any,
},
ProcArgSpec {
name: "relationshipFilter".into(),
ty: ProcType::Any,
},
ProcArgSpec {
name: "labelFilter".into(),
ty: ProcType::Any,
},
ProcArgSpec {
name: "minLevel".into(),
ty: ProcType::Any,
},
ProcArgSpec {
name: "maxLevel".into(),
ty: ProcType::Any,
},
],
outputs: vec![ProcOutSpec {
name: "path".into(),
ty: ProcType::Any,
}],
rows: Vec::new(),
builtin: Some(BuiltinProc::ApocPathExpand),
});
#[cfg(feature = "apoc-path")]
self.register(Procedure {
qualified_name: vec!["apoc".into(), "path".into(), "expandConfig".into()],
inputs: vec![
ProcArgSpec {
name: "startNode".into(),
ty: ProcType::Any,
},
ProcArgSpec {
name: "config".into(),
ty: ProcType::Any,
},
],
outputs: vec![ProcOutSpec {
name: "path".into(),
ty: ProcType::Any,
}],
rows: Vec::new(),
builtin: Some(BuiltinProc::ApocPathExpandConfig),
});
#[cfg(feature = "apoc-path")]
self.register(Procedure {
qualified_name: vec!["apoc".into(), "path".into(), "subgraphNodes".into()],
inputs: vec![
ProcArgSpec {
name: "startNode".into(),
ty: ProcType::Any,
},
ProcArgSpec {
name: "config".into(),
ty: ProcType::Any,
},
],
outputs: vec![ProcOutSpec {
name: "node".into(),
ty: ProcType::Any,
}],
rows: Vec::new(),
builtin: Some(BuiltinProc::ApocPathSubgraphNodes),
});
#[cfg(feature = "apoc-path")]
self.register(Procedure {
qualified_name: vec!["apoc".into(), "path".into(), "subgraphAll".into()],
inputs: vec![
ProcArgSpec {
name: "startNode".into(),
ty: ProcType::Any,
},
ProcArgSpec {
name: "config".into(),
ty: ProcType::Any,
},
],
outputs: vec![
ProcOutSpec {
name: "nodes".into(),
ty: ProcType::Any,
},
ProcOutSpec {
name: "relationships".into(),
ty: ProcType::Any,
},
],
rows: Vec::new(),
builtin: Some(BuiltinProc::ApocPathSubgraphAll),
});
#[cfg(feature = "apoc-path")]
self.register(Procedure {
qualified_name: vec!["apoc".into(), "path".into(), "spanningTree".into()],
inputs: vec![
ProcArgSpec {
name: "startNode".into(),
ty: ProcType::Any,
},
ProcArgSpec {
name: "config".into(),
ty: ProcType::Any,
},
],
outputs: vec![ProcOutSpec {
name: "path".into(),
ty: ProcType::Any,
}],
rows: Vec::new(),
builtin: Some(BuiltinProc::ApocPathSpanningTree),
});
#[cfg(feature = "apoc-cypher")]
self.register(Procedure {
qualified_name: vec!["apoc".into(), "cypher".into(), "run".into()],
inputs: vec![
ProcArgSpec {
name: "cypher".into(),
ty: ProcType::String,
},
ProcArgSpec {
name: "params".into(),
ty: ProcType::Any,
},
],
outputs: vec![ProcOutSpec {
name: "value".into(),
ty: ProcType::Any,
}],
rows: Vec::new(),
builtin: Some(BuiltinProc::ApocCypherRun),
});
#[cfg(feature = "apoc-cypher")]
self.register(Procedure {
qualified_name: vec!["apoc".into(), "cypher".into(), "doIt".into()],
inputs: vec![
ProcArgSpec {
name: "cypher".into(),
ty: ProcType::String,
},
ProcArgSpec {
name: "params".into(),
ty: ProcType::Any,
},
],
outputs: vec![ProcOutSpec {
name: "value".into(),
ty: ProcType::Any,
}],
rows: Vec::new(),
builtin: Some(BuiltinProc::ApocCypherDoIt),
});
#[cfg(feature = "apoc-load")]
self.register(Procedure {
qualified_name: vec!["apoc".into(), "load".into(), "json".into()],
inputs: vec![
ProcArgSpec {
name: "urlOrPath".into(),
ty: ProcType::String,
},
ProcArgSpec {
name: "path".into(),
ty: ProcType::Any,
},
],
outputs: vec![ProcOutSpec {
name: "value".into(),
ty: ProcType::Any,
}],
rows: Vec::new(),
builtin: Some(BuiltinProc::ApocLoadJson),
});
#[cfg(feature = "apoc-load")]
self.register(Procedure {
qualified_name: vec!["apoc".into(), "load".into(), "csv".into()],
inputs: vec![
ProcArgSpec {
name: "urlOrPath".into(),
ty: ProcType::String,
},
ProcArgSpec {
name: "config".into(),
ty: ProcType::Any,
},
],
outputs: vec![
ProcOutSpec {
name: "lineNo".into(),
ty: ProcType::Integer,
},
ProcOutSpec {
name: "list".into(),
ty: ProcType::Any,
},
ProcOutSpec {
name: "map".into(),
ty: ProcType::Any,
},
],
rows: Vec::new(),
builtin: Some(BuiltinProc::ApocLoadCsv),
});
#[cfg(feature = "apoc-export")]
{
let all_inputs = || {
vec![
ProcArgSpec {
name: "file".into(),
ty: ProcType::String,
},
ProcArgSpec {
name: "config".into(),
ty: ProcType::Any,
},
]
};
let query_inputs = || {
vec![
ProcArgSpec {
name: "query".into(),
ty: ProcType::String,
},
ProcArgSpec {
name: "file".into(),
ty: ProcType::String,
},
ProcArgSpec {
name: "config".into(),
ty: ProcType::Any,
},
]
};
let stats_outputs = || {
vec![
ProcOutSpec {
name: "file".into(),
ty: ProcType::String,
},
ProcOutSpec {
name: "source".into(),
ty: ProcType::String,
},
ProcOutSpec {
name: "format".into(),
ty: ProcType::String,
},
ProcOutSpec {
name: "nodes".into(),
ty: ProcType::Integer,
},
ProcOutSpec {
name: "relationships".into(),
ty: ProcType::Integer,
},
ProcOutSpec {
name: "properties".into(),
ty: ProcType::Integer,
},
ProcOutSpec {
name: "rows".into(),
ty: ProcType::Integer,
},
ProcOutSpec {
name: "time".into(),
ty: ProcType::Integer,
},
]
};
for (ns, fn_name, is_query, variant) in [
("csv", "all", false, BuiltinProc::ApocExportCsvAll),
("csv", "query", true, BuiltinProc::ApocExportCsvQuery),
("json", "all", false, BuiltinProc::ApocExportJsonAll),
("json", "query", true, BuiltinProc::ApocExportJsonQuery),
("cypher", "all", false, BuiltinProc::ApocExportCypherAll),
("cypher", "query", true, BuiltinProc::ApocExportCypherQuery),
] {
self.register(Procedure {
qualified_name: vec!["apoc".into(), "export".into(), ns.into(), fn_name.into()],
inputs: if is_query {
query_inputs()
} else {
all_inputs()
},
outputs: stats_outputs(),
rows: Vec::new(),
builtin: Some(variant),
});
}
}
#[cfg(feature = "apoc-trigger")]
{
self.register(Procedure {
qualified_name: vec!["apoc".into(), "trigger".into(), "install".into()],
inputs: vec![
ProcArgSpec {
name: "databaseName".into(),
ty: ProcType::String,
},
ProcArgSpec {
name: "name".into(),
ty: ProcType::String,
},
ProcArgSpec {
name: "statement".into(),
ty: ProcType::String,
},
ProcArgSpec {
name: "selector".into(),
ty: ProcType::Any,
},
ProcArgSpec {
name: "config".into(),
ty: ProcType::Any,
},
],
outputs: vec![
ProcOutSpec {
name: "name".into(),
ty: ProcType::String,
},
ProcOutSpec {
name: "query".into(),
ty: ProcType::String,
},
ProcOutSpec {
name: "installed".into(),
ty: ProcType::Boolean,
},
ProcOutSpec {
name: "previous".into(),
ty: ProcType::Any,
},
],
rows: Vec::new(),
builtin: Some(BuiltinProc::ApocTriggerInstall),
});
self.register(Procedure {
qualified_name: vec!["apoc".into(), "trigger".into(), "drop".into()],
inputs: vec![
ProcArgSpec {
name: "databaseName".into(),
ty: ProcType::String,
},
ProcArgSpec {
name: "name".into(),
ty: ProcType::String,
},
],
outputs: vec![
ProcOutSpec {
name: "name".into(),
ty: ProcType::String,
},
ProcOutSpec {
name: "removed".into(),
ty: ProcType::Boolean,
},
],
rows: Vec::new(),
builtin: Some(BuiltinProc::ApocTriggerDrop),
});
self.register(Procedure {
qualified_name: vec!["apoc".into(), "trigger".into(), "list".into()],
inputs: Vec::new(),
outputs: vec![
ProcOutSpec {
name: "name".into(),
ty: ProcType::String,
},
ProcOutSpec {
name: "query".into(),
ty: ProcType::String,
},
ProcOutSpec {
name: "phase".into(),
ty: ProcType::String,
},
ProcOutSpec {
name: "installed_at".into(),
ty: ProcType::Integer,
},
ProcOutSpec {
name: "paused".into(),
ty: ProcType::Boolean,
},
],
rows: Vec::new(),
builtin: Some(BuiltinProc::ApocTriggerList),
});
for (fn_name, variant) in [
("start", BuiltinProc::ApocTriggerStart),
("stop", BuiltinProc::ApocTriggerStop),
] {
self.register(Procedure {
qualified_name: vec!["apoc".into(), "trigger".into(), fn_name.into()],
inputs: vec![
ProcArgSpec {
name: "databaseName".into(),
ty: ProcType::String,
},
ProcArgSpec {
name: "name".into(),
ty: ProcType::String,
},
],
outputs: vec![
ProcOutSpec {
name: "name".into(),
ty: ProcType::String,
},
ProcOutSpec {
name: "paused".into(),
ty: ProcType::Boolean,
},
],
rows: Vec::new(),
builtin: Some(variant),
});
}
}
}
}