1use std::collections::HashSet;
2use std::io::Cursor;
3use std::sync::{
4 Arc,
5 atomic::{AtomicBool, Ordering},
6};
7
8use anyhow::{Context, Result};
9use depot_client_types::is_head_fence_mismatch;
10pub use depot_client_types::{BindParam, ColumnValue, ExecResult, ExecuteResult, QueryResult};
11#[cfg(feature = "sqlite-local")]
12use parking_lot::Mutex;
13use rivet_envoy_client::protocol;
14use rivet_envoy_client::{
15 handle::EnvoyHandle,
16 utils::{RemoteSqliteConnectionSessionLostError, RemoteSqliteIndeterminateResultError},
17};
18use rivet_error::{ActorSpecifier, RivetError};
19use serde::Serialize;
20use serde_json::{Map as JsonMap, Value as JsonValue};
21#[cfg(feature = "sqlite-local")]
22use tokio::sync::Mutex as AsyncMutex;
23#[cfg(feature = "sqlite-local")]
24use tokio::task::JoinHandle;
25
26#[cfg(feature = "sqlite-local")]
27mod envoy_sqlite_transport;
28mod tx;
29
30pub use tx::{
31 DEFAULT_TRANSACTION_TIMEOUT, SqliteTransaction, TRANSACTION_COORDINATOR_QUEUE_CAPACITY,
32 TransactionConnectionLostError, TransactionCoordinatorClosedError, TransactionExpiredError,
33 TransactionInvalidArgumentError, TransactionQueueFullError, TransactionTerminalError,
34 TransactionUnknownError,
35};
36#[cfg(test)]
37use tx::{
38 TRANSACTION_TERMINAL_CAPACITY, TransactionCoordinatorState, TransactionTerminalState,
39 insert_terminal_state,
40};
41use tx::{TransactionCoordinator, run_detached_transaction_task};
42
43#[cfg(feature = "sqlite-local")]
44use crate::error::ActorLifecycle;
45use crate::error::SqliteRuntimeError;
46#[cfg(all(not(target_arch = "wasm32"), feature = "sqlite-local"))]
47use crate::runtime::RuntimeSpawner;
48
49#[cfg(feature = "sqlite-local")]
50use depot_client::{
51 database::{NativeDatabaseHandle, open_database_from_transport},
52 vfs::{SqliteVfsMetrics, SqliteVfsMetricsSnapshot},
53 worker::{
54 SQLITE_WORKER_QUEUE_CAPACITY, SqliteWorkerCloseTimeoutError, SqliteWorkerClosingError,
55 SqliteWorkerDeadError, SqliteWorkerFatalError, SqliteWorkerOverloadedError,
56 },
57};
58#[cfg(feature = "sqlite-local")]
59use envoy_sqlite_transport::EnvoySqliteTransport;
60
61#[cfg(not(feature = "sqlite-local"))]
62#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
63pub struct SqliteVfsMetricsSnapshot {
64 pub request_build_ns: u64,
65 pub serialize_ns: u64,
66 pub transport_ns: u64,
67 pub state_update_ns: u64,
68 pub total_ns: u64,
69 pub commit_count: u64,
70}
71
72#[derive(Clone)]
73pub struct SqliteRuntimeConfig {
74 pub handle: EnvoyHandle,
75 pub actor_id: String,
76 pub generation: Option<u64>,
77}
78
79#[derive(Clone, Debug)]
80pub struct SqliteBatchStatement {
81 pub sql: String,
82 pub params: Option<Vec<BindParam>>,
83}
84
85#[derive(Clone, Copy, Debug, Eq, PartialEq)]
86pub enum SqliteBackend {
87 LocalNative,
88 RemoteEnvoy,
89}
90
91impl SqliteDb {
92 async fn exec_backend(&self, sql: String) -> Result<QueryResult> {
93 match self.backend {
94 SqliteBackend::LocalNative => self.local_exec(sql).await,
95 SqliteBackend::RemoteEnvoy => self.remote_exec(sql).await,
96 }
97 }
98
99 async fn exec_backend_in_session(
100 &self,
101 sql: String,
102 expected_session: Option<u64>,
103 ) -> Result<(QueryResult, Option<u64>)> {
104 match self.backend {
105 SqliteBackend::LocalNative => self.local_exec(sql).await.map(|result| (result, None)),
106 SqliteBackend::RemoteEnvoy => self
107 .remote_exec_with_session(sql, expected_session)
108 .await
109 .map(|(result, session)| (result, Some(session))),
110 }
111 }
112
113 async fn query_backend(
114 &self,
115 sql: String,
116 params: Option<Vec<BindParam>>,
117 ) -> Result<QueryResult> {
118 match self.backend {
119 SqliteBackend::LocalNative => self.local_query(sql, params).await,
120 SqliteBackend::RemoteEnvoy => self
121 .remote_execute(sql, params)
122 .await
123 .map(ExecuteResult::into_query_result),
124 }
125 }
126
127 async fn run_backend(&self, sql: String, params: Option<Vec<BindParam>>) -> Result<ExecResult> {
128 match self.backend {
129 SqliteBackend::LocalNative => self.local_run(sql, params).await,
130 SqliteBackend::RemoteEnvoy => self
131 .remote_execute(sql, params)
132 .await
133 .map(ExecuteResult::into_exec_result),
134 }
135 }
136
137 async fn execute_backend(
138 &self,
139 sql: String,
140 params: Option<Vec<BindParam>>,
141 ) -> Result<ExecuteResult> {
142 match self.backend {
143 SqliteBackend::LocalNative => self.local_execute(sql, params).await,
144 SqliteBackend::RemoteEnvoy => self.remote_execute(sql, params).await,
145 }
146 }
147
148 async fn execute_backend_in_session(
149 &self,
150 sql: String,
151 params: Option<Vec<BindParam>>,
152 expected_session: Option<u64>,
153 ) -> Result<(ExecuteResult, Option<u64>)> {
154 match self.backend {
155 SqliteBackend::LocalNative => self
156 .local_execute(sql, params)
157 .await
158 .map(|result| (result, None)),
159 SqliteBackend::RemoteEnvoy => self
160 .remote_execute_with_session(sql, params, expected_session)
161 .await
162 .map(|(result, session)| (result, Some(session))),
163 }
164 }
165}
166
167#[derive(Clone)]
168pub struct SqliteDb {
169 handle: Option<EnvoyHandle>,
170 actor_id: Option<String>,
171 actor_key: Option<String>,
172 generation: Option<u64>,
173 backend: SqliteBackend,
174 enabled: bool,
178 #[cfg(feature = "sqlite-local")]
179 db: Arc<Mutex<Option<NativeDatabaseHandle>>>,
182 #[cfg(feature = "sqlite-local")]
183 open_lock: Arc<AsyncMutex<()>>,
184 #[cfg(feature = "sqlite-local")]
185 worker_failure_task: Arc<Mutex<Option<JoinHandle<()>>>>,
186 worker_fatal_reported: Arc<AtomicBool>,
187 transaction_coordinator: Arc<TransactionCoordinator>,
188 #[cfg(feature = "sqlite-local")]
189 vfs_metrics: Option<Arc<dyn SqliteVfsMetrics>>,
190}
191
192impl SqliteDb {
193 pub fn new(handle: EnvoyHandle, actor_id: impl Into<String>, enabled: bool) -> Result<Self> {
194 Self::new_with_remote_sqlite(handle, actor_id, None, None, enabled, false)
195 }
196
197 pub fn new_with_remote_sqlite(
198 handle: EnvoyHandle,
199 actor_id: impl Into<String>,
200 actor_key: Option<String>,
201 generation: Option<u64>,
202 enabled: bool,
203 remote_sqlite: bool,
204 ) -> Result<Self> {
205 Ok(Self {
206 handle: Some(handle),
207 actor_id: Some(actor_id.into()),
208 actor_key,
209 generation,
210 backend: select_sqlite_backend(remote_sqlite)?,
211 enabled,
212 #[cfg(feature = "sqlite-local")]
213 db: Default::default(),
214 #[cfg(feature = "sqlite-local")]
215 open_lock: Default::default(),
216 #[cfg(feature = "sqlite-local")]
217 worker_failure_task: Default::default(),
218 worker_fatal_reported: Default::default(),
219 transaction_coordinator: Default::default(),
220 #[cfg(feature = "sqlite-local")]
221 vfs_metrics: None,
222 })
223 }
224
225 #[cfg(feature = "sqlite-local")]
226 pub(crate) fn set_vfs_metrics(&mut self, metrics: Arc<dyn SqliteVfsMetrics>) {
227 self.vfs_metrics = Some(metrics);
228 }
229
230 pub fn is_enabled(&self) -> bool {
231 self.enabled
232 }
233
234 pub fn backend(&self) -> SqliteBackend {
235 self.backend
236 }
237
238 pub async fn get_pages(
239 &self,
240 request: protocol::SqliteGetPagesRequest,
241 ) -> Result<protocol::SqliteGetPagesResponse> {
242 self.handle()?.sqlite_get_pages(request).await
243 }
244
245 pub async fn commit(
246 &self,
247 request: protocol::SqliteCommitRequest,
248 ) -> Result<protocol::SqliteCommitResponse> {
249 self.handle()?.sqlite_commit(request).await
250 }
251
252 pub async fn open(&self) -> Result<()> {
253 match self.backend {
254 SqliteBackend::LocalNative => {
255 #[cfg(feature = "sqlite-local")]
256 {
257 let _open_guard = self.open_lock.lock().await;
258 if self.db.lock().is_some() {
259 return Ok(());
260 }
261
262 let config = self.runtime_config()?;
263 let vfs_metrics = self.vfs_metrics.clone();
264 let rt_handle = tokio::runtime::Handle::try_current()
265 .context("open sqlite database requires a tokio runtime")?;
266 self.worker_fatal_reported.store(false, Ordering::Release);
267
268 let native_db = self.map_local_worker_result(
269 open_database_from_transport(
270 Arc::new(EnvoySqliteTransport::new(config.handle.clone())),
271 config.actor_id.clone(),
272 config
273 .generation
274 .ok_or_else(|| sqlite_not_configured("generation"))?,
275 rt_handle,
276 vfs_metrics,
277 )
278 .await,
279 )?;
280 self.start_worker_failure_monitor(native_db.clone(), config);
281 *self.db.lock() = Some(native_db);
282 if let Some(metrics) = self.vfs_metrics.as_ref() {
283 metrics.set_worker_active(true);
284 }
285 Ok(())
286 }
287
288 #[cfg(not(feature = "sqlite-local"))]
289 {
290 Err(SqliteRuntimeError::Unavailable.build())
291 }
292 }
293 SqliteBackend::RemoteEnvoy => {
294 self.remote_config()?;
295 Ok(())
296 }
297 }
298 }
299
300 #[cfg(feature = "sqlite-local")]
301 async fn local_exec(&self, sql: String) -> Result<QueryResult> {
302 self.open().await?;
303 self.map_local_worker_result(self.native_db_handle()?.exec(sql).await)
304 }
305
306 #[cfg(not(feature = "sqlite-local"))]
307 async fn local_exec(&self, _sql: String) -> Result<QueryResult> {
308 Err(SqliteRuntimeError::Unavailable.build())
309 }
310
311 #[cfg(feature = "sqlite-local")]
312 async fn local_query(
313 &self,
314 sql: String,
315 params: Option<Vec<BindParam>>,
316 ) -> Result<QueryResult> {
317 self.open().await?;
318 self.map_local_worker_result(self.native_db_handle()?.query(sql, params).await)
319 }
320
321 #[cfg(not(feature = "sqlite-local"))]
322 async fn local_query(
323 &self,
324 _sql: String,
325 _params: Option<Vec<BindParam>>,
326 ) -> Result<QueryResult> {
327 Err(SqliteRuntimeError::Unavailable.build())
328 }
329
330 #[cfg(feature = "sqlite-local")]
331 async fn local_run(&self, sql: String, params: Option<Vec<BindParam>>) -> Result<ExecResult> {
332 self.open().await?;
333 self.map_local_worker_result(self.native_db_handle()?.run(sql, params).await)
334 }
335
336 #[cfg(not(feature = "sqlite-local"))]
337 async fn local_run(&self, _sql: String, _params: Option<Vec<BindParam>>) -> Result<ExecResult> {
338 Err(SqliteRuntimeError::Unavailable.build())
339 }
340
341 #[cfg(feature = "sqlite-local")]
342 async fn local_execute(
343 &self,
344 sql: String,
345 params: Option<Vec<BindParam>>,
346 ) -> Result<ExecuteResult> {
347 self.open().await?;
348 self.map_local_worker_result(self.native_db_handle()?.execute(sql, params).await)
349 }
350
351 #[cfg(not(feature = "sqlite-local"))]
352 async fn local_execute(
353 &self,
354 _sql: String,
355 _params: Option<Vec<BindParam>>,
356 ) -> Result<ExecuteResult> {
357 Err(SqliteRuntimeError::Unavailable.build())
358 }
359
360 pub async fn exec(&self, sql: impl Into<String>) -> Result<QueryResult> {
361 let sql = sql.into();
362 let sql_for_log = sql.clone();
363 let result = match self.begin_regular_operation().await {
364 Ok(_guard) => self.exec_backend(sql).await,
365 Err(error) => Err(error),
366 };
367 match result {
368 Ok(result) => Ok(result),
369 Err(error) => {
370 let error = self.attach_actor(error);
371 self.log_operation_error("exec", &sql_for_log, 0, &error);
372 Err(error)
373 }
374 }
375 }
376
377 pub async fn query(
378 &self,
379 sql: impl Into<String>,
380 params: Option<Vec<BindParam>>,
381 ) -> Result<QueryResult> {
382 let sql = sql.into();
383 let sql_for_log = sql.clone();
384 let binding_count = bind_param_count(¶ms);
385 let result = match self.begin_regular_operation().await {
386 Ok(_guard) => self.query_backend(sql, params).await,
387 Err(error) => Err(error),
388 };
389 match result {
390 Ok(result) => Ok(result),
391 Err(error) => {
392 let error = self.attach_actor(error);
393 self.log_operation_error("query", &sql_for_log, binding_count, &error);
394 Err(error)
395 }
396 }
397 }
398
399 pub async fn run(
400 &self,
401 sql: impl Into<String>,
402 params: Option<Vec<BindParam>>,
403 ) -> Result<ExecResult> {
404 let sql = sql.into();
405 let sql_for_log = sql.clone();
406 let binding_count = bind_param_count(¶ms);
407 let result = match self.begin_regular_operation().await {
408 Ok(_guard) => self.run_backend(sql, params).await,
409 Err(error) => Err(error),
410 };
411 match result {
412 Ok(result) => Ok(result),
413 Err(error) => {
414 let error = self.attach_actor(error);
415 self.log_operation_error("run", &sql_for_log, binding_count, &error);
416 Err(error)
417 }
418 }
419 }
420
421 pub async fn execute(
422 &self,
423 sql: impl Into<String>,
424 params: Option<Vec<BindParam>>,
425 ) -> Result<ExecuteResult> {
426 let sql = sql.into();
427 let sql_for_log = sql.clone();
428 let binding_count = bind_param_count(¶ms);
429 let result = match self.begin_regular_operation().await {
430 Ok(_guard) => self.execute_backend(sql, params).await,
431 Err(error) => Err(error),
432 };
433 match result {
434 Ok(result) => Ok(result),
435 Err(error) => {
436 let error = self.attach_actor(error);
437 self.log_operation_error("execute", &sql_for_log, binding_count, &error);
438 Err(error)
439 }
440 }
441 }
442
443 pub async fn execute_batch(
444 &self,
445 statements: Vec<SqliteBatchStatement>,
446 ) -> Result<Vec<ExecuteResult>> {
447 let statement_count = statements.len();
448 let binding_count = statements
449 .iter()
450 .map(|statement| bind_param_count(&statement.params))
451 .sum();
452 let result = if self.backend == SqliteBackend::RemoteEnvoy {
453 match self.begin_regular_operation().await {
454 Ok(_guard) => self.remote_execute_batch(statements).await,
455 Err(error) => Err(error),
456 }
457 } else {
458 async {
459 let transaction = self.begin_transaction(None).await?;
460 let mut results = Vec::with_capacity(statements.len());
461 for statement in statements {
462 match transaction.execute(statement.sql, statement.params).await {
463 Ok(result) => results.push(result),
464 Err(error) => {
465 return match transaction.rollback().await {
466 Ok(()) => Err(error.context("execute sqlite batch statement")),
467 Err(rollback_error) => {
468 Err(error.context("execute sqlite batch statement").context(
469 rollback_error.context("rollback sqlite batch transaction"),
470 ))
471 }
472 };
473 }
474 }
475 }
476 transaction
477 .commit()
478 .await
479 .context("commit sqlite batch transaction")?;
480 Ok(results)
481 }
482 .await
483 };
484
485 match result {
486 Ok(results) => Ok(results),
487 Err(error) => {
488 let error = self.attach_actor(error);
489 self.log_operation_error_with_count(
490 "execute_batch",
491 "<batch>",
492 binding_count,
493 statement_count,
494 &error,
495 );
496 Err(error)
497 }
498 }
499 }
500
501 pub async fn close(&self) -> Result<()> {
502 let db = self.clone();
503 run_detached_transaction_task(
504 async move {
505 let _gate = db.shutdown_transaction_coordinator().await;
506 db.close_backend().await
507 },
508 "sqlite close task failed",
509 )
510 .await
511 }
512
513 async fn close_backend(&self) -> Result<()> {
514 match self.backend {
515 SqliteBackend::LocalNative => {
516 #[cfg(feature = "sqlite-local")]
517 {
518 let native_db = self.db.lock().take();
519 if let Some(native_db) = native_db {
520 let result = self.map_local_worker_result(native_db.close().await);
521 self.abort_worker_failure_monitor();
522 if let Some(metrics) = self.vfs_metrics.as_ref() {
523 metrics.set_worker_active(false);
524 }
525 result?;
526 }
527 }
528 Ok(())
529 }
530 SqliteBackend::RemoteEnvoy => Ok(()),
531 }
532 }
533
534 pub(crate) async fn cleanup_for_shutdown(&self, reusable: bool) -> Result<()> {
535 if reusable && self.backend == SqliteBackend::RemoteEnvoy {
538 return Ok(());
539 }
540 self.close().await
541 }
542
543 #[cfg(test)]
544 pub(crate) fn fresh_remote_for_test(&self) -> Self {
545 Self::new_with_remote_sqlite(
546 self.handle
547 .clone()
548 .expect("remote sqlite test database should have an envoy handle"),
549 self.actor_id
550 .clone()
551 .expect("remote sqlite test database should have an actor id"),
552 self.actor_key.clone(),
553 self.generation,
554 self.enabled,
555 true,
556 )
557 .expect("remote sqlite test database should be configured")
558 }
559
560 pub fn take_last_kv_error(&self) -> Option<String> {
561 if self.backend != SqliteBackend::LocalNative {
562 return None;
563 }
564
565 #[cfg(feature = "sqlite-local")]
566 {
567 return self
568 .db
569 .lock()
570 .as_ref()
571 .and_then(NativeDatabaseHandle::take_last_kv_error);
572 }
573
574 #[cfg(not(feature = "sqlite-local"))]
575 None
576 }
577
578 #[cfg(feature = "sqlite-local")]
579 fn native_db_handle(&self) -> Result<NativeDatabaseHandle> {
580 self.db
581 .lock()
582 .as_ref()
583 .cloned()
584 .ok_or_else(|| SqliteRuntimeError::Closed.build())
585 }
586
587 #[cfg(feature = "sqlite-local")]
588 fn map_local_worker_result<T>(&self, result: Result<T>) -> Result<T> {
589 match result {
590 Ok(value) => Ok(value),
591 Err(error) => {
592 if is_fatal_worker_error(&error) {
593 self.report_worker_fatal(&error);
594 }
595 Err(map_local_worker_error(error))
596 }
597 }
598 }
599
600 #[cfg(feature = "sqlite-local")]
601 fn report_worker_fatal(&self, error: &anyhow::Error) {
602 let Ok(config) = self.runtime_config() else {
603 return;
604 };
605 report_sqlite_worker_fatal(
606 &self.worker_fatal_reported,
607 config,
608 sqlite_worker_fatal_message(error),
609 );
610 }
611
612 #[cfg(feature = "sqlite-local")]
613 fn start_worker_failure_monitor(
614 &self,
615 native_db: NativeDatabaseHandle,
616 config: SqliteRuntimeConfig,
617 ) {
618 self.abort_worker_failure_monitor();
619 let reported = Arc::clone(&self.worker_fatal_reported);
620 let task = RuntimeSpawner::spawn(async move {
621 if native_db.wait_for_worker_failure().await {
622 report_sqlite_worker_fatal(
623 &reported,
624 config,
625 "sqlite worker thread stopped unexpectedly".to_string(),
626 );
627 }
628 });
629 *self.worker_failure_task.lock() = Some(task);
630 }
631
632 #[cfg(feature = "sqlite-local")]
633 fn abort_worker_failure_monitor(&self) {
634 if let Some(task) = self.worker_failure_task.lock().take() {
635 task.abort();
636 }
637 }
638
639 pub fn metrics(&self) -> Option<SqliteVfsMetricsSnapshot> {
640 #[cfg(feature = "sqlite-local")]
641 {
642 self.db
643 .lock()
644 .as_ref()
645 .map(NativeDatabaseHandle::sqlite_vfs_metrics)
646 }
647
648 #[cfg(not(feature = "sqlite-local"))]
649 {
650 None
651 }
652 }
653
654 pub fn runtime_config(&self) -> Result<SqliteRuntimeConfig> {
655 Ok(SqliteRuntimeConfig {
656 handle: self.handle()?,
657 actor_id: self
658 .actor_id
659 .clone()
660 .ok_or_else(|| sqlite_not_configured("actor id"))?,
661 generation: self.generation,
662 })
663 }
664
665 fn log_operation_error(
666 &self,
667 operation: &'static str,
668 sql: &str,
669 binding_count: usize,
670 error: &anyhow::Error,
671 ) {
672 self.log_operation_error_with_count(operation, sql, binding_count, 1, error);
673 }
674
675 fn log_operation_error_with_count(
676 &self,
677 operation: &'static str,
678 sql: &str,
679 binding_count: usize,
680 statement_count: usize,
681 error: &anyhow::Error,
682 ) {
683 let structured = RivetError::extract(error);
684 let error_chain = error.chain().map(ToString::to_string).collect::<Vec<_>>();
685 tracing::error!(
686 actor_id = self.actor_id.as_deref().unwrap_or("<unknown>"),
687 generation = ?self.generation,
688 backend = ?self.backend,
689 operation,
690 sql,
691 binding_count,
692 statement_count,
693 group = structured.group(),
694 code = structured.code(),
695 error_message = %structured.message(),
696 metadata = ?structured.metadata(),
697 error_chain = ?error_chain,
698 "sqlite operation failed"
699 );
700 }
701
702 fn actor_specifier(&self) -> Option<ActorSpecifier> {
703 let mut specifier = ActorSpecifier::new(self.actor_id.as_ref()?.clone(), self.generation?);
704 if let Some(key) = self.actor_key.as_ref() {
705 specifier = specifier.with_key(key.clone());
706 }
707 Some(specifier)
708 }
709
710 fn attach_actor(&self, error: anyhow::Error) -> anyhow::Error {
711 match self.actor_specifier() {
712 Some(actor) => error.context(actor),
713 None => error,
714 }
715 }
716
717 fn remote_config(&self) -> Result<RemoteSqliteConfig> {
718 let config = self.runtime_config()?;
719 let generation = config
720 .generation
721 .ok_or_else(|| sqlite_not_configured("generation"))?;
722 Ok(RemoteSqliteConfig {
723 namespace_id: config.handle.namespace().to_owned(),
724 handle: config.handle,
725 actor_id: config.actor_id,
726 generation,
727 })
728 }
729
730 async fn remote_exec(&self, sql: String) -> Result<QueryResult> {
731 let config = self.remote_config()?;
732 let response = config
733 .handle
734 .remote_sqlite_exec(protocol::SqliteExecRequest {
735 namespace_id: config.namespace_id,
736 actor_id: config.actor_id,
737 generation: config.generation,
738 sql,
739 })
740 .await
741 .map_err(remote_request_error)?;
742
743 match response {
744 protocol::SqliteExecResponse::SqliteExecOk(ok) => {
745 Ok(query_result_from_protocol(ok.result))
746 }
747 protocol::SqliteExecResponse::SqliteErrorResponse(error) => {
748 Err(self.remote_sqlite_error_response(error))
749 }
750 }
751 }
752
753 async fn remote_exec_with_session(
754 &self,
755 sql: String,
756 expected_session: Option<u64>,
757 ) -> Result<(QueryResult, u64)> {
758 let config = self.remote_config()?;
759 let (response, session) = config
760 .handle
761 .remote_sqlite_exec_with_session(
762 protocol::SqliteExecRequest {
763 namespace_id: config.namespace_id,
764 actor_id: config.actor_id,
765 generation: config.generation,
766 sql,
767 },
768 expected_session,
769 )
770 .await?;
771
772 match response {
773 protocol::SqliteExecResponse::SqliteExecOk(ok) => {
774 Ok((query_result_from_protocol(ok.result), session))
775 }
776 protocol::SqliteExecResponse::SqliteErrorResponse(error) => {
777 Err(self.remote_sqlite_error_response(error))
778 }
779 }
780 }
781
782 async fn remote_execute(
783 &self,
784 sql: String,
785 params: Option<Vec<BindParam>>,
786 ) -> Result<ExecuteResult> {
787 let config = self.remote_config()?;
788 let response = config
789 .handle
790 .remote_sqlite_execute(protocol::SqliteExecuteRequest {
791 namespace_id: config.namespace_id,
792 actor_id: config.actor_id,
793 generation: config.generation,
794 sql,
795 params: params.map(protocol_bind_params),
796 })
797 .await
798 .map_err(remote_request_error)?;
799
800 match response {
801 protocol::SqliteExecuteResponse::SqliteExecuteOk(ok) => {
802 Ok(execute_result_from_protocol(ok.result))
803 }
804 protocol::SqliteExecuteResponse::SqliteErrorResponse(error) => {
805 Err(self.remote_sqlite_error_response(error))
806 }
807 }
808 }
809
810 async fn remote_execute_batch(
811 &self,
812 statements: Vec<SqliteBatchStatement>,
813 ) -> Result<Vec<ExecuteResult>> {
814 let config = self.remote_config()?;
815 let response = config
816 .handle
817 .remote_sqlite_execute_batch(protocol::SqliteExecuteBatchRequest {
818 namespace_id: config.namespace_id,
819 actor_id: config.actor_id,
820 generation: config.generation,
821 statements: statements
822 .into_iter()
823 .map(|statement| protocol::SqliteBatchStatement {
824 sql: statement.sql,
825 params: statement.params.map(protocol_bind_params),
826 })
827 .collect(),
828 })
829 .await
830 .map_err(remote_request_error)?;
831
832 match response {
833 protocol::SqliteExecuteBatchResponse::SqliteExecuteBatchOk(ok) => Ok(ok
834 .results
835 .into_iter()
836 .map(execute_result_from_protocol)
837 .collect()),
838 protocol::SqliteExecuteBatchResponse::SqliteErrorResponse(error) => {
839 Err(self.remote_sqlite_error_response(error))
840 }
841 }
842 }
843
844 async fn remote_execute_with_session(
845 &self,
846 sql: String,
847 params: Option<Vec<BindParam>>,
848 expected_session: Option<u64>,
849 ) -> Result<(ExecuteResult, u64)> {
850 let config = self.remote_config()?;
851 let (response, session) = config
852 .handle
853 .remote_sqlite_execute_with_session(
854 protocol::SqliteExecuteRequest {
855 namespace_id: config.namespace_id,
856 actor_id: config.actor_id,
857 generation: config.generation,
858 sql,
859 params: params.map(protocol_bind_params),
860 },
861 expected_session,
862 )
863 .await?;
864
865 match response {
866 protocol::SqliteExecuteResponse::SqliteExecuteOk(ok) => {
867 Ok((execute_result_from_protocol(ok.result), session))
868 }
869 protocol::SqliteExecuteResponse::SqliteErrorResponse(error) => {
870 Err(self.remote_sqlite_error_response(error))
871 }
872 }
873 }
874
875 pub(crate) async fn query_rows_cbor(
876 &self,
877 sql: &str,
878 params: Option<&[u8]>,
879 ) -> Result<Vec<u8>> {
880 let bind_params = bind_params_from_cbor(sql, params)?;
881 let result = self.query(sql.to_owned(), bind_params).await?;
882 encode_json_as_cbor(&query_result_to_json_rows(&result))
883 }
884
885 pub(crate) async fn exec_rows_cbor(&self, sql: &str) -> Result<Vec<u8>> {
886 let result = self.exec(sql.to_owned()).await?;
887 encode_json_as_cbor(&query_result_to_json_rows(&result))
888 }
889
890 pub(crate) async fn run_cbor(&self, sql: &str, params: Option<&[u8]>) -> Result<ExecResult> {
891 let bind_params = bind_params_from_cbor(sql, params)?;
892 self.run(sql.to_owned(), bind_params).await
893 }
894
895 pub(crate) async fn execute_rows_cbor(
896 &self,
897 sql: &str,
898 params: Option<&[u8]>,
899 ) -> Result<Vec<u8>> {
900 let bind_params = bind_params_from_cbor(sql, params)?;
901 let result = self.execute(sql.to_owned(), bind_params).await?;
902 encode_json_as_cbor(&query_result_to_json_rows(&QueryResult {
903 columns: result.columns,
904 rows: result.rows,
905 }))
906 }
907
908 fn handle(&self) -> Result<EnvoyHandle> {
909 self.handle
910 .clone()
911 .ok_or_else(|| sqlite_not_configured("handle"))
912 }
913
914 fn remote_sqlite_error_response(&self, error: protocol::SqliteErrorResponse) -> anyhow::Error {
915 if is_head_fence_mismatch_response(&error) {
916 if let Ok(config) = self.runtime_config() {
917 report_sqlite_worker_fatal(
918 &self.worker_fatal_reported,
919 config,
920 format!("remote sqlite fatal storage error: {}", error.message),
921 );
922 }
923 return SqliteRuntimeError::Closed.build();
924 }
925
926 remote_sqlite_error_response(error.message)
927 }
928}
929
930fn report_sqlite_worker_fatal(reported: &AtomicBool, config: SqliteRuntimeConfig, message: String) {
931 if reported.swap(true, Ordering::AcqRel) {
932 return;
933 }
934 config.handle.stop_actor(
938 config.actor_id,
939 config
940 .generation
941 .and_then(|generation| generation.try_into().ok()),
942 Some(message),
943 );
944}
945
946struct RemoteSqliteConfig {
947 handle: EnvoyHandle,
948 namespace_id: String,
949 actor_id: String,
950 generation: u64,
951}
952
953fn select_sqlite_backend(remote_sqlite: bool) -> Result<SqliteBackend> {
954 if remote_sqlite {
955 return Ok(SqliteBackend::RemoteEnvoy);
956 }
957
958 #[cfg(feature = "sqlite-local")]
959 {
960 Ok(SqliteBackend::LocalNative)
961 }
962
963 #[cfg(not(feature = "sqlite-local"))]
964 {
965 Err(SqliteRuntimeError::Unavailable.build())
966 }
967}
968
969fn bind_param_count(params: &Option<Vec<BindParam>>) -> usize {
970 params.as_ref().map_or(0, Vec::len)
971}
972
973#[cfg(feature = "sqlite-local")]
974fn is_fatal_worker_error(error: &anyhow::Error) -> bool {
975 error.downcast_ref::<SqliteWorkerFatalError>().is_some()
976 || error.downcast_ref::<SqliteWorkerDeadError>().is_some()
977 || error
978 .downcast_ref::<SqliteWorkerCloseTimeoutError>()
979 .is_some()
980}
981
982#[cfg(feature = "sqlite-local")]
983fn sqlite_worker_fatal_message(error: &anyhow::Error) -> String {
984 if let Some(error) = error.downcast_ref::<SqliteWorkerFatalError>() {
985 return format!("sqlite fatal storage error: {}", error.message());
986 }
987
988 format!("sqlite worker failed: {error}")
989}
990
991#[cfg(feature = "sqlite-local")]
992fn map_local_worker_error(error: anyhow::Error) -> anyhow::Error {
993 if error
994 .downcast_ref::<SqliteWorkerOverloadedError>()
995 .is_some()
996 {
997 return ActorLifecycle::Overloaded {
998 channel: "sqlite_worker".to_string(),
999 capacity: SQLITE_WORKER_QUEUE_CAPACITY,
1000 operation: "execute sqlite command".to_string(),
1001 }
1002 .build();
1003 }
1004
1005 if error.downcast_ref::<SqliteWorkerClosingError>().is_some()
1006 || error.downcast_ref::<SqliteWorkerDeadError>().is_some()
1007 || error.downcast_ref::<SqliteWorkerFatalError>().is_some()
1008 {
1009 return SqliteRuntimeError::Closed.build();
1010 }
1011
1012 error
1013}
1014
1015fn protocol_bind_params(params: Vec<BindParam>) -> Vec<protocol::SqliteBindParam> {
1016 params.into_iter().map(protocol_bind_param).collect()
1017}
1018
1019fn protocol_bind_param(param: BindParam) -> protocol::SqliteBindParam {
1020 match param {
1021 BindParam::Null => protocol::SqliteBindParam::SqliteValueNull,
1022 BindParam::Integer(value) => {
1023 protocol::SqliteBindParam::SqliteValueInteger(protocol::SqliteValueInteger { value })
1024 }
1025 BindParam::Float(value) => {
1026 protocol::SqliteBindParam::SqliteValueFloat(protocol::SqliteValueFloat {
1027 value: value.to_bits().to_be_bytes(),
1028 })
1029 }
1030 BindParam::Text(value) => {
1031 protocol::SqliteBindParam::SqliteValueText(protocol::SqliteValueText { value })
1032 }
1033 BindParam::Blob(value) => {
1034 protocol::SqliteBindParam::SqliteValueBlob(protocol::SqliteValueBlob { value })
1035 }
1036 }
1037}
1038
1039fn query_result_from_protocol(result: protocol::SqliteQueryResult) -> QueryResult {
1040 QueryResult {
1041 columns: result.columns,
1042 rows: result
1043 .rows
1044 .into_iter()
1045 .map(|row| row.into_iter().map(column_value_from_protocol).collect())
1046 .collect(),
1047 }
1048}
1049
1050fn execute_result_from_protocol(result: protocol::SqliteExecuteResult) -> ExecuteResult {
1051 ExecuteResult {
1052 columns: result.columns,
1053 rows: result
1054 .rows
1055 .into_iter()
1056 .map(|row| row.into_iter().map(column_value_from_protocol).collect())
1057 .collect(),
1058 changes: result.changes,
1059 last_insert_row_id: result.last_insert_row_id,
1060 }
1061}
1062
1063fn column_value_from_protocol(value: protocol::SqliteColumnValue) -> ColumnValue {
1064 match value {
1065 protocol::SqliteColumnValue::SqliteValueNull => ColumnValue::Null,
1066 protocol::SqliteColumnValue::SqliteValueInteger(value) => ColumnValue::Integer(value.value),
1067 protocol::SqliteColumnValue::SqliteValueFloat(value) => {
1068 ColumnValue::Float(f64::from_bits(u64::from_be_bytes(value.value)))
1069 }
1070 protocol::SqliteColumnValue::SqliteValueText(value) => ColumnValue::Text(value.value),
1071 protocol::SqliteColumnValue::SqliteValueBlob(value) => ColumnValue::Blob(value.value),
1072 }
1073}
1074
1075fn remote_request_error(error: anyhow::Error) -> anyhow::Error {
1076 if let Some(indeterminate) = error.downcast_ref::<RemoteSqliteIndeterminateResultError>() {
1077 return SqliteRuntimeError::RemoteIndeterminateResult {
1078 operation: indeterminate.operation.to_owned(),
1079 }
1080 .build();
1081 }
1082
1083 if let Some(compatibility) =
1084 error.downcast_ref::<protocol::versioned::ProtocolCompatibilityError>()
1085 {
1086 if compatibility.feature
1087 == protocol::versioned::ProtocolCompatibilityFeature::RemoteSqliteExecution
1088 {
1089 return SqliteRuntimeError::RemoteUnavailable {
1090 reason: compatibility.to_string(),
1091 }
1092 .build();
1093 }
1094 }
1095
1096 error
1097}
1098
1099fn remote_sqlite_error_response(message: String) -> anyhow::Error {
1100 if message.contains("unavailable") || message.contains("unsupported") {
1101 return SqliteRuntimeError::RemoteUnavailable { reason: message }.build();
1102 }
1103
1104 SqliteRuntimeError::RemoteExecutionFailed { message }.build()
1105}
1106
1107fn is_head_fence_mismatch_response(error: &protocol::SqliteErrorResponse) -> bool {
1108 is_head_fence_mismatch(&error.group, &error.code)
1109}
1110impl std::fmt::Debug for SqliteDb {
1111 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1112 f.debug_struct("SqliteDb")
1113 .field("configured", &self.handle.is_some())
1114 .field("actor_id", &self.actor_id)
1115 .finish()
1116 }
1117}
1118
1119fn bind_params_from_cbor(sql: &str, params: Option<&[u8]>) -> Result<Option<Vec<BindParam>>> {
1120 let Some(params) = params else {
1121 return Ok(None);
1122 };
1123 if params.is_empty() {
1124 return Ok(None);
1125 }
1126
1127 let value = ciborium::from_reader::<JsonValue, _>(Cursor::new(params))
1128 .context("decode sqlite bind params as cbor json")?;
1129 match value {
1130 JsonValue::Array(values) => values
1131 .iter()
1132 .map(json_to_bind_param)
1133 .collect::<Result<Vec<_>>>()
1134 .map(Some),
1135 JsonValue::Object(properties) => {
1136 let ordered_names = extract_named_sqlite_parameters(sql);
1137 if ordered_names.is_empty() {
1138 return properties
1139 .values()
1140 .map(json_to_bind_param)
1141 .collect::<Result<Vec<_>>>()
1142 .map(Some);
1143 }
1144
1145 ordered_names
1146 .iter()
1147 .map(|name| {
1148 get_named_sqlite_binding(&properties, name)
1149 .ok_or_else(|| {
1150 SqliteRuntimeError::InvalidBindParameter {
1151 name: name.clone(),
1152 reason: "missing parameter".to_owned(),
1153 }
1154 .build()
1155 })
1156 .and_then(json_to_bind_param)
1157 })
1158 .collect::<Result<Vec<_>>>()
1159 .map(Some)
1160 }
1161 JsonValue::Null => Ok(None),
1162 other => Err(SqliteRuntimeError::InvalidBindParameter {
1163 name: "params".to_owned(),
1164 reason: format!("expected array or object, got {}", json_type_name(&other)),
1165 }
1166 .build()),
1167 }
1168}
1169
1170fn json_to_bind_param(value: &JsonValue) -> Result<BindParam> {
1171 match value {
1172 JsonValue::Null => Ok(BindParam::Null),
1173 JsonValue::Bool(value) => Ok(BindParam::Integer(i64::from(*value))),
1174 JsonValue::Number(value) => {
1175 if let Some(value) = value.as_i64() {
1176 return Ok(BindParam::Integer(value));
1177 }
1178 if let Some(value) = value.as_u64() {
1179 let value = i64::try_from(value)
1180 .context("sqlite integer bind parameter exceeds i64 range")?;
1181 return Ok(BindParam::Integer(value));
1182 }
1183 value.as_f64().map(BindParam::Float).ok_or_else(|| {
1184 SqliteRuntimeError::InvalidBindParameter {
1185 name: "number".to_owned(),
1186 reason: "unsupported numeric value".to_owned(),
1187 }
1188 .build()
1189 })
1190 }
1191 JsonValue::String(value) => Ok(BindParam::Text(value.clone())),
1192 other => Err(SqliteRuntimeError::InvalidBindParameter {
1193 name: "value".to_owned(),
1194 reason: format!("unsupported type {}", json_type_name(other)),
1195 }
1196 .build()),
1197 }
1198}
1199
1200fn sqlite_not_configured(component: &str) -> anyhow::Error {
1201 SqliteRuntimeError::NotConfigured {
1202 component: component.to_owned(),
1203 }
1204 .build()
1205}
1206
1207fn extract_named_sqlite_parameters(sql: &str) -> Vec<String> {
1208 let mut ordered_names = Vec::new();
1209 let mut seen = HashSet::new();
1210 let bytes = sql.as_bytes();
1211 let mut idx = 0;
1212
1213 while idx < bytes.len() {
1214 let byte = bytes[idx];
1215 if !matches!(byte, b':' | b'@' | b'$') {
1216 idx += 1;
1217 continue;
1218 }
1219
1220 let start = idx;
1221 idx += 1;
1222 if idx >= bytes.len() || !is_sqlite_param_start(bytes[idx]) {
1223 continue;
1224 }
1225 idx += 1;
1226 while idx < bytes.len() && is_sqlite_param_continue(bytes[idx]) {
1227 idx += 1;
1228 }
1229
1230 let name = &sql[start..idx];
1231 if seen.insert(name.to_owned()) {
1232 ordered_names.push(name.to_owned());
1233 }
1234 }
1235
1236 ordered_names
1237}
1238
1239fn is_sqlite_param_start(byte: u8) -> bool {
1240 byte == b'_' || byte.is_ascii_alphabetic()
1241}
1242
1243fn is_sqlite_param_continue(byte: u8) -> bool {
1244 byte == b'_' || byte.is_ascii_alphanumeric()
1245}
1246
1247fn get_named_sqlite_binding<'a>(
1248 bindings: &'a JsonMap<String, JsonValue>,
1249 name: &str,
1250) -> Option<&'a JsonValue> {
1251 if let Some(value) = bindings.get(name) {
1252 return Some(value);
1253 }
1254
1255 let bare_name = name.get(1..)?;
1256 if let Some(value) = bindings.get(bare_name) {
1257 return Some(value);
1258 }
1259
1260 for prefix in [":", "@", "$"] {
1261 let candidate = format!("{prefix}{bare_name}");
1262 if let Some(value) = bindings.get(&candidate) {
1263 return Some(value);
1264 }
1265 }
1266
1267 None
1268}
1269
1270fn query_result_to_json_rows(result: &QueryResult) -> JsonValue {
1271 JsonValue::Array(
1272 result
1273 .rows
1274 .iter()
1275 .map(|row| {
1276 let mut object = JsonMap::new();
1277 for (index, column) in result.columns.iter().enumerate() {
1278 let value = row
1279 .get(index)
1280 .map(column_value_to_json)
1281 .unwrap_or(JsonValue::Null);
1282 object.insert(column.clone(), value);
1283 }
1284 JsonValue::Object(object)
1285 })
1286 .collect(),
1287 )
1288}
1289
1290fn column_value_to_json(value: &ColumnValue) -> JsonValue {
1291 match value {
1292 ColumnValue::Null => JsonValue::Null,
1293 ColumnValue::Integer(value) => JsonValue::from(*value),
1294 ColumnValue::Float(value) => JsonValue::from(*value),
1295 ColumnValue::Text(value) => JsonValue::String(value.clone()),
1296 ColumnValue::Blob(value) => {
1297 JsonValue::Array(value.iter().map(|byte| JsonValue::from(*byte)).collect())
1298 }
1299 }
1300}
1301
1302fn encode_json_as_cbor(value: &impl Serialize) -> Result<Vec<u8>> {
1303 let mut encoded = Vec::new();
1304 ciborium::into_writer(value, &mut encoded).context("encode sqlite rows as cbor")?;
1305 Ok(encoded)
1306}
1307
1308fn json_type_name(value: &JsonValue) -> &'static str {
1309 match value {
1310 JsonValue::Null => "null",
1311 JsonValue::Bool(_) => "boolean",
1312 JsonValue::Number(_) => "number",
1313 JsonValue::String(_) => "string",
1314 JsonValue::Array(_) => "array",
1315 JsonValue::Object(_) => "object",
1316 }
1317}
1318
1319#[cfg(test)]
1320#[path = "../../../tests/sqlite.rs"]
1321mod tests;