1 // Copyright 2021, 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 anyhow::{anyhow, Context, Result};
16 use rusqlite::{params, OptionalExtension, Transaction, NO_PARAMS};
17
create_or_get_version(tx: &Transaction, current_version: u32) -> Result<u32>18 pub fn create_or_get_version(tx: &Transaction, current_version: u32) -> Result<u32> {
19 tx.execute(
20 "CREATE TABLE IF NOT EXISTS persistent.version (
21 id INTEGER PRIMARY KEY,
22 version INTEGER);",
23 NO_PARAMS,
24 )
25 .context("In create_or_get_version: Failed to create version table.")?;
26
27 let version = tx
28 .query_row("SELECT version FROM persistent.version WHERE id = 0;", NO_PARAMS, |row| {
29 row.get(0)
30 })
31 .optional()
32 .context("In create_or_get_version: Failed to read version.")?;
33
34 let version = if let Some(version) = version {
35 version
36 } else {
37 // If no version table existed it could mean one of two things:
38 // 1) This database is completely new. In this case the version has to be set
39 // to the current version and the current version which also needs to be
40 // returned.
41 // 2) The database predates db versioning. In this case the version needs to be
42 // set to 0, and 0 needs to be returned.
43 let version = if tx
44 .query_row(
45 "SELECT name FROM persistent.sqlite_master
46 WHERE type = 'table' AND name = 'keyentry';",
47 NO_PARAMS,
48 |_| Ok(()),
49 )
50 .optional()
51 .context("In create_or_get_version: Failed to check for keyentry table.")?
52 .is_none()
53 {
54 current_version
55 } else {
56 0
57 };
58
59 tx.execute("INSERT INTO persistent.version (id, version) VALUES(0, ?);", params![version])
60 .context("In create_or_get_version: Failed to insert initial version.")?;
61 version
62 };
63 Ok(version)
64 }
65
update_version(tx: &Transaction, new_version: u32) -> Result<()>66 pub fn update_version(tx: &Transaction, new_version: u32) -> Result<()> {
67 let updated = tx
68 .execute("UPDATE persistent.version SET version = ? WHERE id = 0;", params![new_version])
69 .context("In update_version: Failed to update row.")?;
70 if updated == 1 {
71 Ok(())
72 } else {
73 Err(anyhow!("In update_version: No rows were updated."))
74 }
75 }
76
upgrade_database<F>(tx: &Transaction, current_version: u32, upgraders: &[F]) -> Result<()> where F: Fn(&Transaction) -> Result<u32> + 'static,77 pub fn upgrade_database<F>(tx: &Transaction, current_version: u32, upgraders: &[F]) -> Result<()>
78 where
79 F: Fn(&Transaction) -> Result<u32> + 'static,
80 {
81 if upgraders.len() < current_version as usize {
82 return Err(anyhow!("In upgrade_database: Insufficient upgraders provided."));
83 }
84 let mut db_version = create_or_get_version(tx, current_version)
85 .context("In upgrade_database: Failed to get database version.")?;
86 while db_version < current_version {
87 db_version = upgraders[db_version as usize](tx).with_context(|| {
88 format!("In upgrade_database: Trying to upgrade from db version {}.", db_version)
89 })?;
90 }
91 update_version(tx, db_version).context("In upgrade_database.")
92 }
93
94 #[cfg(test)]
95 mod test {
96 use super::*;
97 use rusqlite::{Connection, TransactionBehavior, NO_PARAMS};
98
99 #[test]
upgrade_database_test()100 fn upgrade_database_test() {
101 let mut conn = Connection::open_in_memory().unwrap();
102 conn.execute("ATTACH DATABASE 'file::memory:' as persistent;", NO_PARAMS).unwrap();
103
104 let upgraders: Vec<_> = (0..30_u32)
105 .map(move |i| {
106 move |tx: &Transaction| {
107 tx.execute(
108 "INSERT INTO persistent.test (test_field) VALUES(?);",
109 params![i + 1],
110 )
111 .with_context(|| format!("In upgrade_from_{}_to_{}.", i, i + 1))?;
112 Ok(i + 1)
113 }
114 })
115 .collect();
116
117 for legacy in &[false, true] {
118 if *legacy {
119 conn.execute(
120 "CREATE TABLE IF NOT EXISTS persistent.keyentry (
121 id INTEGER UNIQUE,
122 key_type INTEGER,
123 domain INTEGER,
124 namespace INTEGER,
125 alias BLOB,
126 state INTEGER,
127 km_uuid BLOB);",
128 NO_PARAMS,
129 )
130 .unwrap();
131 }
132 for from in 1..29 {
133 for to in from..30 {
134 conn.execute("DROP TABLE IF EXISTS persistent.version;", NO_PARAMS).unwrap();
135 conn.execute("DROP TABLE IF EXISTS persistent.test;", NO_PARAMS).unwrap();
136 conn.execute(
137 "CREATE TABLE IF NOT EXISTS persistent.test (
138 id INTEGER PRIMARY KEY,
139 test_field INTEGER);",
140 NO_PARAMS,
141 )
142 .unwrap();
143
144 {
145 let tx =
146 conn.transaction_with_behavior(TransactionBehavior::Immediate).unwrap();
147 create_or_get_version(&tx, from).unwrap();
148 tx.commit().unwrap();
149 }
150 {
151 let tx =
152 conn.transaction_with_behavior(TransactionBehavior::Immediate).unwrap();
153 upgrade_database(&tx, to, &upgraders).unwrap();
154 tx.commit().unwrap();
155 }
156
157 // In the legacy database case all upgraders starting from 0 have to run. So
158 // after the upgrade step, the expectations need to be adjusted.
159 let from = if *legacy { 0 } else { from };
160
161 // There must be exactly to - from rows.
162 assert_eq!(
163 to - from,
164 conn.query_row(
165 "SELECT COUNT(test_field) FROM persistent.test;",
166 NO_PARAMS,
167 |row| row.get(0)
168 )
169 .unwrap()
170 );
171 // Each row must have the correct relation between id and test_field. If this
172 // is not the case, the upgraders were not executed in the correct order.
173 assert_eq!(
174 to - from,
175 conn.query_row(
176 "SELECT COUNT(test_field) FROM persistent.test
177 WHERE id = test_field - ?;",
178 params![from],
179 |row| row.get(0)
180 )
181 .unwrap()
182 );
183 }
184 }
185 }
186 }
187
188 #[test]
create_or_get_version_new_database()189 fn create_or_get_version_new_database() {
190 let mut conn = Connection::open_in_memory().unwrap();
191 conn.execute("ATTACH DATABASE 'file::memory:' as persistent;", NO_PARAMS).unwrap();
192 {
193 let tx = conn.transaction_with_behavior(TransactionBehavior::Immediate).unwrap();
194 let version = create_or_get_version(&tx, 3).unwrap();
195 tx.commit().unwrap();
196 assert_eq!(version, 3);
197 }
198
199 // Was the version table created as expected?
200 assert_eq!(
201 Ok("version".to_owned()),
202 conn.query_row(
203 "SELECT name FROM persistent.sqlite_master
204 WHERE type = 'table' AND name = 'version';",
205 NO_PARAMS,
206 |row| row.get(0),
207 )
208 );
209
210 // There is exactly one row in the version table.
211 assert_eq!(
212 Ok(1),
213 conn.query_row("SELECT COUNT(id) from persistent.version;", NO_PARAMS, |row| row
214 .get(0))
215 );
216
217 // The version must be set to 3
218 assert_eq!(
219 Ok(3),
220 conn.query_row(
221 "SELECT version from persistent.version WHERE id = 0;",
222 NO_PARAMS,
223 |row| row.get(0)
224 )
225 );
226
227 // Will subsequent calls to create_or_get_version still return the same version even
228 // if the current version changes.
229 {
230 let tx = conn.transaction_with_behavior(TransactionBehavior::Immediate).unwrap();
231 let version = create_or_get_version(&tx, 5).unwrap();
232 tx.commit().unwrap();
233 assert_eq!(version, 3);
234 }
235
236 // There is still exactly one row in the version table.
237 assert_eq!(
238 Ok(1),
239 conn.query_row("SELECT COUNT(id) from persistent.version;", NO_PARAMS, |row| row
240 .get(0))
241 );
242
243 // Bump the version.
244 {
245 let tx = conn.transaction_with_behavior(TransactionBehavior::Immediate).unwrap();
246 update_version(&tx, 5).unwrap();
247 tx.commit().unwrap();
248 }
249
250 // Now the version should have changed.
251 {
252 let tx = conn.transaction_with_behavior(TransactionBehavior::Immediate).unwrap();
253 let version = create_or_get_version(&tx, 7).unwrap();
254 tx.commit().unwrap();
255 assert_eq!(version, 5);
256 }
257
258 // There is still exactly one row in the version table.
259 assert_eq!(
260 Ok(1),
261 conn.query_row("SELECT COUNT(id) from persistent.version;", NO_PARAMS, |row| row
262 .get(0))
263 );
264
265 // The version must be set to 5
266 assert_eq!(
267 Ok(5),
268 conn.query_row(
269 "SELECT version from persistent.version WHERE id = 0;",
270 NO_PARAMS,
271 |row| row.get(0)
272 )
273 );
274 }
275
276 #[test]
create_or_get_version_legacy_database()277 fn create_or_get_version_legacy_database() {
278 let mut conn = Connection::open_in_memory().unwrap();
279 conn.execute("ATTACH DATABASE 'file::memory:' as persistent;", NO_PARAMS).unwrap();
280 // A legacy (version 0) database is detected if the keyentry table exists but no
281 // version table.
282 conn.execute(
283 "CREATE TABLE IF NOT EXISTS persistent.keyentry (
284 id INTEGER UNIQUE,
285 key_type INTEGER,
286 domain INTEGER,
287 namespace INTEGER,
288 alias BLOB,
289 state INTEGER,
290 km_uuid BLOB);",
291 NO_PARAMS,
292 )
293 .unwrap();
294
295 {
296 let tx = conn.transaction_with_behavior(TransactionBehavior::Immediate).unwrap();
297 let version = create_or_get_version(&tx, 3).unwrap();
298 tx.commit().unwrap();
299 // In the legacy case, version 0 must be returned.
300 assert_eq!(version, 0);
301 }
302
303 // Was the version table created as expected?
304 assert_eq!(
305 Ok("version".to_owned()),
306 conn.query_row(
307 "SELECT name FROM persistent.sqlite_master
308 WHERE type = 'table' AND name = 'version';",
309 NO_PARAMS,
310 |row| row.get(0),
311 )
312 );
313
314 // There is exactly one row in the version table.
315 assert_eq!(
316 Ok(1),
317 conn.query_row("SELECT COUNT(id) from persistent.version;", NO_PARAMS, |row| row
318 .get(0))
319 );
320
321 // The version must be set to 0
322 assert_eq!(
323 Ok(0),
324 conn.query_row(
325 "SELECT version from persistent.version WHERE id = 0;",
326 NO_PARAMS,
327 |row| row.get(0)
328 )
329 );
330
331 // Will subsequent calls to create_or_get_version still return the same version even
332 // if the current version changes.
333 {
334 let tx = conn.transaction_with_behavior(TransactionBehavior::Immediate).unwrap();
335 let version = create_or_get_version(&tx, 5).unwrap();
336 tx.commit().unwrap();
337 assert_eq!(version, 0);
338 }
339
340 // There is still exactly one row in the version table.
341 assert_eq!(
342 Ok(1),
343 conn.query_row("SELECT COUNT(id) from persistent.version;", NO_PARAMS, |row| row
344 .get(0))
345 );
346
347 // Bump the version.
348 {
349 let tx = conn.transaction_with_behavior(TransactionBehavior::Immediate).unwrap();
350 update_version(&tx, 5).unwrap();
351 tx.commit().unwrap();
352 }
353
354 // Now the version should have changed.
355 {
356 let tx = conn.transaction_with_behavior(TransactionBehavior::Immediate).unwrap();
357 let version = create_or_get_version(&tx, 7).unwrap();
358 tx.commit().unwrap();
359 assert_eq!(version, 5);
360 }
361
362 // There is still exactly one row in the version table.
363 assert_eq!(
364 Ok(1),
365 conn.query_row("SELECT COUNT(id) from persistent.version;", NO_PARAMS, |row| row
366 .get(0))
367 );
368
369 // The version must be set to 5
370 assert_eq!(
371 Ok(5),
372 conn.query_row(
373 "SELECT version from persistent.version WHERE id = 0;",
374 NO_PARAMS,
375 |row| row.get(0)
376 )
377 );
378 }
379 }
380