1use super::*;
2use rusqlite::{OptionalExtension, Transaction};
3
4pub(crate) const VERSION: u32 = 16;
5/// The schema whose batches still recorded review conflicts.
6pub(crate) const PREVIOUS: u32 = 15;
7
8/// Tables a schema-14 cache shares with this one.
9pub(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.
25pub(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
48pub(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.
57pub(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}