1#[cfg(not(all(target_family = "wasm", target_os = "unknown")))]
2extern crate libsqlite3_sys as ffi;
3
4#[cfg(all(target_family = "wasm", target_os = "unknown"))]
5use sqlite_wasm_rs as ffi;
6
7pub mod authorizer;
8mod bind_collector;
9mod collation_needed;
10mod functions;
11mod hooks;
12mod limits;
13#[cfg(all(
14 test,
15 feature = "std",
16 not(all(target_family = "wasm", target_os = "unknown"))
17))]
18#[allow(unsafe_code)]
19mod oom_test_support;
20mod owned_row;
21mod raw;
22mod row;
23mod serialized_database;
24pub(in crate::sqlite) mod sqlite_blob;
25mod sqlite_value;
26mod statement_iterator;
27mod stmt;
28mod trace;
29mod update_hook;
30
31pub use self::authorizer::{AuthorizerContext, AuthorizerDecision};
32pub use self::bind_collector::SqliteBindCollector;#[diesel_derives::__diesel_public_if(
33 feature = "i-implement-a-third-party-backend-and-opt-into-breaking-changes"
34)]
35pub(in crate::sqlite) use self::bind_collector::SqliteBindCollector;
36pub use self::bind_collector::SqliteBindValue;
37#[cfg(feature = "i-implement-a-third-party-backend-and-opt-into-breaking-changes")]
38pub use self::bind_collector::{OwnedSqliteBindValue, SqliteBindCollectorData, SqliteBindValueRef};
39pub use self::collation_needed::{CollationNeededContext, SqliteTextRep};
40pub use self::limits::SqliteLimit;
41use self::raw::RawConnection;
42pub use self::serialized_database::SerializedDatabase;
43pub use self::sqlite_value::SqliteValue;
44use self::statement_iterator::*;
45use self::stmt::{Statement, StatementUse};
46pub use self::trace::{SqliteTraceEvent, SqliteTraceFlags};
47pub use self::update_hook::{
48 SqliteChangeEvent, SqliteChangeOp, SqliteChangeOps, SqliteUpdateRouter,
49};
50use super::SqliteAggregateFunction;
51use crate::connection::instrumentation::{DynInstrumentation, StrQueryHelper};
52use crate::connection::statement_cache::StatementCache;
53use crate::connection::*;
54use crate::deserialize::{FromSqlRow, StaticallySizedRow};
55use crate::expression::QueryMetadata;
56use crate::query_builder::*;
57use crate::query_dsl::RunQueryDslSupport;
58use crate::query_source::{ColumnHasTable, NamedTable};
59use crate::result::*;
60use crate::serialize::ToSql;
61use crate::sql_types::{HasSqlType, TypeMetadata};
62use crate::sqlite::{Sqlite, SqliteFunctionBehavior};
63use alloc::string::String;
64use alloc::string::ToString;
65use alloc::vec::Vec;
66use core::ffi as libc;
67use core::marker::PhantomData;
68use core::num::NonZeroI64;
69
70#[allow(missing_debug_implementations)]
194#[cfg(feature = "__sqlite-shared")]
195pub struct SqliteConnection {
196 statement_cache: StatementCache<Sqlite, Statement>,
200 raw_connection: RawConnection,
201 transaction_state: AnsiTransactionManager,
202 metadata_lookup: (),
205 instrumentation: DynInstrumentation,
206 serialized_data: Vec<Vec<u8>>,
217}
218
219#[allow(unsafe_code)]
223unsafe impl Send for SqliteConnection {}
224
225impl SimpleConnection for SqliteConnection {
226 fn batch_execute(&mut self, query: &str) -> QueryResult<()> {
227 self.instrumentation
228 .on_connection_event(InstrumentationEvent::StartQuery {
229 query: &StrQueryHelper::new(query),
230 });
231 let resp = self.raw_connection.exec(query);
232 self.instrumentation
233 .on_connection_event(InstrumentationEvent::FinishQuery {
234 query: &StrQueryHelper::new(query),
235 error: resp.as_ref().err(),
236 });
237 resp
238 }
239}
240
241impl ConnectionSealed for SqliteConnection {}
242
243impl Connection for SqliteConnection {
244 type Backend = Sqlite;
245 type TransactionManager = AnsiTransactionManager;
246
247 fn establish(database_url: &str) -> ConnectionResult<Self> {
263 let mut instrumentation = DynInstrumentation::default_instrumentation();
264 instrumentation.on_connection_event(InstrumentationEvent::StartEstablishConnection {
265 url: database_url,
266 });
267
268 let establish_result = Self::establish_inner(database_url);
269 instrumentation.on_connection_event(InstrumentationEvent::FinishEstablishConnection {
270 url: database_url,
271 error: establish_result.as_ref().err(),
272 });
273 let mut conn = establish_result?;
274 conn.instrumentation = instrumentation;
275 Ok(conn)
276 }
277
278 fn execute_returning_count<T>(&mut self, source: &T) -> QueryResult<usize>
279 where
280 T: QueryFragment<Self::Backend> + QueryId,
281 {
282 let statement_use = self.prepared_query(source)?;
283 statement_use.run().and_then(|_| {
284 self.raw_connection
285 .rows_affected_by_last_query()
286 .map_err(Error::DeserializationError)
287 })
288 }
289
290 fn transaction_state(&mut self) -> &mut AnsiTransactionManager
291 where
292 Self: Sized,
293 {
294 &mut self.transaction_state
295 }
296
297 fn instrumentation(&mut self) -> &mut dyn Instrumentation {
298 &mut *self.instrumentation
299 }
300
301 fn set_instrumentation(&mut self, instrumentation: impl Instrumentation) {
302 self.instrumentation = instrumentation.into();
303 }
304
305 fn set_prepared_statement_cache_size(&mut self, size: CacheSize) {
306 self.statement_cache.set_cache_size(size);
307 }
308}
309
310impl LoadConnection<DefaultLoadingMode> for SqliteConnection {
311 type Cursor<'conn, 'query> = StatementIterator<'conn, 'query>;
312 type Row<'conn, 'query> = self::row::SqliteRow<'conn, 'query>;
313
314 fn load<'conn, 'query, T>(
315 &'conn mut self,
316 source: T,
317 ) -> QueryResult<Self::Cursor<'conn, 'query>>
318 where
319 T: Query + QueryFragment<Self::Backend> + QueryId + 'query,
320 Self::Backend: QueryMetadata<T::SqlType>,
321 {
322 let statement = self.prepared_query(source)?;
323
324 Ok(StatementIterator::new(statement))
325 }
326}
327
328impl WithMetadataLookup for SqliteConnection {
329 fn metadata_lookup(&mut self) -> &mut <Sqlite as TypeMetadata>::MetadataLookup {
330 &mut self.metadata_lookup
331 }
332}
333
334#[cfg(feature = "r2d2")]
335impl crate::r2d2::R2D2Connection for crate::sqlite::SqliteConnection {
336 fn ping(&mut self) -> QueryResult<()> {
337 use crate::RunQueryDsl;
338
339 crate::r2d2::CheckConnectionQuery.execute(self).map(|_| ())
340 }
341
342 fn is_broken(&mut self) -> bool {
343 AnsiTransactionManager::is_broken_transaction_manager(self)
344 }
345}
346
347impl MultiConnectionHelper for SqliteConnection {
348 fn to_any<'a>(
349 lookup: &mut <Self::Backend as crate::sql_types::TypeMetadata>::MetadataLookup,
350 ) -> &mut (dyn core::any::Any + 'a) {
351 lookup
352 }
353
354 fn from_any(
355 lookup: &mut dyn core::any::Any,
356 ) -> Option<&mut <Self::Backend as crate::sql_types::TypeMetadata>::MetadataLookup> {
357 lookup.downcast_mut()
358 }
359}
360
361#[derive(#[automatically_derived]
impl ::core::fmt::Debug for CommitDecision {
#[inline]
fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
::core::fmt::Formatter::write_str(f,
match self {
CommitDecision::Proceed => "Proceed",
CommitDecision::Rollback => "Rollback",
})
}
}Debug, #[automatically_derived]
#[doc(hidden)]
unsafe impl ::core::clone::TrivialClone for CommitDecision { }
#[automatically_derived]
impl ::core::clone::Clone for CommitDecision {
#[inline]
fn clone(&self) -> CommitDecision { *self }
}Clone, #[automatically_derived]
impl ::core::marker::Copy for CommitDecision { }Copy, #[automatically_derived]
impl ::core::marker::StructuralPartialEq for CommitDecision { }
#[automatically_derived]
impl ::core::cmp::PartialEq for CommitDecision {
#[inline]
fn eq(&self, other: &CommitDecision) -> bool {
let __self_discr = ::core::intrinsics::discriminant_value(self);
let __arg1_discr = ::core::intrinsics::discriminant_value(other);
__self_discr == __arg1_discr
}
}PartialEq, #[automatically_derived]
impl ::core::cmp::Eq for CommitDecision { }Eq)]
364pub enum CommitDecision {
365 Proceed,
367 Rollback,
369}
370
371#[derive(#[automatically_derived]
impl ::core::fmt::Debug for ProgressDecision {
#[inline]
fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
::core::fmt::Formatter::write_str(f,
match self {
ProgressDecision::Continue => "Continue",
ProgressDecision::Interrupt => "Interrupt",
})
}
}Debug, #[automatically_derived]
#[doc(hidden)]
unsafe impl ::core::clone::TrivialClone for ProgressDecision { }
#[automatically_derived]
impl ::core::clone::Clone for ProgressDecision {
#[inline]
fn clone(&self) -> ProgressDecision { *self }
}Clone, #[automatically_derived]
impl ::core::marker::Copy for ProgressDecision { }Copy, #[automatically_derived]
impl ::core::marker::StructuralPartialEq for ProgressDecision { }
#[automatically_derived]
impl ::core::cmp::PartialEq for ProgressDecision {
#[inline]
fn eq(&self, other: &ProgressDecision) -> bool {
let __self_discr = ::core::intrinsics::discriminant_value(self);
let __arg1_discr = ::core::intrinsics::discriminant_value(other);
__self_discr == __arg1_discr
}
}PartialEq, #[automatically_derived]
impl ::core::cmp::Eq for ProgressDecision { }Eq)]
374pub enum ProgressDecision {
375 Continue,
377 Interrupt,
379}
380
381#[derive(#[automatically_derived]
impl ::core::fmt::Debug for BusyDecision {
#[inline]
fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
::core::fmt::Formatter::write_str(f,
match self {
BusyDecision::Retry => "Retry",
BusyDecision::GiveUp => "GiveUp",
})
}
}Debug, #[automatically_derived]
#[doc(hidden)]
unsafe impl ::core::clone::TrivialClone for BusyDecision { }
#[automatically_derived]
impl ::core::clone::Clone for BusyDecision {
#[inline]
fn clone(&self) -> BusyDecision { *self }
}Clone, #[automatically_derived]
impl ::core::marker::Copy for BusyDecision { }Copy, #[automatically_derived]
impl ::core::marker::StructuralPartialEq for BusyDecision { }
#[automatically_derived]
impl ::core::cmp::PartialEq for BusyDecision {
#[inline]
fn eq(&self, other: &BusyDecision) -> bool {
let __self_discr = ::core::intrinsics::discriminant_value(self);
let __arg1_discr = ::core::intrinsics::discriminant_value(other);
__self_discr == __arg1_discr
}
}PartialEq, #[automatically_derived]
impl ::core::cmp::Eq for BusyDecision { }Eq)]
384pub enum BusyDecision {
385 Retry,
387 GiveUp,
389}
390
391#[derive(#[automatically_derived]
impl ::core::fmt::Debug for AutoVacuumMode {
#[inline]
fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
::core::fmt::Formatter::write_str(f,
match self {
AutoVacuumMode::None => "None",
AutoVacuumMode::Full => "Full",
AutoVacuumMode::Incremental => "Incremental",
})
}
}Debug, #[automatically_derived]
#[doc(hidden)]
unsafe impl ::core::clone::TrivialClone for AutoVacuumMode { }
#[automatically_derived]
impl ::core::clone::Clone for AutoVacuumMode {
#[inline]
fn clone(&self) -> AutoVacuumMode { *self }
}Clone, #[automatically_derived]
impl ::core::marker::Copy for AutoVacuumMode { }Copy, #[automatically_derived]
impl ::core::marker::StructuralPartialEq for AutoVacuumMode { }
#[automatically_derived]
impl ::core::cmp::PartialEq for AutoVacuumMode {
#[inline]
fn eq(&self, other: &AutoVacuumMode) -> bool {
let __self_discr = ::core::intrinsics::discriminant_value(self);
let __arg1_discr = ::core::intrinsics::discriminant_value(other);
__self_discr == __arg1_discr
}
}PartialEq, #[automatically_derived]
impl ::core::cmp::Eq for AutoVacuumMode { }Eq, const _: () =
{
use diesel;
impl<__DB>
diesel::deserialize::FromSql<crate::sql_types::Integer, __DB> for
AutoVacuumMode where __DB: diesel::backend::Backend,
crate::sql_types::Integer: diesel::sql_types::EnumSqlType<true,
__DB>,
<crate::sql_types::Integer as
diesel::sql_types::EnumSqlType<true,
__DB>>::Strategy: diesel::internal::derives::enum_::EnumMapping<__DB>
{
fn from_sql(value:
<__DB as diesel::backend::Backend>::RawValue<'_>)
-> diesel::deserialize::Result<Self> {
const VARIANTS:
&[diesel::internal::derives::enum_::EnumVariant] =
&[diesel::internal::derives::enum_::EnumVariant {
discriminant: 0i128,
rust_name: "None",
sql_name: "None",
},
diesel::internal::derives::enum_::EnumVariant {
discriminant: 1i128,
rust_name: "Full",
sql_name: "Full",
},
diesel::internal::derives::enum_::EnumVariant {
discriminant: 2i128,
rust_name: "Incremental",
sql_name: "Incremental",
}];
let idx =
<<crate::sql_types::Integer as
diesel::sql_types::EnumSqlType<true, __DB>>::Strategy as
diesel::internal::derives::enum_::EnumMapping<__DB>>::map_from_database_value(value,
"AutoVacuumMode", VARIANTS)?;
match idx {
0usize => Ok(Self::None),
1usize => Ok(Self::Full),
2usize => Ok(Self::Incremental),
_ => {
::core::panicking::panic_fmt(format_args!("internal error: entered unreachable code: {0}",
format_args!("We construct all relevant variants")));
}
}
}
}
impl<__DB> diesel::serialize::ToSql<crate::sql_types::Integer, __DB>
for AutoVacuumMode where __DB: diesel::backend::Backend,
crate::sql_types::Integer: diesel::sql_types::EnumSqlType<true,
__DB>,
<crate::sql_types::Integer as
diesel::sql_types::EnumSqlType<true,
__DB>>::Strategy: diesel::internal::derives::enum_::EnumMapping<__DB>
{
fn to_sql<'b>(&'b self,
output: &mut diesel::serialize::Output<'b, '_, __DB>)
-> diesel::serialize::Result {
let variant =
match self {
Self::None =>
&diesel::internal::derives::enum_::EnumVariant {
discriminant: 0i128,
rust_name: "None",
sql_name: "None",
},
Self::Full =>
&diesel::internal::derives::enum_::EnumVariant {
discriminant: 1i128,
rust_name: "Full",
sql_name: "Full",
},
Self::Incremental =>
&diesel::internal::derives::enum_::EnumVariant {
discriminant: 2i128,
rust_name: "Incremental",
sql_name: "Incremental",
},
};
<<crate::sql_types::Integer as
diesel::sql_types::EnumSqlType<true, __DB>>::Strategy as
diesel::internal::derives::enum_::EnumMapping<__DB>>::map_to_database_value(output,
variant)
}
}
impl<'__expr>
diesel::expression::AsExpression<crate::sql_types::Integer> for
&'__expr AutoVacuumMode {
type Expression =
diesel::internal::derives::as_expression::Bound<crate::sql_types::Integer,
Self>;
fn as_expression(self)
->
<Self as
diesel::expression::AsExpression<crate::sql_types::Integer>>::Expression {
diesel::internal::derives::as_expression::Bound::new(self)
}
}
#[diagnostic::do_not_recommend]
impl<'__expr>
diesel::expression::AsExpression<diesel::sql_types::Nullable<crate::sql_types::Integer>>
for &'__expr AutoVacuumMode {
type Expression =
diesel::internal::derives::as_expression::Bound<diesel::sql_types::Nullable<crate::sql_types::Integer>,
Self>;
fn as_expression(self)
->
<Self as
diesel::expression::AsExpression<diesel::sql_types::Nullable<crate::sql_types::Integer>>>::Expression {
diesel::internal::derives::as_expression::Bound::new(self)
}
}
#[diagnostic::do_not_recommend]
impl<'__expr, '__expr2>
diesel::expression::AsExpression<crate::sql_types::Integer> for
&'__expr2 &'__expr AutoVacuumMode {
type Expression =
diesel::internal::derives::as_expression::Bound<crate::sql_types::Integer,
Self>;
fn as_expression(self)
->
<Self as
diesel::expression::AsExpression<crate::sql_types::Integer>>::Expression {
diesel::internal::derives::as_expression::Bound::new(self)
}
}
#[diagnostic::do_not_recommend]
impl<'__expr, '__expr2>
diesel::expression::AsExpression<diesel::sql_types::Nullable<crate::sql_types::Integer>>
for &'__expr2 &'__expr AutoVacuumMode {
type Expression =
diesel::internal::derives::as_expression::Bound<diesel::sql_types::Nullable<crate::sql_types::Integer>,
Self>;
fn as_expression(self)
->
<Self as
diesel::expression::AsExpression<diesel::sql_types::Nullable<crate::sql_types::Integer>>>::Expression {
diesel::internal::derives::as_expression::Bound::new(self)
}
}
impl<__DB>
diesel::serialize::ToSql<diesel::sql_types::Nullable<crate::sql_types::Integer>,
__DB> for AutoVacuumMode where __DB: diesel::backend::Backend,
Self: diesel::serialize::ToSql<crate::sql_types::Integer, __DB> {
fn to_sql<'__b>(&'__b self,
out: &mut diesel::serialize::Output<'__b, '_, __DB>)
-> diesel::serialize::Result {
diesel::serialize::ToSql::<crate::sql_types::Integer,
__DB>::to_sql(self, out)
}
}
impl diesel::expression::AsExpression<crate::sql_types::Integer> for
AutoVacuumMode {
type Expression =
diesel::internal::derives::as_expression::Bound<crate::sql_types::Integer,
Self>;
fn as_expression(self)
->
<Self as
diesel::expression::AsExpression<crate::sql_types::Integer>>::Expression {
diesel::internal::derives::as_expression::Bound::new(self)
}
}
impl diesel::expression::AsExpression<diesel::sql_types::Nullable<crate::sql_types::Integer>>
for AutoVacuumMode {
type Expression =
diesel::internal::derives::as_expression::Bound<diesel::sql_types::Nullable<crate::sql_types::Integer>,
Self>;
fn as_expression(self)
->
<Self as
diesel::expression::AsExpression<diesel::sql_types::Nullable<crate::sql_types::Integer>>>::Expression {
diesel::internal::derives::as_expression::Bound::new(self)
}
}
impl<__DB, __ST> diesel::deserialize::Queryable<__ST, __DB> for
AutoVacuumMode where __DB: diesel::backend::Backend,
__ST: diesel::sql_types::SingleValue,
Self: diesel::deserialize::FromSql<__ST, __DB> {
type Row = Self;
fn build(row: Self) -> diesel::deserialize::Result<Self> {
diesel::deserialize::Result::Ok(row)
}
}
};crate::types::Enum)]
403#[diesel(sql_type = crate::sql_types::Integer)]
404#[non_exhaustive]
405#[repr(i32)]
406pub enum AutoVacuumMode {
407 None = 0,
409 Full = 1,
411 Incremental = 2,
414}
415
416#[derive(#[automatically_derived]
impl ::core::fmt::Debug for WalCheckpointMode {
#[inline]
fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
::core::fmt::Formatter::write_str(f,
match self {
WalCheckpointMode::Passive => "Passive",
WalCheckpointMode::Full => "Full",
WalCheckpointMode::Restart => "Restart",
WalCheckpointMode::Truncate => "Truncate",
WalCheckpointMode::Noop => "Noop",
})
}
}Debug, #[automatically_derived]
#[doc(hidden)]
unsafe impl ::core::clone::TrivialClone for WalCheckpointMode { }
#[automatically_derived]
impl ::core::clone::Clone for WalCheckpointMode {
#[inline]
fn clone(&self) -> WalCheckpointMode { *self }
}Clone, #[automatically_derived]
impl ::core::marker::Copy for WalCheckpointMode { }Copy, #[automatically_derived]
impl ::core::marker::StructuralPartialEq for WalCheckpointMode { }
#[automatically_derived]
impl ::core::cmp::PartialEq for WalCheckpointMode {
#[inline]
fn eq(&self, other: &WalCheckpointMode) -> bool {
let __self_discr = ::core::intrinsics::discriminant_value(self);
let __arg1_discr = ::core::intrinsics::discriminant_value(other);
__self_discr == __arg1_discr
}
}PartialEq, #[automatically_derived]
impl ::core::cmp::Eq for WalCheckpointMode { }Eq)]
420#[non_exhaustive]
421pub enum WalCheckpointMode {
422 Passive,
424 Full,
427 Restart,
430 Truncate,
433 Noop,
439}
440
441#[derive(#[automatically_derived]
impl ::core::fmt::Debug for WalCheckpointOutcome {
#[inline]
fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
::core::fmt::Formatter::debug_struct_field3_finish(f,
"WalCheckpointOutcome", "busy", &self.busy, "log_frames",
&self.log_frames, "checkpointed_frames",
&&self.checkpointed_frames)
}
}Debug, #[automatically_derived]
#[doc(hidden)]
unsafe impl ::core::clone::TrivialClone for WalCheckpointOutcome { }
#[automatically_derived]
impl ::core::clone::Clone for WalCheckpointOutcome {
#[inline]
fn clone(&self) -> WalCheckpointOutcome {
let _: ::core::clone::AssertParamIsClone<bool>;
let _: ::core::clone::AssertParamIsClone<Option<i64>>;
let _: ::core::clone::AssertParamIsClone<Option<i64>>;
*self
}
}Clone, #[automatically_derived]
impl ::core::marker::Copy for WalCheckpointOutcome { }Copy, #[automatically_derived]
impl ::core::marker::StructuralPartialEq for WalCheckpointOutcome { }
#[automatically_derived]
impl ::core::cmp::PartialEq for WalCheckpointOutcome {
#[inline]
fn eq(&self, other: &WalCheckpointOutcome) -> bool {
self.busy == other.busy && self.log_frames == other.log_frames &&
self.checkpointed_frames == other.checkpointed_frames
}
}PartialEq, #[automatically_derived]
impl ::core::cmp::Eq for WalCheckpointOutcome {
#[inline]
#[doc(hidden)]
#[coverage(off)]
fn assert_fields_are_eq(&self) {
let _: ::core::cmp::AssertParamIsEq<bool>;
let _: ::core::cmp::AssertParamIsEq<Option<i64>>;
let _: ::core::cmp::AssertParamIsEq<Option<i64>>;
}
}Eq)]
443#[non_exhaustive]
444pub struct WalCheckpointOutcome {
445 pub busy: bool,
449 pub log_frames: Option<i64>,
452 pub checkpointed_frames: Option<i64>,
457}
458
459impl SqliteConnection {
460 pub fn immediate_transaction<T, E, F>(&mut self, f: F) -> Result<T, E>
482 where
483 F: FnOnce(&mut Self) -> Result<T, E>,
484 E: From<Error>,
485 {
486 self.transaction_sql(f, "BEGIN IMMEDIATE")
487 }
488
489 pub fn exclusive_transaction<T, E, F>(&mut self, f: F) -> Result<T, E>
511 where
512 F: FnOnce(&mut Self) -> Result<T, E>,
513 E: From<Error>,
514 {
515 self.transaction_sql(f, "BEGIN EXCLUSIVE")
516 }
517
518 pub fn last_insert_rowid(&self) -> Option<NonZeroI64> {
554 NonZeroI64::new(self.raw_connection.last_insert_rowid())
555 }
556
557 pub fn get_read_only_blob<'conn, 'query, U>(
586 &'conn self,
587 blob_column: U,
588 row_id: i64,
589 ) -> Result<sqlite_blob::SqliteReadOnlyBlob<'conn>, Error>
590 where
591 'query: 'conn,
592 U: ColumnHasTable,
593 U::Table: NamedTable,
594 {
595 let table = blob_column.table();
596
597 let database_name = table.schema().unwrap_or("main");
598 let column_name = blob_column.name();
599 let table_name = table.table();
600
601 self.raw_connection
602 .blob_open(database_name, table_name, column_name, row_id)
603 }
604
605 fn transaction_sql<T, E, F>(&mut self, f: F, sql: &str) -> Result<T, E>
606 where
607 F: FnOnce(&mut Self) -> Result<T, E>,
608 E: From<Error>,
609 {
610 AnsiTransactionManager::begin_transaction_sql(&mut *self, sql)?;
611 match f(&mut *self) {
612 Ok(value) => {
613 AnsiTransactionManager::commit_transaction(&mut *self)?;
614 Ok(value)
615 }
616 Err(e) => {
617 AnsiTransactionManager::rollback_transaction(&mut *self)?;
618 Err(e)
619 }
620 }
621 }
622
623 fn prepared_query<'conn, 'query, T>(
624 &'conn mut self,
625 source: T,
626 ) -> QueryResult<StatementUse<'conn, 'query>>
627 where
628 T: QueryFragment<Sqlite> + QueryId + 'query,
629 {
630 self.instrumentation
631 .on_connection_event(InstrumentationEvent::StartQuery {
632 query: &crate::debug_query(&source),
633 });
634 let raw_connection = &self.raw_connection;
635 let cache = &mut self.statement_cache;
636 let statement = match cache.cached_statement(
637 &source,
638 &Sqlite,
639 &[],
640 raw_connection,
641 Statement::prepare,
642 &mut *self.instrumentation,
643 ) {
644 Ok(statement) => statement,
645 Err(e) => {
646 self.instrumentation
647 .on_connection_event(InstrumentationEvent::FinishQuery {
648 query: &crate::debug_query(&source),
649 error: Some(&e),
650 });
651
652 return Err(e);
653 }
654 };
655
656 StatementUse::bind(statement, source, &mut *self.instrumentation)
657 }
658
659 #[doc(hidden)]
660 pub fn register_sql_function<ArgsSqlType, RetSqlType, Args, Ret, F>(
661 &mut self,
662 fn_name: &str,
663 behavior: SqliteFunctionBehavior,
664 mut f: F,
665 ) -> QueryResult<()>
666 where
667 F: FnMut(Args) -> Ret + core::panic::UnwindSafe + Send + 'static,
668 Args: FromSqlRow<ArgsSqlType, Sqlite> + StaticallySizedRow<ArgsSqlType, Sqlite>,
669 Ret: ToSql<RetSqlType, Sqlite>,
670 Sqlite: HasSqlType<RetSqlType>,
671 {
672 functions::register(&self.raw_connection, fn_name, behavior, move |_, args| {
673 f(args)
674 })
675 }
676
677 #[doc(hidden)]
678 pub fn register_noarg_sql_function<RetSqlType, Ret, F>(
679 &mut self,
680 fn_name: &str,
681 behavior: SqliteFunctionBehavior,
682 f: F,
683 ) -> QueryResult<()>
684 where
685 F: FnMut() -> Ret + core::panic::UnwindSafe + Send + 'static,
686 Ret: ToSql<RetSqlType, Sqlite>,
687 Sqlite: HasSqlType<RetSqlType>,
688 {
689 functions::register_noargs(&self.raw_connection, fn_name, behavior, f)
690 }
691
692 #[doc(hidden)]
693 pub fn register_aggregate_function<ArgsSqlType, RetSqlType, Args, Ret, A>(
694 &mut self,
695 fn_name: &str,
696 behavior: SqliteFunctionBehavior,
697 ) -> QueryResult<()>
698 where
699 A: SqliteAggregateFunction<Args, Output = Ret> + 'static + Send + core::panic::UnwindSafe,
700 Args: FromSqlRow<ArgsSqlType, Sqlite> + StaticallySizedRow<ArgsSqlType, Sqlite>,
701 Ret: ToSql<RetSqlType, Sqlite>,
702 Sqlite: HasSqlType<RetSqlType>,
703 {
704 functions::register_aggregate::<_, _, _, _, A>(&self.raw_connection, fn_name, behavior)
705 }
706
707 pub fn register_collation<F>(&mut self, collation_name: &str, collation: F) -> QueryResult<()>
743 where
744 F: Fn(&str, &str) -> core::cmp::Ordering + Send + 'static + core::panic::UnwindSafe,
745 {
746 self.raw_connection
747 .register_collation_function(collation_name, collation)
748 }
749
750 pub fn serialize_database_to_buffer(&mut self) -> SerializedDatabase {
761 self.raw_connection.serialize()
762 }
763
764 #[allow(unsafe_code)]
804 pub fn deserialize_readonly_database_from_buffer(&mut self, data: &[u8]) -> QueryResult<()> {
805 self.serialized_data.push(data.to_vec());
808 let last = self
809 .serialized_data
810 .last()
811 .expect("We literally pushed it above, so it's there");
812 unsafe {
813 self.raw_connection.deserialize(last)
816 }
817 }
818
819 #[allow(unsafe_code)]
906 pub unsafe fn with_raw_connection<R, F>(&mut self, f: F) -> R
907 where
908 F: FnOnce(*mut ffi::sqlite3) -> R,
909 {
910 f(self.raw_connection.internal_connection.as_ptr())
911 }
912
913 #[allow(unsafe_code)]
922 pub(crate) unsafe fn with_borrowed_connection<R>(
923 db: core::ptr::NonNull<ffi::sqlite3>,
924 f: impl FnOnce(&mut SqliteConnection) -> R,
925 ) -> R {
926 struct Borrowed(core::mem::ManuallyDrop<SqliteConnection>);
929
930 impl Drop for Borrowed {
931 fn drop(&mut self) {
932 let conn = unsafe { core::mem::ManuallyDrop::take(&mut self.0) };
934 let SqliteConnection {
935 statement_cache,
936 raw_connection,
937 ..
938 } = conn;
939 drop(statement_cache);
942 core::mem::forget(raw_connection);
943 }
944 }
945
946 let mut conn = Borrowed(core::mem::ManuallyDrop::new(SqliteConnection {
947 statement_cache: StatementCache::new(),
948 raw_connection: RawConnection::from_ptr(db),
949 transaction_state: AnsiTransactionManager::default(),
950 metadata_lookup: (),
951 instrumentation: DynInstrumentation::default_instrumentation(),
952 serialized_data: Vec::new(),
953 }));
954
955 let result = f(&mut conn.0);
956
957 if true {
if !#[allow(non_exhaustive_omitted_patterns)] match AnsiTransactionManager::transaction_manager_status_mut(&mut *conn.0).transaction_depth()
{
Ok(None) => true,
_ => false,
} {
{
::core::panicking::panic_fmt(format_args!("callback must not leave an open transaction on the borrowed connection"));
}
};
};debug_assert!(
960 matches!(
961 AnsiTransactionManager::transaction_manager_status_mut(&mut *conn.0)
962 .transaction_depth(),
963 Ok(None)
964 ),
965 "callback must not leave an open transaction on the borrowed connection"
966 );
967
968 result
969 }
970
971 pub fn set_limit(&mut self, limit: SqliteLimit, value: i32) -> i32 {
994 self.raw_connection.set_limit(limit, value)
995 }
996
997 pub fn get_limit(&self, limit: SqliteLimit) -> i32 {
1015 self.raw_connection.get_limit(limit)
1016 }
1017
1018 pub fn set_recommended_security_limits(&mut self) {
1061 self.set_limit(SqliteLimit::Length, SqliteLimit::SAFE_LENGTH_LIMIT);
1062 self.set_limit(SqliteLimit::SqlLength, SqliteLimit::SAFE_SQL_LENGTH_LIMIT);
1063 self.set_limit(
1064 SqliteLimit::ColumnCount,
1065 SqliteLimit::SAFE_COLUMN_COUNT_LIMIT,
1066 );
1067 self.set_limit(SqliteLimit::ExprDepth, SqliteLimit::SAFE_EXPR_DEPTH_LIMIT);
1068 self.set_limit(
1069 SqliteLimit::CompoundSelect,
1070 SqliteLimit::SAFE_COMPOUND_SELECT_LIMIT,
1071 );
1072 self.set_limit(SqliteLimit::VdbeOp, SqliteLimit::SAFE_VDBE_OP_LIMIT);
1073 self.set_limit(
1074 SqliteLimit::FunctionArg,
1075 SqliteLimit::SAFE_FUNCTION_ARG_LIMIT,
1076 );
1077 self.set_limit(SqliteLimit::Attached, SqliteLimit::SAFE_ATTACHED_LIMIT);
1078 self.set_limit(
1079 SqliteLimit::LikePatternLength,
1080 SqliteLimit::SAFE_LIKE_PATTERN_LENGTH_LIMIT,
1081 );
1082 self.set_limit(
1083 SqliteLimit::VariableNumber,
1084 SqliteLimit::SAFE_VARIABLE_NUMBER_LIMIT,
1085 );
1086 self.set_limit(
1087 SqliteLimit::TriggerDepth,
1088 SqliteLimit::SAFE_TRIGGER_DEPTH_LIMIT,
1089 );
1090 }
1091
1092 pub fn set_defensive(&mut self, enabled: bool) -> QueryResult<()> {
1119 self.raw_connection
1120 .set_db_config_bool(ffi::SQLITE_DBCONFIG_DEFENSIVE, enabled)
1121 }
1122
1123 pub fn is_defensive(&self) -> QueryResult<bool> {
1127 self.raw_connection
1128 .get_db_config_bool(ffi::SQLITE_DBCONFIG_DEFENSIVE)
1129 }
1130
1131 pub fn set_trusted_schema(&mut self, trusted: bool) -> QueryResult<()> {
1143 self.raw_connection
1144 .set_db_config_bool(ffi::SQLITE_DBCONFIG_TRUSTED_SCHEMA, trusted)
1145 }
1146
1147 pub fn is_trusted_schema(&self) -> QueryResult<bool> {
1151 self.raw_connection
1152 .get_db_config_bool(ffi::SQLITE_DBCONFIG_TRUSTED_SCHEMA)
1153 }
1154
1155 pub fn with_load_extension_enabled<R, E>(
1183 &mut self,
1184 f: impl FnOnce(&mut Self) -> Result<R, E>,
1185 ) -> Result<R, E>
1186 where
1187 E: From<crate::result::Error>,
1188 {
1189 self.set_load_extension_enabled(true)?;
1190
1191 #[cfg(feature = "std")]
1195 {
1196 match std::panic::catch_unwind(core::panic::AssertUnwindSafe(|| f(self))) {
1197 Ok(r) => {
1198 self.set_load_extension_enabled(false)?;
1199 r
1200 }
1201 Err(panic) => {
1202 let _ = self.set_load_extension_enabled(false);
1203 std::panic::resume_unwind(panic);
1204 }
1205 }
1206 }
1207 #[cfg(not(feature = "std"))]
1208 {
1209 let r = f(self);
1210 self.set_load_extension_enabled(false)?;
1211 r
1212 }
1213 }
1214
1215 fn set_load_extension_enabled(&mut self, enabled: bool) -> QueryResult<()> {
1216 self.raw_connection
1217 .set_db_config_bool(ffi::SQLITE_DBCONFIG_ENABLE_LOAD_EXTENSION, enabled)
1218 }
1219
1220 #[cfg(test)]
1221 fn is_load_extension_enabled(&self) -> QueryResult<bool> {
1222 self.raw_connection
1223 .get_db_config_bool(ffi::SQLITE_DBCONFIG_ENABLE_LOAD_EXTENSION)
1224 }
1225
1226 pub fn set_fts3_tokenizer_enabled(&mut self, enabled: bool) -> QueryResult<()> {
1235 self.raw_connection
1236 .set_db_config_bool(ffi::SQLITE_DBCONFIG_ENABLE_FTS3_TOKENIZER, enabled)
1237 }
1238
1239 pub fn is_fts3_tokenizer_enabled(&self) -> QueryResult<bool> {
1243 self.raw_connection
1244 .get_db_config_bool(ffi::SQLITE_DBCONFIG_ENABLE_FTS3_TOKENIZER)
1245 }
1246
1247 pub fn set_writable_schema(&mut self, enabled: bool) -> QueryResult<()> {
1256 self.raw_connection
1257 .set_db_config_bool(ffi::SQLITE_DBCONFIG_WRITABLE_SCHEMA, enabled)
1258 }
1259
1260 pub fn is_writable_schema(&self) -> QueryResult<bool> {
1264 self.raw_connection
1265 .get_db_config_bool(ffi::SQLITE_DBCONFIG_WRITABLE_SCHEMA)
1266 }
1267
1268 pub fn set_attach_create_enabled(&mut self, enabled: bool) -> QueryResult<()> {
1276 self.raw_connection
1277 .set_db_config_bool(raw::SQLITE_DBCONFIG_ENABLE_ATTACH_CREATE, enabled)
1278 }
1279
1280 pub fn is_attach_create_enabled(&self) -> QueryResult<bool> {
1284 self.raw_connection
1285 .get_db_config_bool(raw::SQLITE_DBCONFIG_ENABLE_ATTACH_CREATE)
1286 }
1287
1288 pub fn set_attach_write_enabled(&mut self, enabled: bool) -> QueryResult<()> {
1295 self.raw_connection
1296 .set_db_config_bool(raw::SQLITE_DBCONFIG_ENABLE_ATTACH_WRITE, enabled)
1297 }
1298
1299 pub fn is_attach_write_enabled(&self) -> QueryResult<bool> {
1303 self.raw_connection
1304 .get_db_config_bool(raw::SQLITE_DBCONFIG_ENABLE_ATTACH_WRITE)
1305 }
1306
1307 pub fn attach_database(&mut self, path: &str, schema_name: &str) -> QueryResult<()> {
1338 use crate::query_dsl::RunQueryDsl;
1339 AttachDatabase { path, schema_name }
1340 .execute(self)
1341 .map(|_| ())
1342 }
1343
1344 pub fn detach_database(&mut self, schema_name: &str) -> QueryResult<()> {
1350 use crate::query_dsl::RunQueryDsl;
1351 DetachDatabase { schema_name }.execute(self).map(|_| ())
1352 }
1353
1354 pub fn set_triggers_enabled(&mut self, enabled: bool) -> QueryResult<()> {
1360 self.raw_connection
1361 .set_db_config_bool(ffi::SQLITE_DBCONFIG_ENABLE_TRIGGER, enabled)
1362 }
1363
1364 pub fn are_triggers_enabled(&self) -> QueryResult<bool> {
1368 self.raw_connection
1369 .get_db_config_bool(ffi::SQLITE_DBCONFIG_ENABLE_TRIGGER)
1370 }
1371
1372 pub fn set_views_enabled(&mut self, enabled: bool) -> QueryResult<()> {
1378 self.raw_connection
1379 .set_db_config_bool(ffi::SQLITE_DBCONFIG_ENABLE_VIEW, enabled)
1380 }
1381
1382 pub fn are_views_enabled(&self) -> QueryResult<bool> {
1386 self.raw_connection
1387 .get_db_config_bool(ffi::SQLITE_DBCONFIG_ENABLE_VIEW)
1388 }
1389
1390 pub fn set_foreign_keys_enabled(&mut self, enabled: bool) -> QueryResult<()> {
1396 self.raw_connection
1397 .set_db_config_bool(ffi::SQLITE_DBCONFIG_ENABLE_FKEY, enabled)
1398 }
1399
1400 pub fn are_foreign_keys_enabled(&self) -> QueryResult<bool> {
1404 self.raw_connection
1405 .get_db_config_bool(ffi::SQLITE_DBCONFIG_ENABLE_FKEY)
1406 }
1407
1408 pub fn set_double_quoted_strings_dml(&mut self, enabled: bool) -> QueryResult<()> {
1416 self.raw_connection
1417 .set_db_config_bool(ffi::SQLITE_DBCONFIG_DQS_DML, enabled)
1418 }
1419
1420 pub fn are_double_quoted_strings_dml_enabled(&self) -> QueryResult<bool> {
1424 self.raw_connection
1425 .get_db_config_bool(ffi::SQLITE_DBCONFIG_DQS_DML)
1426 }
1427
1428 pub fn set_double_quoted_strings_ddl(&mut self, enabled: bool) -> QueryResult<()> {
1436 self.raw_connection
1437 .set_db_config_bool(ffi::SQLITE_DBCONFIG_DQS_DDL, enabled)
1438 }
1439
1440 pub fn are_double_quoted_strings_ddl_enabled(&self) -> QueryResult<bool> {
1444 self.raw_connection
1445 .get_db_config_bool(ffi::SQLITE_DBCONFIG_DQS_DDL)
1446 }
1447
1448 pub fn auto_vacuum(&mut self, schema: Option<&str>) -> QueryResult<AutoVacuumMode> {
1465 use crate::query_dsl::RunQueryDsl;
1466 let query: Pragma<'_, crate::sql_types::Integer> = Pragma::new("auto_vacuum", schema);
1467 query.get_result(self)
1468 }
1469
1470 pub fn set_auto_vacuum(
1490 &mut self,
1491 schema: Option<&str>,
1492 mode: AutoVacuumMode,
1493 ) -> QueryResult<()> {
1494 use crate::query_dsl::RunQueryDsl;
1495 SetPragmaInt {
1497 schema,
1498 name: "auto_vacuum",
1499 value: mode as i32,
1500 }
1501 .execute(self)
1502 .map(|_| ())
1503 }
1504
1505 pub fn page_count(&mut self, schema: Option<&str>) -> QueryResult<i64> {
1526 self.read_pragma_count("page_count", schema)
1527 }
1528
1529 pub fn freelist_count(&mut self, schema: Option<&str>) -> QueryResult<i64> {
1546 self.read_pragma_count("freelist_count", schema)
1547 }
1548
1549 fn read_pragma_count(
1550 &mut self,
1551 pragma: &'static str,
1552 schema: Option<&str>,
1553 ) -> QueryResult<i64> {
1554 use crate::query_dsl::RunQueryDsl;
1555
1556 let query: Pragma<'_, crate::sql_types::BigInt> = Pragma::new(pragma, schema);
1557 query.get_result(self)
1558 }
1559
1560 pub fn incremental_vacuum(
1585 &mut self,
1586 schema: Option<&str>,
1587 pages: Option<u32>,
1588 ) -> QueryResult<()> {
1589 use crate::connection::SimpleConnection;
1590 use crate::query_builder::QueryBuilder;
1591 use crate::sqlite::SqliteQueryBuilder;
1592
1593 let mut query = SqliteQueryBuilder::new();
1598 query.push_sql("PRAGMA ");
1599 query.push_identifier(schema.unwrap_or("main"))?;
1600 query.push_sql(".incremental_vacuum");
1601 if let Some(pages) = pages {
1602 query.push_sql("(");
1603 query.push_sql(&pages.to_string());
1604 query.push_sql(")");
1605 }
1606 self.batch_execute(&query.finish())
1607 }
1608
1609 pub fn vacuum(&mut self, schema: Option<&str>) -> QueryResult<()> {
1631 use crate::query_dsl::RunQueryDsl;
1632
1633 Vacuum { schema, into: None }.execute(self).map(|_| ())
1634 }
1635
1636 pub fn vacuum_into(&mut self, schema: Option<&str>, path: &str) -> QueryResult<()> {
1662 use crate::query_dsl::RunQueryDsl;
1663
1664 Vacuum {
1665 schema,
1666 into: Some(path),
1667 }
1668 .execute(self)
1669 .map(|_| ())
1670 }
1671
1672 pub fn wal_checkpoint(
1719 &mut self,
1720 schema: Option<&str>,
1721 mode: WalCheckpointMode,
1722 ) -> QueryResult<WalCheckpointOutcome> {
1723 use crate::query_dsl::RunQueryDsl;
1724
1725 let (busy, log_frames, checkpointed_frames) =
1726 WalCheckpoint { schema, mode }.get_result::<(i32, i64, i64)>(self)?;
1727 Ok(WalCheckpointOutcome {
1728 busy: busy != 0,
1729 log_frames: (log_frames >= 0).then_some(log_frames),
1731 checkpointed_frames: (checkpointed_frames >= 0).then_some(checkpointed_frames),
1732 })
1733 }
1734
1735 fn register_diesel_sql_functions(&self) -> QueryResult<()> {
1736 use crate::sql_types::{Integer, Text};
1737
1738 functions::register::<Text, Integer, _, _, _>(
1742 &self.raw_connection,
1743 "diesel_manage_updated_at",
1744 SqliteFunctionBehavior::DIRECTONLY,
1745 |conn, table_name: String| {
1746 conn.exec(&::alloc::__export::must_use({
::alloc::fmt::format(format_args!("CREATE TRIGGER __diesel_manage_updated_at_{0}\nAFTER UPDATE ON {0}\nFOR EACH ROW WHEN\n old.updated_at IS NULL AND\n new.updated_at IS NULL OR\n old.updated_at == new.updated_at\nBEGIN\n UPDATE {0}\n SET updated_at = CURRENT_TIMESTAMP\n WHERE ROWID = new.ROWID;\nEND\n",
table_name))
})alloc::format!(
1747 include_str!("diesel_manage_updated_at.sql"),
1748 table_name = table_name
1749 ))
1750 .expect("Failed to create trigger");
1751 0 },
1753 )
1754 }
1755
1756 fn establish_inner(database_url: &str) -> Result<SqliteConnection, ConnectionError> {
1757 use crate::result::ConnectionError::CouldntSetupConfiguration;
1758 let raw_connection = RawConnection::establish(database_url)?;
1759 let conn = Self {
1760 statement_cache: StatementCache::new(),
1761 raw_connection,
1762 transaction_state: AnsiTransactionManager::default(),
1763 metadata_lookup: (),
1764 instrumentation: DynInstrumentation::none(),
1765 serialized_data: Vec::new(),
1766 };
1767 conn.register_diesel_sql_functions()
1768 .map_err(CouldntSetupConfiguration)?;
1769 Ok(conn)
1770 }
1771}
1772
1773fn error_message(err_code: libc::c_int) -> &'static str {
1774 ffi::code_to_str(err_code)
1775}
1776
1777#[derive(const _: () =
{
use diesel;
#[allow(non_camel_case_types)]
impl<'a> diesel::query_builder::QueryId for AttachDatabase<'a> {
type QueryId = AttachDatabase<'static>;
const HAS_STATIC_QUERY_ID: bool = true;
const IS_WINDOW_FUNCTION: bool = false;
}
};QueryId)]
1778struct AttachDatabase<'a> {
1779 path: &'a str,
1780 schema_name: &'a str,
1781}
1782
1783impl QueryFragment<Sqlite> for AttachDatabase<'_> {
1784 fn walk_ast<'b>(&'b self, mut out: AstPass<'_, 'b, Sqlite>) -> QueryResult<()> {
1785 out.push_sql("ATTACH DATABASE ");
1786 out.push_bind_param::<crate::sql_types::Text, _>(self.path)?;
1787 out.push_sql(" AS ");
1788 out.push_bind_param::<crate::sql_types::Text, _>(self.schema_name)?;
1789 Ok(())
1790 }
1791}
1792
1793impl RunQueryDslSupport for AttachDatabase<'_> {}
1794
1795#[derive(const _: () =
{
use diesel;
#[allow(non_camel_case_types)]
impl<'a> diesel::query_builder::QueryId for DetachDatabase<'a> {
type QueryId = DetachDatabase<'static>;
const HAS_STATIC_QUERY_ID: bool = true;
const IS_WINDOW_FUNCTION: bool = false;
}
};QueryId)]
1796struct DetachDatabase<'a> {
1797 schema_name: &'a str,
1798}
1799
1800impl QueryFragment<Sqlite> for DetachDatabase<'_> {
1801 fn walk_ast<'b>(&'b self, mut out: AstPass<'_, 'b, Sqlite>) -> QueryResult<()> {
1802 out.push_sql("DETACH DATABASE ");
1803 out.push_bind_param::<crate::sql_types::Text, _>(self.schema_name)?;
1804 Ok(())
1805 }
1806}
1807
1808impl RunQueryDslSupport for DetachDatabase<'_> {}
1809
1810struct Pragma<'a, ST> {
1814 schema: Option<&'a str>,
1815 name: &'static str,
1816 sql_type: PhantomData<ST>,
1817}
1818
1819impl<'a, ST> Pragma<'a, ST> {
1820 fn new(name: &'static str, schema: Option<&'a str>) -> Self {
1821 Pragma {
1822 schema,
1823 name,
1824 sql_type: PhantomData,
1825 }
1826 }
1827}
1828
1829impl<ST> QueryFragment<Sqlite> for Pragma<'_, ST> {
1830 fn walk_ast<'b>(&'b self, mut out: AstPass<'_, 'b, Sqlite>) -> QueryResult<()> {
1831 out.push_sql("PRAGMA ");
1832 out.push_identifier(self.schema.unwrap_or("main"))?;
1833 out.push_sql(".");
1834 out.push_sql(self.name);
1835 Ok(())
1836 }
1837}
1838
1839impl<ST> QueryId for Pragma<'_, ST> {
1841 type QueryId = ();
1842
1843 const HAS_STATIC_QUERY_ID: bool = false;
1844}
1845
1846impl<ST> Query for Pragma<'_, ST> {
1847 type SqlType = ST;
1848}
1849
1850impl<ST> RunQueryDslSupport for Pragma<'_, ST> {}
1851
1852struct SetPragmaInt<'a> {
1855 schema: Option<&'a str>,
1856 name: &'static str,
1857 value: i32,
1858}
1859
1860impl QueryFragment<Sqlite> for SetPragmaInt<'_> {
1861 fn walk_ast<'b>(&'b self, mut out: AstPass<'_, 'b, Sqlite>) -> QueryResult<()> {
1862 out.push_sql("PRAGMA ");
1863 out.push_identifier(self.schema.unwrap_or("main"))?;
1864 out.push_sql(".");
1865 out.push_sql(self.name);
1866 out.push_sql(" = ");
1867 out.push_sql(&self.value.to_string());
1868 Ok(())
1869 }
1870}
1871
1872impl QueryId for SetPragmaInt<'_> {
1873 type QueryId = ();
1874
1875 const HAS_STATIC_QUERY_ID: bool = false;
1876}
1877
1878impl RunQueryDslSupport for SetPragmaInt<'_> {}
1879
1880struct Vacuum<'a> {
1883 schema: Option<&'a str>,
1884 into: Option<&'a str>,
1885}
1886
1887impl QueryFragment<Sqlite> for Vacuum<'_> {
1888 fn walk_ast<'b>(&'b self, mut out: AstPass<'_, 'b, Sqlite>) -> QueryResult<()> {
1889 out.push_sql("VACUUM ");
1890 out.push_identifier(self.schema.unwrap_or("main"))?;
1891 if let Some(into) = self.into {
1892 out.push_sql(" INTO ");
1893 out.push_bind_param::<crate::sql_types::Text, _>(into)?;
1894 }
1895 Ok(())
1896 }
1897}
1898
1899impl QueryId for Vacuum<'_> {
1901 type QueryId = ();
1902
1903 const HAS_STATIC_QUERY_ID: bool = false;
1904}
1905
1906impl RunQueryDslSupport for Vacuum<'_> {}
1907
1908struct WalCheckpoint<'a> {
1915 schema: Option<&'a str>,
1916 mode: WalCheckpointMode,
1917}
1918
1919impl QueryFragment<Sqlite> for WalCheckpoint<'_> {
1920 fn walk_ast<'b>(&'b self, mut out: AstPass<'_, 'b, Sqlite>) -> QueryResult<()> {
1921 out.push_sql("PRAGMA ");
1922 if let Some(schema) = self.schema {
1923 out.push_identifier(schema)?;
1924 out.push_sql(".");
1925 }
1926 out.push_sql(match self.mode {
1927 WalCheckpointMode::Passive => "wal_checkpoint(PASSIVE)",
1928 WalCheckpointMode::Full => "wal_checkpoint(FULL)",
1929 WalCheckpointMode::Restart => "wal_checkpoint(RESTART)",
1930 WalCheckpointMode::Truncate => "wal_checkpoint(TRUNCATE)",
1931 WalCheckpointMode::Noop => "wal_checkpoint(NOOP)",
1932 });
1933 Ok(())
1934 }
1935}
1936
1937impl QueryId for WalCheckpoint<'_> {
1939 type QueryId = ();
1940
1941 const HAS_STATIC_QUERY_ID: bool = false;
1942}
1943
1944impl Query for WalCheckpoint<'_> {
1945 type SqlType = (
1946 crate::sql_types::Integer,
1947 crate::sql_types::BigInt,
1948 crate::sql_types::BigInt,
1949 );
1950}
1951
1952impl RunQueryDslSupport for WalCheckpoint<'_> {}
1953
1954#[cfg(test)]
1955mod tests {
1956 use super::*;
1957 use crate::dsl::sql;
1958 use crate::prelude::*;
1959 use crate::sql_types::{Integer, Text};
1960 use crate::sqlite::SqliteFunctionBehavior;
1961
1962 fn connection() -> SqliteConnection {
1963 SqliteConnection::establish(":memory:").unwrap()
1964 }
1965
1966 #[diesel_test_helper::test]
1967 #[allow(unsafe_code)]
1968 fn with_raw_connection_can_return_values() {
1969 let connection = &mut connection();
1970
1971 let autocommit_status = unsafe {
1973 connection.with_raw_connection(|raw_conn| ffi::sqlite3_get_autocommit(raw_conn))
1974 };
1975
1976 assert_ne!(autocommit_status, 0, "Expected autocommit to be enabled");
1978 }
1979
1980 #[diesel_test_helper::test]
1981 #[allow(unsafe_code)]
1982 fn with_raw_connection_works_after_diesel_operations() {
1983 let connection = &mut connection();
1984
1985 crate::sql_query("CREATE TABLE test_table (id INTEGER PRIMARY KEY, value TEXT)")
1987 .execute(connection)
1988 .unwrap();
1989 crate::sql_query("INSERT INTO test_table (value) VALUES ('hello')")
1990 .execute(connection)
1991 .unwrap();
1992
1993 let last_rowid = unsafe {
1995 connection.with_raw_connection(|raw_conn| ffi::sqlite3_last_insert_rowid(raw_conn))
1996 };
1997
1998 assert_eq!(last_rowid, 1, "Last insert rowid should be 1");
1999
2000 let count: i64 = sql::<crate::sql_types::BigInt>("SELECT COUNT(*) FROM test_table")
2002 .get_result(connection)
2003 .unwrap();
2004 assert_eq!(count, 1);
2005 }
2006
2007 #[diesel_test_helper::test]
2008 #[allow(unsafe_code)]
2009 fn with_raw_connection_can_execute_raw_sql() {
2010 let connection = &mut connection();
2011
2012 crate::sql_query("CREATE TABLE raw_test (id INTEGER PRIMARY KEY, name TEXT)")
2014 .execute(connection)
2015 .unwrap();
2016
2017 let result = unsafe {
2020 connection.with_raw_connection(|raw_conn| {
2021 let sql = c"INSERT INTO raw_test (name) VALUES ('from_raw')";
2022 let mut err_msg: *mut libc::c_char = core::ptr::null_mut();
2023 let rc = ffi::sqlite3_exec(
2024 raw_conn,
2025 sql.as_ptr(),
2026 None,
2027 core::ptr::null_mut(),
2028 &mut err_msg,
2029 );
2030 if rc != ffi::SQLITE_OK && !err_msg.is_null() {
2031 ffi::sqlite3_free(err_msg as *mut libc::c_void);
2032 }
2033 rc
2034 })
2035 };
2036
2037 assert_eq!(result, ffi::SQLITE_OK, "Raw SQL execution should succeed");
2038
2039 let count: i64 = sql::<crate::sql_types::BigInt>("SELECT COUNT(*) FROM raw_test")
2041 .get_result(connection)
2042 .unwrap();
2043 assert_eq!(count, 1);
2044
2045 let name: String = sql::<Text>("SELECT name FROM raw_test WHERE id = 1")
2046 .get_result(connection)
2047 .unwrap();
2048 assert_eq!(name, "from_raw");
2049 }
2050
2051 #[diesel_test_helper::test]
2052 #[allow(unsafe_code)]
2053 fn with_raw_connection_works_within_transaction() {
2054 let connection = &mut connection();
2055
2056 crate::sql_query("CREATE TABLE txn_test (id INTEGER PRIMARY KEY, value INTEGER)")
2057 .execute(connection)
2058 .unwrap();
2059
2060 connection
2061 .transaction::<_, crate::result::Error, _>(|conn| {
2062 crate::sql_query("INSERT INTO txn_test (value) VALUES (42)")
2063 .execute(conn)
2064 .unwrap();
2065
2066 let autocommit = unsafe {
2068 conn.with_raw_connection(|raw_conn| ffi::sqlite3_get_autocommit(raw_conn))
2069 };
2070
2071 assert_eq!(
2073 autocommit, 0,
2074 "Autocommit should be disabled inside transaction"
2075 );
2076
2077 Ok(())
2078 })
2079 .unwrap();
2080
2081 let autocommit = unsafe {
2083 connection.with_raw_connection(|raw_conn| ffi::sqlite3_get_autocommit(raw_conn))
2084 };
2085 assert_ne!(
2086 autocommit, 0,
2087 "Autocommit should be enabled after transaction"
2088 );
2089 }
2090
2091 #[diesel_test_helper::test]
2092 #[allow(unsafe_code)]
2093 fn with_raw_connection_can_read_database_filename() {
2094 let connection = &mut connection();
2095
2096 let filename = unsafe {
2098 connection.with_raw_connection(|raw_conn| {
2099 let db_name = c"main";
2100 let filename_ptr = ffi::sqlite3_db_filename(raw_conn, db_name.as_ptr());
2101 if filename_ptr.is_null() {
2102 None
2103 } else {
2104 let cstr = core::ffi::CStr::from_ptr(filename_ptr);
2106 Some(cstr.to_string_lossy().into_owned())
2107 }
2108 })
2109 };
2110
2111 assert_eq!(
2114 filename,
2115 Some(String::new()),
2116 "In-memory database filename should be an empty string"
2117 );
2118 }
2119
2120 #[diesel_test_helper::test]
2121 #[allow(unsafe_code)]
2122 fn with_raw_connection_changes_count() {
2123 let connection = &mut connection();
2124
2125 crate::sql_query("CREATE TABLE changes_test (id INTEGER PRIMARY KEY, value INTEGER)")
2126 .execute(connection)
2127 .unwrap();
2128
2129 crate::sql_query("INSERT INTO changes_test (value) VALUES (1), (2), (3)")
2130 .execute(connection)
2131 .unwrap();
2132
2133 let changes = unsafe {
2135 connection.with_raw_connection(|raw_conn| {
2136 let sql = c"UPDATE changes_test SET value = value + 10";
2137 let mut err_msg: *mut libc::c_char = core::ptr::null_mut();
2138 let rc = ffi::sqlite3_exec(
2139 raw_conn,
2140 sql.as_ptr(),
2141 None,
2142 core::ptr::null_mut(),
2143 &mut err_msg,
2144 );
2145 if rc != ffi::SQLITE_OK && !err_msg.is_null() {
2146 ffi::sqlite3_free(err_msg as *mut libc::c_void);
2147 return -1;
2148 }
2149 ffi::sqlite3_changes(raw_conn)
2150 })
2151 };
2152
2153 assert_eq!(changes, 3, "Should have updated 3 rows");
2154
2155 let values: Vec<i32> = sql::<Integer>("SELECT value FROM changes_test ORDER BY id")
2157 .load(connection)
2158 .unwrap();
2159 assert_eq!(values, vec![11, 12, 13]);
2160 }
2161
2162 #[diesel_test_helper::test]
2164 #[allow(unsafe_code)]
2165 #[cfg(not(all(target_family = "wasm", target_os = "unknown")))]
2166 fn with_raw_connection_recovers_after_panic() {
2167 let connection = &mut connection();
2168
2169 crate::sql_query("CREATE TABLE panic_test (id INTEGER PRIMARY KEY, value TEXT)")
2170 .execute(connection)
2171 .unwrap();
2172
2173 let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| unsafe {
2175 connection.with_raw_connection(|_raw_conn| {
2176 panic!("intentional panic inside with_raw_connection");
2177 })
2178 }));
2179 assert!(result.is_err(), "Should have caught the panic");
2180
2181 crate::sql_query("INSERT INTO panic_test (value) VALUES ('after_panic')")
2183 .execute(connection)
2184 .unwrap();
2185
2186 let count: i64 = sql::<crate::sql_types::BigInt>("SELECT COUNT(*) FROM panic_test")
2187 .get_result(connection)
2188 .unwrap();
2189 assert_eq!(count, 1, "Connection should work after panic in callback");
2190 }
2191
2192 #[diesel_test_helper::test]
2194 #[allow(unsafe_code)]
2195 #[cfg(not(all(target_family = "wasm", target_os = "unknown")))]
2196 fn with_raw_connection_can_read_file_database_filename() {
2197 let dir = std::env::temp_dir().join("diesel_test_filename.db");
2198 let db_path = dir.to_str().unwrap();
2199
2200 let _ = std::fs::remove_file(db_path);
2202
2203 let connection = &mut SqliteConnection::establish(db_path).unwrap();
2204
2205 let filename = unsafe {
2207 connection.with_raw_connection(|raw_conn| {
2208 let db_name = c"main";
2209 let filename_ptr = ffi::sqlite3_db_filename(raw_conn, db_name.as_ptr());
2210 if filename_ptr.is_null() {
2211 None
2212 } else {
2213 let cstr = core::ffi::CStr::from_ptr(filename_ptr);
2214 Some(cstr.to_string_lossy().into_owned())
2215 }
2216 })
2217 };
2218
2219 let filename = filename.expect("File-based database should have a filename");
2220 assert!(
2221 filename.contains("diesel_test_filename.db"),
2222 "Filename should contain the database name, got: {filename}"
2223 );
2224
2225 let _ = std::fs::remove_file(db_path);
2227 }
2228
2229 #[declare_sql_function]
2230 extern "SQL" {
2231 fn fun_case(x: Text) -> Text;
2232 fn my_add(x: Integer, y: Integer) -> Integer;
2233 fn answer() -> Integer;
2234 fn add_counter(x: Integer) -> Integer;
2235
2236 #[aggregate]
2237 fn my_sum(expr: Integer) -> Integer;
2238 #[aggregate]
2239 fn range_max(expr1: Integer, expr2: Integer, expr3: Integer) -> Nullable<Integer>;
2240 }
2241
2242 #[diesel_test_helper::test]
2243 fn database_serializes_and_deserializes_successfully() {
2244 let expected_users = vec![
2245 (
2246 1,
2247 "John Doe".to_string(),
2248 "john.doe@example.com".to_string(),
2249 ),
2250 (
2251 2,
2252 "Jane Doe".to_string(),
2253 "jane.doe@example.com".to_string(),
2254 ),
2255 ];
2256
2257 let conn1 = &mut connection();
2258 let _ =
2259 crate::sql_query("CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT, email TEXT)")
2260 .execute(conn1);
2261 let _ = crate::sql_query("INSERT INTO users (name, email) VALUES ('John Doe', 'john.doe@example.com'), ('Jane Doe', 'jane.doe@example.com')")
2262 .execute(conn1);
2263
2264 for _i in 0..2 {
2265 let serialized_database = conn1.serialize_database_to_buffer();
2266 let conn2 = &mut connection();
2267 conn2
2268 .deserialize_readonly_database_from_buffer(
2269 serialized_database.try_as_slice().unwrap(),
2270 )
2271 .unwrap();
2272
2273 let query =
2274 sql::<(Integer, Text, Text)>("SELECT id, name, email FROM users ORDER BY id");
2275 let actual_users = query.load::<(i32, String, String)>(conn2).unwrap();
2276
2277 assert_eq!(expected_users, actual_users);
2278 std::mem::drop(serialized_database);
2282 let query =
2283 sql::<(Integer, Text, Text)>("SELECT id, name, email FROM users ORDER BY id");
2284 let actual_users = query.load::<(i32, String, String)>(conn2).unwrap();
2285
2286 assert_eq!(expected_users, actual_users);
2287 }
2288 }
2289
2290 #[diesel_test_helper::test]
2291 fn database_deserialize_random_bytes() {
2292 let buffer = vec![0, 1, 2, 3, 4];
2293 let conn = &mut SqliteConnection::establish(":memory:").unwrap();
2294
2295 conn.deserialize_readonly_database_from_buffer(&buffer)
2296 .unwrap();
2297
2298 let r = sql::<Integer>("SELECT id FROM users").load::<i32>(conn);
2299
2300 assert!(r.is_err());
2301 assert_eq!(r.unwrap_err().to_string(), "file is not a database");
2302
2303 let conn = &mut SqliteConnection::establish(":memory:").unwrap();
2304
2305 let _ =
2306 crate::sql_query("CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT, email TEXT)")
2307 .execute(conn);
2308 let _ = crate::sql_query("INSERT INTO users (name, email) VALUES ('John Doe', 'john.doe@example.com'), ('Jane Doe', 'jane.doe@example.com')")
2309 .execute(conn);
2310
2311 let db = conn.serialize_database_to_buffer();
2312 let mut bad_buffer = db[..100].to_vec();
2314 bad_buffer.extend(b"whatever");
2315 conn.deserialize_readonly_database_from_buffer(&bad_buffer)
2316 .unwrap();
2317
2318 let r = sql::<Integer>("SELECT id FROM users").load::<i32>(conn);
2319
2320 assert!(r.is_err());
2321 assert_eq!(
2322 r.unwrap_err().to_string(),
2323 "database disk image is malformed"
2324 );
2325
2326 let mut size_fitting_bad_buffer = db[..100].to_vec();
2328 size_fitting_bad_buffer.extend(
2329 core::iter::repeat(b"abcdefghij")
2330 .flatten()
2331 .take(db.len() - 100),
2332 );
2333 let r = conn.deserialize_readonly_database_from_buffer(&size_fitting_bad_buffer);
2334
2335 assert!(r.is_err());
2336 assert_eq!(
2337 r.unwrap_err().to_string(),
2338 "database disk image is malformed"
2339 );
2340 }
2341
2342 #[diesel_test_helper::test]
2343 fn database_serializes_empty_deserialized_database() {
2344 let conn = &mut SqliteConnection::establish(":memory:").unwrap();
2345 conn.deserialize_readonly_database_from_buffer(&[]).unwrap();
2346
2347 let serialized = conn.serialize_database_to_buffer();
2348
2349 assert!(serialized.is_empty());
2350 assert!(serialized.try_as_slice().unwrap().is_empty());
2351 }
2352
2353 #[cfg(all(
2354 feature = "std",
2355 not(all(target_family = "wasm", target_os = "unknown"))
2356 ))]
2357 #[allow(unsafe_code)]
2358 mod sqlite_serialize_oom {
2359 use super::super::oom_test_support::{panic_message, run_in_child, with_heap_limit};
2360 use super::super::{SerializedDatabase, ffi};
2361 use crate::connection::{Connection, SimpleConnection};
2362 use crate::sqlite::SqliteConnection;
2363
2364 const MIN_DATABASE_BYTES: i64 = 1_048_576;
2365
2366 fn with_failing_serialize<R>(f: impl FnOnce() -> R) -> R {
2369 with_heap_limit(65_536, f)
2370 }
2371
2372 #[test]
2373 fn sqlite_serialize_oom_is_contained() {
2374 run_in_child(|| {
2375 let mut conn = large_database();
2376
2377 let (baseline_size, baseline) = serialize_direct(&conn);
2378 assert!(
2379 baseline_size >= MIN_DATABASE_BYTES,
2380 "the serialized database is smaller than 1 MiB"
2381 );
2382 assert!(
2383 !baseline.is_null(),
2384 "SQLite refused to serialize a valid database"
2385 );
2386 unsafe { ffi::sqlite3_free(baseline as _) };
2388
2389 let (reported_size, data) = with_failing_serialize(|| serialize_direct(&conn));
2390 if !data.is_null() {
2391 unsafe { ffi::sqlite3_free(data as _) };
2393 }
2394 assert!(
2395 data.is_null(),
2396 "SQLite did not fail the output allocation of the serialization"
2397 );
2398 assert!(
2400 reported_size >= MIN_DATABASE_BYTES,
2401 "SQLite reported a serialization size of {reported_size} with a null buffer"
2402 );
2403
2404 let serialized: SerializedDatabase =
2405 with_failing_serialize(|| conn.serialize_database_to_buffer());
2406 let error = serialized
2407 .try_as_slice()
2408 .expect_err("the failed output allocation must surface as an error");
2409 assert_eq!(error.to_string(), "out of memory");
2410
2411 let payload = std::panic::catch_unwind(core::panic::AssertUnwindSafe(|| {
2412 core::hint::black_box(serialized[0]);
2413 }))
2414 .expect_err("the serialized database access did not panic");
2415 let message = panic_message(&*payload);
2416 assert!(
2417 message.contains("Cannot access the serialized database: out of memory"),
2418 "SQLite serialization allocation failure surfaced as `{message}` instead \
2419 of a caught allocation panic"
2420 );
2421 });
2422 }
2423
2424 fn large_database() -> SqliteConnection {
2425 let mut conn = SqliteConnection::establish(":memory:").unwrap();
2426 conn.batch_execute(&format!(
2427 "CREATE TABLE blobs (id INTEGER PRIMARY KEY, payload BLOB);
2428 INSERT INTO blobs (payload) VALUES (zeroblob({MIN_DATABASE_BYTES}));"
2429 ))
2430 .unwrap();
2431 conn
2432 }
2433
2434 fn serialize_direct(conn: &SqliteConnection) -> (ffi::sqlite3_int64, *mut u8) {
2435 unsafe {
2437 let mut size: ffi::sqlite3_int64 = 0;
2438 let data = ffi::sqlite3_serialize(
2439 conn.raw_connection.internal_connection.as_ptr(),
2440 core::ptr::null(),
2441 &mut size as *mut _,
2442 0,
2443 );
2444 (size, data)
2445 }
2446 }
2447 }
2448
2449 #[diesel_test_helper::test]
2450 fn register_custom_function() {
2451 let connection = &mut connection();
2452 fun_case_utils::register_impl(connection, |x: String| {
2453 x.chars()
2454 .enumerate()
2455 .map(|(i, c)| {
2456 if i % 2 == 0 {
2457 c.to_lowercase().to_string()
2458 } else {
2459 c.to_uppercase().to_string()
2460 }
2461 })
2462 .collect::<String>()
2463 })
2464 .unwrap();
2465
2466 let mapped_string = crate::select(fun_case("foobar"))
2467 .get_result::<String>(connection)
2468 .unwrap();
2469 assert_eq!("fOoBaR", mapped_string);
2470 }
2471
2472 #[diesel_test_helper::test]
2473 fn register_multiarg_function() {
2474 let connection = &mut connection();
2475 my_add_utils::register_impl(connection, |x: i32, y: i32| x + y).unwrap();
2476
2477 let added = crate::select(my_add(1, 2)).get_result::<i32>(connection);
2478 assert_eq!(Ok(3), added);
2479 }
2480
2481 #[diesel_test_helper::test]
2482 fn register_noarg_function() {
2483 let connection = &mut connection();
2484 answer_utils::register_impl(connection, || 42).unwrap();
2485
2486 let answer = crate::select(answer()).get_result::<i32>(connection);
2487 assert_eq!(Ok(42), answer);
2488 }
2489
2490 #[diesel_test_helper::test]
2491 fn register_nondeterministic_noarg_function() {
2492 let connection = &mut connection();
2493 answer_utils::register_nondeterministic_impl(connection, || 42).unwrap();
2494
2495 let answer = crate::select(answer()).get_result::<i32>(connection);
2496 assert_eq!(Ok(42), answer);
2497 }
2498
2499 #[diesel_test_helper::test]
2500 fn register_nondeterministic_function() {
2501 let connection = &mut connection();
2502 let mut y = 0;
2503 add_counter_utils::register_nondeterministic_impl(connection, move |x: i32| {
2504 y += 1;
2505 x + y
2506 })
2507 .unwrap();
2508
2509 let added = crate::select((add_counter(1), add_counter(1), add_counter(1)))
2510 .get_result::<(i32, i32, i32)>(connection);
2511 assert_eq!(Ok((2, 3, 4)), added);
2512 }
2513
2514 #[derive(Default)]
2515 struct MySum {
2516 sum: i32,
2517 }
2518
2519 impl SqliteAggregateFunction<i32> for MySum {
2520 type Output = i32;
2521
2522 fn step(&mut self, expr: i32) {
2523 self.sum += expr;
2524 }
2525
2526 fn finalize(aggregator: Option<Self>) -> Self::Output {
2527 aggregator.map(|a| a.sum).unwrap_or_default()
2528 }
2529 }
2530
2531 table! {
2532 my_sum_example {
2533 id -> Integer,
2534 value -> Integer,
2535 }
2536 }
2537
2538 #[diesel_test_helper::test]
2539 fn register_aggregate_function() {
2540 use self::my_sum_example::dsl::*;
2541
2542 let connection = &mut connection();
2543 crate::sql_query(
2544 "CREATE TABLE my_sum_example (id integer primary key autoincrement, value integer)",
2545 )
2546 .execute(connection)
2547 .unwrap();
2548 crate::sql_query("INSERT INTO my_sum_example (value) VALUES (1), (2), (3)")
2549 .execute(connection)
2550 .unwrap();
2551
2552 my_sum_utils::register_impl_with_behavior::<MySum, _>(
2553 connection,
2554 SqliteFunctionBehavior::DETERMINISTIC,
2555 )
2556 .unwrap();
2557
2558 let result = my_sum_example
2559 .select(my_sum(value))
2560 .get_result::<i32>(connection);
2561 assert_eq!(Ok(6), result);
2562 }
2563
2564 #[diesel_test_helper::test]
2565 fn register_aggregate_function_returns_finalize_default_on_empty_set() {
2566 use self::my_sum_example::dsl::*;
2567
2568 let connection = &mut connection();
2569 crate::sql_query(
2570 "CREATE TABLE my_sum_example (id integer primary key autoincrement, value integer)",
2571 )
2572 .execute(connection)
2573 .unwrap();
2574
2575 my_sum_utils::register_impl_with_behavior::<MySum, _>(
2576 connection,
2577 SqliteFunctionBehavior::DETERMINISTIC,
2578 )
2579 .unwrap();
2580
2581 let result = my_sum_example
2582 .select(my_sum(value))
2583 .get_result::<i32>(connection);
2584 assert_eq!(Ok(0), result);
2585 }
2586
2587 #[derive(Default)]
2588 struct RangeMax<T> {
2589 max_value: Option<T>,
2590 }
2591
2592 impl<T: Default + Ord + Copy + Clone> SqliteAggregateFunction<(T, T, T)> for RangeMax<T> {
2593 type Output = Option<T>;
2594
2595 fn step(&mut self, (x0, x1, x2): (T, T, T)) {
2596 let max = if x0 >= x1 && x0 >= x2 {
2597 x0
2598 } else if x1 >= x0 && x1 >= x2 {
2599 x1
2600 } else {
2601 x2
2602 };
2603
2604 self.max_value = match self.max_value {
2605 Some(current_max_value) if max > current_max_value => Some(max),
2606 None => Some(max),
2607 _ => self.max_value,
2608 };
2609 }
2610
2611 fn finalize(aggregator: Option<Self>) -> Self::Output {
2612 aggregator?.max_value
2613 }
2614 }
2615
2616 table! {
2617 range_max_example {
2618 id -> Integer,
2619 value1 -> Integer,
2620 value2 -> Integer,
2621 value3 -> Integer,
2622 }
2623 }
2624
2625 #[diesel_test_helper::test]
2626 fn register_aggregate_multiarg_function() {
2627 use self::range_max_example::dsl::*;
2628
2629 let connection = &mut connection();
2630 crate::sql_query(
2631 r#"CREATE TABLE range_max_example (
2632 id integer primary key autoincrement,
2633 value1 integer,
2634 value2 integer,
2635 value3 integer
2636 )"#,
2637 )
2638 .execute(connection)
2639 .unwrap();
2640 crate::sql_query(
2641 "INSERT INTO range_max_example (value1, value2, value3) VALUES (3, 2, 1), (2, 2, 2)",
2642 )
2643 .execute(connection)
2644 .unwrap();
2645
2646 range_max_utils::register_impl_with_behavior::<RangeMax<i32>, _, _, _>(
2647 connection,
2648 SqliteFunctionBehavior::DETERMINISTIC,
2649 )
2650 .unwrap();
2651 let result = range_max_example
2652 .select(range_max(value1, value2, value3))
2653 .get_result::<Option<i32>>(connection)
2654 .unwrap();
2655 assert_eq!(Some(3), result);
2656 }
2657
2658 table! {
2659 my_collation_example {
2660 id -> Integer,
2661 value -> Text,
2662 }
2663 }
2664
2665 #[diesel_test_helper::test]
2666 fn register_collation_function() {
2667 use self::my_collation_example::dsl::*;
2668
2669 let connection = &mut connection();
2670
2671 connection
2672 .register_collation("RUSTNOCASE", |rhs, lhs| {
2673 rhs.to_lowercase().cmp(&lhs.to_lowercase())
2674 })
2675 .unwrap();
2676
2677 crate::sql_query(
2678 "CREATE TABLE my_collation_example (id integer primary key autoincrement, value text collate RUSTNOCASE)",
2679 ).execute(connection)
2680 .unwrap();
2681 crate::sql_query(
2682 "INSERT INTO my_collation_example (value) VALUES ('foo'), ('FOo'), ('f00')",
2683 )
2684 .execute(connection)
2685 .unwrap();
2686
2687 let result = my_collation_example
2688 .filter(value.eq("foo"))
2689 .select(value)
2690 .load::<String>(connection);
2691 assert_eq!(
2692 Ok(&["foo".to_owned(), "FOo".to_owned()][..]),
2693 result.as_ref().map(|vec| vec.as_ref())
2694 );
2695
2696 let result = my_collation_example
2697 .filter(value.eq("FOO"))
2698 .select(value)
2699 .load::<String>(connection);
2700 assert_eq!(
2701 Ok(&["foo".to_owned(), "FOo".to_owned()][..]),
2702 result.as_ref().map(|vec| vec.as_ref())
2703 );
2704
2705 let result = my_collation_example
2706 .filter(value.eq("f00"))
2707 .select(value)
2708 .load::<String>(connection);
2709 assert_eq!(
2710 Ok(&["f00".to_owned()][..]),
2711 result.as_ref().map(|vec| vec.as_ref())
2712 );
2713
2714 let result = my_collation_example
2715 .filter(value.eq("F00"))
2716 .select(value)
2717 .load::<String>(connection);
2718 assert_eq!(
2719 Ok(&["f00".to_owned()][..]),
2720 result.as_ref().map(|vec| vec.as_ref())
2721 );
2722
2723 let result = my_collation_example
2724 .filter(value.eq("oof"))
2725 .select(value)
2726 .load::<String>(connection);
2727 assert_eq!(Ok(&[][..]), result.as_ref().map(|vec| vec.as_ref()));
2728 }
2729
2730 #[diesel_test_helper::test]
2732 fn test_correct_serialization_of_owned_strings() {
2733 use crate::prelude::*;
2734
2735 #[derive(Debug, crate::expression::AsExpression)]
2736 #[diesel(sql_type = diesel::sql_types::Text)]
2737 struct CustomWrapper(String);
2738
2739 impl crate::serialize::ToSql<Text, Sqlite> for CustomWrapper {
2740 fn to_sql<'b>(
2741 &'b self,
2742 out: &mut crate::serialize::Output<'b, '_, Sqlite>,
2743 ) -> crate::serialize::Result {
2744 out.set_value(self.0.to_string());
2745 Ok(crate::serialize::IsNull::No)
2746 }
2747 }
2748
2749 let connection = &mut connection();
2750
2751 let res = crate::select(
2752 CustomWrapper("".into())
2753 .into_sql::<crate::sql_types::Text>()
2754 .nullable(),
2755 )
2756 .get_result::<Option<String>>(connection)
2757 .unwrap();
2758 assert_eq!(res, Some(String::new()));
2759 }
2760
2761 #[diesel_test_helper::test]
2762 fn test_correct_serialization_of_owned_bytes() {
2763 use crate::prelude::*;
2764
2765 #[derive(Debug, crate::expression::AsExpression)]
2766 #[diesel(sql_type = diesel::sql_types::Binary)]
2767 struct CustomWrapper(Vec<u8>);
2768
2769 impl crate::serialize::ToSql<crate::sql_types::Binary, Sqlite> for CustomWrapper {
2770 fn to_sql<'b>(
2771 &'b self,
2772 out: &mut crate::serialize::Output<'b, '_, Sqlite>,
2773 ) -> crate::serialize::Result {
2774 out.set_value(self.0.clone());
2775 Ok(crate::serialize::IsNull::No)
2776 }
2777 }
2778
2779 let connection = &mut connection();
2780
2781 let res = crate::select(
2782 CustomWrapper(Vec::new())
2783 .into_sql::<crate::sql_types::Binary>()
2784 .nullable(),
2785 )
2786 .get_result::<Option<Vec<u8>>>(connection)
2787 .unwrap();
2788 assert_eq!(res, Some(Vec::new()));
2789 }
2790
2791 #[diesel_test_helper::test]
2792 fn correctly_handle_empty_query() {
2793 let check_empty_query_error = |r: crate::QueryResult<usize>| {
2794 assert!(r.is_err());
2795 let err = r.unwrap_err();
2796 assert!(
2797 matches!(err, crate::result::Error::QueryBuilderError(ref b) if b.is::<crate::result::EmptyQuery>()),
2798 "Expected a query builder error, but got {err}"
2799 );
2800 };
2801 let connection = &mut SqliteConnection::establish(":memory:").unwrap();
2802 check_empty_query_error(crate::sql_query("").execute(connection));
2803 check_empty_query_error(crate::sql_query(" ").execute(connection));
2804 check_empty_query_error(crate::sql_query("\n\t").execute(connection));
2805 check_empty_query_error(crate::sql_query("-- SELECT 1;").execute(connection));
2806 }
2807
2808 #[diesel_test_helper::test]
2809 fn last_insert_rowid_returns_none_on_fresh_connection() {
2810 let conn = &mut connection();
2811 assert_eq!(conn.last_insert_rowid(), None);
2812 }
2813
2814 #[diesel_test_helper::test]
2815 fn last_insert_rowid_returns_rowid_after_insert() {
2816 let conn = &mut connection();
2817 crate::sql_query("CREATE TABLE li_test (id INTEGER PRIMARY KEY, val TEXT NOT NULL)")
2818 .execute(conn)
2819 .unwrap();
2820
2821 crate::sql_query("INSERT INTO li_test (val) VALUES ('a')")
2822 .execute(conn)
2823 .unwrap();
2824 assert_eq!(conn.last_insert_rowid(), NonZeroI64::new(1));
2825
2826 crate::sql_query("INSERT INTO li_test (val) VALUES ('b')")
2827 .execute(conn)
2828 .unwrap();
2829 assert_eq!(conn.last_insert_rowid(), NonZeroI64::new(2));
2830 }
2831
2832 #[diesel_test_helper::test]
2833 fn last_insert_rowid_unchanged_after_failed_insert() {
2834 let conn = &mut connection();
2835 crate::sql_query(
2836 "CREATE TABLE li_test2 (id INTEGER PRIMARY KEY, val TEXT NOT NULL UNIQUE)",
2837 )
2838 .execute(conn)
2839 .unwrap();
2840
2841 crate::sql_query("INSERT INTO li_test2 (val) VALUES ('a')")
2842 .execute(conn)
2843 .unwrap();
2844 let rowid = conn.last_insert_rowid();
2845 assert_eq!(rowid, NonZeroI64::new(1));
2846
2847 let result = crate::sql_query("INSERT INTO li_test2 (val) VALUES ('a')").execute(conn);
2849 assert!(result.is_err());
2850
2851 assert_eq!(conn.last_insert_rowid(), NonZeroI64::new(1));
2853 }
2854
2855 #[diesel_test_helper::test]
2856 fn last_insert_rowid_with_explicit_rowid() {
2857 let conn = &mut connection();
2858 crate::sql_query("CREATE TABLE li_test3 (id INTEGER PRIMARY KEY, val TEXT NOT NULL)")
2859 .execute(conn)
2860 .unwrap();
2861
2862 crate::sql_query("INSERT INTO li_test3 (id, val) VALUES (42, 'a')")
2863 .execute(conn)
2864 .unwrap();
2865 assert_eq!(conn.last_insert_rowid(), NonZeroI64::new(42));
2866 }
2867
2868 #[diesel_test_helper::test]
2869 fn last_insert_rowid_unchanged_after_delete_and_update() {
2870 let conn = &mut connection();
2871 crate::sql_query("CREATE TABLE li_test4 (id INTEGER PRIMARY KEY, val TEXT NOT NULL)")
2872 .execute(conn)
2873 .unwrap();
2874
2875 crate::sql_query("INSERT INTO li_test4 (val) VALUES ('a')")
2876 .execute(conn)
2877 .unwrap();
2878 let rowid = conn.last_insert_rowid();
2879 assert_eq!(rowid, NonZeroI64::new(1));
2880
2881 crate::sql_query("UPDATE li_test4 SET val = 'b' WHERE id = 1")
2882 .execute(conn)
2883 .unwrap();
2884 assert_eq!(conn.last_insert_rowid(), NonZeroI64::new(1));
2885
2886 crate::sql_query("DELETE FROM li_test4 WHERE id = 1")
2887 .execute(conn)
2888 .unwrap();
2889 assert_eq!(conn.last_insert_rowid(), NonZeroI64::new(1));
2890 }
2891
2892 #[diesel_test_helper::test]
2893 fn read_bytes_from_blob() {
2894 table! {
2895 blobs {
2896 id -> Integer,
2897 data -> Blob,
2898 data2 -> Blob,
2899 }
2900 }
2901
2902 use std::io::Read;
2903
2904 let conn = &mut connection();
2905
2906 let _ =
2907 crate::sql_query("CREATE TABLE blobs (id INTEGER PRIMARY KEY, data BLOB, data2 BLOB)")
2908 .execute(conn);
2909
2910 let _ = crate::sql_query(
2911 "INSERT INTO blobs (data, data2) VALUES ('abc', 'def'), ('123', '456')",
2912 )
2913 .execute(conn);
2914
2915 let mut data = conn.get_read_only_blob(blobs::data, 1).unwrap();
2916 let mut buf = vec![];
2917 data.read_to_end(&mut buf).unwrap();
2918
2919 assert_eq!(buf, b"abc");
2920
2921 let mut data2 = conn.get_read_only_blob(blobs::data2, 1).unwrap();
2922 let mut buf = vec![];
2923 data2.read_to_end(&mut buf).unwrap();
2924
2925 assert_eq!(buf, b"def");
2926 }
2927
2928 #[diesel_test_helper::test]
2929 fn read_seek_bytes() {
2930 table! {
2931 blobs {
2932 id -> Integer,
2933 data -> Blob,
2934 }
2935 }
2936
2937 use std::io::Read;
2938 use std::io::Seek;
2939 use std::io::SeekFrom;
2940
2941 let conn = &mut connection();
2942
2943 let _ = crate::sql_query("CREATE TABLE blobs (id INTEGER PRIMARY KEY, data BLOB)")
2944 .execute(conn);
2945
2946 let _ = crate::sql_query("INSERT INTO blobs (data) VALUES ('abcdefghi')").execute(conn);
2947
2948 let mut data = conn.get_read_only_blob(blobs::data, 1).unwrap();
2949
2950 let mut buf = [0; 1];
2951 assert_eq!(data.read(&mut buf).unwrap(), 1);
2952 assert_eq!(&buf, b"a");
2953
2954 assert_eq!(data.seek(SeekFrom::Current(1)).unwrap(), 2);
2956
2957 let mut buf = [0; 1];
2958 assert_eq!(data.read(&mut buf).unwrap(), 1);
2959 assert_eq!(&buf, b"c");
2960
2961 assert_eq!(data.seek(SeekFrom::Start(0)).unwrap(), 0);
2963
2964 let mut buf = [0; 1];
2965 assert_eq!(data.read(&mut buf).unwrap(), 1);
2966 assert_eq!(&buf, b"a");
2967
2968 assert_eq!(data.seek(SeekFrom::Current(-10)).unwrap(), 0);
2970
2971 let mut buf = [0; 1];
2972 assert_eq!(data.read(&mut buf).unwrap(), 1);
2973 assert_eq!(&buf, b"a");
2974
2975 data.seek(SeekFrom::Current(100)).unwrap();
2977
2978 let mut buf = [0; 1];
2980 assert_eq!(data.read(&mut buf).unwrap(), 0);
2981 }
2982
2983 #[diesel_test_helper::test]
2984 fn use_conn_after_blob_drop() {
2985 table! {
2986 blobs {
2987 id -> Integer,
2988 data -> Blob,
2989 }
2990 }
2991
2992 let conn = &mut connection();
2993
2994 let _ = crate::sql_query("CREATE TABLE blobs (id INTEGER PRIMARY KEY, data BLOB)")
2995 .execute(conn);
2996
2997 let _ = crate::sql_query("INSERT INTO blobs (data) VALUES ('abc')").execute(conn);
2998
2999 let data = conn.get_read_only_blob(blobs::data, 1).unwrap();
3000 drop(data);
3001
3002 let _ = crate::sql_query("INSERT INTO blobs (data) VALUES ('def')").execute(conn);
3003 }
3004
3005 #[diesel_test_helper::test]
3006 fn blob_transaction() {
3007 table! {
3008 blobs {
3009 id -> Integer,
3010 data -> Blob,
3011 }
3012 }
3013
3014 use std::io::Read;
3015
3016 let conn = &mut connection();
3017
3018 let _ = crate::sql_query("CREATE TABLE blobs (id INTEGER PRIMARY KEY, data BLOB)")
3019 .execute(conn);
3020
3021 let _ = crate::sql_query("INSERT INTO blobs (data) VALUES ('abc')").execute(conn);
3022
3023 {
3024 let mut data = conn.get_read_only_blob(blobs::data, 1).unwrap();
3025 let mut buf = vec![];
3026 data.read_to_end(&mut buf).unwrap();
3027 assert_eq!(buf, b"abc");
3028 }
3029
3030 let res = conn.exclusive_transaction(|conn| {
3031 crate::sql_query("UPDATE blobs SET data = 'def' WHERE id = 1").execute(conn)?;
3032
3033 let mut data = conn.get_read_only_blob(blobs::data, 1).unwrap();
3034 let mut buf = vec![];
3035 data.read_to_end(&mut buf).unwrap();
3036 assert_eq!(buf, b"def");
3037
3038 Result::<(), _>::Err(Error::RollbackTransaction)
3039 });
3040
3041 assert_eq!(res.unwrap_err(), Error::RollbackTransaction);
3042
3043 let mut data = conn.get_read_only_blob(blobs::data, 1).unwrap();
3044 let mut buf = vec![];
3045 data.read_to_end(&mut buf).unwrap();
3046 assert_eq!(buf, b"abc");
3047 }
3048
3049 #[diesel_test_helper::test]
3050 fn aggregate_function_works_with_aligned_data() {
3051 #[derive(Debug, Default)]
3052 #[repr(align(64))]
3053 struct OverAligned;
3054
3055 impl SqliteAggregateFunction<i32> for OverAligned {
3056 type Output = i64;
3057
3058 fn step(&mut self, _value: i32) {
3059 let need = core::mem::align_of::<Self>();
3060 let got = core::mem::align_of_val(self);
3061 assert_eq!(need, got);
3062 }
3063
3064 fn finalize(_agg: Option<Self>) -> i64 {
3065 0
3066 }
3067 }
3068 #[declare_sql_function]
3069 extern "SQL" {
3070 #[aggregate]
3071 fn over_aligned_sum(x: Integer) -> diesel::sql_types::BigInt;
3072 }
3073
3074 let mut conn = SqliteConnection::establish(":memory:").unwrap();
3075 over_aligned_sum_utils::register_impl::<OverAligned, _>(&mut conn).unwrap();
3076
3077 diesel::select(over_aligned_sum(1))
3078 .execute(&mut conn)
3079 .unwrap();
3080 }
3081
3082 #[diesel_test_helper::test]
3083 fn sum_twice() {
3084 #[derive(Default)]
3085 struct Sum(i32);
3086
3087 impl SqliteAggregateFunction<i32> for Sum {
3088 type Output = i32;
3089
3090 fn step(&mut self, value: i32) {
3091 self.0 += value;
3092 }
3093
3094 fn finalize(agg: Option<Self>) -> i32 {
3095 agg.map(|s| s.0).unwrap_or_default()
3096 }
3097 }
3098
3099 #[declare_sql_function]
3100 extern "SQL" {
3101 #[aggregate]
3102 fn my_sum(x: Integer) -> Integer;
3103 }
3104
3105 let mut conn = SqliteConnection::establish(":memory:").unwrap();
3106 my_sum_utils::register_impl::<Sum, _>(&mut conn).unwrap();
3107
3108 conn.batch_execute(
3109 "
3110 CREATE TABLE test(key1 INTEGER, key2 INTEGER);
3111 INSERT INTO test(key1, key2) VALUES (1, 2), (2, 4), (3, 6);
3112",
3113 )
3114 .unwrap();
3115
3116 table! {
3117 test (key1, key2) {
3118 key1 -> Integer,
3119 key2 -> Integer,
3120 }
3121 }
3122
3123 let (first_res, second_res) = test::table
3124 .select((my_sum(test::key1), my_sum(test::key2)))
3125 .get_result::<(i32, i32)>(&mut conn)
3126 .unwrap();
3127
3128 assert_eq!(first_res, 6);
3129 assert_eq!(second_res, 12);
3130
3131 conn.batch_execute("DELETE FROM test").unwrap();
3132 let (first_res, second_res) = test::table
3133 .select((my_sum(test::key1), my_sum(test::key2)))
3134 .get_result::<(i32, i32)>(&mut conn)
3135 .unwrap();
3136
3137 assert_eq!(first_res, 0);
3138 assert_eq!(second_res, 0);
3139 }
3140
3141 #[diesel_test_helper::test]
3142 fn test_injection() {
3143 diesel::table! {
3144 #[sql_name = "quote'table"]
3145 quote_table (id) {
3146 id -> Nullable<Integer>,
3147 name -> Nullable<Text>,
3148 }
3149 }
3150
3151 let mut conn = SqliteConnection::establish(":memory:").unwrap();
3152
3153 conn.batch_execute("CREATE TABLE \"quote'table\" (id INTEGER PRIMARY KEY, name TEXT);")
3154 .unwrap();
3155
3156 diesel::insert_into(quote_table::table)
3157 .values((quote_table::id.eq(1), quote_table::name.eq("Jane")))
3158 .execute(&mut conn)
3159 .unwrap();
3160
3161 let data = quote_table::table
3162 .load::<(Option<i32>, Option<String>)>(&mut conn)
3163 .unwrap();
3164 assert_eq!(data, [(Some(1), Some("Jane".to_owned()))]);
3165 }
3166
3167 #[diesel_test_helper::test]
3168 fn set_limit_returns_previous_value() {
3169 let mut conn = connection();
3170 let original = conn.get_limit(SqliteLimit::SqlLength);
3171
3172 assert_eq!(conn.set_limit(SqliteLimit::SqlLength, 1024), original);
3175 assert_eq!(conn.set_limit(SqliteLimit::SqlLength, 2048), 1024);
3176 assert_eq!(conn.get_limit(SqliteLimit::SqlLength), 2048);
3177 }
3178
3179 #[diesel_test_helper::test]
3180 fn get_limit_does_not_mutate() {
3181 let conn = connection();
3182 let first = conn.get_limit(SqliteLimit::ExprDepth);
3183 assert!(first > 0);
3186 assert_eq!(conn.get_limit(SqliteLimit::ExprDepth), first);
3187 }
3188
3189 #[diesel_test_helper::test]
3190 fn set_limit_enforces_length() {
3191 let mut conn = connection();
3192 conn.set_limit(SqliteLimit::Length, 100);
3193
3194 assert!(
3195 crate::sql_query("SELECT length(randomblob(50))")
3196 .execute(&mut conn)
3197 .is_ok()
3198 );
3199 assert!(
3201 crate::sql_query("SELECT length(randomblob(500))")
3202 .execute(&mut conn)
3203 .is_err()
3204 );
3205 }
3206
3207 #[diesel_test_helper::test]
3208 fn set_limit_enforces_column_count() {
3209 let wide = format!(
3212 "SELECT {}",
3213 (1..=30)
3214 .map(|i| i.to_string())
3215 .collect::<Vec<_>>()
3216 .join(", ")
3217 );
3218
3219 let mut unconstrained = connection();
3220 assert!(crate::sql_query(&wide).execute(&mut unconstrained).is_ok());
3221
3222 let mut conn = connection();
3223 conn.set_limit(SqliteLimit::ColumnCount, 10);
3224 assert!(crate::sql_query(&wide).execute(&mut conn).is_err());
3225 }
3226
3227 #[diesel_test_helper::test]
3228 fn set_limit_enforces_expr_depth() {
3229 let mut conn = connection();
3230 conn.set_limit(SqliteLimit::ExprDepth, 5);
3231
3232 assert!(crate::sql_query("SELECT 1+1").execute(&mut conn).is_ok());
3233 let deep = format!("SELECT {}1", "1+".repeat(40));
3235 assert!(crate::sql_query(&deep).execute(&mut conn).is_err());
3236 }
3237
3238 #[diesel_test_helper::test]
3239 fn set_limit_enforces_compound_select() {
3240 let mut conn = connection();
3241 conn.set_limit(SqliteLimit::CompoundSelect, 2);
3242
3243 assert!(
3244 crate::sql_query("SELECT 1 UNION SELECT 2")
3245 .execute(&mut conn)
3246 .is_ok()
3247 );
3248 assert!(
3250 crate::sql_query(
3251 "SELECT 1 UNION SELECT 2 UNION SELECT 3 UNION SELECT 4 UNION SELECT 5"
3252 )
3253 .execute(&mut conn)
3254 .is_err()
3255 );
3256 }
3257
3258 #[diesel_test_helper::test]
3259 fn set_limit_enforces_vdbe_op() {
3260 let heavy = "SELECT count(*) FROM sqlite_master a, sqlite_master b, sqlite_master c";
3263
3264 let mut unconstrained = connection();
3265 assert!(crate::sql_query(heavy).execute(&mut unconstrained).is_ok());
3266
3267 let mut conn = connection();
3268 conn.set_limit(SqliteLimit::VdbeOp, 5);
3269 assert!(crate::sql_query(heavy).execute(&mut conn).is_err());
3270 }
3271
3272 #[diesel_test_helper::test]
3273 fn set_limit_enforces_function_arg() {
3274 let mut conn = connection();
3275 conn.set_limit(SqliteLimit::FunctionArg, 3);
3276
3277 assert!(
3278 crate::sql_query("SELECT max(1, 2, 3)")
3279 .execute(&mut conn)
3280 .is_ok()
3281 );
3282 assert!(
3284 crate::sql_query("SELECT max(1, 2, 3, 4, 5, 6, 7, 8)")
3285 .execute(&mut conn)
3286 .is_err()
3287 );
3288 }
3289
3290 #[diesel_test_helper::test]
3291 fn set_limit_enforces_attached() {
3292 let mut conn = connection();
3293 conn.set_limit(SqliteLimit::Attached, 0);
3294
3295 assert!(
3297 crate::sql_query("ATTACH DATABASE ':memory:' AS aux_db")
3298 .execute(&mut conn)
3299 .is_err()
3300 );
3301 }
3302
3303 #[diesel_test_helper::test]
3304 fn set_limit_enforces_variable_number() {
3305 let mut conn = connection();
3306 conn.set_limit(
3310 SqliteLimit::VariableNumber,
3311 SqliteLimit::DEFAULT_VARIABLE_NUMBER_LIMIT,
3312 );
3313 let at_limit = format!("SELECT ?{}", SqliteLimit::DEFAULT_VARIABLE_NUMBER_LIMIT);
3314 let past_limit = format!(
3315 "SELECT ?{}",
3316 SqliteLimit::DEFAULT_VARIABLE_NUMBER_LIMIT as i64 + 1
3317 );
3318 assert!(crate::sql_query(&at_limit).execute(&mut conn).is_ok());
3319 assert!(crate::sql_query(&past_limit).execute(&mut conn).is_err());
3320 }
3321
3322 #[diesel_test_helper::test]
3323 fn set_limit_enforces_trigger_depth() {
3324 use crate::connection::SimpleConnection;
3325
3326 let setup = "PRAGMA recursive_triggers = ON;\
3328 CREATE TABLE recur (x INTEGER);\
3329 CREATE TRIGGER recur_tr AFTER INSERT ON recur WHEN NEW.x < 100 \
3330 BEGIN INSERT INTO recur VALUES (NEW.x + 1); END;";
3331
3332 let mut unconstrained = connection();
3334 unconstrained.batch_execute(setup).unwrap();
3335 assert!(
3336 crate::sql_query("INSERT INTO recur VALUES (1)")
3337 .execute(&mut unconstrained)
3338 .is_ok()
3339 );
3340
3341 let mut conn = connection();
3344 conn.set_limit(SqliteLimit::TriggerDepth, 3);
3345 conn.batch_execute(setup).unwrap();
3346 assert!(
3347 crate::sql_query("INSERT INTO recur VALUES (1)")
3348 .execute(&mut conn)
3349 .is_err()
3350 );
3351 }
3352
3353 #[diesel_test_helper::test]
3354 fn worker_threads_limit_has_no_runtime_error_path() {
3355 let mut conn = connection();
3360 conn.set_limit(SqliteLimit::WorkerThreads, 0);
3361 assert_eq!(conn.get_limit(SqliteLimit::WorkerThreads), 0);
3362 assert!(crate::sql_query("SELECT 1").execute(&mut conn).is_ok());
3363 }
3364
3365 #[diesel_test_helper::test]
3366 fn set_limit_enforces_sql_length() {
3367 let mut conn = connection();
3368 conn.set_limit(SqliteLimit::SqlLength, 20);
3369
3370 let result =
3372 crate::sql_query("SELECT * FROM sqlite_master WHERE type = 'table'").execute(&mut conn);
3373 assert!(result.is_err());
3374 }
3375
3376 #[diesel_test_helper::test]
3377 fn set_limit_enforces_like_pattern_length() {
3378 let mut conn = connection();
3379 conn.set_limit(SqliteLimit::LikePatternLength, 100);
3380
3381 assert!(
3382 crate::sql_query("SELECT 'test' LIKE 'te%'")
3383 .execute(&mut conn)
3384 .is_ok()
3385 );
3386
3387 let long_pattern = "%".repeat(200);
3388 let query = format!("SELECT 'test' LIKE '{long_pattern}'");
3389 assert!(crate::sql_query(&query).execute(&mut conn).is_err());
3390 }
3391
3392 #[diesel_test_helper::test]
3393 fn set_limit_clamps_above_compile_time_maximum() {
3394 let mut conn = connection();
3395 conn.set_limit(SqliteLimit::Length, i32::MAX);
3398 let clamped = conn.get_limit(SqliteLimit::Length);
3399 assert!(clamped > 0 && clamped < i32::MAX);
3400 }
3401
3402 #[diesel_test_helper::test]
3403 fn set_recommended_security_limits_applies_documented_table() {
3404 let mut conn = connection();
3405 conn.set_recommended_security_limits();
3406
3407 assert_eq!(conn.get_limit(SqliteLimit::Length), 1_000_000);
3408 assert_eq!(conn.get_limit(SqliteLimit::SqlLength), 100_000);
3409 assert_eq!(conn.get_limit(SqliteLimit::ColumnCount), 100);
3410 assert_eq!(conn.get_limit(SqliteLimit::ExprDepth), 10);
3411 assert_eq!(conn.get_limit(SqliteLimit::CompoundSelect), 3);
3412 assert_eq!(conn.get_limit(SqliteLimit::VdbeOp), 25_000);
3413 assert_eq!(conn.get_limit(SqliteLimit::FunctionArg), 8);
3414 assert_eq!(conn.get_limit(SqliteLimit::Attached), 0);
3415 assert_eq!(conn.get_limit(SqliteLimit::LikePatternLength), 50);
3416 assert_eq!(conn.get_limit(SqliteLimit::VariableNumber), 10);
3417 assert_eq!(conn.get_limit(SqliteLimit::TriggerDepth), 10);
3418 }
3419
3420 #[diesel_test_helper::test]
3421 fn safe_limit_constants_do_not_exceed_defaults() {
3422 let pairs = [
3428 (
3429 SqliteLimit::SAFE_LENGTH_LIMIT,
3430 SqliteLimit::DEFAULT_LENGTH_LIMIT,
3431 ),
3432 (
3433 SqliteLimit::SAFE_SQL_LENGTH_LIMIT,
3434 SqliteLimit::DEFAULT_SQL_LENGTH_LIMIT,
3435 ),
3436 (
3437 SqliteLimit::SAFE_COLUMN_COUNT_LIMIT,
3438 SqliteLimit::DEFAULT_COLUMN_COUNT_LIMIT,
3439 ),
3440 (
3441 SqliteLimit::SAFE_EXPR_DEPTH_LIMIT,
3442 SqliteLimit::DEFAULT_EXPR_DEPTH_LIMIT,
3443 ),
3444 (
3445 SqliteLimit::SAFE_COMPOUND_SELECT_LIMIT,
3446 SqliteLimit::DEFAULT_COMPOUND_SELECT_LIMIT,
3447 ),
3448 (
3449 SqliteLimit::SAFE_VDBE_OP_LIMIT,
3450 SqliteLimit::DEFAULT_VDBE_OP_LIMIT,
3451 ),
3452 (
3453 SqliteLimit::SAFE_FUNCTION_ARG_LIMIT,
3454 SqliteLimit::DEFAULT_FUNCTION_ARG_LIMIT,
3455 ),
3456 (
3457 SqliteLimit::SAFE_ATTACHED_LIMIT,
3458 SqliteLimit::DEFAULT_ATTACHED_LIMIT,
3459 ),
3460 (
3461 SqliteLimit::SAFE_LIKE_PATTERN_LENGTH_LIMIT,
3462 SqliteLimit::DEFAULT_LIKE_PATTERN_LENGTH_LIMIT,
3463 ),
3464 (
3465 SqliteLimit::SAFE_VARIABLE_NUMBER_LIMIT,
3466 SqliteLimit::DEFAULT_VARIABLE_NUMBER_LIMIT,
3467 ),
3468 (
3469 SqliteLimit::SAFE_TRIGGER_DEPTH_LIMIT,
3470 SqliteLimit::DEFAULT_TRIGGER_DEPTH_LIMIT,
3471 ),
3472 (
3473 SqliteLimit::SAFE_WORKER_THREADS_LIMIT,
3474 SqliteLimit::DEFAULT_WORKER_THREADS_LIMIT,
3475 ),
3476 ];
3477 for (safe, default) in pairs {
3478 assert!(
3479 safe <= default,
3480 "safe value {safe} exceeds default {default}"
3481 );
3482 }
3483 }
3484
3485 #[diesel_test_helper::test]
3486 fn safe_limit_constants_match_recommended_setter() {
3487 let mut conn = connection();
3488 conn.set_recommended_security_limits();
3489
3490 assert_eq!(
3491 conn.get_limit(SqliteLimit::Length),
3492 SqliteLimit::SAFE_LENGTH_LIMIT
3493 );
3494 assert_eq!(
3495 conn.get_limit(SqliteLimit::SqlLength),
3496 SqliteLimit::SAFE_SQL_LENGTH_LIMIT
3497 );
3498 assert_eq!(
3499 conn.get_limit(SqliteLimit::ColumnCount),
3500 SqliteLimit::SAFE_COLUMN_COUNT_LIMIT
3501 );
3502 assert_eq!(
3503 conn.get_limit(SqliteLimit::ExprDepth),
3504 SqliteLimit::SAFE_EXPR_DEPTH_LIMIT
3505 );
3506 assert_eq!(
3507 conn.get_limit(SqliteLimit::CompoundSelect),
3508 SqliteLimit::SAFE_COMPOUND_SELECT_LIMIT
3509 );
3510 assert_eq!(
3511 conn.get_limit(SqliteLimit::VdbeOp),
3512 SqliteLimit::SAFE_VDBE_OP_LIMIT
3513 );
3514 assert_eq!(
3515 conn.get_limit(SqliteLimit::FunctionArg),
3516 SqliteLimit::SAFE_FUNCTION_ARG_LIMIT
3517 );
3518 assert_eq!(
3519 conn.get_limit(SqliteLimit::Attached),
3520 SqliteLimit::SAFE_ATTACHED_LIMIT
3521 );
3522 assert_eq!(
3523 conn.get_limit(SqliteLimit::LikePatternLength),
3524 SqliteLimit::SAFE_LIKE_PATTERN_LENGTH_LIMIT
3525 );
3526 assert_eq!(
3527 conn.get_limit(SqliteLimit::VariableNumber),
3528 SqliteLimit::SAFE_VARIABLE_NUMBER_LIMIT
3529 );
3530 assert_eq!(
3531 conn.get_limit(SqliteLimit::TriggerDepth),
3532 SqliteLimit::SAFE_TRIGGER_DEPTH_LIMIT
3533 );
3534 assert_eq!(
3537 conn.get_limit(SqliteLimit::WorkerThreads),
3538 SqliteLimit::SAFE_WORKER_THREADS_LIMIT
3539 );
3540 }
3541
3542 #[diesel_test_helper::test]
3545 fn db_config_defensive_roundtrip() {
3546 let conn = &mut connection();
3547 conn.set_defensive(true).unwrap();
3548 assert!(conn.is_defensive().unwrap());
3549 conn.set_defensive(false).unwrap();
3550 assert!(!conn.is_defensive().unwrap());
3551 }
3552
3553 #[diesel_test_helper::test]
3554 fn db_config_trusted_schema_roundtrip() {
3555 let conn = &mut connection();
3556 conn.set_trusted_schema(false).unwrap();
3557 assert!(!conn.is_trusted_schema().unwrap());
3558 conn.set_trusted_schema(true).unwrap();
3559 assert!(conn.is_trusted_schema().unwrap());
3560 }
3561
3562 #[diesel_test_helper::test]
3563 fn db_config_with_load_extension_enabled_scopes_the_flag() {
3564 let conn = &mut connection();
3565 conn.with_load_extension_enabled(|conn| {
3566 assert!(conn.is_load_extension_enabled().unwrap());
3568 QueryResult::Ok(())
3569 })
3570 .unwrap();
3571 assert!(!conn.is_load_extension_enabled().unwrap());
3573 }
3574
3575 #[cfg(all(
3576 feature = "std",
3577 not(all(target_family = "wasm", target_os = "unknown"))
3578 ))]
3579 #[diesel_test_helper::test]
3580 fn with_load_extension_enabled_disables_after_panic() {
3581 let conn = &mut connection();
3582 let outcome = std::panic::catch_unwind(core::panic::AssertUnwindSafe(|| {
3583 conn.with_load_extension_enabled(|_conn| -> QueryResult<()> {
3584 panic!("boom inside closure");
3585 })
3586 }));
3587 assert!(outcome.is_err(), "panic should propagate");
3588 assert!(
3589 !conn.is_load_extension_enabled().unwrap(),
3590 "extension loading must be disabled again after a panic"
3591 );
3592 }
3593
3594 #[diesel_test_helper::test]
3595 fn db_config_triggers_roundtrip() {
3596 let conn = &mut connection();
3597 conn.set_triggers_enabled(false).unwrap();
3598 assert!(!conn.are_triggers_enabled().unwrap());
3599 conn.set_triggers_enabled(true).unwrap();
3600 assert!(conn.are_triggers_enabled().unwrap());
3601 }
3602
3603 #[diesel_test_helper::test]
3604 fn db_config_views_roundtrip() {
3605 let conn = &mut connection();
3606 conn.set_views_enabled(false).unwrap();
3607 assert!(!conn.are_views_enabled().unwrap());
3608 conn.set_views_enabled(true).unwrap();
3609 assert!(conn.are_views_enabled().unwrap());
3610 }
3611
3612 #[diesel_test_helper::test]
3613 fn db_config_foreign_keys_roundtrip() {
3614 let conn = &mut connection();
3615 conn.set_foreign_keys_enabled(true).unwrap();
3616 assert!(conn.are_foreign_keys_enabled().unwrap());
3617 conn.set_foreign_keys_enabled(false).unwrap();
3618 assert!(!conn.are_foreign_keys_enabled().unwrap());
3619 }
3620
3621 #[diesel_test_helper::test]
3622 fn db_config_dqs_dml_roundtrip() {
3623 let conn = &mut connection();
3624 conn.set_double_quoted_strings_dml(false).unwrap();
3625 assert!(!conn.are_double_quoted_strings_dml_enabled().unwrap());
3626 conn.set_double_quoted_strings_dml(true).unwrap();
3627 assert!(conn.are_double_quoted_strings_dml_enabled().unwrap());
3628 }
3629
3630 #[diesel_test_helper::test]
3631 fn db_config_dqs_ddl_roundtrip() {
3632 let conn = &mut connection();
3633 conn.set_double_quoted_strings_ddl(false).unwrap();
3634 assert!(!conn.are_double_quoted_strings_ddl_enabled().unwrap());
3635 conn.set_double_quoted_strings_ddl(true).unwrap();
3636 assert!(conn.are_double_quoted_strings_ddl_enabled().unwrap());
3637 }
3638
3639 #[diesel_test_helper::test]
3640 fn db_config_fts3_tokenizer_roundtrip() {
3641 let conn = &mut connection();
3642 conn.set_fts3_tokenizer_enabled(false).unwrap();
3643 assert!(!conn.is_fts3_tokenizer_enabled().unwrap());
3644 conn.set_fts3_tokenizer_enabled(true).unwrap();
3645 assert!(conn.is_fts3_tokenizer_enabled().unwrap());
3646 }
3647
3648 #[diesel_test_helper::test]
3649 fn db_config_writable_schema_roundtrip() {
3650 let conn = &mut connection();
3651 conn.set_writable_schema(false).unwrap();
3652 assert!(!conn.is_writable_schema().unwrap());
3653 conn.set_writable_schema(true).unwrap();
3654 assert!(conn.is_writable_schema().unwrap());
3655 }
3656
3657 #[diesel_test_helper::test]
3658 fn db_config_attach_create_roundtrip() {
3659 let conn = &mut connection();
3660 if conn.set_attach_create_enabled(false).is_err() {
3662 return;
3663 }
3664 assert!(!conn.is_attach_create_enabled().unwrap());
3665 conn.set_attach_create_enabled(true).unwrap();
3666 assert!(conn.is_attach_create_enabled().unwrap());
3667 }
3668
3669 #[diesel_test_helper::test]
3670 fn db_config_attach_write_roundtrip() {
3671 let conn = &mut connection();
3672 if conn.set_attach_write_enabled(false).is_err() {
3674 return;
3675 }
3676 assert!(!conn.is_attach_write_enabled().unwrap());
3677 conn.set_attach_write_enabled(true).unwrap();
3678 assert!(conn.is_attach_write_enabled().unwrap());
3679 }
3680
3681 #[diesel_test_helper::test]
3684 fn defensive_mode_blocks_writable_schema() {
3685 let conn = &mut connection();
3686 conn.set_defensive(true).unwrap();
3687 let _ = crate::sql_query("PRAGMA writable_schema = ON").execute(conn);
3689 assert!(!conn.is_writable_schema().unwrap());
3690 }
3691
3692 #[diesel_test_helper::test]
3693 fn foreign_keys_enabled_enforces_constraints() {
3694 let conn = &mut connection();
3695 conn.set_foreign_keys_enabled(true).unwrap();
3696
3697 crate::sql_query("CREATE TABLE parent (id INTEGER PRIMARY KEY)")
3698 .execute(conn)
3699 .unwrap();
3700 crate::sql_query(
3701 "CREATE TABLE child (id INTEGER PRIMARY KEY, parent_id INTEGER REFERENCES parent(id))",
3702 )
3703 .execute(conn)
3704 .unwrap();
3705
3706 let result =
3708 crate::sql_query("INSERT INTO child (id, parent_id) VALUES (1, 999)").execute(conn);
3709 assert!(result.is_err());
3710 }
3711
3712 #[diesel_test_helper::test]
3713 fn views_disabled_blocks_view_queries() {
3714 let conn = &mut connection();
3715 crate::sql_query("CREATE TABLE base (id INTEGER PRIMARY KEY)")
3716 .execute(conn)
3717 .unwrap();
3718 crate::sql_query("INSERT INTO base (id) VALUES (1)")
3719 .execute(conn)
3720 .unwrap();
3721 crate::sql_query("CREATE VIEW base_view AS SELECT id FROM base")
3722 .execute(conn)
3723 .unwrap();
3724
3725 conn.set_views_enabled(true).unwrap();
3727 assert!(
3728 crate::sql_query("SELECT id FROM base_view")
3729 .execute(conn)
3730 .is_ok()
3731 );
3732
3733 conn.set_views_enabled(false).unwrap();
3735 assert!(
3736 crate::sql_query("SELECT id FROM base_view")
3737 .execute(conn)
3738 .is_err()
3739 );
3740 }
3741
3742 #[diesel_test_helper::test]
3743 fn triggers_disabled_prevents_firing() {
3744 let conn = &mut connection();
3745 crate::sql_query("CREATE TABLE source (id INTEGER PRIMARY KEY)")
3746 .execute(conn)
3747 .unwrap();
3748 crate::sql_query("CREATE TABLE trigger_log (n INTEGER)")
3749 .execute(conn)
3750 .unwrap();
3751 crate::sql_query("CREATE TRIGGER log_insert AFTER INSERT ON source BEGIN INSERT INTO trigger_log (n) VALUES (1); END")
3752 .execute(conn)
3753 .unwrap();
3754
3755 conn.set_triggers_enabled(false).unwrap();
3757 crate::sql_query("INSERT INTO source (id) VALUES (1)")
3758 .execute(conn)
3759 .unwrap();
3760 let count: i64 = sql::<crate::sql_types::BigInt>("SELECT COUNT(*) FROM trigger_log")
3761 .get_result(conn)
3762 .unwrap();
3763 assert_eq!(0, count, "trigger should not fire while disabled");
3764
3765 conn.set_triggers_enabled(true).unwrap();
3767 crate::sql_query("INSERT INTO source (id) VALUES (2)")
3768 .execute(conn)
3769 .unwrap();
3770 let count: i64 = sql::<crate::sql_types::BigInt>("SELECT COUNT(*) FROM trigger_log")
3771 .get_result(conn)
3772 .unwrap();
3773 assert_eq!(1, count, "trigger should fire while enabled");
3774 }
3775
3776 #[diesel_test_helper::test]
3777 fn dqs_dml_controls_double_quoted_string_literals() {
3778 let conn = &mut connection();
3779
3780 conn.set_double_quoted_strings_dml(false).unwrap();
3783 let disabled = sql::<Text>(r#"SELECT "bare_token""#).get_result::<String>(conn);
3784 assert!(disabled.is_err());
3785
3786 conn.set_double_quoted_strings_dml(true).unwrap();
3788 let enabled = sql::<Text>(r#"SELECT "bare_token""#).get_result::<String>(conn);
3789 assert_eq!(Ok("bare_token".to_owned()), enabled);
3790 }
3791
3792 #[diesel_test_helper::test]
3793 fn dqs_ddl_controls_double_quoted_string_literals() {
3794 let conn = &mut connection();
3795
3796 conn.set_double_quoted_strings_ddl(false).unwrap();
3799 let disabled =
3800 crate::sql_query(r#"CREATE TABLE dqs_off (name TEXT, CHECK (name <> "not_a_column"))"#)
3801 .execute(conn);
3802 assert!(disabled.is_err());
3803
3804 conn.set_double_quoted_strings_ddl(true).unwrap();
3807 let enabled =
3808 crate::sql_query(r#"CREATE TABLE dqs_on (name TEXT, CHECK (name <> "not_a_column"))"#)
3809 .execute(conn);
3810 assert!(enabled.is_ok());
3811 }
3812
3813 #[diesel_test_helper::test]
3814 fn writable_schema_controls_direct_sqlite_master_writes() {
3815 let conn = &mut connection();
3816 crate::sql_query("CREATE TABLE protected (id INTEGER PRIMARY KEY)")
3817 .execute(conn)
3818 .unwrap();
3819
3820 let update =
3821 "UPDATE sqlite_master SET sql = sql WHERE type = 'table' AND name = 'protected'";
3822
3823 conn.set_writable_schema(false).unwrap();
3825 assert!(crate::sql_query(update).execute(conn).is_err());
3826
3827 conn.set_writable_schema(true).unwrap();
3829 assert!(crate::sql_query(update).execute(conn).is_ok());
3830 }
3831
3832 #[diesel_test_helper::test]
3833 fn fts3_tokenizer_disabled_blocks_the_function() {
3834 let conn = &mut connection();
3835
3836 conn.set_fts3_tokenizer_enabled(true).unwrap();
3838 let enabled = sql::<crate::sql_types::Binary>("SELECT fts3_tokenizer('simple')")
3839 .get_result::<Vec<u8>>(conn);
3840 if enabled.is_err() {
3841 return;
3843 }
3844
3845 conn.set_fts3_tokenizer_enabled(false).unwrap();
3847 let disabled = sql::<crate::sql_types::Binary>("SELECT fts3_tokenizer('simple')")
3848 .get_result::<Vec<u8>>(conn);
3849 assert!(disabled.is_err());
3850 }
3851
3852 #[cfg(not(all(target_family = "wasm", target_os = "unknown")))]
3855 fn temp_db_path(name: &str) -> (tempfile::TempDir, std::path::PathBuf) {
3856 let dir = tempfile::tempdir().unwrap();
3857 let path = dir.path().join(name);
3858 (dir, path)
3859 }
3860
3861 #[cfg(not(all(target_family = "wasm", target_os = "unknown")))]
3862 #[diesel_test_helper::test]
3863 fn attach_create_disabled_blocks_new_database_files() {
3864 let conn = &mut connection();
3865
3866 if conn.set_attach_create_enabled(false).is_err() {
3869 return;
3870 }
3871
3872 let (_dir, path) = temp_db_path("create.db");
3873
3874 assert!(
3876 conn.attach_database(path.to_str().unwrap(), "aux_create")
3877 .is_err()
3878 );
3879
3880 conn.set_attach_create_enabled(true).unwrap();
3882 conn.attach_database(path.to_str().unwrap(), "aux_create")
3883 .unwrap();
3884 conn.detach_database("aux_create").unwrap();
3885 }
3886
3887 #[cfg(not(all(target_family = "wasm", target_os = "unknown")))]
3888 #[diesel_test_helper::test]
3889 fn attach_write_disabled_opens_attached_databases_read_only() {
3890 let conn = &mut connection();
3891
3892 if conn.set_attach_write_enabled(false).is_err() {
3896 return;
3897 }
3898
3899 let (_dir, path) = temp_db_path("write.db");
3901 {
3902 let mut seed = SqliteConnection::establish(path.to_str().unwrap()).unwrap();
3903 crate::sql_query("CREATE TABLE t (id INTEGER)")
3904 .execute(&mut seed)
3905 .unwrap();
3906 }
3907
3908 conn.attach_database(path.to_str().unwrap(), "aux_write")
3910 .unwrap();
3911 assert!(
3912 crate::sql_query("INSERT INTO aux_write.t (id) VALUES (1)")
3913 .execute(conn)
3914 .is_err()
3915 );
3916 conn.detach_database("aux_write").unwrap();
3917
3918 conn.set_attach_write_enabled(true).unwrap();
3920 conn.attach_database(path.to_str().unwrap(), "aux_write")
3921 .unwrap();
3922 crate::sql_query("INSERT INTO aux_write.t (id) VALUES (1)")
3923 .execute(conn)
3924 .unwrap();
3925 conn.detach_database("aux_write").unwrap();
3926 }
3927
3928 #[cfg(not(all(target_family = "wasm", target_os = "unknown")))]
3931 table! {
3932 attach_owners (id) {
3933 id -> Integer,
3934 name -> Text,
3935 }
3936 }
3937 #[cfg(not(all(target_family = "wasm", target_os = "unknown")))]
3938 table! {
3939 aux.attach_pets (id) {
3940 id -> Integer,
3941 owner_id -> Integer,
3942 name -> Text,
3943 }
3944 }
3945 #[cfg(not(all(target_family = "wasm", target_os = "unknown")))]
3946 allow_tables_to_appear_in_same_query!(attach_owners, attach_pets);
3947 #[cfg(not(all(target_family = "wasm", target_os = "unknown")))]
3948 table! {
3949 attach_marker (id) {
3950 id -> Integer,
3951 }
3952 }
3953 #[cfg(not(all(target_family = "wasm", target_os = "unknown")))]
3954 table! {
3955 ro.readonly_marker (id) {
3956 id -> Integer,
3957 }
3958 }
3959
3960 #[cfg(not(all(target_family = "wasm", target_os = "unknown")))]
3961 #[diesel_test_helper::test]
3962 fn attach_database_supports_cross_schema_join_then_detach() {
3963 use crate::connection::SimpleConnection;
3964
3965 let conn = &mut connection();
3966
3967 conn.attach_database(":memory:", "aux").unwrap();
3968
3969 conn.batch_execute(
3972 "CREATE TABLE attach_owners (id INTEGER PRIMARY KEY, name TEXT NOT NULL);
3973 CREATE TABLE aux.attach_pets (id INTEGER PRIMARY KEY, owner_id INTEGER, name TEXT NOT NULL);",
3974 )
3975 .unwrap();
3976
3977 crate::insert_into(attach_owners::table)
3978 .values(&[
3979 (attach_owners::id.eq(1), attach_owners::name.eq("Sean")),
3980 (attach_owners::id.eq(2), attach_owners::name.eq("Tess")),
3981 ])
3982 .execute(conn)
3983 .unwrap();
3984 crate::insert_into(attach_pets::table)
3985 .values((
3986 attach_pets::id.eq(1),
3987 attach_pets::owner_id.eq(1),
3988 attach_pets::name.eq("Ferris"),
3989 ))
3990 .execute(conn)
3991 .unwrap();
3992
3993 let pet_owner = attach_owners::table
3994 .inner_join(attach_pets::table.on(attach_pets::owner_id.eq(attach_owners::id)))
3995 .filter(attach_pets::name.eq("Ferris"))
3996 .select(attach_owners::name)
3997 .get_result::<String>(conn)
3998 .unwrap();
3999 assert_eq!(pet_owner, "Sean");
4000
4001 conn.detach_database("aux").unwrap();
4002
4003 assert!(
4005 attach_pets::table
4006 .select(attach_pets::name)
4007 .get_result::<String>(conn)
4008 .is_err()
4009 );
4010 }
4011
4012 #[cfg(not(all(target_family = "wasm", target_os = "unknown")))]
4013 #[diesel_test_helper::test]
4014 fn attach_database_binds_path_verbatim_without_quoting() {
4015 let (_dir, path) = temp_db_path("o'brien.db");
4018
4019 conn_attach_roundtrip(&path);
4020
4021 let mut direct = SqliteConnection::establish(path.to_str().unwrap()).unwrap();
4024 let count = attach_marker::table
4025 .count()
4026 .get_result::<i64>(&mut direct)
4027 .unwrap();
4028 assert_eq!(count, 0);
4029 }
4030
4031 #[cfg(not(all(target_family = "wasm", target_os = "unknown")))]
4032 fn conn_attach_roundtrip(path: &std::path::Path) {
4033 use crate::connection::SimpleConnection;
4034
4035 let conn = &mut connection();
4036 conn.attach_database(path.to_str().unwrap(), "verbatim")
4037 .unwrap();
4038 conn.batch_execute("CREATE TABLE verbatim.attach_marker (id INTEGER PRIMARY KEY)")
4039 .unwrap();
4040 conn.detach_database("verbatim").unwrap();
4041 }
4042
4043 #[cfg(not(all(target_family = "wasm", target_os = "unknown")))]
4044 #[diesel_test_helper::test]
4045 fn attach_database_interprets_file_uri_query_parameters() {
4046 let (_dir, path) = temp_db_path("uri_seed.db");
4048 {
4049 let mut seed = SqliteConnection::establish(path.to_str().unwrap()).unwrap();
4050 crate::sql_query("CREATE TABLE t (id INTEGER PRIMARY KEY)")
4051 .execute(&mut seed)
4052 .unwrap();
4053 crate::sql_query("INSERT INTO t (id) VALUES (1)")
4054 .execute(&mut seed)
4055 .unwrap();
4056 }
4057
4058 let uri = format!("file:{}?mode=ro", path.display());
4062 let conn = &mut connection();
4063 conn.attach_database(&uri, "ro_schema").unwrap();
4064
4065 let id: i64 = sql::<crate::sql_types::BigInt>("SELECT id FROM ro_schema.t")
4067 .get_result(conn)
4068 .unwrap();
4069 assert_eq!(id, 1);
4070
4071 assert!(
4073 crate::sql_query("INSERT INTO ro_schema.t (id) VALUES (2)")
4074 .execute(conn)
4075 .is_err()
4076 );
4077
4078 conn.detach_database("ro_schema").unwrap();
4079 }
4080
4081 #[diesel_test_helper::test]
4082 fn attach_and_detach_surface_errors_without_panicking() {
4083 let conn = &mut connection();
4084
4085 conn.attach_database(":memory:", "dup").unwrap();
4087 assert!(conn.attach_database(":memory:", "dup").is_err());
4088 conn.detach_database("dup").unwrap();
4089
4090 assert!(conn.detach_database("never_attached").is_err());
4094 }
4095
4096 #[diesel_test_helper::test]
4097 fn attach_database_binds_schema_name_verbatim_without_identifier_quoting() {
4098 use crate::connection::SimpleConnection;
4099
4100 let conn = &mut connection();
4101
4102 let schema = "weird 'schema";
4106 conn.attach_database(":memory:", schema).unwrap();
4107
4108 conn.batch_execute(
4109 r#"CREATE TABLE "weird 'schema".t (id INTEGER PRIMARY KEY);
4110 INSERT INTO "weird 'schema".t (id) VALUES (7);"#,
4111 )
4112 .unwrap();
4113 let id = sql::<Integer>(r#"SELECT id FROM "weird 'schema".t"#)
4114 .get_result::<i32>(conn)
4115 .unwrap();
4116 assert_eq!(id, 7);
4117
4118 conn.detach_database(schema).unwrap();
4119 }
4120
4121 #[cfg(not(all(target_family = "wasm", target_os = "unknown")))]
4122 #[diesel_test_helper::test]
4123 fn attach_database_honors_create_and_write_hardening_knobs() {
4124 use crate::connection::SimpleConnection;
4125
4126 let conn = &mut connection();
4127
4128 if conn.set_attach_create_enabled(false).is_err() {
4130 return;
4131 }
4132
4133 let (_dir_missing, missing) = temp_db_path("nocreate.db");
4135 assert!(
4136 conn.attach_database(missing.to_str().unwrap(), "missing")
4137 .is_err()
4138 );
4139 assert!(!missing.exists());
4140
4141 conn.set_attach_write_enabled(false).unwrap();
4143 let (_dir_existing, existing) = temp_db_path("readonly.db");
4144 {
4145 let mut seed = SqliteConnection::establish(existing.to_str().unwrap()).unwrap();
4146 seed.batch_execute("CREATE TABLE readonly_marker (id INTEGER)")
4147 .unwrap();
4148 }
4149 conn.attach_database(existing.to_str().unwrap(), "ro")
4150 .unwrap();
4151 assert!(
4152 crate::insert_into(readonly_marker::table)
4153 .values(readonly_marker::id.eq(1))
4154 .execute(conn)
4155 .is_err()
4156 );
4157 conn.detach_database("ro").unwrap();
4158 }
4159
4160 #[declare_sql_function]
4163 extern "SQL" {
4164 fn directonly_fn() -> Integer;
4165 fn innocuous_fn() -> Integer;
4166 }
4167
4168 #[diesel_test_helper::test]
4169 fn directonly_function_blocked_from_view() {
4170 let conn = &mut connection();
4171
4172 directonly_fn_utils::register_impl_with_behavior(
4174 conn,
4175 SqliteFunctionBehavior::DIRECTONLY,
4176 || 42,
4177 )
4178 .unwrap();
4179
4180 let result = crate::select(directonly_fn()).get_result::<i32>(conn);
4182 assert_eq!(Ok(42), result);
4183
4184 crate::sql_query("CREATE VIEW test_view AS SELECT directonly_fn() AS val")
4186 .execute(conn)
4187 .unwrap();
4188
4189 conn.set_trusted_schema(false).unwrap();
4191
4192 let result = crate::sql_query("SELECT val FROM test_view").execute(conn);
4194 assert!(result.is_err());
4195 }
4196
4197 #[diesel_test_helper::test]
4198 fn innocuous_function_allowed_from_view_with_untrusted_schema() {
4199 let conn = &mut connection();
4200
4201 innocuous_fn_utils::register_impl_with_behavior(
4203 conn,
4204 SqliteFunctionBehavior::DETERMINISTIC | SqliteFunctionBehavior::INNOCUOUS,
4205 || 99,
4206 )
4207 .unwrap();
4208
4209 crate::sql_query("CREATE VIEW innocuous_view AS SELECT innocuous_fn() AS val")
4211 .execute(conn)
4212 .unwrap();
4213
4214 conn.set_trusted_schema(false).unwrap();
4216
4217 let result = crate::sql_query("SELECT val FROM innocuous_view").execute(conn);
4219 assert!(result.is_ok());
4220 }
4221
4222 #[diesel_test_helper::test]
4223 fn auto_vacuum_all_modes_roundtrip_on_fresh_database() {
4224 for mode in [
4225 AutoVacuumMode::None,
4226 AutoVacuumMode::Full,
4227 AutoVacuumMode::Incremental,
4228 ] {
4229 let conn = &mut connection();
4230 conn.set_auto_vacuum(None, mode).unwrap();
4231 assert_eq!(mode, conn.auto_vacuum(None).unwrap());
4232 }
4233 }
4234
4235 #[diesel_test_helper::test]
4236 fn auto_vacuum_incremental_sticks_across_schema_creation() {
4237 let conn = &mut connection();
4238 conn.set_auto_vacuum(None, AutoVacuumMode::Incremental)
4239 .unwrap();
4240 assert_eq!(AutoVacuumMode::Incremental, conn.auto_vacuum(None).unwrap());
4241
4242 crate::sql_query("CREATE TABLE t (id INTEGER PRIMARY KEY)")
4243 .execute(conn)
4244 .unwrap();
4245 assert_eq!(
4246 AutoVacuumMode::Incremental,
4247 conn.auto_vacuum(None).unwrap(),
4248 "the mode survives once the schema exists"
4249 );
4250 }
4251
4252 #[diesel_test_helper::test]
4253 fn auto_vacuum_change_from_none_requires_vacuum_on_populated_database() {
4254 let conn = &mut connection();
4255 crate::sql_query("CREATE TABLE t (id INTEGER PRIMARY KEY)")
4256 .execute(conn)
4257 .unwrap();
4258 crate::sql_query("INSERT INTO t (id) VALUES (1)")
4259 .execute(conn)
4260 .unwrap();
4261 assert_eq!(AutoVacuumMode::None, conn.auto_vacuum(None).unwrap());
4262
4263 conn.set_auto_vacuum(None, AutoVacuumMode::Full).unwrap();
4266 assert_eq!(
4267 AutoVacuumMode::None,
4268 conn.auto_vacuum(None).unwrap(),
4269 "the change does not take effect without a VACUUM"
4270 );
4271
4272 crate::sql_query("VACUUM").execute(conn).unwrap();
4273 assert_eq!(
4274 AutoVacuumMode::Full,
4275 conn.auto_vacuum(None).unwrap(),
4276 "VACUUM rewrites the file and applies the mode"
4277 );
4278 }
4279
4280 #[diesel_test_helper::test]
4281 fn auto_vacuum_targets_the_named_attached_database() {
4282 let conn = &mut connection();
4283 crate::sql_query("ATTACH DATABASE ':memory:' AS aux")
4284 .execute(conn)
4285 .unwrap();
4286
4287 conn.set_auto_vacuum(Some("aux"), AutoVacuumMode::Full)
4288 .unwrap();
4289 assert_eq!(AutoVacuumMode::Full, conn.auto_vacuum(Some("aux")).unwrap());
4290 assert_eq!(
4291 AutoVacuumMode::None,
4292 conn.auto_vacuum(None).unwrap(),
4293 "main keeps its own default"
4294 );
4295 }
4296
4297 #[diesel_test_helper::test]
4298 fn auto_vacuum_schema_name_with_double_quote_is_handled() {
4299 let conn = &mut connection();
4300 let schema = r#"we"ird"#;
4301 crate::sql_query(alloc::format!(
4302 r#"ATTACH DATABASE ':memory:' AS "{}""#,
4303 schema.replace('"', "\"\"")
4304 ))
4305 .execute(conn)
4306 .unwrap();
4307
4308 conn.set_auto_vacuum(Some(schema), AutoVacuumMode::Incremental)
4309 .unwrap();
4310 assert_eq!(
4311 AutoVacuumMode::Incremental,
4312 conn.auto_vacuum(Some(schema)).unwrap()
4313 );
4314 }
4315
4316 table! {
4317 pragma_probe (id) {
4318 id -> Integer,
4319 payload -> Text,
4320 }
4321 }
4322
4323 table! {
4324 aux.aux_pragma_probe (id) {
4325 id -> Integer,
4326 payload -> Text,
4327 }
4328 }
4329
4330 const PROBE_TABLE: &str =
4331 "CREATE TABLE pragma_probe (id INTEGER PRIMARY KEY, payload TEXT NOT NULL)";
4332
4333 const AUX_PROBE_TABLE: &str =
4334 "CREATE TABLE aux.aux_pragma_probe (id INTEGER PRIMARY KEY, payload TEXT NOT NULL)";
4335
4336 fn overflowing_payload() -> String {
4339 "x".repeat(64 * 1024)
4340 }
4341
4342 fn insert_overflowing_row(conn: &mut SqliteConnection) {
4343 crate::insert_into(pragma_probe::table)
4344 .values((
4345 pragma_probe::id.eq(1),
4346 pragma_probe::payload.eq(overflowing_payload()),
4347 ))
4348 .execute(conn)
4349 .unwrap();
4350 }
4351
4352 #[diesel_test_helper::test]
4353 fn page_count_is_positive_and_grows() {
4354 let conn = &mut connection();
4355 conn.batch_execute(PROBE_TABLE).unwrap();
4356 let initial = conn.page_count(None).unwrap();
4357 assert!(initial > 0, "an initialized database has at least one page");
4358
4359 insert_overflowing_row(conn);
4360
4361 assert!(
4362 conn.page_count(None).unwrap() > initial,
4363 "a row spanning overflow pages grows the page count"
4364 );
4365 }
4366
4367 #[diesel_test_helper::test]
4368 fn freelist_count_tracks_reclaimable_space() {
4369 let conn = &mut connection();
4370 assert_eq!(
4371 0,
4372 conn.freelist_count(None).unwrap(),
4373 "a fresh database has an empty freelist"
4374 );
4375
4376 conn.batch_execute(PROBE_TABLE).unwrap();
4377 insert_overflowing_row(conn);
4378
4379 crate::delete(pragma_probe::table).execute(conn).unwrap();
4380 assert!(
4381 conn.freelist_count(None).unwrap() > 0,
4382 "deleting the row leaves reclaimable pages on the freelist"
4383 );
4384
4385 crate::sql_query("VACUUM").execute(conn).unwrap();
4387 assert_eq!(
4388 0,
4389 conn.freelist_count(None).unwrap(),
4390 "VACUUM reclaims the freelist"
4391 );
4392 }
4393
4394 #[diesel_test_helper::test]
4395 fn schema_targets_the_named_attached_database() {
4396 let conn = &mut connection();
4397 conn.batch_execute(PROBE_TABLE).unwrap();
4398 conn.attach_database(":memory:", "aux").unwrap();
4399 conn.batch_execute(AUX_PROBE_TABLE).unwrap();
4400 crate::insert_into(aux_pragma_probe::table)
4401 .values((
4402 aux_pragma_probe::id.eq(1),
4403 aux_pragma_probe::payload.eq(overflowing_payload()),
4404 ))
4405 .execute(conn)
4406 .unwrap();
4407
4408 let main_pages = conn.page_count(None).unwrap();
4409 let aux_pages = conn.page_count(Some("aux")).unwrap();
4410 assert!(
4411 aux_pages > main_pages,
4412 "the attached database holds the data, main stays small"
4413 );
4414 assert_eq!(
4415 main_pages,
4416 conn.page_count(Some("main")).unwrap(),
4417 "an explicit main matches the default"
4418 );
4419 }
4420
4421 #[diesel_test_helper::test]
4422 fn schema_name_with_backtick_is_escaped() {
4423 let conn = &mut connection();
4426 let schema = "back`tick";
4427 conn.attach_database(":memory:", schema).unwrap();
4428 conn.batch_execute("CREATE TABLE `back``tick`.probe (id INTEGER PRIMARY KEY)")
4429 .unwrap();
4430
4431 assert!(conn.page_count(Some(schema)).unwrap() > 0);
4432 assert_eq!(0, conn.freelist_count(Some(schema)).unwrap());
4433 }
4434
4435 #[diesel_test_helper::test]
4436 fn unknown_schema_is_reported_as_an_error() {
4437 let conn = &mut connection();
4438
4439 assert!(conn.page_count(Some("nope")).is_err());
4440 assert!(conn.freelist_count(Some("nope")).is_err());
4441 }
4442
4443 fn grow_then_empty_freelist(conn: &mut SqliteConnection) {
4446 conn.batch_execute(PROBE_TABLE).unwrap();
4447 let rows = (1..=200)
4448 .map(|id| {
4449 (
4450 pragma_probe::id.eq(id),
4451 pragma_probe::payload.eq("x".repeat(4000)),
4452 )
4453 })
4454 .collect::<Vec<_>>();
4455 crate::insert_into(pragma_probe::table)
4456 .values(rows)
4457 .execute(conn)
4458 .unwrap();
4459 crate::delete(pragma_probe::table).execute(conn).unwrap();
4460 }
4461
4462 fn grow_then_empty_aux_freelist(conn: &mut SqliteConnection) {
4464 conn.batch_execute(AUX_PROBE_TABLE).unwrap();
4465 let rows = (1..=200)
4466 .map(|id| {
4467 (
4468 aux_pragma_probe::id.eq(id),
4469 aux_pragma_probe::payload.eq("x".repeat(4000)),
4470 )
4471 })
4472 .collect::<Vec<_>>();
4473 crate::insert_into(aux_pragma_probe::table)
4474 .values(rows)
4475 .execute(conn)
4476 .unwrap();
4477 crate::delete(aux_pragma_probe::table)
4478 .execute(conn)
4479 .unwrap();
4480 }
4481
4482 #[diesel_test_helper::test]
4483 fn incremental_vacuum_clears_the_whole_freelist() {
4484 let conn = &mut connection();
4485 conn.set_auto_vacuum(None, AutoVacuumMode::Incremental)
4486 .unwrap();
4487 grow_then_empty_freelist(conn);
4488 assert!(
4489 conn.freelist_count(None).unwrap() > 1,
4490 "the deleted rows should leave many pages on the freelist"
4491 );
4492
4493 conn.incremental_vacuum(None, None).unwrap();
4494
4495 assert_eq!(0, conn.freelist_count(None).unwrap());
4498 }
4499
4500 #[diesel_test_helper::test]
4501 fn incremental_vacuum_reclaims_at_most_the_requested_pages() {
4502 let conn = &mut connection();
4503 conn.set_auto_vacuum(None, AutoVacuumMode::Incremental)
4504 .unwrap();
4505 grow_then_empty_freelist(conn);
4506 let before = conn.freelist_count(None).unwrap();
4507 assert!(before > 10, "the bound has to be smaller than the freelist");
4508
4509 conn.incremental_vacuum(None, Some(10)).unwrap();
4510
4511 let after = conn.freelist_count(None).unwrap();
4512 assert!(after >= before - 10, "at most ten pages may be reclaimed");
4513 assert!(after < before, "some pages should have been reclaimed");
4514 }
4515
4516 #[diesel_test_helper::test]
4517 fn incremental_vacuum_is_a_no_op_outside_incremental_mode() {
4518 let conn = &mut connection();
4519 assert_eq!(AutoVacuumMode::None, conn.auto_vacuum(None).unwrap());
4520 grow_then_empty_freelist(conn);
4521 let before = conn.freelist_count(None).unwrap();
4522 assert!(before > 0);
4523
4524 conn.incremental_vacuum(None, None).unwrap();
4525
4526 assert_eq!(
4527 before,
4528 conn.freelist_count(None).unwrap(),
4529 "a database that is not in incremental mode keeps its freelist"
4530 );
4531 }
4532
4533 #[diesel_test_helper::test]
4534 fn incremental_vacuum_targets_the_named_attached_database() {
4535 let conn = &mut connection();
4536 conn.attach_database(":memory:", "aux").unwrap();
4537 conn.set_auto_vacuum(Some("aux"), AutoVacuumMode::Incremental)
4538 .unwrap();
4539
4540 grow_then_empty_aux_freelist(conn);
4541 assert!(conn.freelist_count(Some("aux")).unwrap() > 0);
4542
4543 conn.incremental_vacuum(Some("aux"), None).unwrap();
4544
4545 assert_eq!(0, conn.freelist_count(Some("aux")).unwrap());
4546 }
4547
4548 #[diesel_test_helper::test]
4549 fn incremental_vacuum_escapes_a_backtick_in_the_schema_name() {
4550 let conn = &mut connection();
4553 let schema = "back`tick";
4554 conn.attach_database(":memory:", schema).unwrap();
4555
4556 conn.incremental_vacuum(Some(schema), None).unwrap();
4557
4558 assert_eq!(0, conn.freelist_count(Some(schema)).unwrap());
4559 }
4560
4561 #[diesel_test_helper::test]
4562 fn incremental_vacuum_of_zero_pages_clears_everything() {
4563 let conn = &mut connection();
4565 conn.set_auto_vacuum(None, AutoVacuumMode::Incremental)
4566 .unwrap();
4567 grow_then_empty_freelist(conn);
4568 assert!(conn.freelist_count(None).unwrap() > 0);
4569
4570 conn.incremental_vacuum(None, Some(0)).unwrap();
4571
4572 assert_eq!(0, conn.freelist_count(None).unwrap());
4573 }
4574
4575 #[diesel_test_helper::test]
4576 fn incremental_vacuum_of_an_unknown_schema_is_an_error() {
4577 let conn = &mut connection();
4578
4579 assert!(conn.incremental_vacuum(Some("nope"), None).is_err());
4580 }
4581
4582 fn fill_then_delete(conn: &mut SqliteConnection) {
4585 conn.batch_execute(PROBE_TABLE).unwrap();
4586 crate::insert_into(pragma_probe::table)
4587 .values((
4588 pragma_probe::id.eq(1),
4589 pragma_probe::payload.eq("x".repeat(256 * 1024)),
4590 ))
4591 .execute(conn)
4592 .unwrap();
4593 crate::delete(pragma_probe::table).execute(conn).unwrap();
4594 crate::insert_into(pragma_probe::table)
4595 .values((pragma_probe::id.eq(2), pragma_probe::payload.eq("kept")))
4596 .execute(conn)
4597 .unwrap();
4598 }
4599
4600 fn fill_then_delete_aux(conn: &mut SqliteConnection) {
4602 conn.batch_execute(AUX_PROBE_TABLE).unwrap();
4603 crate::insert_into(aux_pragma_probe::table)
4604 .values((
4605 aux_pragma_probe::id.eq(1),
4606 aux_pragma_probe::payload.eq("x".repeat(256 * 1024)),
4607 ))
4608 .execute(conn)
4609 .unwrap();
4610 crate::delete(aux_pragma_probe::table)
4611 .execute(conn)
4612 .unwrap();
4613 }
4614
4615 #[diesel_test_helper::test]
4616 fn vacuum_repacks_the_database() {
4617 let conn = &mut connection();
4618 fill_then_delete(conn);
4619 let before = conn.page_count(None).unwrap();
4620 assert!(before > 1);
4621
4622 conn.vacuum(None).unwrap();
4623
4624 assert!(
4625 conn.page_count(None).unwrap() < before,
4626 "rebuilding should release the pages the deleted row occupied"
4627 );
4628 assert_eq!(
4629 1,
4630 pragma_probe::table.count().get_result::<i64>(conn).unwrap(),
4631 "the surviving row is still there"
4632 );
4633 }
4634
4635 #[diesel_test_helper::test]
4636 fn vacuum_targets_the_named_attached_database() {
4637 let conn = &mut connection();
4638 conn.attach_database(":memory:", "aux").unwrap();
4639 fill_then_delete_aux(conn);
4640 let before = conn.page_count(Some("aux")).unwrap();
4641 assert!(before > 1);
4642
4643 conn.vacuum(Some("aux")).unwrap();
4644
4645 assert!(conn.page_count(Some("aux")).unwrap() < before);
4646 }
4647
4648 #[diesel_test_helper::test]
4649 fn vacuum_inside_a_transaction_is_an_error() {
4650 use crate::connection::Connection;
4651
4652 let conn = &mut connection();
4653 let result: QueryResult<()> = conn.transaction(|conn| conn.vacuum(None));
4654
4655 assert!(result.is_err());
4656 }
4657
4658 #[cfg(not(all(target_family = "wasm", target_os = "unknown")))]
4659 #[diesel_test_helper::test]
4660 fn vacuum_into_writes_a_readable_copy_through_a_quoted_path() {
4661 let dir = tempfile::tempdir().unwrap();
4662 let destination = dir.path().join("o'brien backup.db");
4665
4666 let conn = &mut connection();
4667 conn.batch_execute(PROBE_TABLE).unwrap();
4668 crate::insert_into(pragma_probe::table)
4669 .values((pragma_probe::id.eq(1), pragma_probe::payload.eq("copied")))
4670 .execute(conn)
4671 .unwrap();
4672
4673 conn.vacuum_into(None, destination.to_str().unwrap())
4674 .unwrap();
4675
4676 let copy = &mut SqliteConnection::establish(destination.to_str().unwrap()).unwrap();
4677 assert_eq!(
4678 "copied",
4679 pragma_probe::table
4680 .select(pragma_probe::payload)
4681 .get_result::<String>(copy)
4682 .unwrap()
4683 );
4684 }
4685
4686 #[cfg(not(all(target_family = "wasm", target_os = "unknown")))]
4687 #[diesel_test_helper::test]
4688 fn vacuum_into_refuses_to_overwrite_an_existing_database() {
4689 let dir = tempfile::tempdir().unwrap();
4690 let destination = dir.path().join("occupied.db");
4691 {
4692 let occupied = &mut SqliteConnection::establish(destination.to_str().unwrap()).unwrap();
4693 occupied.batch_execute(PROBE_TABLE).unwrap();
4694 }
4695
4696 let conn = &mut connection();
4697 conn.batch_execute(PROBE_TABLE).unwrap();
4698
4699 assert!(
4700 conn.vacuum_into(None, destination.to_str().unwrap())
4701 .is_err()
4702 );
4703 }
4704
4705 #[cfg(not(all(target_family = "wasm", target_os = "unknown")))]
4706 #[diesel_test_helper::test]
4707 fn vacuum_into_copies_the_named_attached_database() {
4708 let dir = tempfile::tempdir().unwrap();
4709 let destination = dir.path().join("aux copy.db");
4710
4711 let conn = &mut connection();
4712 conn.attach_database(":memory:", "aux").unwrap();
4713 conn.batch_execute(AUX_PROBE_TABLE).unwrap();
4714 crate::insert_into(aux_pragma_probe::table)
4715 .values((
4716 aux_pragma_probe::id.eq(7),
4717 aux_pragma_probe::payload.eq("copied"),
4718 ))
4719 .execute(conn)
4720 .unwrap();
4721
4722 conn.vacuum_into(Some("aux"), destination.to_str().unwrap())
4723 .unwrap();
4724
4725 let copy = &mut SqliteConnection::establish(destination.to_str().unwrap()).unwrap();
4726 let id = sql::<Integer>("SELECT id FROM aux_pragma_probe")
4729 .get_result::<i32>(copy)
4730 .unwrap();
4731 assert_eq!(7, id);
4732 }
4733
4734 #[diesel_test_helper::test]
4735 fn vacuum_escapes_a_backtick_in_the_schema_name() {
4736 let conn = &mut connection();
4737 let schema = "back`tick";
4738 conn.attach_database(":memory:", schema).unwrap();
4739
4740 conn.vacuum(Some(schema)).unwrap();
4741 }
4742
4743 #[diesel_test_helper::test]
4744 fn vacuuming_two_schemas_rebuilds_each_of_them() {
4745 let conn = &mut connection();
4749 fill_then_delete(conn);
4750 conn.attach_database(":memory:", "aux").unwrap();
4751 fill_then_delete_aux(conn);
4752
4753 let main_before = conn.page_count(None).unwrap();
4754 let aux_before = conn.page_count(Some("aux")).unwrap();
4755
4756 conn.vacuum(None).unwrap();
4757 conn.vacuum(Some("aux")).unwrap();
4758
4759 assert!(
4760 conn.page_count(None).unwrap() < main_before,
4761 "main was rebuilt"
4762 );
4763 assert!(
4764 conn.page_count(Some("aux")).unwrap() < aux_before,
4765 "aux was rebuilt too, not main a second time"
4766 );
4767 }
4768
4769 #[cfg(not(all(target_family = "wasm", target_os = "unknown")))]
4771 fn wal_connection(path: &std::path::Path) -> SqliteConnection {
4772 let mut conn = SqliteConnection::establish(path.to_str().unwrap()).unwrap();
4773 conn.batch_execute("PRAGMA journal_mode = WAL").unwrap();
4774 conn
4775 }
4776
4777 #[cfg(not(all(target_family = "wasm", target_os = "unknown")))]
4778 #[diesel_test_helper::test]
4779 fn wal_checkpoint_truncate_reports_an_emptied_wal() {
4780 let dir = tempfile::tempdir().unwrap();
4781 let conn = &mut wal_connection(&dir.path().join("wal.db"));
4782 conn.batch_execute(PROBE_TABLE).unwrap();
4783 insert_overflowing_row(conn);
4784
4785 let outcome = conn
4786 .wal_checkpoint(None, WalCheckpointMode::Truncate)
4787 .unwrap();
4788
4789 assert!(!outcome.busy);
4790 assert_eq!(Some(0), outcome.log_frames, "the WAL file was truncated");
4791 assert_eq!(Some(0), outcome.checkpointed_frames);
4792 }
4793
4794 #[diesel_test_helper::test]
4795 fn wal_checkpoint_outside_wal_mode_reports_no_frames() {
4796 let conn = &mut connection();
4797
4798 let outcome = conn
4799 .wal_checkpoint(None, WalCheckpointMode::Truncate)
4800 .unwrap();
4801
4802 assert!(!outcome.busy);
4803 assert_eq!(None, outcome.log_frames);
4804 assert_eq!(None, outcome.checkpointed_frames);
4805 }
4806
4807 #[cfg(not(all(target_family = "wasm", target_os = "unknown")))]
4808 #[diesel_test_helper::test]
4809 fn wal_checkpoint_accepts_every_mode() {
4810 let dir = tempfile::tempdir().unwrap();
4811 let conn = &mut wal_connection(&dir.path().join("modes.db"));
4812 conn.batch_execute(PROBE_TABLE).unwrap();
4813
4814 for (row, mode) in [
4815 WalCheckpointMode::Passive,
4816 WalCheckpointMode::Full,
4817 WalCheckpointMode::Restart,
4818 WalCheckpointMode::Truncate,
4819 WalCheckpointMode::Noop,
4820 ]
4821 .into_iter()
4822 .enumerate()
4823 {
4824 crate::insert_into(pragma_probe::table)
4826 .values((
4827 pragma_probe::id.eq(i32::try_from(row).unwrap() + 1),
4828 pragma_probe::payload.eq("row"),
4829 ))
4830 .execute(conn)
4831 .unwrap();
4832
4833 let outcome = conn.wal_checkpoint(None, mode).unwrap();
4834 assert!(!outcome.busy, "{mode:?} had no competing readers");
4835 assert!(
4836 outcome.log_frames.is_some(),
4837 "{mode:?} ran on a WAL database"
4838 );
4839 assert!(outcome.checkpointed_frames.is_some());
4840 assert!(
4841 outcome.checkpointed_frames <= outcome.log_frames,
4842 "{mode:?}: checkpointed frames cannot exceed the log size"
4843 );
4844 }
4845 }
4846
4847 #[cfg(not(all(target_family = "wasm", target_os = "unknown")))]
4848 #[diesel_test_helper::test]
4849 fn wal_checkpoint_noop_reports_state_without_moving_frames() {
4850 let dir = tempfile::tempdir().unwrap();
4851 let conn = &mut wal_connection(&dir.path().join("noop.db"));
4852
4853 let version = crate::select(sql::<Text>("sqlite_version()"))
4855 .get_result::<String>(conn)
4856 .unwrap();
4857 let mut parts = version.split('.').map(|part| part.parse::<u32>().unwrap());
4858 if (parts.next().unwrap(), parts.next().unwrap()) < (3, 51) {
4859 return;
4860 }
4861
4862 conn.batch_execute(PROBE_TABLE).unwrap();
4863 conn.wal_checkpoint(None, WalCheckpointMode::Truncate)
4864 .unwrap();
4865 crate::insert_into(pragma_probe::table)
4866 .values((pragma_probe::id.eq(1), pragma_probe::payload.eq("noop")))
4867 .execute(conn)
4868 .unwrap();
4869
4870 let first = conn.wal_checkpoint(None, WalCheckpointMode::Noop).unwrap();
4871 let second = conn.wal_checkpoint(None, WalCheckpointMode::Noop).unwrap();
4872
4873 assert!(!first.busy, "NOOP never blocks");
4874 assert!(first.log_frames > Some(0), "the insert sits in the WAL");
4875 assert_eq!(Some(0), first.checkpointed_frames, "nothing was moved");
4876 assert_eq!(first, second, "a second NOOP reports the same state");
4877 }
4878
4879 #[cfg(not(all(target_family = "wasm", target_os = "unknown")))]
4880 #[diesel_test_helper::test]
4881 fn wal_checkpoint_reports_busy_while_a_reader_holds_an_old_snapshot() {
4882 use crate::connection::Connection;
4883
4884 let dir = tempfile::tempdir().unwrap();
4885 let path = dir.path().join("busy.db");
4886 let writer = &mut wal_connection(&path);
4887 writer.batch_execute(PROBE_TABLE).unwrap();
4888 insert_overflowing_row(writer);
4889
4890 let reader = &mut SqliteConnection::establish(path.to_str().unwrap()).unwrap();
4891 reader
4892 .transaction::<_, crate::result::Error, _>(|reader| {
4893 let _ = pragma_probe::table.count().get_result::<i64>(reader)?;
4895
4896 crate::insert_into(pragma_probe::table)
4899 .values((pragma_probe::id.eq(2), pragma_probe::payload.eq("late")))
4900 .execute(writer)?;
4901
4902 let outcome = writer.wal_checkpoint(None, WalCheckpointMode::Passive)?;
4905 assert!(!outcome.busy, "PASSIVE never reports busy");
4906 assert!(
4907 outcome.checkpointed_frames < outcome.log_frames,
4908 "the frames past the reader's snapshot stay in the WAL"
4909 );
4910
4911 for mode in [
4912 WalCheckpointMode::Full,
4913 WalCheckpointMode::Restart,
4914 WalCheckpointMode::Truncate,
4915 ] {
4916 let outcome = writer.wal_checkpoint(None, mode)?;
4917 assert!(outcome.busy, "the open reader blocks a {mode:?} checkpoint");
4918 }
4919 Ok(())
4920 })
4921 .unwrap();
4922
4923 let outcome = writer
4924 .wal_checkpoint(None, WalCheckpointMode::Truncate)
4925 .unwrap();
4926 assert!(
4927 !outcome.busy,
4928 "the checkpoint completes once the reader is done"
4929 );
4930 assert_eq!(Some(0), outcome.log_frames);
4931 }
4932
4933 #[cfg(not(all(target_family = "wasm", target_os = "unknown")))]
4934 #[diesel_test_helper::test]
4935 fn wal_checkpoint_targets_the_named_attached_database() {
4936 let dir = tempfile::tempdir().unwrap();
4937 let conn = &mut connection();
4938 conn.attach_database(dir.path().join("aux.db").to_str().unwrap(), "aux")
4939 .unwrap();
4940 conn.batch_execute("PRAGMA aux.journal_mode = WAL").unwrap();
4941 conn.batch_execute(AUX_PROBE_TABLE).unwrap();
4942 crate::insert_into(aux_pragma_probe::table)
4943 .values((
4944 aux_pragma_probe::id.eq(1),
4945 aux_pragma_probe::payload.eq("row"),
4946 ))
4947 .execute(conn)
4948 .unwrap();
4949
4950 let outcome = conn
4951 .wal_checkpoint(Some("aux"), WalCheckpointMode::Truncate)
4952 .unwrap();
4953 assert!(!outcome.busy);
4954 assert_eq!(
4955 Some(0),
4956 outcome.log_frames,
4957 "the attached database was checkpointed"
4958 );
4959
4960 let outcome = conn
4962 .wal_checkpoint(Some("main"), WalCheckpointMode::Truncate)
4963 .unwrap();
4964 assert_eq!(None, outcome.log_frames);
4965 }
4966
4967 #[cfg(not(all(target_family = "wasm", target_os = "unknown")))]
4968 #[diesel_test_helper::test]
4969 fn wal_checkpoint_unqualified_covers_every_attached_database() {
4970 let dir = tempfile::tempdir().unwrap();
4971 let conn = &mut wal_connection(&dir.path().join("main.db"));
4972 conn.batch_execute(PROBE_TABLE).unwrap();
4973 insert_overflowing_row(conn);
4974 conn.attach_database(dir.path().join("aux.db").to_str().unwrap(), "aux")
4975 .unwrap();
4976 conn.batch_execute("PRAGMA aux.journal_mode = WAL").unwrap();
4977 conn.batch_execute(AUX_PROBE_TABLE).unwrap();
4978 crate::insert_into(aux_pragma_probe::table)
4979 .values((
4980 aux_pragma_probe::id.eq(1),
4981 aux_pragma_probe::payload.eq("row"),
4982 ))
4983 .execute(conn)
4984 .unwrap();
4985
4986 conn.wal_checkpoint(None, WalCheckpointMode::Truncate)
4987 .unwrap();
4988
4989 let main_after = conn
4992 .wal_checkpoint(Some("main"), WalCheckpointMode::Passive)
4993 .unwrap();
4994 assert_eq!(Some(0), main_after.log_frames, "main was checkpointed");
4995 let aux_after = conn
4996 .wal_checkpoint(Some("aux"), WalCheckpointMode::Passive)
4997 .unwrap();
4998 assert_eq!(Some(0), aux_after.log_frames, "aux was checkpointed too");
4999 }
5000
5001 #[cfg(not(all(target_family = "wasm", target_os = "unknown")))]
5002 #[diesel_test_helper::test]
5003 fn wal_checkpoint_escapes_a_double_quote_in_the_schema_name() {
5004 let dir = tempfile::tempdir().unwrap();
5007 let conn = &mut connection();
5008 let schema = r#"we"ird"#;
5009 let quoted = schema.replace('"', "\"\"");
5010 conn.attach_database(dir.path().join("weird.db").to_str().unwrap(), schema)
5011 .unwrap();
5012 conn.batch_execute(&alloc::format!(r#"PRAGMA "{quoted}".journal_mode = WAL"#))
5013 .unwrap();
5014 conn.batch_execute(&alloc::format!(
5015 r#"CREATE TABLE "{quoted}".t (id INTEGER PRIMARY KEY)"#
5016 ))
5017 .unwrap();
5018
5019 let outcome = conn
5020 .wal_checkpoint(Some(schema), WalCheckpointMode::Truncate)
5021 .unwrap();
5022 assert_eq!(Some(0), outcome.log_frames, "the quoted schema was reached");
5023 }
5024
5025 #[diesel_test_helper::test]
5026 fn wal_checkpoint_of_an_unknown_schema_is_an_error() {
5027 let conn = &mut connection();
5028
5029 assert!(
5030 conn.wal_checkpoint(Some("nope"), WalCheckpointMode::Passive)
5031 .is_err()
5032 );
5033 }
5034
5035 #[cfg(not(all(target_family = "wasm", target_os = "unknown")))]
5036 #[diesel_test_helper::test]
5037 fn wal_checkpoint_inside_a_transaction_is_an_error() {
5038 use crate::connection::Connection;
5039
5040 let dir = tempfile::tempdir().unwrap();
5041 let conn = &mut wal_connection(&dir.path().join("txn.db"));
5042 conn.batch_execute(PROBE_TABLE).unwrap();
5043
5044 let result: QueryResult<WalCheckpointOutcome> = conn.transaction(|conn| {
5045 crate::insert_into(pragma_probe::table)
5046 .values((pragma_probe::id.eq(1), pragma_probe::payload.eq("txn")))
5047 .execute(conn)?;
5048 conn.wal_checkpoint(None, WalCheckpointMode::Truncate)
5049 });
5050
5051 assert!(result.is_err(), "SQLite reports SQLITE_LOCKED");
5052 }
5053}