aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/lib.rs2
-rw-r--r--src/update.rs143
-rw-r--r--src/yaml.rs72
3 files changed, 217 insertions, 0 deletions
diff --git a/src/lib.rs b/src/lib.rs
index 10240fa..910aa2c 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -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,