Repository: xyb/rocksdb3 (language: python)

Issue:
Feature: add support for OpenAsSecondary
Doc: https://github.com/facebook/rocksdb/wiki/Secondary-instance
Code: https://github.com/facebook/rocksdb/blob/v6.22.1/include/rocksdb/db.h#L192-L215


Discussion / hints:
@ofek sorry for the delayed reply. I haven't had enough time to do this recently, maybe I can try to implement it in 1 or 2 months. But your contribution is always welcome.

Fix patch:
diff --git a/src/lib.rs b/src/lib.rs
index 73e7430..6f134ad 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -86,6 +86,18 @@ fn rocksdb3(_py: Python, m: &PyModule) -> PyResult<()> {
         fn get_iter(&mut self) -> PyResult<iterator::RocksDBIterator> {
             Ok(iterator::RocksDBIterator::new(self.db.clone()))
         }
+
+        /// Tries to catch up with the primary by reading as much as possible from the log files.
+        fn try_catch_up_with_primary(&mut self) -> PyResult<()> {
+            match self.db.try_catch_up_with_primary() {
+                Ok(_) => Ok(()),
+                Err(e) => {
+                    return Err(RocksDBError::new_err(format!(
+                        "can not catch up with the primary: {}", e,
+                    )))
+                }
+            }
+        }
     }
 
     /// Opens a database with default options.
@@ -108,6 +120,27 @@ fn rocksdb3(_py: Python, m: &PyModule) -> PyResult<()> {
         }
     }
 
+    /// Opens the database as a read-only secondary instance.
+    ///
+    /// Positional arguments:
+    /// - `primary_path` (required): Path of the primary database instance.
+    /// - `secondary_path` (required): Path of the secondary database to open.
+    #[pyfn(m, "open_as_secondary")]
+    fn open_as_secondary(primary_path: &str, secondary_path: &str) -> PyResult<RocksDB> {
+        match DB::open_as_secondary(&Options::default(), primary_path, secondary_path) {
+            Ok(db) => Ok(RocksDB {
+                db: Arc::new(db),
+                path: secondary_path.as_bytes().to_vec(),
+            }),
+            Err(e) => {
+                return Err(RocksDBError::new_err(format!(
+                    "can not open secondary instance {} with {}: {}",
+                    secondary_path, primary_path, e,
+                )))
+            }
+        }
+    }
+
     /// Repair the database cannot be opened.
     ///
     /// If a DB cannot be opened, you may attempt to call this method to


Test patch:
diff --git a/tests/test_rocksdb.py b/tests/test_rocksdb.py
index 6580f4f..129378f 100644
--- a/tests/test_rocksdb.py
+++ b/tests/test_rocksdb.py
@@ -112,3 +112,29 @@ def test_iter(db):
 
     assert next(it) == (b'author', b'xyb')
     assert next(it) == (b'hello', b'world')
+
+
+def test_secondary(tmp_path):
+    primary_path = tmp_path / 'primary'
+    db_primary = rocksdb3.open_default(str(primary_path))
+    db_primary.put(b'hello', b'world')
+
+    secondary_path = tmp_path / 'secondary'
+    db_secondary = rocksdb3.open_as_secondary(str(primary_path),
+                                              str(secondary_path))
+
+    assert list(db_secondary.get_iter()) == [(b'hello', b'world')]
+
+
+def test_secondary_catch_up_primary(tmp_path):
+    primary_path = tmp_path / 'primary'
+    db_primary = rocksdb3.open_default(str(primary_path))
+    secondary_path = tmp_path / 'secondary'
+    db_secondary = rocksdb3.open_as_secondary(str(primary_path),
+                                              str(secondary_path))
+    assert db_secondary.get(b'author') == None
+    db_primary.put(b'author', b'xyb')
+
+    db_secondary.try_catch_up_with_primary()
+
+    assert db_secondary.get(b'author') == b'xyb'