Class: Lithos::DB

Inherits:
Object
  • Object
show all
Includes:
Enumerable
Defined in:
lib/lithos.rb,
ext/lithos/lithos.cpp

Instance Method Summary collapse

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

Hash-like access. ‘db` => value or nil; `db = v` stores v.



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

#_closeObject



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;
}

#_compactObject



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;
}

#_flushObject



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 */
}

#closeObject



62
63
64
65
# File 'lib/lithos.rb', line 62

def close
  forbid_during_iteration!
  _close
end

#closed?Boolean

Returns:

  • (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;
}

#compactObject



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_keyObject

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

Returns:

  • (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.)

Raises:

  • (KeyError)


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

#flushObject



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;
}

#inspectObject



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?

Returns:

  • (Boolean)


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;
}

#pathObject



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

#sizeObject 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_hObject



137
138
139
140
141
# File 'lib/lithos.rb', line 137

def to_h
  h = {}
  each { |k, v| h[k] = v }
  h
end