Skip to content

Commit 50edf07

Browse files
committed
Add schema migrations and test them
1 parent a90ba7f commit 50edf07

File tree

2 files changed

+126
-4
lines changed

2 files changed

+126
-4
lines changed
Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
use rusqlite::Connection;
2+
3+
pub(super) fn migrate_schema(connection: &Connection, kv_table_name: &str, from_version: u16, to_version: u16) {
4+
assert!(from_version < to_version);
5+
if from_version == 1 && to_version == 2 {
6+
let sql = format!(
7+
"ALTER TABLE {}
8+
ADD sub_namespace TEXT DEFAULT \"\" NOT NULL;",
9+
kv_table_name);
10+
connection
11+
.execute(&sql, [])
12+
.unwrap_or_else(|e|
13+
panic!("Failed to migrate table {} from user_version {} to {}: {}",
14+
kv_table_name, from_version, to_version, e));
15+
connection
16+
.pragma(Some(rusqlite::DatabaseName::Main), "user_version", to_version, |_| { Ok(()) })
17+
.unwrap_or_else(|e| panic!("Failed to upgrade user_version from {} to {}: {}",
18+
from_version, to_version, e));
19+
}
20+
}
21+
22+
#[cfg(test)]
23+
mod tests {
24+
use crate::sqlite_store::SqliteStore;
25+
use crate::test_utils::do_read_write_remove_list_persist;
26+
27+
use lightning::util::persist::KVStore;
28+
29+
use rusqlite::{named_params, Connection};
30+
31+
use std::fs;
32+
33+
#[test]
34+
fn rwrl_post_schema_1_migration() {
35+
let old_schema_version = 1;
36+
37+
let mut temp_path = std::env::temp_dir();
38+
temp_path.push("rwrl_post_schema_1_migration");
39+
40+
let db_file_name = "test_db".to_string();
41+
let kv_table_name = "test_table".to_string();
42+
43+
let test_namespace = "testspace".to_string();
44+
let test_key = "testkey".to_string();
45+
let test_data = [42u8; 32];
46+
47+
{
48+
// We create a database with a SCHEMA_VERSION 1 table
49+
fs::create_dir_all(temp_path.clone()).unwrap();
50+
let mut db_file_path = temp_path.clone();
51+
db_file_path.push(db_file_name.clone());
52+
53+
let connection = Connection::open(db_file_path.clone()).unwrap();
54+
55+
connection
56+
.pragma(Some(rusqlite::DatabaseName::Main), "user_version", old_schema_version, |_| {
57+
Ok(())
58+
}).unwrap();
59+
60+
let sql = format!(
61+
"CREATE TABLE IF NOT EXISTS {} (
62+
namespace TEXT NOT NULL,
63+
key TEXT NOT NULL CHECK (key <> ''),
64+
value BLOB, PRIMARY KEY ( namespace, key )
65+
);",
66+
kv_table_name
67+
);
68+
69+
connection.execute(&sql, []).unwrap();
70+
71+
// We write some data to to the table
72+
let sql = format!(
73+
"INSERT OR REPLACE INTO {} (namespace, key, value) VALUES (:namespace, :key, :value);",
74+
kv_table_name
75+
);
76+
let mut stmt = connection.prepare_cached(&sql).unwrap();
77+
78+
stmt.execute(
79+
named_params! {
80+
":namespace": test_namespace,
81+
":key": test_key,
82+
":value": test_data,
83+
}).unwrap();
84+
85+
// We read the just written data back to assert it happened.
86+
let sql = format!("SELECT value FROM {} WHERE namespace=:namespace AND key=:key;",
87+
kv_table_name);
88+
let mut stmt = connection.prepare_cached(&sql).unwrap();
89+
90+
let res: Vec<u8> = stmt
91+
.query_row(
92+
named_params! {
93+
":namespace": test_namespace,
94+
":key": test_key,
95+
},
96+
|row| row.get(0),
97+
).unwrap();
98+
99+
assert_eq!(res, test_data);
100+
}
101+
102+
// Check we migrate the db just fine without losing our written data.
103+
let store = SqliteStore::new(temp_path, Some(db_file_name), Some(kv_table_name));
104+
let res = store.read(&test_namespace, "", &test_key).unwrap();
105+
assert_eq!(res, test_data);
106+
107+
// Check we can continue to use the store just fine.
108+
do_read_write_remove_list_persist(&store);
109+
}
110+
}

lightning-persister/src/sqlite_store/mod.rs

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ use std::fs;
1010
use std::path::PathBuf;
1111
use std::sync::{Arc, Mutex};
1212

13+
mod migrations;
14+
1315
/// The default database file name.
1416
pub const DEFAULT_SQLITE_DB_FILE_NAME: &str = "ldk_data.sqlite";
1517

@@ -49,11 +51,21 @@ impl SqliteStore {
4951
panic!("Failed to open/create database file: {}", db_file_path.display())
5052
});
5153

52-
connection
53-
.pragma(Some(rusqlite::DatabaseName::Main), "user_version", SCHEMA_USER_VERSION, |_| {
54-
Ok(())
55-
})
54+
let sql = format!("SELECT user_version FROM pragma_user_version");
55+
let version_res: u16 = connection.query_row(&sql, [], |row| row.get(0)).unwrap();
56+
57+
if version_res == 0 {
58+
// New database, set our SCHEMA_USER_VERSION and continue
59+
connection
60+
.pragma(Some(rusqlite::DatabaseName::Main), "user_version", SCHEMA_USER_VERSION, |_| {
61+
Ok(())
62+
})
5663
.unwrap_or_else(|_| panic!("Failed to set PRAGMA user_version"));
64+
} else if version_res < SCHEMA_USER_VERSION {
65+
migrations::migrate_schema(&connection, &kv_table_name, version_res, SCHEMA_USER_VERSION);
66+
} else if version_res > SCHEMA_USER_VERSION {
67+
panic!("Failed to open database: incompatible schema version {}. Expected: {}", version_res, SCHEMA_USER_VERSION);
68+
}
5769

5870
let sql = format!(
5971
"CREATE TABLE IF NOT EXISTS {} (

0 commit comments

Comments
 (0)