diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/lib.rs | 2 | ||||
-rw-r--r-- | src/update.rs | 143 | ||||
-rw-r--r-- | src/yaml.rs | 72 |
3 files changed, 217 insertions, 0 deletions
@@ -18,11 +18,13 @@ pub mod insert; pub mod select; pub mod sqlite; +pub mod update; pub mod yaml; pub use insert::*; pub use select::*; +pub use update::*; /// Yaqlite errors. diff --git a/src/update.rs b/src/update.rs new file mode 100644 index 0000000..ebed217 --- /dev/null +++ b/src/update.rs @@ -0,0 +1,143 @@ +// Copyright (c) 2022 Teddy Wing +// +// This file is part of Yaqlite. +// +// Yaqlite is free software: you can redistribute it and/or modify it +// under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Yaqlite is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Yaqlite. If not, see <https://www.gnu.org/licenses/>. + +/// Update a YAML record in the given database. +pub fn update( + dbconn: &mut rusqlite::Connection, + table_name: &str, + record_id: &str, + data: &mut yaml_rust::Yaml, +) -> Result<(), crate::Error> { + let table_columns = crate::sqlite::get_column_names(&dbconn, table_name)?; + + let tx = dbconn.transaction()?; + + crate::yaml::db_update( + data, + &tx, + &table_name, + &table_columns, + // TODO: dynamic or user-supplied + "id", + record_id, + )?; + + tx.commit()?; + + Ok(()) +} + + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn updates_database_record_from_yaml() { + #[derive(Debug, PartialEq)] + struct TestRecord { + id: i8, + count: i16, + weight: f32, + description: String, + } + + let mut conn = rusqlite::Connection::open_in_memory().unwrap(); + + conn.execute( + r#" + CREATE TABLE "test" ( + id INTEGER PRIMARY KEY, + count INTEGER, + weight REAL, + description TEXT + ); + "#, + [], + ).unwrap(); + + { + let mut stmt = conn.prepare(r#" + INSERT INTO "test" + (count, weight, description) + VALUES + (?, ?, ?); + "#).unwrap(); + + stmt.insert( + rusqlite::params![ + 55_i16, + 0.8_f32, + "Ounces or grams?", + ], + ).unwrap(); + } + + let expected = TestRecord { + id: 1, + count: 28, + weight: 1.2, + description: r#"This is a multiline + +description."#.to_owned(), + }; + + let mut yaml_data = yaml_rust::YamlLoader::load_from_str( + &format!( +r#"count: {} +weight: {} +description: |- + This is a multiline + + description. +"#, + expected.count, + expected.weight, + ), + ).unwrap(); + let mut yaml_record = yaml_data.get_mut(0).unwrap(); + + update(&mut conn, "test", "1", &mut yaml_record).unwrap(); + + { + let mut stmt = conn.prepare(r#" + SELECT + "id", "count", "weight", "description" + FROM "test" + WHERE "id" = 1; + "#).unwrap(); + + let got = stmt.query_row( + [], + |row| { + Ok( + TestRecord { + id: row.get(0).unwrap(), + count: row.get(1).unwrap(), + weight: row.get(2).unwrap(), + description: row.get(3).unwrap(), + } + ) + } + ).unwrap(); + + assert_eq!(expected, got); + } + + conn.close().unwrap(); + } +} diff --git a/src/yaml.rs b/src/yaml.rs index acf1a9e..d1108f1 100644 --- a/src/yaml.rs +++ b/src/yaml.rs @@ -74,6 +74,78 @@ pub fn db_insert( ) } +/// TODO +pub fn db_update( + doc: &mut yaml::Yaml, + tx: &rusqlite::Transaction, + table_name: &str, + table_columns: &HashSet<String>, + primary_key_column: &str, + record_id: &str, +) -> Result<(), crate::Error> { + with_hash( + doc, + &mut |hash| { + use std::borrow::Cow; + + hash_filter_table_columns(hash, &table_columns); + + let mut stmt = tx.prepare( + &format!( + r#" + UPDATE "{}" + SET + {} + WHERE {} = ?; + "#, + table_name, + + // List of: + // "column_name" = ?, + // "column_name" = ? + hash.keys() + .map(|k| k.as_str()) + .filter(|k| k.is_some()) + + // Always `Some`. + .map(|k| format!(r#""{}" = ?"#, k.unwrap())) + .collect::<Vec<String>>() + .join(", "), + + primary_key_column, + ), + )?; + + // TODO: convert to &[&dyn ToSql] ? + // let values = hash.values().map(|v| Yaml(Cow::Borrowed(v))); + // values.push(primary_key); + // stmt.execute(rusqlite::params_from_iter(values))?; + + // let values: Vec<&dyn rusqlite::ToSql>; + // + // for v in hash.values() { + // values.push(&Yaml(Cow::Borrowed(v))); + // } + // + // stmt.execute(values)?; + + // let values: dyn Iterator<Item = &dyn rusqlite::ToSql> = hash.values(); + // values = values.map(|v| Yaml(Cow::Borrowed(v))); + // values.chain(&[&primary_key]); + // stmt.execute(rusqlite::params_from_iter(values))?; + + let mut values: Vec<_> = hash.values() + .map(|v| Yaml(Cow::Borrowed(v))) + .map(|v| Box::new(v) as Box<dyn rusqlite::ToSql>) + .collect(); + values.push(Box::new(record_id)); + stmt.execute(rusqlite::params_from_iter(values))?; + + Ok(()) + } + ) +} + /// Parse a YAML document and run a function for all hashes in the document. fn with_hash<F>( doc: &mut yaml::Yaml, |