Skip to content

Commit c7cb7ef

Browse files
authored
Merge pull request #945 from benthecarman/migrate-dbs
Implement `MigratableKVStore` for all KV Stores
2 parents e3e7960 + 42276a1 commit c7cb7ef

8 files changed

Lines changed: 622 additions & 26 deletions

File tree

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,7 @@ winapi = { version = "0.3", features = ["winbase"] }
9292

9393
[dev-dependencies]
9494
lightning = { git = "https://github.com/lightningdevkit/rust-lightning", rev = "3dfcc4cca1866c5e5d4d4eaf3b82e09584e2ce5c", features = ["std", "_test_utils"] }
95+
lightning-persister = { git = "https://github.com/lightningdevkit/rust-lightning", rev = "3dfcc4cca1866c5e5d4d4eaf3b82e09584e2ce5c", features = ["tokio"] }
9596
rand = { version = "0.9.2", default-features = false, features = ["std", "thread_rng", "os_rng"] }
9697
proptest = "1.0.0"
9798
regex = "1.5.6"

src/io/in_memory_store.rs

Lines changed: 60 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,9 @@ use std::sync::atomic::{AtomicU64, Ordering};
1111
use std::sync::Mutex;
1212

1313
use lightning::io;
14-
use lightning::util::persist::{KVStore, PageToken, PaginatedKVStore, PaginatedListResponse};
14+
use lightning::util::persist::{
15+
KVStore, MigratableKVStore, PageToken, PaginatedKVStore, PaginatedListResponse,
16+
};
1517

1618
const IN_MEMORY_PAGE_SIZE: usize = 50;
1719

@@ -96,6 +98,28 @@ impl InMemoryStore {
9698
hash_map::Entry::Vacant(_) => Ok(Vec::new()),
9799
}
98100
}
101+
102+
fn list_all_keys_internal(&self) -> io::Result<Vec<(String, String, String)>> {
103+
let persisted_lock = self.persisted_bytes.lock().unwrap();
104+
let capacity = persisted_lock.values().map(|entries| entries.len()).sum();
105+
let mut keys = Vec::with_capacity(capacity);
106+
107+
for (prefixed_namespace, namespace_entries) in persisted_lock.iter() {
108+
let (primary_namespace, secondary_namespace) =
109+
prefixed_namespace.split_once('/').ok_or_else(|| {
110+
io::Error::new(io::ErrorKind::InvalidData, "Invalid namespace format")
111+
})?;
112+
for key in namespace_entries.keys() {
113+
keys.push((
114+
primary_namespace.to_string(),
115+
secondary_namespace.to_string(),
116+
key.clone(),
117+
));
118+
}
119+
}
120+
121+
Ok(keys)
122+
}
99123
}
100124

101125
impl KVStore for InMemoryStore {
@@ -187,5 +211,40 @@ impl PaginatedKVStore for InMemoryStore {
187211
}
188212
}
189213

214+
impl MigratableKVStore for InMemoryStore {
215+
fn list_all_keys(
216+
&self,
217+
) -> impl Future<Output = Result<Vec<(String, String, String)>, io::Error>> + 'static + Send {
218+
let res = self.list_all_keys_internal();
219+
async move { res }
220+
}
221+
}
222+
190223
unsafe impl Sync for InMemoryStore {}
191224
unsafe impl Send for InMemoryStore {}
225+
226+
#[cfg(test)]
227+
mod tests {
228+
use super::*;
229+
230+
#[tokio::test]
231+
async fn in_memory_store_list_all_keys() {
232+
let store = InMemoryStore::new();
233+
234+
KVStore::write(&store, "ns_a", "sub_a", "key_a", vec![1u8]).await.unwrap();
235+
KVStore::write(&store, "ns_a", "sub_b", "key_b", vec![2u8]).await.unwrap();
236+
KVStore::write(&store, "ns_b", "", "key_c", vec![3u8]).await.unwrap();
237+
238+
let mut keys = MigratableKVStore::list_all_keys(&store).await.unwrap();
239+
keys.sort();
240+
241+
assert_eq!(
242+
keys,
243+
vec![
244+
("ns_a".to_string(), "sub_a".to_string(), "key_a".to_string()),
245+
("ns_a".to_string(), "sub_b".to_string(), "key_b".to_string()),
246+
("ns_b".to_string(), "".to_string(), "key_c".to_string()),
247+
]
248+
);
249+
}
250+
}

src/io/postgres_store/mod.rs

