feat: add SQLite migration and DB init with WAL mode
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,99 @@
|
||||
// backend/daemon/src/db.rs
|
||||
use sqlx::SqlitePool;
|
||||
use chrono::Utc;
|
||||
use uuid::Uuid;
|
||||
|
||||
pub async fn init_db(pool: &SqlitePool) -> Result<(), sqlx::Error> {
|
||||
sqlx::query("PRAGMA journal_mode=WAL").execute(pool).await?;
|
||||
sqlx::query("PRAGMA busy_timeout=5000").execute(pool).await?;
|
||||
|
||||
let migration = include_str!("../../../database/migrations/001_initial.sql");
|
||||
for statement in migration.split(';') {
|
||||
let stmt = statement.trim();
|
||||
if !stmt.is_empty() {
|
||||
sqlx::query(stmt).execute(pool).await?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn register_topic(pool: &SqlitePool, dev_eui: &str) -> Result<(), sqlx::Error> {
|
||||
let now = Utc::now().to_rfc3339();
|
||||
let id = Uuid::new_v4().to_string();
|
||||
sqlx::query(
|
||||
"INSERT OR IGNORE INTO topics (id, topic, approved, alarm_status, alarm_threshold_hours, created_at) \
|
||||
VALUES (?, ?, 0, 'unknown', 0.0, ?)"
|
||||
)
|
||||
.bind(&id)
|
||||
.bind(dev_eui)
|
||||
.bind(&now)
|
||||
.execute(pool)
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn update_last_seen(pool: &SqlitePool, dev_eui: &str, timestamp: &str) -> Result<(), sqlx::Error> {
|
||||
sqlx::query("UPDATE topics SET last_seen = ? WHERE topic = ?")
|
||||
.bind(timestamp)
|
||||
.bind(dev_eui)
|
||||
.execute(pool)
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use sqlx::SqlitePool;
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_init_db_creates_tables() {
|
||||
let pool = SqlitePool::connect("sqlite::memory:").await.unwrap();
|
||||
init_db(&pool).await.unwrap();
|
||||
|
||||
let topics: (i64,) = sqlx::query_as("SELECT COUNT(*) FROM topics")
|
||||
.fetch_one(&pool).await.unwrap();
|
||||
assert_eq!(topics.0, 0);
|
||||
|
||||
let sensors: (i64,) = sqlx::query_as("SELECT COUNT(*) FROM sensors")
|
||||
.fetch_one(&pool).await.unwrap();
|
||||
assert_eq!(sensors.0, 0);
|
||||
|
||||
let users: (i64,) = sqlx::query_as("SELECT COUNT(*) FROM users")
|
||||
.fetch_one(&pool).await.unwrap();
|
||||
assert_eq!(users.0, 0);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_init_db_is_idempotent() {
|
||||
let pool = SqlitePool::connect("sqlite::memory:").await.unwrap();
|
||||
init_db(&pool).await.unwrap();
|
||||
init_db(&pool).await.unwrap(); // should not error
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_register_topic() {
|
||||
let pool = SqlitePool::connect("sqlite::memory:").await.unwrap();
|
||||
init_db(&pool).await.unwrap();
|
||||
|
||||
register_topic(&pool, "abcdef1234567890").await.unwrap();
|
||||
|
||||
let count: (i64,) = sqlx::query_as("SELECT COUNT(*) FROM topics WHERE topic = ?")
|
||||
.bind("abcdef1234567890")
|
||||
.fetch_one(&pool).await.unwrap();
|
||||
assert_eq!(count.0, 1);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_register_topic_idempotent() {
|
||||
let pool = SqlitePool::connect("sqlite::memory:").await.unwrap();
|
||||
init_db(&pool).await.unwrap();
|
||||
|
||||
register_topic(&pool, "abcdef1234567890").await.unwrap();
|
||||
register_topic(&pool, "abcdef1234567890").await.unwrap();
|
||||
|
||||
let count: (i64,) = sqlx::query_as("SELECT COUNT(*) FROM topics")
|
||||
.fetch_one(&pool).await.unwrap();
|
||||
assert_eq!(count.0, 1);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user