1use crate::error::Result;
2use crate::reader::GraphReader;
3use crate::value::Value;
4use meshdb_core::Property;
5use std::collections::{BTreeSet, HashMap};
6
7#[derive(Debug, Clone, Copy, PartialEq, Eq)]
13pub enum ProcType {
14 String,
15 Integer,
16 Float,
17 Number,
18 Boolean,
19 Any,
20}
21
22impl ProcType {
23 pub fn parse(s: &str) -> Self {
24 let trimmed = s.trim().trim_end_matches('?').trim();
25 match trimmed.to_ascii_uppercase().as_str() {
26 "STRING" => ProcType::String,
27 "INTEGER" | "INT" => ProcType::Integer,
28 "FLOAT" => ProcType::Float,
29 "NUMBER" | "NUMERIC" => ProcType::Number,
30 "BOOLEAN" | "BOOL" => ProcType::Boolean,
31 _ => ProcType::Any,
32 }
33 }
34
35 pub fn accepts(&self, value: &Value) -> bool {
40 if matches!(value, Value::Null) {
41 return true;
42 }
43 match (self, value) {
44 (ProcType::Any, _) => true,
45 (ProcType::String, Value::Property(Property::String(_))) => true,
46 (ProcType::Integer, Value::Property(Property::Int64(_))) => true,
47 (ProcType::Float, Value::Property(Property::Float64(_))) => true,
48 (ProcType::Float, Value::Property(Property::Int64(_))) => true,
49 (ProcType::Number, Value::Property(Property::Int64(_))) => true,
50 (ProcType::Number, Value::Property(Property::Float64(_))) => true,
51 (ProcType::Boolean, Value::Property(Property::Bool(_))) => true,
52 _ => false,
53 }
54 }
55}
56
57#[derive(Debug, Clone)]
58pub struct ProcArgSpec {
59 pub name: String,
60 pub ty: ProcType,
61}
62
63#[derive(Debug, Clone)]
64pub struct ProcOutSpec {
65 pub name: String,
66 pub ty: ProcType,
67}
68
69#[derive(Debug, Clone)]
81pub struct Procedure {
82 pub qualified_name: Vec<String>,
83 pub inputs: Vec<ProcArgSpec>,
84 pub outputs: Vec<ProcOutSpec>,
85 pub rows: Vec<ProcRow>,
86 pub builtin: Option<BuiltinProc>,
87}
88
89#[derive(Debug, Clone, Copy, PartialEq, Eq)]
94pub enum BuiltinProc {
95 DbLabels,
97 DbRelationshipTypes,
99 DbPropertyKeys,
102 DbConstraints,
106}
107
108pub type ProcRow = HashMap<String, Value>;
113
114impl Procedure {
115 pub fn row_matches(&self, row: &ProcRow, args: &[Value]) -> bool {
122 for (spec, arg) in self.inputs.iter().zip(args.iter()) {
123 let cell = row.get(&spec.name).unwrap_or(&Value::Null);
124 if !values_equal_for_procedure(cell, arg) {
125 return false;
126 }
127 }
128 true
129 }
130
131 pub fn resolve_rows(&self, reader: &dyn GraphReader) -> Result<Vec<ProcRow>> {
135 match self.builtin {
136 None => Ok(self.rows.clone()),
137 Some(BuiltinProc::DbLabels) => builtin_db_labels(reader),
138 Some(BuiltinProc::DbRelationshipTypes) => builtin_db_relationship_types(reader),
139 Some(BuiltinProc::DbPropertyKeys) => builtin_db_property_keys(reader),
140 Some(BuiltinProc::DbConstraints) => builtin_db_constraints(reader),
141 }
142 }
143}
144
145fn str_row(column: &str, value: String) -> ProcRow {
146 let mut row = HashMap::new();
147 row.insert(column.to_string(), Value::Property(Property::String(value)));
148 row
149}
150
151fn builtin_db_labels(reader: &dyn GraphReader) -> Result<Vec<ProcRow>> {
152 let mut labels: BTreeSet<String> = BTreeSet::new();
153 for id in reader.all_node_ids()? {
154 if let Some(n) = reader.get_node(id)? {
155 for l in n.labels {
156 labels.insert(l);
157 }
158 }
159 }
160 Ok(labels.into_iter().map(|l| str_row("label", l)).collect())
161}
162
163fn builtin_db_relationship_types(reader: &dyn GraphReader) -> Result<Vec<ProcRow>> {
164 let mut types: BTreeSet<String> = BTreeSet::new();
165 for id in reader.all_node_ids()? {
166 for (edge_id, _) in reader.outgoing(id)? {
167 if let Some(e) = reader.get_edge(edge_id)? {
168 types.insert(e.edge_type);
169 }
170 }
171 }
172 Ok(types
173 .into_iter()
174 .map(|t| str_row("relationshipType", t))
175 .collect())
176}
177
178fn builtin_db_constraints(reader: &dyn GraphReader) -> Result<Vec<ProcRow>> {
179 let specs = reader.list_property_constraints()?;
180 Ok(specs
181 .into_iter()
182 .map(|spec| {
183 let mut row: ProcRow = HashMap::new();
184 row.insert("name".into(), Value::Property(Property::String(spec.name)));
185 let (scope_tag, target) = match spec.scope {
186 meshdb_storage::ConstraintScope::Node(l) => ("NODE", l),
187 meshdb_storage::ConstraintScope::Relationship(t) => ("RELATIONSHIP", t),
188 };
189 row.insert(
190 "scope".into(),
191 Value::Property(Property::String(scope_tag.into())),
192 );
193 row.insert("label".into(), Value::Property(Property::String(target)));
194 let props: Vec<Property> = spec.properties.into_iter().map(Property::String).collect();
195 row.insert("properties".into(), Value::Property(Property::List(props)));
196 row.insert(
197 "type".into(),
198 Value::Property(Property::String(spec.kind.as_string())),
199 );
200 row
201 })
202 .collect())
203}
204
205fn builtin_db_property_keys(reader: &dyn GraphReader) -> Result<Vec<ProcRow>> {
206 let mut keys: BTreeSet<String> = BTreeSet::new();
207 for id in reader.all_node_ids()? {
208 if let Some(n) = reader.get_node(id)? {
209 for k in n.properties.keys() {
210 keys.insert(k.clone());
211 }
212 for (edge_id, _) in reader.outgoing(id)? {
213 if let Some(e) = reader.get_edge(edge_id)? {
214 for k in e.properties.keys() {
215 keys.insert(k.clone());
216 }
217 }
218 }
219 }
220 }
221 Ok(keys
222 .into_iter()
223 .map(|k| str_row("propertyKey", k))
224 .collect())
225}
226
227fn values_equal_for_procedure(a: &Value, b: &Value) -> bool {
228 match (a, b) {
229 (Value::Null, Value::Null) => true,
230 (Value::Null, _) | (_, Value::Null) => false,
231 (Value::Property(Property::Int64(x)), Value::Property(Property::Int64(y))) => x == y,
232 (Value::Property(Property::Float64(x)), Value::Property(Property::Float64(y))) => x == y,
233 (Value::Property(Property::Int64(i)), Value::Property(Property::Float64(f)))
234 | (Value::Property(Property::Float64(f)), Value::Property(Property::Int64(i))) => {
235 *f == (*i as f64)
236 }
237 (Value::Property(Property::String(x)), Value::Property(Property::String(y))) => x == y,
238 (Value::Property(Property::Bool(x)), Value::Property(Property::Bool(y))) => x == y,
239 _ => a == b,
240 }
241}
242
243#[derive(Debug, Clone, Default)]
250pub struct ProcedureRegistry {
251 procs: HashMap<String, Procedure>,
252}
253
254impl ProcedureRegistry {
255 pub fn new() -> Self {
256 Self::default()
257 }
258
259 pub fn register(&mut self, proc: Procedure) {
260 let key = proc.qualified_name.join(".");
261 self.procs.insert(key, proc);
262 }
263
264 pub fn get(&self, qualified_name: &[String]) -> Option<&Procedure> {
265 self.procs.get(&qualified_name.join("."))
266 }
267
268 pub fn register_defaults(&mut self) {
274 self.register(Procedure {
275 qualified_name: vec!["db".into(), "labels".into()],
276 inputs: Vec::new(),
277 outputs: vec![ProcOutSpec {
278 name: "label".into(),
279 ty: ProcType::String,
280 }],
281 rows: Vec::new(),
282 builtin: Some(BuiltinProc::DbLabels),
283 });
284 self.register(Procedure {
285 qualified_name: vec!["db".into(), "relationshipTypes".into()],
286 inputs: Vec::new(),
287 outputs: vec![ProcOutSpec {
288 name: "relationshipType".into(),
289 ty: ProcType::String,
290 }],
291 rows: Vec::new(),
292 builtin: Some(BuiltinProc::DbRelationshipTypes),
293 });
294 self.register(Procedure {
295 qualified_name: vec!["db".into(), "propertyKeys".into()],
296 inputs: Vec::new(),
297 outputs: vec![ProcOutSpec {
298 name: "propertyKey".into(),
299 ty: ProcType::String,
300 }],
301 rows: Vec::new(),
302 builtin: Some(BuiltinProc::DbPropertyKeys),
303 });
304 self.register(Procedure {
305 qualified_name: vec!["db".into(), "constraints".into()],
306 inputs: Vec::new(),
307 outputs: vec![
308 ProcOutSpec {
309 name: "name".into(),
310 ty: ProcType::String,
311 },
312 ProcOutSpec {
313 name: "scope".into(),
314 ty: ProcType::String,
315 },
316 ProcOutSpec {
317 name: "label".into(),
318 ty: ProcType::String,
319 },
320 ProcOutSpec {
321 name: "properties".into(),
322 ty: ProcType::Any,
327 },
328 ProcOutSpec {
329 name: "type".into(),
330 ty: ProcType::String,
331 },
332 ],
333 rows: Vec::new(),
334 builtin: Some(BuiltinProc::DbConstraints),
335 });
336 }
337}