OXYGENHUB
# 09ffa7b600a9 oxygenhub / crates / hub / tests / schema.rs
2.7 KiB · Rust · last changed Postgres, step 3: the hub builds and passes its tests on PostgreSQL 20 days ago source blame history raw
1
//! The two schemas agree (#211): every table and column SQLite's
2
//! migrations make, `migrations-pg` makes too, so a change written for one
3
//! engine and not the other fails here. Postgres builds only, as the
4
//! Postgres tests run (`OXYHUB_DATABASE_URL`, `OXYHUB_DATABASE_SCHEMA=*`).
5
#![cfg(feature = "postgres")]
6
7
use oxygenhub::config::Config;
8
use std::collections::{BTreeMap, BTreeSet};
9
10
type Tables = BTreeMap<String, BTreeSet<String>>;
11
12
/// The full-text index is each engine's own (FTS5's shadow tables, a
13
/// generated tsvector), and the migrations' ledger is sqlx's.
14
fn compared(table: &str) -> bool {
15
    !table.starts_with("search") && !table.starts_with("_sqlx")
16
}
17
18
async fn sqlite() -> Tables {
19
    use sqlx::Connection;
20
    let mut conn = sqlx::SqliteConnection::connect("sqlite::memory:").await.unwrap();
21
    sqlx::migrate!("./migrations").run(&mut conn).await.unwrap();
22
    let names: Vec<String> =
23
        sqlx::query_scalar("SELECT name FROM sqlite_master WHERE type = 'table' AND name NOT LIKE 'sqlite_%'").fetch_all(&mut conn).await.unwrap();
24
    let mut out = Tables::new();
25
    for t in names.into_iter().filter(|t| compared(t)) {
26
        let cols: Vec<String> =
27
            sqlx::query_scalar(&format!("SELECT name FROM pragma_table_info('{t}')")).fetch_all(&mut conn).await.unwrap();
28
        out.insert(t, cols.into_iter().collect());
29
    }
30
    out
31
}
32
33
async fn postgres() -> Tables {
34
    let dir = tempfile::tempdir().unwrap();
35
    let config = Config::load(dir.path()).unwrap();
36
    let pool = oxygenhub::db::open(&config).await.unwrap();
37
    let rows: Vec<(String, String)> = sqlx::query_as(
38
        "SELECT CAST(t.relname AS TEXT), CAST(a.attname AS TEXT) FROM pg_attribute a JOIN pg_class t ON t.oid = a.attrelid \
39
         WHERE t.relkind = 'r' AND t.relnamespace = CAST(current_schema() AS regnamespace) AND a.attnum > 0 AND NOT a.attisdropped",
40
    )
41
    .fetch_all(&pool)
42
    .await
43
    .unwrap();
44
    let mut out = Tables::new();
45
    for (t, c) in rows.into_iter().filter(|(t, _)| compared(t)) {
46
        out.entry(t).or_default().insert(c);
47
    }
48
    out
49
}
50
51
#[tokio::test]
52
async fn the_postgres_schema_has_every_table_and_column_sqlite_has() {
53
    let (lite, pg) = (sqlite().await, postgres().await);
54
    let only = |a: &Tables, b: &Tables| -> Vec<String> {
55
        a.iter()
56
            .flat_map(|(t, cols)| match b.get(t) {
57
                None => vec![format!("table {t}")],
58
                Some(other) => cols.difference(other).map(|c| format!("{t}.{c}")).collect(),
59
            })
60
            .collect()
61
    };
62
    assert_eq!(only(&lite, &pg), Vec::<String>::new(), "in SQLite's migrations but not migrations-pg");
63
    assert_eq!(only(&pg, &lite), Vec::<String>::new(), "in migrations-pg but not SQLite's migrations");
64
}