use crate::error::Result;
use crate::reader::GraphReader;
use crate::value::Value;
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,
}
pub type ProcRow = HashMap<String, Value>;
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 resolve_rows(&self, reader: &dyn GraphReader) -> Result<Vec<ProcRow>> {
match self.builtin {
None => Ok(self.rows.clone()),
Some(BuiltinProc::DbLabels) => builtin_db_labels(reader),
Some(BuiltinProc::DbRelationshipTypes) => builtin_db_relationship_types(reader),
Some(BuiltinProc::DbPropertyKeys) => builtin_db_property_keys(reader),
Some(BuiltinProc::DbConstraints) => builtin_db_constraints(reader),
}
}
}
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())
}
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>,
}
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("."))
}
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),
});
}
}