blob: 90f56160c9f326eb253ccd328a9a7174021ba3be [file] [log] [blame]
Janis Danisevskisbf15d732020-12-08 10:35:26 -08001// 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
15use crate::error::Error as KsError;
16use anyhow::{Context, Result};
Janis Danisevskis4522c2b2020-11-27 18:04:58 -080017use rusqlite::{types::FromSql, Row, Rows};
Janis Danisevskisbf15d732020-12-08 10:35:26 -080018
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.
25pub fn with_rows_extract_one<'a, T, F>(rows: &mut Rows<'a>, row_extractor: F) -> Result<T>
26where
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
40pub fn with_rows_extract_all<'a, F>(rows: &mut Rows<'a>, mut row_extractor: F) -> Result<()>
41where
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 Danisevskis4522c2b2020-11-27 18:04:58 -080053
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.
57pub struct SqlField<'a>(usize, &'a Row<'a>);
58
59impl<'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]
145macro_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}