syn2mas/mas_writer/
checks.rs

1// Copyright 2024 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("the MAS database is not empty: rows found in at least `{table}`")]
20    MasDatabaseNotEmpty { table: &'static str },
21
22    #[error("query against {table} failed — is this actually a MAS database?")]
23    MaybeNotMas {
24        #[source]
25        source: sqlx::Error,
26        table: &'static str,
27    },
28
29    #[error(transparent)]
30    Sqlx(#[from] sqlx::Error),
31
32    #[error("unable to check if syn2mas is already in progress")]
33    UnableToCheckInProgress(#[source] super::Error),
34}
35
36/// Check that a MAS database is ready for being migrated to.
37///
38/// Concretely, this checks that the database is empty.
39///
40/// If syn2mas is already in progress on this database, the checks are skipped.
41///
42/// # Errors
43///
44/// Errors are returned under the following circumstances:
45///
46/// - If any database access error occurs.
47/// - If any MAS tables involved in the migration are not empty.
48/// - If we can't check whether syn2mas is already in progress on this database
49///   or not.
50#[tracing::instrument(name = "syn2mas.mas_pre_migration_checks", skip_all)]
51pub async fn mas_pre_migration_checks(mas_connection: &mut LockedMasDatabase) -> Result<(), Error> {
52    if is_syn2mas_in_progress(mas_connection.as_mut())
53        .await
54        .map_err(Error::UnableToCheckInProgress)?
55    {
56        // syn2mas already in progress, so we already performed the checks
57        return Ok(());
58    }
59
60    // Check that the database looks like a MAS database and that it is also an
61    // empty database.
62
63    for &table in MAS_TABLES_AFFECTED_BY_MIGRATION {
64        let query = format!("SELECT 1 AS dummy FROM {table} LIMIT 1");
65        let span = tracing::info_span!("db.query", db.query.text = query);
66        let row_present = sqlx::query(&query)
67            .fetch_optional(mas_connection.as_mut())
68            .instrument(span)
69            .await
70            .into_maybe_not_mas(table)?
71            .is_some();
72
73        if row_present {
74            return Err(Error::MasDatabaseNotEmpty { table });
75        }
76    }
77
78    Ok(())
79}