diff options
author | Teddy Wing | 2022-04-02 20:35:28 +0200 |
---|---|---|
committer | Teddy Wing | 2022-04-02 20:35:28 +0200 |
commit | bc0afe2353f925124c67bf5785813432de44af57 (patch) | |
tree | 7c4d12048af856fe7884d6a678fc13916d0472d5 | |
parent | 02bc0d184fa60668bafe8de5ba844d132aa2ffb3 (diff) | |
download | yaqlite-bc0afe2353f925124c67bf5785813432de44af57.tar.bz2 |
Add a SQL update function to update a record from YAML
Add a new `db_update()` function to update an existing database record
from a YAML hash.
Had a little trouble building up the params for the prepared SQL
statement but finally managed to set up the types correctly and include
the record ID to update. Thanks to this Stack Overflow answer by
Shepmaster (https://stackoverflow.com/users/155423/shepmaster) for
pointing me in the right direction:
https://stackoverflow.com/questions/46624591/cannot-call-rusqlites-query-because-it-expects-the-type-rusqlitetypestos/46625932#46625932
-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, |