1use std::collections::HashSet;
2use std::future::Future;
3use std::io::Cursor;
4use std::sync::{
5 Arc,
6 atomic::{AtomicBool, Ordering},
7};
8
9use anyhow::{Context, Result};
10use depot_client_types::is_head_fence_mismatch;
11pub use depot_client_types::{BindParam, ColumnValue, ExecResult, ExecuteResult, QueryResult};
12#[cfg(feature = "sqlite-local")]
13use parking_lot::Mutex;
14use rivet_envoy_client::protocol;
15use rivet_envoy_client::{
16 handle::EnvoyHandle,
17 utils::{RemoteSqliteConnectionSessionLostError, RemoteSqliteIndeterminateResultError},
18};
19use rivet_error::{ActorSpecifier, RivetError};
20use serde::Serialize;
21use serde_json::{Map as JsonMap, Value as JsonValue};
22#[cfg(feature = "sqlite-local")]
23use tokio::sync::Mutex as AsyncMutex;
24#[cfg(feature = "sqlite-local")]
25use tokio::task::JoinHandle;
26use tracing::Instrument as _;
27
28use crate::telemetry::SqliteOperation;
29
30#[cfg(feature = "sqlite-local")]
31mod envoy_sqlite_transport;
32#[cfg(feature = "sqlite-local")]
33mod profiling;
34mod tx;
35
36pub use tx::{
37 DEFAULT_TRANSACTION_TIMEOUT, SqliteTransaction, TRANSACTION_COORDINATOR_QUEUE_CAPACITY,
38 TransactionConnectionLostError, TransactionCoordinatorClosedError, TransactionExpiredError,
39 TransactionInvalidArgumentError, TransactionQueueFullError, TransactionTerminalError,
40 TransactionUnknownError,
41};
42#[cfg(test)]
43use tx::{
44 TRANSACTION_TERMINAL_CAPACITY, TransactionCoordinatorState, TransactionTerminalState,
45 insert_terminal_state,
46};
47use tx::{TransactionCoordinator, run_detached_transaction_task};
48
49#[cfg(feature = "sqlite-local")]
50use crate::error::ActorLifecycle;
51use crate::error::SqliteRuntimeError;
52#[cfg(all(not(target_arch = "wasm32"), feature = "sqlite-local"))]
53use crate::runtime::RuntimeSpawner;
54
55#[cfg(feature = "sqlite-local")]
56use depot_client::{
57 database::{NativeDatabaseHandle, open_database_from_transport},
58 vfs::{SqliteVfsMetrics, SqliteVfsMetricsSnapshot},
59 worker::{
60 SQLITE_WORKER_QUEUE_CAPACITY, SqliteWorkerCloseTimeoutError, SqliteWorkerClosingError,
61 SqliteWorkerDeadError, SqliteWorkerFatalError, SqliteWorkerOverloadedError,
62 SqliteWorkerResult,
63 },
64};
65#[cfg(feature = "sqlite-local")]
66use envoy_sqlite_transport::EnvoySqliteTransport;
67
68#[cfg(not(feature = "sqlite-local"))]
69#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
70pub struct SqliteVfsMetricsSnapshot {
71 pub request_build_ns: u64,
72 pub serialize_ns: u64,
73 pub transport_ns: u64,
74 pub state_update_ns: u64,
75 pub total_ns: u64,
76 pub commit_count: u64,
77}
78
79#[derive(Clone)]
80pub struct SqliteRuntimeConfig {
81 pub handle: EnvoyHandle,
82 pub actor_id: String,
83 pub generation: Option<u64>,
84}
85
86#[derive(Clone, Debug)]
87pub struct SqliteBatchStatement {
88 pub sql: String,
89 pub params: Option<Vec<BindParam>>,
90}
91
92#[derive(Clone, Copy, Debug, Eq, PartialEq)]
93pub enum SqliteBackend {
94 LocalNative,
95 RemoteEnvoy,
96}
97
98#[cfg(feature = "sqlite-local")]
99struct ProfiledBackendResult<T> {
100 result: Result<T>,
101 profile: Option<depot_client::vfs::SqliteOperationProfile>,
102}
103
104impl SqliteDb {
105 #[cfg(feature = "sqlite-local")]
106 async fn exec_backend_profiled(&self, sql: String) -> ProfiledBackendResult<QueryResult> {
107 match self.backend {
108 SqliteBackend::LocalNative => match self.local_exec_profiled(sql).await {
109 Ok(result) => ProfiledBackendResult {
110 result: result.result,
111 profile: Some(result.profile),
112 },
113 Err(error) => ProfiledBackendResult {
114 result: Err(error),
115 profile: None,
116 },
117 },
118 SqliteBackend::RemoteEnvoy => ProfiledBackendResult {
119 result: self.remote_exec(sql).await,
120 profile: None,
121 },
122 }
123 }
124
125 #[cfg(feature = "sqlite-local")]
126 async fn execute_backend_profiled(
127 &self,
128 sql: String,
129 params: Option<Vec<BindParam>>,
130 ) -> ProfiledBackendResult<ExecuteResult> {
131 match self.backend {
132 SqliteBackend::LocalNative => match self.local_execute_profiled(sql, params).await {
133 Ok(result) => ProfiledBackendResult {
134 result: result.result,
135 profile: Some(result.profile),
136 },
137 Err(error) => ProfiledBackendResult {
138 result: Err(error),
139 profile: None,
140 },
141 },
142 SqliteBackend::RemoteEnvoy => ProfiledBackendResult {
143 result: self.remote_execute(sql, params).await,
144 profile: None,
145 },
146 }
147 }
148
149 async fn exec_backend(&self, sql: String) -> Result<QueryResult> {
150 match self.backend {
151 SqliteBackend::LocalNative => self.local_exec(sql).await,
152 SqliteBackend::RemoteEnvoy => self.remote_exec(sql).await,
153 }
154 }
155
156 async fn exec_backend_in_session(
157 &self,
158 sql: String,
159 expected_session: Option<u64>,
160 ) -> Result<(QueryResult, Option<u64>)> {
161 match self.backend {
162 SqliteBackend::LocalNative => self.local_exec(sql).await.map(|result| (result, None)),
163 SqliteBackend::RemoteEnvoy => self
164 .remote_exec_with_session(sql, expected_session)
165 .await
166 .map(|(result, session)| (result, Some(session))),
167 }
168 }
169
170 async fn query_backend(
171 &self,
172 sql: String,
173 params: Option<Vec<BindParam>>,
174 ) -> Result<QueryResult> {
175 match self.backend {
176 SqliteBackend::LocalNative => self
177 .local_execute(sql, params)
178 .await
179 .map(ExecuteResult::into_query_result),
180 SqliteBackend::RemoteEnvoy => self
181 .remote_execute(sql, params)
182 .await
183 .map(ExecuteResult::into_query_result),
184 }
185 }
186
187 async fn run_backend(&self, sql: String, params: Option<Vec<BindParam>>) -> Result<ExecResult> {
188 match self.backend {
189 SqliteBackend::LocalNative => self
190 .local_execute(sql, params)
191 .await
192 .map(ExecuteResult::into_exec_result),
193 SqliteBackend::RemoteEnvoy => self
194 .remote_execute(sql, params)
195 .await
196 .map(ExecuteResult::into_exec_result),
197 }
198 }
199
200 async fn execute_backend(
201 &self,
202 sql: String,
203 params: Option<Vec<BindParam>>,
204 ) -> Result<ExecuteResult> {
205 match self.backend {
206 SqliteBackend::LocalNative => self.local_execute(sql, params).await,
207 SqliteBackend::RemoteEnvoy => self.remote_execute(sql, params).await,
208 }
209 }
210
211 async fn execute_backend_in_session(
212 &self,
213 sql: String,
214 params: Option<Vec<BindParam>>,
215 expected_session: Option<u64>,
216 ) -> Result<(ExecuteResult, Option<u64>)> {
217 match self.backend {
218 SqliteBackend::LocalNative => self
219 .local_execute(sql, params)
220 .await
221 .map(|result| (result, None)),
222 SqliteBackend::RemoteEnvoy => self
223 .remote_execute_with_session(sql, params, expected_session)
224 .await
225 .map(|(result, session)| (result, Some(session))),
226 }
227 }
228}
229
230#[derive(Clone)]
231pub struct SqliteDb {
232 handle: Option<EnvoyHandle>,
233 actor_id: Option<String>,
234 actor_key: Option<String>,
235 generation: Option<u64>,
236 backend: SqliteBackend,
237 enabled: bool,
241 invocation_telemetry: Option<crate::ActorInvocationTelemetry>,
242 #[cfg(feature = "sqlite-local")]
243 db: Arc<Mutex<Option<NativeDatabaseHandle>>>,
246 #[cfg(feature = "sqlite-local")]
247 open_lock: Arc<AsyncMutex<()>>,
248 #[cfg(feature = "sqlite-local")]
249 worker_failure_task: Arc<Mutex<Option<JoinHandle<()>>>>,
250 worker_fatal_reported: Arc<AtomicBool>,
251 transaction_coordinator: Arc<TransactionCoordinator>,
252 #[cfg(feature = "sqlite-local")]
253 vfs_metrics: Option<Arc<dyn SqliteVfsMetrics>>,
254 #[cfg(feature = "sqlite-local")]
255 profiling: Arc<profiling::SqliteProfilingState>,
256}
257
258impl Default for SqliteDb {
259 fn default() -> Self {
260 Self {
261 handle: None,
262 actor_id: None,
263 actor_key: None,
264 generation: None,
265 backend: if cfg!(feature = "sqlite-local") {
266 SqliteBackend::LocalNative
267 } else {
268 SqliteBackend::RemoteEnvoy
269 },
270 enabled: false,
271 invocation_telemetry: None,
272 #[cfg(feature = "sqlite-local")]
273 db: Default::default(),
274 #[cfg(feature = "sqlite-local")]
275 open_lock: Default::default(),
276 #[cfg(feature = "sqlite-local")]
277 worker_failure_task: Default::default(),
278 worker_fatal_reported: Default::default(),
279 transaction_coordinator: Default::default(),
280 #[cfg(feature = "sqlite-local")]
281 vfs_metrics: None,
282 #[cfg(feature = "sqlite-local")]
283 profiling: Default::default(),
284 }
285 }
286}
287
288impl SqliteDb {
289 pub fn new(handle: EnvoyHandle, actor_id: impl Into<String>, enabled: bool) -> Result<Self> {
290 Self::new_with_remote_sqlite(handle, actor_id, None, None, enabled, false)
291 }
292
293 pub fn new_with_remote_sqlite(
294 handle: EnvoyHandle,
295 actor_id: impl Into<String>,
296 actor_key: Option<String>,
297 generation: Option<u64>,
298 enabled: bool,
299 remote_sqlite: bool,
300 ) -> Result<Self> {
301 Ok(Self {
302 handle: Some(handle),
303 actor_id: Some(actor_id.into()),
304 actor_key,
305 generation,
306 backend: select_sqlite_backend(remote_sqlite)?,
307 enabled,
308 invocation_telemetry: None,
309 #[cfg(feature = "sqlite-local")]
310 db: Default::default(),
311 #[cfg(feature = "sqlite-local")]
312 open_lock: Default::default(),
313 #[cfg(feature = "sqlite-local")]
314 worker_failure_task: Default::default(),
315 worker_fatal_reported: Default::default(),
316 transaction_coordinator: Default::default(),
317 #[cfg(feature = "sqlite-local")]
318 vfs_metrics: None,
319 #[cfg(feature = "sqlite-local")]
320 profiling: Default::default(),
321 })
322 }
323
324 #[cfg(feature = "sqlite-local")]
325 pub(crate) fn set_vfs_metrics(&mut self, metrics: Arc<dyn SqliteVfsMetrics>) {
326 self.vfs_metrics = Some(metrics);
327 }
328
329 #[cfg(feature = "sqlite-local")]
330 pub(crate) fn set_profiling_config(&mut self, config: crate::SqliteProfilingConfig) {
331 self.profiling = Arc::new(profiling::SqliteProfilingState::new(config));
332 }
333
334 #[cfg(all(test, feature = "sqlite-local"))]
335 pub(crate) fn from_native_database_for_test(
336 actor_id: impl Into<String>,
337 generation: u64,
338 native_db: NativeDatabaseHandle,
339 metrics: Arc<dyn SqliteVfsMetrics>,
340 profiling: crate::SqliteProfilingConfig,
341 ) -> Self {
342 Self {
343 actor_id: Some(actor_id.into()),
344 generation: Some(generation),
345 enabled: true,
346 db: Arc::new(Mutex::new(Some(native_db))),
347 vfs_metrics: Some(metrics),
348 profiling: Arc::new(profiling::SqliteProfilingState::new(profiling)),
349 ..Self::default()
350 }
351 }
352
353 pub fn is_enabled(&self) -> bool {
354 self.enabled
355 }
356
357 pub fn backend(&self) -> SqliteBackend {
358 self.backend
359 }
360
361 #[doc(hidden)]
362 pub fn with_invocation_telemetry(
363 mut self,
364 telemetry: Option<crate::ActorInvocationTelemetry>,
365 ) -> Self {
366 self.invocation_telemetry = telemetry;
367 self
368 }
369
370 pub(super) async fn traced<T>(
373 &self,
374 operation: SqliteOperation,
375 future: impl Future<Output = Result<T>>,
376 ) -> Result<T> {
377 let Some(mut span) = self
378 .invocation_telemetry
379 .as_ref()
380 .and_then(|telemetry| telemetry.start_sqlite(operation))
381 else {
382 return future.await;
383 };
384 let result = future.instrument(span.span()).await;
385 span.finish(result.as_ref().err());
386 result
387 }
388
389 pub async fn get_pages(
390 &self,
391 request: protocol::SqliteGetPagesRequest,
392 ) -> Result<protocol::SqliteGetPagesResponse> {
393 self.handle()?.sqlite_get_pages(request).await
394 }
395
396 pub async fn commit(
397 &self,
398 request: protocol::SqliteCommitRequest,
399 ) -> Result<protocol::SqliteCommitResponse> {
400 self.handle()?.sqlite_commit(request).await
401 }
402
403 pub async fn open(&self) -> Result<()> {
404 match self.backend {
405 SqliteBackend::LocalNative => {
406 #[cfg(feature = "sqlite-local")]
407 {
408 let _open_guard = self.open_lock.lock().await;
409 if self.db.lock().is_some() {
410 return Ok(());
411 }
412
413 let config = self.runtime_config()?;
414 let vfs_metrics = self.vfs_metrics.clone();
415 let rt_handle = tokio::runtime::Handle::try_current()
416 .context("open sqlite database requires a tokio runtime")?;
417 self.worker_fatal_reported.store(false, Ordering::Release);
418
419 let native_db = self.map_local_worker_result(
420 open_database_from_transport(
421 Arc::new(EnvoySqliteTransport::new(config.handle.clone())),
422 config.actor_id.clone(),
423 config
424 .generation
425 .ok_or_else(|| sqlite_not_configured("generation"))?,
426 rt_handle,
427 vfs_metrics,
428 )
429 .await,
430 )?;
431 self.start_worker_failure_monitor(native_db.clone(), config);
432 *self.db.lock() = Some(native_db);
433 if let Some(metrics) = self.vfs_metrics.as_ref() {
434 metrics.set_worker_active(true);
435 }
436 Ok(())
437 }
438
439 #[cfg(not(feature = "sqlite-local"))]
440 {
441 Err(SqliteRuntimeError::Unavailable.build())
442 }
443 }
444 SqliteBackend::RemoteEnvoy => {
445 self.remote_config()?;
446 Ok(())
447 }
448 }
449 }
450
451 #[cfg(feature = "sqlite-local")]
452 async fn local_exec(&self, sql: String) -> Result<QueryResult> {
453 self.open().await?;
454 self.map_local_worker_result(self.native_db_handle()?.exec(sql).await)
455 }
456
457 #[cfg(feature = "sqlite-local")]
458 async fn local_exec_profiled(&self, sql: String) -> Result<SqliteWorkerResult<QueryResult>> {
459 self.open().await?;
460 self.map_local_worker_result(self.native_db_handle()?.exec_profiled(sql).await)
461 }
462
463 #[cfg(not(feature = "sqlite-local"))]
464 async fn local_exec(&self, _sql: String) -> Result<QueryResult> {
465 Err(SqliteRuntimeError::Unavailable.build())
466 }
467
468 #[cfg(feature = "sqlite-local")]
469 async fn local_execute(
470 &self,
471 sql: String,
472 params: Option<Vec<BindParam>>,
473 ) -> Result<ExecuteResult> {
474 self.open().await?;
475 self.map_local_worker_result(self.native_db_handle()?.execute(sql, params).await)
476 }
477
478 #[cfg(feature = "sqlite-local")]
479 async fn local_execute_profiled(
480 &self,
481 sql: String,
482 params: Option<Vec<BindParam>>,
483 ) -> Result<SqliteWorkerResult<ExecuteResult>> {
484 self.open().await?;
485 self.map_local_worker_result(self.native_db_handle()?.execute_profiled(sql, params).await)
486 }
487
488 #[cfg(not(feature = "sqlite-local"))]
489 async fn local_execute(
490 &self,
491 _sql: String,
492 _params: Option<Vec<BindParam>>,
493 ) -> Result<ExecuteResult> {
494 Err(SqliteRuntimeError::Unavailable.build())
495 }
496
497 #[cfg(feature = "sqlite-local")]
498 fn observe_statement_profile(
499 &self,
500 sql: &str,
501 started_at: crate::time::Instant,
502 transaction_wait: std::time::Duration,
503 profile: Option<depot_client::vfs::SqliteOperationProfile>,
504 outcome: &'static str,
505 transaction_mode: &'static str,
506 ) -> Option<profiling::StatementObservation> {
507 if self.backend != SqliteBackend::LocalNative {
508 return None;
509 }
510 let fingerprint = self.profiling.statement_fingerprint(sql)?;
511 let observation = profiling::StatementObservation {
512 fingerprint,
513 total_ns: duration_ns(started_at.elapsed()),
514 transaction_wait_ns: duration_ns(transaction_wait),
515 profile: profile.unwrap_or_default(),
516 };
517 if let Some(metrics) = &self.vfs_metrics {
518 let metric = depot_client::vfs::SqliteOperationMetric {
519 operation_type: "statement",
520 fingerprint: observation.fingerprint.display.clone(),
521 fingerprint_source: "query",
522 transaction_mode,
523 storage_transport: "proxy",
524 outcome,
525 sql_bytes: sql.len().try_into().unwrap_or(u64::MAX),
526 total_ns: observation.total_ns,
527 transaction_wait_ns: observation.transaction_wait_ns,
528 profile: observation.profile.clone(),
529 };
530 if metrics.observe_operation_profile(&metric)
531 && self.profiling.mark_cataloged(&metric.fingerprint)
532 {
533 metrics.record_fingerprint_catalog(
534 "statement",
535 &metric.fingerprint,
536 sql,
537 profiling::FINGERPRINT_FORMAT_VERSION,
538 );
539 }
540 metrics.emit_operation_diagnostic_event(
541 self.actor_id.as_deref().unwrap_or("unknown"),
542 self.generation,
543 &metric,
544 );
545 }
546 Some(observation)
547 }
548
549 pub async fn exec(&self, sql: impl Into<String>) -> Result<QueryResult> {
550 let sql = sql.into();
551 self.traced(SqliteOperation::Exec, self.exec_untraced(sql))
552 .await
553 }
554
555 async fn exec_untraced(&self, sql: String) -> Result<QueryResult> {
556 let sql_for_log = sql.clone();
557 #[cfg(feature = "sqlite-local")]
558 let started_at = self
559 .profiling
560 .config
561 .enabled
562 .then(crate::time::Instant::now);
563 #[cfg(feature = "sqlite-local")]
564 let transaction_wait_started_at = started_at.map(|_| crate::time::Instant::now());
565 let guard = self.begin_regular_operation().await;
566 #[cfg(feature = "sqlite-local")]
567 let transaction_wait = transaction_wait_started_at.map(|started| started.elapsed());
568 #[cfg(feature = "sqlite-local")]
569 let (result, profile) = match guard {
570 Ok(_guard) if started_at.is_some() => {
571 let profiled = self.exec_backend_profiled(sql).await;
572 (profiled.result, profiled.profile)
573 }
574 Ok(_guard) => (self.exec_backend(sql).await, None),
575 Err(error) => (Err(error), None),
576 };
577 #[cfg(not(feature = "sqlite-local"))]
578 let result = match guard {
579 Ok(_guard) => self.exec_backend(sql).await,
580 Err(error) => Err(error),
581 };
582 #[cfg(feature = "sqlite-local")]
583 if let Some(started_at) = started_at {
584 self.observe_statement_profile(
585 &sql_for_log,
586 started_at,
587 transaction_wait.unwrap_or_default(),
588 profile,
589 if result.is_ok() { "success" } else { "error" },
590 "autocommit",
591 );
592 }
593 match result {
594 Ok(result) => Ok(result),
595 Err(error) => {
596 let error = self.attach_actor(error);
597 self.log_operation_error("exec", &sql_for_log, 0, &error);
598 Err(error)
599 }
600 }
601 }
602
603 pub async fn query(
604 &self,
605 sql: impl Into<String>,
606 params: Option<Vec<BindParam>>,
607 ) -> Result<QueryResult> {
608 let sql = sql.into();
609 self.traced(SqliteOperation::Query, self.query_untraced(sql, params))
610 .await
611 }
612
613 async fn query_untraced(
614 &self,
615 sql: String,
616 params: Option<Vec<BindParam>>,
617 ) -> Result<QueryResult> {
618 let sql_for_log = sql.clone();
619 let binding_count = bind_param_count(¶ms);
620 #[cfg(feature = "sqlite-local")]
621 let started_at = self
622 .profiling
623 .config
624 .enabled
625 .then(crate::time::Instant::now);
626 #[cfg(feature = "sqlite-local")]
627 let transaction_wait_started_at = started_at.map(|_| crate::time::Instant::now());
628 let guard = self.begin_regular_operation().await;
629 #[cfg(feature = "sqlite-local")]
630 let transaction_wait = transaction_wait_started_at.map(|started| started.elapsed());
631 #[cfg(feature = "sqlite-local")]
632 let (result, profile) = match guard {
633 Ok(_guard) if started_at.is_some() => {
634 let profiled = self.execute_backend_profiled(sql, params).await;
635 (
636 profiled.result.map(|result| QueryResult {
637 columns: result.columns,
638 rows: result.rows,
639 }),
640 profiled.profile,
641 )
642 }
643 Ok(_guard) => (self.query_backend(sql, params).await, None),
644 Err(error) => (Err(error), None),
645 };
646 #[cfg(not(feature = "sqlite-local"))]
647 let result = match guard {
648 Ok(_guard) => self.query_backend(sql, params).await,
649 Err(error) => Err(error),
650 };
651 #[cfg(feature = "sqlite-local")]
652 if let Some(started_at) = started_at {
653 self.observe_statement_profile(
654 &sql_for_log,
655 started_at,
656 transaction_wait.unwrap_or_default(),
657 profile,
658 if result.is_ok() { "success" } else { "error" },
659 "autocommit",
660 );
661 }
662 match result {
663 Ok(result) => Ok(result),
664 Err(error) => {
665 let error = self.attach_actor(error);
666 self.log_operation_error("query", &sql_for_log, binding_count, &error);
667 Err(error)
668 }
669 }
670 }
671
672 pub async fn run(
673 &self,
674 sql: impl Into<String>,
675 params: Option<Vec<BindParam>>,
676 ) -> Result<ExecResult> {
677 let sql = sql.into();
678 self.traced(SqliteOperation::Run, self.run_untraced(sql, params))
679 .await
680 }
681
682 async fn run_untraced(
683 &self,
684 sql: String,
685 params: Option<Vec<BindParam>>,
686 ) -> Result<ExecResult> {
687 let sql_for_log = sql.clone();
688 let binding_count = bind_param_count(¶ms);
689 #[cfg(feature = "sqlite-local")]
690 let started_at = self
691 .profiling
692 .config
693 .enabled
694 .then(crate::time::Instant::now);
695 #[cfg(feature = "sqlite-local")]
696 let transaction_wait_started_at = started_at.map(|_| crate::time::Instant::now());
697 let guard = self.begin_regular_operation().await;
698 #[cfg(feature = "sqlite-local")]
699 let transaction_wait = transaction_wait_started_at.map(|started| started.elapsed());
700 #[cfg(feature = "sqlite-local")]
701 let (result, profile) = match guard {
702 Ok(_guard) if started_at.is_some() => {
703 let profiled = self.execute_backend_profiled(sql, params).await;
704 (
705 profiled.result.map(|result| ExecResult {
706 changes: result.changes,
707 }),
708 profiled.profile,
709 )
710 }
711 Ok(_guard) => (self.run_backend(sql, params).await, None),
712 Err(error) => (Err(error), None),
713 };
714 #[cfg(not(feature = "sqlite-local"))]
715 let result = match guard {
716 Ok(_guard) => self.run_backend(sql, params).await,
717 Err(error) => Err(error),
718 };
719 #[cfg(feature = "sqlite-local")]
720 if let Some(started_at) = started_at {
721 self.observe_statement_profile(
722 &sql_for_log,
723 started_at,
724 transaction_wait.unwrap_or_default(),
725 profile,
726 if result.is_ok() { "success" } else { "error" },
727 "autocommit",
728 );
729 }
730 match result {
731 Ok(result) => Ok(result),
732 Err(error) => {
733 let error = self.attach_actor(error);
734 self.log_operation_error("run", &sql_for_log, binding_count, &error);
735 Err(error)
736 }
737 }
738 }
739
740 pub async fn execute(
741 &self,
742 sql: impl Into<String>,
743 params: Option<Vec<BindParam>>,
744 ) -> Result<ExecuteResult> {
745 let sql = sql.into();
746 self.traced(SqliteOperation::Execute, self.execute_untraced(sql, params))
747 .await
748 }
749
750 async fn execute_untraced(
751 &self,
752 sql: String,
753 params: Option<Vec<BindParam>>,
754 ) -> Result<ExecuteResult> {
755 let sql_for_log = sql.clone();
756 let binding_count = bind_param_count(¶ms);
757 #[cfg(feature = "sqlite-local")]
758 let started_at = self
759 .profiling
760 .config
761 .enabled
762 .then(crate::time::Instant::now);
763 #[cfg(feature = "sqlite-local")]
764 let transaction_wait_started_at = started_at.map(|_| crate::time::Instant::now());
765 let guard = self.begin_regular_operation().await;
766 #[cfg(feature = "sqlite-local")]
767 let transaction_wait = transaction_wait_started_at.map(|started| started.elapsed());
768 #[cfg(feature = "sqlite-local")]
769 let (result, profile) = match guard {
770 Ok(_guard) if started_at.is_some() => {
771 let profiled = self.execute_backend_profiled(sql, params).await;
772 (profiled.result, profiled.profile)
773 }
774 Ok(_guard) => (self.execute_backend(sql, params).await, None),
775 Err(error) => (Err(error), None),
776 };
777 #[cfg(not(feature = "sqlite-local"))]
778 let result = match guard {
779 Ok(_guard) => self.execute_backend(sql, params).await,
780 Err(error) => Err(error),
781 };
782 #[cfg(feature = "sqlite-local")]
783 if let Some(started_at) = started_at {
784 self.observe_statement_profile(
785 &sql_for_log,
786 started_at,
787 transaction_wait.unwrap_or_default(),
788 profile,
789 if result.is_ok() { "success" } else { "error" },
790 "autocommit",
791 );
792 }
793 match result {
794 Ok(result) => Ok(result),
795 Err(error) => {
796 let error = self.attach_actor(error);
797 self.log_operation_error("execute", &sql_for_log, binding_count, &error);
798 Err(error)
799 }
800 }
801 }
802
803 pub async fn execute_batch(
804 &self,
805 statements: Vec<SqliteBatchStatement>,
806 ) -> Result<Vec<ExecuteResult>> {
807 self.traced(
808 SqliteOperation::ExecuteBatch,
809 self.execute_batch_untraced(statements),
810 )
811 .await
812 }
813
814 async fn execute_batch_untraced(
815 &self,
816 statements: Vec<SqliteBatchStatement>,
817 ) -> Result<Vec<ExecuteResult>> {
818 let statement_count = statements.len();
819 let binding_count = statements
820 .iter()
821 .map(|statement| bind_param_count(&statement.params))
822 .sum();
823 let result = if self.backend == SqliteBackend::RemoteEnvoy {
824 match self.begin_regular_operation().await {
825 Ok(_guard) => self.remote_execute_batch(statements).await,
826 Err(error) => Err(error),
827 }
828 } else {
829 async {
830 let transaction = self
831 .clone()
832 .with_invocation_telemetry(None)
833 .begin_transaction(None)
834 .await?;
835 let mut results = Vec::with_capacity(statements.len());
836 for statement in statements {
837 match transaction.execute(statement.sql, statement.params).await {
838 Ok(result) => results.push(result),
839 Err(error) => {
840 return match transaction.rollback().await {
841 Ok(()) => Err(error.context("execute sqlite batch statement")),
842 Err(rollback_error) => {
843 Err(error.context("execute sqlite batch statement").context(
844 rollback_error.context("rollback sqlite batch transaction"),
845 ))
846 }
847 };
848 }
849 }
850 }
851 transaction
852 .commit()
853 .await
854 .context("commit sqlite batch transaction")?;
855 Ok(results)
856 }
857 .await
858 };
859
860 match result {
861 Ok(results) => Ok(results),
862 Err(error) => {
863 let error = self.attach_actor(error);
864 self.log_operation_error_with_count(
865 "execute_batch",
866 "<batch>",
867 binding_count,
868 statement_count,
869 &error,
870 );
871 Err(error)
872 }
873 }
874 }
875
876 pub async fn close(&self) -> Result<()> {
877 let db = self.clone();
878 run_detached_transaction_task(
879 async move {
880 let _gate = db.shutdown_transaction_coordinator().await;
881 db.close_backend(false).await
882 },
883 "sqlite close task failed",
884 )
885 .await
886 }
887
888 pub async fn close_and_wait(&self) -> Result<()> {
891 let _gate = self.shutdown_transaction_coordinator().await;
895 self.close_backend(true).await
896 }
897
898 async fn close_backend(&self, _wait_for_worker: bool) -> Result<()> {
899 match self.backend {
900 SqliteBackend::LocalNative => {
901 #[cfg(feature = "sqlite-local")]
902 {
903 let native_db = self.db.lock().clone();
904 if let Some(native_db) = native_db {
905 let close_result = if _wait_for_worker {
906 native_db.close_and_wait().await
907 } else {
908 native_db.close().await
909 };
910 let close_timed_out = close_result.as_ref().err().is_some_and(|error| {
911 error
912 .downcast_ref::<SqliteWorkerCloseTimeoutError>()
913 .is_some()
914 });
915 let release_handle = _wait_for_worker || !close_timed_out;
916 let result = self.map_local_worker_result(close_result);
917 self.abort_worker_failure_monitor();
918 if let Some(metrics) = self.vfs_metrics.as_ref() {
919 metrics.set_worker_active(false);
920 }
921 if release_handle {
925 self.db.lock().take();
926 }
927 result?;
928 }
929 }
930 Ok(())
931 }
932 SqliteBackend::RemoteEnvoy => Ok(()),
933 }
934 }
935
936 pub(crate) async fn cleanup_for_shutdown(&self, reusable: bool) -> Result<()> {
937 if reusable && self.backend == SqliteBackend::RemoteEnvoy {
940 return Ok(());
941 }
942 self.close().await
943 }
944
945 #[cfg(test)]
946 pub(crate) fn fresh_remote_for_test(&self) -> Self {
947 Self::new_with_remote_sqlite(
948 self.handle
949 .clone()
950 .expect("remote sqlite test database should have an envoy handle"),
951 self.actor_id
952 .clone()
953 .expect("remote sqlite test database should have an actor id"),
954 self.actor_key.clone(),
955 self.generation,
956 self.enabled,
957 true,
958 )
959 .expect("remote sqlite test database should be configured")
960 }
961
962 pub fn take_last_kv_error(&self) -> Option<String> {
963 if self.backend != SqliteBackend::LocalNative {
964 return None;
965 }
966
967 #[cfg(feature = "sqlite-local")]
968 {
969 return self
970 .db
971 .lock()
972 .as_ref()
973 .and_then(NativeDatabaseHandle::take_last_kv_error);
974 }
975
976 #[cfg(not(feature = "sqlite-local"))]
977 None
978 }
979
980 #[cfg(feature = "sqlite-local")]
981 fn native_db_handle(&self) -> Result<NativeDatabaseHandle> {
982 self.db
983 .lock()
984 .as_ref()
985 .cloned()
986 .ok_or_else(|| SqliteRuntimeError::Closed.build())
987 }
988
989 #[cfg(feature = "sqlite-local")]
990 fn map_local_worker_result<T>(&self, result: Result<T>) -> Result<T> {
991 match result {
992 Ok(value) => Ok(value),
993 Err(error) => {
994 if is_fatal_worker_error(&error) {
995 self.report_worker_fatal(&error);
996 }
997 Err(map_local_worker_error(error))
998 }
999 }
1000 }
1001
1002 #[cfg(feature = "sqlite-local")]
1003 fn report_worker_fatal(&self, error: &anyhow::Error) {
1004 let Ok(config) = self.runtime_config() else {
1005 return;
1006 };
1007 report_sqlite_worker_fatal(
1008 &self.worker_fatal_reported,
1009 config,
1010 sqlite_worker_fatal_message(error),
1011 );
1012 }
1013
1014 #[cfg(feature = "sqlite-local")]
1015 fn start_worker_failure_monitor(
1016 &self,
1017 native_db: NativeDatabaseHandle,
1018 config: SqliteRuntimeConfig,
1019 ) {
1020 self.abort_worker_failure_monitor();
1021 let reported = Arc::clone(&self.worker_fatal_reported);
1022 let task = RuntimeSpawner::spawn(async move {
1023 if native_db.wait_for_worker_failure().await {
1024 report_sqlite_worker_fatal(
1025 &reported,
1026 config,
1027 "sqlite worker thread stopped unexpectedly".to_string(),
1028 );
1029 }
1030 });
1031 *self.worker_failure_task.lock() = Some(task);
1032 }
1033
1034 #[cfg(feature = "sqlite-local")]
1035 fn abort_worker_failure_monitor(&self) {
1036 if let Some(task) = self.worker_failure_task.lock().take() {
1037 task.abort();
1038 }
1039 }
1040
1041 pub fn metrics(&self) -> Option<SqliteVfsMetricsSnapshot> {
1042 #[cfg(feature = "sqlite-local")]
1043 {
1044 self.db
1045 .lock()
1046 .as_ref()
1047 .map(NativeDatabaseHandle::sqlite_vfs_metrics)
1048 }
1049
1050 #[cfg(not(feature = "sqlite-local"))]
1051 {
1052 None
1053 }
1054 }
1055
1056 pub fn runtime_config(&self) -> Result<SqliteRuntimeConfig> {
1057 Ok(SqliteRuntimeConfig {
1058 handle: self.handle()?,
1059 actor_id: self
1060 .actor_id
1061 .clone()
1062 .ok_or_else(|| sqlite_not_configured("actor id"))?,
1063 generation: self.generation,
1064 })
1065 }
1066
1067 fn log_operation_error(
1068 &self,
1069 operation: &'static str,
1070 sql: &str,
1071 binding_count: usize,
1072 error: &anyhow::Error,
1073 ) {
1074 self.log_operation_error_with_count(operation, sql, binding_count, 1, error);
1075 }
1076
1077 fn log_operation_error_with_count(
1078 &self,
1079 operation: &'static str,
1080 sql: &str,
1081 binding_count: usize,
1082 statement_count: usize,
1083 error: &anyhow::Error,
1084 ) {
1085 let structured = RivetError::extract(error);
1086 let error_chain = error.chain().map(ToString::to_string).collect::<Vec<_>>();
1087 tracing::error!(
1088 actor_id = self.actor_id.as_deref().unwrap_or("<unknown>"),
1089 generation = ?self.generation,
1090 backend = ?self.backend,
1091 operation,
1092 sql,
1093 binding_count,
1094 statement_count,
1095 group = structured.group(),
1096 code = structured.code(),
1097 error_message = %structured.message(),
1098 metadata = ?structured.metadata(),
1099 error_chain = ?error_chain,
1100 "sqlite operation failed"
1101 );
1102 }
1103
1104 fn actor_specifier(&self) -> Option<ActorSpecifier> {
1105 let mut specifier = ActorSpecifier::new(self.actor_id.as_ref()?.clone(), self.generation?);
1106 if let Some(key) = self.actor_key.as_ref() {
1107 specifier = specifier.with_key(key.clone());
1108 }
1109 Some(specifier)
1110 }
1111
1112 fn attach_actor(&self, error: anyhow::Error) -> anyhow::Error {
1113 match self.actor_specifier() {
1114 Some(actor) => error.context(actor),
1115 None => error,
1116 }
1117 }
1118
1119 fn remote_config(&self) -> Result<RemoteSqliteConfig> {
1120 let config = self.runtime_config()?;
1121 let generation = config
1122 .generation
1123 .ok_or_else(|| sqlite_not_configured("generation"))?;
1124 Ok(RemoteSqliteConfig {
1125 namespace_id: config.handle.namespace().to_owned(),
1126 handle: config.handle,
1127 actor_id: config.actor_id,
1128 generation,
1129 })
1130 }
1131
1132 async fn remote_exec(&self, sql: String) -> Result<QueryResult> {
1133 let config = self.remote_config()?;
1134 let response = config
1135 .handle
1136 .remote_sqlite_exec(protocol::SqliteExecRequest {
1137 namespace_id: config.namespace_id,
1138 actor_id: config.actor_id,
1139 generation: config.generation,
1140 sql,
1141 })
1142 .await
1143 .map_err(remote_request_error)?;
1144
1145 match response {
1146 protocol::SqliteExecResponse::SqliteExecOk(ok) => {
1147 Ok(query_result_from_protocol(ok.result))
1148 }
1149 protocol::SqliteExecResponse::SqliteErrorResponse(error) => {
1150 Err(self.remote_sqlite_error_response(error))
1151 }
1152 }
1153 }
1154
1155 async fn remote_exec_with_session(
1156 &self,
1157 sql: String,
1158 expected_session: Option<u64>,
1159 ) -> Result<(QueryResult, u64)> {
1160 let config = self.remote_config()?;
1161 let (response, session) = config
1162 .handle
1163 .remote_sqlite_exec_with_session(
1164 protocol::SqliteExecRequest {
1165 namespace_id: config.namespace_id,
1166 actor_id: config.actor_id,
1167 generation: config.generation,
1168 sql,
1169 },
1170 expected_session,
1171 )
1172 .await?;
1173
1174 match response {
1175 protocol::SqliteExecResponse::SqliteExecOk(ok) => {
1176 Ok((query_result_from_protocol(ok.result), session))
1177 }
1178 protocol::SqliteExecResponse::SqliteErrorResponse(error) => {
1179 Err(self.remote_sqlite_error_response(error))
1180 }
1181 }
1182 }
1183
1184 async fn remote_execute(
1185 &self,
1186 sql: String,
1187 params: Option<Vec<BindParam>>,
1188 ) -> Result<ExecuteResult> {
1189 let config = self.remote_config()?;
1190 let response = config
1191 .handle
1192 .remote_sqlite_execute(protocol::SqliteExecuteRequest {
1193 namespace_id: config.namespace_id,
1194 actor_id: config.actor_id,
1195 generation: config.generation,
1196 sql,
1197 params: params.map(protocol_bind_params),
1198 })
1199 .await
1200 .map_err(remote_request_error)?;
1201
1202 match response {
1203 protocol::SqliteExecuteResponse::SqliteExecuteOk(ok) => {
1204 Ok(execute_result_from_protocol(ok.result))
1205 }
1206 protocol::SqliteExecuteResponse::SqliteErrorResponse(error) => {
1207 Err(self.remote_sqlite_error_response(error))
1208 }
1209 }
1210 }
1211
1212 async fn remote_execute_batch(
1213 &self,
1214 statements: Vec<SqliteBatchStatement>,
1215 ) -> Result<Vec<ExecuteResult>> {
1216 let config = self.remote_config()?;
1217 let response = config
1218 .handle
1219 .remote_sqlite_execute_batch(protocol::SqliteExecuteBatchRequest {
1220 namespace_id: config.namespace_id,
1221 actor_id: config.actor_id,
1222 generation: config.generation,
1223 statements: statements
1224 .into_iter()
1225 .map(|statement| protocol::SqliteBatchStatement {
1226 sql: statement.sql,
1227 params: statement.params.map(protocol_bind_params),
1228 })
1229 .collect(),
1230 })
1231 .await
1232 .map_err(remote_request_error)?;
1233
1234 match response {
1235 protocol::SqliteExecuteBatchResponse::SqliteExecuteBatchOk(ok) => Ok(ok
1236 .results
1237 .into_iter()
1238 .map(execute_result_from_protocol)
1239 .collect()),
1240 protocol::SqliteExecuteBatchResponse::SqliteErrorResponse(error) => {
1241 Err(self.remote_sqlite_error_response(error))
1242 }
1243 }
1244 }
1245
1246 async fn remote_execute_with_session(
1247 &self,
1248 sql: String,
1249 params: Option<Vec<BindParam>>,
1250 expected_session: Option<u64>,
1251 ) -> Result<(ExecuteResult, u64)> {
1252 let config = self.remote_config()?;
1253 let (response, session) = config
1254 .handle
1255 .remote_sqlite_execute_with_session(
1256 protocol::SqliteExecuteRequest {
1257 namespace_id: config.namespace_id,
1258 actor_id: config.actor_id,
1259 generation: config.generation,
1260 sql,
1261 params: params.map(protocol_bind_params),
1262 },
1263 expected_session,
1264 )
1265 .await?;
1266
1267 match response {
1268 protocol::SqliteExecuteResponse::SqliteExecuteOk(ok) => {
1269 Ok((execute_result_from_protocol(ok.result), session))
1270 }
1271 protocol::SqliteExecuteResponse::SqliteErrorResponse(error) => {
1272 Err(self.remote_sqlite_error_response(error))
1273 }
1274 }
1275 }
1276
1277 pub(crate) async fn query_rows_cbor(
1278 &self,
1279 sql: &str,
1280 params: Option<&[u8]>,
1281 ) -> Result<Vec<u8>> {
1282 let bind_params = bind_params_from_cbor(sql, params)?;
1283 let result = self.query(sql.to_owned(), bind_params).await?;
1284 encode_json_as_cbor(&query_result_to_json_rows(&result))
1285 }
1286
1287 pub(crate) async fn exec_rows_cbor(&self, sql: &str) -> Result<Vec<u8>> {
1288 let result = self.exec(sql.to_owned()).await?;
1289 encode_json_as_cbor(&query_result_to_json_rows(&result))
1290 }
1291
1292 pub(crate) async fn run_cbor(&self, sql: &str, params: Option<&[u8]>) -> Result<ExecResult> {
1293 let bind_params = bind_params_from_cbor(sql, params)?;
1294 self.run(sql.to_owned(), bind_params).await
1295 }
1296
1297 pub(crate) async fn execute_rows_cbor(
1298 &self,
1299 sql: &str,
1300 params: Option<&[u8]>,
1301 ) -> Result<Vec<u8>> {
1302 let bind_params = bind_params_from_cbor(sql, params)?;
1303 let result = self.execute(sql.to_owned(), bind_params).await?;
1304 encode_json_as_cbor(&query_result_to_json_rows(&QueryResult {
1305 columns: result.columns,
1306 rows: result.rows,
1307 }))
1308 }
1309
1310 fn handle(&self) -> Result<EnvoyHandle> {
1311 self.handle
1312 .clone()
1313 .ok_or_else(|| sqlite_not_configured("handle"))
1314 }
1315
1316 fn remote_sqlite_error_response(&self, error: protocol::SqliteErrorResponse) -> anyhow::Error {
1317 if is_head_fence_mismatch_response(&error) {
1318 if let Ok(config) = self.runtime_config() {
1319 report_sqlite_worker_fatal(
1320 &self.worker_fatal_reported,
1321 config,
1322 format!("remote sqlite fatal storage error: {}", error.message),
1323 );
1324 }
1325 return SqliteRuntimeError::Closed.build();
1326 }
1327
1328 remote_sqlite_error_response(error.message)
1329 }
1330}
1331
1332fn report_sqlite_worker_fatal(reported: &AtomicBool, config: SqliteRuntimeConfig, message: String) {
1333 if reported.swap(true, Ordering::AcqRel) {
1334 return;
1335 }
1336 config.handle.sleep_actor(
1343 config.actor_id,
1344 config
1345 .generation
1346 .and_then(|generation| generation.try_into().ok()),
1347 Some(message),
1348 );
1349}
1350
1351struct RemoteSqliteConfig {
1352 handle: EnvoyHandle,
1353 namespace_id: String,
1354 actor_id: String,
1355 generation: u64,
1356}
1357
1358fn select_sqlite_backend(remote_sqlite: bool) -> Result<SqliteBackend> {
1359 if remote_sqlite {
1360 return Ok(SqliteBackend::RemoteEnvoy);
1361 }
1362
1363 #[cfg(feature = "sqlite-local")]
1364 {
1365 Ok(SqliteBackend::LocalNative)
1366 }
1367
1368 #[cfg(not(feature = "sqlite-local"))]
1369 {
1370 Err(SqliteRuntimeError::Unavailable.build())
1371 }
1372}
1373
1374fn bind_param_count(params: &Option<Vec<BindParam>>) -> usize {
1375 params.as_ref().map_or(0, Vec::len)
1376}
1377
1378#[cfg(feature = "sqlite-local")]
1379fn duration_ns(duration: std::time::Duration) -> u64 {
1380 duration.as_nanos().try_into().unwrap_or(u64::MAX)
1381}
1382
1383#[cfg(feature = "sqlite-local")]
1384fn is_fatal_worker_error(error: &anyhow::Error) -> bool {
1385 error.downcast_ref::<SqliteWorkerFatalError>().is_some()
1386 || error.downcast_ref::<SqliteWorkerDeadError>().is_some()
1387 || error
1388 .downcast_ref::<SqliteWorkerCloseTimeoutError>()
1389 .is_some()
1390}
1391
1392#[cfg(feature = "sqlite-local")]
1393fn sqlite_worker_fatal_message(error: &anyhow::Error) -> String {
1394 if let Some(error) = error.downcast_ref::<SqliteWorkerFatalError>() {
1395 return format!("sqlite fatal storage error: {}", error.message());
1396 }
1397
1398 format!("sqlite worker failed: {error}")
1399}
1400
1401#[cfg(feature = "sqlite-local")]
1402fn map_local_worker_error(error: anyhow::Error) -> anyhow::Error {
1403 if error
1404 .downcast_ref::<SqliteWorkerOverloadedError>()
1405 .is_some()
1406 {
1407 return ActorLifecycle::Overloaded {
1408 channel: "sqlite_worker".to_string(),
1409 capacity: SQLITE_WORKER_QUEUE_CAPACITY,
1410 operation: "execute sqlite command".to_string(),
1411 }
1412 .build();
1413 }
1414
1415 if error.downcast_ref::<SqliteWorkerClosingError>().is_some()
1416 || error.downcast_ref::<SqliteWorkerDeadError>().is_some()
1417 || error.downcast_ref::<SqliteWorkerFatalError>().is_some()
1418 {
1419 return SqliteRuntimeError::Closed.build();
1420 }
1421
1422 error
1423}
1424
1425fn protocol_bind_params(params: Vec<BindParam>) -> Vec<protocol::SqliteBindParam> {
1426 params.into_iter().map(protocol_bind_param).collect()
1427}
1428
1429fn protocol_bind_param(param: BindParam) -> protocol::SqliteBindParam {
1430 match param {
1431 BindParam::Null => protocol::SqliteBindParam::SqliteValueNull,
1432 BindParam::Integer(value) => {
1433 protocol::SqliteBindParam::SqliteValueInteger(protocol::SqliteValueInteger { value })
1434 }
1435 BindParam::Float(value) => {
1436 protocol::SqliteBindParam::SqliteValueFloat(protocol::SqliteValueFloat {
1437 value: value.to_bits().to_be_bytes(),
1438 })
1439 }
1440 BindParam::Text(value) => {
1441 protocol::SqliteBindParam::SqliteValueText(protocol::SqliteValueText { value })
1442 }
1443 BindParam::Blob(value) => {
1444 protocol::SqliteBindParam::SqliteValueBlob(protocol::SqliteValueBlob { value })
1445 }
1446 }
1447}
1448
1449fn query_result_from_protocol(result: protocol::SqliteQueryResult) -> QueryResult {
1450 QueryResult {
1451 columns: result.columns,
1452 rows: result
1453 .rows
1454 .into_iter()
1455 .map(|row| row.into_iter().map(column_value_from_protocol).collect())
1456 .collect(),
1457 }
1458}
1459
1460fn execute_result_from_protocol(result: protocol::SqliteExecuteResult) -> ExecuteResult {
1461 ExecuteResult {
1462 columns: result.columns,
1463 rows: result
1464 .rows
1465 .into_iter()
1466 .map(|row| row.into_iter().map(column_value_from_protocol).collect())
1467 .collect(),
1468 changes: result.changes,
1469 last_insert_row_id: result.last_insert_row_id,
1470 }
1471}
1472
1473fn column_value_from_protocol(value: protocol::SqliteColumnValue) -> ColumnValue {
1474 match value {
1475 protocol::SqliteColumnValue::SqliteValueNull => ColumnValue::Null,
1476 protocol::SqliteColumnValue::SqliteValueInteger(value) => ColumnValue::Integer(value.value),
1477 protocol::SqliteColumnValue::SqliteValueFloat(value) => {
1478 ColumnValue::Float(f64::from_bits(u64::from_be_bytes(value.value)))
1479 }
1480 protocol::SqliteColumnValue::SqliteValueText(value) => ColumnValue::Text(value.value),
1481 protocol::SqliteColumnValue::SqliteValueBlob(value) => ColumnValue::Blob(value.value),
1482 }
1483}
1484
1485fn remote_request_error(error: anyhow::Error) -> anyhow::Error {
1486 if let Some(indeterminate) = error.downcast_ref::<RemoteSqliteIndeterminateResultError>() {
1487 return SqliteRuntimeError::RemoteIndeterminateResult {
1488 operation: indeterminate.operation.to_owned(),
1489 }
1490 .build();
1491 }
1492
1493 if let Some(compatibility) =
1494 error.downcast_ref::<protocol::versioned::ProtocolCompatibilityError>()
1495 {
1496 if compatibility.feature
1497 == protocol::versioned::ProtocolCompatibilityFeature::RemoteSqliteExecution
1498 {
1499 return SqliteRuntimeError::RemoteUnavailable {
1500 reason: compatibility.to_string(),
1501 }
1502 .build();
1503 }
1504 }
1505
1506 error
1507}
1508
1509fn remote_sqlite_error_response(message: String) -> anyhow::Error {
1510 if message.contains("unavailable") || message.contains("unsupported") {
1511 return SqliteRuntimeError::RemoteUnavailable { reason: message }.build();
1512 }
1513
1514 SqliteRuntimeError::RemoteExecutionFailed { message }.build()
1515}
1516
1517fn is_head_fence_mismatch_response(error: &protocol::SqliteErrorResponse) -> bool {
1518 is_head_fence_mismatch(&error.group, &error.code)
1519}
1520impl std::fmt::Debug for SqliteDb {
1521 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1522 f.debug_struct("SqliteDb")
1523 .field("configured", &self.handle.is_some())
1524 .field("actor_id", &self.actor_id)
1525 .finish()
1526 }
1527}
1528
1529fn bind_params_from_cbor(sql: &str, params: Option<&[u8]>) -> Result<Option<Vec<BindParam>>> {
1530 let Some(params) = params else {
1531 return Ok(None);
1532 };
1533 if params.is_empty() {
1534 return Ok(None);
1535 }
1536
1537 let value = ciborium::from_reader::<JsonValue, _>(Cursor::new(params))
1538 .context("decode sqlite bind params as cbor json")?;
1539 match value {
1540 JsonValue::Array(values) => values
1541 .iter()
1542 .map(json_to_bind_param)
1543 .collect::<Result<Vec<_>>>()
1544 .map(Some),
1545 JsonValue::Object(properties) => {
1546 let ordered_names = extract_named_sqlite_parameters(sql);
1547 if ordered_names.is_empty() {
1548 return properties
1549 .values()
1550 .map(json_to_bind_param)
1551 .collect::<Result<Vec<_>>>()
1552 .map(Some);
1553 }
1554
1555 ordered_names
1556 .iter()
1557 .map(|name| {
1558 get_named_sqlite_binding(&properties, name)
1559 .ok_or_else(|| {
1560 SqliteRuntimeError::InvalidBindParameter {
1561 name: name.clone(),
1562 reason: "missing parameter".to_owned(),
1563 }
1564 .build()
1565 })
1566 .and_then(json_to_bind_param)
1567 })
1568 .collect::<Result<Vec<_>>>()
1569 .map(Some)
1570 }
1571 JsonValue::Null => Ok(None),
1572 other => Err(SqliteRuntimeError::InvalidBindParameter {
1573 name: "params".to_owned(),
1574 reason: format!("expected array or object, got {}", json_type_name(&other)),
1575 }
1576 .build()),
1577 }
1578}
1579
1580fn json_to_bind_param(value: &JsonValue) -> Result<BindParam> {
1581 match value {
1582 JsonValue::Null => Ok(BindParam::Null),
1583 JsonValue::Bool(value) => Ok(BindParam::Integer(i64::from(*value))),
1584 JsonValue::Number(value) => {
1585 if let Some(value) = value.as_i64() {
1586 return Ok(BindParam::Integer(value));
1587 }
1588 if let Some(value) = value.as_u64() {
1589 let value = i64::try_from(value)
1590 .context("sqlite integer bind parameter exceeds i64 range")?;
1591 return Ok(BindParam::Integer(value));
1592 }
1593 value.as_f64().map(BindParam::Float).ok_or_else(|| {
1594 SqliteRuntimeError::InvalidBindParameter {
1595 name: "number".to_owned(),
1596 reason: "unsupported numeric value".to_owned(),
1597 }
1598 .build()
1599 })
1600 }
1601 JsonValue::String(value) => Ok(BindParam::Text(value.clone())),
1602 other => Err(SqliteRuntimeError::InvalidBindParameter {
1603 name: "value".to_owned(),
1604 reason: format!("unsupported type {}", json_type_name(other)),
1605 }
1606 .build()),
1607 }
1608}
1609
1610fn sqlite_not_configured(component: &str) -> anyhow::Error {
1611 SqliteRuntimeError::NotConfigured {
1612 component: component.to_owned(),
1613 }
1614 .build()
1615}
1616
1617fn extract_named_sqlite_parameters(sql: &str) -> Vec<String> {
1618 let mut ordered_names = Vec::new();
1619 let mut seen = HashSet::new();
1620 let bytes = sql.as_bytes();
1621 let mut idx = 0;
1622
1623 while idx < bytes.len() {
1624 let byte = bytes[idx];
1625 if !matches!(byte, b':' | b'@' | b'$') {
1626 idx += 1;
1627 continue;
1628 }
1629
1630 let start = idx;
1631 idx += 1;
1632 if idx >= bytes.len() || !is_sqlite_param_start(bytes[idx]) {
1633 continue;
1634 }
1635 idx += 1;
1636 while idx < bytes.len() && is_sqlite_param_continue(bytes[idx]) {
1637 idx += 1;
1638 }
1639
1640 let name = &sql[start..idx];
1641 if seen.insert(name.to_owned()) {
1642 ordered_names.push(name.to_owned());
1643 }
1644 }
1645
1646 ordered_names
1647}
1648
1649fn is_sqlite_param_start(byte: u8) -> bool {
1650 byte == b'_' || byte.is_ascii_alphabetic()
1651}
1652
1653fn is_sqlite_param_continue(byte: u8) -> bool {
1654 byte == b'_' || byte.is_ascii_alphanumeric()
1655}
1656
1657fn get_named_sqlite_binding<'a>(
1658 bindings: &'a JsonMap<String, JsonValue>,
1659 name: &str,
1660) -> Option<&'a JsonValue> {
1661 if let Some(value) = bindings.get(name) {
1662 return Some(value);
1663 }
1664
1665 let bare_name = name.get(1..)?;
1666 if let Some(value) = bindings.get(bare_name) {
1667 return Some(value);
1668 }
1669
1670 for prefix in [":", "@", "$"] {
1671 let candidate = format!("{prefix}{bare_name}");
1672 if let Some(value) = bindings.get(&candidate) {
1673 return Some(value);
1674 }
1675 }
1676
1677 None
1678}
1679
1680fn query_result_to_json_rows(result: &QueryResult) -> JsonValue {
1681 JsonValue::Array(
1682 result
1683 .rows
1684 .iter()
1685 .map(|row| {
1686 let mut object = JsonMap::new();
1687 for (index, column) in result.columns.iter().enumerate() {
1688 let value = row
1689 .get(index)
1690 .map(column_value_to_json)
1691 .unwrap_or(JsonValue::Null);
1692 object.insert(column.clone(), value);
1693 }
1694 JsonValue::Object(object)
1695 })
1696 .collect(),
1697 )
1698}
1699
1700fn column_value_to_json(value: &ColumnValue) -> JsonValue {
1701 match value {
1702 ColumnValue::Null => JsonValue::Null,
1703 ColumnValue::Integer(value) => JsonValue::from(*value),
1704 ColumnValue::Float(value) => JsonValue::from(*value),
1705 ColumnValue::Text(value) => JsonValue::String(value.clone()),
1706 ColumnValue::Blob(value) => {
1707 JsonValue::Array(value.iter().map(|byte| JsonValue::from(*byte)).collect())
1708 }
1709 }
1710}
1711
1712fn encode_json_as_cbor(value: &impl Serialize) -> Result<Vec<u8>> {
1713 let mut encoded = Vec::new();
1714 ciborium::into_writer(value, &mut encoded).context("encode sqlite rows as cbor")?;
1715 Ok(encoded)
1716}
1717
1718fn json_type_name(value: &JsonValue) -> &'static str {
1719 match value {
1720 JsonValue::Null => "null",
1721 JsonValue::Bool(_) => "boolean",
1722 JsonValue::Number(_) => "number",
1723 JsonValue::String(_) => "string",
1724 JsonValue::Array(_) => "array",
1725 JsonValue::Object(_) => "object",
1726 }
1727}
1728
1729#[cfg(test)]
1730#[path = "../../../tests/sqlite.rs"]
1731mod tests;