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}