akar_main/connection/mod.rs
1//! Connection — used to execute queries against a Database.
2//!
3//! Manages the full query lifecycle: parse → bind → plan → optimize → execute.
4//! DDL statements (CREATE/DROP TABLE) update the catalog directly and return
5//! a message result. DML statements (MATCH/RETURN) produce DataChunk results.
6//!
7//! Supports prepared statements via `prepare()` and `execute()` for
8//! parameterized queries.
9//!
10//! # Concurrent Multi-Writer Support
11//!
12//! Write transactions use `TransactionManager::begin_write()` / `commit()` /
13//! `rollback()` with per-transaction `LocalStorage`, `LocalWAL`, and
14//! `ShadowFile` resources held in `txn_resources`. The full commit pipeline
15//! flushes: LocalStorage → tables, LocalWAL → global WAL, ShadowFile → BM.
16
17pub mod copy;
18pub mod ddl;
19pub mod dml;
20pub mod plan_cache;
21pub mod query;
22pub mod standalone_call;
23pub mod substitute;
24pub mod transaction;
25pub mod utils;
26
27use crate::database::Database;
28use crate::prepared_statement::PreparedStatement;
29use plan_cache::{CachedPlan, PlanCache};
30use std::collections::HashMap;
31use std::sync::{Arc, Mutex};
32
33/// Default capacity for the query plan LRU cache.
34const PLAN_CACHE_CAPACITY: usize = 100;
35
36/// Per-transaction resources held during an active write transaction.
37pub(crate) struct TxnResources {
38 pub local_storage: akar_storage::LocalStorage,
39 pub local_wal: akar_storage::LocalWAL,
40 pub shadow_file: akar_storage::ShadowFile,
41}
42
43/// A connection to the database for executing Cypher queries.
44///
45/// Created via [`Connection::new`] with a reference to a [`Database`].
46/// Supports both ad-hoc queries and prepared statements.
47///
48/// # Examples
49///
50/// ```no_run
51/// use akar_main::database::{Database, SystemConfig};
52/// use akar_main::connection::Connection;
53///
54/// let db = std::sync::Arc::new(Database::new("./my_db", SystemConfig::default())?);
55/// let conn = Connection::new(&db);
56///
57/// // DDL
58/// conn.query("CREATE NODE TABLE Person(name STRING, PRIMARY KEY(name))")?;
59///
60/// // DML
61/// conn.query("CREATE (:Person {name: 'Alice'})")?;
62///
63/// // Query
64/// let result = conn.query("MATCH (p:Person) RETURN p.name")?;
65/// assert!(result.success);
66/// # Ok::<(), String>(())
67/// ```
68pub struct Connection {
69 pub(crate) database: Arc<Database>,
70 /// Cache of prepared statements (query → PreparedStatement).
71 pub(crate) statement_cache: Mutex<HashMap<String, PreparedStatement>>,
72 /// LRU cache of optimized query plans keyed by normalized query string.
73 /// Entries are validated against the catalog version on lookup, so DDL
74 /// (via any connection) invalidates them implicitly.
75 pub(crate) plan_cache: Mutex<PlanCache<CachedPlan>>,
76 /// Per-transaction resources keyed by transaction ID.
77 /// Set up when `begin_write()` is called, cleaned up on commit/rollback.
78 pub(crate) txn_resources: Mutex<HashMap<u64, TxnResources>>,
79 /// Set while an explicit `BEGIN TRANSACTION` is open on this connection.
80 /// DDL statements bypass the txn's LocalStorage/ShadowFile and mutate the
81 /// catalog directly, so they cannot be rolled back — they are rejected
82 /// while this flag is set instead of falsely reporting success (P52.28).
83 pub(crate) explicit_txn_active: std::sync::atomic::AtomicBool,
84 /// Lazily-built processor handler callbacks (sequence, schema DDL, query,
85 /// subquery, standalone-call registry), shared across every query on this
86 /// connection. Cached here rather than on `Database` so the handlers'
87 /// strong `Arc<Database>` captures do not create a reference cycle that
88 /// would keep the database (and its file lock) alive forever (P51.47).
89 processor_handlers: std::sync::OnceLock<Arc<crate::connection::query::ProcessorHandlers>>,
90}
91
92impl Connection {
93 /// Create a new connection to the given database.
94 ///
95 /// A connection owns a statement cache and transaction context.
96 /// Multiple connections can coexist on the same `Database` — they
97 /// share the buffer pool and catalog but hold independent transactions.
98 pub fn new(database: &Arc<Database>) -> Self {
99 Self {
100 database: database.clone(),
101 statement_cache: Mutex::new(HashMap::new()),
102 plan_cache: Mutex::new(PlanCache::new(PLAN_CACHE_CAPACITY)),
103 txn_resources: Mutex::new(HashMap::new()),
104 explicit_txn_active: std::sync::atomic::AtomicBool::new(false),
105 processor_handlers: std::sync::OnceLock::new(),
106 }
107 }
108
109 /// Clear the prepared statement and plan caches.
110 pub fn clear_cache(&self) {
111 if let Ok(mut cache) = self.statement_cache.lock() {
112 cache.clear();
113 }
114 if let Ok(mut cache) = self.plan_cache.lock() {
115 cache.clear();
116 }
117 }
118
119 /// Number of cached prepared statements.
120 pub fn cache_size(&self) -> usize {
121 self.statement_cache.lock().map(|c| c.len()).unwrap_or(0)
122 }
123
124 /// Number of cached query plans.
125 pub fn plan_cache_size(&self) -> usize {
126 self.plan_cache.lock().map(|c| c.len()).unwrap_or(0)
127 }
128}
129
130impl Drop for Connection {
131 fn drop(&mut self) {
132 // A dropped connection must roll back any abandoned write transactions.
133 // Without this, a client that disconnects after `BEGIN` (without
134 // COMMIT/ROLLBACK) leaves `active_write_count` raised and table locks
135 // held forever, wedging single-writer mode (P52.15).
136 let txn_ids: Vec<u64> = self
137 .txn_resources
138 .lock()
139 .map(|map| map.keys().copied().collect())
140 .unwrap_or_default();
141 if txn_ids.is_empty() {
142 return;
143 }
144
145 let tm = &self.database.transaction_manager;
146 let txns: Vec<akar_transaction::Transaction> = tm
147 .active_snapshot()
148 .ok()
149 .map(|mut active| txn_ids.iter().filter_map(|id| active.remove(id)).collect())
150 .unwrap_or_default();
151
152 for mut txn in txns {
153 let id = txn.transaction_id;
154 match self.rollback_write_txn(&mut txn) {
155 Ok(_) => tracing::info!("Rolled back abandoned txn#{id} on connection drop"),
156 Err(e) => tracing::warn!("Failed to roll back abandoned txn#{id} on connection drop: {e}"),
157 }
158 }
159 }
160}