mirror of
https://github.com/block/buzz.git
synced 2026-08-18 06:50:31 +02:00
fix(db): preserve duplicate DM data during upgrade
Signed-off-by: npub13n66s06epmqf2kc3v373ez8hj65cuzyvxzjf93vwpervxqn2u7jq2qd9je <8cf5a83f590ec0955b11647d1c88f796a98e088c30a492c58e0e46c3026ae7a4@buzz.block.builderlab.xyz>
This commit is contained in:
parent
96eea55720
commit
024491648d
@@ -244,8 +244,12 @@ pub(crate) async fn migrate(pool: &SqlitePool) -> Result<()> {
|
||||
"CREATE TRIGGER events_fts_insert AFTER INSERT ON events WHEN new.kind IN (0, 9, 40002, 45001, 45003) BEGIN INSERT INTO events_fts(rowid, content) VALUES (new.rowid, new.content); END",
|
||||
"CREATE TRIGGER events_fts_delete AFTER DELETE ON events WHEN old.kind IN (0, 9, 40002, 45001, 45003) BEGIN INSERT INTO events_fts(events_fts, rowid, content) VALUES ('delete', old.rowid, old.content); END",
|
||||
"CREATE TRIGGER events_fts_update AFTER UPDATE OF content, kind ON events BEGIN INSERT INTO events_fts(events_fts, rowid, content) SELECT 'delete', old.rowid, old.content WHERE old.kind IN (0, 9, 40002, 45001, 45003); INSERT INTO events_fts(rowid, content) SELECT new.rowid, new.content WHERE new.kind IN (0, 9, 40002, 45001, 45003); END",
|
||||
"INSERT OR IGNORE INTO channel_members (channel_id, pubkey, role, joined_at, invited_by, hidden_at, removed_at) SELECT winner.id, cm.pubkey, cm.role, cm.joined_at, cm.invited_by, cm.hidden_at, cm.removed_at FROM channels duplicate JOIN channels winner ON winner.id = (SELECT MIN(candidate.id) FROM channels candidate WHERE candidate.community_id = duplicate.community_id AND candidate.participant_hash = duplicate.participant_hash AND candidate.channel_type = 'dm' AND candidate.deleted_at IS NULL) JOIN channel_members cm ON cm.channel_id = duplicate.id WHERE duplicate.channel_type = 'dm' AND duplicate.deleted_at IS NULL AND duplicate.participant_hash IS NOT NULL AND duplicate.id <> winner.id",
|
||||
"UPDATE channels SET deleted_at = unixepoch() WHERE channel_type = 'dm' AND deleted_at IS NULL AND participant_hash IS NOT NULL AND id <> (SELECT MIN(candidate.id) FROM channels candidate WHERE candidate.community_id = channels.community_id AND candidate.participant_hash = channels.participant_hash AND candidate.channel_type = 'dm' AND candidate.deleted_at IS NULL)",
|
||||
"CREATE TEMP TABLE dm_channel_merges (loser_id TEXT PRIMARY KEY, winner_id TEXT NOT NULL)",
|
||||
"INSERT INTO dm_channel_merges (loser_id, winner_id) SELECT id, winner_id FROM (SELECT id, FIRST_VALUE(id) OVER (PARTITION BY community_id, participant_hash ORDER BY created_at ASC, id ASC) AS winner_id, ROW_NUMBER() OVER (PARTITION BY community_id, participant_hash ORDER BY created_at ASC, id ASC) AS ordinal FROM channels WHERE channel_type = 'dm' AND deleted_at IS NULL AND participant_hash IS NOT NULL) ranked WHERE ordinal > 1",
|
||||
"UPDATE events SET channel_id = (SELECT winner_id FROM dm_channel_merges WHERE loser_id = events.channel_id) WHERE channel_id IN (SELECT loser_id FROM dm_channel_merges)",
|
||||
"INSERT INTO channel_members (channel_id, pubkey, role, joined_at, invited_by, hidden_at, removed_at) SELECT merges.winner_id, members.pubkey, members.role, members.joined_at, members.invited_by, members.hidden_at, members.removed_at FROM channel_members members JOIN dm_channel_merges merges ON merges.loser_id = members.channel_id WHERE true ON CONFLICT(channel_id, pubkey) DO UPDATE SET joined_at = MIN(channel_members.joined_at, excluded.joined_at), invited_by = COALESCE(channel_members.invited_by, excluded.invited_by), hidden_at = CASE WHEN channel_members.hidden_at IS NULL OR excluded.hidden_at IS NULL THEN NULL ELSE MIN(channel_members.hidden_at, excluded.hidden_at) END, removed_at = CASE WHEN channel_members.removed_at IS NULL OR excluded.removed_at IS NULL THEN NULL ELSE MIN(channel_members.removed_at, excluded.removed_at) END",
|
||||
"DELETE FROM channels WHERE id IN (SELECT loser_id FROM dm_channel_merges)",
|
||||
"DROP TABLE dm_channel_merges",
|
||||
"CREATE UNIQUE INDEX IF NOT EXISTS idx_channels_dm_hash ON channels (community_id, participant_hash) WHERE channel_type = 'dm' AND deleted_at IS NULL",
|
||||
"CREATE TABLE IF NOT EXISTS git_repo_names (community_id TEXT NOT NULL, repo_id TEXT NOT NULL, owner_pubkey TEXT NOT NULL, created_at INTEGER NOT NULL DEFAULT (unixepoch()), PRIMARY KEY (community_id, repo_id), FOREIGN KEY (community_id) REFERENCES communities(id) ON DELETE CASCADE)",
|
||||
] {
|
||||
@@ -1930,7 +1934,26 @@ mod tests {
|
||||
async fn repairs_old_v3_fts_privacy_and_dm_uniqueness_on_upgrade() {
|
||||
let path =
|
||||
std::env::temp_dir().join(format!("buzz-db-v3-upgrade-{}.sqlite", Uuid::new_v4()));
|
||||
let pool = connect(path.to_str().unwrap()).await.unwrap();
|
||||
let options = SqliteConnectOptions::from_str(&format!("sqlite://{}", path.display()))
|
||||
.unwrap()
|
||||
.create_if_missing(true)
|
||||
.foreign_keys(true);
|
||||
let pool = SqlitePoolOptions::new()
|
||||
.max_connections(1)
|
||||
.connect_with(options)
|
||||
.await
|
||||
.unwrap();
|
||||
for statement in SCHEMA.split(';').map(str::trim).filter(|s| !s.is_empty()) {
|
||||
sqlx::query(statement).execute(&pool).await.unwrap();
|
||||
}
|
||||
sqlx::query("CREATE TABLE schema_version (singleton INTEGER PRIMARY KEY CHECK (singleton = 1), version INTEGER NOT NULL)")
|
||||
.execute(&pool)
|
||||
.await
|
||||
.unwrap();
|
||||
sqlx::query("INSERT INTO schema_version (singleton, version) VALUES (1, 3)")
|
||||
.execute(&pool)
|
||||
.await
|
||||
.unwrap();
|
||||
let community = ensure_configured_community(&pool, "v3-upgrade.example")
|
||||
.await
|
||||
.unwrap()
|
||||
@@ -1952,15 +1975,35 @@ mod tests {
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
// Recreate the objects emitted by the original v3 migration, including its
|
||||
// broad denylist policy and direct events content table.
|
||||
let winner = "00000000-0000-0000-0000-000000000001";
|
||||
let loser = "00000000-0000-0000-0000-000000000002";
|
||||
let participant_hash = vec![9_u8; 32];
|
||||
for (id, created_at) in [(winner, 100_i64), (loser, 101_i64)] {
|
||||
sqlx::query("INSERT INTO channels (id, community_id, name, channel_type, visibility, participant_hash, created_by, created_at, updated_at) VALUES (?1, ?2, 'dm', 'dm', 'private', ?3, ?4, ?5, ?5)")
|
||||
.bind(id)
|
||||
.bind(community.as_uuid().to_string())
|
||||
.bind(&participant_hash)
|
||||
.bind(vec![10_u8; 32])
|
||||
.bind(created_at)
|
||||
.execute(&pool)
|
||||
.await
|
||||
.unwrap();
|
||||
}
|
||||
sqlx::query("INSERT INTO channel_members (channel_id, pubkey, role, joined_at) VALUES (?1, ?2, 'member', 100), (?3, ?4, 'member', 101)")
|
||||
.bind(winner)
|
||||
.bind(vec![11_u8; 32])
|
||||
.bind(loser)
|
||||
.bind(vec![12_u8; 32])
|
||||
.execute(&pool)
|
||||
.await
|
||||
.unwrap();
|
||||
sqlx::query("UPDATE events SET channel_id = ?1 WHERE content = 'private orchard'")
|
||||
.bind(loser)
|
||||
.execute(&pool)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
for statement in [
|
||||
"DROP TRIGGER events_fts_insert",
|
||||
"DROP TRIGGER events_fts_delete",
|
||||
"DROP TRIGGER events_fts_update",
|
||||
"DROP TABLE events_fts",
|
||||
"DROP VIEW searchable_events",
|
||||
"DROP INDEX idx_channels_dm_hash",
|
||||
"CREATE VIRTUAL TABLE events_fts USING fts5(content, content='events', content_rowid='rowid', tokenize='unicode61')",
|
||||
"INSERT INTO events_fts(events_fts) VALUES ('rebuild')",
|
||||
"CREATE TRIGGER events_fts_insert AFTER INSERT ON events WHEN new.kind NOT IN (1059, 30300, 30350, 30622, 44100, 44101, 44200) BEGIN INSERT INTO events_fts(rowid, content) VALUES (new.rowid, new.content); END",
|
||||
@@ -2037,6 +2080,34 @@ mod tests {
|
||||
.unwrap(),
|
||||
1
|
||||
);
|
||||
assert_eq!(
|
||||
sqlx::query_scalar::<_, i64>("SELECT count(*) FROM channels WHERE community_id = ?1 AND participant_hash = ?2 AND channel_type = 'dm' AND deleted_at IS NULL")
|
||||
.bind(community.as_uuid().to_string())
|
||||
.bind(&participant_hash)
|
||||
.fetch_one(&upgraded)
|
||||
.await
|
||||
.unwrap(),
|
||||
1
|
||||
);
|
||||
assert_eq!(
|
||||
sqlx::query_scalar::<_, i64>(
|
||||
"SELECT count(*) FROM channel_members WHERE channel_id = ?1"
|
||||
)
|
||||
.bind(winner)
|
||||
.fetch_one(&upgraded)
|
||||
.await
|
||||
.unwrap(),
|
||||
2
|
||||
);
|
||||
assert_eq!(
|
||||
sqlx::query_scalar::<_, String>(
|
||||
"SELECT channel_id FROM events WHERE content = 'private orchard'"
|
||||
)
|
||||
.fetch_one(&upgraded)
|
||||
.await
|
||||
.unwrap(),
|
||||
winner
|
||||
);
|
||||
|
||||
for (id, kind, content) in [
|
||||
(vec![5_u8; 32], 45001_i64, "allowed trigger token"),
|
||||
|
||||
Reference in New Issue
Block a user