Janis Danisevskis | bf15d73 | 2020-12-08 10:35:26 -0800 | [diff] [blame] | 1 | // Copyright 2020, The Android Open Source Project |
| 2 | // |
| 3 | // Licensed under the Apache License, Version 2.0 (the "License"); |
| 4 | // you may not use this file except in compliance with the License. |
| 5 | // You may obtain a copy of the License at |
| 6 | // |
| 7 | // http://www.apache.org/licenses/LICENSE-2.0 |
| 8 | // |
| 9 | // Unless required by applicable law or agreed to in writing, software |
| 10 | // distributed under the License is distributed on an "AS IS" BASIS, |
| 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 12 | // See the License for the specific language governing permissions and |
| 13 | // limitations under the License. |
| 14 | |
| 15 | use crate::error::Error as KsError; |
| 16 | use anyhow::{Context, Result}; |
Janis Danisevskis | 4522c2b | 2020-11-27 18:04:58 -0800 | [diff] [blame] | 17 | use rusqlite::{types::FromSql, Row, Rows}; |
Janis Danisevskis | bf15d73 | 2020-12-08 10:35:26 -0800 | [diff] [blame] | 18 | |
| 19 | // Takes Rows as returned by a query call on prepared statement. |
| 20 | // Extracts exactly one row with the `row_extractor` and fails if more |
| 21 | // rows are available. |
| 22 | // If no row was found, `None` is passed to the `row_extractor`. |
| 23 | // This allows the row extractor to decide on an error condition or |
| 24 | // a different default behavior. |
| 25 | pub fn with_rows_extract_one<'a, T, F>(rows: &mut Rows<'a>, row_extractor: F) -> Result<T> |
| 26 | where |
| 27 | F: FnOnce(Option<&Row<'a>>) -> Result<T>, |
| 28 | { |
| 29 | let result = |
| 30 | row_extractor(rows.next().context("with_rows_extract_one: Failed to unpack row.")?); |
| 31 | |
| 32 | rows.next() |
| 33 | .context("In with_rows_extract_one: Failed to unpack unexpected row.")? |
| 34 | .map_or_else(|| Ok(()), |_| Err(KsError::sys())) |
| 35 | .context("In with_rows_extract_one: Unexpected row.")?; |
| 36 | |
| 37 | result |
| 38 | } |
| 39 | |
| 40 | pub fn with_rows_extract_all<'a, F>(rows: &mut Rows<'a>, mut row_extractor: F) -> Result<()> |
| 41 | where |
| 42 | F: FnMut(&Row<'a>) -> Result<()>, |
| 43 | { |
| 44 | loop { |
| 45 | match rows.next().context("In with_rows_extract_all: Failed to unpack row")? { |
| 46 | Some(row) => { |
| 47 | row_extractor(&row).context("In with_rows_extract_all.")?; |
| 48 | } |
| 49 | None => break Ok(()), |
| 50 | } |
| 51 | } |
| 52 | } |
Janis Danisevskis | 4522c2b | 2020-11-27 18:04:58 -0800 | [diff] [blame] | 53 | |
| 54 | /// This struct is defined to postpone converting rusqlite column value to the |
| 55 | /// appropriate key parameter value until we know the corresponding tag value. |
| 56 | /// Wraps the column index and a rusqlite row. |
| 57 | pub struct SqlField<'a>(usize, &'a Row<'a>); |
| 58 | |
| 59 | impl<'a> SqlField<'a> { |
| 60 | /// Creates a new SqlField with the given index and row. |
| 61 | pub fn new(index: usize, row: &'a Row<'a>) -> Self { |
| 62 | Self(index, row) |
| 63 | } |
| 64 | /// Returns the column value from the row, when we know the expected type. |
| 65 | pub fn get<T: FromSql>(&self) -> rusqlite::Result<T> { |
| 66 | self.1.get(self.0) |
| 67 | } |
| 68 | } |
| 69 | |
| 70 | /// This macro implements two types to aid in the implementation of a type safe metadata |
| 71 | /// store. The first is a collection of metadata and the second is the entry in that |
| 72 | /// collection. The caller has to provide the infrastructure to load and store the |
| 73 | /// the collection or individual entries in a SQLite database. The idea is that once |
| 74 | /// the infrastructure for a metadata collection is in place all it takes to add another |
| 75 | /// field is make a new entry in the list of variants (see details below). |
| 76 | /// |
| 77 | /// # Usage |
| 78 | /// ``` |
| 79 | /// impl_metadata!{ |
| 80 | /// /// This is the name of the collection. |
| 81 | /// #[derive(Debug, Default)] |
| 82 | /// pub struct CollectionName; |
| 83 | /// /// This is the name of the Entry type followed by a list of variants, accessor function |
| 84 | /// /// names, and types. |
| 85 | /// #[derive(Debug, Eq, PartialEq)] |
| 86 | /// pub enum EntryName { |
| 87 | /// /// An enum variant with an accessor function name. |
| 88 | /// VariantA(u32) with accessor get_variant_a, |
| 89 | /// /// A second variant. `MyType` must implement rusqlite::types::ToSql and FromSql. |
| 90 | /// VariantB(MyType) with accessor get_variant_b, |
| 91 | /// // --- ADD NEW META DATA FIELDS HERE --- |
| 92 | /// // For backwards compatibility add new entries only to |
| 93 | /// // end of this list and above this comment. |
| 94 | /// }; |
| 95 | /// } |
| 96 | /// ``` |
| 97 | /// |
| 98 | /// expands to: |
| 99 | /// |
| 100 | /// ``` |
| 101 | /// pub enum EntryName { |
| 102 | /// VariantA(u32), |
| 103 | /// VariantB(MyType), |
| 104 | /// } |
| 105 | /// |
| 106 | /// impl EntryName {} |
| 107 | /// /// Returns a numeric variant id that can be used for persistent storage. |
| 108 | /// fn db_tag(&self) -> i64 {...} |
| 109 | /// /// Helper function that constructs a new `EntryName` given a variant identifier |
| 110 | /// /// and a to-be-extracted `SqlFiled` |
| 111 | /// fn new_from_sql(db_tag: i64, data: &SqlField) -> Result<Self> {...} |
| 112 | /// } |
| 113 | /// |
| 114 | /// impl ToSql for EntryName {...} |
| 115 | /// |
| 116 | /// pub struct CollectionName { |
| 117 | /// data: std::collections::HashMap<i64, EntryName>, |
| 118 | /// } |
| 119 | /// |
| 120 | /// impl CollectionName { |
| 121 | /// /// Create a new collection of meta data. |
| 122 | /// pub fn new() -> Self {...} |
| 123 | /// /// Add a new entry to this collection. Replaces existing entries of the |
| 124 | /// /// same variant unconditionally. |
| 125 | /// pub fn add(&mut self, e: EntryName) {...} |
| 126 | /// /// Type safe accessor function for the defined fields. |
| 127 | /// pub fn get_variant_a() -> Option<u32> {...} |
| 128 | /// pub fn get_variant_b() -> Option<MyType> {...} |
| 129 | /// } |
| 130 | /// |
| 131 | /// let mut collection = CollectionName::new(); |
| 132 | /// collection.add(EntryName::VariantA(3)); |
| 133 | /// let three: u32 = collection.get_variant_a().unwrap() |
| 134 | /// ``` |
| 135 | /// |
| 136 | /// The caller of this macro must implement the actual database queries to load and store |
| 137 | /// either a whole collection of metadata or individual fields. For example by associating |
| 138 | /// with the given type: |
| 139 | /// ``` |
| 140 | /// impl CollectionName { |
| 141 | /// fn load(tx: &Transaction) -> Result<Self> {...} |
| 142 | /// } |
| 143 | /// ``` |
| 144 | #[macro_export] |
| 145 | macro_rules! impl_metadata { |
| 146 | // These two macros assign incrementing numeric ids to each field which are used as |
| 147 | // database tags. |
| 148 | (@gen_consts {} {$($n:ident $nid:tt,)*} {$($count:tt)*}) => { |
| 149 | $( |
| 150 | // This allows us to reuse the variant name for these constants. The constants |
| 151 | // are private so that this exception does not spoil the public interface. |
| 152 | #[allow(non_upper_case_globals)] |
| 153 | const $n: i64 = $nid; |
| 154 | )* |
| 155 | }; |
| 156 | (@gen_consts {$first:ident $(,$tail:ident)*} {$($out:tt)*} {$($count:tt)*}) => { |
| 157 | impl_metadata!(@gen_consts {$($tail),*} {$($out)* $first ($($count)*),} {$($count)* + 1}); |
| 158 | }; |
| 159 | ( |
| 160 | $(#[$nmeta:meta])* |
| 161 | $nvis:vis struct $name:ident; |
| 162 | $(#[$emeta:meta])* |
| 163 | $evis:vis enum $entry:ident { |
| 164 | $($(#[$imeta:meta])* $vname:ident($t:ty) with accessor $func:ident),* $(,)? |
| 165 | }; |
| 166 | ) => { |
| 167 | $(#[$emeta])* |
| 168 | $evis enum $entry { |
| 169 | $( |
| 170 | $(#[$imeta])* |
| 171 | $vname($t), |
| 172 | )* |
| 173 | } |
| 174 | |
| 175 | impl $entry { |
| 176 | fn db_tag(&self) -> i64 { |
| 177 | match self { |
| 178 | $(Self::$vname(_) => $name::$vname,)* |
| 179 | } |
| 180 | } |
| 181 | |
| 182 | fn new_from_sql(db_tag: i64, data: &SqlField) -> anyhow::Result<Self> { |
| 183 | match db_tag { |
| 184 | $( |
| 185 | $name::$vname => { |
| 186 | Ok($entry::$vname( |
| 187 | data.get() |
| 188 | .with_context(|| format!( |
| 189 | "In {}::new_from_sql: Unable to get {}.", |
| 190 | stringify!($entry), |
| 191 | stringify!($vname) |
| 192 | ))? |
| 193 | )) |
| 194 | }, |
| 195 | )* |
| 196 | _ => Err(anyhow!(format!( |
| 197 | "In {}::new_from_sql: unknown db tag {}.", |
| 198 | stringify!($entry), db_tag |
| 199 | ))), |
| 200 | } |
| 201 | } |
| 202 | } |
| 203 | |
| 204 | impl rusqlite::types::ToSql for $entry { |
| 205 | fn to_sql(&self) -> rusqlite::Result<rusqlite::types::ToSqlOutput> { |
| 206 | match self { |
| 207 | $($entry::$vname(v) => v.to_sql(),)* |
| 208 | } |
| 209 | } |
| 210 | } |
| 211 | |
| 212 | $(#[$nmeta])* |
| 213 | $nvis struct $name { |
| 214 | data: std::collections::HashMap<i64, $entry>, |
| 215 | } |
| 216 | |
| 217 | impl $name { |
| 218 | /// Create a new instance of $name |
| 219 | pub fn new() -> Self { |
| 220 | Self{data: std::collections::HashMap::new()} |
| 221 | } |
| 222 | |
| 223 | impl_metadata!{@gen_consts {$($vname),*} {} {0}} |
| 224 | |
| 225 | /// Add a new instance of $entry to this collection of metadata. |
| 226 | pub fn add(&mut self, entry: $entry) { |
| 227 | self.data.insert(entry.db_tag(), entry); |
| 228 | } |
| 229 | $( |
| 230 | /// If the variant $vname is set, returns the wrapped value or None otherwise. |
| 231 | pub fn $func(&self) -> Option<&$t> { |
| 232 | if let Some($entry::$vname(v)) = self.data.get(&Self::$vname) { |
| 233 | Some(v) |
| 234 | } else { |
| 235 | None |
| 236 | } |
| 237 | } |
| 238 | )* |
| 239 | } |
| 240 | }; |
| 241 | } |