graphlite_sdk/connection.rs
1//! Database connection and session management
2//!
3//! This module provides the main entry points for working with GraphLite databases.
4//! It follows a similar pattern to rusqlite (SQLite's Rust bindings) but adapted
5//! for graph databases.
6
7use crate::error::{Error, Result};
8use crate::transaction::Transaction;
9use graphlite::{QueryCoordinator, QueryResult};
10use std::sync::Arc;
11
12/// Main entry point for GraphLite database operations
13///
14/// Represents an open connection to a GraphLite database. Despite being an
15/// embedded database, we use "Connection" terminology following the SQLite
16/// convention as it represents the connection to the database files.
17///
18/// # Examples
19///
20/// ```no_run
21/// use graphlite_sdk::GraphLite;
22///
23/// # fn main() -> Result<(), graphlite_sdk::Error> {
24/// // Open a database
25/// let db = GraphLite::open("./mydb")?;
26///
27/// // Create a session for a user
28/// let session = db.session("admin")?;
29///
30/// // Execute a query
31/// let result = session.query("MATCH (n:Person) RETURN n")?;
32/// # Ok(())
33/// # }
34/// ```
35pub struct GraphLite {
36 coordinator: Arc<QueryCoordinator>,
37}
38
39impl GraphLite {
40 /// Open a GraphLite database at the given path
41 ///
42 /// Creates or opens a database at the specified path and initializes
43 /// all necessary components. This is the main entry point for working
44 /// with GraphLite databases.
45 ///
46 /// # Arguments
47 ///
48 /// * `path` - Path to the database directory
49 ///
50 /// # Examples
51 ///
52 /// ```no_run
53 /// use graphlite_sdk::GraphLite;
54 ///
55 /// let db = GraphLite::open("./mydb")?;
56 /// # Ok::<(), graphlite_sdk::Error>(())
57 /// ```
58 pub fn open<P: AsRef<std::path::Path>>(path: P) -> Result<Self> {
59 let coordinator = QueryCoordinator::from_path(path)
60 .map_err(|e| Error::Connection(format!("Failed to open database: {}", e)))?;
61 Ok(GraphLite { coordinator })
62 }
63
64 /// Create a new session for the given user
65 ///
66 /// Sessions provide user context for permissions and security. Each session
67 /// maintains its own transaction state and is isolated from other sessions.
68 ///
69 /// Unlike SQLite which doesn't have sessions, GraphLite uses sessions for:
70 /// - User authentication and permissions
71 /// - Transaction isolation
72 /// - Audit logging
73 ///
74 /// # Arguments
75 ///
76 /// * `username` - Username for the session
77 ///
78 /// # Examples
79 ///
80 /// ```no_run
81 /// # use graphlite_sdk::GraphLite;
82 /// # let db = GraphLite::open("./mydb")?;
83 /// let session = db.session("admin")?;
84 /// # Ok::<(), graphlite_sdk::Error>(())
85 /// ```
86 pub fn session(&self, username: &str) -> Result<Session> {
87 let session_id = self
88 .coordinator
89 .create_simple_session(username)
90 .map_err(|e| Error::Session(format!("Failed to create session: {}", e)))?;
91
92 Ok(Session {
93 id: session_id,
94 coordinator: self.coordinator.clone(),
95 username: username.to_string(),
96 })
97 }
98
99 /// Get access to the underlying QueryCoordinator
100 ///
101 /// Provides direct access to the low-level API when needed for
102 /// advanced operations not yet covered by the SDK.
103 ///
104 /// # Safety
105 ///
106 /// This is an escape hatch for advanced users. Most applications should
107 /// use the high-level SDK API instead.
108 pub fn coordinator(&self) -> &QueryCoordinator {
109 &self.coordinator
110 }
111}
112
113/// Represents an active database session
114///
115/// Sessions provide user context and are required for executing queries.
116/// Unlike SQLite, GraphLite uses sessions for user authentication, permissions,
117/// and transaction isolation.
118///
119/// # Examples
120///
121/// ```no_run
122/// # use graphlite_sdk::GraphLite;
123/// # let db = GraphLite::open("./mydb")?;
124/// let session = db.session("admin")?;
125/// let result = session.query("MATCH (n) RETURN n")?;
126/// # Ok::<(), graphlite_sdk::Error>(())
127/// ```
128pub struct Session {
129 id: String,
130 coordinator: Arc<QueryCoordinator>,
131 username: String,
132}
133
134impl Session {
135 /// Get the session ID
136 ///
137 /// The session ID is used internally for query execution and
138 /// transaction management.
139 pub fn id(&self) -> &str {
140 &self.id
141 }
142
143 /// Get the username associated with this session
144 pub fn username(&self) -> &str {
145 &self.username
146 }
147
148 /// Execute a GQL query in this session
149 ///
150 /// This is the main method for executing queries. For simple read queries,
151 /// this is all you need. For multi-statement operations that need atomicity,
152 /// use transactions instead.
153 ///
154 /// # Arguments
155 ///
156 /// * `query` - GQL query string
157 ///
158 /// # Examples
159 ///
160 /// ```no_run
161 /// # use graphlite_sdk::GraphLite;
162 /// # let db = GraphLite::open("./mydb")?;
163 /// let session = db.session("admin")?;
164 ///
165 /// // Simple query
166 /// let result = session.query("MATCH (n:Person) RETURN n")?;
167 ///
168 /// // Query with parameters (using GQL parameter syntax)
169 /// let result = session.query("MATCH (n:Person {name: 'Alice'}) RETURN n")?;
170 /// # Ok::<(), graphlite_sdk::Error>(())
171 /// ```
172 pub fn query(&self, query: &str) -> Result<QueryResult> {
173 self.coordinator
174 .process_query(query, &self.id)
175 .map_err(|e| Error::Query(format!("Query failed: {}", e)))
176 }
177
178 /// Execute a statement without returning results
179 ///
180 /// This is useful for DDL statements (CREATE SCHEMA, CREATE GRAPH, etc.)
181 /// and DML statements where you don't need the results.
182 ///
183 /// # Arguments
184 ///
185 /// * `statement` - GQL statement to execute
186 ///
187 /// # Examples
188 ///
189 /// ```no_run
190 /// # use graphlite_sdk::GraphLite;
191 /// # let db = GraphLite::open("./mydb")?;
192 /// let session = db.session("admin")?;
193 ///
194 /// // Create a node
195 /// session.execute("CREATE (p:Person {name: 'Alice', age: 30})")?;
196 ///
197 /// // Create a schema
198 /// session.execute("CREATE SCHEMA my_schema")?;
199 /// # Ok::<(), graphlite_sdk::Error>(())
200 /// ```
201 pub fn execute(&self, statement: &str) -> Result<()> {
202 self.coordinator
203 .process_query(statement, &self.id)
204 .map_err(|e| Error::Query(format!("Execute failed: {}", e)))?;
205 Ok(())
206 }
207
208 /// Begin a new transaction
209 ///
210 /// Transactions provide ACID guarantees and can be committed or rolled back.
211 /// Following the rusqlite pattern, transactions will automatically roll back
212 /// when dropped unless explicitly committed.
213 ///
214 /// # Examples
215 ///
216 /// ```no_run
217 /// # use graphlite_sdk::GraphLite;
218 /// # let db = GraphLite::open("./mydb")?;
219 /// let session = db.session("admin")?;
220 ///
221 /// // Transaction with explicit commit
222 /// let mut tx = session.transaction()?;
223 /// tx.execute("CREATE (p:Person {name: 'Alice'})")?;
224 /// tx.execute("CREATE (p:Person {name: 'Bob'})")?;
225 /// tx.commit()?;
226 ///
227 /// // Transaction that auto-rolls back (dropped without commit)
228 /// {
229 /// let mut tx = session.transaction()?;
230 /// tx.execute("CREATE (p:Person {name: 'Charlie'})")?;
231 /// // tx is dropped here, changes are rolled back
232 /// }
233 /// # Ok::<(), graphlite_sdk::Error>(())
234 /// ```
235 pub fn transaction(&self) -> Result<Transaction<'_>> {
236 Transaction::begin(self)
237 }
238
239 /// Get the internal coordinator (for internal SDK use)
240 pub(crate) fn coordinator(&self) -> &QueryCoordinator {
241 &self.coordinator
242 }
243}
244
245#[cfg(test)]
246mod tests {
247
248 #[test]
249 fn test_connection_types_compile() {
250 // Compilation test - ensures types are properly defined
251 }
252}