197 lines
5.9 KiB
Rust
197 lines
5.9 KiB
Rust
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<Self, rusqlite::Error> {
|
|
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<i64, rusqlite::Error> {
|
|
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<Vec<Session>, 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<i64, rusqlite::Error> {
|
|
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<Vec<Measurement>, 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<i64, rusqlite::Error> {
|
|
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<i64, rusqlite::Error> {
|
|
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<Vec<DataPoint>, 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
|
|
);
|
|
";
|