1use super::SqliteConnection;
2use crate::query_builder::{AstPass, Query, QueryFragment, QueryId};
3use crate::query_dsl::RunQueryDslSupport;
4use crate::result::QueryResult;
5use crate::sqlite::Sqlite;
6use alloc::string::ToString;
7use core::marker::PhantomData;
8
9#[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) -> Self { *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: &Self) -> bool {
::core::intrinsics::discriminant_value(self) ==
::core::intrinsics::discriminant_value(other)
}
}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)]
21#[diesel(sql_type = crate::sql_types::Integer)]
22#[non_exhaustive]
23#[repr(i32)]
24pub enum AutoVacuumMode {
25 None = 0,
27 Full = 1,
29 Incremental = 2,
32}
33
34#[derive(#[automatically_derived]
impl ::core::fmt::Debug for WalCheckpointMode {
#[inline]
fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
::core::fmt::Formatter::debug_c_like_enum_write_str(f,
"Passive Full Restart Truncate Noop",
match self {
WalCheckpointMode::Passive => 0usize,
WalCheckpointMode::Full => 8usize,
WalCheckpointMode::Restart => 13usize,
WalCheckpointMode::Truncate => 21usize,
WalCheckpointMode::Noop => 30usize,
})
}
}Debug, #[automatically_derived]
#[doc(hidden)]
unsafe impl ::core::clone::TrivialClone for WalCheckpointMode { }
#[automatically_derived]
impl ::core::clone::Clone for WalCheckpointMode {
#[inline]
fn clone(&self) -> Self { *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: &Self) -> bool {
::core::intrinsics::discriminant_value(self) ==
::core::intrinsics::discriminant_value(other)
}
}PartialEq, #[automatically_derived]
impl ::core::cmp::Eq for WalCheckpointMode { }Eq)]
38#[non_exhaustive]
39pub enum WalCheckpointMode {
40 Passive,
42 Full,
45 Restart,
48 Truncate,
51 Noop,
57}
58
59#[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) -> Self {
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: &Self) -> 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)]
61#[non_exhaustive]
62pub struct WalCheckpointOutcome {
63 pub busy: bool,
67 pub log_frames: Option<i64>,
70 pub checkpointed_frames: Option<i64>,
75}
76
77impl SqliteConnection {
78 pub fn auto_vacuum(&mut self, schema: Option<&str>) -> QueryResult<AutoVacuumMode> {
95 use crate::query_dsl::RunQueryDsl;
96 let query: Pragma<'_, crate::sql_types::Integer> = Pragma::new("auto_vacuum", schema);
97 query.get_result(self)
98 }
99
100 pub fn set_auto_vacuum(
120 &mut self,
121 schema: Option<&str>,
122 mode: AutoVacuumMode,
123 ) -> QueryResult<()> {
124 use crate::query_dsl::RunQueryDsl;
125 SetPragmaInt {
127 schema,
128 name: "auto_vacuum",
129 value: mode as i32,
130 }
131 .execute(self)
132 .map(|_| ())
133 }
134
135 pub fn page_count(&mut self, schema: Option<&str>) -> QueryResult<i64> {
156 self.read_pragma_count("page_count", schema)
157 }
158
159 pub fn freelist_count(&mut self, schema: Option<&str>) -> QueryResult<i64> {
176 self.read_pragma_count("freelist_count", schema)
177 }
178
179 fn read_pragma_count(
180 &mut self,
181 pragma: &'static str,
182 schema: Option<&str>,
183 ) -> QueryResult<i64> {
184 use crate::query_dsl::RunQueryDsl;
185
186 let query: Pragma<'_, crate::sql_types::BigInt> = Pragma::new(pragma, schema);
187 query.get_result(self)
188 }
189
190 pub fn incremental_vacuum(
215 &mut self,
216 schema: Option<&str>,
217 pages: Option<u32>,
218 ) -> QueryResult<()> {
219 use crate::connection::SimpleConnection;
220 use crate::query_builder::QueryBuilder;
221 use crate::sqlite::SqliteQueryBuilder;
222
223 let mut query = SqliteQueryBuilder::new();
228 query.push_sql("PRAGMA ");
229 query.push_identifier(schema.unwrap_or("main"))?;
230 query.push_sql(".incremental_vacuum");
231 if let Some(pages) = pages {
232 query.push_sql("(");
233 query.push_sql(&pages.to_string());
234 query.push_sql(")");
235 }
236 self.batch_execute(&query.finish())
237 }
238
239 pub fn vacuum(&mut self, schema: Option<&str>) -> QueryResult<()> {
261 use crate::query_dsl::RunQueryDsl;
262
263 Vacuum { schema, into: None }.execute(self).map(|_| ())
264 }
265
266 pub fn vacuum_into(&mut self, schema: Option<&str>, path: &str) -> QueryResult<()> {
297 use crate::query_dsl::RunQueryDsl;
298
299 Vacuum {
300 schema,
301 into: Some(path),
302 }
303 .execute(self)
304 .map(|_| ())
305 }
306
307 pub fn wal_checkpoint(
359 &mut self,
360 schema: Option<&str>,
361 mode: WalCheckpointMode,
362 ) -> QueryResult<WalCheckpointOutcome> {
363 use crate::query_dsl::RunQueryDsl;
364
365 let (busy, log_frames, checkpointed_frames) =
366 WalCheckpoint { schema, mode }.get_result::<(i32, i64, i64)>(self)?;
367 Ok(WalCheckpointOutcome {
368 busy: busy != 0,
369 log_frames: (log_frames >= 0).then_some(log_frames),
371 checkpointed_frames: (checkpointed_frames >= 0).then_some(checkpointed_frames),
372 })
373 }
374}
375
376struct Pragma<'a, ST> {
380 schema: Option<&'a str>,
381 name: &'static str,
382 sql_type: PhantomData<ST>,
383}
384
385impl<'a, ST> Pragma<'a, ST> {
386 fn new(name: &'static str, schema: Option<&'a str>) -> Self {
387 Pragma {
388 schema,
389 name,
390 sql_type: PhantomData,
391 }
392 }
393}
394
395impl<ST> QueryFragment<Sqlite> for Pragma<'_, ST> {
396 fn walk_ast<'b>(&'b self, mut out: AstPass<'_, 'b, Sqlite>) -> QueryResult<()> {
397 out.push_sql("PRAGMA ");
398 out.push_identifier(self.schema.unwrap_or("main"))?;
399 out.push_sql(".");
400 out.push_sql(self.name);
401 Ok(())
402 }
403}
404
405impl<ST> QueryId for Pragma<'_, ST> {
407 type QueryId = ();
408
409 const HAS_STATIC_QUERY_ID: bool = false;
410}
411
412impl<ST> Query for Pragma<'_, ST> {
413 type SqlType = ST;
414}
415
416impl<ST> RunQueryDslSupport for Pragma<'_, ST> {}
417
418struct SetPragmaInt<'a> {
421 schema: Option<&'a str>,
422 name: &'static str,
423 value: i32,
424}
425
426impl QueryFragment<Sqlite> for SetPragmaInt<'_> {
427 fn walk_ast<'b>(&'b self, mut out: AstPass<'_, 'b, Sqlite>) -> QueryResult<()> {
428 out.push_sql("PRAGMA ");
429 out.push_identifier(self.schema.unwrap_or("main"))?;
430 out.push_sql(".");
431 out.push_sql(self.name);
432 out.push_sql(" = ");
433 out.push_sql(&self.value.to_string());
434 Ok(())
435 }
436}
437
438impl QueryId for SetPragmaInt<'_> {
439 type QueryId = ();
440
441 const HAS_STATIC_QUERY_ID: bool = false;
442}
443
444impl RunQueryDslSupport for SetPragmaInt<'_> {}
445
446struct Vacuum<'a> {
449 schema: Option<&'a str>,
450 into: Option<&'a str>,
451}
452
453impl QueryFragment<Sqlite> for Vacuum<'_> {
454 fn walk_ast<'b>(&'b self, mut out: AstPass<'_, 'b, Sqlite>) -> QueryResult<()> {
455 out.push_sql("VACUUM ");
456 out.push_identifier(self.schema.unwrap_or("main"))?;
457 if let Some(into) = self.into {
458 out.push_sql(" INTO ");
459 out.push_bind_param::<crate::sql_types::Text, _>(into)?;
460 }
461 Ok(())
462 }
463}
464
465impl QueryId for Vacuum<'_> {
467 type QueryId = ();
468
469 const HAS_STATIC_QUERY_ID: bool = false;
470}
471
472impl RunQueryDslSupport for Vacuum<'_> {}
473
474struct WalCheckpoint<'a> {
481 schema: Option<&'a str>,
482 mode: WalCheckpointMode,
483}
484
485impl QueryFragment<Sqlite> for WalCheckpoint<'_> {
486 fn walk_ast<'b>(&'b self, mut out: AstPass<'_, 'b, Sqlite>) -> QueryResult<()> {
487 out.push_sql("PRAGMA ");
488 if let Some(schema) = self.schema {
489 out.push_identifier(schema)?;
490 out.push_sql(".");
491 }
492 out.push_sql(match self.mode {
493 WalCheckpointMode::Passive => "wal_checkpoint(PASSIVE)",
494 WalCheckpointMode::Full => "wal_checkpoint(FULL)",
495 WalCheckpointMode::Restart => "wal_checkpoint(RESTART)",
496 WalCheckpointMode::Truncate => "wal_checkpoint(TRUNCATE)",
497 WalCheckpointMode::Noop => "wal_checkpoint(NOOP)",
498 });
499 Ok(())
500 }
501}
502
503impl QueryId for WalCheckpoint<'_> {
505 type QueryId = ();
506
507 const HAS_STATIC_QUERY_ID: bool = false;
508}
509
510impl Query for WalCheckpoint<'_> {
511 type SqlType = (
512 crate::sql_types::Integer,
513 crate::sql_types::BigInt,
514 crate::sql_types::BigInt,
515 );
516}
517
518impl RunQueryDslSupport for WalCheckpoint<'_> {}
519
520#[cfg(test)]
521mod tests {
522 use super::*;
523 use crate::connection::SimpleConnection;
524 #[cfg(not(any(all(target_family = "wasm", target_os = "unknown"), miri)))]
525 use crate::dsl::sql;
526 use crate::prelude::*;
527 #[cfg(not(any(all(target_family = "wasm", target_os = "unknown"), miri)))]
528 use crate::sql_types::Integer;
529
530 fn connection() -> SqliteConnection {
531 SqliteConnection::establish(":memory:").unwrap()
532 }
533
534 #[diesel_test_helper::test]
535 fn auto_vacuum_all_modes_roundtrip_on_fresh_database() {
536 for mode in [
537 AutoVacuumMode::None,
538 AutoVacuumMode::Full,
539 AutoVacuumMode::Incremental,
540 ] {
541 let conn = &mut connection();
542 conn.set_auto_vacuum(None, mode).unwrap();
543 assert_eq!(mode, conn.auto_vacuum(None).unwrap());
544 }
545 }
546
547 #[diesel_test_helper::test]
548 fn auto_vacuum_incremental_sticks_across_schema_creation() {
549 let conn = &mut connection();
550 conn.set_auto_vacuum(None, AutoVacuumMode::Incremental)
551 .unwrap();
552 assert_eq!(AutoVacuumMode::Incremental, conn.auto_vacuum(None).unwrap());
553
554 crate::sql_query("CREATE TABLE t (id INTEGER PRIMARY KEY)")
555 .execute(conn)
556 .unwrap();
557 assert_eq!(
558 AutoVacuumMode::Incremental,
559 conn.auto_vacuum(None).unwrap(),
560 "the mode survives once the schema exists"
561 );
562 }
563
564 #[diesel_test_helper::test]
565 fn auto_vacuum_change_from_none_requires_vacuum_on_populated_database() {
566 let conn = &mut connection();
567 crate::sql_query("CREATE TABLE t (id INTEGER PRIMARY KEY)")
568 .execute(conn)
569 .unwrap();
570 crate::sql_query("INSERT INTO t (id) VALUES (1)")
571 .execute(conn)
572 .unwrap();
573 assert_eq!(AutoVacuumMode::None, conn.auto_vacuum(None).unwrap());
574
575 conn.set_auto_vacuum(None, AutoVacuumMode::Full).unwrap();
578 assert_eq!(
579 AutoVacuumMode::None,
580 conn.auto_vacuum(None).unwrap(),
581 "the change does not take effect without a VACUUM"
582 );
583
584 crate::sql_query("VACUUM").execute(conn).unwrap();
585 assert_eq!(
586 AutoVacuumMode::Full,
587 conn.auto_vacuum(None).unwrap(),
588 "VACUUM rewrites the file and applies the mode"
589 );
590 }
591
592 #[diesel_test_helper::test]
593 fn auto_vacuum_targets_the_named_attached_database() {
594 let conn = &mut connection();
595 crate::sql_query("ATTACH DATABASE ':memory:' AS aux")
596 .execute(conn)
597 .unwrap();
598
599 conn.set_auto_vacuum(Some("aux"), AutoVacuumMode::Full)
600 .unwrap();
601 assert_eq!(AutoVacuumMode::Full, conn.auto_vacuum(Some("aux")).unwrap());
602 assert_eq!(
603 AutoVacuumMode::None,
604 conn.auto_vacuum(None).unwrap(),
605 "main keeps its own default"
606 );
607 }
608
609 #[diesel_test_helper::test]
610 fn auto_vacuum_schema_name_with_double_quote_is_handled() {
611 let conn = &mut connection();
612 let schema = r#"we"ird"#;
613 crate::sql_query(alloc::format!(
614 r#"ATTACH DATABASE ':memory:' AS "{}""#,
615 schema.replace('"', "\"\"")
616 ))
617 .execute(conn)
618 .unwrap();
619
620 conn.set_auto_vacuum(Some(schema), AutoVacuumMode::Incremental)
621 .unwrap();
622 assert_eq!(
623 AutoVacuumMode::Incremental,
624 conn.auto_vacuum(Some(schema)).unwrap()
625 );
626 }
627
628 table! {
629 pragma_probe (id) {
630 id -> Integer,
631 payload -> Text,
632 }
633 }
634
635 table! {
636 aux.aux_pragma_probe (id) {
637 id -> Integer,
638 payload -> Text,
639 }
640 }
641
642 const PROBE_TABLE: &str =
643 "CREATE TABLE pragma_probe (id INTEGER PRIMARY KEY, payload TEXT NOT NULL)";
644
645 const AUX_PROBE_TABLE: &str =
646 "CREATE TABLE aux.aux_pragma_probe (id INTEGER PRIMARY KEY, payload TEXT NOT NULL)";
647
648 fn overflowing_payload() -> String {
651 "x".repeat(64 * 1024)
652 }
653
654 fn insert_overflowing_row(conn: &mut SqliteConnection) {
655 crate::insert_into(pragma_probe::table)
656 .values((
657 pragma_probe::id.eq(1),
658 pragma_probe::payload.eq(overflowing_payload()),
659 ))
660 .execute(conn)
661 .unwrap();
662 }
663
664 #[diesel_test_helper::test]
665 fn page_count_is_positive_and_grows() {
666 let conn = &mut connection();
667 conn.batch_execute(PROBE_TABLE).unwrap();
668 let initial = conn.page_count(None).unwrap();
669 assert!(initial > 0, "an initialized database has at least one page");
670
671 insert_overflowing_row(conn);
672
673 assert!(
674 conn.page_count(None).unwrap() > initial,
675 "a row spanning overflow pages grows the page count"
676 );
677 }
678
679 #[diesel_test_helper::test]
680 fn freelist_count_tracks_reclaimable_space() {
681 let conn = &mut connection();
682 assert_eq!(
683 0,
684 conn.freelist_count(None).unwrap(),
685 "a fresh database has an empty freelist"
686 );
687
688 conn.batch_execute(PROBE_TABLE).unwrap();
689 insert_overflowing_row(conn);
690
691 crate::delete(pragma_probe::table).execute(conn).unwrap();
692 assert!(
693 conn.freelist_count(None).unwrap() > 0,
694 "deleting the row leaves reclaimable pages on the freelist"
695 );
696
697 crate::sql_query("VACUUM").execute(conn).unwrap();
699 assert_eq!(
700 0,
701 conn.freelist_count(None).unwrap(),
702 "VACUUM reclaims the freelist"
703 );
704 }
705
706 #[diesel_test_helper::test]
707 fn schema_targets_the_named_attached_database() {
708 let conn = &mut connection();
709 conn.batch_execute(PROBE_TABLE).unwrap();
710 conn.attach_database(":memory:", "aux").unwrap();
711 conn.batch_execute(AUX_PROBE_TABLE).unwrap();
712 crate::insert_into(aux_pragma_probe::table)
713 .values((
714 aux_pragma_probe::id.eq(1),
715 aux_pragma_probe::payload.eq(overflowing_payload()),
716 ))
717 .execute(conn)
718 .unwrap();
719
720 let main_pages = conn.page_count(None).unwrap();
721 let aux_pages = conn.page_count(Some("aux")).unwrap();
722 assert!(
723 aux_pages > main_pages,
724 "the attached database holds the data, main stays small"
725 );
726 assert_eq!(
727 main_pages,
728 conn.page_count(Some("main")).unwrap(),
729 "an explicit main matches the default"
730 );
731 }
732
733 #[diesel_test_helper::test]
734 fn schema_name_with_backtick_is_escaped() {
735 let conn = &mut connection();
738 let schema = "back`tick";
739 conn.attach_database(":memory:", schema).unwrap();
740 conn.batch_execute("CREATE TABLE `back``tick`.probe (id INTEGER PRIMARY KEY)")
741 .unwrap();
742
743 assert!(conn.page_count(Some(schema)).unwrap() > 0);
744 assert_eq!(0, conn.freelist_count(Some(schema)).unwrap());
745 }
746
747 #[cfg(not(miri))] #[diesel_test_helper::test]
749 fn unknown_schema_is_reported_as_an_error() {
750 let conn = &mut connection();
751
752 assert!(conn.page_count(Some("nope")).is_err());
753 assert!(conn.freelist_count(Some("nope")).is_err());
754 }
755
756 fn grow_then_empty_freelist(conn: &mut SqliteConnection) {
759 conn.batch_execute(PROBE_TABLE).unwrap();
760 let rows = (1..=200)
761 .map(|id| {
762 (
763 pragma_probe::id.eq(id),
764 pragma_probe::payload.eq("x".repeat(4000)),
765 )
766 })
767 .collect::<Vec<_>>();
768 crate::insert_into(pragma_probe::table)
769 .values(rows)
770 .execute(conn)
771 .unwrap();
772 crate::delete(pragma_probe::table).execute(conn).unwrap();
773 }
774
775 fn grow_then_empty_aux_freelist(conn: &mut SqliteConnection) {
777 conn.batch_execute(AUX_PROBE_TABLE).unwrap();
778 let rows = (1..=200)
779 .map(|id| {
780 (
781 aux_pragma_probe::id.eq(id),
782 aux_pragma_probe::payload.eq("x".repeat(4000)),
783 )
784 })
785 .collect::<Vec<_>>();
786 crate::insert_into(aux_pragma_probe::table)
787 .values(rows)
788 .execute(conn)
789 .unwrap();
790 crate::delete(aux_pragma_probe::table)
791 .execute(conn)
792 .unwrap();
793 }
794
795 #[diesel_test_helper::test]
796 fn incremental_vacuum_clears_the_whole_freelist() {
797 let conn = &mut connection();
798 conn.set_auto_vacuum(None, AutoVacuumMode::Incremental)
799 .unwrap();
800 grow_then_empty_freelist(conn);
801 assert!(
802 conn.freelist_count(None).unwrap() > 1,
803 "the deleted rows should leave many pages on the freelist"
804 );
805
806 conn.incremental_vacuum(None, None).unwrap();
807
808 assert_eq!(0, conn.freelist_count(None).unwrap());
811 }
812
813 #[diesel_test_helper::test]
814 fn incremental_vacuum_reclaims_at_most_the_requested_pages() {
815 let conn = &mut connection();
816 conn.set_auto_vacuum(None, AutoVacuumMode::Incremental)
817 .unwrap();
818 grow_then_empty_freelist(conn);
819 let before = conn.freelist_count(None).unwrap();
820 assert!(before > 10, "the bound has to be smaller than the freelist");
821
822 conn.incremental_vacuum(None, Some(10)).unwrap();
823
824 let after = conn.freelist_count(None).unwrap();
825 assert!(after >= before - 10, "at most ten pages may be reclaimed");
826 assert!(after < before, "some pages should have been reclaimed");
827 }
828
829 #[diesel_test_helper::test]
830 fn incremental_vacuum_is_a_no_op_outside_incremental_mode() {
831 let conn = &mut connection();
832 assert_eq!(AutoVacuumMode::None, conn.auto_vacuum(None).unwrap());
833 grow_then_empty_freelist(conn);
834 let before = conn.freelist_count(None).unwrap();
835 assert!(before > 0);
836
837 conn.incremental_vacuum(None, None).unwrap();
838
839 assert_eq!(
840 before,
841 conn.freelist_count(None).unwrap(),
842 "a database that is not in incremental mode keeps its freelist"
843 );
844 }
845
846 #[diesel_test_helper::test]
847 fn incremental_vacuum_targets_the_named_attached_database() {
848 let conn = &mut connection();
849 conn.attach_database(":memory:", "aux").unwrap();
850 conn.set_auto_vacuum(Some("aux"), AutoVacuumMode::Incremental)
851 .unwrap();
852
853 grow_then_empty_aux_freelist(conn);
854 assert!(conn.freelist_count(Some("aux")).unwrap() > 0);
855
856 conn.incremental_vacuum(Some("aux"), None).unwrap();
857
858 assert_eq!(0, conn.freelist_count(Some("aux")).unwrap());
859 }
860
861 #[diesel_test_helper::test]
862 fn incremental_vacuum_escapes_a_backtick_in_the_schema_name() {
863 let conn = &mut connection();
866 let schema = "back`tick";
867 conn.attach_database(":memory:", schema).unwrap();
868
869 conn.incremental_vacuum(Some(schema), None).unwrap();
870
871 assert_eq!(0, conn.freelist_count(Some(schema)).unwrap());
872 }
873
874 #[diesel_test_helper::test]
875 fn incremental_vacuum_of_zero_pages_clears_everything() {
876 let conn = &mut connection();
878 conn.set_auto_vacuum(None, AutoVacuumMode::Incremental)
879 .unwrap();
880 grow_then_empty_freelist(conn);
881 assert!(conn.freelist_count(None).unwrap() > 0);
882
883 conn.incremental_vacuum(None, Some(0)).unwrap();
884
885 assert_eq!(0, conn.freelist_count(None).unwrap());
886 }
887
888 #[cfg(not(miri))] #[diesel_test_helper::test]
890 fn incremental_vacuum_of_an_unknown_schema_is_an_error() {
891 let conn = &mut connection();
892
893 assert!(conn.incremental_vacuum(Some("nope"), None).is_err());
894 }
895
896 fn fill_then_delete(conn: &mut SqliteConnection) {
899 conn.batch_execute(PROBE_TABLE).unwrap();
900 crate::insert_into(pragma_probe::table)
901 .values((
902 pragma_probe::id.eq(1),
903 pragma_probe::payload.eq("x".repeat(256 * 1024)),
904 ))
905 .execute(conn)
906 .unwrap();
907 crate::delete(pragma_probe::table).execute(conn).unwrap();
908 crate::insert_into(pragma_probe::table)
909 .values((pragma_probe::id.eq(2), pragma_probe::payload.eq("kept")))
910 .execute(conn)
911 .unwrap();
912 }
913
914 fn fill_then_delete_aux(conn: &mut SqliteConnection) {
916 conn.batch_execute(AUX_PROBE_TABLE).unwrap();
917 crate::insert_into(aux_pragma_probe::table)
918 .values((
919 aux_pragma_probe::id.eq(1),
920 aux_pragma_probe::payload.eq("x".repeat(256 * 1024)),
921 ))
922 .execute(conn)
923 .unwrap();
924 crate::delete(aux_pragma_probe::table)
925 .execute(conn)
926 .unwrap();
927 }
928
929 #[diesel_test_helper::test]
930 fn vacuum_repacks_the_database() {
931 let conn = &mut connection();
932 fill_then_delete(conn);
933 let before = conn.page_count(None).unwrap();
934 assert!(before > 1);
935
936 conn.vacuum(None).unwrap();
937
938 assert!(
939 conn.page_count(None).unwrap() < before,
940 "rebuilding should release the pages the deleted row occupied"
941 );
942 assert_eq!(
943 1,
944 pragma_probe::table.count().get_result::<i64>(conn).unwrap(),
945 "the surviving row is still there"
946 );
947 }
948
949 #[diesel_test_helper::test]
950 fn vacuum_targets_the_named_attached_database() {
951 let conn = &mut connection();
952 conn.attach_database(":memory:", "aux").unwrap();
953 fill_then_delete_aux(conn);
954 let before = conn.page_count(Some("aux")).unwrap();
955 assert!(before > 1);
956
957 conn.vacuum(Some("aux")).unwrap();
958
959 assert!(conn.page_count(Some("aux")).unwrap() < before);
960 }
961
962 #[cfg(not(miri))]
963 #[diesel_test_helper::test]
964 fn vacuum_inside_a_transaction_is_an_error() {
965 use crate::connection::Connection;
966
967 let conn = &mut connection();
968 let result: QueryResult<()> = conn.transaction(|conn| conn.vacuum(None));
969
970 assert!(result.is_err());
971 }
972
973 #[cfg(not(any(all(target_family = "wasm", target_os = "unknown"), miri)))]
974 #[diesel_test_helper::test]
975 fn vacuum_into_writes_a_readable_copy_through_a_quoted_path() {
976 let dir = tempfile::tempdir().unwrap();
977 let destination = dir.path().join("o'brien backup.db");
980
981 let conn = &mut connection();
982 conn.batch_execute(PROBE_TABLE).unwrap();
983 crate::insert_into(pragma_probe::table)
984 .values((pragma_probe::id.eq(1), pragma_probe::payload.eq("copied")))
985 .execute(conn)
986 .unwrap();
987
988 conn.vacuum_into(None, destination.to_str().unwrap())
989 .unwrap();
990
991 let copy = &mut SqliteConnection::establish(destination.to_str().unwrap()).unwrap();
992 assert_eq!(
993 "copied",
994 pragma_probe::table
995 .select(pragma_probe::payload)
996 .get_result::<String>(copy)
997 .unwrap()
998 );
999 }
1000
1001 #[cfg(not(any(all(target_family = "wasm", target_os = "unknown"), miri)))]
1002 #[diesel_test_helper::test]
1003 fn vacuum_into_refuses_to_overwrite_an_existing_database() {
1004 let dir = tempfile::tempdir().unwrap();
1005 let destination = dir.path().join("occupied.db");
1006 {
1007 let occupied = &mut SqliteConnection::establish(destination.to_str().unwrap()).unwrap();
1008 occupied.batch_execute(PROBE_TABLE).unwrap();
1009 }
1010
1011 let conn = &mut connection();
1012 conn.batch_execute(PROBE_TABLE).unwrap();
1013
1014 assert!(
1015 conn.vacuum_into(None, destination.to_str().unwrap())
1016 .is_err()
1017 );
1018 }
1019
1020 #[cfg(not(any(all(target_family = "wasm", target_os = "unknown"), miri)))]
1021 #[diesel_test_helper::test]
1022 fn vacuum_into_copies_the_named_attached_database() {
1023 let dir = tempfile::tempdir().unwrap();
1024 let destination = dir.path().join("aux copy.db");
1025
1026 let conn = &mut connection();
1027 conn.attach_database(":memory:", "aux").unwrap();
1028 conn.batch_execute(AUX_PROBE_TABLE).unwrap();
1029 crate::insert_into(aux_pragma_probe::table)
1030 .values((
1031 aux_pragma_probe::id.eq(7),
1032 aux_pragma_probe::payload.eq("copied"),
1033 ))
1034 .execute(conn)
1035 .unwrap();
1036
1037 conn.vacuum_into(Some("aux"), destination.to_str().unwrap())
1038 .unwrap();
1039
1040 let copy = &mut SqliteConnection::establish(destination.to_str().unwrap()).unwrap();
1041 let id = sql::<Integer>("SELECT id FROM aux_pragma_probe")
1044 .get_result::<i32>(copy)
1045 .unwrap();
1046 assert_eq!(7, id);
1047 }
1048
1049 #[diesel_test_helper::test]
1050 fn vacuum_escapes_a_backtick_in_the_schema_name() {
1051 let conn = &mut connection();
1052 let schema = "back`tick";
1053 conn.attach_database(":memory:", schema).unwrap();
1054
1055 conn.vacuum(Some(schema)).unwrap();
1056 }
1057
1058 #[diesel_test_helper::test]
1059 fn vacuuming_two_schemas_rebuilds_each_of_them() {
1060 let conn = &mut connection();
1064 fill_then_delete(conn);
1065 conn.attach_database(":memory:", "aux").unwrap();
1066 fill_then_delete_aux(conn);
1067
1068 let main_before = conn.page_count(None).unwrap();
1069 let aux_before = conn.page_count(Some("aux")).unwrap();
1070
1071 conn.vacuum(None).unwrap();
1072 conn.vacuum(Some("aux")).unwrap();
1073
1074 assert!(
1075 conn.page_count(None).unwrap() < main_before,
1076 "main was rebuilt"
1077 );
1078 assert!(
1079 conn.page_count(Some("aux")).unwrap() < aux_before,
1080 "aux was rebuilt too, not main a second time"
1081 );
1082 }
1083
1084 #[cfg(not(any(all(target_family = "wasm", target_os = "unknown"), miri)))]
1086 fn wal_connection(path: &std::path::Path) -> SqliteConnection {
1087 let mut conn = SqliteConnection::establish(path.to_str().unwrap()).unwrap();
1088 conn.batch_execute("PRAGMA journal_mode = WAL").unwrap();
1089 conn
1090 }
1091
1092 #[cfg(not(any(all(target_family = "wasm", target_os = "unknown"), miri)))]
1093 #[diesel_test_helper::test]
1094 fn wal_checkpoint_truncate_reports_an_emptied_wal() {
1095 let dir = tempfile::tempdir().unwrap();
1096 let conn = &mut wal_connection(&dir.path().join("wal.db"));
1097 conn.batch_execute(PROBE_TABLE).unwrap();
1098 insert_overflowing_row(conn);
1099
1100 let outcome = conn
1101 .wal_checkpoint(None, WalCheckpointMode::Truncate)
1102 .unwrap();
1103
1104 assert!(!outcome.busy);
1105 assert_eq!(Some(0), outcome.log_frames, "the WAL file was truncated");
1106 assert_eq!(Some(0), outcome.checkpointed_frames);
1107 }
1108
1109 #[diesel_test_helper::test]
1110 fn wal_checkpoint_outside_wal_mode_reports_no_frames() {
1111 let conn = &mut connection();
1112
1113 let outcome = conn
1114 .wal_checkpoint(None, WalCheckpointMode::Truncate)
1115 .unwrap();
1116
1117 assert!(!outcome.busy);
1118 assert_eq!(None, outcome.log_frames);
1119 assert_eq!(None, outcome.checkpointed_frames);
1120 }
1121
1122 #[cfg(not(any(all(target_family = "wasm", target_os = "unknown"), miri)))]
1123 #[diesel_test_helper::test]
1124 fn wal_checkpoint_accepts_every_mode() {
1125 let dir = tempfile::tempdir().unwrap();
1126 let conn = &mut wal_connection(&dir.path().join("modes.db"));
1127 conn.batch_execute(PROBE_TABLE).unwrap();
1128
1129 for (row, mode) in [
1130 WalCheckpointMode::Passive,
1131 WalCheckpointMode::Full,
1132 WalCheckpointMode::Restart,
1133 WalCheckpointMode::Truncate,
1134 WalCheckpointMode::Noop,
1135 ]
1136 .into_iter()
1137 .enumerate()
1138 {
1139 crate::insert_into(pragma_probe::table)
1141 .values((
1142 pragma_probe::id.eq(i32::try_from(row).unwrap() + 1),
1143 pragma_probe::payload.eq("row"),
1144 ))
1145 .execute(conn)
1146 .unwrap();
1147
1148 let outcome = conn.wal_checkpoint(None, mode).unwrap();
1149 assert!(!outcome.busy, "{mode:?} had no competing readers");
1150 assert!(
1151 outcome.log_frames.is_some(),
1152 "{mode:?} ran on a WAL database"
1153 );
1154 assert!(outcome.checkpointed_frames.is_some());
1155 assert!(
1156 outcome.checkpointed_frames <= outcome.log_frames,
1157 "{mode:?}: checkpointed frames cannot exceed the log size"
1158 );
1159 }
1160 }
1161
1162 #[cfg(not(any(all(target_family = "wasm", target_os = "unknown"), miri)))]
1163 #[diesel_test_helper::test]
1164 fn wal_checkpoint_noop_reports_state_without_moving_frames() {
1165 let dir = tempfile::tempdir().unwrap();
1166 let conn = &mut wal_connection(&dir.path().join("noop.db"));
1167
1168 if conn.version() < crate::connection::DatabaseVersion::new(3, 51, 0) {
1170 return;
1171 }
1172
1173 conn.batch_execute(PROBE_TABLE).unwrap();
1174 conn.wal_checkpoint(None, WalCheckpointMode::Truncate)
1175 .unwrap();
1176 crate::insert_into(pragma_probe::table)
1177 .values((pragma_probe::id.eq(1), pragma_probe::payload.eq("noop")))
1178 .execute(conn)
1179 .unwrap();
1180
1181 let first = conn.wal_checkpoint(None, WalCheckpointMode::Noop).unwrap();
1182 let second = conn.wal_checkpoint(None, WalCheckpointMode::Noop).unwrap();
1183
1184 assert!(!first.busy, "NOOP never blocks");
1185 assert!(first.log_frames > Some(0), "the insert sits in the WAL");
1186 assert_eq!(Some(0), first.checkpointed_frames, "nothing was moved");
1187 assert_eq!(first, second, "a second NOOP reports the same state");
1188 }
1189
1190 #[cfg(not(any(all(target_family = "wasm", target_os = "unknown"), miri)))]
1191 #[diesel_test_helper::test]
1192 fn wal_checkpoint_reports_busy_while_a_reader_holds_an_old_snapshot() {
1193 use crate::connection::Connection;
1194
1195 let dir = tempfile::tempdir().unwrap();
1196 let path = dir.path().join("busy.db");
1197 let writer = &mut wal_connection(&path);
1198 writer.batch_execute(PROBE_TABLE).unwrap();
1199 insert_overflowing_row(writer);
1200
1201 let reader = &mut SqliteConnection::establish(path.to_str().unwrap()).unwrap();
1202 reader
1203 .transaction::<_, crate::result::Error, _>(|reader| {
1204 let _ = pragma_probe::table.count().get_result::<i64>(reader)?;
1206
1207 crate::insert_into(pragma_probe::table)
1210 .values((pragma_probe::id.eq(2), pragma_probe::payload.eq("late")))
1211 .execute(writer)?;
1212
1213 let outcome = writer.wal_checkpoint(None, WalCheckpointMode::Passive)?;
1216 assert!(!outcome.busy, "PASSIVE never reports busy");
1217 assert!(
1218 outcome.checkpointed_frames < outcome.log_frames,
1219 "the frames past the reader's snapshot stay in the WAL"
1220 );
1221
1222 for mode in [
1223 WalCheckpointMode::Full,
1224 WalCheckpointMode::Restart,
1225 WalCheckpointMode::Truncate,
1226 ] {
1227 let outcome = writer.wal_checkpoint(None, mode)?;
1228 assert!(outcome.busy, "the open reader blocks a {mode:?} checkpoint");
1229 }
1230 Ok(())
1231 })
1232 .unwrap();
1233
1234 let outcome = writer
1235 .wal_checkpoint(None, WalCheckpointMode::Truncate)
1236 .unwrap();
1237 assert!(
1238 !outcome.busy,
1239 "the checkpoint completes once the reader is done"
1240 );
1241 assert_eq!(Some(0), outcome.log_frames);
1242 }
1243
1244 #[cfg(not(any(all(target_family = "wasm", target_os = "unknown"), miri)))]
1245 #[diesel_test_helper::test]
1246 fn wal_checkpoint_targets_the_named_attached_database() {
1247 let dir = tempfile::tempdir().unwrap();
1248 let conn = &mut connection();
1249 conn.attach_database(dir.path().join("aux.db").to_str().unwrap(), "aux")
1250 .unwrap();
1251 conn.batch_execute("PRAGMA aux.journal_mode = WAL").unwrap();
1252 conn.batch_execute(AUX_PROBE_TABLE).unwrap();
1253 crate::insert_into(aux_pragma_probe::table)
1254 .values((
1255 aux_pragma_probe::id.eq(1),
1256 aux_pragma_probe::payload.eq("row"),
1257 ))
1258 .execute(conn)
1259 .unwrap();
1260
1261 let outcome = conn
1262 .wal_checkpoint(Some("aux"), WalCheckpointMode::Truncate)
1263 .unwrap();
1264 assert!(!outcome.busy);
1265 assert_eq!(
1266 Some(0),
1267 outcome.log_frames,
1268 "the attached database was checkpointed"
1269 );
1270
1271 let outcome = conn
1273 .wal_checkpoint(Some("main"), WalCheckpointMode::Truncate)
1274 .unwrap();
1275 assert_eq!(None, outcome.log_frames);
1276 }
1277
1278 #[cfg(not(any(all(target_family = "wasm", target_os = "unknown"), miri)))]
1279 #[diesel_test_helper::test]
1280 fn wal_checkpoint_unqualified_covers_every_attached_database() {
1281 let dir = tempfile::tempdir().unwrap();
1282 let conn = &mut wal_connection(&dir.path().join("main.db"));
1283 conn.batch_execute(PROBE_TABLE).unwrap();
1284 insert_overflowing_row(conn);
1285 conn.attach_database(dir.path().join("aux.db").to_str().unwrap(), "aux")
1286 .unwrap();
1287 conn.batch_execute("PRAGMA aux.journal_mode = WAL").unwrap();
1288 conn.batch_execute(AUX_PROBE_TABLE).unwrap();
1289 crate::insert_into(aux_pragma_probe::table)
1290 .values((
1291 aux_pragma_probe::id.eq(1),
1292 aux_pragma_probe::payload.eq("row"),
1293 ))
1294 .execute(conn)
1295 .unwrap();
1296
1297 conn.wal_checkpoint(None, WalCheckpointMode::Truncate)
1298 .unwrap();
1299
1300 let main_after = conn
1303 .wal_checkpoint(Some("main"), WalCheckpointMode::Passive)
1304 .unwrap();
1305 assert_eq!(Some(0), main_after.log_frames, "main was checkpointed");
1306 let aux_after = conn
1307 .wal_checkpoint(Some("aux"), WalCheckpointMode::Passive)
1308 .unwrap();
1309 assert_eq!(Some(0), aux_after.log_frames, "aux was checkpointed too");
1310 }
1311
1312 #[cfg(not(any(all(target_family = "wasm", target_os = "unknown"), miri)))]
1313 #[diesel_test_helper::test]
1314 fn wal_checkpoint_escapes_a_double_quote_in_the_schema_name() {
1315 let dir = tempfile::tempdir().unwrap();
1318 let conn = &mut connection();
1319 let schema = r#"we"ird"#;
1320 let quoted = schema.replace('"', "\"\"");
1321 conn.attach_database(dir.path().join("weird.db").to_str().unwrap(), schema)
1322 .unwrap();
1323 conn.batch_execute(&alloc::format!(r#"PRAGMA "{quoted}".journal_mode = WAL"#))
1324 .unwrap();
1325 conn.batch_execute(&alloc::format!(
1326 r#"CREATE TABLE "{quoted}".t (id INTEGER PRIMARY KEY)"#
1327 ))
1328 .unwrap();
1329
1330 let outcome = conn
1331 .wal_checkpoint(Some(schema), WalCheckpointMode::Truncate)
1332 .unwrap();
1333 assert_eq!(Some(0), outcome.log_frames, "the quoted schema was reached");
1334 }
1335
1336 #[cfg(not(miri))] #[diesel_test_helper::test]
1338 fn wal_checkpoint_of_an_unknown_schema_is_an_error() {
1339 let conn = &mut connection();
1340
1341 assert!(
1342 conn.wal_checkpoint(Some("nope"), WalCheckpointMode::Passive)
1343 .is_err()
1344 );
1345 }
1346
1347 #[cfg(not(any(all(target_family = "wasm", target_os = "unknown"), miri)))]
1348 #[diesel_test_helper::test]
1349 fn wal_checkpoint_inside_a_transaction_is_an_error() {
1350 use crate::connection::Connection;
1351
1352 let dir = tempfile::tempdir().unwrap();
1353 let conn = &mut wal_connection(&dir.path().join("txn.db"));
1354 conn.batch_execute(PROBE_TABLE).unwrap();
1355
1356 let result: QueryResult<WalCheckpointOutcome> = conn.transaction(|conn| {
1357 crate::insert_into(pragma_probe::table)
1358 .values((pragma_probe::id.eq(1), pragma_probe::payload.eq("txn")))
1359 .execute(conn)?;
1360 conn.wal_checkpoint(None, WalCheckpointMode::Truncate)
1361 });
1362
1363 assert!(result.is_err(), "SQLite reports SQLITE_LOCKED");
1364 }
1365}