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