Class: Lithos::DB
- Inherits:
-
Object
- Object
- Lithos::DB
- Includes:
- Enumerable
- Defined in:
- lib/lithos.rb,
ext/lithos/lithos.cpp
Instance Method Summary collapse
-
#[](key) ⇒ Object
Hash-like access.
- #[]=(key, value) ⇒ Object
- #_close ⇒ Object
- #_compact ⇒ Object
- #_delete(key) ⇒ Object
- #_each_range(vlo, vloi, vhi, vhii) ⇒ Object
- #_flush ⇒ Object
-
#_put(key, val) ⇒ Object
Mutators are primitives wrapped by the Ruby layer, which forbids calling them during iteration (a pure-Ruby raise — never a longjmp through C++).
- #close ⇒ Object
- #closed? ⇒ Boolean
- #compact ⇒ Object
- #delete(key) ⇒ Object
-
#each(&block) ⇒ Object
(also: #each_pair)
Iterate all pairs in ascending (unsigned-byte) key order.
-
#each_key ⇒ Object
Iterate keys only, ascending.
- #empty? ⇒ Boolean
-
#fetch(key, *default) ⇒ Object
Like Hash#fetch: return the value, else the block result, else the default, else raise KeyError.
- #flush ⇒ Object
-
#get(key) ⇒ Object
chainable; ‘db=v` still evaluates to v per Ruby semantics.
- #initialize(*args) ⇒ Object constructor
- #inspect ⇒ Object
- #key?(key) ⇒ Boolean (also: #include?, #has_key?, #member?)
- #path ⇒ Object
-
#put(key, value) ⇒ Object
— mutators (forbidden while an each/scan is in progress) ————– The merge iterator holds raw pointers into memory-mapped SSTables and live memtable iterators; mutating mid-scan would free them underneath it.
-
#range(from, to, &block) ⇒ Object
Half-open range [from, to) in ascending order.
-
#scan(gte: nil, gt: nil, lte: nil, lt: nil, &block) ⇒ Object
Ordered range scan.
-
#size ⇒ Object
(also: #length)
Number of live keys.
- #to_h ⇒ Object
Constructor Details
#initialize(*args) ⇒ Object
851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 |
# File 'ext/lithos/lithos.cpp', line 851
static VALUE db_initialize(int argc, VALUE *argv, VALUE self) {
VALUE vpath, vopts;
rb_scan_args(argc, argv, "1:", &vpath, &vopts);
StringValue(vpath);
DBWrap *w; TypedData_Get_Struct(self, DBWrap, &db_type, w);
DB *db = w->db;
if (!db->closed) rb_raise(eError, "lithos: store is already open"); /* no double-init */
db->dir_utf8.assign(RSTRING_PTR(vpath), (size_t)RSTRING_LEN(vpath));
db->dir = utf8_to_wide(db->dir_utf8.data(), db->dir_utf8.size());
if (!NIL_P(vopts)) {
VALUE s = rb_hash_aref(vopts, ID2SYM(rb_intern("sync")));
if (!NIL_P(s)) db->sync_writes = RTEST(s);
VALUE ml = rb_hash_aref(vopts, ID2SYM(rb_intern("memtable_bytes")));
if (!NIL_P(ml)) db->memtable_limit = (size_t)NUM2ULL(ml);
}
/* All filesystem work (lock, load SSTables, WAL replay) happens in
bootstrap(), which never raises; we raise here from a clean frame. */
if (!db->bootstrap()) {
db->release();
rb_raise(eError, "lithos: %s", db->errbuf);
}
return self;
}
|
Instance Method Details
#[](key) ⇒ Object
68 69 70 |
# File 'lib/lithos.rb', line 68 def [](key) get(key) end |
#[]=(key, value) ⇒ Object
72 73 74 75 |
# File 'lib/lithos.rb', line 72 def []=(key, value) put(key, value) value end |
#_close ⇒ Object
987 988 989 990 991 992 993 994 995 |
# File 'ext/lithos/lithos.cpp', line 987
static VALUE db_close(VALUE self) {
DBWrap *w; TypedData_Get_Struct(self, DBWrap, &db_type, w);
if (w && w->db && !w->db->closed) {
DB *db = w->db;
db->flush(); /* best-effort clean shutdown; data is already WAL-durable */
db->release();
}
return Qnil;
}
|
#_compact ⇒ Object
981 982 983 984 985 |
# File 'ext/lithos/lithos.cpp', line 981
static VALUE db_compact(VALUE self) {
DB *db = get_db(self);
if (!db->compact()) rb_raise(eError, "lithos: %s", db->errbuf);
return self;
}
|
#_delete(key) ⇒ Object
925 926 927 928 929 930 931 932 933 934 935 936 937 938 939 940 941 942 |
# File 'ext/lithos/lithos.cpp', line 925
static VALUE db_delete(VALUE self, VALUE key) {
DB *db = get_db(self);
bool existed = RTEST(db_key_p(self, key));
bool ok;
{
std::string k = to_bytes(key);
ok = db->wal_append(KIND_TOMB, k, std::string());
if (!ok) db->fail("WAL append");
else {
Record r; r.kind = KIND_TOMB;
db->mem[k] = r;
db->mem_bytes += k.size();
ok = db->maybe_flush();
}
} /* k freed before any raise */
if (!ok) rb_raise(eError, "lithos: %s", db->errbuf);
return existed ? Qtrue : Qfalse;
}
|
#_each_range(vlo, vloi, vhi, vhii) ⇒ Object
947 948 949 950 951 952 953 954 955 956 957 958 959 960 961 962 963 964 965 966 967 968 969 970 971 972 973 974 |
# File 'ext/lithos/lithos.cpp', line 947
static VALUE db_each_range(VALUE self, VALUE vlo, VALUE vloi, VALUE vhi, VALUE vhii) {
DB *db = get_db(self);
if (!rb_block_given_p())
rb_raise(rb_eLocalJumpError, "lithos: no block given");
MergeRun *m = new MergeRun();
m->db = db;
{
/* Convert the bounds FIRST (StringValue can raise TypeError); copy into
the heap MergeRun so these stack locals are gone before any later jump. */
bool have_lo = !NIL_P(vlo), have_hi = !NIL_P(vhi);
const std::string *lo = NULL, *hi = NULL;
try {
if (have_lo) { m->lo = to_bytes(vlo); lo = &m->lo; }
if (have_hi) { m->hi = to_bytes(vhi); hi = &m->hi; }
db->build_sources(m->src, lo, RTEST(vloi), hi, RTEST(vhii));
} catch (...) { delete m; rb_raise(eError, "lithos: out of memory building scan"); }
}
/* Run the merge. merge_yield catches block exceptions/breaks via rb_protect
and returns a Ruby tag; NO longjmp crosses a C++ frame. We free our state,
then re-raise the tag from this clean C frame. (Mutation-during-iteration
is forbidden in the Ruby layer, which keeps that guard a pure-Ruby raise.) */
int state = merge_yield(m->src, m->ek, m->ev);
delete m;
if (state) rb_jump_tag(state); /* propagate the block's break/exception cleanly */
return self;
}
|
#_flush ⇒ Object
976 977 978 979 980 |
# File 'ext/lithos/lithos.cpp', line 976
static VALUE db_flush(VALUE self) {
DB *db = get_db(self);
if (!db->flush()) rb_raise(eError, "lithos: %s", db->errbuf);
return self;
}
|
#_put(key, val) ⇒ Object
Mutators are primitives wrapped by the Ruby layer, which forbids calling
them during iteration (a pure-Ruby raise — never a longjmp through C++).
877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 |
# File 'ext/lithos/lithos.cpp', line 877
static VALUE db_put(VALUE self, VALUE key, VALUE val) {
DB *db = get_db(self);
StringValue(key); StringValue(val); /* TypeError here: no C++ object live yet */
bool ok;
{
std::string k(RSTRING_PTR(key), (size_t)RSTRING_LEN(key));
std::string v(RSTRING_PTR(val), (size_t)RSTRING_LEN(val));
ok = db->wal_append(KIND_VALUE, k, v);
if (!ok) db->fail("WAL append");
else {
Record r; r.kind = KIND_VALUE; r.value = v;
db->mem[k] = r;
db->mem_bytes += k.size() + v.size();
ok = db->maybe_flush();
}
} /* k, v freed before any raise */
if (!ok) rb_raise(eError, "lithos: %s", db->errbuf);
return self; /* chainable; `db[k]=v` still evaluates to v per Ruby semantics */
}
|
#close ⇒ Object
62 63 64 65 |
# File 'lib/lithos.rb', line 62 def close forbid_during_iteration! _close end |
#closed? ⇒ Boolean
996 997 998 999 |
# File 'ext/lithos/lithos.cpp', line 996
static VALUE db_closed_p(VALUE self) {
DBWrap *w; TypedData_Get_Struct(self, DBWrap, &db_type, w);
return (w && w->db && !w->db->closed) ? Qfalse : Qtrue;
}
|
#compact ⇒ Object
57 58 59 60 |
# File 'lib/lithos.rb', line 57 def compact forbid_during_iteration! _compact end |
#delete(key) ⇒ Object
47 48 49 50 |
# File 'lib/lithos.rb', line 47 def delete(key) forbid_during_iteration! _delete(key) end |
#each(&block) ⇒ Object Also known as: each_pair
Iterate all pairs in ascending (unsigned-byte) key order.
97 98 99 100 101 |
# File 'lib/lithos.rb', line 97 def each(&block) return enum_for(:each) unless block bounded_each(nil, false, nil, false, &block) end |
#each_key ⇒ Object
Iterate keys only, ascending.
105 106 107 108 109 |
# File 'lib/lithos.rb', line 105 def each_key return enum_for(:each_key) unless block_given? bounded_each(nil, false, nil, false) { |k, _| yield k } end |
#empty? ⇒ Boolean
132 133 134 135 |
# File 'lib/lithos.rb', line 132 def empty? each { return false } true end |
#fetch(key, *default) ⇒ Object
Like Hash#fetch: return the value, else the block result, else the default, else raise KeyError. (Stored values are never nil, so nil == absent.)
79 80 81 82 83 84 85 86 87 88 89 90 |
# File 'lib/lithos.rb', line 79 def fetch(key, *default) if default.size > 1 raise ArgumentError, "wrong number of arguments (given #{default.size + 1}, expected 1..2)" end value = get(key) return value unless value.nil? return yield(key) if block_given? return default.first unless default.empty? raise KeyError, "key not found: #{key.inspect}" end |
#flush ⇒ Object
52 53 54 55 |
# File 'lib/lithos.rb', line 52 def flush forbid_during_iteration! _flush end |
#get(key) ⇒ Object
chainable; ‘db=v` still evaluates to v per Ruby semantics
897 898 899 900 901 902 903 904 905 906 907 908 909 |
# File 'ext/lithos/lithos.cpp', line 897
static VALUE db_get(VALUE self, VALUE key) {
DB *db = get_db(self);
std::string k = to_bytes(key);
Memtable::const_iterator it = db->mem.find(k);
if (it != db->mem.end()) return it->second.kind == KIND_VALUE ? from_bytes(it->second.value) : Qnil;
std::string out;
for (size_t i = db->ssts.size(); i-- > 0;) {
int r = db->ssts[i]->get(k, &out);
if (r == 1) return from_bytes(out);
if (r == 2) return Qnil; /* tombstone shadows older */
}
return Qnil;
}
|
#inspect ⇒ Object
143 144 145 |
# File 'lib/lithos.rb', line 143 def inspect "#<Lithos::DB path=#{path.inspect}#{closed? ? ' (closed)' : ''}>" end |
#key?(key) ⇒ Boolean Also known as: include?, has_key?, member?
911 912 913 914 915 916 917 918 919 920 921 922 923 |
# File 'ext/lithos/lithos.cpp', line 911
static VALUE db_key_p(VALUE self, VALUE key) {
DB *db = get_db(self);
std::string k = to_bytes(key);
Memtable::const_iterator it = db->mem.find(k);
if (it != db->mem.end()) return it->second.kind == KIND_VALUE ? Qtrue : Qfalse;
std::string out;
for (size_t i = db->ssts.size(); i-- > 0;) {
int r = db->ssts[i]->get(k, &out);
if (r == 1) return Qtrue;
if (r == 2) return Qfalse;
}
return Qfalse;
}
|
#path ⇒ Object
1000 1001 1002 1003 1004 |
# File 'ext/lithos/lithos.cpp', line 1000
static VALUE db_path(VALUE self) {
DBWrap *w; TypedData_Get_Struct(self, DBWrap, &db_type, w);
/* the path is conceptually UTF-8 text (not binary like keys/values) */
return rb_utf8_str_new(w->db->dir_utf8.data(), (long)w->db->dir_utf8.size());
}
|
#put(key, value) ⇒ Object
— mutators (forbidden while an each/scan is in progress) ————– The merge iterator holds raw pointers into memory-mapped SSTables and live memtable iterators; mutating mid-scan would free them underneath it. We refuse here with a plain Ruby exception (which unwinds safely), rather than let it reach the C engine during iteration.
42 43 44 45 |
# File 'lib/lithos.rb', line 42 def put(key, value) forbid_during_iteration! _put(key, value) end |
#range(from, to, &block) ⇒ Object
Half-open range [from, to) in ascending order.
120 121 122 |
# File 'lib/lithos.rb', line 120 def range(from, to, &block) bounded_each(from, true, to, false, &block) end |
#scan(gte: nil, gt: nil, lte: nil, lt: nil, &block) ⇒ Object
Ordered range scan. Bounds are optional; gt/lt are exclusive, gte/lte inclusive. Returns an Enumerator without a block.
113 114 115 116 117 |
# File 'lib/lithos.rb', line 113 def scan(gte: nil, gt: nil, lte: nil, lt: nil, &block) lower, lower_incl = gt ? [gt, false] : (gte ? [gte, true] : [nil, false]) upper, upper_incl = lt ? [lt, false] : (lte ? [lte, true] : [nil, false]) bounded_each(lower, lower_incl, upper, upper_incl, &block) end |
#size ⇒ Object Also known as: length
Number of live keys. O(n) — counts via an ordered scan.
125 126 127 128 129 |
# File 'lib/lithos.rb', line 125 def size n = 0 each_key { n += 1 } n end |
#to_h ⇒ Object
137 138 139 140 141 |
# File 'lib/lithos.rb', line 137 def to_h h = {} each { |k, v| h[k] = v } h end |