| 1 | use super::*; |
| 2 | use rusqlite::{OptionalExtension, Transaction}; |
| 3 | |
| 4 | pub(crate) const VERSION: u32 = 16; |
| 5 | /// The schema whose batches still recorded review conflicts. |
| 6 | pub(crate) const PREVIOUS: u32 = 15; |
| 7 | |
| 8 | /// Tables a schema-14 cache shares with this one. |
| 9 | pub(crate) const KEPT: &str = " |
| 10 | CREATE TABLE receipts ( |
| 11 | edit_id INTEGER PRIMARY KEY CHECK(edit_id>0), |
| 12 | revision TEXT NOT NULL |
| 13 | ) STRICT; |
| 14 | CREATE TABLE archived ( |
| 15 | edit_id INTEGER PRIMARY KEY CHECK(edit_id>0), |
| 16 | archive TEXT NOT NULL |
| 17 | ) STRICT; |
| 18 | CREATE TABLE assets ( |
| 19 | name TEXT PRIMARY KEY NOT NULL, |
| 20 | data BLOB NOT NULL, |
| 21 | sha256 BLOB NOT NULL CHECK(length(sha256)=32) |
| 22 | ) STRICT;"; |
| 23 | |
| 24 | /// The queue: the image its edits apply to, in chunks, and the edits in publication batches. |
| 25 | pub(crate) const QUEUE: &str = " |
| 26 | CREATE TABLE base (chunk INTEGER PRIMARY KEY CHECK(chunk>=0), bytes BLOB NOT NULL) STRICT; |
| 27 | CREATE TABLE remote (chunk INTEGER PRIMARY KEY CHECK(chunk>=0), bytes BLOB NOT NULL) STRICT; |
| 28 | CREATE TABLE batches ( |
| 29 | id INTEGER PRIMARY KEY AUTOINCREMENT CHECK(id>0), |
| 30 | sealed TEXT, |
| 31 | revisions TEXT, |
| 32 | attempted INTEGER NOT NULL DEFAULT 0 CHECK(attempted IN (0,1)), |
| 33 | CHECK((sealed IS NULL) = (revisions IS NULL)), |
| 34 | CHECK(attempted=0 OR sealed IS NOT NULL) |
| 35 | ) STRICT; |
| 36 | CREATE TABLE edits ( |
| 37 | id INTEGER PRIMARY KEY AUTOINCREMENT CHECK(id>0), |
| 38 | batch INTEGER NOT NULL REFERENCES batches(id) ON DELETE CASCADE, |
| 39 | author TEXT NOT NULL, |
| 40 | edit TEXT NOT NULL, |
| 41 | payloads TEXT |
| 42 | ) STRICT; |
| 43 | CREATE TABLE payloads ( |
| 44 | sha256 BLOB PRIMARY KEY CHECK(length(sha256)=32), |
| 45 | bytes BLOB NOT NULL |
| 46 | ) STRICT;"; |
| 47 | |
| 48 | pub(crate) fn create(transaction: &Transaction<'_>) -> Result<()> { |
| 49 | transaction.execute_batch(QUEUE)?; |
| 50 | transaction.execute_batch(KEPT)?; |
| 51 | Ok(()) |
| 52 | } |
| 53 | |
| 54 | /// Converts a schema-15 cache: its batches lose the review conflict and its page, which |
| 55 | /// a merge no longer records. A conflict it held is queued work the next rebase keeps as |
| 56 | /// conflict pages. Foreign keys stay off while `edits` briefly references the old table. |
| 57 | pub(crate) fn upgrade(connection: &mut Connection) -> Result<()> { |
| 58 | connection.execute_batch("PRAGMA foreign_keys=OFF")?; |
| 59 | let upgraded = (|| -> Result<()> { |
| 60 | let transaction = connection.transaction_with_behavior(TransactionBehavior::Exclusive)?; |
| 61 | let sequence: Option<i64> = transaction |
| 62 | .query_row( |
| 63 | "SELECT seq FROM sqlite_sequence WHERE name='batches'", |
| 64 | [], |
| 65 | |row| row.get(0), |
| 66 | ) |
| 67 | .optional()?; |
| 68 | transaction.execute_batch( |
| 69 | "CREATE TABLE upgraded ( |
| 70 | id INTEGER PRIMARY KEY AUTOINCREMENT CHECK(id>0), |
| 71 | sealed TEXT, |
| 72 | revisions TEXT, |
| 73 | attempted INTEGER NOT NULL DEFAULT 0 CHECK(attempted IN (0,1)), |
| 74 | CHECK((sealed IS NULL) = (revisions IS NULL)), |
| 75 | CHECK(attempted=0 OR sealed IS NOT NULL) |
| 76 | ) STRICT; |
| 77 | INSERT INTO upgraded(id, sealed, revisions, attempted) |
| 78 | SELECT id, sealed, revisions, attempted FROM batches; |
| 79 | DROP TABLE batches; |
| 80 | ALTER TABLE upgraded RENAME TO batches;", |
| 81 | )?; |
| 82 | if let Some(sequence) = sequence { |
| 83 | transaction.execute( |
| 84 | "UPDATE sqlite_sequence SET seq=max(seq, ?1) WHERE name='batches'", |
| 85 | [sequence], |
| 86 | )?; |
| 87 | } |
| 88 | let dangling: bool = transaction.query_row( |
| 89 | "SELECT EXISTS(SELECT 1 FROM pragma_foreign_key_check)", |
| 90 | [], |
| 91 | |row| row.get(0), |
| 92 | )?; |
| 93 | if dangling { |
| 94 | return Err(io::Error::new( |
| 95 | io::ErrorKind::InvalidData, |
| 96 | "The schema-15 cache holds edits without their batch", |
| 97 | ) |
| 98 | .into()); |
| 99 | } |
| 100 | transaction.pragma_update(None, "user_version", VERSION)?; |
| 101 | transaction.commit()?; |
| 102 | Ok(()) |
| 103 | })(); |
| 104 | connection.execute_batch("PRAGMA foreign_keys=ON")?; |
| 105 | upgraded |
| 106 | } |