Lines changed: 63 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,9 @@ use std::sync::atomic::{AtomicU64, Ordering};
1212
use std::sync::{Arc, Mutex};
1313

1414
use lightning::io;
15-
use lightning::util::persist::{KVStore, PageToken, PaginatedKVStore, PaginatedListResponse};
15+
use lightning::util::persist::{
16+
KVStore, MigratableKVStore, PageToken, PaginatedKVStore, PaginatedListResponse,
17+
};
1618
use lightning_types::string::PrintableString;
1719
use native_tls::TlsConnector;
1820
use postgres_native_tls::MakeTlsConnector;
@@ -351,6 +353,24 @@ impl PaginatedKVStore for PostgresStore {
351353
}
352354
}
353355

356+
impl MigratableKVStore for PostgresStore {
357+
fn list_all_keys(
358+
&self,
359+
) -> impl Future<Output = Result<Vec<(String, String, String)>, io::Error>> + 'static + Send {
360+
let inner = Arc::clone(&self.inner);
361+
let runtime = self.internal_runtime();
362+
async move {
363+
let task = runtime.spawn(async move { inner.list_all_keys_internal().await });
364+
task.await.map_err(|e| {
365+
io::Error::new(
366+
io::ErrorKind::Other,
367+
format!("PostgreSQL runtime task failed: {}", e),
368+
)
369+
})?
370+
}
371+
}
372+
}
373+
354374
struct PostgresStoreInner {
355375
pool: SmallPool,
356376
config: Config,
@@ -725,6 +745,25 @@ impl PostgresStoreInner {
725745
Ok(keys)
726746
}
727747

748+
async fn list_all_keys_internal(&self) -> io::Result<Vec<(String, String, String)>> {
749+
let sql = format!(
750+
"SELECT primary_namespace, secondary_namespace, key FROM {}",
751+
self.kv_table_name_sql
752+
);
753+
754+
let err_map = |e: PgError| {
755+
let msg = format!("Failed to retrieve queried rows: {e}");
756+
io::Error::new(io::ErrorKind::Other, msg)
757+
};
758+
759+
let mut locked = self.locked_client().await?;
760+
let rows = query_with_retry!(self, locked, err_map, locked.query(sql.as_str(), &[]))?;
761+
762+
let keys: Vec<(String, String, String)> =
763+
rows.iter().map(|row| (row.get(0), row.get(1), row.get(2))).collect();
764+
Ok(keys)
765+
}
766+
728767
async fn list_paginated_internal(
729768
&self, primary_namespace: &str, secondary_namespace: &str, page_token: Option<PageToken>,
730769
) -> io::Result<PaginatedListResponse> {
@@ -904,6 +943,29 @@ mod tests {
904943
cleanup_store(&store_1).await;
905944
}
906945

946+
#[tokio::test(flavor = "multi_thread")]
947+
async fn test_postgres_store_list_all_keys() {
948+
let store = create_test_store("test_pg_list_all_keys").await;
949+
950+
KVStore::write(&store, "ns_a", "sub_a", "key_a", vec![1u8]).await.unwrap();
951+
KVStore::write(&store, "ns_a", "sub_b", "key_b", vec![2u8]).await.unwrap();
952+
KVStore::write(&store, "ns_b", "", "key_c", vec![3u8]).await.unwrap();
953+
954+
let mut keys = MigratableKVStore::list_all_keys(&store).await.unwrap();
955+
keys.sort();
956+
957+
assert_eq!(
958+
keys,
959+
vec![
960+
("ns_a".to_string(), "sub_a".to_string(), "key_a".to_string()),
961+
("ns_a".to_string(), "sub_b".to_string(), "key_b".to_string()),
962+
("ns_b".to_string(), "".to_string(), "key_c".to_string()),
963+
]
964+
);
965+
966+
cleanup_store(&store).await;
967+
}
968+
907969
async fn kill_connection(store: &PostgresStore) {
908970
// Terminate every backend in the pool so the next op deterministically
909971
// hits a closed connection regardless of which slot `get` selects.

src/io/sqlite_store/mod.rs

Lines changed: 82 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,9 @@ use std::sync::atomic::{AtomicI64, AtomicU64, Ordering};
1414
use std::sync::{Arc, Mutex};
1515

1616
use lightning::io;
17-
use lightning::util::persist::{KVStore, PageToken, PaginatedKVStore, PaginatedListResponse};
17+
use lightning::util::persist::{
18+
KVStore, MigratableKVStore, PageToken, PaginatedKVStore, PaginatedListResponse,
19+
};
1820
use lightning_types::string::PrintableString;
1921
use rusqlite::{named_params, Connection};
2022

@@ -202,6 +204,21 @@ impl PaginatedKVStore for SqliteStore {
202204
}
203205
}
204206

207+
impl MigratableKVStore for SqliteStore {
208+
fn list_all_keys(
209+
&self,
210+
) -> impl Future<Output = Result<Vec<(String, String, String)>, io::Error>> + 'static + Send {
211+
let inner = Arc::clone(&self.inner);
212+
let fut = tokio::task::spawn_blocking(move || inner.list_all_keys_internal());
213+
async move {
214+
fut.await.unwrap_or_else(|e| {
215+
let msg = format!("Failed to IO operation due join error: {}", e);
216+
Err(io::Error::new(io::ErrorKind::Other, msg))
217+
})
218+
}
219+
}
220+
}
221+
205222
struct SqliteStoreInner {
206223
connection: Arc<Mutex<Connection>>,
207224
data_dir: PathBuf,
@@ -486,6 +503,42 @@ impl SqliteStoreInner {
486503
Ok(keys)
487504
}
488505

506+
fn list_all_keys_internal(&self) -> io::Result<Vec<(String, String, String)>> {
507+
let locked_conn = self.connection.lock().expect("lock");
508+
509+
let sql = format!(
510+
"SELECT primary_namespace, secondary_namespace, key FROM {}",
511+
self.kv_table_name
512+
);
513+
let count_sql = format!("SELECT COUNT(*) FROM {}", self.kv_table_name);
514+
let count: usize =
515+
locked_conn.query_row(&count_sql, [], |row| row.get(0)).map_err(|e| {
516+
let msg = format!("Failed to count rows: {}", e);
517+
io::Error::new(io::ErrorKind::Other, msg)
518+
})?;
519+
520+
let mut stmt = locked_conn.prepare_cached(&sql).map_err(|e| {
521+
let msg = format!("Failed to prepare statement: {}", e);
522+
io::Error::new(io::ErrorKind::Other, msg)
523+
})?;
524+
525+
let mut keys = Vec::with_capacity(count);
526+
let rows_iter =
527+
stmt.query_map([], |row| Ok((row.get(0)?, row.get(1)?, row.get(2)?))).map_err(|e| {
528+
let msg = format!("Failed to retrieve queried rows: {}", e);
529+
io::Error::new(io::ErrorKind::Other, msg)
530+
})?;
531+
532+
for key in rows_iter {
533+
keys.push(key.map_err(|e| {
534+
let msg = format!("Failed to retrieve queried rows: {}", e);
535+
io::Error::new(io::ErrorKind::Other, msg)
536+
})?);
537+
}
538+
539+
Ok(keys)
540+
}
541+
489542
fn list_paginated_internal(
490543
&self, primary_namespace: &str, secondary_namespace: &str, page_token: Option<PageToken>,
491544
) -> io::Result<PaginatedListResponse> {
@@ -679,6 +732,34 @@ mod tests {
679732
do_test_store(&store_0, &store_1)
680733
}
681734

735+
#[tokio::test]
736+
async fn test_sqlite_store_list_all_keys() {
737+
let mut temp_path = random_storage_path();
738+
temp_path.push("test_sqlite_store_list_all_keys");
739+
let store = SqliteStore::new(
740+
temp_path,
741+
Some("test_db".to_string()),
742+
Some("test_table".to_string()),
743+
)
744+
.unwrap();
745+
746+
KVStore::write(&store, "ns_a", "sub_a", "key_a", vec![1u8]).await.unwrap();
747+
KVStore::write(&store, "ns_a", "sub_b", "key_b", vec![2u8]).await.unwrap();
748+
KVStore::write(&store, "ns_b", "", "key_c", vec![3u8]).await.unwrap();
749+
750+
let mut keys = MigratableKVStore::list_all_keys(&store).await.unwrap();
751+
keys.sort();
752+
753+
assert_eq!(
754+
keys,
755+
vec![
756+
("ns_a".to_string(), "sub_a".to_string(), "key_a".to_string()),
757+
("ns_a".to_string(), "sub_b".to_string(), "key_b".to_string()),
758+
("ns_b".to_string(), "".to_string(), "key_c".to_string()),
759+
]
760+
);
761+
}
762+
682763
#[tokio::test]
683764
async fn test_sqlite_store_paginated_listing() {
684765
let mut temp_path = random_storage_path();

0 commit comments

Comments
 (0)