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::{handle::EnvoyHandle, utils::RemoteSqliteIndeterminateResultError};
15use rivet_error::{ActorSpecifier, RivetError};
16use serde::Serialize;
17use serde_json::{Map as JsonMap, Value as JsonValue};
18#[cfg(feature = "sqlite-local")]
19use tokio::sync::Mutex as AsyncMutex;
20#[cfg(feature = "sqlite-local")]
21use tokio::task::JoinHandle;
22
23#[cfg(feature = "sqlite-local")]
24mod envoy_sqlite_transport;
25
26#[cfg(feature = "sqlite-local")]
27use crate::error::ActorLifecycle;
28use crate::error::SqliteRuntimeError;
29#[cfg(feature = "sqlite-local")]
30use crate::runtime::RuntimeSpawner;
31
32#[cfg(feature = "sqlite-local")]
33use depot_client::{
34 database::{NativeDatabaseHandle, open_database_from_transport},
35 vfs::{SqliteVfsMetrics, SqliteVfsMetricsSnapshot},
36 worker::{
37 SQLITE_WORKER_QUEUE_CAPACITY, SqliteWorkerCloseTimeoutError, SqliteWorkerClosingError,
38 SqliteWorkerDeadError, SqliteWorkerFatalError, SqliteWorkerOverloadedError,
39 },
40};
41#[cfg(feature = "sqlite-local")]
42use envoy_sqlite_transport::EnvoySqliteTransport;
43
44#[cfg(not(feature = "sqlite-local"))]
45#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
46pub struct SqliteVfsMetricsSnapshot {
47 pub request_build_ns: u64,
48 pub serialize_ns: u64,
49 pub transport_ns: u64,
50 pub state_update_ns: u64,
51 pub total_ns: u64,
52 pub commit_count: u64,
53}
54
55#[derive(Clone)]
56pub struct SqliteRuntimeConfig {
57 pub handle: EnvoyHandle,
58 pub actor_id: String,
59 pub generation: Option<u64>,
60}
61
62#[derive(Clone, Copy, Debug, Eq, PartialEq)]
63pub enum SqliteBackend {
64 LocalNative,
65 RemoteEnvoy,
66 Unavailable,
67}
68
69impl Default for SqliteBackend {
70 fn default() -> Self {
71 Self::Unavailable
72 }
73}
74
75#[derive(Clone, Default)]
76pub struct SqliteDb {
77 handle: Option<EnvoyHandle>,
78 actor_id: Option<String>,
79 actor_key: Option<String>,
80 generation: Option<u64>,
81 backend: SqliteBackend,
82 enabled: bool,
86 #[cfg(feature = "sqlite-local")]
87 db: Arc<Mutex<Option<NativeDatabaseHandle>>>,
90 #[cfg(feature = "sqlite-local")]
91 open_lock: Arc<AsyncMutex<()>>,
92 #[cfg(feature = "sqlite-local")]
93 worker_failure_task: Arc<Mutex<Option<JoinHandle<()>>>>,
94 worker_fatal_reported: Arc<AtomicBool>,
95 #[cfg(feature = "sqlite-local")]
96 vfs_metrics: Option<Arc<dyn SqliteVfsMetrics>>,
97}
98
99impl SqliteDb {
100 pub fn new(handle: EnvoyHandle, actor_id: impl Into<String>, enabled: bool) -> Self {
101 Self::new_with_remote_sqlite(handle, actor_id, None, None, enabled, false)
102 }
103
104 pub fn new_with_remote_sqlite(
105 handle: EnvoyHandle,
106 actor_id: impl Into<String>,
107 actor_key: Option<String>,
108 generation: Option<u64>,
109 enabled: bool,
110 remote_sqlite: bool,
111 ) -> Self {
112 Self {
113 handle: Some(handle),
114 actor_id: Some(actor_id.into()),
115 actor_key,
116 generation,
117 backend: select_sqlite_backend(enabled, remote_sqlite),
118 enabled,
119 #[cfg(feature = "sqlite-local")]
120 db: Default::default(),
121 #[cfg(feature = "sqlite-local")]
122 open_lock: Default::default(),
123 #[cfg(feature = "sqlite-local")]
124 worker_failure_task: Default::default(),
125 worker_fatal_reported: Default::default(),
126 #[cfg(feature = "sqlite-local")]
127 vfs_metrics: None,
128 }
129 }
130
131 #[cfg(feature = "sqlite-local")]
132 pub(crate) fn set_vfs_metrics(&mut self, metrics: Arc<dyn SqliteVfsMetrics>) {
133 self.vfs_metrics = Some(metrics);
134 }
135
136 pub fn is_enabled(&self) -> bool {
137 self.enabled
138 }
139
140 pub fn backend(&self) -> SqliteBackend {
141 self.backend
142 }
143
144 pub async fn get_pages(
145 &self,
146 request: protocol::SqliteGetPagesRequest,
147 ) -> Result<protocol::SqliteGetPagesResponse> {
148 self.handle()?.sqlite_get_pages(request).await
149 }
150
151 pub async fn commit(
152 &self,
153 request: protocol::SqliteCommitRequest,
154 ) -> Result<protocol::SqliteCommitResponse> {
155 self.handle()?.sqlite_commit(request).await
156 }
157
158 pub async fn open(&self) -> Result<()> {
159 match self.backend {
160 SqliteBackend::LocalNative => {
161 #[cfg(feature = "sqlite-local")]
162 {
163 let _open_guard = self.open_lock.lock().await;
164 if self.db.lock().is_some() {
165 return Ok(());
166 }
167
168 let config = self.runtime_config()?;
169 let vfs_metrics = self.vfs_metrics.clone();
170 let rt_handle = tokio::runtime::Handle::try_current()
171 .context("open sqlite database requires a tokio runtime")?;
172 self.worker_fatal_reported.store(false, Ordering::Release);
173
174 let native_db = self.map_local_worker_result(
175 open_database_from_transport(
176 Arc::new(EnvoySqliteTransport::new(config.handle.clone())),
177 config.actor_id.clone(),
178 config
179 .generation
180 .ok_or_else(|| sqlite_not_configured("generation"))?,
181 rt_handle,
182 vfs_metrics,
183 )
184 .await,
185 )?;
186 self.start_worker_failure_monitor(native_db.clone(), config);
187 *self.db.lock() = Some(native_db);
188 if let Some(metrics) = self.vfs_metrics.as_ref() {
189 metrics.set_worker_active(true);
190 }
191 Ok(())
192 }
193
194 #[cfg(not(feature = "sqlite-local"))]
195 {
196 Err(SqliteRuntimeError::Unavailable.build())
197 }
198 }
199 SqliteBackend::RemoteEnvoy => {
200 self.remote_config()?;
201 Ok(())
202 }
203 SqliteBackend::Unavailable => Err(SqliteRuntimeError::Unavailable.build()),
204 }
205 }
206
207 #[cfg(feature = "sqlite-local")]
208 async fn local_exec(&self, sql: String) -> Result<QueryResult> {
209 self.open().await?;
210 self.map_local_worker_result(self.native_db_handle()?.exec(sql).await)
211 }
212
213 #[cfg(not(feature = "sqlite-local"))]
214 async fn local_exec(&self, _sql: String) -> Result<QueryResult> {
215 Err(SqliteRuntimeError::Unavailable.build())
216 }
217
218 #[cfg(feature = "sqlite-local")]
219 async fn local_query(
220 &self,
221 sql: String,
222 params: Option<Vec<BindParam>>,
223 ) -> Result<QueryResult> {
224 self.open().await?;
225 self.map_local_worker_result(self.native_db_handle()?.query(sql, params).await)
226 }
227
228 #[cfg(not(feature = "sqlite-local"))]
229 async fn local_query(
230 &self,
231 _sql: String,
232 _params: Option<Vec<BindParam>>,
233 ) -> Result<QueryResult> {
234 Err(SqliteRuntimeError::Unavailable.build())
235 }
236
237 #[cfg(feature = "sqlite-local")]
238 async fn local_run(&self, sql: String, params: Option<Vec<BindParam>>) -> Result<ExecResult> {
239 self.open().await?;
240 self.map_local_worker_result(self.native_db_handle()?.run(sql, params).await)
241 }
242
243 #[cfg(not(feature = "sqlite-local"))]
244 async fn local_run(&self, _sql: String, _params: Option<Vec<BindParam>>) -> Result<ExecResult> {
245 Err(SqliteRuntimeError::Unavailable.build())
246 }
247
248 #[cfg(feature = "sqlite-local")]
249 async fn local_execute(
250 &self,
251 sql: String,
252 params: Option<Vec<BindParam>>,
253 ) -> Result<ExecuteResult> {
254 self.open().await?;
255 self.map_local_worker_result(self.native_db_handle()?.execute(sql, params).await)
256 }
257
258 #[cfg(not(feature = "sqlite-local"))]
259 async fn local_execute(
260 &self,
261 _sql: String,
262 _params: Option<Vec<BindParam>>,
263 ) -> Result<ExecuteResult> {
264 Err(SqliteRuntimeError::Unavailable.build())
265 }
266
267 pub async fn exec(&self, sql: impl Into<String>) -> Result<QueryResult> {
268 let sql = sql.into();
269 let sql_for_log = sql.clone();
270 let result = match self.backend {
271 SqliteBackend::LocalNative => self.local_exec(sql).await,
272 SqliteBackend::RemoteEnvoy => self.remote_exec(sql).await,
273 SqliteBackend::Unavailable => Err(SqliteRuntimeError::Unavailable.build()),
274 };
275 match result {
276 Ok(result) => Ok(result),
277 Err(error) => {
278 let error = self.attach_actor(error);
279 self.log_operation_error("exec", &sql_for_log, 0, &error);
280 Err(error)
281 }
282 }
283 }
284
285 pub async fn query(
286 &self,
287 sql: impl Into<String>,
288 params: Option<Vec<BindParam>>,
289 ) -> Result<QueryResult> {
290 let sql = sql.into();
291 let sql_for_log = sql.clone();
292 let binding_count = bind_param_count(¶ms);
293 let result = match self.backend {
294 SqliteBackend::LocalNative => self.local_query(sql, params).await,
295 SqliteBackend::RemoteEnvoy => self
296 .remote_execute(sql, params)
297 .await
298 .map(ExecuteResult::into_query_result),
299 SqliteBackend::Unavailable => Err(SqliteRuntimeError::Unavailable.build()),
300 };
301 match result {
302 Ok(result) => Ok(result),
303 Err(error) => {
304 let error = self.attach_actor(error);
305 self.log_operation_error("query", &sql_for_log, binding_count, &error);
306 Err(error)
307 }
308 }
309 }
310
311 pub async fn run(
312 &self,
313 sql: impl Into<String>,
314 params: Option<Vec<BindParam>>,
315 ) -> Result<ExecResult> {
316 let sql = sql.into();
317 let sql_for_log = sql.clone();
318 let binding_count = bind_param_count(¶ms);
319 let result = match self.backend {
320 SqliteBackend::LocalNative => self.local_run(sql, params).await,
321 SqliteBackend::RemoteEnvoy => self
322 .remote_execute(sql, params)
323 .await
324 .map(ExecuteResult::into_exec_result),
325 SqliteBackend::Unavailable => Err(SqliteRuntimeError::Unavailable.build()),
326 };
327 match result {
328 Ok(result) => Ok(result),
329 Err(error) => {
330 let error = self.attach_actor(error);
331 self.log_operation_error("run", &sql_for_log, binding_count, &error);
332 Err(error)
333 }
334 }
335 }
336
337 pub async fn execute(
338 &self,
339 sql: impl Into<String>,
340 params: Option<Vec<BindParam>>,
341 ) -> Result<ExecuteResult> {
342 let sql = sql.into();
343 let sql_for_log = sql.clone();
344 let binding_count = bind_param_count(¶ms);
345 let result = match self.backend {
346 SqliteBackend::LocalNative => self.local_execute(sql, params).await,
347 SqliteBackend::RemoteEnvoy => self.remote_execute(sql, params).await,
348 SqliteBackend::Unavailable => Err(SqliteRuntimeError::Unavailable.build()),
349 };
350 match result {
351 Ok(result) => Ok(result),
352 Err(error) => {
353 let error = self.attach_actor(error);
354 self.log_operation_error("execute", &sql_for_log, binding_count, &error);
355 Err(error)
356 }
357 }
358 }
359
360 pub async fn close(&self) -> Result<()> {
361 match self.backend {
362 SqliteBackend::LocalNative => {
363 #[cfg(feature = "sqlite-local")]
364 {
365 let native_db = self.db.lock().take();
366 if let Some(native_db) = native_db {
367 let result = self.map_local_worker_result(native_db.close().await);
368 self.abort_worker_failure_monitor();
369 if let Some(metrics) = self.vfs_metrics.as_ref() {
370 metrics.set_worker_active(false);
371 }
372 result?;
373 }
374 }
375 Ok(())
376 }
377 SqliteBackend::RemoteEnvoy | SqliteBackend::Unavailable => Ok(()),
378 }
379 }
380
381 pub(crate) async fn cleanup(&self) -> Result<()> {
382 self.close().await
383 }
384
385 pub fn take_last_kv_error(&self) -> Option<String> {
386 if self.backend != SqliteBackend::LocalNative {
387 return None;
388 }
389
390 #[cfg(feature = "sqlite-local")]
391 {
392 return self
393 .db
394 .lock()
395 .as_ref()
396 .and_then(NativeDatabaseHandle::take_last_kv_error);
397 }
398
399 #[cfg(not(feature = "sqlite-local"))]
400 None
401 }
402
403 #[cfg(feature = "sqlite-local")]
404 fn native_db_handle(&self) -> Result<NativeDatabaseHandle> {
405 self.db
406 .lock()
407 .as_ref()
408 .cloned()
409 .ok_or_else(|| SqliteRuntimeError::Closed.build())
410 }
411
412 #[cfg(feature = "sqlite-local")]
413 fn map_local_worker_result<T>(&self, result: Result<T>) -> Result<T> {
414 match result {
415 Ok(value) => Ok(value),
416 Err(error) => {
417 if is_fatal_worker_error(&error) {
418 self.report_worker_fatal(&error);
419 }
420 Err(map_local_worker_error(error))
421 }
422 }
423 }
424
425 #[cfg(feature = "sqlite-local")]
426 fn report_worker_fatal(&self, error: &anyhow::Error) {
427 let Ok(config) = self.runtime_config() else {
428 return;
429 };
430 report_sqlite_worker_fatal(
431 &self.worker_fatal_reported,
432 config,
433 sqlite_worker_fatal_message(error),
434 );
435 }
436
437 #[cfg(feature = "sqlite-local")]
438 fn start_worker_failure_monitor(
439 &self,
440 native_db: NativeDatabaseHandle,
441 config: SqliteRuntimeConfig,
442 ) {
443 self.abort_worker_failure_monitor();
444 let reported = Arc::clone(&self.worker_fatal_reported);
445 let task = RuntimeSpawner::spawn(async move {
446 if native_db.wait_for_worker_failure().await {
447 report_sqlite_worker_fatal(
448 &reported,
449 config,
450 "sqlite worker thread stopped unexpectedly".to_string(),
451 );
452 }
453 });
454 *self.worker_failure_task.lock() = Some(task);
455 }
456
457 #[cfg(feature = "sqlite-local")]
458 fn abort_worker_failure_monitor(&self) {
459 if let Some(task) = self.worker_failure_task.lock().take() {
460 task.abort();
461 }
462 }
463
464 pub fn metrics(&self) -> Option<SqliteVfsMetricsSnapshot> {
465 #[cfg(feature = "sqlite-local")]
466 {
467 self.db
468 .lock()
469 .as_ref()
470 .map(NativeDatabaseHandle::sqlite_vfs_metrics)
471 }
472
473 #[cfg(not(feature = "sqlite-local"))]
474 {
475 None
476 }
477 }
478
479 pub fn runtime_config(&self) -> Result<SqliteRuntimeConfig> {
480 Ok(SqliteRuntimeConfig {
481 handle: self.handle()?,
482 actor_id: self
483 .actor_id
484 .clone()
485 .ok_or_else(|| sqlite_not_configured("actor id"))?,
486 generation: self.generation,
487 })
488 }
489
490 fn log_operation_error(
491 &self,
492 operation: &'static str,
493 sql: &str,
494 binding_count: usize,
495 error: &anyhow::Error,
496 ) {
497 let structured = RivetError::extract(error);
498 let error_chain = error.chain().map(ToString::to_string).collect::<Vec<_>>();
499 tracing::error!(
500 actor_id = self.actor_id.as_deref().unwrap_or("<unknown>"),
501 generation = ?self.generation,
502 backend = ?self.backend,
503 operation,
504 sql,
505 binding_count,
506 group = structured.group(),
507 code = structured.code(),
508 error_message = %structured.message(),
509 metadata = ?structured.metadata(),
510 error_chain = ?error_chain,
511 "sqlite operation failed"
512 );
513 }
514
515 fn actor_specifier(&self) -> Option<ActorSpecifier> {
516 let mut specifier = ActorSpecifier::new(self.actor_id.as_ref()?.clone(), self.generation?);
517 if let Some(key) = self.actor_key.as_ref() {
518 specifier = specifier.with_key(key.clone());
519 }
520 Some(specifier)
521 }
522
523 fn attach_actor(&self, error: anyhow::Error) -> anyhow::Error {
524 match self.actor_specifier() {
525 Some(actor) => error.context(actor),
526 None => error,
527 }
528 }
529
530 fn remote_config(&self) -> Result<RemoteSqliteConfig> {
531 let config = self.runtime_config()?;
532 let generation = config
533 .generation
534 .ok_or_else(|| sqlite_not_configured("generation"))?;
535 Ok(RemoteSqliteConfig {
536 namespace_id: config.handle.namespace().to_owned(),
537 handle: config.handle,
538 actor_id: config.actor_id,
539 generation,
540 })
541 }
542
543 async fn remote_exec(&self, sql: String) -> Result<QueryResult> {
544 let config = self.remote_config()?;
545 let response = config
546 .handle
547 .remote_sqlite_exec(protocol::SqliteExecRequest {
548 namespace_id: config.namespace_id,
549 actor_id: config.actor_id,
550 generation: config.generation,
551 sql,
552 })
553 .await
554 .map_err(remote_request_error)?;
555
556 match response {
557 protocol::SqliteExecResponse::SqliteExecOk(ok) => {
558 Ok(query_result_from_protocol(ok.result))
559 }
560 protocol::SqliteExecResponse::SqliteErrorResponse(error) => {
561 Err(self.remote_sqlite_error_response(error))
562 }
563 }
564 }
565
566 async fn remote_execute(
567 &self,
568 sql: String,
569 params: Option<Vec<BindParam>>,
570 ) -> Result<ExecuteResult> {
571 let config = self.remote_config()?;
572 let response = config
573 .handle
574 .remote_sqlite_execute(protocol::SqliteExecuteRequest {
575 namespace_id: config.namespace_id,
576 actor_id: config.actor_id,
577 generation: config.generation,
578 sql,
579 params: params.map(protocol_bind_params),
580 })
581 .await
582 .map_err(remote_request_error)?;
583
584 match response {
585 protocol::SqliteExecuteResponse::SqliteExecuteOk(ok) => {
586 Ok(execute_result_from_protocol(ok.result))
587 }
588 protocol::SqliteExecuteResponse::SqliteErrorResponse(error) => {
589 Err(self.remote_sqlite_error_response(error))
590 }
591 }
592 }
593
594 pub(crate) async fn query_rows_cbor(
595 &self,
596 sql: &str,
597 params: Option<&[u8]>,
598 ) -> Result<Vec<u8>> {
599 let bind_params = bind_params_from_cbor(sql, params)?;
600 let result = self.query(sql.to_owned(), bind_params).await?;
601 encode_json_as_cbor(&query_result_to_json_rows(&result))
602 }
603
604 pub(crate) async fn exec_rows_cbor(&self, sql: &str) -> Result<Vec<u8>> {
605 let result = self.exec(sql.to_owned()).await?;
606 encode_json_as_cbor(&query_result_to_json_rows(&result))
607 }
608
609 pub(crate) async fn run_cbor(&self, sql: &str, params: Option<&[u8]>) -> Result<ExecResult> {
610 let bind_params = bind_params_from_cbor(sql, params)?;
611 self.run(sql.to_owned(), bind_params).await
612 }
613
614 pub(crate) async fn execute_rows_cbor(
615 &self,
616 sql: &str,
617 params: Option<&[u8]>,
618 ) -> Result<Vec<u8>> {
619 let bind_params = bind_params_from_cbor(sql, params)?;
620 let result = self.execute(sql.to_owned(), bind_params).await?;
621 encode_json_as_cbor(&query_result_to_json_rows(&QueryResult {
622 columns: result.columns,
623 rows: result.rows,
624 }))
625 }
626
627 fn handle(&self) -> Result<EnvoyHandle> {
628 self.handle
629 .clone()
630 .ok_or_else(|| sqlite_not_configured("handle"))
631 }
632
633 fn remote_sqlite_error_response(&self, error: protocol::SqliteErrorResponse) -> anyhow::Error {
634 if is_head_fence_mismatch_response(&error) {
635 if let Ok(config) = self.runtime_config() {
636 report_sqlite_worker_fatal(
637 &self.worker_fatal_reported,
638 config,
639 format!("remote sqlite fatal storage error: {}", error.message),
640 );
641 }
642 return SqliteRuntimeError::Closed.build();
643 }
644
645 remote_sqlite_error_response(error.message)
646 }
647}
648
649fn report_sqlite_worker_fatal(reported: &AtomicBool, config: SqliteRuntimeConfig, message: String) {
650 if reported.swap(true, Ordering::AcqRel) {
651 return;
652 }
653 config.handle.stop_actor(
657 config.actor_id,
658 config
659 .generation
660 .and_then(|generation| generation.try_into().ok()),
661 Some(message),
662 );
663}
664
665struct RemoteSqliteConfig {
666 handle: EnvoyHandle,
667 namespace_id: String,
668 actor_id: String,
669 generation: u64,
670}
671
672fn select_sqlite_backend(enabled: bool, remote_sqlite: bool) -> SqliteBackend {
673 if enabled && remote_sqlite {
674 return SqliteBackend::RemoteEnvoy;
675 }
676
677 #[cfg(feature = "sqlite-local")]
678 {
679 SqliteBackend::LocalNative
680 }
681
682 #[cfg(not(feature = "sqlite-local"))]
683 {
684 SqliteBackend::Unavailable
685 }
686}
687
688fn bind_param_count(params: &Option<Vec<BindParam>>) -> usize {
689 params.as_ref().map_or(0, Vec::len)
690}
691
692#[cfg(feature = "sqlite-local")]
693fn is_fatal_worker_error(error: &anyhow::Error) -> bool {
694 error.downcast_ref::<SqliteWorkerFatalError>().is_some()
695 || error.downcast_ref::<SqliteWorkerDeadError>().is_some()
696 || error
697 .downcast_ref::<SqliteWorkerCloseTimeoutError>()
698 .is_some()
699}
700
701#[cfg(feature = "sqlite-local")]
702fn sqlite_worker_fatal_message(error: &anyhow::Error) -> String {
703 if let Some(error) = error.downcast_ref::<SqliteWorkerFatalError>() {
704 return format!("sqlite fatal storage error: {}", error.message());
705 }
706
707 format!("sqlite worker failed: {error}")
708}
709
710#[cfg(feature = "sqlite-local")]
711fn map_local_worker_error(error: anyhow::Error) -> anyhow::Error {
712 if error
713 .downcast_ref::<SqliteWorkerOverloadedError>()
714 .is_some()
715 {
716 return ActorLifecycle::Overloaded {
717 channel: "sqlite_worker".to_string(),
718 capacity: SQLITE_WORKER_QUEUE_CAPACITY,
719 operation: "execute sqlite command".to_string(),
720 }
721 .build();
722 }
723
724 if error.downcast_ref::<SqliteWorkerClosingError>().is_some()
725 || error.downcast_ref::<SqliteWorkerDeadError>().is_some()
726 || error.downcast_ref::<SqliteWorkerFatalError>().is_some()
727 {
728 return SqliteRuntimeError::Closed.build();
729 }
730
731 error
732}
733
734fn protocol_bind_params(params: Vec<BindParam>) -> Vec<protocol::SqliteBindParam> {
735 params.into_iter().map(protocol_bind_param).collect()
736}
737
738fn protocol_bind_param(param: BindParam) -> protocol::SqliteBindParam {
739 match param {
740 BindParam::Null => protocol::SqliteBindParam::SqliteValueNull,
741 BindParam::Integer(value) => {
742 protocol::SqliteBindParam::SqliteValueInteger(protocol::SqliteValueInteger { value })
743 }
744 BindParam::Float(value) => {
745 protocol::SqliteBindParam::SqliteValueFloat(protocol::SqliteValueFloat {
746 value: value.to_bits().to_be_bytes(),
747 })
748 }
749 BindParam::Text(value) => {
750 protocol::SqliteBindParam::SqliteValueText(protocol::SqliteValueText { value })
751 }
752 BindParam::Blob(value) => {
753 protocol::SqliteBindParam::SqliteValueBlob(protocol::SqliteValueBlob { value })
754 }
755 }
756}
757
758fn query_result_from_protocol(result: protocol::SqliteQueryResult) -> QueryResult {
759 QueryResult {
760 columns: result.columns,
761 rows: result
762 .rows
763 .into_iter()
764 .map(|row| row.into_iter().map(column_value_from_protocol).collect())
765 .collect(),
766 }
767}
768
769fn execute_result_from_protocol(result: protocol::SqliteExecuteResult) -> ExecuteResult {
770 ExecuteResult {
771 columns: result.columns,
772 rows: result
773 .rows
774 .into_iter()
775 .map(|row| row.into_iter().map(column_value_from_protocol).collect())
776 .collect(),
777 changes: result.changes,
778 last_insert_row_id: result.last_insert_row_id,
779 }
780}
781
782fn column_value_from_protocol(value: protocol::SqliteColumnValue) -> ColumnValue {
783 match value {
784 protocol::SqliteColumnValue::SqliteValueNull => ColumnValue::Null,
785 protocol::SqliteColumnValue::SqliteValueInteger(value) => ColumnValue::Integer(value.value),
786 protocol::SqliteColumnValue::SqliteValueFloat(value) => {
787 ColumnValue::Float(f64::from_bits(u64::from_be_bytes(value.value)))
788 }
789 protocol::SqliteColumnValue::SqliteValueText(value) => ColumnValue::Text(value.value),
790 protocol::SqliteColumnValue::SqliteValueBlob(value) => ColumnValue::Blob(value.value),
791 }
792}
793
794fn remote_request_error(error: anyhow::Error) -> anyhow::Error {
795 if let Some(indeterminate) = error.downcast_ref::<RemoteSqliteIndeterminateResultError>() {
796 return SqliteRuntimeError::RemoteIndeterminateResult {
797 operation: indeterminate.operation.to_owned(),
798 }
799 .build();
800 }
801
802 if let Some(compatibility) =
803 error.downcast_ref::<protocol::versioned::ProtocolCompatibilityError>()
804 {
805 if compatibility.feature
806 == protocol::versioned::ProtocolCompatibilityFeature::RemoteSqliteExecution
807 {
808 return SqliteRuntimeError::RemoteUnavailable {
809 reason: compatibility.to_string(),
810 }
811 .build();
812 }
813 }
814
815 error
816}
817
818fn remote_sqlite_error_response(message: String) -> anyhow::Error {
819 if message.contains("unavailable") || message.contains("unsupported") {
820 return SqliteRuntimeError::RemoteUnavailable { reason: message }.build();
821 }
822
823 SqliteRuntimeError::RemoteExecutionFailed { message }.build()
824}
825
826fn is_head_fence_mismatch_response(error: &protocol::SqliteErrorResponse) -> bool {
827 is_head_fence_mismatch(&error.group, &error.code)
828}
829impl std::fmt::Debug for SqliteDb {
830 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
831 f.debug_struct("SqliteDb")
832 .field("configured", &self.handle.is_some())
833 .field("actor_id", &self.actor_id)
834 .finish()
835 }
836}
837
838fn bind_params_from_cbor(sql: &str, params: Option<&[u8]>) -> Result<Option<Vec<BindParam>>> {
839 let Some(params) = params else {
840 return Ok(None);
841 };
842 if params.is_empty() {
843 return Ok(None);
844 }
845
846 let value = ciborium::from_reader::<JsonValue, _>(Cursor::new(params))
847 .context("decode sqlite bind params as cbor json")?;
848 match value {
849 JsonValue::Array(values) => values
850 .iter()
851 .map(json_to_bind_param)
852 .collect::<Result<Vec<_>>>()
853 .map(Some),
854 JsonValue::Object(properties) => {
855 let ordered_names = extract_named_sqlite_parameters(sql);
856 if ordered_names.is_empty() {
857 return properties
858 .values()
859 .map(json_to_bind_param)
860 .collect::<Result<Vec<_>>>()
861 .map(Some);
862 }
863
864 ordered_names
865 .iter()
866 .map(|name| {
867 get_named_sqlite_binding(&properties, name)
868 .ok_or_else(|| {
869 SqliteRuntimeError::InvalidBindParameter {
870 name: name.clone(),
871 reason: "missing parameter".to_owned(),
872 }
873 .build()
874 })
875 .and_then(json_to_bind_param)
876 })
877 .collect::<Result<Vec<_>>>()
878 .map(Some)
879 }
880 JsonValue::Null => Ok(None),
881 other => Err(SqliteRuntimeError::InvalidBindParameter {
882 name: "params".to_owned(),
883 reason: format!("expected array or object, got {}", json_type_name(&other)),
884 }
885 .build()),
886 }
887}
888
889fn json_to_bind_param(value: &JsonValue) -> Result<BindParam> {
890 match value {
891 JsonValue::Null => Ok(BindParam::Null),
892 JsonValue::Bool(value) => Ok(BindParam::Integer(i64::from(*value))),
893 JsonValue::Number(value) => {
894 if let Some(value) = value.as_i64() {
895 return Ok(BindParam::Integer(value));
896 }
897 if let Some(value) = value.as_u64() {
898 let value = i64::try_from(value)
899 .context("sqlite integer bind parameter exceeds i64 range")?;
900 return Ok(BindParam::Integer(value));
901 }
902 value.as_f64().map(BindParam::Float).ok_or_else(|| {
903 SqliteRuntimeError::InvalidBindParameter {
904 name: "number".to_owned(),
905 reason: "unsupported numeric value".to_owned(),
906 }
907 .build()
908 })
909 }
910 JsonValue::String(value) => Ok(BindParam::Text(value.clone())),
911 other => Err(SqliteRuntimeError::InvalidBindParameter {
912 name: "value".to_owned(),
913 reason: format!("unsupported type {}", json_type_name(other)),
914 }
915 .build()),
916 }
917}
918
919fn sqlite_not_configured(component: &str) -> anyhow::Error {
920 SqliteRuntimeError::NotConfigured {
921 component: component.to_owned(),
922 }
923 .build()
924}
925
926fn extract_named_sqlite_parameters(sql: &str) -> Vec<String> {
927 let mut ordered_names = Vec::new();
928 let mut seen = HashSet::new();
929 let bytes = sql.as_bytes();
930 let mut idx = 0;
931
932 while idx < bytes.len() {
933 let byte = bytes[idx];
934 if !matches!(byte, b':' | b'@' | b'$') {
935 idx += 1;
936 continue;
937 }
938
939 let start = idx;
940 idx += 1;
941 if idx >= bytes.len() || !is_sqlite_param_start(bytes[idx]) {
942 continue;
943 }
944 idx += 1;
945 while idx < bytes.len() && is_sqlite_param_continue(bytes[idx]) {
946 idx += 1;
947 }
948
949 let name = &sql[start..idx];
950 if seen.insert(name.to_owned()) {
951 ordered_names.push(name.to_owned());
952 }
953 }
954
955 ordered_names
956}
957
958fn is_sqlite_param_start(byte: u8) -> bool {
959 byte == b'_' || byte.is_ascii_alphabetic()
960}
961
962fn is_sqlite_param_continue(byte: u8) -> bool {
963 byte == b'_' || byte.is_ascii_alphanumeric()
964}
965
966fn get_named_sqlite_binding<'a>(
967 bindings: &'a JsonMap<String, JsonValue>,
968 name: &str,
969) -> Option<&'a JsonValue> {
970 if let Some(value) = bindings.get(name) {
971 return Some(value);
972 }
973
974 let bare_name = name.get(1..)?;
975 if let Some(value) = bindings.get(bare_name) {
976 return Some(value);
977 }
978
979 for prefix in [":", "@", "$"] {
980 let candidate = format!("{prefix}{bare_name}");
981 if let Some(value) = bindings.get(&candidate) {
982 return Some(value);
983 }
984 }
985
986 None
987}
988
989fn query_result_to_json_rows(result: &QueryResult) -> JsonValue {
990 JsonValue::Array(
991 result
992 .rows
993 .iter()
994 .map(|row| {
995 let mut object = JsonMap::new();
996 for (index, column) in result.columns.iter().enumerate() {
997 let value = row
998 .get(index)
999 .map(column_value_to_json)
1000 .unwrap_or(JsonValue::Null);
1001 object.insert(column.clone(), value);
1002 }
1003 JsonValue::Object(object)
1004 })
1005 .collect(),
1006 )
1007}
1008
1009fn column_value_to_json(value: &ColumnValue) -> JsonValue {
1010 match value {
1011 ColumnValue::Null => JsonValue::Null,
1012 ColumnValue::Integer(value) => JsonValue::from(*value),
1013 ColumnValue::Float(value) => JsonValue::from(*value),
1014 ColumnValue::Text(value) => JsonValue::String(value.clone()),
1015 ColumnValue::Blob(value) => {
1016 JsonValue::Array(value.iter().map(|byte| JsonValue::from(*byte)).collect())
1017 }
1018 }
1019}
1020
1021fn encode_json_as_cbor(value: &impl Serialize) -> Result<Vec<u8>> {
1022 let mut encoded = Vec::new();
1023 ciborium::into_writer(value, &mut encoded).context("encode sqlite rows as cbor")?;
1024 Ok(encoded)
1025}
1026
1027fn json_type_name(value: &JsonValue) -> &'static str {
1028 match value {
1029 JsonValue::Null => "null",
1030 JsonValue::Bool(_) => "boolean",
1031 JsonValue::Number(_) => "number",
1032 JsonValue::String(_) => "string",
1033 JsonValue::Array(_) => "array",
1034 JsonValue::Object(_) => "object",
1035 }
1036}
1037
1038#[cfg(test)]
1039#[path = "../../tests/sqlite.rs"]
1040mod tests;