// 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 . /// Select a record matching `record_id` from `dbconn` as YAML. pub fn select( dbconn: &rusqlite::Connection, table_name: &str, record_id: &str, exclude_columns: Option<&[C]>, ) -> Result where C: AsRef + PartialEq { select_by_column( dbconn, table_name, &crate::sqlite::table_primary_key_column(dbconn, table_name)?, record_id, exclude_columns, ) } /// Select a record matching `record_id` in the `primary_key_column` column from /// `dbconn` as YAML. pub fn select_by_column( dbconn: &rusqlite::Connection, table_name: &str, primary_key_column: &str, record_id: &str, exclude_columns: Option<&[C]>, ) -> Result where C: AsRef + PartialEq { use crate::yaml::Yaml; let mut stmt = dbconn.prepare( &format!( r#" SELECT * FROM "{}" WHERE "{}" = :pk; "#, table_name, primary_key_column, ), )?; let column_names: Vec = stmt .column_names() .into_iter() .map(String::from) .collect(); let rows = stmt.query_map( rusqlite::named_params! { ":pk": record_id, }, |row| { let mut data = yaml_rust::yaml::Hash::new(); for (i, column) in column_names.iter().enumerate() { // Don't include excluded columns in the resulting hash. If // `exclude_columns` is None, exclude the primary key column. match exclude_columns { Some(exclude_columns) => if exclude_columns.iter().any(|c| c == column) { continue } None => if column == primary_key_column { continue } } let column_name = column.to_owned(); let column_value: Yaml = row.get(i)?; data.insert( yaml_rust::Yaml::String(column_name), column_value.into_inner(), ); } Ok(data) }, )?; // Only one record is expected. let mut records = yaml_rust::yaml::Array::with_capacity(1); for row_result in rows { records.push(yaml_rust::Yaml::Hash(row_result?)); } match records.len() { 0 => Ok(yaml_rust::Yaml::Null), 1 => Ok(records.swap_remove(0)), _ => Ok(yaml_rust::Yaml::Array(records)), } } #[cfg(test)] mod tests { use super::*; #[test] fn select_extracts_a_database_record_as_yaml() { struct TestRecord { count: i16, description: String, } let record = TestRecord { count: 99, description: "This is a test. With multiple paragraphs.".to_owned(), }; let mut yaml_hash = yaml_rust::yaml::Hash::new(); yaml_hash.insert( yaml_rust::Yaml::String("count".to_owned()), yaml_rust::Yaml::Integer(record.count.into()), ); yaml_hash.insert( yaml_rust::Yaml::String("description".to_owned()), yaml_rust::Yaml::String(record.description.clone()), ); let expected = yaml_rust::Yaml::Hash(yaml_hash); let conn = rusqlite::Connection::open_in_memory().unwrap(); conn.execute( r#" CREATE TABLE "test" ( id INTEGER PRIMARY KEY, count INTEGER, description TEXT ); "#, [] ).unwrap(); { let mut stmt = conn.prepare(r#" INSERT INTO "test" (count, description) VALUES (?, ?); "#).unwrap(); stmt.insert( rusqlite::params![record.count, record.description], ).unwrap(); let got = select::(&conn, "test", "1", None).unwrap(); assert_eq!(expected, got); } conn.close().unwrap(); } }