max / audiofiles
1 file changed,
+90 insertions,
-38 deletions
| @@ -134,7 +134,7 @@ ALTER TABLE audio_analysis ADD COLUMN classification TEXT; | |||
| 134 | 134 | "#; | |
| 135 | 135 | ||
| 136 | 136 | const MIGRATION_004: &str = r#" | |
| 137 | - | CREATE TABLE waveform_data ( | |
| 137 | + | CREATE TABLE IF NOT EXISTS waveform_data ( | |
| 138 | 138 | hash TEXT PRIMARY KEY REFERENCES samples(hash) ON DELETE CASCADE, | |
| 139 | 139 | num_buckets INTEGER NOT NULL, | |
| 140 | 140 | peak_data BLOB NOT NULL, | |
| @@ -142,17 +142,17 @@ CREATE TABLE waveform_data ( | |||
| 142 | 142 | duration REAL NOT NULL, | |
| 143 | 143 | generated_at INTEGER NOT NULL | |
| 144 | 144 | ); | |
| 145 | - | CREATE INDEX idx_analysis_duration ON audio_analysis(duration); | |
| 146 | - | CREATE INDEX idx_analysis_classification ON audio_analysis(classification); | |
| 147 | - | CREATE INDEX idx_samples_name ON samples(original_name); | |
| 145 | + | CREATE INDEX IF NOT EXISTS idx_analysis_duration ON audio_analysis(duration); | |
| 146 | + | CREATE INDEX IF NOT EXISTS idx_analysis_classification ON audio_analysis(classification); | |
| 147 | + | CREATE INDEX IF NOT EXISTS idx_samples_name ON samples(original_name); | |
| 148 | 148 | "#; | |
| 149 | 149 | ||
| 150 | 150 | const MIGRATION_005: &str = r#" | |
| 151 | - | CREATE TABLE user_config (key TEXT PRIMARY KEY, value TEXT NOT NULL); | |
| 151 | + | CREATE TABLE IF NOT EXISTS user_config (key TEXT PRIMARY KEY, value TEXT NOT NULL); | |
| 152 | 152 | "#; | |
| 153 | 153 | ||
| 154 | 154 | const MIGRATION_006: &str = r#" | |
| 155 | - | CREATE TABLE fingerprints ( | |
| 155 | + | CREATE TABLE IF NOT EXISTS fingerprints ( | |
| 156 | 156 | hash TEXT PRIMARY KEY REFERENCES samples(hash) ON DELETE CASCADE, | |
| 157 | 157 | envelope BLOB NOT NULL, | |
| 158 | 158 | sample_rate INTEGER NOT NULL, | |
| @@ -165,11 +165,11 @@ const MIGRATION_007: &str = r#" | |||
| 165 | 165 | ALTER TABLE vfs ADD COLUMN sync_files INTEGER NOT NULL DEFAULT 0; | |
| 166 | 166 | ||
| 167 | 167 | -- Sync metadata key-value store | |
| 168 | - | CREATE TABLE sync_state ( | |
| 168 | + | CREATE TABLE IF NOT EXISTS sync_state ( | |
| 169 | 169 | key TEXT PRIMARY KEY, | |
| 170 | 170 | value TEXT NOT NULL | |
| 171 | 171 | ); | |
| 172 | - | INSERT INTO sync_state (key, value) VALUES | |
| 172 | + | INSERT OR IGNORE INTO sync_state (key, value) VALUES | |
| 173 | 173 | ('device_id', ''), | |
| 174 | 174 | ('pull_cursor', ''), | |
| 175 | 175 | ('auto_sync_enabled', '0'), | |
| @@ -179,7 +179,7 @@ INSERT INTO sync_state (key, value) VALUES | |||
| 179 | 179 | ('initial_snapshot_done', '0'); | |
| 180 | 180 | ||
| 181 | 181 | -- Local change log for push/pull sync | |
| 182 | - | CREATE TABLE sync_changelog ( | |
| 182 | + | CREATE TABLE IF NOT EXISTS sync_changelog ( | |
| 183 | 183 | id INTEGER PRIMARY KEY AUTOINCREMENT, | |
| 184 | 184 | table_name TEXT NOT NULL, | |
| 185 | 185 | op TEXT NOT NULL, | |
| @@ -188,12 +188,12 @@ CREATE TABLE sync_changelog ( | |||
| 188 | 188 | data TEXT, | |
| 189 | 189 | pushed INTEGER NOT NULL DEFAULT 0 | |
| 190 | 190 | ); | |
| 191 | - | CREATE INDEX idx_changelog_pushed ON sync_changelog(pushed); | |
| 191 | + | CREATE INDEX IF NOT EXISTS idx_changelog_pushed ON sync_changelog(pushed); | |
| 192 | 192 | ||
| 193 | 193 | -- ── Triggers: record changes unless applying remote data ── | |
| 194 | 194 | ||
| 195 | 195 | -- samples | |
| 196 | - | CREATE TRIGGER sync_samples_insert AFTER INSERT ON samples | |
| 196 | + | CREATE TRIGGER IF NOT EXISTS sync_samples_insert AFTER INSERT ON samples | |
| 197 | 197 | WHEN (SELECT value FROM sync_state WHERE key = 'applying_remote') != '1' | |
| 198 | 198 | BEGIN | |
| 199 | 199 | INSERT INTO sync_changelog (table_name, op, row_id, data) | |
| @@ -203,7 +203,7 @@ BEGIN | |||
| 203 | 203 | 'import_date', NEW.import_date, 'last_modified', NEW.last_modified)); | |
| 204 | 204 | END; | |
| 205 | 205 | ||
| 206 | - | CREATE TRIGGER sync_samples_update AFTER UPDATE ON samples | |
| 206 | + | CREATE TRIGGER IF NOT EXISTS sync_samples_update AFTER UPDATE ON samples | |
| 207 | 207 | WHEN (SELECT value FROM sync_state WHERE key = 'applying_remote') != '1' | |
| 208 | 208 | BEGIN | |
| 209 | 209 | INSERT INTO sync_changelog (table_name, op, row_id, data) | |
| @@ -213,7 +213,7 @@ BEGIN | |||
| 213 | 213 | 'import_date', NEW.import_date, 'last_modified', NEW.last_modified)); | |
| 214 | 214 | END; | |
| 215 | 215 | ||
| 216 | - | CREATE TRIGGER sync_samples_delete AFTER DELETE ON samples | |
| 216 | + | CREATE TRIGGER IF NOT EXISTS sync_samples_delete AFTER DELETE ON samples | |
| 217 | 217 | WHEN (SELECT value FROM sync_state WHERE key = 'applying_remote') != '1' | |
| 218 | 218 | BEGIN | |
| 219 | 219 | INSERT INTO sync_changelog (table_name, op, row_id, data) | |
| @@ -221,7 +221,7 @@ BEGIN | |||
| 221 | 221 | END; | |
| 222 | 222 | ||
| 223 | 223 | -- audio_analysis | |
| 224 | - | CREATE TRIGGER sync_audio_analysis_insert AFTER INSERT ON audio_analysis | |
| 224 | + | CREATE TRIGGER IF NOT EXISTS sync_audio_analysis_insert AFTER INSERT ON audio_analysis | |
| 225 | 225 | WHEN (SELECT value FROM sync_state WHERE key = 'applying_remote') != '1' | |
| 226 | 226 | BEGIN | |
| 227 | 227 | INSERT INTO sync_changelog (table_name, op, row_id, data) | |
| @@ -235,7 +235,7 @@ BEGIN | |||
| 235 | 235 | 'zero_crossing_rate', NEW.zero_crossing_rate, 'classification', NEW.classification)); | |
| 236 | 236 | END; | |
| 237 | 237 | ||
| 238 | - | CREATE TRIGGER sync_audio_analysis_update AFTER UPDATE ON audio_analysis | |
| 238 | + | CREATE TRIGGER IF NOT EXISTS sync_audio_analysis_update AFTER UPDATE ON audio_analysis | |
| 239 | 239 | WHEN (SELECT value FROM sync_state WHERE key = 'applying_remote') != '1' | |
| 240 | 240 | BEGIN | |
| 241 | 241 | INSERT INTO sync_changelog (table_name, op, row_id, data) | |
| @@ -249,7 +249,7 @@ BEGIN | |||
| 249 | 249 | 'zero_crossing_rate', NEW.zero_crossing_rate, 'classification', NEW.classification)); | |
| 250 | 250 | END; | |
| 251 | 251 | ||
| 252 | - | CREATE TRIGGER sync_audio_analysis_delete AFTER DELETE ON audio_analysis | |
| 252 | + | CREATE TRIGGER IF NOT EXISTS sync_audio_analysis_delete AFTER DELETE ON audio_analysis | |
| 253 | 253 | WHEN (SELECT value FROM sync_state WHERE key = 'applying_remote') != '1' | |
| 254 | 254 | BEGIN | |
| 255 | 255 | INSERT INTO sync_changelog (table_name, op, row_id, data) | |
| @@ -257,7 +257,7 @@ BEGIN | |||
| 257 | 257 | END; | |
| 258 | 258 | ||
| 259 | 259 | -- vfs | |
| 260 | - | CREATE TRIGGER sync_vfs_insert AFTER INSERT ON vfs | |
| 260 | + | CREATE TRIGGER IF NOT EXISTS sync_vfs_insert AFTER INSERT ON vfs | |
| 261 | 261 | WHEN (SELECT value FROM sync_state WHERE key = 'applying_remote') != '1' | |
| 262 | 262 | BEGIN | |
| 263 | 263 | INSERT INTO sync_changelog (table_name, op, row_id, data) | |
| @@ -267,7 +267,7 @@ BEGIN | |||
| 267 | 267 | 'sync_files', NEW.sync_files)); | |
| 268 | 268 | END; | |
| 269 | 269 | ||
| 270 | - | CREATE TRIGGER sync_vfs_update AFTER UPDATE ON vfs | |
| 270 | + | CREATE TRIGGER IF NOT EXISTS sync_vfs_update AFTER UPDATE ON vfs | |
| 271 | 271 | WHEN (SELECT value FROM sync_state WHERE key = 'applying_remote') != '1' | |
| 272 | 272 | BEGIN | |
| 273 | 273 | INSERT INTO sync_changelog (table_name, op, row_id, data) | |
| @@ -277,7 +277,7 @@ BEGIN | |||
| 277 | 277 | 'sync_files', NEW.sync_files)); | |
| 278 | 278 | END; | |
| 279 | 279 | ||
| 280 | - | CREATE TRIGGER sync_vfs_delete AFTER DELETE ON vfs | |
| 280 | + | CREATE TRIGGER IF NOT EXISTS sync_vfs_delete AFTER DELETE ON vfs | |
| 281 | 281 | WHEN (SELECT value FROM sync_state WHERE key = 'applying_remote') != '1' | |
| 282 | 282 | BEGIN | |
| 283 | 283 | INSERT INTO sync_changelog (table_name, op, row_id, data) | |
| @@ -285,7 +285,7 @@ BEGIN | |||
| 285 | 285 | END; | |
| 286 | 286 | ||
| 287 | 287 | -- vfs_nodes | |
| 288 | - | CREATE TRIGGER sync_vfs_nodes_insert AFTER INSERT ON vfs_nodes | |
| 288 | + | CREATE TRIGGER IF NOT EXISTS sync_vfs_nodes_insert AFTER INSERT ON vfs_nodes | |
| 289 | 289 | WHEN (SELECT value FROM sync_state WHERE key = 'applying_remote') != '1' | |
| 290 | 290 | BEGIN | |
| 291 | 291 | INSERT INTO sync_changelog (table_name, op, row_id, data) | |
| @@ -295,7 +295,7 @@ BEGIN | |||
| 295 | 295 | 'sample_hash', NEW.sample_hash, 'created_at', NEW.created_at)); | |
| 296 | 296 | END; | |
| 297 | 297 | ||
| 298 | - | CREATE TRIGGER sync_vfs_nodes_update AFTER UPDATE ON vfs_nodes | |
| 298 | + | CREATE TRIGGER IF NOT EXISTS sync_vfs_nodes_update AFTER UPDATE ON vfs_nodes | |
| 299 | 299 | WHEN (SELECT value FROM sync_state WHERE key = 'applying_remote') != '1' | |
| 300 | 300 | BEGIN | |
| 301 | 301 | INSERT INTO sync_changelog (table_name, op, row_id, data) | |
| @@ -305,7 +305,7 @@ BEGIN | |||
| 305 | 305 | 'sample_hash', NEW.sample_hash, 'created_at', NEW.created_at)); | |
| 306 | 306 | END; | |
| 307 | 307 | ||
| 308 | - | CREATE TRIGGER sync_vfs_nodes_delete AFTER DELETE ON vfs_nodes | |
| 308 | + | CREATE TRIGGER IF NOT EXISTS sync_vfs_nodes_delete AFTER DELETE ON vfs_nodes | |
| 309 | 309 | WHEN (SELECT value FROM sync_state WHERE key = 'applying_remote') != '1' | |
| 310 | 310 | BEGIN | |
| 311 | 311 | INSERT INTO sync_changelog (table_name, op, row_id, data) | |
| @@ -313,7 +313,7 @@ BEGIN | |||
| 313 | 313 | END; | |
| 314 | 314 | ||
| 315 | 315 | -- tags | |
| 316 | - | CREATE TRIGGER sync_tags_insert AFTER INSERT ON tags | |
| 316 | + | CREATE TRIGGER IF NOT EXISTS sync_tags_insert AFTER INSERT ON tags | |
| 317 | 317 | WHEN (SELECT value FROM sync_state WHERE key = 'applying_remote') != '1' | |
| 318 | 318 | BEGIN | |
| 319 | 319 | INSERT INTO sync_changelog (table_name, op, row_id, data) | |
| @@ -321,7 +321,7 @@ BEGIN | |||
| 321 | 321 | json_object('sample_hash', NEW.sample_hash, 'tag', NEW.tag)); | |
| 322 | 322 | END; | |
| 323 | 323 | ||
| 324 | - | CREATE TRIGGER sync_tags_delete AFTER DELETE ON tags | |
| 324 | + | CREATE TRIGGER IF NOT EXISTS sync_tags_delete AFTER DELETE ON tags | |
| 325 | 325 | WHEN (SELECT value FROM sync_state WHERE key = 'applying_remote') != '1' | |
| 326 | 326 | BEGIN | |
| 327 | 327 | INSERT INTO sync_changelog (table_name, op, row_id, data) | |
| @@ -329,7 +329,7 @@ BEGIN | |||
| 329 | 329 | END; | |
| 330 | 330 | ||
| 331 | 331 | -- collections | |
| 332 | - | CREATE TRIGGER sync_collections_insert AFTER INSERT ON collections | |
| 332 | + | CREATE TRIGGER IF NOT EXISTS sync_collections_insert AFTER INSERT ON collections | |
| 333 | 333 | WHEN (SELECT value FROM sync_state WHERE key = 'applying_remote') != '1' | |
| 334 | 334 | BEGIN | |
| 335 | 335 | INSERT INTO sync_changelog (table_name, op, row_id, data) | |
| @@ -338,7 +338,7 @@ BEGIN | |||
| 338 | 338 | 'description', NEW.description, 'created_at', NEW.created_at)); | |
| 339 | 339 | END; | |
| 340 | 340 | ||
| 341 | - | CREATE TRIGGER sync_collections_update AFTER UPDATE ON collections | |
| 341 | + | CREATE TRIGGER IF NOT EXISTS sync_collections_update AFTER UPDATE ON collections | |
| 342 | 342 | WHEN (SELECT value FROM sync_state WHERE key = 'applying_remote') != '1' | |
| 343 | 343 | BEGIN | |
| 344 | 344 | INSERT INTO sync_changelog (table_name, op, row_id, data) | |
| @@ -347,7 +347,7 @@ BEGIN | |||
| 347 | 347 | 'description', NEW.description, 'created_at', NEW.created_at)); | |
| 348 | 348 | END; | |
| 349 | 349 | ||
| 350 | - | CREATE TRIGGER sync_collections_delete AFTER DELETE ON collections | |
| 350 | + | CREATE TRIGGER IF NOT EXISTS sync_collections_delete AFTER DELETE ON collections | |
| 351 | 351 | WHEN (SELECT value FROM sync_state WHERE key = 'applying_remote') != '1' | |
| 352 | 352 | BEGIN | |
| 353 | 353 | INSERT INTO sync_changelog (table_name, op, row_id, data) | |
| @@ -355,7 +355,7 @@ BEGIN | |||
| 355 | 355 | END; | |
| 356 | 356 | ||
| 357 | 357 | -- collection_members | |
| 358 | - | CREATE TRIGGER sync_collection_members_insert AFTER INSERT ON collection_members | |
| 358 | + | CREATE TRIGGER IF NOT EXISTS sync_collection_members_insert AFTER INSERT ON collection_members | |
| 359 | 359 | WHEN (SELECT value FROM sync_state WHERE key = 'applying_remote') != '1' | |
| 360 | 360 | BEGIN | |
| 361 | 361 | INSERT INTO sync_changelog (table_name, op, row_id, data) | |
| @@ -365,7 +365,7 @@ BEGIN | |||
| 365 | 365 | 'added_at', NEW.added_at)); | |
| 366 | 366 | END; | |
| 367 | 367 | ||
| 368 | - | CREATE TRIGGER sync_collection_members_delete AFTER DELETE ON collection_members | |
| 368 | + | CREATE TRIGGER IF NOT EXISTS sync_collection_members_delete AFTER DELETE ON collection_members | |
| 369 | 369 | WHEN (SELECT value FROM sync_state WHERE key = 'applying_remote') != '1' | |
| 370 | 370 | BEGIN | |
| 371 | 371 | INSERT INTO sync_changelog (table_name, op, row_id, data) | |
| @@ -374,7 +374,7 @@ BEGIN | |||
| 374 | 374 | END; | |
| 375 | 375 | ||
| 376 | 376 | -- smart_folders | |
| 377 | - | CREATE TRIGGER sync_smart_folders_insert AFTER INSERT ON smart_folders | |
| 377 | + | CREATE TRIGGER IF NOT EXISTS sync_smart_folders_insert AFTER INSERT ON smart_folders | |
| 378 | 378 | WHEN (SELECT value FROM sync_state WHERE key = 'applying_remote') != '1' | |
| 379 | 379 | BEGIN | |
| 380 | 380 | INSERT INTO sync_changelog (table_name, op, row_id, data) | |
| @@ -383,7 +383,7 @@ BEGIN | |||
| 383 | 383 | 'query_json', NEW.query_json, 'created_at', NEW.created_at)); | |
| 384 | 384 | END; | |
| 385 | 385 | ||
| 386 | - | CREATE TRIGGER sync_smart_folders_update AFTER UPDATE ON smart_folders | |
| 386 | + | CREATE TRIGGER IF NOT EXISTS sync_smart_folders_update AFTER UPDATE ON smart_folders | |
| 387 | 387 | WHEN (SELECT value FROM sync_state WHERE key = 'applying_remote') != '1' | |
| 388 | 388 | BEGIN | |
| 389 | 389 | INSERT INTO sync_changelog (table_name, op, row_id, data) | |
| @@ -392,7 +392,7 @@ BEGIN | |||
| 392 | 392 | 'query_json', NEW.query_json, 'created_at', NEW.created_at)); | |
| 393 | 393 | END; | |
| 394 | 394 | ||
| 395 | - | CREATE TRIGGER sync_smart_folders_delete AFTER DELETE ON smart_folders | |
| 395 | + | CREATE TRIGGER IF NOT EXISTS sync_smart_folders_delete AFTER DELETE ON smart_folders | |
| 396 | 396 | WHEN (SELECT value FROM sync_state WHERE key = 'applying_remote') != '1' | |
| 397 | 397 | BEGIN | |
| 398 | 398 | INSERT INTO sync_changelog (table_name, op, row_id, data) | |
| @@ -400,7 +400,7 @@ BEGIN | |||
| 400 | 400 | END; | |
| 401 | 401 | ||
| 402 | 402 | -- user_config (exclude sync-internal keys) | |
| 403 | - | CREATE TRIGGER sync_user_config_insert AFTER INSERT ON user_config | |
| 403 | + | CREATE TRIGGER IF NOT EXISTS sync_user_config_insert AFTER INSERT ON user_config | |
| 404 | 404 | WHEN (SELECT value FROM sync_state WHERE key = 'applying_remote') != '1' | |
| 405 | 405 | AND NEW.key NOT LIKE 'sync_%' | |
| 406 | 406 | BEGIN | |
| @@ -409,7 +409,7 @@ BEGIN | |||
| 409 | 409 | json_object('key', NEW.key, 'value', NEW.value)); | |
| 410 | 410 | END; | |
| 411 | 411 | ||
| 412 | - | CREATE TRIGGER sync_user_config_update AFTER UPDATE ON user_config | |
| 412 | + | CREATE TRIGGER IF NOT EXISTS sync_user_config_update AFTER UPDATE ON user_config | |
| 413 | 413 | WHEN (SELECT value FROM sync_state WHERE key = 'applying_remote') != '1' | |
| 414 | 414 | AND NEW.key NOT LIKE 'sync_%' | |
| 415 | 415 | BEGIN | |
| @@ -418,7 +418,7 @@ BEGIN | |||
| 418 | 418 | json_object('key', NEW.key, 'value', NEW.value)); | |
| 419 | 419 | END; | |
| 420 | 420 | ||
| 421 | - | CREATE TRIGGER sync_user_config_delete AFTER DELETE ON user_config | |
| 421 | + | CREATE TRIGGER IF NOT EXISTS sync_user_config_delete AFTER DELETE ON user_config | |
| 422 | 422 | WHEN (SELECT value FROM sync_state WHERE key = 'applying_remote') != '1' | |
| 423 | 423 | AND OLD.key NOT LIKE 'sync_%' | |
| 424 | 424 | BEGIN | |
| @@ -586,11 +586,11 @@ CREATE TABLE IF NOT EXISTS edit_history ( | |||
| 586 | 586 | params_json TEXT, | |
| 587 | 587 | created_at INTEGER NOT NULL DEFAULT (unixepoch()) | |
| 588 | 588 | ); | |
| 589 | - | CREATE INDEX idx_edit_history_source ON edit_history(source_hash); | |
| 590 | - | CREATE INDEX idx_edit_history_result ON edit_history(result_hash); | |
| 589 | + | CREATE INDEX IF NOT EXISTS idx_edit_history_source ON edit_history(source_hash); | |
| 590 | + | CREATE INDEX IF NOT EXISTS idx_edit_history_result ON edit_history(result_hash); | |
| 591 | 591 | ||
| 592 | 592 | -- Sync trigger for edit_history | |
| 593 | - | CREATE TRIGGER sync_edit_history_insert AFTER INSERT ON edit_history | |
| 593 | + | CREATE TRIGGER IF NOT EXISTS sync_edit_history_insert AFTER INSERT ON edit_history | |
| 594 | 594 | WHEN (SELECT value FROM sync_state WHERE key = 'applying_remote') != '1' | |
| 595 | 595 | BEGIN | |
| 596 | 596 | INSERT INTO sync_changelog (table_name, op, row_id, data) | |
| @@ -952,6 +952,58 @@ mod tests { | |||
| 952 | 952 | assert_eq!(version, 17); | |
| 953 | 953 | } | |
| 954 | 954 | ||
| 955 | + | /// Open a fresh file-backed DB, close, reopen. The second open re-enters | |
| 956 | + | /// `migrate()`; with `user_version=17` no migration body runs, but the | |
| 957 | + | /// shape verifies our open/close cycle is clean (no locks, no WAL leak). | |
| 958 | + | #[test] | |
| 959 | + | fn migration_replay_from_file_no_op() { | |
| 960 | + | let dir = tempfile::tempdir().unwrap(); | |
| 961 | + | let path = dir.path().join("audiofiles.db"); | |
| 962 | + | ||
| 963 | + | let db = Database::open(&path).unwrap(); | |
| 964 | + | drop(db); | |
| 965 | + | ||
| 966 | + | let db = Database::open(&path).unwrap(); | |
| 967 | + | let version: i32 = db | |
| 968 | + | .conn() | |
| 969 | + | .query_row("PRAGMA user_version", [], |row| row.get(0)) | |
| 970 | + | .unwrap(); | |
| 971 | + | assert_eq!(version, 17); | |
| 972 | + | } | |
| 973 | + | ||
| 974 | + | /// Simulates the worst-case recovery path: a prior partial migration left | |
| 975 | + | /// every object in place but `user_version` rolled back. Re-running | |
| 976 | + | /// `migrate()` against the pre-populated schema must succeed without | |
| 977 | + | /// duplicate-object errors. This catches the "silent failure → bump | |
| 978 | + | /// user_version" bug class for every NEW migration we add. | |
| 979 | + | /// | |
| 980 | + | /// We roll back to version 2 (not 0): M001 is the initial schema and | |
| 981 | + | /// M002 is a `DROP TABLE tags; ALTER tags_v2 RENAME TO tags` rebuild | |
| 982 | + | /// dance — neither is replay-safe by construction, and neither needs to | |
| 983 | + | /// be (both ship to fresh DBs exactly once). Every migration from M003 | |
| 984 | + | /// onward MUST be replay-safe; if you add a new one that isn't, this | |
| 985 | + | /// test fails and you should add `IF NOT EXISTS` / `DROP IF EXISTS` / | |
| 986 | + | /// `INSERT OR IGNORE` accordingly. | |
| 987 | + | #[test] | |
| 988 | + | fn migration_replay_from_version_two_against_full_schema() { | |
| 989 | + | let dir = tempfile::tempdir().unwrap(); | |
| 990 | + | let path = dir.path().join("audiofiles.db"); | |
| 991 | + | ||
| 992 | + | Database::open(&path).unwrap(); | |
| 993 | + | ||
| 994 | + | { | |
| 995 | + | let conn = Connection::open(&path).unwrap(); | |
| 996 | + | conn.execute_batch("PRAGMA user_version = 2").unwrap(); | |
| 997 | + | } | |
| 998 | + | ||
| 999 | + | let db = Database::open(&path).unwrap(); | |
| 1000 | + | let version: i32 = db | |
| 1001 | + | .conn() | |
| 1002 | + | .query_row("PRAGMA user_version", [], |row| row.get(0)) | |
| 1003 | + | .unwrap(); | |
| 1004 | + | assert_eq!(version, 17); | |
| 1005 | + | } | |
| 1006 | + | ||
| 955 | 1007 | #[test] | |
| 956 | 1008 | fn foreign_keys_enforced() { | |
| 957 | 1009 | let db = Database::open_in_memory().unwrap(); |