Skip to content

Commit 3de60f9

Browse files
committed
Add schema migrations and test them
1 parent 8b2fe24 commit 3de60f9

File tree

2 files changed

+123
-1
lines changed

2 files changed

+123
-1
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: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ use std::fs;
1111
use std::path::PathBuf;
1212
use std::sync::{Arc, Mutex};
1313

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

@@ -54,13 +56,23 @@ impl SqliteStore {
5456
io::Error::new(io::ErrorKind::Other, msg)
5557
})?;
5658

57-
connection.pragma(Some(rusqlite::DatabaseName::Main),
59+
let sql = format!("SELECT user_version FROM pragma_user_version");
60+
let version_res: u16 = connection.query_row(&sql, [], |row| row.get(0)).unwrap();
61+
62+
if version_res == 0 {
63+
// New database, set our SCHEMA_USER_VERSION and continue
64+
connection.pragma(Some(rusqlite::DatabaseName::Main),
5865
"user_version", SCHEMA_USER_VERSION, |_| {
5966
Ok(())
6067
}).map_err(|e| {
6168
let msg = format!("Failed to set PRAGMA user_version: {}", e);
6269
io::Error::new(io::ErrorKind::Other, msg)
6370
})?;
71+
} else if version_res < SCHEMA_USER_VERSION {
72+
migrations::migrate_schema(&connection, &kv_table_name, version_res, SCHEMA_USER_VERSION);
73+
} else if version_res > SCHEMA_USER_VERSION {
74+
panic!("Failed to open database: incompatible schema version {}. Expected: {}", version_res, SCHEMA_USER_VERSION);
75+
}
6476

6577
let sql = format!(
6678
"CREATE TABLE IF NOT EXISTS {} (

0 commit comments

Comments
 (0)