use super::{
acquire_handle_slot, acquire_unit_lease, async_trait, bind_params, execute_prepared_batch,
execute_query, execute_query_page, execute_query_row, map_rusqlite_err, map_write_boundary_err,
map_write_err, open_standalone_writer_on_blocking, prepare_batch_statements,
prepare_cached_sql_statement, reject_transaction_control_statements, run_manual_atomic_unit,
Any, Arc, AtomicEventRows, AtomicUnitOp, ConnectionPool, PageRequest, PoolBackedReader,
PoolBackedWriter, SlotTimeoutClass, SqlRow, SqlStatement, SqlValue, SqliteError, SqliteReader,
SqliteWriter, StandaloneHandle, StorageCapability, StorageError,
};
pub(super) struct InlineWriter {
pub(super) event_rows: Option<Arc<AtomicEventRows>>,
pub(super) conn: *const rusqlite::Connection,
}
unsafe impl Send for InlineWriter {}
impl InlineWriter {
fn conn(&self) -> &rusqlite::Connection {
unsafe { &*self.conn }
}
}
#[async_trait]
impl khive_storage::SqlReader for InlineWriter {
async fn query_row(
&mut self,
statement: SqlStatement,
) -> khive_storage::types::StorageResult<Option<SqlRow>> {
execute_query_row(self.conn(), &statement)
.map_err(|e| map_rusqlite_err(e, "inline.query_row"))
}
async fn query_all(
&mut self,
statement: SqlStatement,
) -> khive_storage::types::StorageResult<Vec<SqlRow>> {
execute_query(self.conn(), &statement).map_err(|e| map_rusqlite_err(e, "inline.query_all"))
}
async fn query_page(
&mut self,
statement: SqlStatement,
page: PageRequest,
) -> khive_storage::types::StorageResult<Vec<SqlRow>> {
execute_query_page(self.conn(), &statement, &page)
.map_err(|e| map_rusqlite_err(e, "inline.query_page"))
}
async fn query_scalar(
&mut self,
statement: SqlStatement,
) -> khive_storage::types::StorageResult<Option<SqlValue>> {
let row = khive_storage::SqlReader::query_row(self, statement).await?;
Ok(row.and_then(|r| r.columns.into_iter().next().map(|c| c.value)))
}
async fn explain(
&mut self,
statement: SqlStatement,
) -> khive_storage::types::StorageResult<Vec<SqlRow>> {
let explain_stmt = SqlStatement {
sql: format!("EXPLAIN QUERY PLAN {}", statement.sql),
params: statement.params,
label: statement.label,
};
khive_storage::SqlReader::query_all(self, explain_stmt).await
}
}
#[async_trait]
impl khive_storage::SqlWriter for InlineWriter {
async fn execute(
&mut self,
statement: SqlStatement,
) -> khive_storage::types::StorageResult<u64> {
let mut stmt = prepare_cached_sql_statement(self.conn(), &statement.sql)
.map_err(|e| map_write_err(e, "inline.execute"))?;
bind_params(&mut stmt, &statement.params)
.map_err(|e| map_write_err(e, "inline.execute"))?;
let affected = stmt
.raw_execute()
.map_err(|e| map_write_err(e, "inline.execute"))?;
if let Some(event_rows) = self.event_rows.as_deref() {
event_rows.observe(&statement, affected as u64);
}
Ok(affected as u64)
}
async fn execute_batch(
&mut self,
statements: Vec<SqlStatement>,
) -> khive_storage::types::StorageResult<u64> {
reject_transaction_control_statements(&statements, "inline.execute_batch")?;
let prepared = prepare_batch_statements(self.conn(), &statements)
.map_err(|e| map_write_err(e, "inline.execute_batch"))?;
execute_prepared_batch(
self.conn(),
prepared,
&statements,
self.event_rows.as_deref(),
)
.map_err(|e| map_write_err(e, "inline.execute_batch"))
}
async fn execute_script(&mut self, script: String) -> khive_storage::types::StorageResult<()> {
self.conn()
.execute_batch(&script)
.map_err(|e| map_write_err(e, "inline.execute_script"))
}
}
pub(super) fn block_on_sync<F: std::future::Future>(fut: F) -> Result<F::Output, StorageError> {
use std::task::{Context, Poll, RawWaker, RawWakerVTable, Waker};
fn no_op(_: *const ()) {}
fn clone_waker(_: *const ()) -> RawWaker {
RawWaker::new(std::ptr::null(), &VTABLE)
}
static VTABLE: RawWakerVTable = RawWakerVTable::new(clone_waker, no_op, no_op, no_op);
let raw_waker = RawWaker::new(std::ptr::null(), &VTABLE);
let waker = unsafe { Waker::from_raw(raw_waker) };
let mut cx = Context::from_waker(&waker);
let mut fut = std::pin::pin!(fut);
match fut.as_mut().poll(&mut cx) {
Poll::Ready(v) => Ok(v),
Poll::Pending => {
tracing::error!(target: "khive_db::sql_bridge",
"block_on_sync: atomic_unit future suspended on its first poll — \
the closure passed to SqlAccess::atomic_unit must be non-blocking \
(synchronous InlineWriter calls only, no real .await point)"
);
Err(StorageError::Internal(
"atomic_unit future suspended — closure must be non-blocking".to_string(),
))
}
}
}
pub struct SqlBridge {
pool: Arc<ConnectionPool>,
pub(super) is_file_backed: bool,
}
impl SqlBridge {
pub fn new(pool: Arc<ConnectionPool>, _is_file_backed: bool) -> Self {
let is_file_backed = pool.canonical_path().is_some();
Self {
pool,
is_file_backed,
}
}
}
#[async_trait]
impl khive_storage::SqlAccess for SqlBridge {
fn database_path(&self) -> Option<std::path::PathBuf> {
self.pool.canonical_path().map(std::path::Path::to_path_buf)
}
async fn reader(
&self,
) -> khive_storage::types::StorageResult<Box<dyn khive_storage::SqlReader>> {
if self.is_file_backed {
Ok(Box::new(SqliteReader {
handle: None,
pool: Arc::clone(&self.pool),
poisoned: false,
}))
} else {
Ok(Box::new(PoolBackedReader {
pool: Arc::clone(&self.pool),
transaction: None,
}))
}
}
async fn writer(
&self,
) -> khive_storage::types::StorageResult<Box<dyn khive_storage::SqlWriter>> {
if self.is_file_backed {
if self.pool.config().read_only {
return Err(StorageError::Pool {
operation: "writer".into(),
message: "backend is read-only".into(),
});
}
let db = crate::timeout_sink::db_label(&self.pool);
let writer_task = match self.pool.writer_task_handle() {
Ok(handle) => handle,
Err(e) => {
if self.pool.config().write_routing_strict {
return Err(e);
}
tracing::warn!(target: "khive_db::sql_bridge",
error = %e,
"KHIVE_WRITE_ROUTING is not strict; writer() degrades to the \
standalone-connection path"
);
None
}
};
if writer_task.is_none() && self.pool.config().write_routing_strict {
return Err(StorageError::Pool {
operation: "writer".into(),
message: "KHIVE_WRITE_ROUTING=strict but no writer-task handle is \
available; refusing to fall back to a direct connection"
.into(),
});
}
if writer_task.is_none() && self.pool.write_queue_active() {
crate::timeout_sink::emit_direct_route_violation(
&db,
crate::timeout_sink::Site::DirectRouteSqlBridgeWriter,
);
}
let handle = if writer_task.is_none() {
let handle_slot = acquire_handle_slot(
self.pool.sql_bridge_writer_slots(),
self.pool.config().checkout_timeout,
"sql_bridge.writer_handle",
SlotTimeoutClass::Admission,
)
.await?;
let (conn, handle_slot) =
open_standalone_writer_on_blocking(Arc::clone(&self.pool), handle_slot).await?;
Some(StandaloneHandle {
conn,
_retained_slot: Some(handle_slot),
read_transaction_slot: None,
})
} else {
None
};
Ok(Box::new(SqliteWriter {
observe_direct_errors: true,
event_rows: None,
handle,
writer_task,
origin: self.pool.origin(),
db,
pool: Arc::clone(&self.pool),
held_lease: None,
}))
} else {
Ok(Box::new(PoolBackedWriter {
pool: Arc::clone(&self.pool),
}))
}
}
async fn atomic_unit(
&self,
op: AtomicUnitOp,
) -> khive_storage::types::StorageResult<Box<dyn Any + Send>> {
let event_rows = Arc::new(AtomicEventRows::default());
let result = async {
if self.is_file_backed {
if self.pool.config().read_only {
return Err(StorageError::Pool {
operation: "atomic_unit".into(),
message: "backend is read-only".into(),
});
}
let handle = self.pool.writer_task_handle()?;
if handle.is_none() && self.pool.config().write_routing_strict {
return Err(StorageError::Pool {
operation: "atomic_unit".into(),
message: "KHIVE_WRITE_ROUTING=strict but no writer-task handle is \
available; refusing to fall back to a direct connection"
.into(),
});
}
if handle.is_none() && self.pool.write_queue_active() {
crate::timeout_sink::emit_direct_route_violation(
&crate::timeout_sink::db_label(&self.pool),
crate::timeout_sink::Site::DirectRouteAtomicUnit,
);
}
if let Some(writer_task) = handle {
let pending_event_rows = Arc::clone(&event_rows);
return writer_task
.send_bounded_at(
move |conn| {
let mut inline = InlineWriter {
event_rows: Some(Arc::clone(&pending_event_rows)),
conn: conn as *const rusqlite::Connection,
};
match block_on_sync(op(&mut inline)) {
Ok(inner) => inner,
Err(e) => Err(e),
}
},
"atomic_unit",
)
.await;
}
let handle_slot = acquire_handle_slot(
self.pool.sql_bridge_writer_slots(),
self.pool.config().checkout_timeout,
"sql_bridge.atomic_unit_handle",
SlotTimeoutClass::Admission,
)
.await?;
let unit_lease = acquire_unit_lease(Arc::clone(&self.pool)).await?;
let (conn, handle_slot) =
open_standalone_writer_on_blocking(Arc::clone(&self.pool), handle_slot).await?;
let mut writer = SqliteWriter {
observe_direct_errors: false,
event_rows: Some(Arc::clone(&event_rows)),
handle: Some(StandaloneHandle {
conn,
_retained_slot: Some(handle_slot),
read_transaction_slot: None,
}),
writer_task: None,
origin: self.pool.origin(),
db: crate::timeout_sink::db_label(&self.pool),
pool: Arc::clone(&self.pool),
held_lease: unit_lease,
};
run_manual_atomic_unit(&mut writer, op, self.pool.origin())
.await
.inspect_err(|error| self.pool.record_direct_writer_error(error))
} else {
let pool = Arc::clone(&self.pool);
let pending_event_rows = Arc::clone(&event_rows);
tokio::task::spawn_blocking(move || {
let guard = pool.try_writer().map_err(|error: SqliteError| {
StorageError::driver(StorageCapability::Sql, "atomic_unit", error)
})?;
let conn = guard.conn();
if !conn.is_autocommit() {
pool.retire_pooled_writer(conn);
return Err(StorageError::writer_task_terminated(
khive_storage::WriterTaskRequestState::SideEffectsUnknown,
));
}
if let Err(error) = conn.execute_batch("BEGIN IMMEDIATE") {
if !conn.is_autocommit() {
pool.retire_pooled_writer(conn);
return Err(SqliteError::settlement_with_cause(
&error,
khive_storage::error::SqliteWriteStage::Begin,
)
.into_storage_error(StorageCapability::Sql, "atomic_unit.begin"));
}
return Err(map_write_boundary_err(
error,
"atomic_unit.begin",
khive_storage::error::SqliteWriteStage::Begin,
))
.inspect_err(|error| pool.record_direct_writer_error(error));
}
let _tx_handle = khive_storage::tx_registry::register_scoped(
Some("atomic_unit".to_string()),
pool.origin(),
);
let (result, terminal_state) = crate::writer_task::execute_wrapped_transaction(
conn,
"atomic_unit.commit",
|conn| {
let mut inline = InlineWriter {
event_rows: Some(Arc::clone(&pending_event_rows)),
conn: conn as *const rusqlite::Connection,
};
block_on_sync(op(&mut inline)).and_then(|result| result)
},
);
if terminal_state.is_some() {
pool.retire_pooled_writer(conn);
}
result.inspect_err(|error| pool.record_direct_writer_error(error))
})
.await
.map_err(|error| {
StorageError::driver(StorageCapability::Sql, "atomic_unit", error)
})?
}
}
.await;
khive_storage::usage::account_event_write(
result.as_ref().map(|_| event_rows.committed_rows()),
);
result
}
}