1use std::any::Any;
10use std::sync::Arc;
11
12use async_trait::async_trait;
13
14use khive_storage::error::StorageError;
15use khive_storage::types::{SqlColumn, SqlRow, SqlStatement, SqlValue};
16use khive_storage::{AtomicUnitOp, StorageCapability};
17
18use crate::error::SqliteError;
19use crate::pool::ConnectionPool;
20
21fn row_to_sql_row(row: &rusqlite::Row<'_>, col_count: usize, col_names: &[String]) -> SqlRow {
27 let mut columns = Vec::with_capacity(col_count);
28 for i in 0..col_count {
29 let value = match row.get_ref(i) {
30 Ok(rusqlite::types::ValueRef::Null) => SqlValue::Null,
31 Ok(rusqlite::types::ValueRef::Integer(v)) => SqlValue::Integer(v),
32 Ok(rusqlite::types::ValueRef::Real(v)) => SqlValue::Float(v),
33 Ok(rusqlite::types::ValueRef::Text(bytes)) => {
34 SqlValue::Text(String::from_utf8_lossy(bytes).into_owned())
35 }
36 Ok(rusqlite::types::ValueRef::Blob(bytes)) => SqlValue::Blob(bytes.to_vec()),
37 Err(_) => SqlValue::Null,
38 };
39 columns.push(SqlColumn {
40 name: col_names.get(i).cloned().unwrap_or_default(),
41 value,
42 });
43 }
44 SqlRow { columns }
45}
46
47pub(crate) fn bind_params(
54 stmt: &mut rusqlite::Statement<'_>,
55 params: &[SqlValue],
56) -> Result<(), rusqlite::Error> {
57 for (i, param) in params.iter().enumerate() {
58 let idx = i + 1; match param {
60 SqlValue::Null => stmt.raw_bind_parameter(idx, rusqlite::types::Null)?,
61 SqlValue::Bool(v) => stmt.raw_bind_parameter(idx, *v as i64)?,
62 SqlValue::Integer(v) => stmt.raw_bind_parameter(idx, *v)?,
63 SqlValue::Float(v) => stmt.raw_bind_parameter(idx, *v)?,
64 SqlValue::Text(v) => stmt.raw_bind_parameter(idx, v.as_str())?,
65 SqlValue::Blob(v) => stmt.raw_bind_parameter(idx, v.as_slice())?,
66 SqlValue::Json(v) => {
67 let s = serde_json::to_string(v).unwrap_or_default();
68 stmt.raw_bind_parameter(idx, s.as_str())?;
69 }
70 SqlValue::Uuid(v) => stmt.raw_bind_parameter(idx, v.to_string().as_str())?,
71 SqlValue::Timestamp(v) => {
72 stmt.raw_bind_parameter(idx, v.timestamp_micros())?;
73 }
74 }
75 }
76 Ok(())
77}
78
79fn execute_query(
81 conn: &rusqlite::Connection,
82 statement: &SqlStatement,
83) -> Result<Vec<SqlRow>, rusqlite::Error> {
84 let mut stmt = conn.prepare(&statement.sql)?;
85 bind_params(&mut stmt, &statement.params)?;
86
87 let col_count = stmt.column_count();
88 let col_names: Vec<String> = (0..col_count)
89 .map(|i| stmt.column_name(i).unwrap_or("").to_string())
90 .collect();
91
92 let mut rows = Vec::new();
93 let mut raw_rows = stmt.raw_query();
94 while let Some(row) = raw_rows.next()? {
95 rows.push(row_to_sql_row(row, col_count, &col_names));
96 }
97 Ok(rows)
98}
99
100fn map_rusqlite_err(e: rusqlite::Error, op: &'static str) -> StorageError {
102 StorageError::driver(StorageCapability::Sql, op, e)
103}
104
105fn open_standalone_reader(pool: &ConnectionPool) -> Result<rusqlite::Connection, StorageError> {
110 let config = pool.config();
111 let path = config.path.as_ref().ok_or_else(|| StorageError::Pool {
112 operation: "reader".into(),
113 message: "in-memory databases do not support standalone readers; use pool-backed".into(),
114 })?;
115
116 let conn = rusqlite::Connection::open_with_flags(
117 path,
118 rusqlite::OpenFlags::SQLITE_OPEN_READ_ONLY
119 | rusqlite::OpenFlags::SQLITE_OPEN_NO_MUTEX
120 | rusqlite::OpenFlags::SQLITE_OPEN_URI,
121 )
122 .map_err(|e| map_rusqlite_err(e, "open_reader"))?;
123
124 conn.busy_timeout(config.busy_timeout)
125 .map_err(|e| map_rusqlite_err(e, "open_reader"))?;
126 conn.pragma_update(None, "cache_size", "-65536")
127 .map_err(|e| map_rusqlite_err(e, "open_reader"))?;
128 conn.pragma_update(None, "mmap_size", "1073741824")
129 .map_err(|e| map_rusqlite_err(e, "open_reader"))?;
130
131 Ok(conn)
132}
133
134fn open_standalone_writer(pool: &ConnectionPool) -> Result<rusqlite::Connection, StorageError> {
135 let config = pool.config();
136 let path = config.path.as_ref().ok_or_else(|| StorageError::Pool {
137 operation: "writer".into(),
138 message: "in-memory databases do not support standalone writer; use pool-backed".into(),
139 })?;
140
141 let conn = rusqlite::Connection::open_with_flags(
142 path,
143 rusqlite::OpenFlags::SQLITE_OPEN_READ_WRITE
144 | rusqlite::OpenFlags::SQLITE_OPEN_NO_MUTEX
145 | rusqlite::OpenFlags::SQLITE_OPEN_URI,
146 )
147 .map_err(|e| map_rusqlite_err(e, "open_writer"))?;
148
149 conn.busy_timeout(config.busy_timeout)
150 .map_err(|e| map_rusqlite_err(e, "open_writer"))?;
151 conn.pragma_update(None, "cache_size", "-65536")
152 .map_err(|e| map_rusqlite_err(e, "open_writer"))?;
153 conn.pragma_update(None, "mmap_size", "1073741824")
154 .map_err(|e| map_rusqlite_err(e, "open_writer"))?;
155
156 Ok(conn)
157}
158
159struct SqliteReader {
164 conn: Option<rusqlite::Connection>,
165}
166
167#[async_trait]
168impl khive_storage::SqlReader for SqliteReader {
169 async fn query_row(
170 &mut self,
171 statement: SqlStatement,
172 ) -> khive_storage::types::StorageResult<Option<SqlRow>> {
173 let conn = self.conn.take().ok_or_else(|| StorageError::Pool {
174 operation: "query_row".into(),
175 message: "connection already consumed".into(),
176 })?;
177 let (conn, result) = tokio::task::spawn_blocking(move || {
178 let res = execute_query(&conn, &statement);
179 (conn, res)
180 })
181 .await
182 .map_err(|e| StorageError::driver(StorageCapability::Sql, "query_row", e))?;
183 self.conn = Some(conn);
184 let rows = result.map_err(|e| map_rusqlite_err(e, "query_row"))?;
185 Ok(rows.into_iter().next())
186 }
187
188 async fn query_all(
189 &mut self,
190 statement: SqlStatement,
191 ) -> khive_storage::types::StorageResult<Vec<SqlRow>> {
192 let conn = self.conn.take().ok_or_else(|| StorageError::Pool {
193 operation: "query_all".into(),
194 message: "connection already consumed".into(),
195 })?;
196 let (conn, result) = tokio::task::spawn_blocking(move || {
197 let res = execute_query(&conn, &statement);
198 (conn, res)
199 })
200 .await
201 .map_err(|e| StorageError::driver(StorageCapability::Sql, "query_all", e))?;
202 self.conn = Some(conn);
203 result.map_err(|e| map_rusqlite_err(e, "query_all"))
204 }
205
206 async fn query_scalar(
207 &mut self,
208 statement: SqlStatement,
209 ) -> khive_storage::types::StorageResult<Option<SqlValue>> {
210 let row = self.query_row(statement).await?;
211 Ok(row.and_then(|r| r.columns.into_iter().next().map(|c| c.value)))
212 }
213
214 async fn explain(
215 &mut self,
216 statement: SqlStatement,
217 ) -> khive_storage::types::StorageResult<Vec<SqlRow>> {
218 let explain_stmt = SqlStatement {
219 sql: format!("EXPLAIN QUERY PLAN {}", statement.sql),
220 params: statement.params,
221 label: statement.label,
222 };
223 self.query_all(explain_stmt).await
224 }
225}
226
227struct SqliteWriter {
232 conn: Option<rusqlite::Connection>,
233 writer_task: Option<crate::writer_task::WriterTaskHandle>,
239 origin: khive_storage::tx_registry::TxOrigin,
242}
243
244#[async_trait]
245impl khive_storage::SqlReader for SqliteWriter {
246 async fn query_row(
247 &mut self,
248 statement: SqlStatement,
249 ) -> khive_storage::types::StorageResult<Option<SqlRow>> {
250 let conn = self.conn.take().ok_or_else(|| StorageError::Pool {
251 operation: "writer.query_row".into(),
252 message: "connection already consumed".into(),
253 })?;
254 let (conn, result) = tokio::task::spawn_blocking(move || {
255 let res = execute_query(&conn, &statement);
256 (conn, res)
257 })
258 .await
259 .map_err(|e| StorageError::driver(StorageCapability::Sql, "writer.query_row", e))?;
260 self.conn = Some(conn);
261 let rows = result.map_err(|e| map_rusqlite_err(e, "writer.query_row"))?;
262 Ok(rows.into_iter().next())
263 }
264
265 async fn query_all(
266 &mut self,
267 statement: SqlStatement,
268 ) -> khive_storage::types::StorageResult<Vec<SqlRow>> {
269 let conn = self.conn.take().ok_or_else(|| StorageError::Pool {
270 operation: "writer.query_all".into(),
271 message: "connection already consumed".into(),
272 })?;
273 let (conn, result) = tokio::task::spawn_blocking(move || {
274 let res = execute_query(&conn, &statement);
275 (conn, res)
276 })
277 .await
278 .map_err(|e| StorageError::driver(StorageCapability::Sql, "writer.query_all", e))?;
279 self.conn = Some(conn);
280 result.map_err(|e| map_rusqlite_err(e, "writer.query_all"))
281 }
282
283 async fn query_scalar(
284 &mut self,
285 statement: SqlStatement,
286 ) -> khive_storage::types::StorageResult<Option<SqlValue>> {
287 let row = khive_storage::SqlReader::query_row(self, statement).await?;
288 Ok(row.and_then(|r| r.columns.into_iter().next().map(|c| c.value)))
289 }
290
291 async fn explain(
292 &mut self,
293 statement: SqlStatement,
294 ) -> khive_storage::types::StorageResult<Vec<SqlRow>> {
295 let explain_stmt = SqlStatement {
296 sql: format!("EXPLAIN QUERY PLAN {}", statement.sql),
297 params: statement.params,
298 label: statement.label,
299 };
300 khive_storage::SqlReader::query_all(self, explain_stmt).await
301 }
302}
303
304#[async_trait]
305impl khive_storage::SqlWriter for SqliteWriter {
306 async fn execute(
307 &mut self,
308 statement: SqlStatement,
309 ) -> khive_storage::types::StorageResult<u64> {
310 if let Some(writer_task) = self.writer_task.clone() {
316 return writer_task
317 .send(move |conn| {
318 let mut stmt = conn
319 .prepare(&statement.sql)
320 .map_err(|e| map_rusqlite_err(e, "execute"))?;
321 bind_params(&mut stmt, &statement.params)
322 .map_err(|e| map_rusqlite_err(e, "execute"))?;
323 let affected = stmt
324 .raw_execute()
325 .map_err(|e| map_rusqlite_err(e, "execute"))?;
326 Ok(affected as u64)
327 })
328 .await;
329 }
330
331 let conn = self.conn.take().ok_or_else(|| StorageError::Pool {
332 operation: "execute".into(),
333 message: "connection already consumed".into(),
334 })?;
335 let (conn, result) = tokio::task::spawn_blocking(move || {
336 let res = (|| -> Result<usize, rusqlite::Error> {
337 let mut stmt = conn.prepare(&statement.sql)?;
338 bind_params(&mut stmt, &statement.params)?;
339 stmt.raw_execute()
340 })();
341 (conn, res)
342 })
343 .await
344 .map_err(|e| StorageError::driver(StorageCapability::Sql, "execute", e))?;
345 self.conn = Some(conn);
346 let affected = result.map_err(|e| map_rusqlite_err(e, "execute"))?;
347 Ok(affected as u64)
348 }
349
350 async fn execute_batch(
351 &mut self,
352 statements: Vec<SqlStatement>,
353 ) -> khive_storage::types::StorageResult<u64> {
354 if let Some(writer_task) = self.writer_task.clone() {
363 return writer_task
364 .send(move |conn| {
365 let mut total: u64 = 0;
366 for statement in &statements {
367 let mut stmt = conn
368 .prepare(&statement.sql)
369 .map_err(|e| map_rusqlite_err(e, "execute_batch"))?;
370 bind_params(&mut stmt, &statement.params)
371 .map_err(|e| map_rusqlite_err(e, "execute_batch"))?;
372 total += stmt
373 .raw_execute()
374 .map_err(|e| map_rusqlite_err(e, "execute_batch"))?
375 as u64;
376 }
377 Ok(total)
378 })
379 .await;
380 }
381
382 let conn = self.conn.take().ok_or_else(|| StorageError::Pool {
383 operation: "execute_batch".into(),
384 message: "connection already consumed".into(),
385 })?;
386 let origin = self.origin.clone();
387 let (conn, result) = tokio::task::spawn_blocking(move || {
388 if let Err(e) = conn.execute_batch("BEGIN IMMEDIATE") {
389 return (conn, Err(e));
390 }
391 let _tx_handle = khive_storage::tx_registry::register_scoped(
397 Some("execute_batch".to_string()),
398 origin,
399 );
400 let result = (|| -> Result<u64, rusqlite::Error> {
401 let mut total: u64 = 0;
402 for statement in &statements {
403 let mut stmt = conn.prepare(&statement.sql)?;
404 bind_params(&mut stmt, &statement.params)?;
405 total += stmt.raw_execute()? as u64;
406 }
407 conn.execute_batch("COMMIT")?;
408 Ok(total)
409 })();
410 if result.is_err() {
411 let _ = conn.execute_batch("ROLLBACK");
412 }
413 (conn, result)
415 })
416 .await
417 .map_err(|e| StorageError::driver(StorageCapability::Sql, "execute_batch", e))?;
418 self.conn = Some(conn);
419 result.map_err(|e| map_rusqlite_err(e, "execute_batch"))
420 }
421
422 async fn execute_script(&mut self, script: String) -> khive_storage::types::StorageResult<()> {
423 if let Some(writer_task) = self.writer_task.clone() {
433 return writer_task
434 .send(move |conn| {
435 conn.execute_batch(&script)
436 .map_err(|e| map_rusqlite_err(e, "execute_script"))
437 })
438 .await;
439 }
440
441 let conn = self.conn.take().ok_or_else(|| StorageError::Pool {
442 operation: "execute_script".into(),
443 message: "connection already consumed".into(),
444 })?;
445 let (conn, result) = tokio::task::spawn_blocking(move || {
446 let res = conn.execute_batch(&script);
447 (conn, res)
448 })
449 .await
450 .map_err(|e| StorageError::driver(StorageCapability::Sql, "execute_script", e))?;
451 self.conn = Some(conn);
452 result.map_err(|e| map_rusqlite_err(e, "execute_script"))
453 }
454
455 async fn execute_script_top_level(
456 &mut self,
457 script: String,
458 ) -> khive_storage::types::StorageResult<()> {
459 if let Some(writer_task) = self.writer_task.clone() {
467 return writer_task
468 .send_top_level(move |conn| {
469 conn.execute_batch(&script)
470 .map_err(|e| map_rusqlite_err(e, "execute_script_top_level"))
471 })
472 .await;
473 }
474
475 let conn = self.conn.take().ok_or_else(|| StorageError::Pool {
479 operation: "execute_script_top_level".into(),
480 message: "connection already consumed".into(),
481 })?;
482 let (conn, result) = tokio::task::spawn_blocking(move || {
483 let res = conn.execute_batch(&script);
484 (conn, res)
485 })
486 .await
487 .map_err(|e| StorageError::driver(StorageCapability::Sql, "execute_script_top_level", e))?;
488 self.conn = Some(conn);
489 result.map_err(|e| map_rusqlite_err(e, "execute_script_top_level"))
490 }
491}
492
493struct PoolBackedReader {
498 pool: Arc<ConnectionPool>,
499}
500
501#[async_trait]
502impl khive_storage::SqlReader for PoolBackedReader {
503 async fn query_row(
504 &mut self,
505 statement: SqlStatement,
506 ) -> khive_storage::types::StorageResult<Option<SqlRow>> {
507 let pool = Arc::clone(&self.pool);
508 tokio::task::spawn_blocking(move || {
509 let guard = pool
510 .reader()
511 .map_err(|e| StorageError::driver(StorageCapability::Sql, "pool_reader", e))?;
512 let rows = execute_query(&guard, &statement)
513 .map_err(|e| map_rusqlite_err(e, "pool_reader.query_row"))?;
514 Ok(rows.into_iter().next())
515 })
516 .await
517 .map_err(|e| StorageError::driver(StorageCapability::Sql, "pool_reader.query_row", e))?
518 }
519
520 async fn query_all(
521 &mut self,
522 statement: SqlStatement,
523 ) -> khive_storage::types::StorageResult<Vec<SqlRow>> {
524 let pool = Arc::clone(&self.pool);
525 tokio::task::spawn_blocking(move || {
526 let guard = pool
527 .reader()
528 .map_err(|e| StorageError::driver(StorageCapability::Sql, "pool_reader", e))?;
529 execute_query(&guard, &statement)
530 .map_err(|e| map_rusqlite_err(e, "pool_reader.query_all"))
531 })
532 .await
533 .map_err(|e| StorageError::driver(StorageCapability::Sql, "pool_reader.query_all", e))?
534 }
535
536 async fn query_scalar(
537 &mut self,
538 statement: SqlStatement,
539 ) -> khive_storage::types::StorageResult<Option<SqlValue>> {
540 let row = self.query_row(statement).await?;
541 Ok(row.and_then(|r| r.columns.into_iter().next().map(|c| c.value)))
542 }
543
544 async fn explain(
545 &mut self,
546 statement: SqlStatement,
547 ) -> khive_storage::types::StorageResult<Vec<SqlRow>> {
548 let explain_stmt = SqlStatement {
549 sql: format!("EXPLAIN QUERY PLAN {}", statement.sql),
550 params: statement.params,
551 label: statement.label,
552 };
553 self.query_all(explain_stmt).await
554 }
555}
556
557struct PoolBackedWriter {
558 pool: Arc<ConnectionPool>,
559}
560
561#[async_trait]
562impl khive_storage::SqlReader for PoolBackedWriter {
563 async fn query_row(
564 &mut self,
565 statement: SqlStatement,
566 ) -> khive_storage::types::StorageResult<Option<SqlRow>> {
567 let pool = Arc::clone(&self.pool);
568 tokio::task::spawn_blocking(move || {
569 let guard = pool.try_writer().map_err(|e: SqliteError| {
570 StorageError::driver(StorageCapability::Sql, "pool_writer.query_row", e)
571 })?;
572 let rows = execute_query(&guard, &statement)
573 .map_err(|e| map_rusqlite_err(e, "pool_writer.query_row"))?;
574 Ok(rows.into_iter().next())
575 })
576 .await
577 .map_err(|e| StorageError::driver(StorageCapability::Sql, "pool_writer.query_row", e))?
578 }
579
580 async fn query_all(
581 &mut self,
582 statement: SqlStatement,
583 ) -> khive_storage::types::StorageResult<Vec<SqlRow>> {
584 let pool = Arc::clone(&self.pool);
585 tokio::task::spawn_blocking(move || {
586 let guard = pool.try_writer().map_err(|e: SqliteError| {
587 StorageError::driver(StorageCapability::Sql, "pool_writer.query_all", e)
588 })?;
589 execute_query(&guard, &statement)
590 .map_err(|e| map_rusqlite_err(e, "pool_writer.query_all"))
591 })
592 .await
593 .map_err(|e| StorageError::driver(StorageCapability::Sql, "pool_writer.query_all", e))?
594 }
595
596 async fn query_scalar(
597 &mut self,
598 statement: SqlStatement,
599 ) -> khive_storage::types::StorageResult<Option<SqlValue>> {
600 let row = khive_storage::SqlReader::query_row(self, statement).await?;
601 Ok(row.and_then(|r| r.columns.into_iter().next().map(|c| c.value)))
602 }
603
604 async fn explain(
605 &mut self,
606 statement: SqlStatement,
607 ) -> khive_storage::types::StorageResult<Vec<SqlRow>> {
608 let explain_stmt = SqlStatement {
609 sql: format!("EXPLAIN QUERY PLAN {}", statement.sql),
610 params: statement.params,
611 label: statement.label,
612 };
613 khive_storage::SqlReader::query_all(self, explain_stmt).await
614 }
615}
616
617#[async_trait]
618impl khive_storage::SqlWriter for PoolBackedWriter {
619 async fn execute(
620 &mut self,
621 statement: SqlStatement,
622 ) -> khive_storage::types::StorageResult<u64> {
623 let pool = Arc::clone(&self.pool);
624 tokio::task::spawn_blocking(move || {
625 let guard = pool.try_writer().map_err(|e: SqliteError| {
626 StorageError::driver(StorageCapability::Sql, "pool_writer.execute", e)
627 })?;
628 let mut stmt = guard
629 .prepare(&statement.sql)
630 .map_err(|e| map_rusqlite_err(e, "pool_writer.execute"))?;
631 bind_params(&mut stmt, &statement.params)
632 .map_err(|e| map_rusqlite_err(e, "pool_writer.execute"))?;
633 let rows = stmt
634 .raw_execute()
635 .map_err(|e| map_rusqlite_err(e, "pool_writer.execute"))?;
636 Ok(rows as u64)
637 })
638 .await
639 .map_err(|e| StorageError::driver(StorageCapability::Sql, "pool_writer.execute", e))?
640 }
641
642 async fn execute_batch(
643 &mut self,
644 statements: Vec<SqlStatement>,
645 ) -> khive_storage::types::StorageResult<u64> {
646 let pool = Arc::clone(&self.pool);
647 tokio::task::spawn_blocking(move || {
648 let guard = pool.try_writer().map_err(|e: SqliteError| {
649 StorageError::driver(StorageCapability::Sql, "pool_writer.execute_batch", e)
650 })?;
651 guard
652 .execute_batch("BEGIN IMMEDIATE")
653 .map_err(|e| map_rusqlite_err(e, "pool_writer.execute_batch"))?;
654 let _tx_handle = khive_storage::tx_registry::register_scoped(
655 Some("pool_writer.execute_batch".to_string()),
656 pool.origin(),
657 );
658 let result = (|| -> Result<u64, StorageError> {
659 let mut total = 0u64;
660 for statement in &statements {
661 let mut stmt = guard
662 .prepare(&statement.sql)
663 .map_err(|e| map_rusqlite_err(e, "pool_writer.execute_batch"))?;
664 bind_params(&mut stmt, &statement.params)
665 .map_err(|e| map_rusqlite_err(e, "pool_writer.execute_batch"))?;
666 total += stmt
667 .raw_execute()
668 .map_err(|e| map_rusqlite_err(e, "pool_writer.execute_batch"))?
669 as u64;
670 }
671 Ok(total)
672 })();
673 match result {
674 Ok(total) => {
675 if let Err(e) = guard.execute_batch("COMMIT") {
676 let _ = guard.execute_batch("ROLLBACK");
677 Err(map_rusqlite_err(e, "pool_writer.execute_batch"))
678 } else {
679 Ok(total)
680 }
681 }
682 Err(e) => {
683 let _ = guard.execute_batch("ROLLBACK");
684 Err(e)
685 }
686 }
687 })
688 .await
689 .map_err(|e| StorageError::driver(StorageCapability::Sql, "pool_writer.execute_batch", e))?
690 }
691
692 async fn execute_script(&mut self, script: String) -> khive_storage::types::StorageResult<()> {
693 let pool = Arc::clone(&self.pool);
694 tokio::task::spawn_blocking(move || {
695 let guard = pool.try_writer().map_err(|e: SqliteError| {
696 StorageError::driver(StorageCapability::Sql, "pool_writer.execute_script", e)
697 })?;
698 guard
699 .execute_batch(&script)
700 .map_err(|e| map_rusqlite_err(e, "pool_writer.execute_script"))
701 })
702 .await
703 .map_err(|e| {
704 StorageError::driver(StorageCapability::Sql, "pool_writer.execute_script", e)
705 })?
706 }
707}
708
709struct InlineWriter {
735 conn: *const rusqlite::Connection,
736}
737
738unsafe impl Send for InlineWriter {}
745
746impl InlineWriter {
747 fn conn(&self) -> &rusqlite::Connection {
751 unsafe { &*self.conn }
752 }
753}
754
755#[async_trait]
756impl khive_storage::SqlReader for InlineWriter {
757 async fn query_row(
758 &mut self,
759 statement: SqlStatement,
760 ) -> khive_storage::types::StorageResult<Option<SqlRow>> {
761 let rows = execute_query(self.conn(), &statement)
762 .map_err(|e| map_rusqlite_err(e, "inline.query_row"))?;
763 Ok(rows.into_iter().next())
764 }
765
766 async fn query_all(
767 &mut self,
768 statement: SqlStatement,
769 ) -> khive_storage::types::StorageResult<Vec<SqlRow>> {
770 execute_query(self.conn(), &statement).map_err(|e| map_rusqlite_err(e, "inline.query_all"))
771 }
772
773 async fn query_scalar(
774 &mut self,
775 statement: SqlStatement,
776 ) -> khive_storage::types::StorageResult<Option<SqlValue>> {
777 let row = khive_storage::SqlReader::query_row(self, statement).await?;
778 Ok(row.and_then(|r| r.columns.into_iter().next().map(|c| c.value)))
779 }
780
781 async fn explain(
782 &mut self,
783 statement: SqlStatement,
784 ) -> khive_storage::types::StorageResult<Vec<SqlRow>> {
785 let explain_stmt = SqlStatement {
786 sql: format!("EXPLAIN QUERY PLAN {}", statement.sql),
787 params: statement.params,
788 label: statement.label,
789 };
790 khive_storage::SqlReader::query_all(self, explain_stmt).await
791 }
792}
793
794#[async_trait]
795impl khive_storage::SqlWriter for InlineWriter {
796 async fn execute(
797 &mut self,
798 statement: SqlStatement,
799 ) -> khive_storage::types::StorageResult<u64> {
800 let mut stmt = self
801 .conn()
802 .prepare(&statement.sql)
803 .map_err(|e| map_rusqlite_err(e, "inline.execute"))?;
804 bind_params(&mut stmt, &statement.params)
805 .map_err(|e| map_rusqlite_err(e, "inline.execute"))?;
806 let affected = stmt
807 .raw_execute()
808 .map_err(|e| map_rusqlite_err(e, "inline.execute"))?;
809 Ok(affected as u64)
810 }
811
812 async fn execute_batch(
813 &mut self,
814 statements: Vec<SqlStatement>,
815 ) -> khive_storage::types::StorageResult<u64> {
816 let mut total: u64 = 0;
817 for statement in &statements {
818 let mut stmt = self
819 .conn()
820 .prepare(&statement.sql)
821 .map_err(|e| map_rusqlite_err(e, "inline.execute_batch"))?;
822 bind_params(&mut stmt, &statement.params)
823 .map_err(|e| map_rusqlite_err(e, "inline.execute_batch"))?;
824 total += stmt
825 .raw_execute()
826 .map_err(|e| map_rusqlite_err(e, "inline.execute_batch"))?
827 as u64;
828 }
829 Ok(total)
830 }
831
832 async fn execute_script(&mut self, script: String) -> khive_storage::types::StorageResult<()> {
833 self.conn()
834 .execute_batch(&script)
835 .map_err(|e| map_rusqlite_err(e, "inline.execute_script"))
836 }
837}
838
839fn block_on_sync<F: std::future::Future>(fut: F) -> Result<F::Output, StorageError> {
862 use std::task::{Context, Poll, RawWaker, RawWakerVTable, Waker};
863
864 fn no_op(_: *const ()) {}
865 fn clone_waker(_: *const ()) -> RawWaker {
866 RawWaker::new(std::ptr::null(), &VTABLE)
867 }
868 static VTABLE: RawWakerVTable = RawWakerVTable::new(clone_waker, no_op, no_op, no_op);
869
870 let raw_waker = RawWaker::new(std::ptr::null(), &VTABLE);
873 let waker = unsafe { Waker::from_raw(raw_waker) };
874 let mut cx = Context::from_waker(&waker);
875
876 let mut fut = std::pin::pin!(fut);
877 match fut.as_mut().poll(&mut cx) {
878 Poll::Ready(v) => Ok(v),
879 Poll::Pending => {
880 tracing::error!(
881 "block_on_sync: atomic_unit future suspended on its first poll — \
882 the closure passed to SqlAccess::atomic_unit must be non-blocking \
883 (synchronous InlineWriter calls only, no real .await point)"
884 );
885 Err(StorageError::Internal(
886 "atomic_unit future suspended — closure must be non-blocking".to_string(),
887 ))
888 }
889 }
890}
891
892async fn run_manual_atomic_unit(
897 writer: &mut dyn khive_storage::SqlWriter,
898 op: AtomicUnitOp,
899 origin: khive_storage::tx_registry::TxOrigin,
900) -> khive_storage::types::StorageResult<Box<dyn Any + Send>> {
901 fn tx_stmt(sql: &str, label: &str) -> SqlStatement {
902 SqlStatement {
903 sql: sql.to_string(),
904 params: vec![],
905 label: Some(label.to_string()),
906 }
907 }
908 khive_storage::SqlWriter::execute(writer, tx_stmt("BEGIN IMMEDIATE", "begin")).await?;
909 let _tx_handle =
910 khive_storage::tx_registry::register_scoped(Some("atomic_unit".to_string()), origin);
911
912 let result = op(writer).await;
913
914 match result {
915 Ok(value) => {
916 match khive_storage::SqlWriter::execute(writer, tx_stmt("COMMIT", "commit")).await {
917 Ok(_) => Ok(value),
918 Err(e) => {
919 let _ =
920 khive_storage::SqlWriter::execute(writer, tx_stmt("ROLLBACK", "rollback"))
921 .await;
922 Err(e)
923 }
924 }
925 }
926 Err(e) => {
927 let _ =
928 khive_storage::SqlWriter::execute(writer, tx_stmt("ROLLBACK", "rollback")).await;
929 Err(e)
930 }
931 }
932}
933
934pub struct SqlBridge {
946 pool: Arc<ConnectionPool>,
947 is_file_backed: bool,
948}
949
950impl SqlBridge {
951 pub fn new(pool: Arc<ConnectionPool>, is_file_backed: bool) -> Self {
953 Self {
954 pool,
955 is_file_backed,
956 }
957 }
958}
959
960#[async_trait]
961impl khive_storage::SqlAccess for SqlBridge {
962 async fn reader(
963 &self,
964 ) -> khive_storage::types::StorageResult<Box<dyn khive_storage::SqlReader>> {
965 if self.is_file_backed {
966 let conn = open_standalone_reader(&self.pool)?;
967 Ok(Box::new(SqliteReader { conn: Some(conn) }))
968 } else {
969 Ok(Box::new(PoolBackedReader {
970 pool: Arc::clone(&self.pool),
971 }))
972 }
973 }
974
975 async fn writer(
976 &self,
977 ) -> khive_storage::types::StorageResult<Box<dyn khive_storage::SqlWriter>> {
978 if self.is_file_backed {
979 if self.pool.config().read_only {
980 return Err(StorageError::Pool {
981 operation: "writer".into(),
982 message: "backend is read-only".into(),
983 });
984 }
985 let conn = open_standalone_writer(&self.pool)?;
986 let writer_task = self.pool.writer_task_handle().ok().flatten();
990 Ok(Box::new(SqliteWriter {
991 conn: Some(conn),
992 writer_task,
993 origin: self.pool.origin(),
994 }))
995 } else {
996 Ok(Box::new(PoolBackedWriter {
997 pool: Arc::clone(&self.pool),
998 }))
999 }
1000 }
1001
1002 async fn atomic_unit(
1013 &self,
1014 op: AtomicUnitOp,
1015 ) -> khive_storage::types::StorageResult<Box<dyn Any + Send>> {
1016 if self.is_file_backed {
1017 if self.pool.config().read_only {
1018 return Err(StorageError::Pool {
1019 operation: "atomic_unit".into(),
1020 message: "backend is read-only".into(),
1021 });
1022 }
1023 if let Some(writer_task) = self.pool.writer_task_handle()? {
1027 return writer_task
1033 .send(move |conn| {
1034 let mut inline = InlineWriter {
1035 conn: conn as *const rusqlite::Connection,
1036 };
1037 match block_on_sync(op(&mut inline)) {
1047 Ok(inner) => inner,
1048 Err(e) => Err(e),
1049 }
1050 })
1051 .await;
1052 }
1053 let conn = open_standalone_writer(&self.pool)?;
1057 let mut writer = SqliteWriter {
1058 conn: Some(conn),
1059 writer_task: None,
1060 origin: self.pool.origin(),
1061 };
1062 run_manual_atomic_unit(&mut writer, op, self.pool.origin()).await
1063 } else {
1064 let mut writer = PoolBackedWriter {
1068 pool: Arc::clone(&self.pool),
1069 };
1070 run_manual_atomic_unit(&mut writer, op, self.pool.origin()).await
1071 }
1072 }
1073}
1074
1075#[cfg(test)]
1076mod tests {
1077 use super::*;
1078 use crate::pool::PoolConfig;
1079 use khive_storage::types::{SqlStatement, SqlValue};
1080 use khive_storage::SqlAccess as _;
1081
1082 #[tokio::test]
1088 async fn execute_batch_routes_through_writer_task_when_flag_enabled() {
1089 let dir = tempfile::tempdir().unwrap();
1090 let path = dir.path().join("write_queue_execute_batch.db");
1091 let config = PoolConfig {
1092 path: Some(path.clone()),
1093 write_queue_enabled: true,
1094 ..PoolConfig::default()
1095 };
1096 let pool = Arc::new(ConnectionPool::new(config).unwrap());
1097 {
1098 let guard = pool.writer().unwrap();
1099 guard
1100 .conn()
1101 .execute_batch(
1102 "CREATE TABLE IF NOT EXISTS write_queue_batch_test \
1103 (id INTEGER PRIMARY KEY, val TEXT NOT NULL)",
1104 )
1105 .unwrap();
1106 }
1107
1108 let bridge = SqlBridge::new(Arc::clone(&pool), true);
1109
1110 let mut writer = bridge.writer().await.unwrap();
1111 let affected = writer
1112 .execute_batch(vec![
1113 SqlStatement {
1114 sql: "INSERT INTO write_queue_batch_test (id, val) VALUES (?1, ?2)".into(),
1115 params: vec![SqlValue::Integer(1), SqlValue::Text("a".into())],
1116 label: None,
1117 },
1118 SqlStatement {
1119 sql: "INSERT INTO write_queue_batch_test (id, val) VALUES (?1, ?2)".into(),
1120 params: vec![SqlValue::Integer(2), SqlValue::Text("b".into())],
1121 label: None,
1122 },
1123 ])
1124 .await
1125 .unwrap();
1126 assert_eq!(affected, 2);
1127
1128 let mut reader = bridge.reader().await.unwrap();
1129 let count = reader
1130 .query_scalar(SqlStatement {
1131 sql: "SELECT COUNT(*) FROM write_queue_batch_test".into(),
1132 params: vec![],
1133 label: None,
1134 })
1135 .await
1136 .unwrap();
1137 assert!(
1138 matches!(count, Some(SqlValue::Integer(2))),
1139 "expected 2 rows, got {count:?}"
1140 );
1141 assert_eq!(
1142 pool.writer_task_spawn_count(),
1143 1,
1144 "the flag-ON path must actually spawn and use the writer task"
1145 );
1146 }
1147
1148 #[tokio::test]
1155 async fn execute_batch_rolls_back_atomically_on_mid_sequence_failure() {
1156 let dir = tempfile::tempdir().unwrap();
1157 let path = dir.path().join("write_queue_execute_batch_rollback.db");
1158 let config = PoolConfig {
1159 path: Some(path.clone()),
1160 write_queue_enabled: true,
1161 ..PoolConfig::default()
1162 };
1163 let pool = Arc::new(ConnectionPool::new(config).unwrap());
1164 {
1165 let guard = pool.writer().unwrap();
1166 guard
1167 .conn()
1168 .execute_batch(
1169 "CREATE TABLE IF NOT EXISTS write_queue_rollback_test \
1170 (id INTEGER PRIMARY KEY, val TEXT NOT NULL)",
1171 )
1172 .unwrap();
1173 }
1174
1175 let bridge = SqlBridge::new(Arc::clone(&pool), true);
1176
1177 let mut writer = bridge.writer().await.unwrap();
1178 let result = writer
1179 .execute_batch(vec![
1180 SqlStatement {
1182 sql: "INSERT INTO write_queue_rollback_test (id, val) VALUES (?1, ?2)".into(),
1183 params: vec![SqlValue::Integer(1), SqlValue::Text("first".into())],
1184 label: None,
1185 },
1186 SqlStatement {
1188 sql: "INSERT INTO write_queue_rollback_test (id, val) VALUES (?1, ?2)".into(),
1189 params: vec![SqlValue::Integer(1), SqlValue::Text("duplicate".into())],
1190 label: None,
1191 },
1192 SqlStatement {
1194 sql: "INSERT INTO write_queue_rollback_test (id, val) VALUES (?1, ?2)".into(),
1195 params: vec![SqlValue::Integer(2), SqlValue::Text("third".into())],
1196 label: None,
1197 },
1198 ])
1199 .await;
1200 assert!(
1201 result.is_err(),
1202 "a batch with a mid-sequence PK conflict must return an error"
1203 );
1204
1205 let mut reader = bridge.reader().await.unwrap();
1206 let count = reader
1207 .query_scalar(SqlStatement {
1208 sql: "SELECT COUNT(*) FROM write_queue_rollback_test".into(),
1209 params: vec![],
1210 label: None,
1211 })
1212 .await
1213 .unwrap();
1214 assert!(
1215 matches!(count, Some(SqlValue::Integer(0))),
1216 "the whole request must roll back — including statement 1's \
1217 otherwise-successful INSERT — not just the failing statement; \
1218 got {count:?}"
1219 );
1220 }
1221
1222 #[tokio::test]
1239 async fn atomic_unit_pending_future_errors_without_killing_writer_task() {
1240 let dir = tempfile::tempdir().unwrap();
1241 let path = dir.path().join("atomic_unit_pending_future.db");
1242 let config = PoolConfig {
1243 path: Some(path.clone()),
1244 write_queue_enabled: true,
1245 ..PoolConfig::default()
1246 };
1247 let pool = Arc::new(ConnectionPool::new(config).unwrap());
1248 {
1249 let guard = pool.writer().unwrap();
1250 guard
1251 .conn()
1252 .execute_batch(
1253 "CREATE TABLE IF NOT EXISTS atomic_unit_pending_test \
1254 (id INTEGER PRIMARY KEY, val TEXT NOT NULL)",
1255 )
1256 .unwrap();
1257 }
1258 assert!(
1259 pool.writer_task_handle().unwrap().is_some(),
1260 "writer task must be spawned with the flag on for a file-backed pool"
1261 );
1262
1263 let bridge = SqlBridge::new(Arc::clone(&pool), true);
1264
1265 let pending_op: AtomicUnitOp = Box::new(|_writer| {
1268 Box::pin(std::future::pending::<
1269 khive_storage::types::StorageResult<Box<dyn std::any::Any + Send>>,
1270 >())
1271 });
1272
1273 let pending_result = bridge.atomic_unit(pending_op).await;
1274 assert!(
1275 pending_result.is_err(),
1276 "a Pending-on-first-poll atomic_unit closure must return Err, \
1277 not panic; got {pending_result:?}"
1278 );
1279
1280 let ok_op: AtomicUnitOp = Box::new(|writer| {
1285 Box::pin(async move {
1286 writer
1287 .execute(SqlStatement {
1288 sql: "INSERT INTO atomic_unit_pending_test (id, val) VALUES (?1, ?2)"
1289 .into(),
1290 params: vec![SqlValue::Integer(1), SqlValue::Text("survived".into())],
1291 label: None,
1292 })
1293 .await
1294 .map_err(|e| {
1295 khive_storage::StorageError::driver(
1296 StorageCapability::Sql,
1297 "atomic_unit_pending_future_test_insert",
1298 e,
1299 )
1300 })?;
1301 Ok(Box::new(()) as Box<dyn std::any::Any + Send>)
1302 })
1303 });
1304 let ok_result = bridge.atomic_unit(ok_op).await;
1305 assert!(
1306 ok_result.is_ok(),
1307 "writer task must survive a Pending misuse and keep serving \
1308 subsequent well-behaved atomic_unit requests; got {ok_result:?}"
1309 );
1310
1311 let mut reader = bridge.reader().await.unwrap();
1312 let count = reader
1313 .query_scalar(SqlStatement {
1314 sql: "SELECT COUNT(*) FROM atomic_unit_pending_test".into(),
1315 params: vec![],
1316 label: None,
1317 })
1318 .await
1319 .unwrap();
1320 assert!(
1321 matches!(count, Some(SqlValue::Integer(1))),
1322 "the well-behaved atomic_unit call after the Pending misuse must \
1323 have actually committed its write; got {count:?}"
1324 );
1325 }
1326}