Skip to main content

diesel/sqlite/connection/
mod.rs

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/// Connections for the SQLite backend. Unlike other backends, SQLite supported
71/// connection URLs are:
72///
73/// - File paths (`test.db`)
74/// - [URIs](https://sqlite.org/uri.html) (`file://test.db`)
75/// - Special identifiers (`:memory:`)
76///
77/// # Supported loading model implementations
78///
79/// * [`DefaultLoadingMode`]
80///
81/// As `SqliteConnection` only supports a single loading mode implementation,
82/// it is **not required** to explicitly specify a loading mode
83/// when calling [`RunQueryDsl::load_iter()`] or [`LoadConnection::load`]
84///
85/// [`RunQueryDsl::load_iter()`]: crate::query_dsl::RunQueryDsl::load_iter
86///
87/// ## DefaultLoadingMode
88///
89/// `SqliteConnection` only supports a single loading mode, which loads
90/// values row by row from the result set.
91///
92/// ```rust
93/// # include!("../../doctest_setup.rs");
94/// #
95/// # fn main() {
96/// #     run_test().unwrap();
97/// # }
98/// #
99/// # fn run_test() -> QueryResult<()> {
100/// #     use schema::users;
101/// #     let connection = &mut establish_connection();
102/// use diesel::connection::DefaultLoadingMode;
103/// {
104///     // scope to restrict the lifetime of the iterator
105///     let iter1 = users::table.load_iter::<(i32, String), DefaultLoadingMode>(connection)?;
106///
107///     for r in iter1 {
108///         let (id, name) = r?;
109///         println!("Id: {} Name: {}", id, name);
110///     }
111/// }
112///
113/// // works without specifying the loading mode
114/// let iter2 = users::table.load_iter::<(i32, String), _>(connection)?;
115///
116/// for r in iter2 {
117///     let (id, name) = r?;
118///     println!("Id: {} Name: {}", id, name);
119/// }
120/// #   Ok(())
121/// # }
122/// ```
123///
124/// This mode does **not support** creating
125/// multiple iterators using the same connection.
126///
127/// ```compile_fail
128/// # include!("../../doctest_setup.rs");
129/// #
130/// # fn main() {
131/// #     run_test().unwrap();
132/// # }
133/// #
134/// # fn run_test() -> QueryResult<()> {
135/// #     use schema::users;
136/// #     let connection = &mut establish_connection();
137/// use diesel::connection::DefaultLoadingMode;
138///
139/// let iter1 = users::table.load_iter::<(i32, String), DefaultLoadingMode>(connection)?;
140/// let iter2 = users::table.load_iter::<(i32, String), DefaultLoadingMode>(connection)?;
141///
142/// for r in iter1 {
143///     let (id, name) = r?;
144///     println!("Id: {} Name: {}", id, name);
145/// }
146///
147/// for r in iter2 {
148///     let (id, name) = r?;
149///     println!("Id: {} Name: {}", id, name);
150/// }
151/// #   Ok(())
152/// # }
153/// ```
154///
155/// # Concurrency
156///
157/// By default, when running into a database lock, the operation will abort with a
158/// `Database locked` error. However, it's possible to configure it for greater concurrency,
159/// trading latency for not having to deal with retries yourself.
160///
161/// You can use this example as blue-print for which statements to run after establishing a connection.
162/// It is **important** to run each `PRAGMA` in a single statement to make sure all of them apply
163/// correctly. In addition the order of the `PRAGMA` statements is relevant to prevent timeout
164/// issues for the later `PRAGMA` statements.
165///
166/// ```rust
167/// # include!("../../doctest_setup.rs");
168/// #
169/// # fn main() {
170/// #     run_test().unwrap();
171/// # }
172/// #
173/// # fn run_test() -> QueryResult<()> {
174/// #     use schema::users;
175/// use diesel::connection::SimpleConnection;
176/// use diesel::sqlite::WalCheckpointMode;
177/// let conn = &mut establish_connection();
178/// // see https://fractaledmind.github.io/2023/09/07/enhancing-rails-sqlite-fine-tuning/
179/// // sleep if the database is busy, this corresponds to up to 2 seconds sleeping time.
180/// conn.batch_execute("PRAGMA busy_timeout = 2000;")?;
181/// // better write-concurrency
182/// conn.batch_execute("PRAGMA journal_mode = WAL;")?;
183/// // fsync only in critical moments
184/// conn.batch_execute("PRAGMA synchronous = NORMAL;")?;
185/// // write WAL changes back every 1000 pages, for an in average 1MB WAL file.
186/// // May affect readers if number is increased
187/// conn.batch_execute("PRAGMA wal_autocheckpoint = 1000;")?;
188/// // free some space by truncating possibly massive WAL files from the last run
189/// conn.wal_checkpoint(None, WalCheckpointMode::Truncate)?;
190/// #   Ok(())
191/// # }
192/// ```
193#[allow(missing_debug_implementations)]
194#[cfg(feature = "__sqlite-shared")]
195pub struct SqliteConnection {
196    // statement_cache needs to be before raw_connection
197    // otherwise we will get errors about open statements before closing the
198    // connection itself
199    statement_cache: StatementCache<Sqlite, Statement>,
200    raw_connection: RawConnection,
201    transaction_state: AnsiTransactionManager,
202    // this exists for the sole purpose of implementing `WithMetadataLookup` trait
203    // and avoiding static mut which will be deprecated in 2024 edition
204    metadata_lookup: (),
205    instrumentation: DynInstrumentation,
206    // We potentially need to store a serialized
207    // database in here to make sure the database bytes
208    // live as long as the connection
209    // This is used by SqliteConnection::deserialize_readonly_database_from_buffer
210    // only
211    // This field needs to come after the RawConnection
212    // as we need to make sure the data are still there until the
213    // connection is dropped
214    //
215    // We are not allowed to modify the inner buffer until the database connection is dropped
216    serialized_data: Vec<Vec<u8>>,
217}
218
219// This relies on the invariant that RawConnection or Statement are never
220// leaked. If a reference to one of those was held on a different thread, this
221// would not be thread safe.
222#[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    /// Establish a connection to the database specified by `database_url`.
248    ///
249    /// See [SqliteConnection] for supported `database_url`.
250    ///
251    /// If the database does not exist, this method will try to
252    /// create a new database and then establish a connection to it.
253    ///
254    /// ## WASM support
255    ///
256    /// If you plan to use this connection type on the `wasm32-unknown-unknown` target please
257    /// make sure to read the following notes:
258    ///
259    /// * The database is stored in memory by default.
260    /// * Persistent VFS (Virtual File Systems) is optional,
261    ///   see <https://github.com/Spxg/sqlite-wasm-rs> for details
262    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/// The decision returned by an [`on_commit`](SqliteConnection::on_commit)
362/// callback, controlling whether a pending commit completes.
363#[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    /// Let the commit proceed normally.
366    Proceed,
367    /// Convert the commit into a rollback.
368    Rollback,
369}
370
371/// The decision returned by an [`on_progress`](SqliteConnection::on_progress)
372/// callback, controlling whether a long-running query keeps executing.
373#[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    /// Let the query continue executing.
376    Continue,
377    /// Interrupt the query (causes `SQLITE_INTERRUPT`).
378    Interrupt,
379}
380
381/// The decision returned by an [`on_busy`](SqliteConnection::on_busy)
382/// callback when the database is locked.
383#[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 the locked operation.
386    Retry,
387    /// Give up, returning `SQLITE_BUSY` to the caller.
388    GiveUp,
389}
390
391/// The `auto_vacuum` mode of a database, controlling whether and when SQLite
392/// reclaims freed pages back to the file.
393///
394/// The mode is stored in the database file, not the connection. [`Full`] and
395/// [`Incremental`] can be switched between at any time, but changing from or to
396/// [`None`] only takes effect on a database with no tables yet, or after a
397/// subsequent `VACUUM` rewrites the file.
398///
399/// [`None`]: AutoVacuumMode::None
400/// [`Full`]: AutoVacuumMode::Full
401/// [`Incremental`]: AutoVacuumMode::Incremental
402#[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    /// Freed pages stay on the freelist and the file never shrinks (default).
408    None = 0,
409    /// Freed pages are reclaimed and the file truncated at every commit.
410    Full = 1,
411    /// Freelist bookkeeping is kept, pages are reclaimed only when
412    /// `incremental_vacuum` runs.
413    Incremental = 2,
414}
415
416/// The mode of a [`wal_checkpoint`](SqliteConnection::wal_checkpoint) run,
417/// matching the modes of
418/// [`sqlite3_wal_checkpoint_v2`](https://www.sqlite.org/c3ref/wal_checkpoint_v2.html).
419#[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    /// Checkpoint what is possible without waiting on readers or writers.
423    Passive,
424    /// Wait until there is no writer and every reader reads from the most
425    /// recent snapshot, then checkpoint every frame.
426    Full,
427    /// Like [`Full`](Self::Full), then wait until no reader uses the WAL, so
428    /// the next writer restarts the log.
429    Restart,
430    /// Like [`Restart`](Self::Restart), then truncate the WAL file to zero
431    /// bytes.
432    Truncate,
433    /// Report the WAL state without checkpointing anything.
434    ///
435    /// Requires SQLite 3.51.0 or later. Older versions do not know this
436    /// mode and silently run a [`Passive`](Self::Passive) checkpoint
437    /// instead.
438    Noop,
439}
440
441/// The result of a [`wal_checkpoint`](SqliteConnection::wal_checkpoint) run.
442#[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    /// Whether a busy reader or writer stopped the checkpoint early. Only
446    /// the blocking modes set it: [`Passive`](WalCheckpointMode::Passive)
447    /// reports `false` even when it left frames behind.
448    pub busy: bool,
449    /// Frames in the WAL after the checkpoint, `None` when the database is
450    /// not in WAL mode.
451    pub log_frames: Option<i64>,
452    /// Frames of the WAL moved into the database file, `None` when the
453    /// database is not in WAL mode. Counted within the current log, so a
454    /// [`Truncate`](WalCheckpointMode::Truncate) run reports `Some(0)`
455    /// because the log was emptied, not because nothing was moved.
456    pub checkpointed_frames: Option<i64>,
457}
458
459impl SqliteConnection {
460    /// Run a transaction with `BEGIN IMMEDIATE`
461    ///
462    /// This method will return an error if a transaction is already open.
463    ///
464    /// # Example
465    ///
466    /// ```rust
467    /// # include!("../../doctest_setup.rs");
468    /// #
469    /// # fn main() {
470    /// #     run_test().unwrap();
471    /// # }
472    /// #
473    /// # fn run_test() -> QueryResult<()> {
474    /// #     let mut conn = SqliteConnection::establish(":memory:").unwrap();
475    /// conn.immediate_transaction(|conn| {
476    ///     // Do stuff in a transaction
477    ///     Ok(())
478    /// })
479    /// # }
480    /// ```
481    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    /// Run a transaction with `BEGIN EXCLUSIVE`
490    ///
491    /// This method will return an error if a transaction is already open.
492    ///
493    /// # Example
494    ///
495    /// ```rust
496    /// # include!("../../doctest_setup.rs");
497    /// #
498    /// # fn main() {
499    /// #     run_test().unwrap();
500    /// # }
501    /// #
502    /// # fn run_test() -> QueryResult<()> {
503    /// #     let mut conn = SqliteConnection::establish(":memory:").unwrap();
504    /// conn.exclusive_transaction(|conn| {
505    ///     // Do stuff in a transaction
506    ///     Ok(())
507    /// })
508    /// # }
509    /// ```
510    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    /// Returns the rowid of the most recent successful INSERT on this connection.
519    ///
520    /// Returns `None` if no successful INSERT into a rowid table has been performed
521    /// on this connection, and `Some(rowid)` otherwise.
522    ///
523    /// See [the SQLite documentation](https://www.sqlite.org/c3ref/last_insert_rowid.html)
524    /// for details.
525    ///
526    /// # Caveats
527    /// - Inserts into `WITHOUT ROWID` tables are not recorded
528    /// - Failed `INSERT` (constraint violations) do not change the value
529    /// - `INSERT OR REPLACE` always updates the value
530    /// - Within triggers, returns the rowid of the trigger's INSERT;
531    ///   reverts after the trigger completes
532    ///
533    /// # Example
534    /// ```rust
535    /// # include!("../../doctest_setup.rs");
536    /// # fn main() {
537    /// #     run_test().unwrap();
538    /// # }
539    /// # fn run_test() -> QueryResult<()> {
540    /// use core::num::NonZeroI64;
541    /// use diesel::connection::SimpleConnection;
542    /// let conn = &mut SqliteConnection::establish(":memory:").unwrap();
543    /// conn.batch_execute("CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT NOT NULL)")?;
544    /// conn.batch_execute("INSERT INTO users (name) VALUES ('Sean')")?;
545    /// let rowid = conn.last_insert_rowid();
546    /// assert_eq!(rowid, NonZeroI64::new(1));
547    /// conn.batch_execute("INSERT INTO users (name) VALUES ('Tess')")?;
548    /// let rowid = conn.last_insert_rowid();
549    /// assert_eq!(rowid, NonZeroI64::new(2));
550    /// # Ok(())
551    /// # }
552    /// ```
553    pub fn last_insert_rowid(&self) -> Option<NonZeroI64> {
554        NonZeroI64::new(self.raw_connection.last_insert_rowid())
555    }
556
557    /// Returns an object that can be used to stream a BLOB from the database
558    ///
559    /// # Example
560    ///
561    /// ```rust
562    /// # include!("../../doctest_setup.rs");
563    /// # table! {
564    /// #     myblobs {
565    /// #         id -> Integer,
566    /// #         mydata -> Blob,
567    /// #     }
568    /// # }
569    /// # fn main() {
570    /// #     run_test().unwrap();
571    /// # }
572    /// # fn run_test() -> Result<(), Box<dyn std::error::Error>> {
573    /// use std::io::Read;
574    /// use diesel::connection::SimpleConnection;
575    /// let conn = &mut SqliteConnection::establish(":memory:").unwrap();
576    /// conn.batch_execute("CREATE TABLE myblobs (id INTEGER PRIMARY KEY, mydata BLOB)")?;
577    /// conn.batch_execute("INSERT INTO myblobs (mydata) VALUES ('abc')")?;
578    /// let mut data = conn.get_read_only_blob(myblobs::mydata, 1)?;
579    /// let mut buf = vec![];
580    /// data.read_to_end(&mut buf)?;
581    /// assert_eq!(buf, b"abc");
582    /// # Ok(())
583    /// # }
584    /// ```
585    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    /// Register a collation function.
708    ///
709    /// `collation` must always return the same answer given the same inputs.
710    /// If `collation` panics and unwinds the stack, the process is aborted, since it is used
711    /// across a C FFI boundary, which cannot be unwound across and there is no way to
712    /// signal failures via the SQLite interface in this case..
713    ///
714    /// If the name is already registered it will be overwritten.
715    ///
716    /// This method will return an error if registering the function fails, either due to an
717    /// out-of-memory situation or because a collation with that name already exists and is
718    /// currently being used in parallel by a query.
719    ///
720    /// The collation needs to be specified when creating a table:
721    /// `CREATE TABLE my_table ( str TEXT COLLATE MY_COLLATION )`,
722    /// where `MY_COLLATION` corresponds to name passed as `collation_name`.
723    ///
724    /// # Example
725    ///
726    /// ```rust
727    /// # include!("../../doctest_setup.rs");
728    /// #
729    /// # fn main() {
730    /// #     run_test().unwrap();
731    /// # }
732    /// #
733    /// # fn run_test() -> QueryResult<()> {
734    /// #     let mut conn = SqliteConnection::establish(":memory:").unwrap();
735    /// // sqlite NOCASE only works for ASCII characters,
736    /// // this collation allows handling UTF-8 (barring locale differences)
737    /// conn.register_collation("RUSTNOCASE", |rhs, lhs| {
738    ///     rhs.to_lowercase().cmp(&lhs.to_lowercase())
739    /// })
740    /// # }
741    /// ```
742    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    /// Serialize the current SQLite database into a byte buffer.
751    ///
752    /// The serialized data is identical to the data that would be written to disk if the database
753    /// was saved in a file.
754    ///
755    /// # Returns
756    ///
757    /// This function returns a [`SerializedDatabase`] wrapping the serialized
758    /// bytes. If SQLite fails to allocate the buffer holding them, the failure
759    /// is reported by [`SerializedDatabase::try_as_slice`].
760    pub fn serialize_database_to_buffer(&mut self) -> SerializedDatabase {
761        self.raw_connection.serialize()
762    }
763
764    /// Deserialize an SQLite database from a byte buffer.
765    ///
766    /// This function takes a byte slice and attempts to deserialize it into a SQLite database.
767    /// If successful, the database is loaded into the connection. If the deserialization fails,
768    /// an error is returned.
769    ///
770    /// The database is opened in READONLY mode.
771    ///
772    /// # Example
773    ///
774    /// ```no_run
775    /// # use diesel::sqlite::SerializedDatabase;
776    /// # use diesel::sqlite::SqliteConnection;
777    /// # use diesel::result::QueryResult;
778    /// # use diesel::sql_query;
779    /// # use diesel::Connection;
780    /// # use diesel::RunQueryDsl;
781    /// # fn main() {
782    /// let connection = &mut SqliteConnection::establish(":memory:").unwrap();
783    ///
784    /// sql_query("CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT, email TEXT)")
785    ///     .execute(connection).unwrap();
786    /// sql_query("INSERT INTO users (name, email) VALUES ('John Doe', 'john.doe@example.com'), ('Jane Doe', 'jane.doe@example.com')")
787    ///     .execute(connection).unwrap();
788    ///
789    /// // Serialize the database to a byte vector
790    /// let serialized_db: SerializedDatabase = connection.serialize_database_to_buffer();
791    ///
792    /// // Create a new in-memory SQLite database
793    /// let connection = &mut SqliteConnection::establish(":memory:").unwrap();
794    ///
795    /// // Deserialize the byte vector into the new database
796    /// connection.deserialize_readonly_database_from_buffer(serialized_db.try_as_slice().unwrap()).unwrap();
797    /// #
798    /// # }
799    /// ```
800    // TODO: Diesel 3.0 This signature needs to change, we want to expose more options (schema name, readonly)
801    // and also ensure that this is not as unsafe as the current construct anymore. Maybe just accept a owned buffer or static pointer
802    // only instead? (So `Cow<'static, [u8]>`?)
803    #[allow(unsafe_code)]
804    pub fn deserialize_readonly_database_from_buffer(&mut self, data: &[u8]) -> QueryResult<()> {
805        // we copy the buffer here
806        // to make sure the underlying buffer lives as long as the connection
807        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            // SAFETY: We store the buffer inside of the connection and we never touch it until
814            // we drop the connection
815            self.raw_connection.deserialize(last)
816        }
817    }
818
819    /// Provides temporary access to the raw SQLite database connection handle.
820    ///
821    /// This method provides a way to access the underlying `sqlite3` pointer,
822    /// enabling direct use of the SQLite C API for advanced features that
823    /// Diesel does not wrap, such as the [session extension](https://www.sqlite.org/sessionintro.html),
824    /// [hooks](https://www.sqlite.org/c3ref/update_hook.html), or other advanced APIs.
825    ///
826    /// # Why Diesel Doesn't Wrap These APIs
827    ///
828    /// Certain SQLite features, such as the session extension, are **optional** and only
829    /// available when SQLite is compiled with specific flags (e.g., `-DSQLITE_ENABLE_SESSION`
830    /// and `-DSQLITE_ENABLE_PREUPDATE_HOOK` for sessions). These compile-time options determine
831    /// whether the corresponding C API functions exist in the SQLite library's ABI.
832    ///
833    /// Because Diesel must work with any SQLite library at runtime—including system-provided
834    /// libraries that may lack these optional features—it **cannot safely provide wrappers**
835    /// for APIs that may or may not exist. Doing so would either:
836    ///
837    /// - Cause **linker errors** at compile time if the user's `libsqlite3-sys` wasn't compiled
838    ///   with the required flags, or
839    /// - Cause **undefined behavior** at runtime if Diesel called functions that don't exist
840    ///   in the linked library.
841    ///
842    /// While feature gates could theoretically solve this problem, Diesel already has an
843    /// extensive API surface with many existing feature combinations. Each new feature gate
844    /// adds a **combinatorial explosion** of test configurations that must be validated,
845    /// making the library increasingly difficult to maintain. Therefore, exposing the raw
846    /// connection is the preferred approach for niche SQLite features.
847    ///
848    /// By exposing the raw connection handle, Diesel allows users who **know** they have
849    /// access to a properly configured SQLite build to use these advanced features directly
850    /// through their own FFI bindings.
851    ///
852    /// # Safety
853    ///
854    /// This method is marked `unsafe` because improper use of the raw connection handle
855    /// can lead to undefined behavior. The caller must ensure that:
856    ///
857    /// - The connection handle is **not closed** during the callback.
858    /// - The connection handle is **not stored** beyond the callback's scope.
859    /// - Concurrent access rules are respected (SQLite connections are not thread-safe
860    ///   unless using serialized threading mode).
861    /// - **Transaction state is not modified** — do not execute `BEGIN`, `COMMIT`,
862    ///   `ROLLBACK`, or `SAVEPOINT` statements via the raw handle. Diesel's
863    ///   [`AnsiTransactionManager`] tracks transaction nesting internally, and
864    ///   bypassing it will cause Diesel's view of the transaction state to diverge
865    ///   from SQLite's actual state.
866    /// - **Diesel's prepared statements are not disturbed** — do not call
867    ///   `sqlite3_finalize()` or `sqlite3_reset()` on statements that belong to
868    ///   Diesel's `StatementCache`. Doing so will cause use-after-free or
869    ///   double-free when Diesel later accesses those statements.
870    ///
871    /// [`AnsiTransactionManager`]: crate::connection::AnsiTransactionManager
872    ///
873    /// # Example
874    ///
875    /// ```rust
876    /// use diesel::sqlite::SqliteConnection;
877    /// use diesel::Connection;
878    ///
879    /// let mut conn = SqliteConnection::establish(":memory:").unwrap();
880    ///
881    /// // SAFETY: We do not close or store the connection handle,
882    /// // and we do not modify Diesel-managed state (transactions, cached statements).
883    /// let is_valid = unsafe {
884    ///     conn.with_raw_connection(|raw_conn| {
885    ///         // The raw connection pointer can be passed to SQLite C API functions
886    ///         // from your own `libsqlite3-sys` (native) or `sqlite-wasm-rs` (WASM)
887    ///         // dependency — for example, `sqlite3_get_autocommit(raw_conn)` or
888    ///         // `sqlite3session_create(raw_conn, ...)`.
889    ///         !raw_conn.is_null()
890    ///     })
891    /// };
892    /// assert!(is_valid);
893    /// ```
894    ///
895    /// # Platform Notes
896    ///
897    /// This method works identically on both native and WASM targets. However,
898    /// you must depend on the appropriate FFI crate for your target:
899    ///
900    /// - **Native**: Add `libsqlite3-sys` as a dependency
901    /// - **WASM** (`wasm32-unknown-unknown`): Add `sqlite-wasm-rs` as a dependency
902    ///
903    /// Both crates expose a compatible `sqlite3` type that can be used with the
904    /// pointer returned by this method.
905    #[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    /// Runs `f` with a borrowed `SqliteConnection` wrapping `db`, giving SQLite
914    /// callbacks the full connection API. Statements prepared during `f` are
915    /// finalized on return, but `db` is left open, since SQLite owns it.
916    ///
917    /// # Safety
918    ///
919    /// `db` must be a valid `sqlite3` handle that stays open for the duration
920    /// of the call.
921    #[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        // Tears the borrowed connection down on every exit path, including a
927        // panic unwinding out of `f`.
928        struct Borrowed(core::mem::ManuallyDrop<SqliteConnection>);
929
930        impl Drop for Borrowed {
931            fn drop(&mut self) {
932                // SAFETY: `self.0` is not touched again after this take.
933                let conn = unsafe { core::mem::ManuallyDrop::take(&mut self.0) };
934                let SqliteConnection {
935                    statement_cache,
936                    raw_connection,
937                    ..
938                } = conn;
939                // Finalize prepared statements, but do not run `RawConnection`'s
940                // `Drop`, which would close a handle we do not own.
941                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        // The borrowed connection is discarded without committing or rolling
958        // back, so a transaction left open by `f` would leak onto the handle.
959        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    /// Set a runtime limit for this connection, returning its previous value.
972    ///
973    /// Lowering these limits is a way to harden a connection against untrusted
974    /// SQL. See the [SQLite documentation](https://www.sqlite.org/c3ref/limit.html)
975    /// for the meaning of each [`SqliteLimit`].
976    ///
977    /// # Example
978    ///
979    /// ```rust
980    /// # include!("../../doctest_setup.rs");
981    /// # fn main() { run_test(); }
982    /// # fn run_test() {
983    /// use diesel::sqlite::SqliteLimit;
984    ///
985    /// let mut conn = SqliteConnection::establish(":memory:").unwrap();
986    ///
987    /// // Cap SQL statement length at 1 KiB, keeping the previous value.
988    /// let previous = conn.set_limit(SqliteLimit::SqlLength, 1024);
989    /// assert!(previous > 0);
990    /// assert_eq!(conn.get_limit(SqliteLimit::SqlLength), 1024);
991    /// # }
992    /// ```
993    pub fn set_limit(&mut self, limit: SqliteLimit, value: i32) -> i32 {
994        self.raw_connection.set_limit(limit, value)
995    }
996
997    /// Get the current value of a runtime limit for this connection.
998    ///
999    /// See the [SQLite documentation](https://www.sqlite.org/c3ref/limit.html)
1000    /// for the meaning of each [`SqliteLimit`].
1001    ///
1002    /// # Example
1003    ///
1004    /// ```rust
1005    /// # include!("../../doctest_setup.rs");
1006    /// # fn main() { run_test(); }
1007    /// # fn run_test() {
1008    /// use diesel::sqlite::SqliteLimit;
1009    ///
1010    /// let conn = SqliteConnection::establish(":memory:").unwrap();
1011    /// assert!(conn.get_limit(SqliteLimit::SqlLength) > 0);
1012    /// # }
1013    /// ```
1014    pub fn get_limit(&self, limit: SqliteLimit) -> i32 {
1015        self.raw_connection.get_limit(limit)
1016    }
1017
1018    /// Apply SQLite's recommended limits for hardening against untrusted SQL.
1019    ///
1020    /// These are the values from the "Untrusted SQL Inputs" table of SQLite's
1021    /// [security documentation](https://sqlite.org/security.html). They are
1022    /// intentionally restrictive, so call [`set_limit`](Self::set_limit)
1023    /// afterwards to relax any that are too aggressive for your application.
1024    ///
1025    /// | Limit | Value |
1026    /// |-------|-------|
1027    /// | `Length` | 1,000,000 |
1028    /// | `SqlLength` | 100,000 |
1029    /// | `ColumnCount` | 100 |
1030    /// | `ExprDepth` | 10 |
1031    /// | `CompoundSelect` | 3 |
1032    /// | `VdbeOp` | 25,000 |
1033    /// | `FunctionArg` | 8 |
1034    /// | `Attached` | 0 |
1035    /// | `LikePatternLength` | 50 |
1036    /// | `VariableNumber` | 10 |
1037    /// | `TriggerDepth` | 10 |
1038    ///
1039    /// The table's `PARSER_DEPTH` recommendation is omitted because it is a
1040    /// compile-time only setting with no runtime `sqlite3_limit()` category.
1041    /// `WorkerThreads` is left untouched (its default of 0 is already safe).
1042    ///
1043    /// # Example
1044    ///
1045    /// ```rust
1046    /// # include!("../../doctest_setup.rs");
1047    /// # fn main() { run_test(); }
1048    /// # fn run_test() {
1049    /// use diesel::sqlite::SqliteLimit;
1050    ///
1051    /// let mut conn = SqliteConnection::establish(":memory:").unwrap();
1052    /// conn.set_recommended_security_limits();
1053    /// assert_eq!(conn.get_limit(SqliteLimit::SqlLength), 100_000);
1054    ///
1055    /// // Relax an individual limit that is too strict for this application.
1056    /// conn.set_limit(SqliteLimit::VariableNumber, 999);
1057    /// assert_eq!(conn.get_limit(SqliteLimit::VariableNumber), 999);
1058    /// # }
1059    /// ```
1060    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    /// Enable or disable SQLite defensive mode.
1093    ///
1094    /// When enabled, defensive mode prevents direct writes to shadow tables
1095    /// (FTS5, R-Tree, etc.), dangerous PRAGMAs like `writable_schema`,
1096    /// `sqlite3_deserialize()` from opening unsafe database images, and other
1097    /// potentially dangerous operations. Enable it for any connection that may
1098    /// process untrusted data. It is the single most important hardening flag.
1099    ///
1100    /// Requires SQLite 3.26.0 or later, otherwise returns an error.
1101    ///
1102    /// # Security Hardening Recipe
1103    ///
1104    /// ```rust
1105    /// # include!("../../doctest_setup.rs");
1106    /// # fn main() {
1107    /// #     let mut conn = SqliteConnection::establish(":memory:").unwrap();
1108    /// conn.set_defensive(true).unwrap();
1109    /// conn.set_trusted_schema(false).unwrap();
1110    /// conn.set_recommended_security_limits();
1111    /// # }
1112    /// ```
1113    ///
1114    /// Extension loading is off by default. Enable it only when needed via
1115    /// [`with_load_extension_enabled`][Self::with_load_extension_enabled]. See
1116    /// [`set_recommended_security_limits`][Self::set_recommended_security_limits]
1117    /// to harden the SQLite resource limits as well.
1118    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    /// Check if defensive mode is enabled.
1124    ///
1125    /// See [`set_defensive`][Self::set_defensive] for details.
1126    pub fn is_defensive(&self) -> QueryResult<bool> {
1127        self.raw_connection
1128            .get_db_config_bool(ffi::SQLITE_DBCONFIG_DEFENSIVE)
1129    }
1130
1131    /// Enable or disable trusted schema mode.
1132    ///
1133    /// When disabled (untrusted), SQL functions called from schema objects
1134    /// (views, triggers, CHECK constraints, DEFAULT expressions, generated
1135    /// columns, expression indexes) are restricted to those marked
1136    /// [`INNOCUOUS`][crate::sqlite::SqliteFunctionBehavior::INNOCUOUS]. Disable
1137    /// it when opening database files from untrusted sources, and register your
1138    /// custom functions with appropriate
1139    /// [`SqliteFunctionBehavior`][crate::sqlite::SqliteFunctionBehavior] flags.
1140    ///
1141    /// Requires SQLite 3.31.0 or later, otherwise returns an error.
1142    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    /// Check if trusted schema mode is enabled.
1148    ///
1149    /// See [`set_trusted_schema`][Self::set_trusted_schema] for details.
1150    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    /// Runs the given closure with the `load_extension()` SQL function enabled,
1156    /// disabling it again afterwards.
1157    ///
1158    /// This controls the [`load_extension()`](https://www.sqlite.org/lang_corefunc.html#load_extension)
1159    /// **SQL function**, not the `sqlite3_load_extension()` C API (which Diesel
1160    /// does not expose). Extension loading is off by default, and scoping it to a
1161    /// closure keeps the window in which it is enabled as small as possible.
1162    ///
1163    /// Requires SQLite 3.13.0 or later, otherwise returns an error. Has no effect
1164    /// if SQLite was compiled with `SQLITE_OMIT_LOAD_EXTENSION`.
1165    ///
1166    /// # Panics
1167    ///
1168    /// If `f` panics, extension loading is disabled again before the panic
1169    /// resumes. no-std builds cannot catch the unwind, so there the flag is
1170    /// restored only on a normal return.
1171    ///
1172    /// # Example
1173    ///
1174    /// ```rust
1175    /// # include!("../../doctest_setup.rs");
1176    /// # fn main() {
1177    /// #     let mut conn = SqliteConnection::establish(":memory:").unwrap();
1178    /// let result: QueryResult<()> = conn.with_load_extension_enabled(|_conn| Ok(()));
1179    /// result.unwrap();
1180    /// # }
1181    /// ```
1182    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        // On std builds, catch a panic from `f` so extension loading is restored
1192        // before the panic is resumed. no-std cannot catch unwinding, so there
1193        // the flag is restored only on a normal return.
1194        #[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    /// Enable or disable the `fts3_tokenizer()` SQL function.
1227    ///
1228    /// The [`fts3_tokenizer()`](https://www.sqlite.org/fts3.html#f3tknzr) function
1229    /// allows overloading the default FTS3/FTS4 tokenizer, which can be exploited
1230    /// if an attacker can execute arbitrary SQL. Disable it unless you need custom
1231    /// FTS3 tokenizers.
1232    ///
1233    /// Requires SQLite 3.12.0 or later, otherwise returns an error.
1234    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    /// Check if the `fts3_tokenizer()` SQL function is enabled.
1240    ///
1241    /// See [`set_fts3_tokenizer_enabled`][Self::set_fts3_tokenizer_enabled] for details.
1242    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    /// Enable or disable direct writes to `sqlite_master`.
1248    ///
1249    /// When enabled, allows direct modification of the `sqlite_master` table,
1250    /// which can corrupt the database if misused. Keep it disabled unless you
1251    /// need to repair or modify the schema directly. Defensive mode
1252    /// ([`set_defensive`][Self::set_defensive]) also prevents this.
1253    ///
1254    /// Requires SQLite 3.28.0 or later, otherwise returns an error.
1255    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    /// Check if direct writes to `sqlite_master` are enabled.
1261    ///
1262    /// See [`set_writable_schema`][Self::set_writable_schema] for details.
1263    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    /// Enable or disable ATTACH from creating new database files.
1269    ///
1270    /// When disabled, [`ATTACH`](https://www.sqlite.org/lang_attach.html) can only
1271    /// open existing database files, not create new ones. Disable it where
1272    /// database file creation should be restricted.
1273    ///
1274    /// Requires SQLite 3.49.0 or later, otherwise returns an error.
1275    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    /// Check if ATTACH can create new database files.
1281    ///
1282    /// See [`set_attach_create_enabled`][Self::set_attach_create_enabled] for details.
1283    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    /// Enable or disable ATTACH from opening databases in write mode.
1289    ///
1290    /// When disabled, all attached databases are opened as read-only. Disable it
1291    /// to restrict write access to attached databases.
1292    ///
1293    /// Requires SQLite 3.49.0 or later, otherwise returns an error.
1294    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    /// Check if ATTACH can open databases in write mode.
1300    ///
1301    /// See [`set_attach_write_enabled`][Self::set_attach_write_enabled] for details.
1302    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    /// Attach the database file at `path` under `schema_name`.
1308    ///
1309    /// Runs [`ATTACH DATABASE ? AS ?`](https://www.sqlite.org/lang_attach.html) with
1310    /// both operands bound as parameters, so no SQL string escaping is needed.
1311    /// Diesel always opens connections with `SQLITE_OPEN_URI`, so a path beginning
1312    /// with `file:` is interpreted as a URI exactly as it is in
1313    /// [`SqliteConnection::establish`].
1314    ///
1315    /// A missing file is created as an empty database unless
1316    /// [`set_attach_create_enabled(false)`][Self::set_attach_create_enabled] is set,
1317    /// and [`set_attach_write_enabled(false)`][Self::set_attach_write_enabled] attaches
1318    /// read-only. The attach count is bounded (10 by default). A transaction across the
1319    /// main and an attached file is crash-atomic per file only under WAL.
1320    ///
1321    /// # Example
1322    ///
1323    /// ```rust
1324    /// # include!("../../doctest_setup.rs");
1325    /// #
1326    /// # fn main() {
1327    /// #     run_test().unwrap();
1328    /// # }
1329    /// #
1330    /// # fn run_test() -> QueryResult<()> {
1331    /// #     let conn = &mut SqliteConnection::establish(":memory:").unwrap();
1332    /// conn.attach_database(":memory:", "aux")?;
1333    /// conn.detach_database("aux")?;
1334    /// #     Ok(())
1335    /// # }
1336    /// ```
1337    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    /// Detach the database previously attached under `schema_name`.
1345    ///
1346    /// Runs [`DETACH DATABASE ?`](https://www.sqlite.org/lang_detach.html) with the
1347    /// schema name bound as a parameter. Detaching a schema still in use fails with an
1348    /// ordinary error.
1349    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    /// Enable or disable trigger execution.
1355    ///
1356    /// When disabled, triggers will not fire for any DML operations.
1357    ///
1358    /// Requires SQLite 3.8.7 or later, otherwise returns an error.
1359    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    /// Check if trigger execution is enabled.
1365    ///
1366    /// See [`set_triggers_enabled`][Self::set_triggers_enabled] for details.
1367    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    /// Enable or disable view expansion.
1373    ///
1374    /// When disabled, queries against views will fail.
1375    ///
1376    /// Requires SQLite 3.30.0 or later, otherwise returns an error.
1377    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    /// Check if view expansion is enabled.
1383    ///
1384    /// See [`set_views_enabled`][Self::set_views_enabled] for details.
1385    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    /// Enable or disable foreign key constraint enforcement.
1391    ///
1392    /// This is equivalent to `PRAGMA foreign_keys = ON/OFF`.
1393    ///
1394    /// Requires SQLite 3.8.7 or later, otherwise returns an error.
1395    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    /// Check if foreign key constraints are enabled.
1401    ///
1402    /// See [`set_foreign_keys_enabled`][Self::set_foreign_keys_enabled] for details.
1403    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    /// Enable or disable double-quoted strings in DML statements.
1409    ///
1410    /// When enabled, double-quoted strings are interpreted as string literals
1411    /// rather than identifiers, a legacy behavior that can cause issues. Disable
1412    /// it for stricter SQL compliance.
1413    ///
1414    /// Requires SQLite 3.29.0 or later, otherwise returns an error.
1415    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    /// Check if double-quoted strings in DML are enabled.
1421    ///
1422    /// See [`set_double_quoted_strings_dml`][Self::set_double_quoted_strings_dml] for details.
1423    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    /// Enable or disable double-quoted strings in DDL statements.
1429    ///
1430    /// When enabled, double-quoted strings are interpreted as string literals
1431    /// rather than identifiers, a legacy behavior that can cause issues. Disable
1432    /// it for stricter SQL compliance.
1433    ///
1434    /// Requires SQLite 3.29.0 or later, otherwise returns an error.
1435    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    /// Check if double-quoted strings in DDL are enabled.
1441    ///
1442    /// See [`set_double_quoted_strings_ddl`][Self::set_double_quoted_strings_ddl] for details.
1443    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    /// Read the [`auto_vacuum`](AutoVacuumMode) mode of a database.
1449    ///
1450    /// `schema` selects an attached database by name, `None` reads `main`.
1451    ///
1452    /// ```rust
1453    /// # include!("../../doctest_setup.rs");
1454    /// # fn main() {
1455    /// #     run_test().unwrap();
1456    /// # }
1457    /// # fn run_test() -> QueryResult<()> {
1458    /// use diesel::sqlite::AutoVacuumMode;
1459    /// let conn = &mut SqliteConnection::establish(":memory:").unwrap();
1460    /// assert_eq!(conn.auto_vacuum(None)?, AutoVacuumMode::None);
1461    /// #     Ok(())
1462    /// # }
1463    /// ```
1464    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    /// Set the [`auto_vacuum`](AutoVacuumMode) mode of a database.
1471    ///
1472    /// `schema` selects an attached database by name, `None` targets `main`.
1473    /// Changing from or to [`AutoVacuumMode::None`] only takes effect on a
1474    /// database with no tables yet, or after a subsequent `VACUUM`.
1475    ///
1476    /// ```rust
1477    /// # include!("../../doctest_setup.rs");
1478    /// # fn main() {
1479    /// #     run_test().unwrap();
1480    /// # }
1481    /// # fn run_test() -> QueryResult<()> {
1482    /// use diesel::sqlite::AutoVacuumMode;
1483    /// let conn = &mut SqliteConnection::establish(":memory:").unwrap();
1484    /// conn.set_auto_vacuum(None, AutoVacuumMode::Incremental)?;
1485    /// assert_eq!(conn.auto_vacuum(None)?, AutoVacuumMode::Incremental);
1486    /// #     Ok(())
1487    /// # }
1488    /// ```
1489    pub fn set_auto_vacuum(
1490        &mut self,
1491        schema: Option<&str>,
1492        mode: AutoVacuumMode,
1493    ) -> QueryResult<()> {
1494        use crate::query_dsl::RunQueryDsl;
1495        // #[repr(i32)] guarantees the discriminant fits exactly in i32.
1496        SetPragmaInt {
1497            schema,
1498            name: "auto_vacuum",
1499            value: mode as i32,
1500        }
1501        .execute(self)
1502        .map(|_| ())
1503    }
1504
1505    /// Total number of pages in a database, via `PRAGMA page_count`.
1506    ///
1507    /// `schema` selects an attached database by name, `None` reads `main`.
1508    /// Multiply by the page size for the size the database accounts for.
1509    ///
1510    /// ```rust
1511    /// # include!("../../doctest_setup.rs");
1512    /// # fn main() {
1513    /// #     run_test().unwrap();
1514    /// # }
1515    /// # fn run_test() -> QueryResult<()> {
1516    /// use diesel::connection::SimpleConnection;
1517    /// let conn = &mut SqliteConnection::establish(":memory:").unwrap();
1518    /// // An empty database occupies no pages until something is written.
1519    /// assert_eq!(conn.page_count(None)?, 0);
1520    /// conn.batch_execute("CREATE TABLE items (id INTEGER PRIMARY KEY)")?;
1521    /// assert!(conn.page_count(None)? > 0);
1522    /// #     Ok(())
1523    /// # }
1524    /// ```
1525    pub fn page_count(&mut self, schema: Option<&str>) -> QueryResult<i64> {
1526        self.read_pragma_count("page_count", schema)
1527    }
1528
1529    /// Unused pages on a database's freelist, via `PRAGMA freelist_count`.
1530    ///
1531    /// `schema` selects an attached database by name, `None` reads `main`. A
1532    /// growing freelist is reclaimable space, freed by `VACUUM`.
1533    ///
1534    /// ```rust
1535    /// # include!("../../doctest_setup.rs");
1536    /// # fn main() {
1537    /// #     run_test().unwrap();
1538    /// # }
1539    /// # fn run_test() -> QueryResult<()> {
1540    /// let conn = &mut SqliteConnection::establish(":memory:").unwrap();
1541    /// assert_eq!(conn.freelist_count(None)?, 0);
1542    /// #     Ok(())
1543    /// # }
1544    /// ```
1545    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    /// Shrink a database by releasing freelist pages, without the full rewrite
1561    /// [`VACUUM`](https://www.sqlite.org/lang_vacuum.html) performs.
1562    ///
1563    /// `schema` selects an attached database by name, `None` targets `main`. `pages`
1564    /// bounds how many pages are reclaimed. As SQLite specifies, `None` or a value
1565    /// below one clears the whole freelist, as does a bound larger than it. Only
1566    /// databases in [`AutoVacuumMode::Incremental`] have anything to reclaim, on any
1567    /// other mode this succeeds and does nothing.
1568    ///
1569    /// ```rust
1570    /// # include!("../../doctest_setup.rs");
1571    /// # fn main() {
1572    /// #     run_test().unwrap();
1573    /// # }
1574    /// # fn run_test() -> QueryResult<()> {
1575    /// use diesel::sqlite::AutoVacuumMode;
1576    /// let conn = &mut SqliteConnection::establish(":memory:").unwrap();
1577    /// conn.set_auto_vacuum(None, AutoVacuumMode::Incremental)?;
1578    /// // Reclaim at most 8 pages, then whatever is left.
1579    /// conn.incremental_vacuum(None, Some(8))?;
1580    /// conn.incremental_vacuum(None, None)?;
1581    /// #     Ok(())
1582    /// # }
1583    /// ```
1584    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        // SQLite frees one page per step of this statement, so it only empties the
1594        // freelist when run to completion. `batch_execute` uses `sqlite3_exec`, which
1595        // does that. A prepared statement would not: `StatementUse::run` steps once,
1596        // which frees a single page and silently leaves the rest.
1597        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    /// Rebuild a database, repacking it into the smallest space it can occupy.
1610    ///
1611    /// `schema` selects an attached database by name, `None` targets `main`.
1612    ///
1613    /// This cannot run inside a transaction, needs free space of up to twice the size
1614    /// of the database while it runs, and renumbers the implicit `rowid` of any table
1615    /// declared without an `INTEGER PRIMARY KEY`.
1616    ///
1617    /// Naming a schema requires SQLite 3.24.0 or later, otherwise returns an error.
1618    ///
1619    /// ```rust
1620    /// # include!("../../doctest_setup.rs");
1621    /// # fn main() {
1622    /// #     run_test().unwrap();
1623    /// # }
1624    /// # fn run_test() -> QueryResult<()> {
1625    /// let conn = &mut SqliteConnection::establish(":memory:").unwrap();
1626    /// conn.vacuum(None)?;
1627    /// #     Ok(())
1628    /// # }
1629    /// ```
1630    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    /// Write a vacuumed copy of a database to `path`, leaving the original untouched.
1637    ///
1638    /// This is SQLite's online backup: the copy is consistent, defragmented, and taken
1639    /// without blocking readers. `schema` selects an attached database by name, `None`
1640    /// copies `main`. The path is a bind parameter, so it needs no quoting.
1641    ///
1642    /// `path` may name a file that does not exist or one that is empty, but writing
1643    /// over an existing database fails rather than replacing it.
1644    ///
1645    /// Requires SQLite 3.27.0 or later, otherwise returns an error.
1646    ///
1647    /// ```rust
1648    /// # include!("../../doctest_setup.rs");
1649    /// # fn main() {
1650    /// #     run_test().unwrap();
1651    /// # }
1652    /// # fn run_test() -> QueryResult<()> {
1653    /// # let dir = tempfile::tempdir().unwrap();
1654    /// # let backup = dir.path().join("backup.db");
1655    /// let conn = &mut SqliteConnection::establish(":memory:").unwrap();
1656    /// conn.vacuum_into(None, backup.to_str().unwrap())?;
1657    /// # assert!(backup.exists());
1658    /// #     Ok(())
1659    /// # }
1660    /// ```
1661    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    /// Checkpoint the [write-ahead log](https://www.sqlite.org/wal.html),
1673    /// moving committed frames from the WAL file into the database file.
1674    ///
1675    /// `schema` selects one attached database by name. Unlike the other
1676    /// maintenance helpers, `None` does not mean `main`: SQLite defines the
1677    /// unqualified pragma to checkpoint every attached database. With
1678    /// `None` and several attached databases the C API leaves the frame
1679    /// counts undefined.
1680    ///
1681    /// A checkpoint stopped early by a reader or writer on another
1682    /// connection is not an error: it sets
1683    /// [`busy`](WalCheckpointOutcome::busy). On a database that is not in
1684    /// WAL mode the call succeeds with both frame counts `None`, so it is
1685    /// safe to issue unconditionally. Inside a transaction on its own
1686    /// connection it fails with `SQLITE_LOCKED`.
1687    ///
1688    /// The mode argument requires SQLite 3.7.6 or later,
1689    /// [`Truncate`](WalCheckpointMode::Truncate) requires 3.8.8 or later,
1690    /// and [`Noop`](WalCheckpointMode::Noop) requires 3.51.0 or later.
1691    /// Older versions do not report an error and treat an unrecognized
1692    /// mode as [`Passive`](WalCheckpointMode::Passive).
1693    ///
1694    /// ```rust
1695    /// # include!("../../doctest_setup.rs");
1696    /// #
1697    /// # fn main() {
1698    /// #     run_test().unwrap();
1699    /// # }
1700    /// #
1701    /// # fn run_test() -> QueryResult<()> {
1702    /// use diesel::connection::SimpleConnection;
1703    /// use diesel::sqlite::WalCheckpointMode;
1704    /// # let dir = tempfile::tempdir().unwrap();
1705    /// # let path = dir.path().join("app.db");
1706    /// let conn = &mut SqliteConnection::establish(path.to_str().unwrap()).unwrap();
1707    /// conn.batch_execute("PRAGMA journal_mode = WAL")?;
1708    /// conn.batch_execute("CREATE TABLE logs (line TEXT NOT NULL)")?;
1709    ///
1710    /// let outcome = conn.wal_checkpoint(None, WalCheckpointMode::Truncate)?;
1711    /// assert!(!outcome.busy);
1712    /// // The whole WAL was moved into the database file and the log truncated.
1713    /// assert_eq!(outcome.log_frames, Some(0));
1714    /// assert_eq!(outcome.checkpointed_frames, Some(0));
1715    /// #     Ok(())
1716    /// # }
1717    /// ```
1718    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            // On a database not in WAL mode both counts come back as -1.
1730            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        // This function has side effects (creates triggers), so it should not
1739        // be deterministic. We use DIRECTONLY to prevent it from being called
1740        // from malicious schema objects in untrusted databases.
1741        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 // have to return *something*
1752            },
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
1810// A `PRAGMA` accepts no bind parameters, neither for the schema it targets nor for the
1811// value it assigns, so the schema is rendered as a quoted identifier by the query
1812// builder. `name` is always a constant chosen here, never caller data.
1813struct 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
1839// The schema name is runtime data, so the rendered SQL is not determined by the type.
1840impl<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
1852// `PRAGMA name = value` takes no bind parameter for the value either, so the integer is
1853// rendered as a literal.
1854struct 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
1880// `VACUUM` names its schema as an identifier, so that operand is quoted by the query
1881// builder, while the `INTO` destination is an expression and binds normally.
1882struct 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
1899// The schema name is runtime data, so the rendered SQL is not determined by the type.
1900impl QueryId for Vacuum<'_> {
1901    type QueryId = ();
1902
1903    const HAS_STATIC_QUERY_ID: bool = false;
1904}
1905
1906impl RunQueryDslSupport for Vacuum<'_> {}
1907
1908// Like `Pragma`, no operand can be a bind parameter. Unlike `Pragma`, a
1909// `None` schema stays unqualified on purpose: the unqualified pragma
1910// checkpoints every attached database, while a qualified one targets a
1911// single schema. The whole checkpoint runs on the first step of the
1912// statement and yields exactly one row, so a prepared statement works here
1913// (unlike `incremental_vacuum`).
1914struct 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
1937// The schema name and mode are runtime data, so the rendered SQL is not determined by the type.
1938impl 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        // SAFETY: We only read connection status, which doesn't modify state.
1972        let autocommit_status = unsafe {
1973            connection.with_raw_connection(|raw_conn| ffi::sqlite3_get_autocommit(raw_conn))
1974        };
1975
1976        // Outside a transaction, autocommit should be enabled (returns non-zero)
1977        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        // First, do some Diesel operations
1986        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        // SAFETY: We only read the last insert rowid, which is a read-only operation.
1994        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        // Verify Diesel still works after using raw connection
2001        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        // Create a table using Diesel first
2013        crate::sql_query("CREATE TABLE raw_test (id INTEGER PRIMARY KEY, name TEXT)")
2014            .execute(connection)
2015            .unwrap();
2016
2017        // SAFETY: We execute a simple INSERT via raw SQLite API.
2018        // This modifies the database but in a way compatible with Diesel.
2019        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        // Verify the insert worked using Diesel
2040        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                // SAFETY: We only read the autocommit status inside a transaction.
2067                let autocommit = unsafe {
2068                    conn.with_raw_connection(|raw_conn| ffi::sqlite3_get_autocommit(raw_conn))
2069                };
2070
2071                // Inside a transaction, autocommit should be disabled (returns 0)
2072                assert_eq!(
2073                    autocommit, 0,
2074                    "Autocommit should be disabled inside transaction"
2075                );
2076
2077                Ok(())
2078            })
2079            .unwrap();
2080
2081        // After transaction commits, autocommit should be re-enabled
2082        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        // SAFETY: We only read the database filename, which is a read-only operation.
2097        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                    // For :memory: databases, this might return empty string or special value
2105                    let cstr = core::ffi::CStr::from_ptr(filename_ptr);
2106                    Some(cstr.to_string_lossy().into_owned())
2107                }
2108            })
2109        };
2110
2111        // For in-memory databases, sqlite3_db_filename returns a non-null pointer
2112        // to an empty string
2113        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        // Update all rows using raw connection
2134        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        // Verify the updates using Diesel
2156        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    // catch_unwind is not available in WASM (panic = "abort")
2163    #[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        // Panic inside the callback
2174        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        // Connection should still be usable after the panic
2182        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    // Filesystem access is not available in WASM
2193    #[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        // Clean up from any previous run
2201        let _ = std::fs::remove_file(db_path);
2202
2203        let connection = &mut SqliteConnection::establish(db_path).unwrap();
2204
2205        // SAFETY: We only read the database filename, which is a read-only operation.
2206        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        // Clean up
2226        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            // drop the database here
2279            // and requery the database to make sure the database owns
2280            // required data
2281            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        // only get a valid header, but append garbage
2313        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        // only get a valid header, but append garbage
2327        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        // 64 KiB covers statement setup but cannot hold the 1 MiB serialization,
2367        // pinning the failure to output allocation after SQLite reports its size.
2368        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                // SAFETY: `sqlite3_serialize` returned this buffer and no wrapper owns it.
2387                unsafe { ffi::sqlite3_free(baseline as _) };
2388
2389                let (reported_size, data) = with_failing_serialize(|| serialize_direct(&conn));
2390                if !data.is_null() {
2391                    // SAFETY: `sqlite3_serialize` returned this buffer and no wrapper owns it.
2392                    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                // SQLite reports the required size before attempting output allocation.
2399                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            // SAFETY: The connection is live, a null schema selects `main`, and `size` is a writable out-parameter.
2436            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    // regression test for https://github.com/diesel-rs/diesel/issues/3425
2731    #[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        // This should fail due to UNIQUE constraint
2848        let result = crate::sql_query("INSERT INTO li_test2 (val) VALUES ('a')").execute(conn);
2849        assert!(result.is_err());
2850
2851        // rowid should be unchanged
2852        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        // Seek one forward
2955        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        // Seek back to start
2962        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        // Seek before start
2969        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        // Seek after end
2976        data.seek(SeekFrom::Current(100)).unwrap();
2977
2978        // Now we don't get any bytes back
2979        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        // Setting a new value returns the old one, and a second set returns the
3173        // value installed by the first.
3174        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        // Querying is implemented by passing -1 to sqlite3_limit, which must
3184        // leave the limit unchanged.
3185        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        // A 500-byte blob exceeds the 100-byte row/value limit ("string or blob too big").
3200        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        // A wide result set runs under the default column limit but fails once the limit is
3210        // lowered below its column count ("too many columns in result set").
3211        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        // A 40-deep addition tree exceeds the parse-tree depth of five.
3234        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        // Five UNION terms exceed the limit of two ("too many terms in compound SELECT").
3249        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        // The same heavy statement runs under the default opcode budget but fails once that
3261        // budget is restricted to a tiny value (reported as SQLITE_NOMEM).
3262        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        // Eight arguments exceed the limit of three ("too many arguments on function max").
3283        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        // With zero attachments allowed, any ATTACH is rejected ("too many attached databases").
3296        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        // The published default sits below the bundled ceiling, so it is applied verbatim and
3307        // acts as the boundary: a parameter index at the limit is accepted, one past it is
3308        // rejected ("variable number must be between ?1 and ?N").
3309        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        // A recursive trigger that terminates on its own at x = 100.
3327        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        // Under the default depth the recursion completes.
3333        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        // A tiny depth limit is hit before the recursion can terminate
3342        // ("too many levels of trigger recursion").
3343        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        // Unlike the other categories, WorkerThreads only caps the number of auxiliary sort
3356        // threads a statement may start. Lowering it never raises an error, it only affects
3357        // performance. There is therefore no enforcement failure to assert, only that the value
3358        // is applied and ordinary queries keep working.
3359        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        // A statement longer than 20 bytes is rejected by SQLite.
3371        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        // SQLite clamps a requested value to its hard compile-time ceiling
3396        // rather than accepting it verbatim.
3397        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        // The hardened value for each category is a tightening of SQLite's published default, so
3423        // it must never be larger. This is asserted instead of comparing the `DEFAULT_*`
3424        // constants to a fresh connection, because the runtime default of categories such as
3425        // `FunctionArg` and `VariableNumber` is build-dependent (the bundled libsqlite3-sys
3426        // raises several of them), while these published constants are fixed.
3427        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        // The recommended setter leaves `WorkerThreads` untouched because its default is already
3535        // safe, so assert that the documented safe value matches what the connection reports.
3536        assert_eq!(
3537            conn.get_limit(SqliteLimit::WorkerThreads),
3538            SqliteLimit::SAFE_WORKER_THREADS_LIMIT
3539        );
3540    }
3541
3542    // ---- db_config tests ----
3543
3544    #[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            // Enabled for the duration of the closure.
3567            assert!(conn.is_load_extension_enabled().unwrap());
3568            QueryResult::Ok(())
3569        })
3570        .unwrap();
3571        // Disabled again afterwards.
3572        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        // ATTACH_CREATE requires SQLite 3.46.0+; skip if unsupported
3661        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        // ATTACH_WRITE requires SQLite 3.46.0+; skip if unsupported
3673        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    // ---- behavioral db_config tests ----
3682
3683    #[diesel_test_helper::test]
3684    fn defensive_mode_blocks_writable_schema() {
3685        let conn = &mut connection();
3686        conn.set_defensive(true).unwrap();
3687        // In defensive mode, writable_schema should remain off even if we try to set it
3688        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        // Insert a child row with no matching parent — should fail with FK enabled
3707        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        // Enabled (default): the view can be queried.
3726        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        // Disabled: queries that reference the view fail.
3734        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        // Disabled: inserting into `source` must not fire the trigger.
3756        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        // Enabled: the trigger fires and writes one row.
3766        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        // Disabled: a double-quoted token in DML is parsed as an identifier, so a
3781        // bare `"text"` that is not a column errors.
3782        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        // Enabled: the same token is accepted as a string literal.
3787        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        // Disabled: a double-quoted token in a CHECK constraint is parsed as an
3797        // identifier. As there is no such column, creating the table errors.
3798        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        // Enabled: the same token is accepted as a string literal, so the CHECK
3805        // constraint (and the table) are created successfully.
3806        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        // Disabled (default): a direct write to sqlite_master is rejected.
3824        conn.set_writable_schema(false).unwrap();
3825        assert!(crate::sql_query(update).execute(conn).is_err());
3826
3827        // Enabled: the same write is permitted.
3828        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        // Enable first to detect whether FTS3 is compiled into this SQLite build.
3837        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            // FTS3 is not available in this build, so there is nothing to assert.
3842            return;
3843        }
3844
3845        // Disabled: the `fts3_tokenizer()` SQL function is no longer callable.
3846        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    // These ATTACH tests need a real filesystem (temp files), which is not
3853    // available on the wasm target, where SQLite is in-memory only.
3854    #[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        // The ATTACH_CREATE option was added in SQLite 3.49.0; skip on older
3867        // libraries (e.g. the system SQLite on the Ubuntu 24.04 CI runners).
3868        if conn.set_attach_create_enabled(false).is_err() {
3869            return;
3870        }
3871
3872        let (_dir, path) = temp_db_path("create.db");
3873
3874        // Disabled: attaching a path that does not exist yet must fail.
3875        assert!(
3876            conn.attach_database(path.to_str().unwrap(), "aux_create")
3877                .is_err()
3878        );
3879
3880        // Enabled: the same ATTACH now creates and opens the file.
3881        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        // The ATTACH_WRITE option was added in SQLite 3.49.0; skip on older
3893        // libraries (e.g. the system SQLite on the Ubuntu 24.04 CI runners).
3894        // This guard also leaves ATTACH_WRITE disabled for the first check below.
3895        if conn.set_attach_write_enabled(false).is_err() {
3896            return;
3897        }
3898
3899        // Seed an existing on-disk database with a table to write into.
3900        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        // Disabled: the attached database is opened read-only, so writes fail.
3909        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        // Enabled: the attached database is writable again.
3919        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    // Tables used by the ATTACH round-trip tests below. Their CREATE statements are
3929    // DDL (raw SQL), but rows and reads go through the typed query DSL.
3930    #[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        // Schema-qualified CREATE is DDL and stays raw SQL. The rows and the join
3970        // below use the typed query DSL.
3971        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        // The attached schema is gone, so querying it now fails.
4004        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        // A single quote in the path would break a hand-assembled ATTACH statement.
4016        // Bound parameters take the path verbatim.
4017        let (_dir, path) = temp_db_path("o'brien.db");
4018
4019        conn_attach_roundtrip(&path);
4020
4021        // The table created through ATTACH persisted to the literal file, so a
4022        // fresh connection to that exact path can read it.
4023        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        // Seed a database with a row to read through the attached schema.
4047        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        // Attach with a `file:` URI and `mode=ro`. If SQLite treated the bound
4059        // string as a literal filename it would fail to find the file; interpreting
4060        // it as a URI opens the real file in read-only mode instead.
4061        let uri = format!("file:{}?mode=ro", path.display());
4062        let conn = &mut connection();
4063        conn.attach_database(&uri, "ro_schema").unwrap();
4064
4065        // Read from the attached schema: proves the ATTACH opened a real file.
4066        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        // Write fails: the URI `mode=ro` parameter took effect.
4072        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        // A duplicate schema name on attach is an error, not a panic.
4086        conn.attach_database(":memory:", "dup").unwrap();
4087        assert!(conn.attach_database(":memory:", "dup").is_err());
4088        conn.detach_database("dup").unwrap();
4089
4090        // Detaching an unknown schema is likewise an error. Detaching one still in
4091        // use cannot occur through the safe API: an in-flight iterator holds
4092        // `&mut conn`, so no detach can overlap it.
4093        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        // A space or quote in the schema name would need identifier quoting in a
4103        // hand-assembled statement. Bound as a parameter it is taken verbatim. The
4104        // reference below stays raw SQL: such a schema is not expressible via `table!`.
4105        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        // ATTACH_CREATE and ATTACH_WRITE need SQLite 3.49.0+, so skip on older libraries.
4129        if conn.set_attach_create_enabled(false).is_err() {
4130            return;
4131        }
4132
4133        // Create disabled: attaching a nonexistent path fails and materializes nothing.
4134        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        // Write disabled: an existing database attaches read-only, so writes fail.
4142        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    // ---- DIRECTONLY / INNOCUOUS function behavior tests ----
4161
4162    #[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        // Register a DIRECTONLY function
4173        directonly_fn_utils::register_impl_with_behavior(
4174            conn,
4175            SqliteFunctionBehavior::DIRECTONLY,
4176            || 42,
4177        )
4178        .unwrap();
4179
4180        // Direct call works
4181        let result = crate::select(directonly_fn()).get_result::<i32>(conn);
4182        assert_eq!(Ok(42), result);
4183
4184        // Create a view that calls the function
4185        crate::sql_query("CREATE VIEW test_view AS SELECT directonly_fn() AS val")
4186            .execute(conn)
4187            .unwrap();
4188
4189        // Disable trusted schema so DIRECTONLY is enforced from schema objects
4190        conn.set_trusted_schema(false).unwrap();
4191
4192        // Querying the view should fail because the function is DIRECTONLY
4193        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        // Register an INNOCUOUS function
4202        innocuous_fn_utils::register_impl_with_behavior(
4203            conn,
4204            SqliteFunctionBehavior::DETERMINISTIC | SqliteFunctionBehavior::INNOCUOUS,
4205            || 99,
4206        )
4207        .unwrap();
4208
4209        // Create a view that calls the function
4210        crate::sql_query("CREATE VIEW innocuous_view AS SELECT innocuous_fn() AS val")
4211            .execute(conn)
4212            .unwrap();
4213
4214        // Disable trusted schema
4215        conn.set_trusted_schema(false).unwrap();
4216
4217        // Querying the view should succeed because the function is INNOCUOUS
4218        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        // On a populated database the switch away from `None` is silently
4264        // deferred until a full rewrite.
4265        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    // Large enough to spill onto overflow pages, so the database outgrows a single page
4337    // and leaves reclaimable pages behind once the row is deleted.
4338    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        // `VACUUM` has no query DSL equivalent.
4386        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        // The query builder quotes SQLite identifiers with backticks, so a backtick is
4424        // the character that has to be doubled.
4425        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    // Leaves many pages on the freelist, so `incremental_vacuum` has something to
4444    // reclaim and a bound smaller than the freelist is meaningful.
4445    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    // The same, in an attached schema.
4463    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        // Stepping the pragma only once would free a single page and leave the rest, so
4496        // this also pins that the statement is driven to completion.
4497        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        // An unquoted identifier would be a syntax error, and the wrong quoting would
4551        // address a different database.
4552        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        // SQLite specifies that a bound below one clears the whole freelist.
4564        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    // Leaves the database holding one small row but occupying many pages, so a rebuild
4583    // has something to reclaim.
4584    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    // The same, in an attached schema.
4601    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        // A quote in the path would break a hand-assembled statement. It is a bind
4663        // parameter, so it is taken verbatim.
4664        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        // In the copy the table sits in `main`, while `aux_pragma_probe` is declared
4727        // schema-qualified, so this one read cannot go through it.
4728        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        // The schema is part of the rendered SQL, so the two calls must not share a
4746        // prepared statement. If they did, the second would rebuild the first's
4747        // database again and leave this one untouched.
4748        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    // WAL requires a real file.
4770    #[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            // A fresh row per round gives every mode frames to move.
4825            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        // NOOP exists since SQLite 3.51.0, older versions run PASSIVE instead.
4854        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                // Take the read snapshot, BEGIN alone defers it to the first read.
4894                let _ = pragma_probe::table.count().get_result::<i64>(reader)?;
4895
4896                // Grow the WAL past the reader's snapshot, so a blocking
4897                // checkpoint cannot complete.
4898                crate::insert_into(pragma_probe::table)
4899                    .values((pragma_probe::id.eq(2), pragma_probe::payload.eq("late")))
4900                    .execute(writer)?;
4901
4902                // Passive is never reported busy, it checkpoints up to the
4903                // reader's snapshot and leaves the rest.
4904                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        // `main` is not in WAL mode, so a checkpoint naming it reports no frames.
4961        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        // Both WALs are empty afterwards, which a qualified passive
4990        // checkpoint reports without moving anything.
4991        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        // An unquoted identifier would be a syntax error, and the wrong quoting
5005        // would address a different database.
5006        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}