Skip to main content

sqlite_embedded/
database.rs

1use std::sync::Arc;
2use std::time::Instant;
3
4use anyhow::{Result, anyhow};
5use async_trait::async_trait;
6use rivet_envoy_protocol as protocol;
7use tokio::runtime::Handle;
8use tokio::sync::watch;
9
10use crate::{
11	query::{BindParam, ExecResult, ExecuteResult, QueryResult},
12	vfs::{
13		InitialPages, NativeVfsHandle, SqliteOpenPhase, SqliteTransportHandle, SqliteVfs,
14		SqliteVfsMetrics, SqliteVfsMetricsSnapshot, VfsConfig,
15		fetch_initial_pages_for_registration,
16	},
17	worker::{
18		SqlitePool, SqliteWorkerCloseTimeoutError, SqliteWorkerFatalError, SqliteWorkerHandle,
19		SqliteWorkerResult,
20	},
21};
22
23#[derive(Clone)]
24pub struct NativeDatabaseHandle {
25	vfs: NativeVfsHandle,
26	worker: SqliteWorkerHandle,
27}
28
29pub fn vfs_name_for_actor_database(actor_id: &str, generation: u64) -> String {
30	format!("envoy-sqlite-{actor_id}-g{generation}")
31}
32
33struct GenerationFencedTransport {
34	inner: SqliteTransportHandle,
35	generation: u64,
36}
37
38#[async_trait]
39impl crate::vfs::SqliteTransport for GenerationFencedTransport {
40	async fn get_pages(
41		&self,
42		mut request: protocol::SqliteGetPagesRequest,
43	) -> Result<protocol::SqliteGetPagesResponse> {
44		request.expected_generation.get_or_insert(self.generation);
45		self.inner.get_pages(request).await
46	}
47
48	async fn commit(
49		&self,
50		mut request: protocol::SqliteCommitRequest,
51	) -> Result<protocol::SqliteCommitResponse> {
52		request.expected_generation.get_or_insert(self.generation);
53		self.inner.commit(request).await
54	}
55
56	async fn commit_stage_begin(
57		&self,
58		mut request: protocol::SqliteCommitStageBeginRequest,
59	) -> Result<protocol::SqliteCommitStageBeginResponse> {
60		request.expected_generation.get_or_insert(self.generation);
61		self.inner.commit_stage_begin(request).await
62	}
63
64	async fn commit_stage_segment(
65		&self,
66		mut request: protocol::SqliteCommitStageSegmentRequest,
67	) -> Result<protocol::SqliteCommitStageSegmentResponse> {
68		request.expected_generation.get_or_insert(self.generation);
69		self.inner.commit_stage_segment(request).await
70	}
71
72	async fn commit_finalize(
73		&self,
74		mut request: protocol::SqliteCommitFinalizeRequest,
75	) -> Result<protocol::SqliteCommitFinalizeResponse> {
76		request.expected_generation.get_or_insert(self.generation);
77		self.inner.commit_finalize(request).await
78	}
79}
80
81pub async fn open_database_from_transport(
82	transport: SqliteTransportHandle,
83	actor_id: String,
84	generation: u64,
85	rt_handle: Handle,
86	metrics: Option<Arc<dyn SqliteVfsMetrics>>,
87) -> Result<NativeDatabaseHandle> {
88	open_database_from_transport_on_pool(None, transport, actor_id, generation, rt_handle, metrics)
89		.await
90}
91
92/// Opens the database on the runtime's shared worker pool, or on a dedicated single-thread
93/// pool when `pool` is `None`.
94pub async fn open_database_from_transport_on_pool(
95	pool: Option<&SqlitePool>,
96	transport: SqliteTransportHandle,
97	actor_id: String,
98	generation: u64,
99	rt_handle: Handle,
100	metrics: Option<Arc<dyn SqliteVfsMetrics>>,
101) -> Result<NativeDatabaseHandle> {
102	let vfs_name = vfs_name_for_actor_database(&actor_id, generation);
103	open_database_from_transport_with_guard(
104		pool, vfs_name, transport, actor_id, generation, rt_handle, metrics, None,
105	)
106	.await
107}
108
109/// Opens the database of an actor whose start showed its storage was never written, on the
110/// runtime's shared worker pool, without reading any page first: a new database has none.
111///
112/// Its first write carries no expected head, which storage accepts only while the database is
113/// empty, so a database that turns out to hold pages fails that write with a head fence mismatch
114/// instead of being overwritten.
115pub async fn open_new_database_from_transport_on_pool(
116	pool: Option<&SqlitePool>,
117	transport: SqliteTransportHandle,
118	actor_id: String,
119	generation: u64,
120	rt_handle: Handle,
121	metrics: Option<Arc<dyn SqliteVfsMetrics>>,
122) -> Result<NativeDatabaseHandle> {
123	let vfs_name = vfs_name_for_actor_database(&actor_id, generation);
124	open_database(
125		pool,
126		vfs_name,
127		transport,
128		actor_id,
129		generation,
130		rt_handle,
131		metrics,
132		None,
133		Preload::Empty,
134	)
135	.await
136}
137
138/// Like [`open_database_from_transport_on_pool`], with `guard` installed on the connection for
139/// SQL sent by an untrusted peer. `vfs_name` must be unique in the process while the database
140/// is open: registering a second VFS under a live name replaces the first.
141#[allow(clippy::too_many_arguments)]
142pub async fn open_database_from_transport_with_guard(
143	pool: Option<&SqlitePool>,
144	vfs_name: String,
145	transport: SqliteTransportHandle,
146	actor_id: String,
147	generation: u64,
148	rt_handle: Handle,
149	metrics: Option<Arc<dyn SqliteVfsMetrics>>,
150	guard: Option<crate::guard::ConnectionGuard>,
151) -> Result<NativeDatabaseHandle> {
152	open_database(
153		pool,
154		vfs_name,
155		transport,
156		actor_id,
157		generation,
158		rt_handle,
159		metrics,
160		guard,
161		Preload::Fetch,
162	)
163	.await
164}
165
166/// How an open learns the database's first pages.
167enum Preload {
168	/// Read them from storage before registering the VFS.
169	Fetch,
170	/// The database is known to be new, so it starts empty with no read.
171	Empty,
172}
173
174#[allow(clippy::too_many_arguments)]
175async fn open_database(
176	pool: Option<&SqlitePool>,
177	vfs_name: String,
178	transport: SqliteTransportHandle,
179	actor_id: String,
180	generation: u64,
181	rt_handle: Handle,
182	metrics: Option<Arc<dyn SqliteVfsMetrics>>,
183	guard: Option<crate::guard::ConnectionGuard>,
184	preload: Preload,
185) -> Result<NativeDatabaseHandle> {
186	let open_timer = SqliteOpenTimer::new(&metrics);
187	// A database without a shared pool runs on its own single-thread pool, which also owns its
188	// page budget.
189	let owned_pool;
190	let pool = match pool {
191		Some(pool) => pool,
192		None => {
193			owned_pool = SqlitePool::new(1, None);
194			&owned_pool
195		}
196	};
197	let mut config = VfsConfig::default();
198	config.page_budget = pool.page_budget().clone();
199	let transport: SqliteTransportHandle = Arc::new(GenerationFencedTransport {
200		inner: transport,
201		generation,
202	});
203	let initial_pages = match preload {
204		Preload::Fetch => {
205			let preload_start = Instant::now();
206			let preload_result = fetch_initial_pages_for_registration(
207				transport.clone(),
208				&actor_id,
209				generation,
210				&config,
211			)
212			.await
213			.map_err(|err| anyhow!("failed to preload sqlite pages: {err}"));
214			let initial_pages = observe_open_phase_result(
215				&metrics,
216				SqliteOpenPhase::InitialPreload,
217				preload_start,
218				preload_result,
219			)?;
220			record_startup_preload_pages(
221				&metrics,
222				u64::from(initial_pages.requested_page_count),
223				initial_pages.pages.len() as u64,
224			);
225			initial_pages
226		}
227		Preload::Empty => InitialPages::default(),
228	};
229
230	let vfs_register_start = Instant::now();
231	let vfs_result = SqliteVfs::register_with_transport_and_initial_pages(
232		&vfs_name,
233		transport,
234		actor_id.clone(),
235		rt_handle,
236		config,
237		initial_pages,
238		metrics.clone(),
239	)
240	.map(Arc::new)
241	.map_err(|err| anyhow!("failed to register sqlite VFS: {err}"));
242	let vfs = observe_open_phase_result(
243		&metrics,
244		SqliteOpenPhase::VfsRegister,
245		vfs_register_start,
246		vfs_result,
247	)?;
248
249	let worker_ready_start = Instant::now();
250	let worker_ready_result: Result<NativeDatabaseHandle> = async {
251		let native_db = NativeDatabaseHandle {
252			worker: SqliteWorkerHandle::start_on_with_guard(
253				pool,
254				vfs.clone(),
255				actor_id,
256				metrics.clone(),
257				guard,
258			)?,
259			vfs,
260		};
261		native_db.initialize().await?;
262		Ok(native_db)
263	}
264	.await;
265	let native_db = observe_open_phase_result(
266		&metrics,
267		SqliteOpenPhase::WorkerReady,
268		worker_ready_start,
269		worker_ready_result,
270	)?;
271	open_timer.finish_success();
272	Ok(native_db)
273}
274
275/// Records the total SQLite open metric when the open attempt leaves scope.
276///
277/// Individual open phases record their own durations. This guard owns the total
278/// duration so early returns cannot forget the total error metric.
279struct SqliteOpenTimer {
280	metrics: Option<Arc<dyn SqliteVfsMetrics>>,
281	started_at: Instant,
282	finished: bool,
283}
284
285impl SqliteOpenTimer {
286	fn new(metrics: &Option<Arc<dyn SqliteVfsMetrics>>) -> Self {
287		Self {
288			metrics: metrics.clone(),
289			started_at: Instant::now(),
290			finished: false,
291		}
292	}
293
294	fn finish_success(mut self) {
295		observe_open_phase(
296			&self.metrics,
297			SqliteOpenPhase::Total,
298			"success",
299			self.started_at,
300		);
301		self.finished = true;
302	}
303}
304
305impl Drop for SqliteOpenTimer {
306	fn drop(&mut self) {
307		if self.finished {
308			return;
309		}
310
311		observe_open_phase(
312			&self.metrics,
313			SqliteOpenPhase::Total,
314			"error",
315			self.started_at,
316		);
317	}
318}
319
320fn observe_open_phase_result<T, E>(
321	metrics: &Option<Arc<dyn SqliteVfsMetrics>>,
322	phase: SqliteOpenPhase,
323	start: Instant,
324	result: std::result::Result<T, E>,
325) -> std::result::Result<T, E> {
326	let outcome = if result.is_ok() { "success" } else { "error" };
327	observe_open_phase(metrics, phase, outcome, start);
328	result
329}
330
331fn observe_open_phase(
332	metrics: &Option<Arc<dyn SqliteVfsMetrics>>,
333	phase: SqliteOpenPhase,
334	outcome: &'static str,
335	start: Instant,
336) {
337	if let Some(metrics) = metrics {
338		metrics.observe_open_phase(phase, outcome, start.elapsed().as_nanos() as u64);
339	}
340}
341
342fn record_startup_preload_pages(
343	metrics: &Option<Arc<dyn SqliteVfsMetrics>>,
344	requested_pages: u64,
345	loaded_pages: u64,
346) {
347	if let Some(metrics) = metrics {
348		if requested_pages > 0 {
349			metrics.record_startup_preload_pages("requested", requested_pages);
350		}
351		if loaded_pages > 0 {
352			metrics.record_startup_preload_pages("loaded", loaded_pages);
353		}
354	}
355}
356
357impl NativeDatabaseHandle {
358	pub fn new(vfs: NativeVfsHandle, file_name: String) -> Result<Self> {
359		Self::new_with_metrics(vfs, file_name, None)
360	}
361
362	/// Runs the database on a dedicated single-thread pool.
363	pub fn new_with_metrics(
364		vfs: NativeVfsHandle,
365		file_name: String,
366		metrics: Option<Arc<dyn SqliteVfsMetrics>>,
367	) -> Result<Self> {
368		Ok(Self {
369			worker: SqliteWorkerHandle::start(vfs.clone(), file_name, metrics)?,
370			vfs,
371		})
372	}
373
374	/// Runs the database on a shared worker pool.
375	pub fn new_on_pool(
376		pool: &SqlitePool,
377		vfs: NativeVfsHandle,
378		file_name: String,
379		metrics: Option<Arc<dyn SqliteVfsMetrics>>,
380	) -> Result<Self> {
381		Ok(Self {
382			worker: SqliteWorkerHandle::start_on(pool, vfs.clone(), file_name, metrics)?,
383			vfs,
384		})
385	}
386
387	pub async fn exec(&self, sql: String) -> Result<QueryResult> {
388		self.check_fatal_error()?;
389		self.map_worker_result(self.worker.exec(sql).await)
390	}
391
392	pub async fn exec_profiled(&self, sql: String) -> Result<SqliteWorkerResult<QueryResult>> {
393		self.check_fatal_error()?;
394		self.map_worker_result(self.worker.exec_profiled(sql).await)
395	}
396
397	/// Holds every later commit in memory until `uncork_commits` sends them to depot as one
398	/// commit. None of the held transactions is durable until uncorking succeeds.
399	pub async fn cork_commits(&self) -> Result<()> {
400		self.check_fatal_error()?;
401		self.map_worker_result(self.worker.cork_commits().await)
402	}
403
404	/// Sends the transactions committed since `cork_commits` to depot as one commit. Inside an
405	/// explicit transaction it waits until the transaction ends, so the commit includes it.
406	pub async fn uncork_commits(&self) -> Result<()> {
407		self.check_fatal_error()?;
408		self.map_worker_result(self.worker.uncork_commits().await)
409	}
410
411	pub async fn query(&self, sql: String, params: Option<Vec<BindParam>>) -> Result<QueryResult> {
412		self.execute(sql, params).await.map(|result| QueryResult {
413			columns: result.columns,
414			rows: result.rows,
415		})
416	}
417
418	pub async fn run(&self, sql: String, params: Option<Vec<BindParam>>) -> Result<ExecResult> {
419		self.execute(sql, params).await.map(|result| ExecResult {
420			changes: result.changes,
421		})
422	}
423
424	pub async fn execute(
425		&self,
426		sql: String,
427		params: Option<Vec<BindParam>>,
428	) -> Result<ExecuteResult> {
429		self.check_fatal_error()?;
430		self.map_worker_result(self.worker.execute(sql, params).await)
431	}
432
433	pub async fn execute_profiled(
434		&self,
435		sql: String,
436		params: Option<Vec<BindParam>>,
437	) -> Result<SqliteWorkerResult<ExecuteResult>> {
438		self.check_fatal_error()?;
439		self.map_worker_result(self.worker.execute_profiled(sql, params).await)
440	}
441
442	/// Marks this database's later work as background work; see
443	/// [`SqliteWorkerHandle::set_background`].
444	pub fn set_background(&self, background: bool) {
445		self.worker.set_background(background);
446	}
447
448	/// Runs `statements` in order as one worker command; see
449	/// [`SqliteWorkerHandle::execute_batch`].
450	pub async fn execute_batch(
451		&self,
452		statements: Vec<(String, Option<Vec<BindParam>>)>,
453	) -> Result<Vec<ExecuteResult>> {
454		self.check_fatal_error()?;
455		self.map_worker_result(self.worker.execute_batch(statements).await)
456	}
457
458	pub async fn close(&self) -> Result<()> {
459		self.map_worker_close_result(self.worker.close().await)
460	}
461
462	/// Closes the database without returning while its worker still owns SQLite.
463	/// Any returned error is reported only after the worker thread has terminated.
464	pub async fn close_and_wait(&self) -> Result<()> {
465		self.map_worker_close_result(self.worker.close_and_wait().await)
466	}
467
468	fn map_worker_close_result(&self, result: Result<()>) -> Result<()> {
469		match result {
470			Ok(()) => Ok(()),
471			// The caller uses this concrete outcome to retain the native handle for
472			// a later strict join. Never hide it behind a previously recorded VFS
473			// failure while the worker may still own SQLite.
474			Err(error)
475				if error
476					.downcast_ref::<SqliteWorkerCloseTimeoutError>()
477					.is_some() =>
478			{
479				Err(error)
480			}
481			Err(error) => Err(self.fatal_error().unwrap_or(error)),
482		}
483	}
484
485	pub async fn wait_for_worker_failure(&self) -> bool {
486		self.worker.wait_for_failure().await
487	}
488
489	/// Returns whether the connection has an open explicit transaction, such as
490	/// one started by a manual `BEGIN`, as of the last completed command.
491	pub fn explicit_transaction_open(&self) -> bool {
492		self.worker.explicit_transaction_open()
493	}
494
495	/// Subscribes to explicit transaction state changes. The value changes
496	/// before the reply of the command that changed it is delivered.
497	pub fn subscribe_explicit_transaction(&self) -> watch::Receiver<bool> {
498		self.worker.subscribe_explicit_transaction()
499	}
500
501	pub fn take_last_kv_error(&self) -> Option<String> {
502		self.vfs.take_last_error()
503	}
504
505	pub fn clone_fatal_error(&self) -> Option<String> {
506		self.vfs.clone_fatal_error()
507	}
508
509	/// Reads back a file the connection wrote through its own VFS, such as a `VACUUM INTO` output.
510	pub fn read_vfs_file(&self, path: &str) -> Option<Vec<u8>> {
511		self.vfs.read_aux_file(path)
512	}
513
514	/// Drops a file the connection wrote through its own VFS, releasing the memory holding it.
515	pub fn delete_vfs_file(&self, path: &str) {
516		self.vfs.delete_aux_file(path);
517	}
518
519	pub fn sqlite_vfs_metrics(&self) -> SqliteVfsMetricsSnapshot {
520		self.vfs.sqlite_vfs_metrics()
521	}
522
523	
524
525	
526
527	
528
529	
530
531	async fn initialize(&self) -> Result<()> {
532		self.map_worker_result(self.worker.wait_ready().await)
533	}
534
535	fn check_fatal_error(&self) -> Result<()> {
536		if let Some(error) = self.fatal_error() {
537			return Err(error);
538		}
539
540		Ok(())
541	}
542
543	fn map_worker_result<T>(&self, result: Result<T>) -> Result<T> {
544		match result {
545			Ok(value) => {
546				self.check_fatal_error()?;
547				Ok(value)
548			}
549			Err(error) if error.is::<crate::vfs::SqliteCommitIndeterminateError>() => Err(error),
550			Err(error)
551				if error.is::<crate::vfs::SqliteTransactionTooLargeError>()
552					&& self.fatal_error().is_none() =>
553			{
554				Err(error)
555			}
556			Err(error) => Err(self.fatal_error().unwrap_or(error)),
557		}
558	}
559
560	fn fatal_error(&self) -> Option<anyhow::Error> {
561		self.clone_fatal_error()
562			.map(|message| SqliteWorkerFatalError::new(message).into())
563	}
564}
565
566