Skip to main content

sqlite_embedded/
query.rs

1use std::error::Error;
2use std::ffi::{CStr, CString};
3use std::fmt;
4use std::os::raw::c_char;
5use std::ptr;
6
7use anyhow::{Result, anyhow};
8use libsqlite3_sys::{
9	SQLITE_BLOB, SQLITE_DONE, SQLITE_FLOAT, SQLITE_INTEGER, SQLITE_NULL, SQLITE_OK, SQLITE_ROW,
10	SQLITE_TEXT, SQLITE_TRANSIENT, sqlite3, sqlite3_bind_blob, sqlite3_bind_double,
11	sqlite3_bind_int64, sqlite3_bind_null, sqlite3_bind_text, sqlite3_changes, sqlite3_column_blob,
12	sqlite3_column_bytes, sqlite3_column_count, sqlite3_column_double, sqlite3_column_int64,
13	sqlite3_column_name, sqlite3_column_text, sqlite3_column_type, sqlite3_errmsg,
14	sqlite3_extended_errcode, sqlite3_file_control, sqlite3_finalize, sqlite3_last_insert_rowid,
15	sqlite3_prepare_v2, sqlite3_sql, sqlite3_step, sqlite3_stmt,
16};
17pub use sqlite_types::{BindParam, ColumnValue, ExecResult, ExecuteResult, QueryResult};
18
19use crate::vfs::{FCNTL_COMMIT_PAGE_CAP, SqliteTransactionTooLargeError};
20
21#[derive(Clone, Copy, Debug, Default)]
22pub struct SqliteQueryProfile {
23	pub bind_count: u64,
24	pub bind_logical_bytes: u64,
25	pub result_rows: u64,
26	pub result_columns: u64,
27	pub result_logical_bytes: u64,
28}
29
30#[derive(Debug)]
31pub struct SqliteStatementError {
32	pub code: i32,
33	pub statement_index: u32,
34	pub message: String,
35}
36
37impl fmt::Display for SqliteStatementError {
38	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
39		f.write_str(&self.message)
40	}
41}
42
43impl Error for SqliteStatementError {}
44
45/// Logical bytes every result value costs before its text or blob bytes.
46const RESULT_VALUE_BYTES: u64 = 8;
47
48/// A statement's result grew past the command's result byte cap. The statement is stopped
49/// before the value that crossed the cap is copied out of SQLite.
50#[derive(Debug)]
51pub struct SqliteResultTooLargeError {
52	pub max_bytes: u64,
53}
54
55impl fmt::Display for SqliteResultTooLargeError {
56	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
57		write!(
58			f,
59			"sqlite response too large: the result passes the {} byte limit",
60			self.max_bytes
61		)
62	}
63}
64
65impl Error for SqliteResultTooLargeError {}
66
67/// Charges one statement's result values against an optional logical byte cap while its rows
68/// are produced. Each value costs 8 bytes plus its text or blob length, so the result is
69/// refused before its bytes are copied rather than after the whole result is built.
70struct ResultBudget {
71	max_bytes: Option<u64>,
72	used_bytes: u64,
73}
74
75impl ResultBudget {
76	fn new(max_bytes: Option<u64>) -> Self {
77		Self {
78			max_bytes,
79			used_bytes: 0,
80		}
81	}
82
83	/// Charges the value in column `index` of the current row and returns the value bytes it
84	/// charged. Call it before the value is read with `sqlite3_column_text` or
85	/// `sqlite3_column_blob`.
86	fn charge(&mut self, stmt: *mut sqlite3_stmt, index: i32) -> Result<u64> {
87		if self.max_bytes.is_none() {
88			return Ok(0);
89		}
90
91		let value_bytes = match unsafe { sqlite3_column_type(stmt, index) } {
92			SQLITE_TEXT | SQLITE_BLOB => unsafe { sqlite3_column_bytes(stmt, index) }.max(0) as u64,
93			_ => 0,
94		};
95		self.add(RESULT_VALUE_BYTES.saturating_add(value_bytes))?;
96		Ok(value_bytes)
97	}
98
99	/// Charges the bytes a text value grew by when invalid UTF-8 was replaced while copying
100	/// it, so the cap bounds the copied result and not only SQLite's bytes.
101	fn charge_conversion(&mut self, charged_bytes: u64, value: &ColumnValue) -> Result<()> {
102		if let ColumnValue::Text(text) = value {
103			let text_bytes = text.len() as u64;
104			if text_bytes > charged_bytes {
105				self.add(text_bytes - charged_bytes)?;
106			}
107		}
108		Ok(())
109	}
110
111	fn add(&mut self, bytes: u64) -> Result<()> {
112		let Some(max_bytes) = self.max_bytes else {
113			return Ok(());
114		};
115
116		self.used_bytes = self.used_bytes.saturating_add(bytes);
117		if self.used_bytes > max_bytes {
118			return Err(SqliteResultTooLargeError { max_bytes }.into());
119		}
120
121		Ok(())
122	}
123}
124
125/// Reads the current row, charging every value to `budget` before copying it.
126fn read_row(
127	stmt: *mut sqlite3_stmt,
128	column_count: usize,
129	budget: &mut ResultBudget,
130) -> Result<Vec<ColumnValue>> {
131	let mut row = Vec::with_capacity(column_count);
132	for index in 0..column_count {
133		let charged_bytes = budget.charge(stmt, index as i32)?;
134		let value = column_value(stmt, index as i32);
135		budget.charge_conversion(charged_bytes, &value)?;
136		row.push(value);
137	}
138	Ok(row)
139}
140
141/// Like [`read_row`], also counting the row into `profile`.
142fn read_row_profiled(
143	stmt: *mut sqlite3_stmt,
144	column_count: usize,
145	budget: &mut ResultBudget,
146	profile: &mut SqliteQueryProfile,
147) -> Result<Vec<ColumnValue>> {
148	let mut row = Vec::with_capacity(column_count);
149	for index in 0..column_count {
150		let charged_bytes = budget.charge(stmt, index as i32)?;
151		let value = column_value_profiled(stmt, index as i32, profile);
152		budget.charge_conversion(charged_bytes, &value)?;
153		row.push(value);
154	}
155	Ok(row)
156}
157
158pub fn execute_statement(
159	db: *mut sqlite3,
160	sql: &str,
161	params: Option<&[BindParam]>,
162) -> Result<ExecResult> {
163	let c_sql = CString::new(sql).map_err(|err| anyhow!(err.to_string()))?;
164	let mut stmt = ptr::null_mut();
165	let rc = unsafe { sqlite3_prepare_v2(db, c_sql.as_ptr(), -1, &mut stmt, ptr::null_mut()) };
166	if rc != SQLITE_OK {
167		return Err(sqlite_error(db, 0, "failed to prepare sqlite statement"));
168	}
169	if stmt.is_null() {
170		return Ok(ExecResult { changes: 0 });
171	}
172
173	let result = (|| {
174		refuse_oversize_vacuum(db, stmt)?;
175		if let Some(params) = params {
176			bind_params(db, stmt, params)?;
177		}
178
179		loop {
180			let step_rc = unsafe { sqlite3_step(stmt) };
181			if step_rc == SQLITE_DONE {
182				break;
183			}
184			if step_rc != SQLITE_ROW {
185				return Err(sqlite_error(db, 0, "failed to execute sqlite statement"));
186			}
187		}
188
189		Ok(ExecResult {
190			changes: unsafe { sqlite3_changes(db) as i64 },
191		})
192	})();
193
194	unsafe {
195		sqlite3_finalize(stmt);
196	}
197
198	result
199}
200
201pub fn query_statement(
202	db: *mut sqlite3,
203	sql: &str,
204	params: Option<&[BindParam]>,
205) -> Result<QueryResult> {
206	let c_sql = CString::new(sql).map_err(|err| anyhow!(err.to_string()))?;
207	let mut stmt = ptr::null_mut();
208	let rc = unsafe { sqlite3_prepare_v2(db, c_sql.as_ptr(), -1, &mut stmt, ptr::null_mut()) };
209	if rc != SQLITE_OK {
210		return Err(sqlite_error(db, 0, "failed to prepare sqlite query"));
211	}
212	if stmt.is_null() {
213		return Ok(QueryResult {
214			columns: Vec::new(),
215			rows: Vec::new(),
216		});
217	}
218
219	let result = (|| {
220		refuse_oversize_vacuum(db, stmt)?;
221		if let Some(params) = params {
222			bind_params(db, stmt, params)?;
223		}
224
225		let columns = collect_columns(stmt);
226		let mut rows = Vec::new();
227
228		loop {
229			let step_rc = unsafe { sqlite3_step(stmt) };
230			if step_rc == SQLITE_DONE {
231				break;
232			}
233			if step_rc != SQLITE_ROW {
234				return Err(sqlite_error(db, 0, "failed to step sqlite query"));
235			}
236
237			let mut row = Vec::with_capacity(columns.len());
238			for index in 0..columns.len() {
239				row.push(column_value(stmt, index as i32));
240			}
241			rows.push(row);
242		}
243
244		Ok(QueryResult { columns, rows })
245	})();
246
247	unsafe {
248		sqlite3_finalize(stmt);
249	}
250
251	result
252}
253
254/// Runs one statement and collects its rows. With `max_result_bytes`, the statement stops with
255/// [`SqliteResultTooLargeError`] as soon as its result passes that many logical bytes.
256pub fn execute_single_statement(
257	db: *mut sqlite3,
258	sql: &str,
259	params: Option<&[BindParam]>,
260	max_result_bytes: Option<u64>,
261) -> Result<ExecuteResult> {
262	let c_sql = CString::new(sql).map_err(|err| anyhow!(err.to_string()))?;
263	let mut stmt = ptr::null_mut();
264	let mut tail = ptr::null();
265	let rc = unsafe { sqlite3_prepare_v2(db, c_sql.as_ptr(), -1, &mut stmt, &mut tail) };
266	if rc != SQLITE_OK {
267		return Err(sqlite_error(
268			db,
269			0,
270			"failed to prepare sqlite execute statement",
271		));
272	}
273	if has_non_whitespace_tail(tail) {
274		if !stmt.is_null() {
275			unsafe {
276				sqlite3_finalize(stmt);
277			}
278		}
279		return Err(SqliteStatementError {
280			code: -1,
281			statement_index: 0,
282			message: "sqlite execute only supports a single statement".to_owned(),
283		}
284		.into());
285	}
286	if stmt.is_null() {
287		return Ok(ExecuteResult {
288			columns: Vec::new(),
289			rows: Vec::new(),
290			changes: 0,
291			last_insert_row_id: None,
292		});
293	}
294
295	let result = (|| {
296		refuse_oversize_vacuum(db, stmt)?;
297		if let Some(params) = params {
298			bind_params(db, stmt, params)?;
299		}
300
301		let columns = collect_columns(stmt);
302		let mut rows = Vec::new();
303		let mut budget = ResultBudget::new(max_result_bytes);
304		loop {
305			let step_rc = unsafe { sqlite3_step(stmt) };
306			if step_rc == SQLITE_DONE {
307				break;
308			}
309			if step_rc != SQLITE_ROW {
310				return Err(sqlite_error(
311					db,
312					0,
313					"failed to step sqlite execute statement",
314				));
315			}
316
317			rows.push(read_row(stmt, columns.len(), &mut budget)?);
318		}
319
320		let changes = unsafe { sqlite3_changes(db) as i64 };
321		Ok(ExecuteResult {
322			columns,
323			rows,
324			changes,
325			last_insert_row_id: (changes > 0).then(|| unsafe { sqlite3_last_insert_rowid(db) }),
326		})
327	})();
328
329	unsafe {
330		sqlite3_finalize(stmt);
331	}
332
333	result
334}
335
336/// Like [`execute_single_statement`], also returning the statement's profile.
337pub fn execute_single_statement_profiled(
338	db: *mut sqlite3,
339	sql: &str,
340	params: Option<&[BindParam]>,
341	max_result_bytes: Option<u64>,
342) -> Result<(ExecuteResult, SqliteQueryProfile)> {
343	let c_sql = CString::new(sql).map_err(|err| anyhow!(err.to_string()))?;
344	let mut stmt = ptr::null_mut();
345	let mut tail = ptr::null();
346	let rc = unsafe { sqlite3_prepare_v2(db, c_sql.as_ptr(), -1, &mut stmt, &mut tail) };
347	if rc != SQLITE_OK {
348		return Err(sqlite_error(
349			db,
350			0,
351			"failed to prepare sqlite execute statement",
352		));
353	}
354	if has_non_whitespace_tail(tail) {
355		if !stmt.is_null() {
356			unsafe {
357				sqlite3_finalize(stmt);
358			}
359		}
360		return Err(SqliteStatementError {
361			code: -1,
362			statement_index: 0,
363			message: "sqlite execute only supports a single statement".to_owned(),
364		}
365		.into());
366	}
367	if stmt.is_null() {
368		return Ok((
369			ExecuteResult {
370				columns: Vec::new(),
371				rows: Vec::new(),
372				changes: 0,
373				last_insert_row_id: None,
374			},
375			SqliteQueryProfile::default(),
376		));
377	}
378
379	let result = (|| {
380		refuse_oversize_vacuum(db, stmt)?;
381		let mut profile = SqliteQueryProfile::default();
382		if let Some(params) = params {
383			bind_params_profiled(db, stmt, params, &mut profile)?;
384		}
385
386		let columns = collect_columns(stmt);
387		profile.result_columns = columns.len() as u64;
388		let mut rows = Vec::new();
389		let mut budget = ResultBudget::new(max_result_bytes);
390		loop {
391			let step_rc = unsafe { sqlite3_step(stmt) };
392			if step_rc == SQLITE_DONE {
393				break;
394			}
395			if step_rc != SQLITE_ROW {
396				return Err(sqlite_error(
397					db,
398					0,
399					"failed to step sqlite execute statement",
400				));
401			}
402
403			rows.push(read_row_profiled(
404				stmt,
405				columns.len(),
406				&mut budget,
407				&mut profile,
408			)?);
409			profile.result_rows = profile.result_rows.saturating_add(1);
410		}
411
412		let changes = unsafe { sqlite3_changes(db) as i64 };
413		Ok((
414			ExecuteResult {
415				columns,
416				rows,
417				changes,
418				last_insert_row_id: (changes > 0).then(|| unsafe { sqlite3_last_insert_rowid(db) }),
419			},
420			profile,
421		))
422	})();
423
424	unsafe {
425		sqlite3_finalize(stmt);
426	}
427
428	result
429}
430
431/// Runs every statement in `sql` and returns the rows of the last one that has columns or
432/// rows. With `max_result_bytes`, each statement's result is capped at that many logical bytes.
433pub fn exec_statements(
434	db: *mut sqlite3,
435	sql: &str,
436	max_result_bytes: Option<u64>,
437) -> Result<QueryResult> {
438	let c_sql = CString::new(sql).map_err(|err| anyhow!(err.to_string()))?;
439	let mut remaining = c_sql.as_ptr();
440	let mut statement_index = 0_u32;
441	let mut final_result = QueryResult {
442		columns: Vec::new(),
443		rows: Vec::new(),
444	};
445
446	while unsafe { *remaining } != 0 {
447		let mut stmt = ptr::null_mut();
448		let mut tail = ptr::null();
449		let rc = unsafe { sqlite3_prepare_v2(db, remaining, -1, &mut stmt, &mut tail) };
450		if rc != SQLITE_OK {
451			return Err(sqlite_error(
452				db,
453				statement_index,
454				"failed to prepare sqlite exec statement",
455			));
456		}
457
458		if stmt.is_null() {
459			if tail == remaining {
460				break;
461			}
462			remaining = tail;
463			continue;
464		}
465
466		// A statement with columns replaces the earlier result, so release that result before
467		// this statement's rows are collected and only one result is held at a time.
468		if unsafe { sqlite3_column_count(stmt) } > 0 {
469			final_result = QueryResult {
470				columns: Vec::new(),
471				rows: Vec::new(),
472			};
473		}
474
475		let result = (|| {
476			refuse_oversize_vacuum(db, stmt)?;
477			let columns = collect_columns(stmt);
478			let mut rows = Vec::new();
479			let mut budget = ResultBudget::new(max_result_bytes);
480			loop {
481				let step_rc = unsafe { sqlite3_step(stmt) };
482				if step_rc == SQLITE_DONE {
483					break;
484				}
485				if step_rc != SQLITE_ROW {
486					return Err(sqlite_error(
487						db,
488						statement_index,
489						"failed to step sqlite exec statement",
490					));
491				}
492
493				rows.push(read_row(stmt, columns.len(), &mut budget)?);
494			}
495
496			Ok((columns, rows))
497		})();
498
499		unsafe {
500			sqlite3_finalize(stmt);
501		}
502
503		let (columns, rows) = result?;
504		if !columns.is_empty() || !rows.is_empty() {
505			final_result = QueryResult { columns, rows };
506		}
507
508		if tail == remaining {
509			break;
510		}
511		remaining = tail;
512		statement_index = statement_index.saturating_add(1);
513	}
514
515	Ok(final_result)
516}
517
518/// Like [`exec_statements`], also returning the profile of the returned statement.
519pub fn exec_statements_profiled(
520	db: *mut sqlite3,
521	sql: &str,
522	max_result_bytes: Option<u64>,
523) -> Result<(QueryResult, SqliteQueryProfile)> {
524	let c_sql = CString::new(sql).map_err(|err| anyhow!(err.to_string()))?;
525	let mut remaining = c_sql.as_ptr();
526	let mut statement_index = 0_u32;
527	let mut final_result = QueryResult {
528		columns: Vec::new(),
529		rows: Vec::new(),
530	};
531	let mut final_profile = SqliteQueryProfile::default();
532
533	while unsafe { *remaining } != 0 {
534		let mut stmt = ptr::null_mut();
535		let mut tail = ptr::null();
536		let rc = unsafe { sqlite3_prepare_v2(db, remaining, -1, &mut stmt, &mut tail) };
537		if rc != SQLITE_OK {
538			return Err(sqlite_error(
539				db,
540				statement_index,
541				"failed to prepare sqlite exec statement",
542			));
543		}
544
545		if stmt.is_null() {
546			if tail == remaining {
547				break;
548			}
549			remaining = tail;
550			continue;
551		}
552
553		// A statement with columns replaces the earlier result, so release that result before
554		// this statement's rows are collected and only one result is held at a time.
555		if unsafe { sqlite3_column_count(stmt) } > 0 {
556			final_result = QueryResult {
557				columns: Vec::new(),
558				rows: Vec::new(),
559			};
560		}
561
562		let result = (|| {
563			refuse_oversize_vacuum(db, stmt)?;
564			let columns = collect_columns(stmt);
565			let mut profile = SqliteQueryProfile {
566				result_columns: columns.len() as u64,
567				..Default::default()
568			};
569			let mut rows = Vec::new();
570			let mut budget = ResultBudget::new(max_result_bytes);
571			loop {
572				let step_rc = unsafe { sqlite3_step(stmt) };
573				if step_rc == SQLITE_DONE {
574					break;
575				}
576				if step_rc != SQLITE_ROW {
577					return Err(sqlite_error(
578						db,
579						statement_index,
580						"failed to step sqlite exec statement",
581					));
582				}
583
584				rows.push(read_row_profiled(
585					stmt,
586					columns.len(),
587					&mut budget,
588					&mut profile,
589				)?);
590				profile.result_rows = profile.result_rows.saturating_add(1);
591			}
592
593			Ok((columns, rows, profile))
594		})();
595
596		unsafe {
597			sqlite3_finalize(stmt);
598		}
599
600		let (columns, rows, profile) = result?;
601		if !columns.is_empty() || !rows.is_empty() {
602			final_result = QueryResult { columns, rows };
603			final_profile = profile;
604		}
605
606		if tail == remaining {
607			break;
608		}
609		remaining = tail;
610		statement_index = statement_index.saturating_add(1);
611	}
612
613	Ok((final_result, final_profile))
614}
615
616fn bind_params(
617	db: *mut sqlite3,
618	stmt: *mut libsqlite3_sys::sqlite3_stmt,
619	params: &[BindParam],
620) -> Result<()> {
621	for (index, param) in params.iter().enumerate() {
622		bind_param(db, stmt, (index + 1) as i32, param)?;
623	}
624
625	Ok(())
626}
627
628fn bind_params_profiled(
629	db: *mut sqlite3,
630	stmt: *mut libsqlite3_sys::sqlite3_stmt,
631	params: &[BindParam],
632	profile: &mut SqliteQueryProfile,
633) -> Result<()> {
634	for (index, param) in params.iter().enumerate() {
635		profile.bind_count = profile.bind_count.saturating_add(1);
636		profile.bind_logical_bytes = profile
637			.bind_logical_bytes
638			.saturating_add(logical_bind_bytes(param));
639		bind_param(db, stmt, (index + 1) as i32, param)?;
640	}
641
642	Ok(())
643}
644
645fn bind_param(
646	db: *mut sqlite3,
647	stmt: *mut libsqlite3_sys::sqlite3_stmt,
648	bind_index: i32,
649	param: &BindParam,
650) -> Result<()> {
651	let rc = match param {
652		BindParam::Null => unsafe { sqlite3_bind_null(stmt, bind_index) },
653		BindParam::Integer(value) => unsafe { sqlite3_bind_int64(stmt, bind_index, *value) },
654		BindParam::Float(value) => unsafe { sqlite3_bind_double(stmt, bind_index, *value) },
655		BindParam::Text(value) => unsafe {
656			sqlite3_bind_text(
657				stmt,
658				bind_index,
659				value.as_ptr() as *const c_char,
660				value.len() as i32,
661				SQLITE_TRANSIENT(),
662			)
663		},
664		BindParam::Blob(value) => unsafe {
665			sqlite3_bind_blob(
666				stmt,
667				bind_index,
668				value.as_ptr() as *const _,
669				value.len() as i32,
670				SQLITE_TRANSIENT(),
671			)
672		},
673	};
674
675	if rc != SQLITE_OK {
676		return Err(sqlite_error(db, 0, "failed to bind sqlite parameter"));
677	}
678
679	Ok(())
680}
681
682fn collect_columns(stmt: *mut libsqlite3_sys::sqlite3_stmt) -> Vec<String> {
683	let column_count = unsafe { sqlite3_column_count(stmt) };
684	(0..column_count)
685		.map(|index| unsafe {
686			let name_ptr = sqlite3_column_name(stmt, index);
687			if name_ptr.is_null() {
688				String::new()
689			} else {
690				CStr::from_ptr(name_ptr).to_string_lossy().into_owned()
691			}
692		})
693		.collect()
694}
695
696fn column_value(stmt: *mut libsqlite3_sys::sqlite3_stmt, index: i32) -> ColumnValue {
697	match unsafe { sqlite3_column_type(stmt, index) } {
698		SQLITE_NULL => ColumnValue::Null,
699		SQLITE_INTEGER => ColumnValue::Integer(unsafe { sqlite3_column_int64(stmt, index) }),
700		SQLITE_FLOAT => ColumnValue::Float(unsafe { sqlite3_column_double(stmt, index) }),
701		SQLITE_TEXT => {
702			let text_ptr = unsafe { sqlite3_column_text(stmt, index) };
703			if text_ptr.is_null() {
704				ColumnValue::Null
705			} else {
706				let text_len = unsafe { sqlite3_column_bytes(stmt, index) } as usize;
707				let text = String::from_utf8_lossy(unsafe {
708					std::slice::from_raw_parts(text_ptr as *const u8, text_len)
709				})
710				.into_owned();
711				ColumnValue::Text(text)
712			}
713		}
714		SQLITE_BLOB => {
715			let blob_ptr = unsafe { sqlite3_column_blob(stmt, index) };
716			let blob_len = unsafe { sqlite3_column_bytes(stmt, index) } as usize;
717			// SQLite returns a null pointer for an empty BLOB, distinct from SQL NULL.
718			// Do not construct a slice from that pointer, even with length zero.
719			if blob_len == 0 {
720				ColumnValue::Blob(Vec::new())
721			} else if blob_ptr.is_null() {
722				ColumnValue::Null
723			} else {
724				let blob = unsafe { std::slice::from_raw_parts(blob_ptr as *const u8, blob_len) };
725				ColumnValue::Blob(blob.to_vec())
726			}
727		}
728		_ => ColumnValue::Null,
729	}
730}
731
732fn column_value_profiled(
733	stmt: *mut libsqlite3_sys::sqlite3_stmt,
734	index: i32,
735	profile: &mut SqliteQueryProfile,
736) -> ColumnValue {
737	let mut logical_bytes = 0_u64;
738	let value = match unsafe { sqlite3_column_type(stmt, index) } {
739		SQLITE_NULL => ColumnValue::Null,
740		SQLITE_INTEGER => {
741			logical_bytes = 8;
742			ColumnValue::Integer(unsafe { sqlite3_column_int64(stmt, index) })
743		}
744		SQLITE_FLOAT => {
745			logical_bytes = 8;
746			ColumnValue::Float(unsafe { sqlite3_column_double(stmt, index) })
747		}
748		SQLITE_TEXT => {
749			let text_ptr = unsafe { sqlite3_column_text(stmt, index) };
750			if text_ptr.is_null() {
751				ColumnValue::Null
752			} else {
753				let text_len = unsafe { sqlite3_column_bytes(stmt, index) } as usize;
754				logical_bytes = text_len as u64;
755				let text = String::from_utf8_lossy(unsafe {
756					std::slice::from_raw_parts(text_ptr as *const u8, text_len)
757				})
758				.into_owned();
759				ColumnValue::Text(text)
760			}
761		}
762		SQLITE_BLOB => {
763			let blob_ptr = unsafe { sqlite3_column_blob(stmt, index) };
764			let blob_len = unsafe { sqlite3_column_bytes(stmt, index) } as usize;
765			// SQLite returns a null pointer for an empty BLOB, distinct from SQL NULL.
766			// Do not construct a slice from that pointer, even with length zero.
767			if blob_len == 0 {
768				ColumnValue::Blob(Vec::new())
769			} else if blob_ptr.is_null() {
770				ColumnValue::Null
771			} else {
772				logical_bytes = blob_len as u64;
773				let blob = unsafe { std::slice::from_raw_parts(blob_ptr as *const u8, blob_len) };
774				ColumnValue::Blob(blob.to_vec())
775			}
776		}
777		_ => ColumnValue::Null,
778	};
779	profile.result_logical_bytes = profile.result_logical_bytes.saturating_add(logical_bytes);
780	value
781}
782
783fn logical_bind_bytes(param: &BindParam) -> u64 {
784	match param {
785		BindParam::Null => 0,
786		BindParam::Integer(_) | BindParam::Float(_) => 8,
787		BindParam::Text(value) => value.len() as u64,
788		BindParam::Blob(value) => value.len() as u64,
789	}
790}
791
792/// Refuses an in-place `VACUUM` before it runs when the database holds more in-use pages than
793/// one commit may carry.
794///
795/// VACUUM rewrites the whole database in one transaction. Without this check it reads every
796/// page and builds the full copy in memory before the VFS can refuse the copy itself. In-use
797/// pages are an estimate of the copy's size: a sparsely filled database can compact below the
798/// cap and is still refused here, which keeps a large VACUUM from holding a whole copy in
799/// memory. Connections on other VFSes report no cap and are not checked.
800fn refuse_oversize_vacuum(db: *mut sqlite3, stmt: *mut sqlite3_stmt) -> Result<()> {
801	let sql = unsafe { sqlite3_sql(stmt) };
802	if sql.is_null() {
803		return Ok(());
804	}
805	let sql = unsafe { CStr::from_ptr(sql) }.to_string_lossy();
806	if !is_in_place_vacuum(&sql) {
807		return Ok(());
808	}
809
810	let mut max_pages: i64 = 0;
811	let rc = unsafe {
812		sqlite3_file_control(
813			db,
814			c"main".as_ptr(),
815			FCNTL_COMMIT_PAGE_CAP,
816			(&mut max_pages as *mut i64).cast(),
817		)
818	};
819	if rc != SQLITE_OK {
820		return Ok(());
821	}
822
823	let (Some(page_count), Some(freelist_count), Some(page_size)) = (
824		pragma_i64(db, c"PRAGMA main.page_count;"),
825		pragma_i64(db, c"PRAGMA main.freelist_count;"),
826		pragma_i64(db, c"PRAGMA main.page_size;"),
827	) else {
828		return Ok(());
829	};
830	let in_use_pages = page_count.saturating_sub(freelist_count);
831	if in_use_pages > max_pages {
832		return Err(SqliteTransactionTooLargeError {
833			max_pages: max_pages as usize,
834			page_size: page_size as usize,
835		}
836		.into());
837	}
838
839	Ok(())
840}
841
842/// Whether a statement is a `VACUUM` of the main database in place. `VACUUM INTO` writes a
843/// separate file and `VACUUM temp` another schema, so neither commits to the main database.
844pub(crate) fn is_in_place_vacuum(sql: &str) -> bool {
845	let words = sql_words(sql);
846	let mut words = words.iter();
847	if !words
848		.next()
849		.is_some_and(|word| word.eq_ignore_ascii_case("vacuum"))
850	{
851		return false;
852	}
853
854	match words.next() {
855		None => true,
856		Some(word) if word.eq_ignore_ascii_case("into") => false,
857		Some(schema) => {
858			let schema = schema.trim_matches(|c| matches!(c, '"' | '`' | '[' | ']' | '\''));
859			schema.eq_ignore_ascii_case("main")
860				&& !words.any(|word| word.eq_ignore_ascii_case("into"))
861		}
862	}
863}
864
865/// Splits SQL into whitespace or `;` separated words, dropping `--` and `/* */` comments.
866/// Quoted text is not interpreted, which is enough to read a statement's leading keywords.
867fn sql_words(sql: &str) -> Vec<&str> {
868	let mut words = Vec::new();
869	let mut rest = sql;
870	loop {
871		rest = rest.trim_start_matches(|c: char| c.is_ascii_whitespace() || c == ';');
872		if rest.is_empty() {
873			return words;
874		}
875		if let Some(comment) = rest.strip_prefix("--") {
876			rest = comment.find('\n').map_or("", |end| &comment[end + 1..]);
877			continue;
878		}
879		if let Some(comment) = rest.strip_prefix("/*") {
880			rest = comment.find("*/").map_or("", |end| &comment[end + 2..]);
881			continue;
882		}
883		let end = rest
884			.char_indices()
885			.find(|(index, c)| {
886				c.is_ascii_whitespace()
887					|| *c == ';' || rest[*index..].starts_with("--")
888					|| rest[*index..].starts_with("/*")
889			})
890			.map_or(rest.len(), |(index, _)| index);
891		words.push(&rest[..end]);
892		rest = &rest[end..];
893	}
894}
895
896/// Reads a single integer pragma, or `None` if it fails.
897fn pragma_i64(db: *mut sqlite3, sql: &CStr) -> Option<i64> {
898	let mut stmt = ptr::null_mut();
899	let rc = unsafe { sqlite3_prepare_v2(db, sql.as_ptr(), -1, &mut stmt, ptr::null_mut()) };
900	if rc != SQLITE_OK || stmt.is_null() {
901		return None;
902	}
903	let value = if unsafe { sqlite3_step(stmt) } == SQLITE_ROW {
904		Some(unsafe { sqlite3_column_int64(stmt, 0) })
905	} else {
906		None
907	};
908	unsafe {
909		sqlite3_finalize(stmt);
910	}
911	value
912}
913
914fn has_non_whitespace_tail(tail: *const c_char) -> bool {
915	if tail.is_null() {
916		return false;
917	}
918
919	let bytes = unsafe { CStr::from_ptr(tail).to_bytes() };
920	bytes.iter().any(|byte| !byte.is_ascii_whitespace())
921}
922
923fn sqlite_error(db: *mut sqlite3, statement_index: u32, context: &str) -> anyhow::Error {
924	let (code, detail) = unsafe {
925		if db.is_null() {
926			(-1, "unknown sqlite error".to_string())
927		} else {
928			(
929				sqlite3_extended_errcode(db),
930				CStr::from_ptr(sqlite3_errmsg(db))
931					.to_string_lossy()
932					.into_owned(),
933			)
934		}
935	};
936	SqliteStatementError {
937		code,
938		statement_index,
939		message: format!("{context}: {detail}"),
940	}
941	.into()
942}
943
944