// Copyright (C) 2024-2026 Whiterun LLC // // This software is licensed under the GNU Affero General Public License (AGPL), version 3.0 or later. // A copy of the license can be found in the LICENSE file or at https://www.gnu.org/licenses/agpl-3.0.html use anyhow::Result; use log::info; use sqlx::sqlite::{SqliteConnectOptions, SqliteJournalMode, SqlitePoolOptions}; use sqlx::SqlitePool; use std::path::Path; use std::time::Duration; use super::DB; use crate::db::bcmr::prepare_tables as bcmr_prepare_tables; use crate::db::cauldron::config::check_db_version; use crate::db::cauldron::prepare_tables as cauldron_prepare_tables; use crate::db::crc20::prepare_tables as crc20_prepare_tables; use crate::db::oracle::prepare_tables as oracle_prepare_tables; /// Create read and write database pools for a given database path async fn create_db_pool(db_path: &str, read_max_size: u32) -> (bool, SqlitePool, SqlitePool) { let db_exists = Path::new(db_path).exists(); info!("Initializing connection to {db_path} (read_slots={read_max_size})"); let write_opts = SqliteConnectOptions::new() .filename(db_path) .journal_mode(SqliteJournalMode::Wal) .foreign_keys(true) .busy_timeout(Duration::from_secs(30)) .create_if_missing(true); let write_pool = SqlitePoolOptions::new() .max_connections(2) .connect_with(write_opts) .await .expect("Failed to initialize write database"); let read_opts = SqliteConnectOptions::new() .filename(db_path) .journal_mode(SqliteJournalMode::Wal) .foreign_keys(true) .busy_timeout(Duration::from_secs(30)) .create_if_missing(false) .read_only(true); let read_pool = SqlitePoolOptions::new() .max_connections(read_max_size) .connect_with(read_opts) .await .expect("Failed to initialize read database"); (db_exists, write_pool, read_pool) } /// Get the database directory path based on network pub fn get_db_directory(network: &str) -> &'static str { if network.to_lowercase() == "chipnet" { "chipnet" } else { "." } } /// Create network-specific directory if needed pub fn ensure_db_directory(db_dir: &str) -> Result<()> { if db_dir != "." { std::fs::create_dir_all(db_dir).map_err(|e| { anyhow::anyhow!("Failed to create database directory '{}': {}", db_dir, e) })?; } Ok(()) } /// Get the full database path for a filename based on the database directory fn db_path(db_dir: &str, filename: &str) -> String { if db_dir == "." { filename.to_string() } else { format!("{}/{}", db_dir, filename) } } /// Per-database read pool sizes pub struct ReadSlots { pub cauldron: u32, pub bcmr: u32, pub crc20: u32, pub oracle: u32, } /// Initialize all databases and return a DB struct pub async fn initialize_databases(network: &str, read_slots: ReadSlots) -> Result { let db_dir = get_db_directory(network); ensure_db_directory(db_dir)?; // Initialize cauldron database let (db_exists, cauldron_db_write, cauldron_db_read) = create_db_pool(&db_path(db_dir, "cauldron.db"), read_slots.cauldron).await; if !db_exists { cauldron_prepare_tables(&cauldron_db_write).await; } else { check_db_version(&cauldron_db_read).await?; } // Initialize BCMR database let (db_exists, bcmr_db_write, bcmr_db_read) = create_db_pool(&db_path(db_dir, "bcmr.db"), read_slots.bcmr).await; if !db_exists { bcmr_prepare_tables(&bcmr_db_write).await; } // Initialize CRC20 database let (db_exists, crc20_db_write, crc20_db_read) = create_db_pool(&db_path(db_dir, "crc20.db"), read_slots.crc20).await; if !db_exists { crc20_prepare_tables(&crc20_db_write).await; } // Initialize oracle database let (db_exists, oracle_db_write, oracle_db_read) = create_db_pool(&db_path(db_dir, "oracle.db"), read_slots.oracle).await; if !db_exists { oracle_prepare_tables(&oracle_db_write).await; } Ok(DB { cauldron_w: cauldron_db_write, cauldron_r: cauldron_db_read, bcmr_w: bcmr_db_write, bcmr_r: bcmr_db_read, crc20_w: crc20_db_write, crc20_r: crc20_db_read, oracle_w: oracle_db_write, oracle_r: oracle_db_read, }) }