db.rs (3283B)
1 /* 2 This file is part of TALER 3 Copyright (C) 2025, 2026 Taler Systems SA 4 5 TALER is free software; you can redistribute it and/or modify it under the 6 terms of the GNU Affero General Public License as published by the Free Software 7 Foundation; either version 3, or (at your option) any later version. 8 9 TALER is distributed in the hope that it will be useful, but WITHOUT ANY 10 WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR 11 A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. 12 13 You should have received a copy of the GNU Affero General Public License along with 14 TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/> 15 */ 16 17 use std::{path::Path, str::FromStr as _}; 18 19 use sqlx::{ 20 Connection, PgConnection, PgPool, Postgres, pool::PoolConnection, postgres::PgConnectOptions, 21 }; 22 use taler_api::config::DbCfg; 23 use taler_common::{ 24 config::{Config, parser::ConfigSource}, 25 db::{dbinit, pool}, 26 }; 27 use tracing::info; 28 29 use crate::setup_tracing; 30 31 pub async fn db_test_setup(src: ConfigSource) -> (PoolConnection<Postgres>, PgPool) { 32 let cfg = Config::from_file(src, None::<&str>).unwrap(); 33 let name = format!("{}db-postgres", src.component_name); 34 let sect = cfg.section(&name); 35 let db_cfg = DbCfg::parse(sect).unwrap(); 36 db_test_setup_manual(db_cfg.sql_dir.as_ref(), src.component_name).await 37 } 38 39 pub async fn db_test_setup_manual( 40 sql_dir: &Path, 41 component_name: &str, 42 ) -> (PoolConnection<Postgres>, PgPool) { 43 setup_tracing(); 44 let cfg = test_db().await; 45 let pool = pool(cfg, &component_name.replace("-", "_")).await.unwrap(); 46 let mut conn = pool.acquire().await.unwrap(); 47 48 dbinit(&mut conn, sql_dir, component_name, true) 49 .await 50 .unwrap(); 51 52 (conn, pool) 53 } 54 55 async fn test_db() -> PgConnectOptions { 56 let mut conn = PgConnection::connect("postgres:///taler_rust_check") 57 .await 58 .unwrap(); 59 60 // Find a free slot via Advisory Locks 61 let row: Option<(String, bool)> = sqlx::query_as( 62 " 63 SELECT 64 v.db_name, 65 EXISTS ( 66 SELECT 1 67 FROM pg_catalog.pg_database 68 WHERE datname = v.db_name 69 ) AS already_exists 70 FROM generate_series(0, 1000) AS id 71 CROSS JOIN LATERAL ( 72 SELECT CASE 73 WHEN id = 0 THEN 'taler_rust_test' 74 ELSE 'taler_rust_test_' || id 75 END AS db_name 76 ) AS v 77 WHERE pg_try_advisory_lock(id) 78 LIMIT 1; 79 ", 80 ) 81 .fetch_optional(&mut conn) 82 .await 83 .unwrap(); 84 85 let Some((name, exists)) = row else { 86 panic!("Could not find a free database slot after 1000 attempts.") 87 }; 88 if !exists { 89 sqlx::raw_sql(&format!("CREATE DATABASE {name}")) 90 .execute(&mut conn) 91 .await 92 .unwrap(); 93 } 94 // We need this connection to stay open to keep the advisory lock in place 95 // Leaking it is OK in tests 96 std::mem::forget(conn); 97 let db_url = format!("postgresql:///{name}"); 98 info!(target: "test", "Running on db {db_url}"); 99 PgConnectOptions::from_str(&db_url).unwrap() 100 }