stately_arrow/backend.rs
1//! Backend trait and metadata types for data source connectors.
2//!
3//! This module defines the core abstractions for integrating data sources with `DataFusion`:
4//!
5//! - [`Backend`]: Trait that data source implementations must implement to be queryable.
6//! - [`BackendMetadata`]: Static metadata describing a backend's capabilities and kind.
7//! - [`ConnectionMetadata`]: Runtime metadata for a specific connector instance.
8//! - [`Capability`]: What operations a connector supports (SQL execution, listing).
9//! - [`ConnectionKind`]: The type of data source (object store, database, etc.).
10
11use async_trait::async_trait;
12use datafusion::execution::context::SessionContext;
13
14use crate::error::{Error, Result};
15use crate::response::ListSummary;
16
17/// Runtime behaviour for a connector that can be queried via the viewer.
18///
19/// Implement this trait to create a new data source backend. The backend is responsible
20/// for preparing the `DataFusion` session and providing listing capabilities.
21///
22/// # Example
23///
24/// ```rust,ignore
25/// use async_trait::async_trait;
26/// use stately_arrow::{Backend, ConnectionMetadata, ListSummary, Result};
27///
28/// pub struct MyBackend {
29/// metadata: ConnectionMetadata,
30/// }
31///
32/// #[async_trait]
33/// impl Backend for MyBackend {
34/// fn connection(&self) -> &ConnectionMetadata {
35/// &self.metadata
36/// }
37///
38/// async fn prepare_session(&self, session: &SessionContext) -> Result<()> {
39/// // Register catalogs, tables, or object stores
40/// Ok(())
41/// }
42///
43/// async fn list(&self, database: Option<&str>) -> Result<ListSummary> {
44/// // Return available tables/files
45/// Ok(ListSummary::Tables(vec![]))
46/// }
47/// }
48/// ```
49#[async_trait]
50pub trait Backend: Send + Sync {
51 /// Returns metadata about this connection instance.
52 fn connection(&self) -> &ConnectionMetadata;
53
54 /// Prepare the `DataFusion` session before queries run.
55 ///
56 /// Use this to register catalogs, schemas, tables, or object stores
57 /// that queries against this connector will need.
58 async fn prepare_session(&self, _session: &SessionContext) -> Result<()> { Ok(()) }
59
60 /// List tables, files, or other queryable items exposed by this connector.
61 ///
62 /// The `database` parameter allows filtering by database/schema for
63 /// database connectors, or by path prefix for object store connectors.
64 async fn list(&self, _database: Option<&str>) -> Result<ListSummary> {
65 Err(Error::UnsupportedConnector("Connector does not support table listing".into()))
66 }
67}
68
69impl std::fmt::Debug for &dyn Backend {
70 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
71 f.debug_struct("Backend").field("connection", self.connection()).finish()
72 }
73}
74
75/// Static metadata describing a backend implementation.
76///
77/// Backends provide this metadata to indicate their type and capabilities.
78/// Use the builder methods to construct instances.
79#[non_exhaustive]
80#[derive(
81 Debug,
82 Clone,
83 PartialEq,
84 Eq,
85 Hash,
86 serde::Serialize,
87 serde::Deserialize,
88 utoipa::ToResponse,
89 utoipa::ToSchema,
90)]
91pub struct BackendMetadata {
92 /// The kind of data source this backend connects to.
93 pub kind: ConnectionKind,
94 /// Capabilities this backend supports.
95 pub capabilities: Vec<Capability>,
96}
97
98impl BackendMetadata {
99 /// Create new backend metadata with default capabilities (`ExecuteSql` + `List`).
100 pub fn new(kind: ConnectionKind) -> Self {
101 Self { kind, capabilities: vec![Capability::ExecuteSql, Capability::List] }
102 }
103
104 /// Set the capabilities for this backend.
105 #[must_use]
106 pub fn with_capabilities(mut self, capabilities: Vec<Capability>) -> Self {
107 self.capabilities = capabilities;
108 self
109 }
110}
111
112/// Runtime metadata describing a connector instance.
113///
114/// This combines the connector's identity with its backend metadata,
115/// including the `DataFusion` catalog it's registered under.
116#[derive(
117 Debug,
118 Clone,
119 PartialEq,
120 Eq,
121 Hash,
122 serde::Serialize,
123 serde::Deserialize,
124 utoipa::ToResponse,
125 utoipa::ToSchema,
126)]
127pub struct ConnectionMetadata {
128 /// Unique identifier for this connector.
129 pub id: String,
130 /// Human-readable name.
131 pub name: String,
132 /// The `DataFusion` catalog this connector is registered under.
133 pub catalog: Option<String>,
134 /// Backend metadata (kind and capabilities).
135 pub metadata: BackendMetadata,
136}
137
138impl ConnectionMetadata {
139 /// Check if this connector has a specific capability.
140 pub fn has(&self, capability: Capability) -> bool {
141 self.metadata.capabilities.contains(&capability)
142 }
143}
144
145/// Capabilities a connector can expose.
146#[non_exhaustive]
147#[derive(
148 Debug, Clone, Copy, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize, utoipa::ToSchema,
149)]
150#[serde(rename_all = "snake_case")]
151pub enum Capability {
152 /// Connector supports executing ad-hoc SQL queries through `DataFusion`.
153 ExecuteSql,
154 /// Connector can enumerate available tables/files for browsing.
155 List,
156}
157
158/// The type of data source a connector connects to.
159#[non_exhaustive]
160#[derive(
161 Debug, Clone, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize, utoipa::ToSchema,
162)]
163#[serde(rename_all = "snake_case")]
164pub enum ConnectionKind {
165 /// Object storage (S3, GCS, Azure, local filesystem).
166 ObjectStore,
167 /// Relational database (`ClickHouse`, `PostgreSQL`, etc.).
168 Database,
169 /// Custom connector type.
170 Other(String),
171}
172
173impl AsRef<str> for ConnectionKind {
174 fn as_ref(&self) -> &str {
175 match self {
176 ConnectionKind::ObjectStore => "object_store",
177 ConnectionKind::Database => "database",
178 ConnectionKind::Other(kind) => kind,
179 }
180 }
181}