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