1use crate::{DB, Error, ffi};
17
18use libc::{c_int, c_uchar};
19use std::ffi::CString;
20use std::path::Path;
21
22pub struct BackupEngineInfo {
27 pub timestamp: i64,
29 pub backup_id: u32,
31 pub size: u64,
33 pub num_files: u32,
35}
36
37pub struct BackupEngine {
38 inner: *mut ffi::rocksdb_backup_engine_t,
39}
40
41pub struct BackupEngineOptions {
42 inner: *mut ffi::rocksdb_options_t,
43}
44
45pub struct RestoreOptions {
46 inner: *mut ffi::rocksdb_restore_options_t,
47}
48
49impl BackupEngine {
50 pub fn open<P: AsRef<Path>>(opts: &BackupEngineOptions, path: P) -> Result<Self, Error> {
52 let path = path.as_ref();
53 let cpath = if let Ok(e) = CString::new(path.to_string_lossy().as_bytes()) {
54 e
55 } else {
56 return Err(Error::new(
57 "Failed to convert path to CString \
58 when opening backup engine"
59 .to_owned(),
60 ));
61 };
62
63 let be: *mut ffi::rocksdb_backup_engine_t;
64 unsafe {
65 be = ffi_try!(ffi::rocksdb_backup_engine_open(opts.inner, cpath.as_ptr()));
66 }
67
68 if be.is_null() {
69 return Err(Error::new("Could not initialize backup engine.".to_owned()));
70 }
71
72 Ok(Self { inner: be })
73 }
74
75 pub fn create_new_backup(&mut self, db: &DB) -> Result<(), Error> {
80 self.create_new_backup_flush(db, false)
81 }
82
83 pub fn create_new_backup_flush(
88 &mut self,
89 db: &DB,
90 flush_before_backup: bool,
91 ) -> Result<(), Error> {
92 unsafe {
93 ffi_try!(ffi::rocksdb_backup_engine_create_new_backup_flush(
94 self.inner,
95 db.inner,
96 flush_before_backup as c_uchar,
97 ));
98 Ok(())
99 }
100 }
101
102 pub fn purge_old_backups(&mut self, num_backups_to_keep: usize) -> Result<(), Error> {
103 unsafe {
104 ffi_try!(ffi::rocksdb_backup_engine_purge_old_backups(
105 self.inner,
106 num_backups_to_keep as u32,
107 ));
108 Ok(())
109 }
110 }
111
112 pub fn restore_from_latest_backup<D: AsRef<Path>, W: AsRef<Path>>(
134 &mut self,
135 db_dir: D,
136 wal_dir: W,
137 opts: &RestoreOptions,
138 ) -> Result<(), Error> {
139 let db_dir = db_dir.as_ref();
140 let c_db_dir = if let Ok(c) = CString::new(db_dir.to_string_lossy().as_bytes()) {
141 c
142 } else {
143 return Err(Error::new(
144 "Failed to convert db_dir to CString \
145 when restoring from latest backup"
146 .to_owned(),
147 ));
148 };
149
150 let wal_dir = wal_dir.as_ref();
151 let c_wal_dir = if let Ok(c) = CString::new(wal_dir.to_string_lossy().as_bytes()) {
152 c
153 } else {
154 return Err(Error::new(
155 "Failed to convert wal_dir to CString \
156 when restoring from latest backup"
157 .to_owned(),
158 ));
159 };
160
161 unsafe {
162 ffi_try!(ffi::rocksdb_backup_engine_restore_db_from_latest_backup(
163 self.inner,
164 c_db_dir.as_ptr(),
165 c_wal_dir.as_ptr(),
166 opts.inner,
167 ));
168 }
169 Ok(())
170 }
171
172 pub fn restore_from_backup<D: AsRef<Path>, W: AsRef<Path>>(
176 &mut self,
177 db_dir: D,
178 wal_dir: W,
179 opts: &RestoreOptions,
180 backup_id: u32,
181 ) -> Result<(), Error> {
182 let db_dir = db_dir.as_ref();
183 let c_db_dir = if let Ok(c) = CString::new(db_dir.to_string_lossy().as_bytes()) {
184 c
185 } else {
186 return Err(Error::new(
187 "Failed to convert db_dir to CString \
188 when restoring from latest backup"
189 .to_owned(),
190 ));
191 };
192
193 let wal_dir = wal_dir.as_ref();
194 let c_wal_dir = if let Ok(c) = CString::new(wal_dir.to_string_lossy().as_bytes()) {
195 c
196 } else {
197 return Err(Error::new(
198 "Failed to convert wal_dir to CString \
199 when restoring from latest backup"
200 .to_owned(),
201 ));
202 };
203
204 unsafe {
205 ffi_try!(ffi::rocksdb_backup_engine_restore_db_from_backup(
206 self.inner,
207 c_db_dir.as_ptr(),
208 c_wal_dir.as_ptr(),
209 opts.inner,
210 backup_id,
211 ));
212 }
213 Ok(())
214 }
215
216 pub fn verify_backup(&self, backup_id: u32) -> Result<(), Error> {
224 unsafe {
225 ffi_try!(ffi::rocksdb_backup_engine_verify_backup(
226 self.inner, backup_id,
227 ));
228 }
229 Ok(())
230 }
231
232 pub fn get_backup_info(&self) -> Vec<BackupEngineInfo> {
240 unsafe {
241 let i = ffi::rocksdb_backup_engine_get_backup_info(self.inner);
242
243 let n = ffi::rocksdb_backup_engine_info_count(i);
244
245 let mut info = Vec::with_capacity(n as usize);
246 for index in 0..n {
247 info.push(BackupEngineInfo {
248 timestamp: ffi::rocksdb_backup_engine_info_timestamp(i, index),
249 backup_id: ffi::rocksdb_backup_engine_info_backup_id(i, index),
250 size: ffi::rocksdb_backup_engine_info_size(i, index),
251 num_files: ffi::rocksdb_backup_engine_info_number_files(i, index),
252 });
253 }
254
255 ffi::rocksdb_backup_engine_info_destroy(i);
257
258 info
259 }
260 }
261}
262
263impl BackupEngineOptions {
264 }
266
267impl RestoreOptions {
268 pub fn set_keep_log_files(&mut self, keep_log_files: bool) {
269 unsafe {
270 ffi::rocksdb_restore_options_set_keep_log_files(self.inner, keep_log_files as c_int);
271 }
272 }
273}
274
275impl Default for BackupEngineOptions {
276 fn default() -> Self {
277 unsafe {
278 let opts = ffi::rocksdb_options_create();
279 if opts.is_null() {
280 panic!("Could not create RocksDB backup options");
281 }
282 Self { inner: opts }
283 }
284 }
285}
286
287impl Default for RestoreOptions {
288 fn default() -> Self {
289 unsafe {
290 let opts = ffi::rocksdb_restore_options_create();
291 if opts.is_null() {
292 panic!("Could not create RocksDB restore options");
293 }
294 Self { inner: opts }
295 }
296 }
297}
298
299impl Drop for BackupEngine {
300 fn drop(&mut self) {
301 unsafe {
302 ffi::rocksdb_backup_engine_close(self.inner);
303 }
304 }
305}
306
307impl Drop for BackupEngineOptions {
308 fn drop(&mut self) {
309 unsafe {
310 ffi::rocksdb_options_destroy(self.inner);
311 }
312 }
313}
314
315impl Drop for RestoreOptions {
316 fn drop(&mut self) {
317 unsafe {
318 ffi::rocksdb_restore_options_destroy(self.inner);
319 }
320 }
321}
322
323#[test]
324fn restore_from_latest() {
325 use crate::TemporaryDBPath;
326 use crate::ops::{Get, Open, Put};
327
328 let path = TemporaryDBPath::new();
330 let restore_path = TemporaryDBPath::new();
331 {
332 let db = DB::open_default(&path).unwrap();
333 assert!(db.put(b"k1", b"v1111").is_ok());
334 let value = db.get(b"k1");
335 assert_eq!(value.unwrap().unwrap().to_utf8().unwrap(), "v1111");
336 {
337 let backup_path = TemporaryDBPath::new();
338 let backup_opts = BackupEngineOptions::default();
339 let mut backup_engine = BackupEngine::open(&backup_opts, &backup_path).unwrap();
340 assert!(backup_engine.create_new_backup(&db).is_ok());
341
342 let info = backup_engine.get_backup_info();
344 assert!(!info.is_empty());
345 info.iter().for_each(|i| {
346 assert!(backup_engine.verify_backup(i.backup_id).is_ok());
347 assert!(i.size > 0);
348 });
349
350 let mut restore_option = RestoreOptions::default();
351 restore_option.set_keep_log_files(false); let restore_status = backup_engine.restore_from_latest_backup(
353 &restore_path,
354 &restore_path,
355 &restore_option,
356 );
357 assert!(restore_status.is_ok());
358
359 let db_restore = DB::open_default(&restore_path).unwrap();
360 let value = db_restore.get(b"k1");
361 assert_eq!(value.unwrap().unwrap().to_utf8().unwrap(), "v1111");
362 }
363 }
364}
365
366#[test]
367fn restore_from_backup() {
368 use crate::TemporaryDBPath;
369 use crate::ops::{Get, Open, Put};
370
371 let path = TemporaryDBPath::new();
373 let restore_path = TemporaryDBPath::new();
374 {
375 let db = DB::open_default(&path).unwrap();
376 assert!(db.put(b"k1", b"v1111").is_ok());
377 let value = db.get(b"k1");
378 assert_eq!(value.unwrap().unwrap().to_utf8().unwrap(), "v1111");
379 {
380 let backup_path = TemporaryDBPath::new();
381 let backup_opts = BackupEngineOptions::default();
382 let mut backup_engine = BackupEngine::open(&backup_opts, &backup_path).unwrap();
383 assert!(backup_engine.create_new_backup(&db).is_ok());
384
385 let info = backup_engine.get_backup_info();
387 assert!(!info.is_empty());
388 info.iter().for_each(|i| {
389 assert!(backup_engine.verify_backup(i.backup_id).is_ok());
390 assert!(i.size > 0);
391 });
392
393 let backup_id = info.first().unwrap().backup_id;
394 let mut restore_option = RestoreOptions::default();
395 restore_option.set_keep_log_files(false); let restore_status = backup_engine.restore_from_backup(
397 &restore_path,
398 &restore_path,
399 &restore_option,
400 backup_id,
401 );
402 assert!(restore_status.is_ok());
403
404 let db_restore = DB::open_default(&restore_path).unwrap();
405 let value = db_restore.get(b"k1");
406 assert_eq!(value.unwrap().unwrap().to_utf8().unwrap(), "v1111");
407 }
408 }
409}