1 /**
2  * D-LevelDB DateBase Object
3  *
4  * This is the main database object.  This object connects to a Leveldb database.
5  *
6  * Copyright: Copyright © 2013 Byron Heads
7  * License: <a href="http://www.boost.org/LICENSE_1_0.txt">Boost License 1.0</a>.
8  * Authors: Byron Heads
9  *
10  * Example:
11  ---
12  auto opt = new Options;
13  opt.create_if_missing = true;
14  auto db = new DB(opt, "/my/db/");
15  db.put(Slice("PI"), Slice.Ref!double(3.14));
16  double pi;
17  enforce(db.get(Slice("PI"), pi));
18  assert(pi == 3.14);
19  ---
20  * Todo: Add searching and transactions
21 */
22 /*          Copyright  © 2013 Byron Heads
23  * Distributed under the Boost Software License, Version 1.0.
24  *    (See accompanying file LICENSE_1_0.txt or copy at
25  *          http://www.boost.org/LICENSE_1_0.txt)
26  */
27 module leveldb.db;
28 
29 private import std..string : toStringz;
30 private import std.conv : to;
31 private import std.traits : isPointer, isArray;
32 private import std.c..string : memcpy;
33 private import core.memory : GC;
34 
35 // Use the temp space for unittesting
36 version(unittest)
37 {
38     private import std.file : 
39         tempDir, mkdirRecurse, rmdirRecurse ;
40     private import std.stdio;
41 
42     const __gshared const(string) tempPath;
43     static this()
44     {
45         tempPath = tempDir() ~ `/unittest_d_leveldb/`;
46         try
47         {
48             writeln("Temp Path: ", tempPath);
49             mkdirRecurse(tempPath);
50         } catch(Exception e) {}
51     }
52 
53     static ~this()
54     {
55         try
56         {
57             rmdirRecurse(tempPath);
58         } catch(Exception e) {}
59     }
60 }
61 
62 private import
63     leveldb.exceptions,
64     leveldb.slice,
65     leveldb.writebatch,
66     leveldb.options;
67 
68 private import deimos.leveldb.leveldb;
69 
70 /**
71  * LevelDB DB
72  * 
73  * Throws: LeveldbException on errors
74  */
75 class DB
76 {
77 private:
78     leveldb_t _db;  /// Internal LevelDB Pointer
79 
80 public:
81     /** Create a new unconnected DB */
82     this()
83     {}
84 
85     /**
86      * Opens a new connection to a levelDB database on creation
87      * 
88      * Params:
89      *      opt = LevelDB Options, sets the db options
90      *      path = path to the leveldb files, each DB needs its own path
91      * Throws: LeveldbException
92      */
93     this(Options opt, string path)
94     {
95         open(opt, path);
96     }
97 
98     /** Force database to close on destruction, cleans up library memory */
99     ~this()
100     {
101         close();
102     }
103 
104     /**
105      * Opens a new connection to a levelDB database
106      * 
107      * Params:
108      *      opt = LevelDB Options, sets the db options
109      *      path = path to the leveldb files, each DB needs its own path
110      * Throws: LeveldbException
111      */
112     void open(Options opt, string path)
113     {
114         // Close the connection if we are trying to reopen the db
115         close();
116 
117         // Catch any leveldb errors
118         char* errptr = null;
119         scope(failure) if(errptr) leveldb_free(errptr);
120 
121         _db = leveldb_open(opt.ptr, toStringz(path), &errptr);
122         if(errptr) throw new LeveldbException(errptr);
123         if(!_db) throw new LeveldbException(`Failed to connect to '` ~ path ~ `', unknown reason`);
124     }
125 
126     /**
127      * Close DB connection, also frees _db pointer in leveldb lib
128      */
129     @property
130     void close() nothrow
131     {
132         if(isOpen)
133         {
134             leveldb_close(_db);
135             _db = null;
136         }
137     }
138 
139     /**
140      * Inserts/Updates a given value at a given key.
141      *
142      * Example:
143      ---
144      auto opt = new Options;
145      opt.create_if_missing = true;
146      auto db = new DB(opt, "/my/db/");
147      db.put(Slice("User1"), "John Doe");
148      ---
149      * Throws: LeveldbException
150      */
151     void put(K, V)(in K key, in V val, const(WriteOptions) opt = DefaultWriteOptions)
152     {
153         if(!isOpen) throw new LeveldbException(`Not connected to a valid db`);
154 
155         char* errptr = null;
156         scope(failure) if(errptr) leveldb_free(errptr);
157 
158         leveldb_put(_db, opt.ptr, key._lib_obj_ptr__, key._lib_obj_size__, val._lib_obj_ptr__, val._lib_obj_size__, &errptr);
159         if(errptr) throw new LeveldbException(errptr);
160     }
161 
162     /**
163      * Deletes a key from the db
164      *
165      * Example:
166      ---
167      auto opt = new Options;
168      opt.create_if_missing = true;
169      auto db = new DB(opt, "/my/db/");
170      db.put("User1", Slice("John Doe"));
171      db.del("User1");
172      ---
173      * Throws: LeveldbException
174      */
175     void del(T)(in T key, const(WriteOptions) opt = DefaultWriteOptions)
176     {
177         if(!isOpen) throw new LeveldbException(`Not connected to a valid db`);
178         
179         char* errptr = null;
180         scope(failure) if(errptr) leveldb_free(errptr);
181 
182         leveldb_delete(_db, opt.ptr, key._lib_obj_ptr__, key._lib_obj_size__, &errptr);
183         if(errptr) throw new LeveldbException(errptr);
184     }
185 
186     /**
187      * finds an entry in the db or returns the default value
188      * Example
189      ---
190      auto opt = new Options;
191      opt.create_if_missing = true;
192      auto db = new DB(opt, "/my/db/");
193      db.put("user_1245_name", "John Smith);
194      assert(db.find("user_1245_name", "") == "John Smith");
195      ---
196      * Throws: LeveldbException
197      */
198     V find(K, V)(in K key, V def, const(ReadOptions) opt = DefaultReadOptions)
199         if(!is(V == interface))
200     {
201         if(!isOpen) throw new LeveldbException(`Not connected to a valid db`);
202 
203         char* errptr = null;
204         scope(failure) if(errptr) leveldb_free(errptr);
205 
206         size_t vallen;
207         auto valptr = leveldb_get(_db, opt.ptr, key._lib_obj_ptr__, key._lib_obj_size__, &vallen, &errptr);
208         scope(exit) if(valptr !is null) leveldb_free(valptr);
209         if(errptr) throw new LeveldbException(errptr);
210         if(valptr is null) return def;
211 
212         static if(isSomeString!V || isArray!V)
213 	{
214             return cast(V)(cast(char[])(valptr)[0..vallen]).dup;
215 	}
216         else static if(is(V == class))
217         {
218             if(typeid(V).sizeof > vallen)
219                 throw new LeveldbException("Assignment size is larger then data size");
220             return *(cast(V*)valptr).dup;
221         }
222         else
223         {
224             if(V.sizeof > vallen)
225                 throw new LeveldbException("Assignment size is larger then slice data size");
226             return *(cast(V*)valptr);
227         }
228     }
229 
230     /**
231      * Gets an entry from the DB
232      *
233      * Only accepts an array for the key.
234      * V must be convertable from char array.
235      *
236      * Example:
237      ---
238      auto opt = new Options;
239      opt.create_if_missing = true;
240      auto db = new DB(opt, "/my/db/");
241      db.put("User1", Slice("John Doe"));
242      string name;
243      enforce(db.get("User1", name));
244      assert(name == "John Doe");
245      ---
246      * Throws: LeveldbException
247      * Returns: true if the key was found in the DB
248      */
249     bool get(T, V)(in T key, out V value, const(ReadOptions) opt = DefaultReadOptions)
250         if(!is(V == interface))
251     {
252         if(!isOpen) throw new LeveldbException(`Not connected to a valid db`);
253 
254         char* errptr = null;
255         scope(failure) if(errptr) leveldb_free(errptr);
256 
257         size_t vallen;
258         auto valptr = leveldb_get(_db, opt.ptr, key._lib_obj_ptr__, key._lib_obj_size__, &vallen, &errptr);
259         scope(exit) if(valptr !is null) leveldb_free(valptr);
260         if(errptr) throw new LeveldbException(errptr);
261         if(valptr is null) return false;
262 
263         static if(isSomeString!V || isArray!V)
264 	{
265             value = cast(V)(cast(char[])(valptr)[0..vallen]).dup;
266 	}
267         else static if(is(V == class))
268         {
269             if(typeid(V).sizeof > vallen)
270                 throw new LeveldbException("Assignment size is larger then data size");
271             value = *(cast(V*)valptr).dup;
272         }
273         else
274         {
275             if(V.sizeof > vallen)
276                 throw new LeveldbException("Assignment size is larger then slice data size");
277             value = *(cast(V*)valptr);
278         }
279 
280         return true;
281     }
282 
283     /**
284      * Gets an entry from the DB as a Slice.
285      *
286      * Example:
287      ---
288      auto opt = new Options;
289      opt.create_if_missing = true;
290      auto db = new DB(opt, "/my/db/");
291      auto uuid = UUID("8AB3060E-2cba-4f23-b74c-b52db3bdfb46");
292      db.put("My UUID", uuid.data);
293      auto name = db.get_slice("My UUID");
294      assert(name.as!UUID == uuid);
295      ---
296      * Throws: LeveldbException
297      * Returns: A Slice struct, this holds the returned pointer and size
298      * Slice will safely clean up the result
299      */
300     auto get_slice(T)(T key, const(ReadOptions) opt = DefaultReadOptions)
301     {
302         if(!isOpen) throw new LeveldbException(`Not connected to a valid db`);
303 
304         char* errptr = null;
305         scope(failure) if(errptr) leveldb_free(errptr);
306 
307         size_t vallen;
308         void* val = leveldb_get(_db, opt.ptr, key._lib_obj_ptr__, key._lib_obj_size__, &vallen, &errptr);
309         scope(failure) if(val) leveldb_free(val);
310         if(errptr) throw new LeveldbException(errptr);
311         return Slice(val, vallen, true);
312     }
313 
314     /**
315      * Sublmits a BatchWrite to the DB.
316      *
317      * Used to do batch writes.
318      *
319      * Example:
320      ---
321      auto opt = new Options;
322      opt.create_if_missing = true;
323      auto db = new DB(opt, "/my/db/");
324      // Unsafe banking example
325      auto batch = new WriteBatch;
326 
327      double joe = db.get_slice(Slice("Joe")).as!double;
328      double sally = db.get_slice(Slice("Sally")).as!double;
329 
330      joe -= 10.00;
331      sally += 10.00;
332      if(joe < 0.0)
333         joe -= 30.00; // overdraft fee
334 
335      // submit the put in a single update
336      batch.put(Slice("Joe"), Slice(joe));
337      batch.put(Slice("Sally"), Slice(sally));
338      db.write(batch);
339      ---
340      * Throws: LeveldbException
341      */
342     void write(const(WriteBatch) batch, const(WriteOptions) opt = DefaultWriteOptions)
343     {
344         if(!isOpen) throw new LeveldbException(`Not connected to a valid db`);
345 
346         char* errptr = null;
347         scope(failure) if(errptr) leveldb_free(errptr);
348 
349         leveldb_write(_db, opt.ptr, cast(leveldb_writebatch_t)batch.ptr, &errptr);
350         if(errptr) throw new LeveldbException(errptr);
351     }
352 
353     /** 
354      * Returns a readonly snapshot
355      *
356      * Throws: LeveldbException
357      */
358     @property
359     ASnapshot snapshot()
360     {
361         if(!isOpen) throw new LeveldbException(`Not connected to a valid db`);
362         return new Snapshot();
363     }
364 
365     /* 
366      * Returns a database iterator
367      *
368      * Throws: LeveldbException
369      */
370     @property
371     Iterator iterator(const(ReadOptions) opt = DefaultReadOptions)
372     {
373         if(!isOpen) throw new LeveldbException(`Not connected to a valid db`);
374         return new Iterator(opt);
375     }
376 
377     /**
378      * Tests if the database is open
379      *
380      * Returns: true if there is an open database
381      */
382     @property
383     bool isOpen() inout nothrow
384     {
385         return _db !is null;
386     }
387 
388     /**
389      * Short cut iterator, treate the db like an iterator
390      */
391     int opApply(int delegate(Slice) dg)
392     {
393         return iterator.opApply(dg);
394     }
395 
396     int opApplyReverse(int delegate(Slice) dg)
397     {
398         return iterator.opApplyReverse(dg);
399     }
400 
401     int opApply(int delegate(Slice, Slice) dg)
402     {
403         return iterator.opApply(dg);
404     }
405 
406     int opApplyReverse(int delegate(Slice, Slice) dg)
407     {
408         return iterator.opApplyReverse(dg);
409     }
410 
411     /**
412      * DB Snapshot
413      *
414      * Snapshots can be applied to ReadOptions.  Created from a DB object
415      *
416      * Example:
417      ---
418      auto opt = new Options;
419      opt.create_if_missing = true;
420      auto db = new DB(opt, "/my/db/")
421 
422      auto snap = db.snapshot;
423      db.put(Slice("Future"), Slice("Stuff"));
424 
425      auto ro = new ReadOptions;
426      ro.snapshot(snap);
427 
428      string str;
429      assert(db.get(Slice("Future"), str));
430      assert(!db.get(Slice("Future"), str, ro));
431      ---
432      * Throws: LeveldbException
433      */
434     class Snapshot : ASnapshot
435     {
436     private:
437         leveldb_snapshot_t _snap;
438 
439     public:
440         @property 
441         override inout(leveldb_snapshot_t) ptr() inout
442         {
443             return _snap;
444         }
445 
446         this()
447         {
448             if((_snap = cast(leveldb_snapshot_t)leveldb_create_snapshot(_db)) is null)
449                 throw new LeveldbException("Failed to create snapshot");
450         }
451 
452         /** Cleanup snapshot memory */
453         ~this()
454         {
455             if(valid)
456             {
457                 leveldb_release_snapshot(_db, _snap);
458                 _snap = null;
459             }
460         }
461 
462         /// test if the snapshot has been created
463         @property 
464         bool valid() inout
465         {
466             return _snap !is null;
467         }
468     } // SnapShot
469 
470     /**
471      * DB Iterator
472      *
473      * Can iterate the db
474      *
475      * Example:
476      ---
477      auto opt = new Options;
478      opt.create_if_missing = true;
479      auto db = new DB(opt, "/my/db/")
480 
481      auto it = db.iterator;
482      foreach(Slice key, Slice value; it)
483      {
484         writeln(key.as!string, " - ", value.as!string);
485      }
486     
487      ---
488      * Throws: LeveldbException
489      */
490     class Iterator
491     {
492     private:
493         leveldb_iterator_t _iter;
494 
495     package:
496         @property
497         inout(leveldb_iterator_t) ptr() inout
498         {
499             return _iter;
500         }
501 
502     public:
503         this(const(ReadOptions) opt = DefaultReadOptions)
504         {
505             if((_iter = leveldb_create_iterator(_db, opt.ptr)) is null)
506                 throw new LeveldbException("Failed to create iterator");
507         }
508 
509         ~this()
510         {
511             if(ok)
512             {
513                 leveldb_iter_destroy(_iter);
514                 _iter = null;
515             }
516         }
517 
518         /// Iterator created
519         @property
520         bool ok() inout
521         {
522             return _iter !is null;
523         }
524 
525         /// Iterator has more data to read
526         @property
527         bool valid() inout
528         {
529             return cast(bool)leveldb_iter_valid(_iter);
530         }
531 
532 	@property
533 	bool empty() inout
534 	{
535 		return !valid;
536 	}
537 
538         /// Seek to front of data
539         @property 
540         void seek_to_first()
541         {
542             leveldb_iter_seek_to_first(_iter);
543         }
544 
545         /// Seek to end of data
546         @property 
547         void seek_to_last()
548         {
549             leveldb_iter_seek_to_last(_iter);
550         }
551 
552         /// Seek to given slice.
553         @property
554         void seek(T)(T key)
555         {
556             leveldb_iter_seek(_iter, key._lib_obj_ptr__, key._lib_obj_size__);
557         }
558 
559         /// Move to next item
560         @property
561         void next()
562         {
563             leveldb_iter_next(_iter);
564         }
565 
566 	alias next popFront;
567 
568         /// Move to previous item
569         @property
570         void prev()
571         {
572             leveldb_iter_prev(_iter);
573         }
574 
575         /// Return the current key
576         @property
577         Slice key()
578         {
579             debug if(!valid) throw new LeveldbException("Accessing invalid iterator");
580             size_t vallen;
581             void* val = cast(void*)leveldb_iter_key(_iter, &vallen);
582             scope(failure) if(val) leveldb_free(val);
583             return Slice(val, vallen, false);
584         }
585 
586         /// Return the current value
587         @property
588         auto value()
589         {
590             debug if(!valid) throw new LeveldbException("Accessing invalid iterator");
591             size_t vallen;
592             void* val = cast(void*)leveldb_iter_value(_iter, &vallen);
593             scope(failure) if(val) leveldb_free(val);
594             return Slice(val, vallen, false);
595         }
596 
597 	/// return the front of the iterator
598 	@property
599 	auto front()
600 	{
601 		return [key, value];
602 	}
603 
604         /// Gets the current error status of the iterator
605         @property
606         string status() inout
607         {
608             char* errptr = null;
609             scope(exit) if(errptr) leveldb_free(cast(void*)errptr);
610             leveldb_iter_get_error(_iter, &errptr);
611             return to!string(errptr);
612         }
613 
614         /// For each on iterator
615         int opApply(int delegate(Slice) dg)
616         {
617             int result = 0;
618             for(seek_to_first; valid; next)
619             {
620                 result = dg(value);
621                 if(result) return result;
622             }
623             return result;
624         }
625 
626         int opApplyReverse(int delegate(Slice) dg)
627         {
628             int result = 0;
629             for(seek_to_last; valid; prev)
630             {
631                 result = dg(value);
632                 if(result) return result;
633             }
634             return result;
635         }
636 
637         int opApply(int delegate(Slice, Slice) dg)
638         {
639             int result = 0;
640             for(seek_to_first; valid; next)
641             {
642                 result = dg(key, value);
643                 if(result) return result;
644             }
645             return result;
646         }
647 
648         int opApplyReverse(int delegate(Slice, Slice) dg)
649         {
650             int result = 0;
651             for(seek_to_last; valid; prev)
652             {
653                 result = dg(key, value);
654                 if(result) return result;
655             }
656             return result;
657         }
658     } //Iterator
659 
660     /**
661      * Destory/delete a non-locked leveldb
662      */
663     static void destroyDB(const Options opt, string path)
664     {
665         char* errptr = null;
666         scope(exit) if(errptr) leveldb_free(errptr);
667         leveldb_destroy_db(opt.ptr, toStringz(path), &errptr);
668         if(errptr) throw new LeveldbException(errptr);
669     }
670 
671     /**
672      * Attempt to repair a non-locked leveldb
673      */
674 
675     static void repairDB(const Options opt, string path)
676     {
677         char* errptr = null;
678         scope(exit) if(errptr) leveldb_free(errptr);
679 
680         leveldb_repair_db(opt.ptr, toStringz(path), &errptr);
681         if(errptr) throw new LeveldbException(errptr);
682     }
683 } // class DB
684 
685 
686 // Basic open, write string close, open get string, del string, get it
687 
688 unittest
689 {
690     auto opt = new Options;
691     opt.create_if_missing = true;
692     auto db = new DB(opt, tempPath ~ `s1`);
693     assert(db.isOpen);
694     int i = 1234;
695     db.put("simple", i);
696     assert(i == 1234);
697     i = 0;
698     db.get("simple", i);
699     double x = 3.145;
700     assert(i == 1234);
701     db.put("simple", x);
702     x = 0;
703     db.get("simple", x);
704     assert(x == 3.145, x.to!string);
705     db.destroy;
706 }
707 
708 unittest
709 {
710     auto opt = new Options;
711     string ret;
712     opt.create_if_missing = true;
713     auto db = new DB(opt, tempPath ~ `s1`);
714     assert(db.isOpen);
715     assert(Slice("World")._lib_obj_size__ == "World"._lib_obj_size__);
716     assert(Slice("World")._lib_obj_size__ == 5);
717     ret = "World";
718     db.put(Slice("Hello"), ret);
719     ret = "";
720     assert(db.get_slice(Slice("Hello")).as!string == "World");
721     assert(db.get(Slice("Hello"), ret));
722     assert(ret == "World");
723     db.close;
724     assert(!db.isOpen);
725     db.open(opt, tempPath ~ `s1`);
726     assert(db.isOpen);
727     assert(db.get(Slice("Hello"), ret));
728     assert(ret == "World");
729     db.del("Hello");
730     assert(!db.get(Slice("Hello"), ret));
731     assert(ret != "World");
732     destroy(db); // force destructor to be called
733     db.destroyDB(opt, tempPath ~ `s1`);
734 }
735 
736 unittest
737 {
738     auto opt = new Options;
739     opt.create_if_missing = true;
740     auto db = new DB(opt, tempPath ~ `s1`);
741     assert(db.isOpen);
742     db.put("Hello", "World");
743     db.close;
744     assert(!db.isOpen);
745     db.open(opt, tempPath ~ `s1`);
746     assert(db.isOpen);
747     string ret;
748     assert(db.get(Slice("Hello"), ret));
749     assert(ret == "World");
750     db.del(Slice("Hello"));
751     assert(!db.get(Slice("Hello"), ret));
752     assert(ret != "World");
753     destroy(db); // force destructor to be called
754 }
755 
756 // Test raw get
757 unittest
758 {
759     import std.math;
760     auto opt = new Options;
761     opt.create_if_missing = true;
762     auto db = new DB(opt, tempPath ~ `s1`);
763     auto pi = PI;
764     db.put(Slice("PI"), Slice(pi));
765     assert(db.get(Slice("PI"), pi));
766     assert(pi == PI);
767     assert(!db.get_slice("PI2").ok);
768     auto pi2 = db.get_slice(Slice("PI"));
769     assert(pi2.ok);
770     assert(pi2.length == pi.sizeof);
771     assert(pi2.as!real == pi);
772 }
773 
774 // Test raw get
775 unittest
776 {
777     auto opt = new Options;
778     opt.create_if_missing = true;
779     auto db = new DB(opt, tempPath ~ `s4`);
780     db.put(Slice("SCORE"), Slice.Ref(234L));
781     long pi;
782     assert(db.get("SCORE", pi));
783     assert(pi == 234);
784     assert(!db.get_slice("SCORE2").ok);
785     auto pi2 = db.get_slice(Slice("SCORE"));
786     assert(pi2.ok);
787     assert(pi2.length == pi.sizeof);
788     assert(pi2.as!long == 234L);
789 }
790 
791 // test structs as key and value
792 unittest
793 {
794     struct Point
795     {
796         double x, y;
797     }
798 
799     import std.uuid;
800     auto uuid = randomUUID();
801 
802     auto opt = new Options;
803     opt.create_if_missing = true;
804     auto db = new DB(opt, tempPath ~ `s2`);
805     auto p = Point(55, 44);
806     Point p2;
807     db.put(Slice(uuid.data), Slice(p));
808     auto o1 = db.get_slice(Slice(uuid.data));
809     assert(o1.as!Point.x == p.x);
810     assert(o1.as!Point.y == p.y);
811     assert(db.get(Slice(uuid.data), p2));
812     auto o2 = db.get_slice(uuid.data);
813     db.del(uuid.data);
814     GC.collect();
815     assert(p2.x == p.x);
816     assert(p2.y == p.y);
817     assert(!db.get(uuid.data, p2));
818 }
819 
820 unittest
821 {
822     auto opt = new Options;
823     opt.create_if_missing = true;
824     auto db = new DB(opt, tempPath ~ `wb1`);
825 
826     db.put(Slice("Joe"), Slice.Ref(25));
827     db.put(Slice("Sally"), Slice.Ref(905));
828     assert(db.get_slice("Joe").as!int == 25);
829     assert(db.get_slice(Slice("Sally")).as!int == 905);
830 
831     auto joe = db.get_slice(Slice("Joe")).as!int - 10;
832     auto sally = db.get_slice(Slice("Sally")).as!int + 10;
833 
834     auto wb = new WriteBatch();
835     wb.put(Slice("Joe"), joe);
836     wb.put("Sally", Slice(sally));
837     assert(db.get_slice(Slice("Joe")).as!int == 25);
838     assert(db.get_slice("Sally").as!int == 905);
839     wb.clear;
840     db.write(wb);
841     assert(db.get_slice(Slice("Joe")).as!int == 25);
842     assert(db.get_slice("Sally").as!int == 905);
843     wb.put("Joe", joe);
844     wb.put(Slice("Sally"), Slice(sally));
845     db.write(wb);
846     assert(db.get_slice("Joe").as!int == joe);
847     assert(db.get_slice("Sally").as!int == sally);
848 }
849 
850 unittest
851 {
852     auto opt = new Options;
853     opt.create_if_missing = true;
854     auto db = new DB(opt, tempPath ~ `wb2`);
855 
856     db.put("A", 1);
857     db.put("B", 1);
858     assert(db.get_slice("A").ok);
859     assert(db.get_slice("B").ok);
860     auto wb = new WriteBatch();
861     wb.del("A");
862     assert(db.get_slice("A").ok);
863     assert(db.get_slice("B").ok);
864     db.write(wb);
865     assert(!db.get_slice("A").ok);
866     assert(db.get_slice("B").ok);
867     db.put("A", 1);
868     wb.clear;
869     wb.del(Slice("B"));
870     assert(db.get_slice("A").ok);
871     assert(db.get_slice("B").ok);
872     db.write(wb);
873     assert(db.get_slice("A").ok);
874     assert(!db.get_slice("B").ok);
875 }
876 
877 unittest
878 {
879     auto opt = new Options;
880     opt.create_if_missing = true;
881     DB.destroyDB(opt, tempPath ~ `ss1`);
882     auto db = new DB(opt, tempPath ~ `ss1`);
883     auto snap = db.snapshot;
884     assert(snap);
885     db.put(Slice("Future"), "Stuff");
886     auto ro = new ReadOptions;
887     ro.snapshot(snap);
888     string str;
889     assert(db.get(Slice("Future"), str));
890     assert(str == "Stuff");
891     assert(!db.get("Future", str, ro));
892     assert(str == "");
893     assert(db.get("Future", str));
894     snap = db.snapshot;
895     ro.snapshot(snap);
896     assert(db.get("Future", str, ro));
897 }
898 
899 unittest
900 {
901     auto opt = new Options;
902     opt.create_if_missing = true;
903     DB.destroyDB(opt, tempPath ~ `it1/`);
904     auto db = new DB(opt, tempPath ~ `it1/`);
905     db.put(Slice("Hello"), Slice("World"));
906 
907     auto it = db.iterator;
908     foreach(Slice key, Slice value; it)
909     {
910         assert(key.as!string == "Hello");
911         assert(value.as!string == "World");
912     }
913     foreach_reverse(Slice key, Slice value; it)
914     {
915         assert(key.as!string == "Hello");
916         assert(value.as!string == "World");
917     }
918 
919     db.put(1, Slice.Ref(1));
920     it = db.iterator;
921     for(it.seek(Slice.Ref(1)); it.valid; it.next)
922     {
923         assert(it.key.ok);
924         assert(it.value.ok);
925     }
926 
927     it = db.iterator;
928     for(it.seek(1); it.valid; it.next)
929     {
930         assert(it.key.ok);
931         assert(it.value.ok);
932     }
933 }
934 
935 unittest
936 {
937     auto opt = new Options;
938     opt.create_if_missing = true;
939     DB.destroyDB(opt, tempPath ~ `it2`);
940     auto db = new DB(opt, tempPath ~ `it2/`);
941     assert(db.isOpen);
942     foreach(int i; 1..10)
943     {
944         db.put(i, i*2);
945     }
946     auto it = db.iterator;
947     
948     foreach(Slice key, Slice value; it)
949     {
950         assert(value.as!int == key.as!int * 2);
951     }
952     foreach_reverse(Slice key, Slice value; it)
953     {
954         assert(value.as!int == key.as!int * 2);
955     }
956 }
957 
958 unittest
959 {
960     auto opt = new Options;
961     opt.create_if_missing = true;
962     DB.destroyDB(opt, tempPath ~ `it3/`);
963     auto db = new DB(opt, tempPath ~ `it3/`);
964     db.put("Hello", "World");
965 
966     foreach(Slice key, Slice value; db)
967     {
968         assert(key.as!string == "Hello");
969         assert(value.as!string == "World");
970     }
971     foreach_reverse(Slice key, Slice value; db)
972     {
973         assert(key.as!string == "Hello");
974         assert(value.as!string == "World");
975     }
976 }
977 
978 unittest
979 {
980     auto opt = new Options;
981     opt.create_if_missing = true;
982     DB.destroyDB(opt, tempPath ~ `it4`);
983     auto db = new DB(opt, tempPath ~ `it4/`);
984     assert(db.isOpen);
985     foreach(int i; 1..10)
986     {
987         db.put(Slice(i), i*2);
988     }
989     foreach(Slice key, Slice value; db)
990     {
991         assert(value.as!int == key.as!int * 2);
992     }
993     foreach_reverse(Slice key, Slice value; db)
994     {
995         assert(value.as!int == key.as!int * 2);
996     }
997 }
998 
999 
1000 unittest
1001 {
1002     auto opt = new Options;
1003     opt.create_if_missing = true;
1004     DB.destroyDB(opt, tempPath ~ `find`);
1005     auto db = new DB(opt, tempPath ~ `find/`);
1006     assert(db.isOpen);
1007     db.put("i1", "blah1.png");
1008     db.put("i2", "blah2.png");
1009     assert(db.find("i1", "null.png") == "blah1.png");
1010     assert(db.find("i2", "null.png") == "blah2.png");
1011     assert(db.find("i3", "null.png") == "null.png");
1012 }
1013