1 module leveldb.db;
2 /**
3  * D-LevelDB DateBase Object
4  *
5  * This is the main database object.  This object connects to a Leveldb database.
6  *
7  * Copyright: Copyright © 2013 Byron Heads
8  * License: <a href="http://www.boost.org/LICENSE_1_0.txt">Boost License 1.0</a>.
9  * Authors: Byron Heads
10  *
11  * todo: add examples
12 */
13 
14 /**
15  *          Copyright  © 2013 Byron Heads
16  * Distributed under the Boost Software License, Version 1.0.
17  *    (See accompanying file LICENSE_1_0.txt or copy at
18  *          http://www.boost.org/LICENSE_1_0.txt)
19  */
20 
21 private:
22 import deimos.leveldb.leveldb, 
23     leveldb.exceptions, 
24     leveldb.slice, 
25     leveldb.writebatch, 
26     leveldb.options;
27 
28 debug import std.stdio : writeln;
29 
30 import std.string : toStringz;
31 import std.traits;
32 
33 public:
34 
35 /**
36  * LevelDB DB
37  * 
38  * Throws: LeveldbException on errors
39  */
40 struct DB(alias pack, alias unpack)
41 {
42 private:
43     leveldb_t _db;  /// Internal LevelDB Pointer
44 
45 public:
46     @disable this(); // removing default empty constructor
47     @disable this(this); // removing default empty constructor
48 
49     /**
50      * Opens a new connection to a levelDB database on creation
51      * 
52      * Params:
53      *      path = path to the leveldb files, DBs are folders that contain db files
54      *      opt = LevelDB Options, sets the db options
55      * Throws: LeveldbException
56      */
57     this(string path, Options opt) {
58         open(path, opt);
59     }
60 
61     /** Force database to close on destruction, cleans up library memory */
62     ~this() {
63         close();
64     }
65 
66     /**
67      * Opens a new connection to a levelDB database
68      * 
69      * Params:
70      *      path = path to the leveldb files, each DB needs its own path
71      *      opt = LevelDB Options, sets the db options
72      * Throws: LeveldbException
73      */
74     @property final 
75     void open(string path, Options opt) {
76         // Close the connection if we are trying to reopen the db
77         close();
78 
79         char* errptr = null;
80         scope(exit) if(errptr !is null) leveldb_free(errptr);
81 
82         _db = leveldb_open(opt.ptr, path.toStringz, &errptr);
83 
84         dbEnforce(errptr is null);
85         dbEnforce(_db !is null, `Failed to connect to '` ~ path ~ `', unknown reason`);
86     }
87 
88     /**
89      * Close DB connection, also frees _db pointer in leveldb lib
90      */
91     @property final
92     auto ref close() nothrow {
93         if(isOpen) {
94             leveldb_close(_db);
95             _db = null;
96         }
97         return this;
98     }
99 
100     @property final
101     auto ref del(K)(in K key, const(WriteOptions) opt = DefaultWriteOptions)
102     {
103         dbEnforce(isOpen, "Not connected to a db");
104         
105         char* errptr = null;
106         scope(exit) if(errptr !is null) leveldb_free(errptr);
107 
108         static if(isPrimitive!K) {
109             auto _key = Slice.make(key);
110             leveldb_delete(_db, opt.ptr, _key.cptr, _key.size, &errptr);
111         } else {
112             const(ubyte)[] keyBuf = pack!K(key);
113             leveldb_delete(_db, opt.ptr, keyBuf.ptr, keyBuf.length, &errptr);
114         }
115 
116         dbEnforce(!errptr);
117         return this;
118     }
119 
120     final
121     auto ref put(K, V)(in K key, in V val, const(WriteOptions) opt = DefaultWriteOptions) {
122         dbEnforce(isOpen, "Not connected to a db");
123 
124         char* errptr = null;
125         scope(exit) if(errptr !is null) leveldb_free(errptr);
126 
127         static if(isPrimitive!K && isPrimitive!V) {
128             auto _key = Slice.make(key);
129             auto _val = Slice.make(val);
130             debug writeln(key, " - ", _key, " ", val, " - ", _val);
131         } else static if (isPrimitive!K) {
132             const(ubyte)[] valBuf = pack!V(val);
133             leveldb_put(_db, opt.ptr, key.ptr, key.size, valBuf.ptr, valBuf.length, &errptr);
134         } else static if (isPrimitive!V) {
135             const(ubyte)[] keyBuf = pack!K(key);
136             leveldb_put(_db, opt.ptr, keyBuf.ptr, keyBuf.length, val.ptr, V.sizeof, &errptr);
137         } else {
138             const(ubyte)[] keyBuf = pack!K(key);
139             const(ubyte)[] valBuf = pack!V(val);
140             leveldb_put(_db, opt.ptr, keyBuf.ptr, keyBuf.length, valBuf.ptr, valBuf.length, &errptr);
141         }
142         leveldb_put(_db, opt.ptr, _key.cptr, _key.size, _val.cptr, _val.size, &errptr);
143         dbEnforce(!errptr);
144         debug writeln("put ", _key.as!string, ", ", _val.as!string, " or ", _val.as!int, " - ", _val.size);
145 
146         return this;
147     }
148 
149     final
150     V find(K, V)(in K key, lazy V def, const(ReadOptions) opt = DefaultReadOptions) {
151         dbEnforce(isOpen, "Not connected to a db");
152         
153         char* errptr = null;
154         scope(exit) if(errptr !is null) leveldb_free(errptr);
155 
156         size_t vallen; // size of the return slice
157 
158         static if(isPrimitive!K) {
159             auto _key = Slice.make(key);
160         } else {
161             auto _key = Slice.make(pack!K(key));
162         }
163         auto _val = Slice.make(leveldb_get(_db, opt.ptr, _key.cptr, _key.size, &vallen, &errptr), vallen, true);
164         dbEnforce(!errptr);
165         debug writeln("find ", _key.as!string, ", ", _val.as!string, " or ", _val.as!int);
166 
167         // Not in db return default
168         if(!_val.ok) {
169             return def;
170         }
171 
172         static if(isPrimitive!V) {
173             return _val.as!V;
174         } else {
175             return unpack!V(cast(ubyte[])valret);
176         }
177     }
178 
179     final
180     bool get(K, V)(in K key, ref V val, const(ReadOptions) opt = DefaultReadOptions) {
181         import std.conv : to;
182 
183         dbEnforce(isOpen, "Not connected to a db");
184         
185         char* errptr = null;
186         scope(exit) if(errptr !is null) leveldb_free(errptr);
187 
188         size_t vallen; // size of the return slice
189 
190         static if(isPrimitive!K) {
191             auto _key = Slice.make(key);
192             auto valret = leveldb_get(_db, opt.ptr, _key.cptr, _key.size, &vallen, &errptr);
193         } else {
194             const(ubyte)[] keyBuf = pack!K(key);
195             auto valret = leveldb_get(_db, opt.ptr, keyBuf.ptr, keyBuf.length, &vallen, &errptr);
196         }
197         scope(exit) if(valret !is null) leveldb_free(valret); // make sure we clean this up
198 
199         dbEnforce(!errptr);
200 
201         // Not in db
202         if (valret is null) {
203             return false;
204         }
205         
206         static if(isSomeString!V) {
207             val = (cast(V)valret[0..vallen]).dup;
208         } else static if(isPrimitive!V) {
209             val = *(cast(V*)valret);
210         } else {
211             val = unpack!V(cast(ubyte[])valret);
212         }
213         return true;
214     }
215 
216 
217     /+
218 
219     /**
220      * Sublmits a BatchWrite to the DB.
221      *
222      * Used to do batch writes.
223      *
224      * Example:
225      ---
226      auto opt = new Options;
227      opt.create_if_missing = true;
228      auto db = new DB(opt, "/my/db/");
229      // Unsafe banking example
230      auto batch = new WriteBatch;
231 
232      double joe = db.get_slice(Slice("Joe")).as!double;
233      double sally = db.get_slice(Slice("Sally")).as!double;
234 
235      joe -= 10.00;
236      sally += 10.00;
237      if(joe < 0.0)
238         joe -= 30.00; // overdraft fee
239 
240      // submit the put in a single update
241      batch.put(Slice("Joe"), Slice(joe));
242      batch.put(Slice("Sally"), Slice(sally));
243      db.write(batch);
244      ---
245      * Throws: LeveldbException
246      */
247     void write(const(WriteBatch) batch, const(WriteOptions) opt = DefaultWriteOptions)
248     {
249         if(!isOpen) throw new LeveldbException(`Not connected to a valid db`);
250 
251         char* errptr = null;
252         scope(failure) if(errptr) leveldb_free(errptr);
253 
254         leveldb_write(_db, opt.ptr, cast(leveldb_writebatch_t)batch.ptr, &errptr);
255         if(errptr) throw new LeveldbException(errptr);
256     }
257 
258     /** 
259      * Returns a readonly snapshot
260      *
261      * Throws: LeveldbException
262      */
263     @property
264     ASnapshot snapshot()
265     {
266         if(!isOpen) throw new LeveldbException(`Not connected to a valid db`);
267         return new Snapshot();
268     }
269 
270     /* 
271      * Returns a database iterator
272      *
273      * Throws: LeveldbException
274      */
275     @property
276     Iterator iterator(const(ReadOptions) opt = DefaultReadOptions)
277     {
278         if(!isOpen) throw new LeveldbException(`Not connected to a valid db`);
279         return new Iterator(opt);
280     }
281 
282     /**
283      * Short cut iterator, treate the db like an iterator
284      */
285     int opApply(int delegate(Slice) dg)
286     {
287         return iterator.opApply(dg);
288     }
289 
290     int opApplyReverse(int delegate(Slice) dg)
291     {
292         return iterator.opApplyReverse(dg);
293     }
294 
295     int opApply(int delegate(Slice, Slice) dg)
296     {
297         return iterator.opApply(dg);
298     }
299 
300     int opApplyReverse(int delegate(Slice, Slice) dg)
301     {
302         return iterator.opApplyReverse(dg);
303     }
304 
305     /**
306      * DB Snapshot
307      *
308      * Snapshots can be applied to ReadOptions.  Created from a DB object
309      *
310      * Example:
311      ---
312      auto opt = new Options;
313      opt.create_if_missing = true;
314      auto db = new DB(opt, "/my/db/")
315 
316      auto snap = db.snapshot;
317      db.put(Slice("Future"), Slice("Stuff"));
318 
319      auto ro = new ReadOptions;
320      ro.snapshot(snap);
321 
322      string str;
323      assert(db.get(Slice("Future"), str));
324      assert(!db.get(Slice("Future"), str, ro));
325      ---
326      * Throws: LeveldbException
327      */
328     class Snapshot : ASnapshot
329     {
330     private:
331         leveldb_snapshot_t _snap;
332 
333     public:
334         @property 
335         override inout(leveldb_snapshot_t) ptr() inout
336         {
337             return _snap;
338         }
339 
340         this()
341         {
342             if((_snap = cast(leveldb_snapshot_t)leveldb_create_snapshot(_db)) is null)
343                 throw new LeveldbException("Failed to create snapshot");
344         }
345 
346         /** Cleanup snapshot memory */
347         ~this()
348         {
349             if(valid)
350             {
351                 leveldb_release_snapshot(_db, _snap);
352                 _snap = null;
353             }
354         }
355 
356         /// test if the snapshot has been created
357         @property 
358         bool valid() inout
359         {
360             return _snap !is null;
361         }
362     } // SnapShot
363 
364     /**
365      * DB Iterator
366      *
367      * Can iterate the db
368      *
369      * Example:
370      ---
371      auto opt = new Options;
372      opt.create_if_missing = true;
373      auto db = new DB(opt, "/my/db/")
374 
375      auto it = db.iterator;
376      foreach(Slice key, Slice value; it)
377      {
378         writeln(key.as!string, " - ", value.as!string);
379      }
380     
381      ---
382      * Throws: LeveldbException
383      */
384     class Iterator
385     {
386     private:
387         leveldb_iterator_t _iter;
388 
389     package:
390         @property
391         inout(leveldb_iterator_t) ptr() inout
392         {
393             return _iter;
394         }
395 
396     public:
397         this(const(ReadOptions) opt = DefaultReadOptions)
398         {
399             if((_iter = leveldb_create_iterator(_db, opt.ptr)) is null)
400                 throw new LeveldbException("Failed to create iterator");
401         }
402 
403         ~this()
404         {
405             if(ok)
406             {
407                 leveldb_iter_destroy(_iter);
408                 _iter = null;
409             }
410         }
411 
412         /// Iterator created
413         @property
414         bool ok() inout
415         {
416             return _iter !is null;
417         }
418 
419         /// Iterator has more data to read
420         @property
421         bool valid() inout
422         {
423             return cast(bool)leveldb_iter_valid(_iter);
424         }
425 
426 	@property
427 	bool empty() inout
428 	{
429 		return !valid;
430 	}
431 
432         /// Seek to front of data
433         @property 
434         void seek_to_first()
435         {
436             leveldb_iter_seek_to_first(_iter);
437         }
438 
439         /// Seek to end of data
440         @property 
441         void seek_to_last()
442         {
443             leveldb_iter_seek_to_last(_iter);
444         }
445 
446         /// Seek to given slice.
447         @property
448         void seek(T)(T key)
449         {
450             leveldb_iter_seek(_iter, key._lib_obj_ptr__, key._lib_obj_size__);
451         }
452 
453         /// Move to next item
454         @property
455         void next()
456         {
457             leveldb_iter_next(_iter);
458         }
459 
460 	alias next popFront;
461 
462         /// Move to previous item
463         @property
464         void prev()
465         {
466             leveldb_iter_prev(_iter);
467         }
468 
469         /// Return the current key
470         @property
471         Slice key()
472         {
473             debug if(!valid) throw new LeveldbException("Accessing invalid iterator");
474             size_t vallen;
475             void* val = cast(void*)leveldb_iter_key(_iter, &vallen);
476             scope(failure) if(val) leveldb_free(val);
477             return Slice(val, vallen, false);
478         }
479 
480         /// Return the current value
481         @property
482         auto value()
483         {
484             debug if(!valid) throw new LeveldbException("Accessing invalid iterator");
485             size_t vallen;
486             void* val = cast(void*)leveldb_iter_value(_iter, &vallen);
487             scope(failure) if(val) leveldb_free(val);
488             return Slice(val, vallen, false);
489         }
490 
491 	/// return the front of the iterator
492 	@property
493 	auto front()
494 	{
495 		return [key, value];
496 	}
497 
498         /// Gets the current error status of the iterator
499         @property
500         string status() inout
501         {
502             char* errptr = null;
503             scope(exit) if(errptr) leveldb_free(cast(void*)errptr);
504             leveldb_iter_get_error(_iter, &errptr);
505             return to!string(errptr);
506         }
507 
508         /// For each on iterator
509         int opApply(int delegate(Slice) dg)
510         {
511             int result = 0;
512             for(seek_to_first; valid; next)
513             {
514                 result = dg(value);
515                 if(result) return result;
516             }
517             return result;
518         }
519 
520         int opApplyReverse(int delegate(Slice) dg)
521         {
522             int result = 0;
523             for(seek_to_last; valid; prev)
524             {
525                 result = dg(value);
526                 if(result) return result;
527             }
528             return result;
529         }
530 
531         int opApply(int delegate(Slice, Slice) dg)
532         {
533             int result = 0;
534             for(seek_to_first; valid; next)
535             {
536                 result = dg(key, value);
537                 if(result) return result;
538             }
539             return result;
540         }
541 
542         int opApplyReverse(int delegate(Slice, Slice) dg)
543         {
544             int result = 0;
545             for(seek_to_last; valid; prev)
546             {
547                 result = dg(key, value);
548                 if(result) return result;
549             }
550             return result;
551         }
552     } //Iterator
553     +/
554 
555     /**
556      * Tests if the database is open
557      *
558      * Returns: true if there is an open database
559      */
560     @property final
561     bool isOpen() inout pure nothrow
562     {
563         return _db !is null;
564     }
565 
566     /**
567      * Destory/delete a non-locked leveldb
568      */
569     static final
570     void destroy(in string path, const(Options) opt) {
571         char* errptr = null;
572         scope(exit) if(errptr) leveldb_free(errptr);
573 
574         leveldb_destroy_db(opt.ptr, toStringz(path), &errptr);
575         
576         dbEnforce(!errptr);
577     }
578 
579     /**
580      * Attempt to repair a non-locked leveldb
581      */
582     static final
583     void repair(const Options opt, string path)
584     {
585         char* errptr = null;
586         scope(exit) if(errptr) leveldb_free(errptr);
587 
588         leveldb_repair_db(opt.ptr, toStringz(path), &errptr);
589 
590         dbEnforce(!errptr);
591     }
592 } // class DB
593 
594 
595 // Basic open, and close
596 unittest {
597     import std.file, std.path;
598     auto opt = new Options;
599     opt.create_if_missing = true;
600     auto db = new DB!(null, null)(buildNormalizedPath(tempDir, "d-leveldb_unittest.db"), opt);
601     assert(db.isOpen);
602     assert(!db.close.isOpen);
603 }
604 
605 // Putting in basic values and getting them back
606 unittest {
607     import std.file, std.path;
608     auto opt = new Options;
609     opt.create_if_missing = true;
610     auto db = new DB!(null, null)(buildNormalizedPath(tempDir, "d-leveldb_unittest.db"), opt);
611     assert(db.isOpen);
612     db.put("testing", 123).put(123, "blah");
613     assert(db.find("testing", 5566) == 123);
614     db.del("testing").del("nottesting");
615     assert(db.find("testing", 5566) == 5566);
616     db.put('a', 35.46);
617     assert(db.find('a', 5566.36) == 35.46);
618     assert(db.find(123, "null") == "blah", "|" ~ db.find(123, "null") ~ "| != " ~ "blah");
619     db.del(123);
620     assert(db.find(123, "null") == "null");
621     assert(!db.close.isOpen);
622     db = new DB!(null, null)(buildNormalizedPath(tempDir, "d-leveldb_unittest.db"), opt);
623     assert(db.find(123, "null") == "null");
624     db.close;
625 }
626 
627 // Putting in basic values and getting them back
628 unittest {
629     import std.file, std.path;
630     auto opt = new Options;
631     opt.create_if_missing = true;
632     auto db = new DB!(null, null)(buildNormalizedPath(tempDir, "d-leveldb_unittest.db"), opt);
633     assert(db.isOpen);
634     db.put("testing", 123).put(123, "blah");
635 
636     int x;
637     assert(db.get("testing", x));
638     assert(x == 123);
639     assert(!db.get("Testing", x));
640     assert(x == 123);
641 
642     string y;
643     assert(db.get(123, y));
644     assert(y == "blah");
645 
646     db.close;
647 }