use rusqlite::{Connection, params}; #[derive(Debug, Clone)] #[allow(dead_code)] pub struct Session { pub id: i64, pub name: String, pub notes: String, pub created_at: String, } #[derive(Debug, Clone)] #[allow(dead_code)] pub struct Measurement { pub id: i64, pub session_id: i64, pub mtype: String, pub params_json: String, pub created_at: String, } #[derive(Debug, Clone)] #[allow(dead_code)] pub struct DataPoint { pub id: i64, pub measurement_id: i64, pub idx: i32, pub data_json: String, } pub struct Storage { conn: Connection, } impl Storage { pub fn open() -> Result { let dir = dirs(); std::fs::create_dir_all(&dir).ok(); let path = dir.join("measurements.db"); let conn = Connection::open(path)?; conn.execute_batch("PRAGMA journal_mode=WAL; PRAGMA foreign_keys=ON;")?; conn.execute_batch(SCHEMA)?; Ok(Self { conn }) } pub fn create_session(&self, name: &str, notes: &str) -> Result { self.conn.execute( "INSERT INTO sessions (name, notes) VALUES (?1, ?2)", params![name, notes], )?; Ok(self.conn.last_insert_rowid()) } pub fn list_sessions(&self) -> Result, rusqlite::Error> { let mut stmt = self.conn.prepare( "SELECT id, name, notes, created_at FROM sessions ORDER BY created_at DESC", )?; let rows = stmt.query_map([], |row| { Ok(Session { id: row.get(0)?, name: row.get(1)?, notes: row.get(2)?, created_at: row.get(3)?, }) })?; rows.collect() } pub fn delete_session(&self, id: i64) -> Result<(), rusqlite::Error> { self.conn.execute("DELETE FROM sessions WHERE id = ?1", params![id])?; Ok(()) } pub fn create_measurement( &self, session_id: i64, mtype: &str, params_json: &str, ) -> Result { self.conn.execute( "INSERT INTO measurements (session_id, type, params_json) VALUES (?1, ?2, ?3)", params![session_id, mtype, params_json], )?; Ok(self.conn.last_insert_rowid()) } pub fn add_data_point( &self, measurement_id: i64, idx: i32, data_json: &str, ) -> Result<(), rusqlite::Error> { self.conn.execute( "INSERT INTO data_points (measurement_id, idx, data_json) VALUES (?1, ?2, ?3)", params![measurement_id, idx, data_json], )?; Ok(()) } pub fn add_data_points_batch( &self, measurement_id: i64, points: &[(i32, String)], ) -> Result<(), rusqlite::Error> { let tx = self.conn.unchecked_transaction()?; { let mut stmt = tx.prepare( "INSERT INTO data_points (measurement_id, idx, data_json) VALUES (?1, ?2, ?3)", )?; for (idx, json) in points { stmt.execute(params![measurement_id, idx, json])?; } } tx.commit() } pub fn get_measurements(&self, session_id: i64) -> Result, rusqlite::Error> { let mut stmt = self.conn.prepare( "SELECT id, session_id, type, params_json, created_at \ FROM measurements WHERE session_id = ?1 ORDER BY created_at DESC", )?; let rows = stmt.query_map(params![session_id], |row| { Ok(Measurement { id: row.get(0)?, session_id: row.get(1)?, mtype: row.get(2)?, params_json: row.get(3)?, created_at: row.get(4)?, }) })?; rows.collect() } pub fn measurement_count(&self, session_id: i64) -> Result { self.conn.query_row( "SELECT COUNT(*) FROM measurements WHERE session_id = ?1", params![session_id], |row| row.get(0), ) } pub fn data_point_count(&self, measurement_id: i64) -> Result { self.conn.query_row( "SELECT COUNT(*) FROM data_points WHERE measurement_id = ?1", params![measurement_id], |row| row.get(0), ) } pub fn delete_measurement(&self, id: i64) -> Result<(), rusqlite::Error> { self.conn.execute("DELETE FROM measurements WHERE id = ?1", params![id])?; Ok(()) } pub fn get_data_points(&self, measurement_id: i64) -> Result, rusqlite::Error> { let mut stmt = self.conn.prepare( "SELECT id, measurement_id, idx, data_json \ FROM data_points WHERE measurement_id = ?1 ORDER BY idx", )?; let rows = stmt.query_map(params![measurement_id], |row| { Ok(DataPoint { id: row.get(0)?, measurement_id: row.get(1)?, idx: row.get(2)?, data_json: row.get(3)?, }) })?; rows.collect() } } fn dirs() -> std::path::PathBuf { dirs_home().join(".eis4") } fn dirs_home() -> std::path::PathBuf { std::env::var("HOME") .map(std::path::PathBuf::from) .unwrap_or_else(|_| std::path::PathBuf::from(".")) } const SCHEMA: &str = " CREATE TABLE IF NOT EXISTS sessions ( id INTEGER PRIMARY KEY, name TEXT NOT NULL, notes TEXT NOT NULL DEFAULT '', created_at TEXT NOT NULL DEFAULT (datetime('now')) ); CREATE TABLE IF NOT EXISTS measurements ( id INTEGER PRIMARY KEY, session_id INTEGER NOT NULL REFERENCES sessions(id) ON DELETE CASCADE, type TEXT NOT NULL, params_json TEXT NOT NULL DEFAULT '{}', created_at TEXT NOT NULL DEFAULT (datetime('now')) ); CREATE TABLE IF NOT EXISTS data_points ( id INTEGER PRIMARY KEY, measurement_id INTEGER NOT NULL REFERENCES measurements(id) ON DELETE CASCADE, idx INTEGER NOT NULL, data_json TEXT NOT NULL ); ";