Skip to main content

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}