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	let _stmt = FinalizeOnDrop(stmt);
167	if rc != SQLITE_OK {
168		return Err(sqlite_error(db, 0, "failed to prepare sqlite statement"));
169	}
170	if stmt.is_null() {
171		return Ok(ExecResult { changes: 0 });
172	}
173
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
194pub fn query_statement(
195	db: *mut sqlite3,
196	sql: &str,
197	params: Option<&[BindParam]>,
198) -> Result<QueryResult> {
199	let c_sql = CString::new(sql).map_err(|err| anyhow!(err.to_string()))?;
200	let mut stmt = ptr::null_mut();
201	let rc = unsafe { sqlite3_prepare_v2(db, c_sql.as_ptr(), -1, &mut stmt, ptr::null_mut()) };
202	let _stmt = FinalizeOnDrop(stmt);
203	if rc != SQLITE_OK {
204		return Err(sqlite_error(db, 0, "failed to prepare sqlite query"));
205	}
206	if stmt.is_null() {
207		return Ok(QueryResult {
208			columns: Vec::new(),
209			rows: Vec::new(),
210		});
211	}
212
213	refuse_oversize_vacuum(db, stmt)?;
214	if let Some(params) = params {
215		bind_params(db, stmt, params)?;
216	}
217
218	let columns = collect_columns(stmt);
219	let mut rows = Vec::new();
220
221	loop {
222		let step_rc = unsafe { sqlite3_step(stmt) };
223		if step_rc == SQLITE_DONE {
224			break;
225		}
226		if step_rc != SQLITE_ROW {
227			return Err(sqlite_error(db, 0, "failed to step sqlite query"));
228		}
229
230		let mut row = Vec::with_capacity(columns.len());
231		for index in 0..columns.len() {
232			row.push(column_value(stmt, index as i32));
233		}
234		rows.push(row);
235	}
236
237	Ok(QueryResult { columns, rows })
238}
239
240/// Runs one statement and collects its rows. With `max_result_bytes`, the statement stops with
241/// [`SqliteResultTooLargeError`] as soon as its result passes that many logical bytes.
242pub fn execute_single_statement(
243	db: *mut sqlite3,
244	sql: &str,
245	params: Option<&[BindParam]>,
246	max_result_bytes: Option<u64>,
247) -> Result<ExecuteResult> {
248	let c_sql = CString::new(sql).map_err(|err| anyhow!(err.to_string()))?;
249	let mut stmt = ptr::null_mut();
250	let mut tail = ptr::null();
251	let rc = unsafe { sqlite3_prepare_v2(db, c_sql.as_ptr(), -1, &mut stmt, &mut tail) };
252	let _stmt = FinalizeOnDrop(stmt);
253	if rc != SQLITE_OK {
254		return Err(sqlite_error(
255			db,
256			0,
257			"failed to prepare sqlite execute statement",
258		));
259	}
260	if has_non_whitespace_tail(tail) {
261		return Err(SqliteStatementError {
262			code: -1,
263			statement_index: 0,
264			message: "sqlite execute only supports a single statement".to_owned(),
265		}
266		.into());
267	}
268	if stmt.is_null() {
269		return Ok(ExecuteResult {
270			columns: Vec::new(),
271			rows: Vec::new(),
272			changes: 0,
273			last_insert_row_id: None,
274		});
275	}
276
277	refuse_oversize_vacuum(db, stmt)?;
278	if let Some(params) = params {
279		bind_params(db, stmt, params)?;
280	}
281
282	let columns = collect_columns(stmt);
283	let mut rows = Vec::new();
284	let mut budget = ResultBudget::new(max_result_bytes);
285	loop {
286		let step_rc = unsafe { sqlite3_step(stmt) };
287		if step_rc == SQLITE_DONE {
288			break;
289		}
290		if step_rc != SQLITE_ROW {
291			return Err(sqlite_error(
292				db,
293				0,
294				"failed to step sqlite execute statement",
295			));
296		}
297
298		rows.push(read_row(stmt, columns.len(), &mut budget)?);
299	}
300
301	let changes = unsafe { sqlite3_changes(db) as i64 };
302	Ok(ExecuteResult {
303		columns,
304		rows,
305		changes,
306		last_insert_row_id: (changes > 0).then(|| unsafe { sqlite3_last_insert_rowid(db) }),
307	})
308}
309
310/// Like [`execute_single_statement`], also returning the statement's profile.
311pub fn execute_single_statement_profiled(
312	db: *mut sqlite3,
313	sql: &str,
314	params: Option<&[BindParam]>,
315	max_result_bytes: Option<u64>,
316) -> Result<(ExecuteResult, SqliteQueryProfile)> {
317	let c_sql = CString::new(sql).map_err(|err| anyhow!(err.to_string()))?;
318	let mut stmt = ptr::null_mut();
319	let mut tail = ptr::null();
320	let rc = unsafe { sqlite3_prepare_v2(db, c_sql.as_ptr(), -1, &mut stmt, &mut tail) };
321	let _stmt = FinalizeOnDrop(stmt);
322	if rc != SQLITE_OK {
323		return Err(sqlite_error(
324			db,
325			0,
326			"failed to prepare sqlite execute statement",
327		));
328	}
329	if has_non_whitespace_tail(tail) {
330		return Err(SqliteStatementError {
331			code: -1,
332			statement_index: 0,
333			message: "sqlite execute only supports a single statement".to_owned(),
334		}
335		.into());
336	}
337	if stmt.is_null() {
338		return Ok((
339			ExecuteResult {
340				columns: Vec::new(),
341				rows: Vec::new(),
342				changes: 0,
343				last_insert_row_id: None,
344			},
345			SqliteQueryProfile::default(),
346		));
347	}
348
349	refuse_oversize_vacuum(db, stmt)?;
350	let mut profile = SqliteQueryProfile::default();
351	if let Some(params) = params {
352		bind_params_profiled(db, stmt, params, &mut profile)?;
353	}
354
355	let columns = collect_columns(stmt);
356	profile.result_columns = columns.len() as u64;
357	let mut rows = Vec::new();
358	let mut budget = ResultBudget::new(max_result_bytes);
359	loop {
360		let step_rc = unsafe { sqlite3_step(stmt) };
361		if step_rc == SQLITE_DONE {
362			break;
363		}
364		if step_rc != SQLITE_ROW {
365			return Err(sqlite_error(
366				db,
367				0,
368				"failed to step sqlite execute statement",
369			));
370		}
371
372		rows.push(read_row_profiled(
373			stmt,
374			columns.len(),
375			&mut budget,
376			&mut profile,
377		)?);
378		profile.result_rows = profile.result_rows.saturating_add(1);
379	}
380
381	let changes = unsafe { sqlite3_changes(db) as i64 };
382	Ok((
383		ExecuteResult {
384			columns,
385			rows,
386			changes,
387			last_insert_row_id: (changes > 0).then(|| unsafe { sqlite3_last_insert_rowid(db) }),
388		},
389		profile,
390	))
391}
392
393/// Runs every statement in `sql` and returns the rows of the last one that has columns or
394/// rows. With `max_result_bytes`, each statement's result is capped at that many logical bytes.
395pub fn exec_statements(
396	db: *mut sqlite3,
397	sql: &str,
398	max_result_bytes: Option<u64>,
399) -> Result<QueryResult> {
400	let c_sql = CString::new(sql).map_err(|err| anyhow!(err.to_string()))?;
401	let mut remaining = c_sql.as_ptr();
402	let mut statement_index = 0_u32;
403	let mut final_result = QueryResult {
404		columns: Vec::new(),
405		rows: Vec::new(),
406	};
407
408	while unsafe { *remaining } != 0 {
409		let mut stmt = ptr::null_mut();
410		let mut tail = ptr::null();
411		let rc = unsafe { sqlite3_prepare_v2(db, remaining, -1, &mut stmt, &mut tail) };
412		let _stmt = FinalizeOnDrop(stmt);
413		if rc != SQLITE_OK {
414			return Err(sqlite_error(
415				db,
416				statement_index,
417				"failed to prepare sqlite exec statement",
418			));
419		}
420
421		if stmt.is_null() {
422			if tail == remaining {
423				break;
424			}
425			remaining = tail;
426			continue;
427		}
428
429		// A statement with columns replaces the earlier result, so release that result before
430		// this statement's rows are collected and only one result is held at a time.
431		if unsafe { sqlite3_column_count(stmt) } > 0 {
432			final_result = QueryResult {
433				columns: Vec::new(),
434				rows: Vec::new(),
435			};
436		}
437
438		refuse_oversize_vacuum(db, stmt)?;
439		let columns = collect_columns(stmt);
440		let mut rows = Vec::new();
441		let mut budget = ResultBudget::new(max_result_bytes);
442		loop {
443			let step_rc = unsafe { sqlite3_step(stmt) };
444			if step_rc == SQLITE_DONE {
445				break;
446			}
447			if step_rc != SQLITE_ROW {
448				return Err(sqlite_error(
449					db,
450					statement_index,
451					"failed to step sqlite exec statement",
452				));
453			}
454
455			rows.push(read_row(stmt, columns.len(), &mut budget)?);
456		}
457
458		if !columns.is_empty() || !rows.is_empty() {
459			final_result = QueryResult { columns, rows };
460		}
461
462		if tail == remaining {
463			break;
464		}
465		remaining = tail;
466		statement_index = statement_index.saturating_add(1);
467	}
468
469	Ok(final_result)
470}
471
472/// Like [`exec_statements`], also returning the profile of the returned statement.
473pub fn exec_statements_profiled(
474	db: *mut sqlite3,
475	sql: &str,
476	max_result_bytes: Option<u64>,
477) -> Result<(QueryResult, SqliteQueryProfile)> {
478	let c_sql = CString::new(sql).map_err(|err| anyhow!(err.to_string()))?;
479	let mut remaining = c_sql.as_ptr();
480	let mut statement_index = 0_u32;
481	let mut final_result = QueryResult {
482		columns: Vec::new(),
483		rows: Vec::new(),
484	};
485	let mut final_profile = SqliteQueryProfile::default();
486
487	while unsafe { *remaining } != 0 {
488		let mut stmt = ptr::null_mut();
489		let mut tail = ptr::null();
490		let rc = unsafe { sqlite3_prepare_v2(db, remaining, -1, &mut stmt, &mut tail) };
491		let _stmt = FinalizeOnDrop(stmt);
492		if rc != SQLITE_OK {
493			return Err(sqlite_error(
494				db,
495				statement_index,
496				"failed to prepare sqlite exec statement",
497			));
498		}
499
500		if stmt.is_null() {
501			if tail == remaining {
502				break;
503			}
504			remaining = tail;
505			continue;
506		}
507
508		// A statement with columns replaces the earlier result, so release that result before
509		// this statement's rows are collected and only one result is held at a time.
510		if unsafe { sqlite3_column_count(stmt) } > 0 {
511			final_result = QueryResult {
512				columns: Vec::new(),
513				rows: Vec::new(),
514			};
515		}
516
517		refuse_oversize_vacuum(db, stmt)?;
518		let columns = collect_columns(stmt);
519		let mut profile = SqliteQueryProfile {
520			result_columns: columns.len() as u64,
521			..Default::default()
522		};
523		let mut rows = Vec::new();
524		let mut budget = ResultBudget::new(max_result_bytes);
525		loop {
526			let step_rc = unsafe { sqlite3_step(stmt) };
527			if step_rc == SQLITE_DONE {
528				break;
529			}
530			if step_rc != SQLITE_ROW {
531				return Err(sqlite_error(
532					db,
533					statement_index,
534					"failed to step sqlite exec statement",
535				));
536			}
537
538			rows.push(read_row_profiled(
539				stmt,
540				columns.len(),
541				&mut budget,
542				&mut profile,
543			)?);
544			profile.result_rows = profile.result_rows.saturating_add(1);
545		}
546
547		if !columns.is_empty() || !rows.is_empty() {
548			final_result = QueryResult { columns, rows };
549			final_profile = profile;
550		}
551
552		if tail == remaining {
553			break;
554		}
555		remaining = tail;
556		statement_index = statement_index.saturating_add(1);
557	}
558
559	Ok((final_result, final_profile))
560}
561
562fn bind_params(
563	db: *mut sqlite3,
564	stmt: *mut libsqlite3_sys::sqlite3_stmt,
565	params: &[BindParam],
566) -> Result<()> {
567	for (index, param) in params.iter().enumerate() {
568		bind_param(db, stmt, (index + 1) as i32, param)?;
569	}
570
571	Ok(())
572}
573
574fn bind_params_profiled(
575	db: *mut sqlite3,
576	stmt: *mut libsqlite3_sys::sqlite3_stmt,
577	params: &[BindParam],
578	profile: &mut SqliteQueryProfile,
579) -> Result<()> {
580	for (index, param) in params.iter().enumerate() {
581		profile.bind_count = profile.bind_count.saturating_add(1);
582		profile.bind_logical_bytes = profile
583			.bind_logical_bytes
584			.saturating_add(logical_bind_bytes(param));
585		bind_param(db, stmt, (index + 1) as i32, param)?;
586	}
587
588	Ok(())
589}
590
591fn bind_param(
592	db: *mut sqlite3,
593	stmt: *mut libsqlite3_sys::sqlite3_stmt,
594	bind_index: i32,
595	param: &BindParam,
596) -> Result<()> {
597	let rc = match param {
598		BindParam::Null => unsafe { sqlite3_bind_null(stmt, bind_index) },
599		BindParam::Integer(value) => unsafe { sqlite3_bind_int64(stmt, bind_index, *value) },
600		BindParam::Float(value) => unsafe { sqlite3_bind_double(stmt, bind_index, *value) },
601		BindParam::Text(value) => unsafe {
602			sqlite3_bind_text(
603				stmt,
604				bind_index,
605				value.as_ptr() as *const c_char,
606				value.len() as i32,
607				SQLITE_TRANSIENT(),
608			)
609		},
610		BindParam::Blob(value) => unsafe {
611			sqlite3_bind_blob(
612				stmt,
613				bind_index,
614				value.as_ptr() as *const _,
615				value.len() as i32,
616				SQLITE_TRANSIENT(),
617			)
618		},
619	};
620
621	if rc != SQLITE_OK {
622		return Err(sqlite_error(db, 0, "failed to bind sqlite parameter"));
623	}
624
625	Ok(())
626}
627
628fn collect_columns(stmt: *mut libsqlite3_sys::sqlite3_stmt) -> Vec<String> {
629	let column_count = unsafe { sqlite3_column_count(stmt) };
630	(0..column_count)
631		.map(|index| unsafe {
632			let name_ptr = sqlite3_column_name(stmt, index);
633			if name_ptr.is_null() {
634				String::new()
635			} else {
636				CStr::from_ptr(name_ptr).to_string_lossy().into_owned()
637			}
638		})
639		.collect()
640}
641
642fn column_value(stmt: *mut libsqlite3_sys::sqlite3_stmt, index: i32) -> ColumnValue {
643	match unsafe { sqlite3_column_type(stmt, index) } {
644		SQLITE_NULL => ColumnValue::Null,
645		SQLITE_INTEGER => ColumnValue::Integer(unsafe { sqlite3_column_int64(stmt, index) }),
646		SQLITE_FLOAT => ColumnValue::Float(unsafe { sqlite3_column_double(stmt, index) }),
647		SQLITE_TEXT => {
648			let text_ptr = unsafe { sqlite3_column_text(stmt, index) };
649			if text_ptr.is_null() {
650				ColumnValue::Null
651			} else {
652				let text_len = unsafe { sqlite3_column_bytes(stmt, index) } as usize;
653				let text = String::from_utf8_lossy(unsafe {
654					std::slice::from_raw_parts(text_ptr as *const u8, text_len)
655				})
656				.into_owned();
657				ColumnValue::Text(text)
658			}
659		}
660		SQLITE_BLOB => {
661			let blob_ptr = unsafe { sqlite3_column_blob(stmt, index) };
662			let blob_len = unsafe { sqlite3_column_bytes(stmt, index) } as usize;
663			// SQLite returns a null pointer for an empty BLOB, distinct from SQL NULL.
664			// Do not construct a slice from that pointer, even with length zero.
665			if blob_len == 0 {
666				ColumnValue::Blob(Vec::new())
667			} else if blob_ptr.is_null() {
668				ColumnValue::Null
669			} else {
670				let blob = unsafe { std::slice::from_raw_parts(blob_ptr as *const u8, blob_len) };
671				ColumnValue::Blob(blob.to_vec())
672			}
673		}
674		_ => ColumnValue::Null,
675	}
676}
677
678fn column_value_profiled(
679	stmt: *mut libsqlite3_sys::sqlite3_stmt,
680	index: i32,
681	profile: &mut SqliteQueryProfile,
682) -> ColumnValue {
683	let mut logical_bytes = 0_u64;
684	let value = match unsafe { sqlite3_column_type(stmt, index) } {
685		SQLITE_NULL => ColumnValue::Null,
686		SQLITE_INTEGER => {
687			logical_bytes = 8;
688			ColumnValue::Integer(unsafe { sqlite3_column_int64(stmt, index) })
689		}
690		SQLITE_FLOAT => {
691			logical_bytes = 8;
692			ColumnValue::Float(unsafe { sqlite3_column_double(stmt, index) })
693		}
694		SQLITE_TEXT => {
695			let text_ptr = unsafe { sqlite3_column_text(stmt, index) };
696			if text_ptr.is_null() {
697				ColumnValue::Null
698			} else {
699				let text_len = unsafe { sqlite3_column_bytes(stmt, index) } as usize;
700				logical_bytes = text_len as u64;
701				let text = String::from_utf8_lossy(unsafe {
702					std::slice::from_raw_parts(text_ptr as *const u8, text_len)
703				})
704				.into_owned();
705				ColumnValue::Text(text)
706			}
707		}
708		SQLITE_BLOB => {
709			let blob_ptr = unsafe { sqlite3_column_blob(stmt, index) };
710			let blob_len = unsafe { sqlite3_column_bytes(stmt, index) } as usize;
711			// SQLite returns a null pointer for an empty BLOB, distinct from SQL NULL.
712			// Do not construct a slice from that pointer, even with length zero.
713			if blob_len == 0 {
714				ColumnValue::Blob(Vec::new())
715			} else if blob_ptr.is_null() {
716				ColumnValue::Null
717			} else {
718				logical_bytes = blob_len as u64;
719				let blob = unsafe { std::slice::from_raw_parts(blob_ptr as *const u8, blob_len) };
720				ColumnValue::Blob(blob.to_vec())
721			}
722		}
723		_ => ColumnValue::Null,
724	};
725	profile.result_logical_bytes = profile.result_logical_bytes.saturating_add(logical_bytes);
726	value
727}
728
729fn logical_bind_bytes(param: &BindParam) -> u64 {
730	match param {
731		BindParam::Null => 0,
732		BindParam::Integer(_) | BindParam::Float(_) => 8,
733		BindParam::Text(value) => value.len() as u64,
734		BindParam::Blob(value) => value.len() as u64,
735	}
736}
737
738/// Refuses an in-place `VACUUM` before it runs when the database holds more in-use pages than
739/// one commit may carry.
740///
741/// VACUUM rewrites the whole database in one transaction. Without this check it reads every
742/// page and builds the full copy in memory before the VFS can refuse the copy itself. In-use
743/// pages are an estimate of the copy's size: a sparsely filled database can compact below the
744/// cap and is still refused here, which keeps a large VACUUM from holding a whole copy in
745/// memory. Connections on other VFSes report no cap and are not checked.
746fn refuse_oversize_vacuum(db: *mut sqlite3, stmt: *mut sqlite3_stmt) -> Result<()> {
747	let sql = unsafe { sqlite3_sql(stmt) };
748	if sql.is_null() {
749		return Ok(());
750	}
751	let sql = unsafe { CStr::from_ptr(sql) }.to_string_lossy();
752	if !is_in_place_vacuum(&sql) {
753		return Ok(());
754	}
755
756	let mut max_pages: i64 = 0;
757	let rc = unsafe {
758		sqlite3_file_control(
759			db,
760			c"main".as_ptr(),
761			FCNTL_COMMIT_PAGE_CAP,
762			(&mut max_pages as *mut i64).cast(),
763		)
764	};
765	if rc != SQLITE_OK {
766		return Ok(());
767	}
768
769	let (Some(page_count), Some(freelist_count), Some(page_size)) = (
770		pragma_i64(db, c"PRAGMA main.page_count;"),
771		pragma_i64(db, c"PRAGMA main.freelist_count;"),
772		pragma_i64(db, c"PRAGMA main.page_size;"),
773	) else {
774		return Ok(());
775	};
776	let in_use_pages = page_count.saturating_sub(freelist_count);
777	if in_use_pages > max_pages {
778		return Err(SqliteTransactionTooLargeError {
779			max_pages: max_pages as usize,
780			page_size: page_size as usize,
781		}
782		.into());
783	}
784
785	Ok(())
786}
787
788/// Whether a statement is a `VACUUM` of the main database in place. `VACUUM INTO` writes a
789/// separate file and `VACUUM temp` another schema, so neither commits to the main database.
790pub(crate) fn is_in_place_vacuum(sql: &str) -> bool {
791	let words = sql_words(sql);
792	let mut words = words.iter();
793	if !words
794		.next()
795		.is_some_and(|word| word.eq_ignore_ascii_case("vacuum"))
796	{
797		return false;
798	}
799
800	match words.next() {
801		None => true,
802		Some(word) if word.eq_ignore_ascii_case("into") => false,
803		Some(schema) => {
804			let schema = schema.trim_matches(|c| matches!(c, '"' | '`' | '[' | ']' | '\''));
805			schema.eq_ignore_ascii_case("main")
806				&& !words.any(|word| word.eq_ignore_ascii_case("into"))
807		}
808	}
809}
810
811/// Splits SQL into whitespace or `;` separated words, dropping `--` and `/* */` comments.
812/// Quoted text is not interpreted, which is enough to read a statement's leading keywords.
813fn sql_words(sql: &str) -> Vec<&str> {
814	let mut words = Vec::new();
815	let mut rest = sql;
816	loop {
817		rest = rest.trim_start_matches(|c: char| c.is_ascii_whitespace() || c == ';');
818		if rest.is_empty() {
819			return words;
820		}
821		if let Some(comment) = rest.strip_prefix("--") {
822			rest = comment.find('\n').map_or("", |end| &comment[end + 1..]);
823			continue;
824		}
825		if let Some(comment) = rest.strip_prefix("/*") {
826			rest = comment.find("*/").map_or("", |end| &comment[end + 2..]);
827			continue;
828		}
829		let end = rest
830			.char_indices()
831			.find(|(index, c)| {
832				c.is_ascii_whitespace()
833					|| *c == ';' || rest[*index..].starts_with("--")
834					|| rest[*index..].starts_with("/*")
835			})
836			.map_or(rest.len(), |(index, _)| index);
837		words.push(&rest[..end]);
838		rest = &rest[end..];
839	}
840}
841
842/// Reads a single integer pragma, or `None` if it fails.
843fn pragma_i64(db: *mut sqlite3, sql: &CStr) -> Option<i64> {
844	let mut stmt = ptr::null_mut();
845	let rc = unsafe { sqlite3_prepare_v2(db, sql.as_ptr(), -1, &mut stmt, ptr::null_mut()) };
846	let _stmt = FinalizeOnDrop(stmt);
847	if rc != SQLITE_OK || stmt.is_null() {
848		return None;
849	}
850	if unsafe { sqlite3_step(stmt) } == SQLITE_ROW {
851		Some(unsafe { sqlite3_column_int64(stmt, 0) })
852	} else {
853		None
854	}
855}
856
857/// Finalizes a prepared statement when its owner leaves scope, including by an error return or
858/// a panic, so no statement outlives the call that prepared it.
859struct FinalizeOnDrop(*mut sqlite3_stmt);
860
861impl Drop for FinalizeOnDrop {
862	fn drop(&mut self) {
863		// SAFETY: the statement was prepared by the call that owns this guard and is used by no
864		// one else. Finalizing a null statement is a no-op.
865		unsafe {
866			sqlite3_finalize(self.0);
867		}
868	}
869}
870
871fn has_non_whitespace_tail(tail: *const c_char) -> bool {
872	if tail.is_null() {
873		return false;
874	}
875
876	let bytes = unsafe { CStr::from_ptr(tail).to_bytes() };
877	bytes.iter().any(|byte| !byte.is_ascii_whitespace())
878}
879
880fn sqlite_error(db: *mut sqlite3, statement_index: u32, context: &str) -> anyhow::Error {
881	let (code, detail) = unsafe {
882		if db.is_null() {
883			(-1, "unknown sqlite error".to_string())
884		} else {
885			(
886				sqlite3_extended_errcode(db),
887				CStr::from_ptr(sqlite3_errmsg(db))
888					.to_string_lossy()
889					.into_owned(),
890			)
891		}
892	};
893	SqliteStatementError {
894		code,
895		statement_index,
896		message: format!("{context}: {detail}"),
897	}
898	.into()
899}
900
901