db.rs (2979B)
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 'taler_rust_test_' || s.id, 65 EXISTS (SELECT FROM pg_database WHERE datname = 'taler_rust_test_' || s.id) 66 FROM generate_series(1, 1000) AS s(id) 67 WHERE pg_try_advisory_lock(s.id) 68 LIMIT 1 69 ", 70 ) 71 .fetch_optional(&mut conn) 72 .await 73 .unwrap(); 74 75 let Some((name, exists)) = row else { 76 panic!("Could not find a free database slot after 1000 attempts.") 77 }; 78 if !exists { 79 sqlx::raw_sql(&format!("CREATE DATABASE {name}")) 80 .execute(&mut conn) 81 .await 82 .unwrap(); 83 } 84 // We need this connection to stay open to keep the advisory lock in place 85 // Leaking it is OK in tests 86 std::mem::forget(conn); 87 let db_url = format!("postgresql:/{name}"); 88 info!("Running on db {db_url}"); 89 PgConnectOptions::from_str(&db_url).unwrap() 90 }