1/* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
4use crate::error::warn;
5use rusqlite::Transaction;
6use sql_support::open_database;
78const CREATE_TABLE_PUSH_SQL: &str = include_str!("schema.sql");
910pub struct PushConnectionInitializer;
1112impl open_database::ConnectionInitializer for PushConnectionInitializer {
13const NAME: &'static str = "push db";
14const END_VERSION: u32 = 3;
1516// This is such a simple database that we do almost nothing!
17 // * We have no foreign keys, so `PRAGMA foreign_keys = ON;` is pointless.
18 // * We have no temp tables, so `PRAGMA temp_store = 2;` is pointless.
19 // * We don't even use transactions, so `PRAGMA journal_mode=WAL;` is pointless.
20 // * We have a tiny number of different SQL statements, so
21 // set_prepared_statement_cache_capacity is pointless.
22 // * We have no SQL functions.
23 // Kinda makes you wonder why we use a sql db at all :)
24 // So - no "prepare" and no "finish" methods.
25fn init(&self, db: &Transaction<'_>) -> open_database::Result<()> {
26 db.execute_batch(CREATE_TABLE_PUSH_SQL)?;
27Ok(())
28 }
2930fn upgrade_from(&self, db: &Transaction<'_>, version: u32) -> open_database::Result<()> {
31match version {
320 => db.execute_batch(CREATE_TABLE_PUSH_SQL)?,
331 => db.execute_batch(CREATE_TABLE_PUSH_SQL)?,
342 => {
35// We dropped the `uaid` and `native_id` columns and added a constraint that scope
36 // must not be an empty string and must be unique.
37let sql = format!(
38"
39 -- rename the old table.
40 ALTER TABLE push_record RENAME TO push_record_old;
41 -- create the new table with the new schema.
42 {CREATE_TABLE_PUSH_SQL};
43 -- move the data across.
44 INSERT OR IGNORE INTO push_record ({COMMON_COLS})
45 SELECT {COMMON_COLS} FROM push_record_old WHERE length(scope) > 0;
46 -- drop the old table
47 DROP TABLE push_record_old;",
48 CREATE_TABLE_PUSH_SQL = CREATE_TABLE_PUSH_SQL,
49 COMMON_COLS = COMMON_COLS,
50 );
51 db.execute_batch(&sql)?;
52 }
53 other => {
54warn!(
55"Loaded future schema version {} (we only understand version {}). \
56 Optimistically ",
57 other,
58Self::END_VERSION
59 )
60 }
61 };
62Ok(())
63 }
64}
6566pub const COMMON_COLS: &str = "
67 channel_id,
68 endpoint,
69 scope,
70 key,
71 ctime,
72 app_server_key
73";
7475#[cfg(test)]
76mod test {
77use crate::internal::storage::db::{PushDb, Storage};
78use rusqlite::{Connection, OpenFlags};
79use sql_support::ConnExt;
8081const CREATE_V2_SCHEMA: &str = include_str!("test/schema_v2.sql");
8283#[test]
84fn test_migrate_v2_v3() {
85 error_support::init_for_tests();
86let tempdir = tempfile::tempdir().unwrap();
87let path = tempdir.path().join("push_v2.sql");
8889let conn = Connection::open_with_flags(path.clone(), OpenFlags::default()).unwrap();
90 conn.execute_batch(CREATE_V2_SCHEMA).unwrap();
9192// insert some stuff
93conn.execute_batch(
94r#"
95 INSERT INTO push_record (
96 uaid, channel_id, endpoint, scope, key, ctime, app_server_key, native_id
97 ) VALUES
98 ("id-1", "cid1", "ep-1", "sc-1", x'1234', 1, "ask-1", "nid-1"),
99 -- duplicate scope, which isn't allowed in the new schema
100 ("id-2", "cid2", "ep-2", "sc-1", x'5678', 2, "ask-2", "nid-2"),
101 -- empty scope, which isn't allowed in the new schema
102 ("id-3", "cid3", "ep-3", "", x'0000', 3, "ask-3", "nid-3")
103 ;
104 INSERT into meta_data (
105 key, value
106 ) VALUES
107 ("key-1", "value-1"),
108 ("key-2", "value-2")
109 "#,
110 )
111 .unwrap();
112113// reopen the database.
114drop(conn);
115let db = PushDb::open(path).expect("should open");
116117// Should only have 1 row in push_record
118assert_eq!(
119 db.query_one::<u32>("SELECT COUNT(*) FROM push_record")
120 .unwrap(),
1211
122);
123let record = db
124 .get_record("cid1")
125 .expect("should work")
126 .expect("should get a record");
127assert_eq!(record.channel_id, "cid1");
128assert_eq!(record.endpoint, "ep-1");
129assert_eq!(record.scope, "sc-1");
130assert_eq!(record.key, [0x12, 0x34]);
131assert_eq!(record.ctime.0, 1);
132assert_eq!(record.app_server_key.unwrap(), "ask-1");
133134// But both metadata ones.
135assert_eq!(
136 db.db
137 .query_one::<u32>("SELECT COUNT(*) FROM meta_data")
138 .unwrap(),
1392
140);
141assert_eq!(db.get_meta("key-1").unwrap().unwrap(), "value-1");
142assert_eq!(db.get_meta("key-2").unwrap().unwrap(), "value-2");
143 }
144}