Skip to main content

boltr/server/
backend.rs

1//! The `BoltBackend` trait: core abstraction for Bolt server implementations.
2
3use std::collections::HashMap;
4
5use crate::error::BoltError;
6use crate::server::auth::AuthInfo;
7use crate::types::{BoltDict, BoltValue};
8
9/// Opaque handle identifying a Bolt session (one per TCP connection).
10#[derive(Debug, Clone, PartialEq, Eq, Hash)]
11pub struct SessionHandle(pub String);
12
13/// Opaque handle identifying a transaction within a session.
14#[derive(Debug, Clone, PartialEq, Eq, Hash)]
15pub struct TransactionHandle(pub String);
16
17/// Configuration extracted from the HELLO message.
18pub struct SessionConfig {
19    pub user_agent: String,
20    pub database: Option<String>,
21}
22
23/// A session property that can be modified.
24pub enum SessionProperty {
25    Database(String),
26}
27
28/// Transaction access mode.
29#[derive(Debug, Clone, Copy, PartialEq, Eq)]
30pub enum AccessMode {
31    Read,
32    Write,
33}
34
35/// Authentication credentials extracted from HELLO/LOGON.
36#[derive(Debug, Clone)]
37pub struct AuthCredentials {
38    pub scheme: String,
39    pub principal: Option<String>,
40    pub credentials: Option<String>,
41}
42
43/// A single row of query results.
44#[derive(Debug, Clone)]
45pub struct BoltRecord {
46    pub values: Vec<BoltValue>,
47}
48
49/// Metadata about a query result set.
50#[derive(Debug, Clone)]
51pub struct ResultMetadata {
52    pub columns: Vec<String>,
53    pub extra: BoltDict,
54}
55
56/// A complete query result: metadata + records + summary.
57#[derive(Debug, Clone)]
58pub struct ResultStream {
59    pub metadata: ResultMetadata,
60    pub records: Vec<BoltRecord>,
61    pub summary: BoltDict,
62}
63
64/// A server address with a role in the routing table.
65#[derive(Debug, Clone)]
66pub struct RoutingServer {
67    pub addresses: Vec<String>,
68    pub role: String,
69}
70
71/// Routing table returned by the ROUTE message handler.
72#[derive(Debug, Clone)]
73pub struct RoutingTable {
74    /// Time-to-live in seconds for the routing table.
75    pub ttl: i64,
76    /// Database name.
77    pub db: String,
78    /// Server entries with roles (WRITE, READ, ROUTE).
79    pub servers: Vec<RoutingServer>,
80}
81
82/// Extracts bookmarks from a Bolt extra dict.
83///
84/// Drivers send bookmarks as `{"bookmarks": ["bk:1", "bk:2"]}` in the
85/// extra field of BEGIN and RUN messages.
86///
87/// ```
88/// use boltr::types::{BoltDict, BoltValue};
89/// use boltr::server::extract_bookmarks;
90///
91/// let extra = BoltDict::from([
92///     ("bookmarks".to_string(), BoltValue::List(vec![
93///         BoltValue::String("bk:tx-1".to_string()),
94///         BoltValue::String("bk:tx-2".to_string()),
95///     ])),
96/// ]);
97/// let bookmarks = extract_bookmarks(&extra);
98/// assert_eq!(bookmarks, vec!["bk:tx-1", "bk:tx-2"]);
99///
100/// // Returns empty vec when no bookmarks are present.
101/// let empty = extract_bookmarks(&BoltDict::new());
102/// assert!(empty.is_empty());
103/// ```
104pub fn extract_bookmarks(extra: &BoltDict) -> Vec<String> {
105    match extra.get("bookmarks") {
106        Some(BoltValue::List(list)) => list
107            .iter()
108            .filter_map(|v| match v {
109                BoltValue::String(s) => Some(s.clone()),
110                _ => None,
111            })
112            .collect(),
113        _ => Vec::new(),
114    }
115}
116
117/// The core backend trait that Bolt server implementations must provide.
118///
119/// One session maps to one TCP connection. The connection handler calls
120/// these methods in response to Bolt messages.
121///
122/// # Bookmarks
123///
124/// Bookmarks enable causal consistency across transactions. Drivers send
125/// bookmarks in the `extra` dict of BEGIN and RUN messages (key: `"bookmarks"`,
126/// value: list of strings). Use [`extract_bookmarks`] to parse them.
127///
128/// After a successful COMMIT, the server should include `"bookmark"` in the
129/// returned metadata dict. The driver will use this bookmark in subsequent
130/// transactions to ensure causal ordering.
131#[async_trait::async_trait]
132pub trait BoltBackend: Send + Sync + 'static {
133    // -- Session lifecycle --
134
135    /// Create a new session. Called once during HELLO processing.
136    async fn create_session(&self, config: &SessionConfig) -> Result<SessionHandle, BoltError>;
137
138    /// Called after successful authentication to associate identity with a session.
139    ///
140    /// Backends can use this to scope session permissions based on the
141    /// authenticated principal. Default implementation is a no-op.
142    async fn set_session_auth(
143        &self,
144        _session: &SessionHandle,
145        _auth_info: AuthInfo,
146    ) -> Result<(), BoltError> {
147        Ok(())
148    }
149
150    /// Close a session and release resources. Called on GOODBYE or disconnect.
151    async fn close_session(&self, session: &SessionHandle) -> Result<(), BoltError>;
152
153    /// Update a session property (e.g., switch database).
154    async fn configure_session(
155        &self,
156        session: &SessionHandle,
157        property: SessionProperty,
158    ) -> Result<(), BoltError>;
159
160    /// Reset session to clean state (default database, no transaction).
161    async fn reset_session(&self, session: &SessionHandle) -> Result<(), BoltError>;
162
163    // -- Query execution --
164
165    /// Execute a query. The `extra` dict may contain `db`, `language`, `timeout`, etc.
166    async fn execute(
167        &self,
168        session: &SessionHandle,
169        query: &str,
170        parameters: &HashMap<String, BoltValue>,
171        extra: &BoltDict,
172        transaction: Option<&TransactionHandle>,
173    ) -> Result<ResultStream, BoltError>;
174
175    // -- Transactions --
176
177    /// Begin an explicit transaction.
178    async fn begin_transaction(
179        &self,
180        session: &SessionHandle,
181        extra: &BoltDict,
182    ) -> Result<TransactionHandle, BoltError>;
183
184    /// Commit the current explicit transaction.
185    async fn commit(
186        &self,
187        session: &SessionHandle,
188        transaction: &TransactionHandle,
189    ) -> Result<BoltDict, BoltError>;
190
191    /// Roll back the current explicit transaction.
192    async fn rollback(
193        &self,
194        session: &SessionHandle,
195        transaction: &TransactionHandle,
196    ) -> Result<(), BoltError>;
197
198    // -- Server info --
199
200    /// Returns metadata to include in the HELLO SUCCESS response.
201    async fn get_server_info(&self) -> Result<BoltDict, BoltError>;
202
203    // -- Routing --
204
205    /// Returns a routing table for cluster-aware drivers.
206    ///
207    /// The default implementation returns an error indicating that routing
208    /// is not supported. Single-server backends should override this to
209    /// return a table pointing to themselves.
210    async fn route(
211        &self,
212        _routing_context: &BoltDict,
213        _bookmarks: &[String],
214        _db: Option<&str>,
215    ) -> Result<RoutingTable, BoltError> {
216        Err(BoltError::Protocol("routing not supported".into()))
217    }
218}