riftenlabs-indexer/src/db/cauldron/config.rs
Dagur Valberg Johannsson f421327a5d
Switch to tokio and sqlx
This makes the application fully async; freeing web server threads to
handle new connections when waiting on SQL queries.

Additionally sqlx will allow easier move to a different database if
needed in the future.

Includes some SQL optimizations as well (slow queries more easliy identifiable
with sqlx).
2026-02-18 10:16:51 +01:00

124 lines
3.5 KiB
Rust

// 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::{bail, Result};
use sqlx::{Sqlite, SqlitePool};
pub const DB_VERSION: u32 = 5;
const DB_VERSION_KEY: &str = "db_version";
/// Create the config table
pub async fn create_table(pool: &SqlitePool) {
sqlx::query(
"CREATE TABLE config (
key TEXT PRIMARY KEY,
value TEXT
)",
)
.execute(pool)
.await
.expect("failed to create config table");
}
/// Add or update config entry.
/// Accepts both `&SqlitePool` and `&mut SqliteConnection` (including transactions).
pub async fn config_set<'e>(
executor: impl sqlx::Executor<'e, Database = Sqlite>,
key: &str,
value: &str,
) {
sqlx::query("INSERT OR REPLACE INTO config (key, value) VALUES (?, ?)")
.bind(key)
.bind(value)
.execute(executor)
.await
.unwrap();
}
/// Get config entry
pub async fn config_get(pool: &SqlitePool, key: &str) -> Result<Option<String>> {
let row: Option<(String,)> = sqlx::query_as("SELECT value FROM config WHERE key = ?")
.bind(key)
.fetch_optional(pool)
.await?;
Ok(row.map(|r| r.0))
}
/// Check database version and panic if it doesn't match expected version
pub async fn check_db_version(pool: &SqlitePool) -> Result<()> {
let version_str = config_get(pool, DB_VERSION_KEY).await?;
match version_str {
Some(version_str) => {
let version: u32 = version_str.parse().unwrap_or(0);
if version != DB_VERSION {
bail!(
"Database version mismatch. Expected {}, got {}",
DB_VERSION,
version
);
}
}
None => {
bail!(
"Database version not found. Expected version {}",
DB_VERSION
);
}
}
Ok(())
}
/// Set database version
pub async fn set_db_version(pool: &SqlitePool) {
config_set(pool, DB_VERSION_KEY, &DB_VERSION.to_string()).await;
}
#[cfg(test)]
mod tests {
use super::*;
#[tokio::test]
async fn test_db_version_functionality() {
let pool = SqlitePool::connect("sqlite::memory:").await.unwrap();
create_table(&pool).await;
set_db_version(&pool).await;
let version_str = config_get(&pool, DB_VERSION_KEY).await.unwrap().unwrap();
let version: u32 = version_str.parse().unwrap();
assert_eq!(version, DB_VERSION);
check_db_version(&pool).await.unwrap();
// Test with wrong version
sqlx::query("INSERT OR REPLACE INTO config (key, value) VALUES (?, ?)")
.bind(DB_VERSION_KEY)
.bind("1")
.execute(&pool)
.await
.unwrap();
let result = check_db_version(&pool).await;
assert!(result.is_err());
assert!(result
.unwrap_err()
.to_string()
.contains("Database version mismatch"));
// Test with missing version
sqlx::query("DELETE FROM config WHERE key = ?")
.bind(DB_VERSION_KEY)
.execute(&pool)
.await
.unwrap();
let result = check_db_version(&pool).await;
assert!(result.is_err());
assert!(result
.unwrap_err()
.to_string()
.contains("Database version not found"));
}
}