syn2mas/mas_writer/
checks.rs

1// Copyright 2024, 2025 New Vector Ltd.
2//
3// SPDX-License-Identifier: AGPL-3.0-only
4// Please see LICENSE in the repository root for full details.
5
6//! # MAS Database Checks
7//!
8//! This module provides safety checks to run against a MAS database before
9//! running the Synapse-to-MAS migration.
10
11use thiserror::Error;
12use thiserror_ext::ContextInto;
13use tracing::Instrument as _;
14
15use super::{MAS_TABLES_AFFECTED_BY_MIGRATION, is_syn2mas_in_progress, locking::LockedMasDatabase};
16
17#[derive(Debug, Error, ContextInto)]
18pub enum Error {
19    #[error(
20        "The MAS database is not empty: rows found in at least `{table}`. Please drop and recreate the database, then try again."
21    )]
22    MasDatabaseNotEmpty { table: &'static str },
23
24    #[error("Query against {table} failed — is this actually a MAS database?")]
25    MaybeNotMas {
26        #[source]
27        source: sqlx::Error,
28        table: &'static str,
29    },
30
31    #[error(transparent)]
32    Sqlx(#[from] sqlx::Error),
33
34    #[error("Unable to check if syn2mas is already in progress")]
35    UnableToCheckInProgress(#[source] super::Error),
36}
37
38/// Check that a MAS database is ready for being migrated to.
39///
40/// Concretely, this checks that the database is empty.
41///
42/// If syn2mas is already in progress on this database, the checks are skipped.
43///
44/// # Errors
45///
46/// Errors are returned under the following circumstances:
47///
48/// - If any database access error occurs.
49/// - If any MAS tables involved in the migration are not empty.
50/// - If we can't check whether syn2mas is already in progress on this database
51///   or not.
52#[tracing::instrument(name = "syn2mas.mas_pre_migration_checks", skip_all)]
53pub async fn mas_pre_migration_checks(mas_connection: &mut LockedMasDatabase) -> Result<(), Error> {
54    if is_syn2mas_in_progress(mas_connection.as_mut())
55        .await
56        .map_err(Error::UnableToCheckInProgress)?
57    {
58        // syn2mas already in progress, so we already performed the checks
59        return Ok(());
60    }
61
62    // Check that the database looks like a MAS database and that it is also an
63    // empty database.
64
65    for &table in MAS_TABLES_AFFECTED_BY_MIGRATION {
66        let query = format!("SELECT 1 AS dummy FROM {table} LIMIT 1");
67        let span = tracing::info_span!("db.query", db.query.text = query);
68        let row_present = sqlx::query(&query)
69            .fetch_optional(mas_connection.as_mut())
70            .instrument(span)
71            .await
72            .into_maybe_not_mas(table)?
73            .is_some();
74
75        if row_present {
76            return Err(Error::MasDatabaseNotEmpty { table });
77        }
78    }
79
80    Ok(())
81}