1use crate::db::sql_fns;
6use crate::sync::address::name_utils::{join_name_parts, NameParts};
7use error_support::debug;
8use rusqlite::{functions::FunctionFlags, Connection, Transaction};
9use sql_support::open_database::{ConnectionInitializer, Error, Result};
10
11pub const ADDRESS_COMMON_COLS: &str = "
12 guid,
13 name,
14 organization,
15 street_address,
16 address_level3,
17 address_level2,
18 address_level1,
19 postal_code,
20 country,
21 tel,
22 email,
23 time_created,
24 time_last_used,
25 time_last_modified,
26 times_used";
27
28pub const ADDRESS_COMMON_VALS: &str = "
29 :guid,
30 :name,
31 :organization,
32 :street_address,
33 :address_level3,
34 :address_level2,
35 :address_level1,
36 :postal_code,
37 :country,
38 :tel,
39 :email,
40 :time_created,
41 :time_last_used,
42 :time_last_modified,
43 :times_used";
44
45pub const CREDIT_CARD_COMMON_COLS: &str = "
46 guid,
47 cc_name,
48 cc_number_enc,
49 cc_number_last_4,
50 cc_exp_month,
51 cc_exp_year,
52 cc_type,
53 time_created,
54 time_last_used,
55 time_last_modified,
56 times_used";
57
58pub const CREDIT_CARD_COMMON_VALS: &str = "
59 :guid,
60 :cc_name,
61 :cc_number_enc,
62 :cc_number_last_4,
63 :cc_exp_month,
64 :cc_exp_year,
65 :cc_type,
66 :time_created,
67 :time_last_used,
68 :time_last_modified,
69 :times_used";
70
71pub const PASSPORT_COMMON_COLS: &str = "
72 guid,
73 name,
74 country,
75 passport_number,
76 issue_date_month,
77 issue_date_day,
78 issue_date_year,
79 expiry_date_month,
80 expiry_date_day,
81 expiry_date_year,
82 time_created,
83 time_last_used,
84 time_last_modified,
85 times_used";
86
87pub const PASSPORT_COMMON_VALS: &str = "
88 :guid,
89 :name,
90 :country,
91 :passport_number,
92 :issue_date_month,
93 :issue_date_day,
94 :issue_date_year,
95 :expiry_date_month,
96 :expiry_date_day,
97 :expiry_date_year,
98 :time_created,
99 :time_last_used,
100 :time_last_modified,
101 :times_used";
102
103const CREATE_SHARED_SCHEMA_SQL: &str = include_str!("../../sql/create_shared_schema.sql");
104const CREATE_SHARED_TRIGGERS_SQL: &str = include_str!("../../sql/create_shared_triggers.sql");
105const CREATE_SYNC_TEMP_TABLES_SQL: &str = include_str!("../../sql/create_sync_temp_tables.sql");
106
107pub struct AutofillConnectionInitializer;
108
109impl ConnectionInitializer for AutofillConnectionInitializer {
110 const NAME: &'static str = "autofill db";
111 const END_VERSION: u32 = 5;
112
113 fn prepare(&self, conn: &Connection, _db_empty: bool) -> Result<()> {
114 define_functions(conn)?;
115
116 let initial_pragmas = "
117 -- use in-memory storage
118 PRAGMA temp_store = 2;
119 -- use write-ahead logging
120 PRAGMA journal_mode = WAL;
121 -- autofill does not use foreign keys at present but this is probably a good pragma to set
122 PRAGMA foreign_keys = ON;
123 ";
124 conn.execute_batch(initial_pragmas)?;
125
126 conn.set_prepared_statement_cache_capacity(128);
127 Ok(())
128 }
129
130 fn init(&self, db: &Transaction<'_>) -> Result<()> {
131 Ok(db.execute_batch(CREATE_SHARED_SCHEMA_SQL)?)
132 }
133
134 fn upgrade_from(&self, db: &Transaction<'_>, version: u32) -> Result<()> {
135 match version {
136 0 => upgrade_from_v0(db),
139 1 => upgrade_from_v1(db),
140 2 => upgrade_from_v2(db),
141 3 => upgrade_from_v3(db),
142 4 => upgrade_from_v4(db),
143 _ => Err(Error::IncompatibleVersion(version)),
144 }
145 }
146
147 fn finish(&self, db: &Connection) -> Result<()> {
148 Ok(db.execute_batch(CREATE_SHARED_TRIGGERS_SQL)?)
149 }
150}
151
152fn define_functions(c: &Connection) -> Result<()> {
153 c.create_scalar_function(
154 "generate_guid",
155 0,
156 FunctionFlags::SQLITE_UTF8,
157 sql_fns::generate_guid,
158 )?;
159 c.create_scalar_function("now", 0, FunctionFlags::SQLITE_UTF8, sql_fns::now)?;
160
161 Ok(())
162}
163
164fn upgrade_from_v0(db: &Connection) -> Result<()> {
165 db.execute_batch(
174 "
175 DROP TABLE IF EXISTS credit_cards_data;
176 CREATE TABLE credit_cards_data (
177 guid TEXT NOT NULL PRIMARY KEY CHECK(length(guid) != 0),
178 cc_name TEXT NOT NULL,
179 cc_number_enc TEXT NOT NULL CHECK(length(cc_number_enc) > 20),
180 cc_number_last_4 TEXT NOT NULL CHECK(length(cc_number_last_4) <= 4),
181 cc_exp_month INTEGER,
182 cc_exp_year INTEGER,
183 cc_type TEXT NOT NULL,
184 time_created INTEGER NOT NULL,
185 time_last_used INTEGER,
186 time_last_modified INTEGER NOT NULL,
187 times_used INTEGER NOT NULL,
188 sync_change_counter INTEGER NOT NULL
189 );
190 ",
191 )?;
192 Ok(())
193}
194
195fn upgrade_from_v1(db: &Connection) -> Result<()> {
196 db.execute_batch(
202 "
203 CREATE TABLE new_credit_cards_data (
204 guid TEXT NOT NULL PRIMARY KEY CHECK(length(guid) != 0),
205 cc_name TEXT NOT NULL,
206 cc_number_enc TEXT NOT NULL CHECK(length(cc_number_enc) > 20 OR cc_number_enc == ''),
207 cc_number_last_4 TEXT NOT NULL CHECK(length(cc_number_last_4) <= 4),
208 cc_exp_month INTEGER,
209 cc_exp_year INTEGER,
210 cc_type TEXT NOT NULL,
211 time_created INTEGER NOT NULL,
212 time_last_used INTEGER,
213 time_last_modified INTEGER NOT NULL,
214 times_used INTEGER NOT NULL,
215 sync_change_counter INTEGER NOT NULL
216 );
217 INSERT INTO new_credit_cards_data(guid, cc_name, cc_number_enc, cc_number_last_4, cc_exp_month,
218 cc_exp_year, cc_type, time_created, time_last_used, time_last_modified, times_used,
219 sync_change_counter)
220 SELECT guid, cc_name, cc_number_enc, cc_number_last_4, cc_exp_month, cc_exp_year, cc_type,
221 time_created, time_last_used, time_last_modified, times_used, sync_change_counter
222 FROM credit_cards_data;
223 DROP TABLE credit_cards_data;
224 ALTER TABLE new_credit_cards_data RENAME to credit_cards_data;
225 ")?;
226 Ok(())
227}
228
229fn upgrade_from_v2(db: &Connection) -> Result<()> {
230 db.execute_batch("ALTER TABLE addresses_data ADD COLUMN name TEXT NOT NULL DEFAULT ''")?;
231
232 let mut stmt =
233 db.prepare("SELECT guid, given_name, additional_name, family_name FROM addresses_data")?;
234 let rows = stmt.query_map([], |row| {
235 Ok((
236 row.get::<_, String>("guid")?,
237 row.get::<_, String>("given_name")?,
238 row.get::<_, String>("additional_name")?,
239 row.get::<_, String>("family_name")?,
240 ))
241 })?;
242
243 for row in rows {
244 let (guid, given, middle, family) = row?;
245 let full_name = join_name_parts(&NameParts {
246 given,
247 middle,
248 family,
249 });
250
251 db.execute(
252 "UPDATE addresses_data SET name = (:name) WHERE guid = (:guid)",
253 rusqlite::named_params! { ":name": full_name, ":guid": guid},
254 )?;
255 }
256
257 db.execute_batch(
258 "
259 ALTER TABLE addresses_data DROP COLUMN given_name;
260 ALTER TABLE addresses_data DROP COLUMN additional_name;
261 ALTER TABLE addresses_data DROP COLUMN family_name;
262 ",
263 )?;
264
265 Ok(())
266}
267
268fn upgrade_from_v3(db: &Connection) -> Result<()> {
269 let migration_string: &str = include_str!("../../sql/migrations/v4_migration.sql");
270 db.execute_batch(migration_string)?;
271 Ok(())
272}
273
274fn upgrade_from_v4(db: &Connection) -> Result<()> {
275 db.execute_batch(CREATE_SHARED_SCHEMA_SQL)?;
280 Ok(())
281}
282
283pub fn create_empty_sync_temp_tables(db: &Connection) -> Result<()> {
284 debug!("Initializing sync temp tables");
285 db.execute_batch(CREATE_SYNC_TEMP_TABLES_SQL)?;
286 Ok(())
287}
288
289#[cfg(test)]
290mod tests {
291 use super::*;
292 use crate::db::addresses::get_address;
293 use crate::db::credit_cards::get_credit_card;
294 use crate::db::test::new_mem_db;
295 use sql_support::open_database::test_utils::MigratedDatabaseFile;
296 use sync_guid::Guid;
297 use types::Timestamp;
298
299 const CREATE_V0_DB: &str = include_str!("../../sql/tests/create_v0_db.sql");
300 const CREATE_V1_DB: &str = include_str!("../../sql/tests/create_v1_db.sql");
301 const CREATE_V2_DB: &str = include_str!("../../sql/tests/create_v2_db.sql");
302 const CREATE_V3_DB: &str = include_str!("../../sql/tests/create_v3_db.sql");
303 const CREATE_V4_DB: &str = include_str!("../../sql/tests/create_v4_db.sql");
304
305 #[test]
306 fn test_create_schema_twice() {
307 let db = new_mem_db();
308 db.execute_batch(CREATE_SHARED_SCHEMA_SQL)
309 .expect("should allow running main schema creation twice");
310 db.execute_batch(CREATE_SYNC_TEMP_TABLES_SQL)
312 .expect("should allow running sync temp tables first time");
313 db.execute_batch(CREATE_SYNC_TEMP_TABLES_SQL)
314 .expect("should allow running sync temp tables second time");
315 }
316
317 #[test]
318 fn test_all_upgrades() {
319 let db_file = MigratedDatabaseFile::new(AutofillConnectionInitializer, CREATE_V1_DB);
321 db_file.run_all_upgrades();
322 let conn = db_file.open();
323
324 let cc = get_credit_card(&conn, &Guid::new("A")).unwrap();
326 assert_eq!(cc.guid, "A");
327 assert_eq!(cc.cc_name, "Jane Doe");
328 assert_eq!(cc.cc_number_enc, "012345678901234567890");
329 assert_eq!(cc.cc_number_last_4, "1234");
330 assert_eq!(cc.cc_exp_month, 1);
331 assert_eq!(cc.cc_exp_year, 2020);
332 assert_eq!(cc.cc_type, "visa");
333 assert_eq!(cc.metadata.time_created, Timestamp(0));
334 assert_eq!(cc.metadata.time_last_used, Timestamp(1));
335 assert_eq!(cc.metadata.time_last_modified, Timestamp(2));
336 assert_eq!(cc.metadata.times_used, 3);
337 assert_eq!(cc.metadata.sync_change_counter, 0);
338
339 let address = get_address(&conn, &Guid::new("A")).unwrap();
340 assert_eq!(address.guid, "A");
341 assert_eq!(address.name, "Jane JaneDoe2 Doe");
342 assert_eq!(address.organization, "Mozilla");
343 assert_eq!(address.street_address, "123 Maple lane");
344 assert_eq!(address.address_level3, "Shelbyville");
345 assert_eq!(address.address_level2, "Springfield");
346 assert_eq!(address.address_level1, "MA");
347 assert_eq!(address.postal_code, "12345");
348 assert_eq!(address.country, "US");
349 assert_eq!(address.tel, "01-234-567-8000");
350 assert_eq!(address.email, "jane@hotmail.com");
351 assert_eq!(address.metadata.time_created, Timestamp(0));
352 assert_eq!(address.metadata.time_last_used, Timestamp(1));
353 assert_eq!(address.metadata.time_last_modified, Timestamp(2));
354 assert_eq!(address.metadata.times_used, 3);
355 assert_eq!(address.metadata.sync_change_counter, 0);
356 }
357
358 #[test]
359 fn test_upgrade_version_0() {
360 let db_file = MigratedDatabaseFile::new(AutofillConnectionInitializer, CREATE_V0_DB);
361 let select_cc_number_enc = "SELECT cc_number_enc from credit_cards_data";
364 db_file
365 .open()
366 .execute_batch(select_cc_number_enc)
367 .expect_err("select should fail due to bad field name");
368
369 db_file.upgrade_to(1);
370
371 db_file
372 .open()
373 .execute_batch(select_cc_number_enc)
374 .expect("select should now work");
375 }
376
377 #[test]
378 fn test_upgrade_version_1() {
379 let db_file = MigratedDatabaseFile::new(AutofillConnectionInitializer, CREATE_V1_DB);
380
381 db_file.upgrade_to(2);
382 let db = db_file.open();
383
384 db.execute("UPDATE credit_cards_data SET cc_number_enc=''", [])
386 .expect("blank cc_number_enc should be valid");
387 db.execute("UPDATE credit_cards_data SET cc_number_enc='x'", [])
388 .expect_err("cc_number_enc should be invalid");
389 }
390
391 #[test]
392 fn test_upgrade_version_2() {
393 let db_file = MigratedDatabaseFile::new(AutofillConnectionInitializer, CREATE_V2_DB);
394 let db = db_file.open();
395
396 db.execute_batch("SELECT name from addresses_data")
397 .expect_err("select should fail");
398 db.execute_batch("SELECT street_address from addresses_data")
399 .expect("street_address should work");
400 db.execute_batch("SELECT additional_name from addresses_data")
401 .expect("additional_name should work");
402 db.execute_batch("SELECT family_name from addresses_data")
403 .expect("family_name should work");
404
405 db_file.upgrade_to(3);
406
407 db.execute_batch("SELECT name from addresses_data")
408 .expect("select name should now work");
409 db.execute_batch("SELECT given_name from addresses_data")
410 .expect_err("given_name should fail");
411 db.execute_batch("SELECT additional_name from addresses_data")
412 .expect_err("additional_name should fail");
413 db.execute_batch("SELECT family_name from addresses_data")
414 .expect_err("family_name should fail");
415
416 let mut address = get_address(&db, &Guid::new("A")).unwrap();
417 assert_eq!(address.guid, "A");
418 assert_eq!(address.name, "Jane John Doe");
419
420 address = get_address(&db, &Guid::new("B")).unwrap();
421 assert_eq!(address.guid, "B");
422
423 assert_eq!(address.name, "");
425 }
426
427 #[test]
428 fn test_upgrade_version_3() {
429 let db_file = MigratedDatabaseFile::new(AutofillConnectionInitializer, CREATE_V3_DB);
430 let db = db_file.open();
431
432 let mut address = get_address(&db, &Guid::new("A")).unwrap();
434 assert_eq!(address.guid, "A");
435 assert_eq!(address.name, "Jane John Doe");
436 assert_eq!(address.address_level1, "Massachusetts");
437
438 address = get_address(&db, &Guid::new("B")).unwrap();
439 assert_eq!(address.guid, "B");
440 assert_eq!(address.address_level1, "Ontario");
441
442 db_file.upgrade_to(4);
443
444 address = get_address(&db, &Guid::new("A")).unwrap();
446 assert_eq!(address.guid, "A");
447 assert_eq!(address.name, "Jane John Doe");
448 assert_eq!(address.address_level1, "MA");
449
450 address = get_address(&db, &Guid::new("B")).unwrap();
451 assert_eq!(address.guid, "B");
452 assert_eq!(address.address_level1, "ON");
453 }
454
455 #[test]
456 fn test_upgrade_version_4() {
457 let db_file = MigratedDatabaseFile::new(AutofillConnectionInitializer, CREATE_V4_DB);
458 let db = db_file.open();
459
460 db.execute_batch("SELECT guid FROM passports_data")
462 .expect_err("passports_data should not exist at v4");
463
464 db_file.upgrade_to(5);
465
466 db.execute_batch("SELECT guid FROM passports_data")
468 .expect("passports_data should exist at v5");
469 }
470}