diff --git a/sprinklers_database/src/lib.rs b/sprinklers_database/src/lib.rs index 50abec9..671fc6f 100644 --- a/sprinklers_database/src/lib.rs +++ b/sprinklers_database/src/lib.rs @@ -8,10 +8,10 @@ pub use migrations::create_migrations; pub use rusqlite::Connection as DbConn; -use sprinklers_core::model::{Programs, Sections}; +use sprinklers_core::model::{Program, Programs, Sections}; use eyre::Result; -use rusqlite::NO_PARAMS; +use rusqlite::{params, NO_PARAMS}; pub fn setup_db() -> Result { // let conn = DbConn::open_in_memory()?; @@ -40,7 +40,7 @@ pub fn query_sections(conn: &DbConn) -> Result { pub fn query_programs(conn: &DbConn) -> Result { let mut statement = conn.prepare_cached( " - SELECT p.id, p.name, ps.sequence, p.enabled, p.schedule + SELECT p.id, p.name, p.enabled, p.schedule, ps.sequence FROM programs AS p INNER JOIN program_sequences AS ps ON ps.program_id = p.id;", @@ -53,3 +53,34 @@ pub fn query_programs(conn: &DbConn) -> Result { } Ok(programs) } + +pub fn update_program(conn: &mut DbConn, prog: &Program) -> Result<()> { + let trans = conn.transaction()?; + trans + .prepare_cached( + " + UPDATE programs + SET (name, enabled, schedule) = (?2, ?3, ?4) + WHERE id = ?1;", + )? + .execute(&program::as_sql(prog))?; + trans + .prepare_cached( + " + DELETE FROM program_sequence_items AS psi + WHERE psi.program_id = ?1;", + )? + .execute(params![prog.id])?; + let mut insert_prog_seq = trans.prepare_cached( + " + INSERT INTO program_sequence_items + (program_id, seq_num, section_id, duration) + VALUES (?1, ?2, ?3, ?4);", + )?; + for params in program::sequence_as_sql(prog) { + insert_prog_seq.execute(¶ms)?; + } + drop(insert_prog_seq); + trans.commit()?; + Ok(()) +} diff --git a/sprinklers_database/src/program.rs b/sprinklers_database/src/program.rs index a9dbf87..711d51a 100644 --- a/sprinklers_database/src/program.rs +++ b/sprinklers_database/src/program.rs @@ -1,18 +1,18 @@ use sprinklers_core::{ - model::{Program, ProgramSequence}, + model::{Program, ProgramId, ProgramItem, ProgramSequence, SectionId}, schedule::Schedule, }; use rusqlite::{ - types::{FromSql, FromSqlError, FromSqlResult, ValueRef}, - Result as SqlResult, Row as SqlRow, + types::{FromSql, FromSqlError, FromSqlResult, ToSqlOutput, Value, ValueRef}, + Error as SqlError, Result as SqlResult, Row as SqlRow, ToSql, }; -use serde::Deserialize; +use serde::{Deserialize, Serialize}; -struct SqlJson(T); +pub struct SqlJson(T); impl SqlJson { - fn into_inner(self) -> T { + pub fn into_inner(self) -> T { self.0 } } @@ -32,6 +32,17 @@ where } } +impl ToSql for SqlJson +where + T: Serialize, +{ + fn to_sql(&self) -> SqlResult> { + serde_json::to_string(&self.0) + .map(|serialized| ToSqlOutput::Owned(Value::Text(serialized))) + .map_err(|err| SqlError::ToSqlConversionFailure(Box::new(err))) + } +} + type SqlProgramSequence = SqlJson; type SqlSchedule = SqlJson; @@ -39,8 +50,83 @@ pub fn from_sql<'a>(row: &SqlRow<'a>) -> SqlResult { Ok(Program { id: row.get(0)?, name: row.get(1)?, - sequence: row.get::<_, SqlProgramSequence>(2)?.into_inner(), - enabled: row.get(3)?, - schedule: row.get::<_, SqlSchedule>(4)?.into_inner(), + enabled: row.get(2)?, + schedule: row.get::<_, SqlSchedule>(3)?.into_inner(), + sequence: row.get::<_, SqlProgramSequence>(4)?.into_inner(), }) } + +pub struct SqlProgram<'a> { + id: ProgramId, + name: &'a String, + enabled: bool, + schedule: SqlJson<&'a Schedule>, +} + +impl<'a> IntoIterator for &'a SqlProgram<'a> { + type Item = &'a dyn ToSql; + type IntoIter = std::vec::IntoIter; + + fn into_iter(self) -> Self::IntoIter { + vec![ + &self.id as &dyn ToSql, + self.name, + &self.enabled, + &self.schedule, + ] + .into_iter() + } +} + +pub fn as_sql(program: &Program) -> SqlProgram { + SqlProgram { + id: program.id, + name: &program.name, + enabled: program.enabled, + schedule: SqlJson(&program.schedule), + } +} + +pub struct SqlProgramItem { + program_id: ProgramId, + seq_num: isize, + section_id: SectionId, + duration: f64, +} + +impl<'a> IntoIterator for &'a SqlProgramItem { + type Item = &'a dyn ToSql; + type IntoIter = std::vec::IntoIter; + + fn into_iter(self) -> Self::IntoIter { + vec![ + &self.program_id as &dyn ToSql, + &self.seq_num, + &self.section_id, + &self.duration, + ] + .into_iter() + } +} + +pub fn item_as_sql( + program_item: &ProgramItem, + program_id: ProgramId, + seq_num: usize, +) -> SqlProgramItem { + SqlProgramItem { + program_id, + seq_num: (seq_num + 1) as isize, + section_id: program_item.section_id, + duration: program_item.duration.as_secs_f64(), + } +} + +pub fn sequence_as_sql<'a>(program: &'a Program) -> impl Iterator + 'a { + let program_id = program.id; + program + .sequence + .iter() + .enumerate() + .map(move |(seq_num, item)| item_as_sql(item, program_id, seq_num)) +}