Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 11 additions & 2 deletions migrations/sqlite/2026-08-18-132346-0000_channel/down.sql
Original file line number Diff line number Diff line change
@@ -1,8 +1,17 @@
DROP TABLE IF EXISTS channel;
CREATE TABLE channel
PRAGMA foreign_keys = OFF;
BEGIN;

CREATE TABLE channel_temp
(
id VARCHAR(24) PRIMARY KEY NOT NULL,
name VARCHAR NOT NULL,
avatar VARCHAR NOT NULL,
verified BOOLEAN NOT NULL
);
INSERT INTO channel_temp
SELECT id, name, COALESCE(avatar, ''), verified FROM channel;
DROP TABLE channel;
ALTER TABLE channel_temp RENAME TO channel;

COMMIT;
PRAGMA foreign_keys = ON;
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
run_in_transaction = false
13 changes: 11 additions & 2 deletions migrations/sqlite/2026-08-18-132346-0000_channel/up.sql
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
-- make avatar url nullable
-- https://stackoverflow.com/questions/4007014/alter-column-in-sqlite
-- Rebuilding the parent table while foreign keys are enabled would execute the
-- ON DELETE actions and erase rows that reference channels. This migration runs
-- outside Diesel's transaction so the first PRAGMA takes effect, then wraps the
-- rebuild in its own transaction.
PRAGMA foreign_keys = OFF;
BEGIN;

-- Make avatar nullable.
CREATE TABLE channel_temp
(
id VARCHAR(24) PRIMARY KEY NOT NULL,
Expand All @@ -10,3 +16,6 @@ CREATE TABLE channel_temp
INSERT INTO channel_temp SELECT * FROM channel;
DROP TABLE channel;
ALTER TABLE channel_temp RENAME TO channel;

COMMIT;
PRAGMA foreign_keys = ON;
74 changes: 74 additions & 0 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -278,6 +278,36 @@ async fn run_migrations(pool: &DbPool, database_url: &str, approval: Option<&str
mod tests {
use super::require_migration_approval;

#[cfg(feature = "sqlite")]
#[derive(diesel::QueryableByName)]
struct RowCount {
#[diesel(sql_type = diesel::sql_types::BigInt)]
count: i64,
}

#[cfg(feature = "sqlite")]
fn table_count(conn: &mut diesel::SqliteConnection, table: &str) -> i64 {
use diesel::RunQueryDsl;

diesel::sql_query(format!("SELECT COUNT(*) AS count FROM {table}"))
.get_result::<RowCount>(conn)
.unwrap()
.count
}

#[cfg(feature = "sqlite")]
fn foreign_keys_enabled(conn: &mut diesel::SqliteConnection) -> bool {
use diesel::RunQueryDsl;

diesel::sql_query(
"SELECT COUNT(*) AS count FROM pragma_foreign_keys WHERE foreign_keys = 1",
)
.get_result::<RowCount>(conn)
.unwrap()
.count
== 1
}

#[test]
fn fresh_database_does_not_require_approval() {
assert!(require_migration_approval(false, &["20260721".to_owned()], None).is_ok());
Expand All @@ -290,4 +320,48 @@ mod tests {
assert!(require_migration_approval(true, &pending, Some("20260721")).is_err());
assert!(require_migration_approval(true, &pending, Some("20260721,20260722")).is_ok());
}

#[cfg(feature = "sqlite")]
#[test]
fn channel_migration_preserves_referencing_rows() {
use diesel::Connection;
use diesel::connection::SimpleConnection;
use diesel_migrations::MigrationHarness;

let mut conn = diesel::SqliteConnection::establish(":memory:").unwrap();
conn.batch_execute("PRAGMA foreign_keys = ON;").unwrap();

let migrations = conn.pending_migrations(super::MIGRATIONS).unwrap();
let channel_migration = migrations
.iter()
.position(|migration| migration.name().version().to_string() == "202608181323460000")
.expect("channel migration must be embedded");

conn.run_migrations(&migrations[..channel_migration])
.unwrap();
conn.batch_execute(
"INSERT INTO channel (id, name, avatar, verified) \
VALUES ('channel-1', 'Channel', 'https://example.test/avatar', false); \
INSERT INTO video (id, title, upload_date, uploader_id, thumbnail_url, duration) \
VALUES ('video-1', 'Video', 0, 'channel-1', \
'https://example.test/thumbnail', 60);",
)
.unwrap();

assert_eq!(table_count(&mut conn, "video"), 1);
conn.run_migration(migrations[channel_migration].as_ref())
.unwrap();
assert!(foreign_keys_enabled(&mut conn));
assert_eq!(table_count(&mut conn, "channel"), 1);
assert_eq!(table_count(&mut conn, "video"), 1);

conn.batch_execute("UPDATE channel SET avatar = NULL;")
.unwrap();
conn.revert_migration(migrations[channel_migration].as_ref())
.unwrap();
assert!(foreign_keys_enabled(&mut conn));
assert_eq!(table_count(&mut conn, "channel"), 1);
assert_eq!(table_count(&mut conn, "video"), 1);
assert_eq!(table_count(&mut conn, "pragma_foreign_key_check"), 0);
}
}