Skip to main content

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}