marsdb_query/procedure.rs
1//! Pluggable stored-procedure support (`CALL proc(...) YIELD ...`).
2//!
3//! MarsDB itself ships no built-in procedures -- this only defines the
4//! interface an embedder (or a test harness, see `marsdb-tck`) implements
5//! to make `CALL` resolve to something real. `Executor` never invents
6//! procedure behavior on its own; every `CALL` fails with "procedure not
7//! found" unless a provider is supplied via `ExecutionOptions::procedures`.
8
9use std::sync::Arc;
10
11use crate::error::QueryError;
12use crate::value::Value;
13
14/// A procedure's declared shape -- everything `Executor` needs to validate
15/// a `CALL` at the point it's about to run (arity, coarse argument-type
16/// compatibility, output column names for `YIELD`), before ever asking
17/// `ProcedureProvider::call` to actually produce rows.
18#[derive(Debug, Clone)]
19pub struct ProcedureSignature {
20 /// Declared input parameter names, in order -- used both for the
21 /// `InvalidNumberOfArguments` arity check and for resolving a
22 /// standalone call's *implicit* arguments (`CALL proc`, no parens) from
23 /// same-named `$params`.
24 pub inputs: Vec<String>,
25 /// Each input's declared type, in the same order as `inputs` -- one of
26 /// `INTEGER`/`FLOAT`/`NUMBER`/`STRING`/`BOOLEAN` (optionally suffixed
27 /// with `?` for nullable, which every real procedure signature is in
28 /// practice; the `?` doesn't change compatibility checking here since
29 /// `Value::Null` is always accepted regardless of declared type).
30 /// Unrecognized type names are tolerated (treated as "accept
31 /// anything") rather than rejected -- this is a coarse compile-time
32 /// sanity check, not a full type system.
33 pub input_types: Vec<String>,
34 /// Declared output column names, in order -- what `YIELD *`/an
35 /// unqualified `YIELD name` (no `AS`) binds, and what `call`'s own
36 /// returned rows are positionally shaped as.
37 pub outputs: Vec<String>,
38}
39
40/// Implemented by whatever embeds MarsDB to make `CALL` resolve to real
41/// behavior. `Executor` calls `signature` once per `CALL` (for compile-
42/// time-shaped validation) and `call` once per input row (standalone: once
43/// total, since there's no input row to iterate).
44pub trait ProcedureProvider: Send + Sync {
45 /// `None` means "no such procedure" -- `Executor` reports this as a
46 /// `ProcedureError`/`ProcedureNotFound`-flavored `QueryError` (no
47 /// dedicated `QueryError` variant for this taxonomy — see
48 /// `CYPHER_COVERAGE.md`'s own error-taxonomy scope note; any error is
49 /// enough for TCK's coarse checking).
50 fn signature(&self, name: &str) -> Option<ProcedureSignature>;
51 /// `args` is already fully evaluated and type-checked against
52 /// `signature(name)`'s `input_types`, in declared-input order. Returns
53 /// the procedure's output rows, each with exactly `signature(name)`'s
54 /// `outputs.len()` values in that same order -- `Executor` never
55 /// inspects a row's shape beyond that, so how a provider produces them
56 /// (a fixed lookup table, real computation, ...) is entirely up to it.
57 fn call(&self, name: &str, args: &[Value]) -> Result<Vec<Vec<Value>>, QueryError>;
58}
59
60/// `Arc<dyn ProcedureProvider>` wrapper -- same "manual `Debug`, derived
61/// `Clone`" shape `executor::ExecutionObserver` already uses for its own
62/// `Arc<dyn Fn(..)>`, so `ExecutionOptions` (which embeds this) can keep
63/// deriving both.
64#[derive(Clone)]
65pub struct Procedures(pub Arc<dyn ProcedureProvider>);
66
67impl std::fmt::Debug for Procedures {
68 fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
69 formatter.write_str("Procedures(..)")
70 }
71}