1use crate::{ffi, Error, DB};
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>>(
135 &mut self,
136 db_dir: D,
137 wal_dir: W,
138 opts: &RestoreOptions,
139 ) -> Result<(), Error> {
140 let db_dir = db_dir.as_ref();
141 let c_db_dir = if let Ok(c) = CString::new(db_dir.to_string_lossy().as_bytes()) {
142 c
143 } else {
144 return Err(Error::new(
145 "Failed to convert db_dir to CString \
146 when restoring from latest backup"
147 .to_owned(),
148 ));
149 };
150
151 let wal_dir = wal_dir.as_ref();
152 let c_wal_dir = if let Ok(c) = CString::new(wal_dir.to_string_lossy().as_bytes()) {
153 c
154 } else {
155 return Err(Error::new(
156 "Failed to convert wal_dir to CString \
157 when restoring from latest backup"
158 .to_owned(),
159 ));
160 };
161
162 unsafe {
163 ffi_try!(ffi::rocksdb_backup_engine_restore_db_from_latest_backup(
164 self.inner,
165 c_db_dir.as_ptr(),
166 c_wal_dir.as_ptr(),
167 opts.inner,
168 ));
169 }
170 Ok(())
171 }
172
173 pub fn restore_from_backup<D: AsRef<Path>, W: AsRef<Path>>(
177 &mut self,
178 db_dir: D,
179 wal_dir: W,
180 opts: &RestoreOptions,
181 backup_id: u32,
182 ) -> Result<(), Error> {
183 let db_dir = db_dir.as_ref();
184 let c_db_dir = if let Ok(c) = CString::new(db_dir.to_string_lossy().as_bytes()) {
185 c
186 } else {
187 return Err(Error::new(
188 "Failed to convert db_dir to CString \
189 when restoring from latest backup"
190 .to_owned(),
191 ));
192 };
193
194 let wal_dir = wal_dir.as_ref();
195 let c_wal_dir = if let Ok(c) = CString::new(wal_dir.to_string_lossy().as_bytes()) {
196 c
197 } else {
198 return Err(Error::new(
199 "Failed to convert wal_dir to CString \
200 when restoring from latest backup"
201 .to_owned(),
202 ));
203 };
204
205 unsafe {
206 ffi_try!(ffi::rocksdb_backup_engine_restore_db_from_backup(
207 self.inner,
208 c_db_dir.as_ptr(),
209 c_wal_dir.as_ptr(),
210 opts.inner,
211 backup_id,
212 ));
213 }
214 Ok(())
215 }
216
217 pub fn verify_backup(&self, backup_id: u32) -> Result<(), Error> {
225 unsafe {
226 ffi_try!(ffi::rocksdb_backup_engine_verify_backup(
227 self.inner, backup_id,
228 ));
229 }
230 Ok(())
231 }
232
233 pub fn get_backup_info(&self) -> Vec<BackupEngineInfo> {
241 unsafe {
242 let i = ffi::rocksdb_backup_engine_get_backup_info(self.inner);
243
244 let n = ffi::rocksdb_backup_engine_info_count(i);
245
246 let mut info = Vec::with_capacity(n as usize);
247 for index in 0..n {
248 info.push(BackupEngineInfo {
249 timestamp: ffi::rocksdb_backup_engine_info_timestamp(i, index),
250 backup_id: ffi::rocksdb_backup_engine_info_backup_id(i, index),
251 size: ffi::rocksdb_backup_engine_info_size(i, index),
252 num_files: ffi::rocksdb_backup_engine_info_number_files(i, index),
253 });
254 }
255
256 ffi::rocksdb_backup_engine_info_destroy(i);
258
259 info
260 }
261 }
262}
263
264impl BackupEngineOptions {
265 }
267
268impl RestoreOptions {
269 pub fn set_keep_log_files(&mut self, keep_log_files: bool) {
270 unsafe {
271 ffi::rocksdb_restore_options_set_keep_log_files(self.inner, keep_log_files as c_int);
272 }
273 }
274}
275
276impl Default for BackupEngineOptions {
277 fn default() -> Self {
278 unsafe {
279 let opts = ffi::rocksdb_options_create();
280 if opts.is_null() {
281 panic!("Could not create RocksDB backup options");
282 }
283 Self { inner: opts }
284 }
285 }
286}
287
288impl Default for RestoreOptions {
289 fn default() -> Self {
290 unsafe {
291 let opts = ffi::rocksdb_restore_options_create();
292 if opts.is_null() {
293 panic!("Could not create RocksDB restore options");
294 }
295 Self { inner: opts }
296 }
297 }
298}
299
300impl Drop for BackupEngine {
301 fn drop(&mut self) {
302 unsafe {
303 ffi::rocksdb_backup_engine_close(self.inner);
304 }
305 }
306}
307
308impl Drop for BackupEngineOptions {
309 fn drop(&mut self) {
310 unsafe {
311 ffi::rocksdb_options_destroy(self.inner);
312 }
313 }
314}
315
316impl Drop for RestoreOptions {
317 fn drop(&mut self) {
318 unsafe {
319 ffi::rocksdb_restore_options_destroy(self.inner);
320 }
321 }
322}
323
324#[test]
325fn restore_from_latest() {
326 use crate::ops::{Get, Open, Put};
327 use crate::TemporaryDBPath;
328
329 let path = TemporaryDBPath::new();
331 let restore_path = TemporaryDBPath::new();
332 {
333 let db = DB::open_default(&path).unwrap();
334 assert!(db.put(b"k1", b"v1111").is_ok());
335 let value = db.get(b"k1");
336 assert_eq!(value.unwrap().unwrap().to_utf8().unwrap(), "v1111");
337 {
338 let backup_path = TemporaryDBPath::new();
339 let backup_opts = BackupEngineOptions::default();
340 let mut backup_engine = BackupEngine::open(&backup_opts, &backup_path).unwrap();
341 assert!(backup_engine.create_new_backup(&db).is_ok());
342
343 let info = backup_engine.get_backup_info();
345 assert!(!info.is_empty());
346 info.iter().for_each(|i| {
347 assert!(backup_engine.verify_backup(i.backup_id).is_ok());
348 assert!(i.size > 0);
349 });
350
351 let mut restore_option = RestoreOptions::default();
352 restore_option.set_keep_log_files(false); let restore_status = backup_engine.restore_from_latest_backup(
354 &restore_path,
355 &restore_path,
356 &restore_option,
357 );
358 assert!(restore_status.is_ok());
359
360 let db_restore = DB::open_default(&restore_path).unwrap();
361 let value = db_restore.get(b"k1");
362 assert_eq!(value.unwrap().unwrap().to_utf8().unwrap(), "v1111");
363 }
364 }
365}
366
367#[test]
368fn restore_from_backup() {
369 use crate::ops::{Get, Open, Put};
370 use crate::TemporaryDBPath;
371
372 let path = TemporaryDBPath::new();
374 let restore_path = TemporaryDBPath::new();
375 {
376 let db = DB::open_default(&path).unwrap();
377 assert!(db.put(b"k1", b"v1111").is_ok());
378 let value = db.get(b"k1");
379 assert_eq!(value.unwrap().unwrap().to_utf8().unwrap(), "v1111");
380 {
381 let backup_path = TemporaryDBPath::new();
382 let backup_opts = BackupEngineOptions::default();
383 let mut backup_engine = BackupEngine::open(&backup_opts, &backup_path).unwrap();
384 assert!(backup_engine.create_new_backup(&db).is_ok());
385
386 let info = backup_engine.get_backup_info();
388 assert!(!info.is_empty());
389 info.iter().for_each(|i| {
390 assert!(backup_engine.verify_backup(i.backup_id).is_ok());
391 assert!(i.size > 0);
392 });
393
394 let backup_id = info.get(0).unwrap().backup_id;
395 let mut restore_option = RestoreOptions::default();
396 restore_option.set_keep_log_files(false); let restore_status = backup_engine.restore_from_backup(
398 &restore_path,
399 &restore_path,
400 &restore_option,
401 backup_id,
402 );
403 assert!(restore_status.is_ok());
404
405 let db_restore = DB::open_default(&restore_path).unwrap();
406 let value = db_restore.get(b"k1");
407 assert_eq!(value.unwrap().unwrap().to_utf8().unwrap(), "v1111");
408 }
409 }
410}