Skip to content

Commit 56d7566

Browse files
committed
f Add schema migrations and test them
1 parent 8640d12 commit 56d7566

File tree

2 files changed

+134
-1
lines changed

2 files changed

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

lightning-persister/src/sqlite_store/mod.rs

Lines changed: 16 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,26 @@ 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,
73+
SCHEMA_USER_VERSION)?;
74+
} else if version_res > SCHEMA_USER_VERSION {
75+
let msg = format!("Failed to open database: incompatible schema version {}. Expected: {}",
76+
version_res, SCHEMA_USER_VERSION);
77+
return Err(io::Error::new(io::ErrorKind::Other, msg));
78+
}
6479

6580
let sql = format!(
6681
"CREATE TABLE IF NOT EXISTS {} (

0 commit comments

Comments
 (0)