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 }