Skip to main content

uni_plugin/traits/
catalog.rs

1//! Catalog / virtual-schema plugins + replacement scans.
2
3use std::sync::Arc;
4
5use arrow_schema::SchemaRef;
6use datafusion::execution::SendableRecordBatchStream;
7use datafusion::logical_expr::Expr;
8use datafusion::physical_plan::Statistics;
9use smol_str::SmolStr;
10
11use crate::errors::FnError;
12use crate::qname::QName;
13
14/// A catalog provider exposing labels / edge-types not backed by `uni-store`.
15pub trait CatalogProvider: Send + Sync {
16    /// Catalog name (used as a prefix in qualified label / edge references).
17    fn name(&self) -> &str;
18
19    /// Enumerate labels in this catalog.
20    ///
21    /// # Errors
22    ///
23    /// Returns [`FnError`] if the listing fails.
24    fn list_labels(&self) -> Result<Vec<CatalogLabel>, FnError>;
25
26    /// Enumerate edge types in this catalog.
27    ///
28    /// # Errors
29    ///
30    /// Returns [`FnError`] if the listing fails.
31    fn list_edge_types(&self) -> Result<Vec<CatalogEdgeType>, FnError>;
32
33    /// Resolve a label name to a queryable table reference.
34    fn resolve_label(&self, label: &str) -> Option<Arc<dyn CatalogTable>>;
35
36    /// Resolve an edge type name.
37    fn resolve_edge_type(&self, edge: &str) -> Option<Arc<dyn CatalogTable>>;
38}
39
40/// Catalog-declared label descriptor.
41#[derive(Clone, Debug)]
42pub struct CatalogLabel {
43    /// Label name.
44    pub name: SmolStr,
45    /// Optional human description.
46    pub doc: String,
47}
48
49/// Catalog-declared edge-type descriptor.
50#[derive(Clone, Debug)]
51pub struct CatalogEdgeType {
52    /// Edge type name.
53    pub name: SmolStr,
54    /// Optional human description.
55    pub doc: String,
56}
57
58/// A queryable catalog table — like a DataFusion `TableProvider` but in the
59/// plugin namespace.
60pub trait CatalogTable: Send + Sync {
61    /// Schema of rows this table produces.
62    fn schema(&self) -> SchemaRef;
63
64    /// Stream rows matching the optional projection, filters, and limit.
65    ///
66    /// # Errors
67    ///
68    /// Returns [`FnError`] if the scan cannot start.
69    fn scan(
70        &self,
71        projection: Option<&[usize]>,
72        filters: &[Expr],
73        limit: Option<usize>,
74    ) -> Result<SendableRecordBatchStream, FnError>;
75
76    /// Cardinality / size statistics, if known.
77    fn statistics(&self) -> Option<Statistics> {
78        None
79    }
80}
81
82/// Request to a [`ReplacementScanProvider`].
83#[derive(Debug)]
84#[non_exhaustive]
85pub enum ReplacementRequest<'a> {
86    /// Unknown label encountered in `MATCH (n:Foo)`.
87    Label(&'a str),
88    /// Unknown procedure encountered in `CALL`.
89    Procedure(&'a QName),
90    /// Unknown scalar function encountered in an expression.
91    Function(&'a QName),
92}
93
94/// Replacement to use in place of an unknown identifier.
95#[non_exhaustive]
96pub enum Replacement {
97    /// Serve via a catalog table.
98    CatalogTable(Arc<dyn CatalogTable>),
99    /// Rewrite the call to a different procedure.
100    Procedure(QName),
101    /// Rewrite the call to a different scalar function.
102    Function(QName),
103}
104
105impl std::fmt::Debug for Replacement {
106    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
107        match self {
108            Self::CatalogTable(_) => f
109                .debug_tuple("CatalogTable")
110                .field(&"<dyn CatalogTable>")
111                .finish(),
112            Self::Procedure(q) => f.debug_tuple("Procedure").field(q).finish(),
113            Self::Function(q) => f.debug_tuple("Function").field(q).finish(),
114        }
115    }
116}
117
118/// Replacement-scan provider — DuckDB-style auto-routing of unknown
119/// identifiers.
120pub trait ReplacementScanProvider: Send + Sync {
121    /// Attempt to provide a [`Replacement`] for the given request.
122    fn replace(&self, request: &ReplacementRequest<'_>) -> Option<Replacement>;
123}