use std::collections::BTreeSet;
use rusqlite::Result as SqlResult;
use greg::{
Point,
Frame
};
use crate::data::{
Activity,
Entry,
SetEntry
};
use super::{
Database,
Measure
};
pub trait Log: Database {
fn get_entry(&self, id: u64) -> SqlResult<Entry> {
self.db().query_row(
"SELECT id, activity_id, start, stop FROM entry WHERE id = ?1",
[id],
|row| row.try_into()
)
}
fn get_last_entries(&self, n: usize) -> SqlResult<Vec<Entry>> {
let mut stmt = self.db().prepare(
"SELECT id, activity_id, start, stop \
FROM entry \
ORDER BY id DESC \
LIMIT ?1"
)?;
let mut entries = Vec::new();
let mut rows = stmt.query([n])?;
while let Some(row) = rows.next()? {
entries.push(row.try_into()?);
}
Ok(entries)
}
fn start(&self, activity: &Activity, when: Point)
-> SqlResult<u64>
{
self.damage_at(activity.id, when)?;
let mut stmt = self.db().prepare(
"INSERT INTO entry (activity_id, start) \
VALUES (?1, ?2)"
)?;
stmt.insert((activity.id, when)).map(|i| i as u64)
}
fn stop(&self, entry: &Entry, when: Point) -> SqlResult<bool> {
self.damage_at(entry.activity_id, when)?;
let mut stmt = self.db().prepare(
"UPDATE entry SET stop = ?2 WHERE id = ?1 AND stop IS NULL"
)?;
stmt.execute((entry.id, when)).map(|i| i == 1)
}
fn update(&self, entry: &Entry) -> SqlResult<bool> {
self.damage_at(entry.activity_id, entry.start)?;
let mut stmt = self.db().prepare(
"UPDATE entry SET \
activity_id = ?2, \
start = ?3, \
stop = ?4 \
WHERE id = ?1"
)?;
let Entry {id, activity_id, start, stop} = entry;
stmt.execute((id, activity_id, start, stop)).map(|i| i == 1)
}
fn delete(&self, entry: &Entry) -> SqlResult<bool> {
self.damage_at(entry.activity_id, entry.start)?;
let mut stmt = self.db().prepare("DELETE from entry WHERE id = ?1")?;
stmt.execute([entry.id]).map(|i| i == 1)
}
fn insert(&self, entry: &Entry) -> SqlResult<bool> {
self.damage_at(entry.activity_id, entry.start)?;
self.db().execute(
"INSERT INTO entry VALUES (?1, ?2, ?3, ?4)",
(entry.id, entry.activity_id, entry.start, entry.stop)
).map(|i| i == 1)
}
fn create(
&self,
activity: &Activity,
start: Point,
stop: Point)
-> SqlResult<u64>
{
self.damage_at(activity.id, start)?;
let mut stmt = self.db().prepare(
"INSERT INTO entry (activity_id, start, stop) VALUES (?1, ?2, ?3)"
)?;
stmt.insert((activity.id, start, stop)).map(|i| i as u64)
}
fn recap(&self, frame: Frame) -> SqlResult<Vec<Entry>> {
let mut stmt = self.db().prepare(
"SELECT \
id, activity_id, start, stop \
FROM entry \
WHERE ( \
(?1 <= start AND start < ?2) \
OR (?1 < stop AND stop <= ?2) \
OR (start < ?1 AND ?2 < stop) \
OR (start <= ?1 AND stop IS NULL) \
OR (?1 == ?2 AND ?1 == start) \
) \
ORDER BY start"
)?;
let mut entries = Vec::new();
let mut rows = stmt.query((frame.start, frame.stop))?;
while let Some(row) = rows.next()? {
entries.push(row.try_into()?);
}
Ok(entries)
}
fn get_active_entries(&self) -> SqlResult<Vec<Entry>> {
let mut stmt = self.db().prepare(
"SELECT id, activity_id, start, stop \
FROM entry \
WHERE stop IS NULL \
ORDER BY start"
)?;
let mut entries = Vec::new();
let mut rows = stmt.query([])?;
while let Some(row) = rows.next()? {
entries.push(row.try_into()?);
}
Ok(entries)
}
fn load_set(&self) -> SqlResult<BTreeSet<SetEntry>> {
let mut set = BTreeSet::new();
let mut stmt = self.db().prepare(
"SELECT activity_id, start, stop FROM entry ORDER BY start"
)?;
let mut rows = stmt.query([])?;
while let Some(row) = rows.next()? {
let (activity_id, start, stop) = row.try_into()?;
set.insert(SetEntry {activity_id, start, stop});
}
Ok(set)
}
}
impl<Db: Database + ?Sized> Log for Db {}
#[test]
fn empty() -> SqlResult<()> {
let db = crate::Nunc::testing().unwrap();
assert!(db.get_active_entries()?.is_empty());
let jan_1970 = Point::from_epoch(0);
let may_2033 = Point::from_epoch(2000000000);
assert!(db.recap(Frame::new(jan_1970..jan_1970))?.is_empty());
assert!(db.recap(Frame::new(jan_1970..may_2033))?.is_empty());
assert!(db.recap(Frame::new(may_2033..may_2033))?.is_empty());
assert!(db.recap(Frame::new(may_2033..jan_1970))?.is_empty());
Ok(())
}
#[test]
fn start_stop() -> SqlResult<()> {
use super::Tree;
let db = crate::Nunc::testing().unwrap();
crate::tests::add_example_tree(&db)?;
db.get_active_entries()?;
let run = db.find_activity("Run")?.unwrap();
let ent_id = db.start(&run, Point::now())?;
let ent = db.get_entry(ent_id)?;
assert_eq!(ent, db.get_active_entries()?[0]);
db.stop(&ent, Point::now())?;
let ent_id = db.start(&run, Point::from_epoch(1000))?;
let ent = db.get_entry(ent_id)?;
db.stop(&ent, Point::from_epoch(999)).unwrap_err();
Ok(())
}
#[test]
fn create_insert() -> SqlResult<()> {
use super::Tree;
let db = crate::Nunc::testing().unwrap();
crate::tests::add_example_tree(&db)?;
db.get_active_entries()?;
let run = db.find_activity("Run")?.unwrap();
let now = Point::now();
let ent_id = db.create(&run, now, now)?;
let ent = db.get_entry(ent_id)?;
assert!(!db.stop(&ent, Point::now())?);
let ent = Entry {
id: 100,
activity_id: run.id,
start: now,
stop: Some(now)
};
assert!(db.insert(&ent)?);
assert_eq!(ent, db.get_entry(ent.id)?);
assert!(!db.stop(&ent, Point::now())?);
Ok(())
}
#[test]
fn update() -> SqlResult<()> {
use super::Tree;
let db = crate::Nunc::testing().unwrap();
crate::tests::add_example_tree(&db)?;
db.get_active_entries()?;
let run = db.find_activity("Run")?.unwrap();
let chess = db.find_activity("Chess")?.unwrap();
let now = Point::now();
let ent_id = db.create(&run, now, now)?;
let mut ent = db.get_entry(ent_id)?;
ent.stop = Some(Point::now() - greg::Span::MINUTE);
assert!(db.update(&ent).is_err());
ent.activity_id = chess.id;
assert!(db.update(&ent).is_err());
ent.stop = None;
assert!(db.update(&ent)?);
ent.stop = Some(Point::now() + greg::Span::MINUTE);
assert!(db.update(&ent)?);
ent.stop = None;
assert!(db.update(&ent)?);
ent.start = Point::now() - greg::Span::MINUTE;
ent.stop = Some(Point::now() + greg::Span::MINUTE);
assert!(db.update(&ent)?);
Ok(())
}