1use libc::{
2 c_uint,
3 size_t,
4};
5use std::ffi::CString;
6#[cfg(windows)]
7use std::ffi::OsStr;
8#[cfg(unix)]
9use std::os::unix::ffi::OsStrExt;
10use std::path::Path;
11use std::sync::Mutex;
12use std::{
13 fmt,
14 mem,
15 ptr,
16 result,
17};
18
19use ffi;
20
21use byteorder::{
22 ByteOrder,
23 NativeEndian,
24};
25
26use cursor::Cursor;
27use database::Database;
28use error::{
29 lmdb_result,
30 Error,
31 Result,
32};
33use flags::{
34 DatabaseFlags,
35 EnvironmentFlags,
36};
37use transaction::{
38 RoTransaction,
39 RwTransaction,
40 Transaction,
41};
42
43#[cfg(windows)]
44trait OsStrExtLmdb {
46 fn as_bytes(&self) -> &[u8];
47}
48#[cfg(windows)]
49impl OsStrExtLmdb for OsStr {
50 fn as_bytes(&self) -> &[u8] {
51 &self.to_str().unwrap().as_bytes()
52 }
53}
54
55pub struct Environment {
59 env: *mut ffi::MDB_env,
60 dbi_open_mutex: Mutex<()>,
61}
62
63impl Environment {
64 #[allow(clippy::new_ret_no_self)]
66 pub fn new() -> EnvironmentBuilder {
67 EnvironmentBuilder {
68 flags: EnvironmentFlags::empty(),
69 max_readers: None,
70 max_dbs: None,
71 map_size: None,
72 }
73 }
74
75 pub fn env(&self) -> *mut ffi::MDB_env {
80 self.env
81 }
82
83 pub fn open_db<'env>(&'env self, name: Option<&str>) -> Result<Database> {
98 let mutex = self.dbi_open_mutex.lock();
99 let txn = self.begin_ro_txn()?;
100 let db = unsafe { txn.open_db(name)? };
101 txn.commit()?;
102 drop(mutex);
103 Ok(db)
104 }
105
106 pub fn create_db<'env>(&'env self, name: Option<&str>, flags: DatabaseFlags) -> Result<Database> {
121 let mutex = self.dbi_open_mutex.lock();
122 let txn = self.begin_rw_txn()?;
123 let db = unsafe { txn.create_db(name, flags)? };
124 txn.commit()?;
125 drop(mutex);
126 Ok(db)
127 }
128
129 pub fn get_db_flags(&self, db: Database) -> Result<DatabaseFlags> {
133 let txn = self.begin_ro_txn()?;
134 let mut flags: c_uint = 0;
135 unsafe {
136 lmdb_result(ffi::mdb_dbi_flags(txn.txn(), db.dbi(), &mut flags))?;
137 }
138 Ok(DatabaseFlags::from_bits(flags).unwrap())
139 }
140
141 pub fn begin_ro_txn<'env>(&'env self) -> Result<RoTransaction<'env>> {
143 RoTransaction::new(self)
144 }
145
146 pub fn begin_rw_txn<'env>(&'env self) -> Result<RwTransaction<'env>> {
149 RwTransaction::new(self)
150 }
151
152 pub fn sync(&self, force: bool) -> Result<()> {
158 unsafe {
159 lmdb_result(ffi::mdb_env_sync(
160 self.env(),
161 if force {
162 1
163 } else {
164 0
165 },
166 ))
167 }
168 }
169
170 pub unsafe fn close_db(&mut self, db: Database) {
184 ffi::mdb_dbi_close(self.env, db.dbi());
185 }
186
187 pub fn stat(&self) -> Result<Stat> {
189 unsafe {
190 let mut stat = Stat::new();
191 lmdb_try!(ffi::mdb_env_stat(self.env(), stat.mdb_stat()));
192 Ok(stat)
193 }
194 }
195
196 pub fn info(&self) -> Result<Info> {
198 unsafe {
199 let mut info = Info(mem::zeroed());
200 lmdb_try!(ffi::mdb_env_info(self.env(), &mut info.0));
201 Ok(info)
202 }
203 }
204
205 pub fn freelist(&self) -> Result<size_t> {
229 let mut freelist: size_t = 0;
230 let db = Database::freelist_db();
231 let txn = self.begin_ro_txn()?;
232 let mut cursor = txn.open_ro_cursor(db)?;
233
234 for result in cursor.iter() {
235 let (_key, value) = result?;
236 if value.len() < mem::size_of::<size_t>() {
237 return Err(Error::Corrupted);
238 }
239
240 let s = &value[..mem::size_of::<size_t>()];
241 if cfg!(target_pointer_width = "64") {
242 freelist += NativeEndian::read_u64(s) as size_t;
243 } else {
244 freelist += NativeEndian::read_u32(s) as size_t;
245 }
246 }
247
248 Ok(freelist)
249 }
250
251 pub fn set_map_size(&self, size: size_t) -> Result<()> {
268 unsafe { lmdb_result(ffi::mdb_env_set_mapsize(self.env(), size)) }
269 }
270}
271
272pub struct Stat(ffi::MDB_stat);
276
277impl Stat {
278 pub(crate) fn new() -> Stat {
280 unsafe { Stat(mem::zeroed()) }
281 }
282
283 pub(crate) fn mdb_stat(&mut self) -> *mut ffi::MDB_stat {
285 &mut self.0
286 }
287}
288
289impl Stat {
290 #[inline]
292 pub fn page_size(&self) -> u32 {
293 self.0.ms_psize
294 }
295
296 #[inline]
298 pub fn depth(&self) -> u32 {
299 self.0.ms_depth
300 }
301
302 #[inline]
304 pub fn branch_pages(&self) -> usize {
305 self.0.ms_branch_pages
306 }
307
308 #[inline]
310 pub fn leaf_pages(&self) -> usize {
311 self.0.ms_leaf_pages
312 }
313
314 #[inline]
316 pub fn overflow_pages(&self) -> usize {
317 self.0.ms_overflow_pages
318 }
319
320 #[inline]
322 pub fn entries(&self) -> usize {
323 self.0.ms_entries
324 }
325}
326
327pub struct Info(ffi::MDB_envinfo);
331
332impl Info {
333 #[inline]
335 pub fn map_size(&self) -> usize {
336 self.0.me_mapsize
337 }
338
339 #[inline]
341 pub fn last_pgno(&self) -> usize {
342 self.0.me_last_pgno
343 }
344
345 #[inline]
347 pub fn last_txnid(&self) -> usize {
348 self.0.me_last_txnid
349 }
350
351 #[inline]
353 pub fn max_readers(&self) -> u32 {
354 self.0.me_maxreaders
355 }
356
357 #[inline]
359 pub fn num_readers(&self) -> u32 {
360 self.0.me_numreaders
361 }
362}
363
364unsafe impl Send for Environment {}
365unsafe impl Sync for Environment {}
366
367impl fmt::Debug for Environment {
368 fn fmt(&self, f: &mut fmt::Formatter) -> result::Result<(), fmt::Error> {
369 f.debug_struct("Environment").finish()
370 }
371}
372
373impl Drop for Environment {
374 fn drop(&mut self) {
375 unsafe { ffi::mdb_env_close(self.env) }
376 }
377}
378
379#[derive(Debug, PartialEq, Eq, Copy, Clone)]
385pub struct EnvironmentBuilder {
386 flags: EnvironmentFlags,
387 max_readers: Option<c_uint>,
388 max_dbs: Option<c_uint>,
389 map_size: Option<size_t>,
390}
391
392impl EnvironmentBuilder {
393 pub fn open(&self, path: &Path) -> Result<Environment> {
400 self.open_with_permissions(path, 0o644)
401 }
402
403 pub fn open_with_permissions(&self, path: &Path, mode: ffi::mdb_mode_t) -> Result<Environment> {
410 let mut env: *mut ffi::MDB_env = ptr::null_mut();
411 unsafe {
412 lmdb_try!(ffi::mdb_env_create(&mut env));
413 if let Some(max_readers) = self.max_readers {
414 lmdb_try_with_cleanup!(ffi::mdb_env_set_maxreaders(env, max_readers), ffi::mdb_env_close(env))
415 }
416 if let Some(max_dbs) = self.max_dbs {
417 lmdb_try_with_cleanup!(ffi::mdb_env_set_maxdbs(env, max_dbs), ffi::mdb_env_close(env))
418 }
419 if let Some(map_size) = self.map_size {
420 lmdb_try_with_cleanup!(ffi::mdb_env_set_mapsize(env, map_size), ffi::mdb_env_close(env))
421 }
422 let path = match CString::new(path.as_os_str().as_bytes()) {
423 Ok(path) => path,
424 Err(..) => return Err(::Error::Invalid),
425 };
426 lmdb_try_with_cleanup!(
427 ffi::mdb_env_open(env, path.as_ptr(), self.flags.bits(), mode),
428 ffi::mdb_env_close(env)
429 );
430 }
431 Ok(Environment {
432 env,
433 dbi_open_mutex: Mutex::new(()),
434 })
435 }
436
437 pub fn set_flags(&mut self, flags: EnvironmentFlags) -> &mut EnvironmentBuilder {
439 self.flags = flags;
440 self
441 }
442
443 pub fn set_max_readers(&mut self, max_readers: c_uint) -> &mut EnvironmentBuilder {
451 self.max_readers = Some(max_readers);
452 self
453 }
454
455 pub fn set_max_dbs(&mut self, max_dbs: c_uint) -> &mut EnvironmentBuilder {
465 self.max_dbs = Some(max_dbs);
466 self
467 }
468
469 pub fn set_map_size(&mut self, map_size: size_t) -> &mut EnvironmentBuilder {
480 self.map_size = Some(map_size);
481 self
482 }
483}
484
485#[cfg(test)]
486mod test {
487
488 extern crate byteorder;
489
490 use self::byteorder::{
491 ByteOrder,
492 LittleEndian,
493 };
494 use tempdir::TempDir;
495
496 use flags::*;
497
498 use super::*;
499
500 #[test]
501 fn test_open() {
502 let dir = TempDir::new("test").unwrap();
503
504 assert!(Environment::new().set_flags(EnvironmentFlags::READ_ONLY).open(dir.path()).is_err());
506
507 assert!(Environment::new().open(dir.path()).is_ok());
509
510 assert!(Environment::new().set_flags(EnvironmentFlags::READ_ONLY).open(dir.path()).is_ok());
512 }
513
514 #[test]
515 fn test_begin_txn() {
516 let dir = TempDir::new("test").unwrap();
517
518 {
519 let env = Environment::new().open(dir.path()).unwrap();
521
522 assert!(env.begin_rw_txn().is_ok());
523 assert!(env.begin_ro_txn().is_ok());
524 }
525
526 {
527 let env = Environment::new().set_flags(EnvironmentFlags::READ_ONLY).open(dir.path()).unwrap();
529
530 assert!(env.begin_rw_txn().is_err());
531 assert!(env.begin_ro_txn().is_ok());
532 }
533 }
534
535 #[test]
536 fn test_open_db() {
537 let dir = TempDir::new("test").unwrap();
538 let env = Environment::new().set_max_dbs(1).open(dir.path()).unwrap();
539
540 assert!(env.open_db(None).is_ok());
541 assert!(env.open_db(Some("testdb")).is_err());
542 }
543
544 #[test]
545 fn test_create_db() {
546 let dir = TempDir::new("test").unwrap();
547 let env = Environment::new().set_max_dbs(11).open(dir.path()).unwrap();
548 assert!(env.open_db(Some("testdb")).is_err());
549 assert!(env.create_db(Some("testdb"), DatabaseFlags::empty()).is_ok());
550 assert!(env.open_db(Some("testdb")).is_ok())
551 }
552
553 #[test]
554 fn test_close_database() {
555 let dir = TempDir::new("test").unwrap();
556 let mut env = Environment::new().set_max_dbs(10).open(dir.path()).unwrap();
557
558 let db = env.create_db(Some("db"), DatabaseFlags::empty()).unwrap();
559 unsafe {
560 env.close_db(db);
561 }
562 assert!(env.open_db(Some("db")).is_ok());
563 }
564
565 #[test]
566 fn test_sync() {
567 let dir = TempDir::new("test").unwrap();
568 {
569 let env = Environment::new().open(dir.path()).unwrap();
570 assert!(env.sync(true).is_ok());
571 }
572 {
573 let env = Environment::new().set_flags(EnvironmentFlags::READ_ONLY).open(dir.path()).unwrap();
574 assert!(env.sync(true).is_err());
575 }
576 }
577
578 #[test]
579 fn test_stat() {
580 let dir = TempDir::new("test").unwrap();
581 let env = Environment::new().open(dir.path()).unwrap();
582
583 let stat = env.stat().unwrap();
585 assert_eq!(stat.page_size(), 4096);
586 assert_eq!(stat.depth(), 0);
587 assert_eq!(stat.branch_pages(), 0);
588 assert_eq!(stat.leaf_pages(), 0);
589 assert_eq!(stat.overflow_pages(), 0);
590 assert_eq!(stat.entries(), 0);
591
592 let db = env.open_db(None).unwrap();
593
594 for i in 0..64 {
596 let mut value = [0u8; 8];
597 LittleEndian::write_u64(&mut value, i);
598 let mut tx = env.begin_rw_txn().expect("begin_rw_txn");
599 tx.put(db, &value, &value, WriteFlags::default()).expect("tx.put");
600 tx.commit().expect("tx.commit")
601 }
602
603 let stat = env.stat().unwrap();
605 assert_eq!(stat.page_size(), 4096);
606 assert_eq!(stat.depth(), 1);
607 assert_eq!(stat.branch_pages(), 0);
608 assert_eq!(stat.leaf_pages(), 1);
609 assert_eq!(stat.overflow_pages(), 0);
610 assert_eq!(stat.entries(), 64);
611 }
612
613 #[test]
614 fn test_info() {
615 let map_size = 1024 * 1024;
616 let dir = TempDir::new("test").unwrap();
617 let env = Environment::new().set_map_size(map_size).open(dir.path()).unwrap();
618
619 let info = env.info().unwrap();
620 assert_eq!(info.map_size(), map_size);
621 assert_eq!(info.last_pgno(), 1);
622 assert_eq!(info.last_txnid(), 0);
623 assert_eq!(info.max_readers(), 126);
625 assert_eq!(info.num_readers(), 0);
626 }
627
628 #[test]
629 fn test_freelist() {
630 let dir = TempDir::new("test").unwrap();
631 let env = Environment::new().open(dir.path()).unwrap();
632
633 let db = env.open_db(None).unwrap();
634 let mut freelist = env.freelist().unwrap();
635 assert_eq!(freelist, 0);
636
637 for i in 0..64 {
639 let mut value = [0u8; 8];
640 LittleEndian::write_u64(&mut value, i);
641 let mut tx = env.begin_rw_txn().expect("begin_rw_txn");
642 tx.put(db, &value, &value, WriteFlags::default()).expect("tx.put");
643 tx.commit().expect("tx.commit")
644 }
645 let mut tx = env.begin_rw_txn().expect("begin_rw_txn");
646 tx.clear_db(db).expect("clear");
647 tx.commit().expect("tx.commit");
648
649 freelist = env.freelist().unwrap();
651 assert!(freelist > 0);
652 }
653
654 #[test]
655 fn test_set_map_size() {
656 let dir = TempDir::new("test").unwrap();
657 let env = Environment::new().open(dir.path()).unwrap();
658
659 let mut info = env.info().unwrap();
660 let default_size = info.map_size();
661
662 env.set_map_size(0).unwrap();
664 info = env.info().unwrap();
665 assert_eq!(info.map_size(), default_size);
666
667 env.set_map_size(2 * default_size).unwrap();
668 info = env.info().unwrap();
669 assert_eq!(info.map_size(), 2 * default_size);
670
671 env.set_map_size(4 * default_size).unwrap();
672 info = env.info().unwrap();
673 assert_eq!(info.map_size(), 4 * default_size);
674
675 env.set_map_size(2 * default_size).unwrap();
677 info = env.info().unwrap();
678 assert_eq!(info.map_size(), 2 * default_size);
679 }
680}