iota_graphql_rpc/server/
compatibility_check.rs

1// Copyright (c) Mysten Labs, Inc.
2// Modifications Copyright (c) 2024 IOTA Stiftung
3// SPDX-License-Identifier: Apache-2.0
4
5use diesel::{
6    QueryDsl, QueryResult, RunQueryDsl,
7    query_builder::{AstPass, Query, QueryFragment, QueryId},
8    sql_types::Bool,
9};
10
11use crate::{
12    data::{Db, DbConnection, DieselBackend, DieselConn, QueryExecutor},
13    error::Error,
14};
15
16/// Generates a function: `check_all_tables` that runs a query against every
17/// table this GraphQL service is aware of, to test for schema compatibility.
18/// Each query is of the form:
19///
20///   SELECT TRUE FROM (...) q WHERE FALSE
21///
22/// where `...` is a query selecting all of the fields from the given table. The
23/// query is expected to return no results, but will complain if it relies on a
24/// column that doesn't exist.
25macro_rules! generate_compatibility_check {
26    ($($table:ident),*) => {
27        pub(crate) async fn check_all_tables(db: &Db) -> Result<(), Error> {
28            use futures::future::join_all;
29            use iota_indexer::schema::*;
30
31            let futures = vec![
32                $(
33                    db.execute(|conn| Ok::<_, diesel::result::Error>(
34                        conn.results::<_, bool>(move || Check {
35                            query: $table::table.select($table::all_columns)
36                        })
37                        .is_ok()
38                    ))
39                ),*
40            ];
41
42            let results = join_all(futures).await;
43            if results.into_iter().all(|res| res.unwrap_or(false)) {
44                Ok(())
45            } else {
46                Err(Error::Internal(
47                    "One or more tables are missing expected columns".into(),
48                ))
49            }
50        }
51    };
52}
53
54iota_indexer::for_all_tables!(generate_compatibility_check);
55
56#[derive(Debug, Clone, Copy, QueryId)]
57struct Check<Q> {
58    query: Q,
59}
60
61impl<Q: Query> Query for Check<Q> {
62    type SqlType = Bool;
63}
64
65impl<Q> RunQueryDsl<DieselConn> for Check<Q> {}
66
67impl<Q: QueryFragment<DieselBackend>> QueryFragment<DieselBackend> for Check<Q> {
68    fn walk_ast<'b>(&'b self, mut out: AstPass<'_, 'b, DieselBackend>) -> QueryResult<()> {
69        out.push_sql("SELECT TRUE FROM (");
70        self.query.walk_ast(out.reborrow())?;
71        out.push_sql(") q WHERE FALSE");
72        Ok(())
73    }
74}