class LMDB::Database

An LMDB Database is a table of key-value pairs. It is stored as part of the {Environment}.

By default, each key in a Database maps to one value. However, a Database can be configured at creation to allow duplicate keys, in which case one key will map to multiple values.

A Database stores the keys in a sorted order. The order can also be set with options when the database is created.

The basic operations on a database are to {#put}, {#get}, and {#delete} records. One can also iterate through the records in a database using a {Cursor}.

@example Typical usage

env = LMDB.new "databasedir"
db = env.database "databasename"
db.put "key1", "value1"
db.put "key2", "value2"
db.get "key1"              #=> "value1"
env.close

Public Instance Methods

[](key) click to toggle source

Retrieve the value of a record from a database @param key the record key to retrieve @return value of the record for that key, or nil if there is

no record with that key

@see get(key)

# File lib/lmdb/database.rb, line 29
def [](key)
  get(key)
end
[]=(key, value) click to toggle source

Set (write or update) a record in a database. @param key key for the record @param value the value of the record @return returns the value of the record @see put(key, value) @example

db['a'] = 'b'     #=> 'b'
db['b'] = 1234    #=> 1234
db['a']           #=> 'b'
# File lib/lmdb/database.rb, line 42
def []=(key, value)
  put key, value
  value
end
cardinality(key) click to toggle source

Return the cardinality (number of duplicates) of a given key. Works whether :dupsort is set or not. @param key [#to_s] The key in question. @return [Integer] The number of entries under the key.

# File lib/lmdb/database.rb, line 100
def cardinality(key)
  ret = 0
  maybe_txn true do
  # env.transaction do
    if get key
      if dupsort?
        cursor do |c|
          c.set key
          ret = c.count
        end
      else
        ret = 1
      end
    end
  end
  ret
end
clear() click to toggle source

@overload clear

Empty out the database
@return nil
@note The clear happens transactionally.
static VALUE database_clear(VALUE self) {
        DATABASE(self, database);
        if (!active_txn(database->env))
                return call_with_transaction(database->env, self, "clear", 0, 0, 0);
        check(mdb_drop(need_txn(database->env), database->dbi, 0));
        return Qnil;
}
cursor() click to toggle source

@overload cursor

Create a cursor to iterate through a database. Uses current
transaction, if any. Otherwise, if called with a block,
creates a new transaction for the scope of the block.
Otherwise, fails.

@see Cursor
@yield [cursor] A block to be executed with the cursor.
@yieldparam cursor [Cursor] The cursor to be used to iterate
@example
 db = env.database "abc"
 db.cursor do |c|
   key, value = c.next
   puts "#{key}: #{value}"
 end
static VALUE database_cursor(VALUE self) {
        DATABASE(self, database);
        if (!active_txn(database->env)) {
                if (!rb_block_given_p()) {
                        rb_raise(cError, "Must call with block or active transaction.");
                }
                return call_with_transaction(database->env, self, "cursor", 0, 0, 0);
        }

        MDB_cursor* cur;
        check(mdb_cursor_open(need_txn(database->env), database->dbi, &cur));

        Cursor* cursor;
        VALUE vcur = Data_Make_Struct(cCursor, Cursor, cursor_mark, cursor_free, cursor);
        cursor->cur = cur;
        cursor->db = self;

        if (rb_block_given_p()) {
                int exception;
                VALUE ret = rb_protect(rb_yield, vcur, &exception);
                if (exception) {
                        cursor_close(vcur);
                        rb_jump_tag(exception);
                }
                cursor_close(vcur);
                return ret;
        }
        else {
                VALUE vtxn = environment_active_txn(database->env);
                if (NIL_P(vtxn)) {
                        rb_fatal("Internal error: transaction finished unexpectedly.");
                }
                else {
                        TRANSACTION(vtxn, txn);
                        rb_ary_push(txn->cursors, vcur);
                }
        }

        return vcur;
}
delete(p1, p2 = v2) click to toggle source

@overload delete(key, value=nil)

Deletes records from the database. This function removes key/data pairs from the database. If the database does not support sorted duplicate data items (:dupsort) the value parameter is ignored. If the database supports sorted duplicates and the value parameter is nil, all of the duplicate data items for the key will be deleted. Otherwise, if the data parameter is non-nil only the matching data item will be deleted.

@param key The key of the record to delete. @param value The optional value of the record to delete. @raise [Error] if the specified key/value pair is not in the database.

static VALUE database_delete(int argc, VALUE *argv, VALUE self) {
        DATABASE(self, database);
        if (!active_txn(database->env))
                return call_with_transaction(database->env, self, "delete", argc, argv, 0);

        VALUE vkey, vval;
        rb_scan_args(argc, argv, "11", &vkey, &vval);

        vkey = StringValue(vkey);

        MDB_val key;
        key.mv_size = RSTRING_LEN(vkey);
        key.mv_data = RSTRING_PTR(vkey);

        if (NIL_P(vval)) {
                check(mdb_del(need_txn(database->env), database->dbi, &key, 0));
        } else {
                vval = StringValue(vval);
                MDB_val value;
                value.mv_size = RSTRING_LEN(vval);
                value.mv_data = RSTRING_PTR(vval);
                check(mdb_del(need_txn(database->env), database->dbi, &key, &value));
        }

        return Qnil;
}
delete?(key, value = nil) click to toggle source

Delete the key (and optional value pair) if it exists; do not complain about missing keys. @param key [#to_s] The key. @param value [#to_s] The optional value.

# File lib/lmdb/database.rb, line 139
def delete?(key, value = nil)
  delete key, value if has? key, value
end
drop() click to toggle source

@overload drop

Remove a database from the environment.
@return nil
@note The drop happens transactionally.
static VALUE database_drop(VALUE self) {
        DATABASE(self, database);
        if (!active_txn(database->env))
                return call_with_transaction(database->env, self, "drop", 0, 0, 0);
        check(mdb_drop(need_txn(database->env), database->dbi, 1));
        return Qnil;
}
dupfixed?() click to toggle source

@overload dupfixed?

Returns whether the database is in +:dupfixed+ mode.
@return [true, false]
static VALUE database_is_dupfixed(VALUE self) {
        DATABASE(self, database);
        if (!active_txn(database->env))
                return call_with_transaction(database->env, self,
                                             "dupfixed?", 0, 0, MDB_RDONLY);
        unsigned int flags;
        check(mdb_dbi_flags(need_txn(database->env), database->dbi, &flags));

        return (flags & MDB_DUPFIXED) == 0 ? Qfalse : Qtrue;
}
dupsort?() click to toggle source

@overload dupsort?

Returns whether the database is in +:dupsort+ mode.
@return [true, false]
static VALUE database_is_dupsort(VALUE self) {
        DATABASE(self, database);
        if (!active_txn(database->env))
                return call_with_transaction(database->env, self,
                                             "dupsort?", 0, 0, MDB_RDONLY);
        unsigned int flags;
        check(mdb_dbi_flags(need_txn(database->env), database->dbi, &flags));

        return (flags & MDB_DUPSORT) == 0 ? Qfalse : Qtrue;
}
each() { |i| ... } click to toggle source

Iterate through the records in a database @yield [i] Gives a record [key, value] to the block @yieldparam [Array] i The key, value pair for each record @example

db.each do |record|
  key, value = record
  puts "at #{key}: #{value}"
end
# File lib/lmdb/database.rb, line 13
def each
  maybe_txn true do
  # env.transaction do
    cursor do |c|
      while i = c.next
        yield(i)
      end
    end
  end
end
each_key() { |first| ... } click to toggle source

Iterate over each key in the database, skipping over duplicate records.

@yield key [String] the next key in the database. @return [Enumerator] in lieu of a block.

# File lib/lmdb/database.rb, line 57
def each_key(&block)
  return enum_for :each_key unless block_given?
  maybe_txn true do
  #env.transaction do
    cursor do |c|
      while (rec = c.next true)
        yield rec.first
      end
    end
  end
end
each_value(key) { |value| ... } click to toggle source

Iterate over the duplicate values of a given key, using an implicit cursor. Works whether :dupsort is set or not.

@param key [#to_s] The key in question. @yield value [String] the next value associated with the key. @return [Enumerator] in lieu of a block.

# File lib/lmdb/database.rb, line 75
def each_value(key, &block)
  return enum_for :each_value, key unless block_given?

  value = get(key) or return
  unless dupsort?
    yield value
    return
  end

  maybe_txn true do
  # env.transaction do
    cursor do |c|
      method = :set
      while rec = c.send(method, key)
        method = :next_range
        yield rec[1]
      end
    end
  end
end
env() click to toggle source

@overload env

@return [Environment] the environment to which this database belongs.
static VALUE database_env(VALUE self) {
        DATABASE(self, database);
        return database->env;
}
flags() click to toggle source

@overload flags

Return the flags used to open the database.
@return [Hash] The flags.
static VALUE database_get_flags(VALUE self) {
        DATABASE(self, database);
        if (!active_txn(database->env))
                return call_with_transaction(database->env,
                                             self, "flags", 0, 0, MDB_RDONLY);
        unsigned int flags;
        check(mdb_dbi_flags(need_txn(database->env), database->dbi, &flags));
        return flags2hash(flags);
}
get(p1) click to toggle source

@overload get(key)

Retrieves one value associated with this key.
This function retrieves key/data pairs from the database.  If the
database supports duplicate keys (+:dupsort+) then the first data
item for the key will be returned. Retrieval of other items
requires the use of {#cursor}.
@param key The key of the record to retrieve.
static VALUE database_get(VALUE self, VALUE vkey) {
        DATABASE(self, database);
        if (!active_txn(database->env))
                return call_with_transaction(database->env, self, "get", 1, &vkey, MDB_RDONLY);

        vkey = StringValue(vkey);
        MDB_val key, value;
        key.mv_size = RSTRING_LEN(vkey);
        key.mv_data = RSTRING_PTR(vkey);

        int ret = mdb_get(need_txn(database->env), database->dbi, &key, &value);
        if (ret == MDB_NOTFOUND)
                return Qnil;
        check(ret);
        return rb_str_new(value.mv_data, value.mv_size);
}
has?(key, value = nil) click to toggle source

Test if the database has a given key (or, if opened in :dupsort, value)

# File lib/lmdb/database.rb, line 120
def has?(key, value = nil)
  v = get(key) or return false
  return true if value.nil? or value.to_s == v
  return false unless dupsort?

  ret = false
  # read-only txn was having trouble being nested inside a read-write
  maybe_txn true do
  # env.transaction true do
  # env.transaction do
    cursor { |c| ret = !!c.set(key, value) }
  end
  ret
end
keys() click to toggle source

Get the keys as an array. @return [Array] of keys.

# File lib/lmdb/database.rb, line 49
def keys
  each_key.to_a
end
put(p1, p2, p3 = {}) click to toggle source

@overload put(key, value, options)

Stores items into a database.
This function stores key/value pairs in the database. The default
behavior is to enter the new key/value pair, replacing any
previously existing key if duplicates are disallowed, or adding a
duplicate data item if duplicates are allowed (+:dupsort+).
@param key The key of the record to set
@param value The value to insert for this key
@option options [Boolean] :nodupdata Enter the new key/value
    pair only if it does not already appear in the database. This
    flag may only be specified if the database was opened with
    +:dupsort+. The function will raise an {Error} if the
    key/data pair already appears in the database.
@option options [Boolean] :nooverwrite Enter the new key/value
    pair only if the key does not already appear in the
    database. The function will raise an {Error] if the key
    already appears in the database, even if the database
    supports duplicates (+:dupsort+).
@option options [Boolean] :append Append the given key/data pair
    to the end of the database. No key comparisons are
    performed. This option allows fast bulk loading when keys are
    already known to be in the correct order. Loading unsorted
    keys with this flag will cause data corruption.
@option options [Boolean] :appenddup As above, but for sorted dup
    data.
static VALUE database_put(int argc, VALUE *argv, VALUE self) {
    DATABASE(self, database);
    if (!active_txn(database->env))
        return call_with_transaction(database->env, self, "put", argc, argv, 0);

    VALUE vkey, vval, option_hash = Qnil;
#ifdef RB_SCAN_ARGS_KEYWORDS
    rb_scan_args_kw(RB_SCAN_ARGS_LAST_HASH_KEYWORDS,
                    argc, argv, "20:", &vkey, &vval, &option_hash);
#else
    rb_scan_args(argc, argv, "20:", &vkey, &vval, &option_hash);
#endif

    int flags = 0;
    if (!NIL_P(option_hash))
        rb_hash_foreach(option_hash, (int (*)(ANYARGS))database_put_flags,
                        (VALUE)&flags);

    vkey = StringValue(vkey);
    vval = StringValue(vval);

    MDB_val key, value;
    key.mv_size = RSTRING_LEN(vkey);
    key.mv_data = RSTRING_PTR(vkey);
    value.mv_size = RSTRING_LEN(vval);
    value.mv_data = RSTRING_PTR(vval);

    check(mdb_put(need_txn(database->env), database->dbi, &key, &value, flags));
    return Qnil;
}
size() click to toggle source

@return the number of records in this database

# File lib/lmdb/database.rb, line 144
def size
  stat[:entries]
end
stat() click to toggle source

@overload stat

Return useful statistics about a database.
@return [Hash] the statistics
* +:psize+ Size of a database page
* +:depth+ Depth (height) of the B-tree
* +:branch_pages+ Number of internal (non-leaf) pages
* +:leaf_pages+ Number of leaf pages
* +:overflow_pages+ Number of overflow pages
* +:entries+ Number of data items
static VALUE database_stat(VALUE self) {
        DATABASE(self, database);
        if (!active_txn(database->env))
                return call_with_transaction(database->env,
                                             self, "stat", 0, 0, MDB_RDONLY);

        MDB_stat stat;
        check(mdb_stat(need_txn(database->env), database->dbi, &stat));
        return stat2hash(&stat);
}

Private Instance Methods

maybe_txn(readonly) { |t| ... } click to toggle source

having trouble with read-only transactions embedded in read-write for some reason; can’t pin it down to test it yet so going to do this (djt; 2020-02-10)

# File lib/lmdb/database.rb, line 153
def maybe_txn(readonly, &block)
  if t = env.active_txn
    yield t
  else
    env.transaction !!readonly do |t|
      yield t
    end
  end
end