diff --git a/lib/sbdb.rb b/lib/sbdb.rb index 9b68299..cc59366 100644 --- a/lib/sbdb.rb +++ b/lib/sbdb.rb @@ -1,269 +1,16 @@ require 'bdb' +require 'sbdb/environment' +require 'sbdb/db' +require 'sbdb/cursor' module SBDB - CREATE = Bdb::DB_CREATE + CREATE = Bdb::DB_CREATE AUTO_COMMIT = Bdb::DB_AUTO_COMMIT - INIT_TXN = Bdb::DB_INIT_TXN - INIT_LOCK = Bdb::DB_INIT_LOCK - INIT_LOG = Bdb::DB_INIT_LOG - INIT_MPOOL = Bdb::DB_INIT_MPOOL - # Environments are for storing one or more databases and are important - # if you want to work with more than one process on one database. - # You needn't use Environment, but it's usefull. - class Environment - # returns the Bdb-object. - def bdb_object - @env - end - - def initialize dir = nil, flags = nil, mode = nil - dif ||= '.' - flags ||= INIT_TXN | INIT_LOCK | INIT_LOG | INIT_MPOOL | CREATE - mode ||= 0 - @env = Bdb::Env.new 0 - begin @env.open dir, flags, mode - rescue Object - close - raise - end - - return self unless block_given? - - begin yield self - ensure close - end - nil - end - - # Close the Environment. - # First you should close all databases! - def close - @env.close - end - - class << self - alias open new - end - - # Opens a Database. - # see SBDB::DB, SBDB::Btree, SBDB::Hash, SBDB::Recno, SBDB::Queue - def open type, *p, &e - p[5] = self - type ||= SBDB::Unkown - type.new *p, &e - end - alias db open - alias open_db open - - def [] file, name = nil, &e - open nil, file, name, CREATE | AUTO_COMMIT, &e - end - end - Env = Environment - - class DB - UNKNOWN = Bdb::Db::UNKNOWN - BTREE = Bdb::Db::BTREE - HASH = Bdb::Db::HASH - QUEUE = Bdb::Db::QUEUE - ARRAY = RECNO = Bdb::Db::RECNO - - attr_reader :home - - include Enumerable - def bdb_object - @db - end - - class << self - def new *p, &e - x = super *p - return x unless e - begin e.call x - ensure - x.sync - x.close - end - end - alias open new - end - - def initialize file, name = nil, type = nil, flags = nil, mode = nil, txn = nil, env = nil - flags ||= 0 - type ||= UNKNOWN - type = BTREE if type == UNKNOWN and (flags & CREATE) == CREATE - mode ||= 0 - @home = env - @db = env ? env.bdb_object.db : Bdb::Db.new - begin @db.open txn, file, name, type, flags, mode - rescue Object - close - raise $! - end - end - - def sync - @db.sync - end - - def close f = nil - @db.close f || 0 - end - - def [] k - @db.get nil, k, nil, 0 - end - - def []= k, v - @db.put nil, k, v, 0 - end - - def cursor &e - Cursor.new self, &e - end - - def each k = nil, v = nil, &e - cursor{|c|c.each k, v, &e} - end - - def reverse k = nil, v = nil, &e - cursor{|c|c.reverse k, v, &e} - end - - def to_hash k = nil, v = nil - h = {} - each( k, v) {|k, v| h[ k] = v } - h - end - end - - class Cursor - NEXT = Bdb::DB_NEXT - FIRST = Bdb::DB_FIRST - LAST = Bdb::DB_LAST - PREV = Bdb::DB_PREV - SET = Bdb::DB_SET - - attr_reader :db - - include Enumerable - def bdb_object - @cursor - end - - def self.new *p - x = super *p - return x unless block_given? - begin yield x - ensure x.close - end - end - - def initialize ref - obj = ref.bdb_object - @cursor, @db = *if Cursor === ref - [obj.dup, ref.db] - else [obj.cursor( nil, 0), ref] - end - end - - def close - @cursor.close - end - - def get k, v, f - @cursor.get k, v, f - end - - def count - @cursor.count - end - - def reverse k = nil, v = nil, &e - each k, v, LAST, PREV, &e - end - - def each k = nil, v = nil, f = nil, n = nil - return Enumerator.new( self, :each, k, v, f, n) unless block_given? - n ||= NEXT - e = @cursor.get k, v, f || FIRST - return unless e - yield *e - yield *e while e = @cursor.get( k, v, n) - nil - end - - def first k = nil, v = nil - @cursor.get k, v, FIRST - end - - def last k = nil, v = nil - @cursor.get k, v, LAST - end - - def next k = nil, v = nil - @cursor.get k, v, NEXT - end - - def prev k = nil, v = nil - @cursor.get k, v, PREV - end - end - - class Unknown < DB - def self.new *p, &e - db = super *p[0...2], UNKNOWN, *p[2..-1], &e - case db.bdb_object.get_type - when BTREE then Btree.new *p - when HASH then Hash.new *p - when RECNO then Recno.new *p - when QUEUE then Queue.new *p - else super *p[0...2], UNKNOWN, *p[2..-1], &e - end - ensure - db.close - end - end - - class Btree < DB - def self.new *p, &e - super *p[0...2], BTREE, *p[2..-1], &e - end - end - - class Hash < DB - def self.new *p, &e - super *p[0...2], HASH, *p[2..-1], &e - end - end - - class Recno < DB - def self.new *p, &e - super *p[0...2], RECNO, *p[2..-1], &e - end - - def [] k - super k.to_s - end - - def []= k, v - super k.to_s - end - end - Array = Recno - - class Queue < DB - def self.new *p, &e - super *p[0...2], QUEUE, *p[2..-1], &e - end - - def [] k - super k.to_s - end - - def []= k, v - super k.to_s - end - end + def btree( *p) Btree.new *p end + def hash( *p) Hash.new *p end + def recno( *p) Recno.new *p end + def queue( *p) Queue.new *p end + def unknown( *p) Unknown.new *p end + alias open_db unknown end diff --git a/lib/sbdb/cursor.rb b/lib/sbdb/cursor.rb new file mode 100644 index 0000000..50454a7 --- /dev/null +++ b/lib/sbdb/cursor.rb @@ -0,0 +1,52 @@ + +module SBDB + class Cursor + NEXT = Bdb::DB_NEXT + FIRST = Bdb::DB_FIRST + LAST = Bdb::DB_LAST + PREV = Bdb::DB_PREV + SET = Bdb::DB_SET + + attr_reader :db + + include Enumerable + def bdb_object() @cursor end + def close() @cursor.close end + def get( k, v, f) @cursor.get k, v, f end + def count() @cursor.count end + def first( k = nil, v = nil) @cursor.get k, v, FIRST end + def last( k = nil, v = nil) @cursor.get k, v, LAST end + def next( k = nil, v = nil) @cursor.get k, v, NEXT end + def prev( k = nil, v = nil) @cursor.get k, v, PREV end + + def self.new *p + x = super *p + return x unless block_given? + begin yield x + ensure x.close + end + end + + def initialize ref + @cursor, @db = *case ref + when Cursor then [ref.bdb_object.dup, ref.db] + when Bd::Db::Cursor then [ref] + else [ref.bdb_object.cursor( nil, 0), ref] + end + end + + def reverse k = nil, v = nil, &e + each k, v, LAST, PREV, &e + end + + def each k = nil, v = nil, f = nil, n = nil + return Enumerator.new( self, :each, k, v, f, n) unless block_given? + n ||= NEXT + e = @cursor.get k, v, f || FIRST + return unless e + yield *e + yield *e while e = @cursor.get( k, v, n) + nil + end + end +end diff --git a/lib/sbdb/db.rb b/lib/sbdb/db.rb new file mode 100644 index 0000000..0d44f27 --- /dev/null +++ b/lib/sbdb/db.rb @@ -0,0 +1,105 @@ +require 'bdb' +require 'sbdb/cursor' + +module SBDB + class DB + UNKNOWN = Bdb::Db::UNKNOWN + BTREE = Bdb::Db::BTREE + HASH = Bdb::Db::HASH + QUEUE = Bdb::Db::QUEUE + ARRAY = RECNO = Bdb::Db::RECNO + + attr_reader :home + include Enumerable + def bdb_object() @db end + def sync() @db.sync end + def close( f = nil) @db.close f || 0 end + def []( k) @db.get nil, k, nil, 0 end + def []=( k, v) @db.put nil, k, v, 0 end + def cursor( &e) Cursor.new self, &e end + + class << self + def new *p, &e + x = super *p + return x unless e + begin e.call x + ensure + x.sync + x.close + end + end + alias open new + end + + def initialize file, name = nil, type = nil, flags = nil, mode = nil, txn = nil, env = nil + flags ||= 0 + type ||= UNKNOWN + type = BTREE if type == UNKNOWN and (flags & CREATE) == CREATE + @home, @db = env, env ? env.bdb_object.db : Bdb::Db.new + begin @db.open txn, file, name, type, flags, mode || 0 + rescue Object + close + raise $! + end + end + + def each k = nil, v = nil, &e + cursor{|c|c.each k, v, &e} + end + + def reverse k = nil, v = nil, &e + cursor{|c|c.reverse k, v, &e} + end + + def to_hash k = nil, v = nil + h = {} + each( k, v) {|k, v| h[ k] = v } + h + end + end + + class Unknown < DB + def self.new *p, &e + db = super *p[0...2], UNKNOWN, *p[2..-1], &e + case db.bdb_object.get_type + when BTREE then Btree.new *p + when HASH then Hash.new *p + when RECNO then Recno.new *p + when QUEUE then Queue.new *p + else super *p[0...2], UNKNOWN, *p[2..-1], &e + end + ensure db.close + end + end + + class Btree < DB + def self.new *p, &e + super *p[0...2], BTREE, *p[2..-1], &e + end + end + + class Hash < DB + def self.new *p, &e + super *p[0...2], HASH, *p[2..-1], &e + end + end + + class Recno < DB + def self.new *p, &e + super *p[0...2], RECNO, *p[2..-1], &e + end + + def []( k) super k.to_s end + def []=( k, v) super k.to_s end + end + Array = Recno + + class Queue < DB + def self.new *p, &e + super *p[0...2], QUEUE, *p[2..-1], &e + end + + def []( k) super k.to_s end + def []=( k, v) super k.to_s end + end +end diff --git a/lib/sbdb/environment.rb b/lib/sbdb/environment.rb new file mode 100644 index 0000000..fe6ddcb --- /dev/null +++ b/lib/sbdb/environment.rb @@ -0,0 +1,77 @@ +require 'bdb' +require 'sbdb/weakhash' +require 'sbdb/db' + +module SBDB + # Environments are for storing one or more databases and are important + # if you want to work with more than one process on one database. + # You needn't use Environment, but it's usefull. + class Environment + INIT_TXN = Bdb::DB_INIT_TXN + INIT_LOCK = Bdb::DB_INIT_LOCK + INIT_LOG = Bdb::DB_INIT_LOG + INIT_MPOOL = Bdb::DB_INIT_MPOOL + INIT_TRANSACTION = INIT_TXN | INIT_LOCK | INIT_LOG | INIT_MPOOL + LOCKDOWN = Bdb::DB_LOCKDOWN + NOMMAP = Bdb::DB_NOMMAP + PRIVATE = Bdb::DB_PRIVATE + SYSTEM_MEM = Bdb::DB_SYSTEM_MEM + TXN_NOSYNC = Bdb::DB_TXN_NOSYNC + + # returns the Bdb-object. + def bdb_object() @env end + # Opens a Btree in this Environment + def btree( *p, &e) Btree.new *p[0...5], self, p[5..-1], &e end + # Opens a Hash in this Environment + def hash( *p, &e) Hash.new *p[0...5], self, p[5..-1], &e end + # Opens a Recno in this Environment + def recno( *p, &e) Recno.new *p[0...5], self, p[5..-1], &e end + # Opens a Queue in this Environment + def queue( *p, &e) Queue.new *p[0...5], self, p[5..-1], &e end + # Opens a DB of unknown type in this Environment + def unknown( *p, &e) Unknown.new *p[0...5], self, p[5..-1], &e end + + def initialize dir = nil, flags = nil, mode = nil + @env = Bdb::Env.new 0 + begin @env.open dir || '.', flags || INIT_TRANSACTION | CREATE, mode || 0 + rescue Object + close + raise + end + + return self unless block_given? + + begin yield self + ensure close + end + nil + end + + # Close the Environment. + # First you should close all databases! + def close + @dbs.each{|db|db.close} + @env.close + end + + class << self + alias open new + end + + # Opens a Database. + # see SBDB::DB, SBDB::Btree, SBDB::Hash, SBDB::Recno, SBDB::Queue + def open type, *p, &e + (type || SBDB::Unkown).new *p[0...5], self, p[5..-1], &e + end + alias db open + alias open_db open + + # Returns the DB like open, but if it's already opened, + # it returns the old instance. + # If you use this, never use close. It's possible somebody else use it. + def [] file, name = nil, type = nil, &e + @dbs[ [file, name]] ||= (type || SBDB::Unkown).new file, name, nil, nil, self, &e + end + end + Env = Environment +end diff --git a/lib/sbdb/weakhash.rb b/lib/sbdb/weakhash.rb new file mode 100644 index 0000000..3bd0248 --- /dev/null +++ b/lib/sbdb/weakhash.rb @@ -0,0 +1,48 @@ +module SBDB + +# See http://eigenclass.org/hiki/deferred-finalizers-in-Ruby + +class WeakHash + attr_reader :cache + def initialize( cache = Hash.new ) + @cache = cache + @key_map = {} + @rev_cache = Hash.new{|h,k| h[k] = {}} + @reclaim_value = lambda do |value_id| + if @rev_cache.has_key? value_id + @rev_cache[value_id].each_key{|key| @cache.delete key} + @rev_cache.delete value_id + end + end + @reclaim_key = lambda do |key_id| + if @key_map.has_key? key_id + @cache.delete @key_map[key_id] + end + end + end + + def []( key ) + value_id = @cache[key] + return ObjectSpace._id2ref(value_id) unless value_id.nil? + nil + rescue RangeError + nil + end + + def []=( key, value ) + case key + when Fixnum, Symbol, true, false + key2 = key + else + key2 = key.dup + end + @rev_cache[value.object_id][key2] = true + @cache[key2] = value.object_id + @key_map[key.object_id] = key2 + + ObjectSpace.define_finalizer(value, @reclaim_value) + ObjectSpace.define_finalizer(key, @reclaim_key) + end +end + +end diff --git a/sbdb.gemspec b/sbdb.gemspec index 9df2741..43fa861 100644 --- a/sbdb.gemspec +++ b/sbdb.gemspec @@ -9,16 +9,21 @@ Gem::Specification.new do |s| s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version= s.authors = ["Denis Knauf"] - s.date = %q{2010-01-29} + s.date = %q{2010-02-02} s.description = %q{Simple Ruby Berkeley DB wrapper library for bdb.} s.email = %q{Denis.Knauf@gmail.com} s.extra_rdoc_files = [ - "README" + "LICENSE", + "README" ] s.files = [ "README", "VERSION", - "lib/sbdb.rb" + "lib/sbdb.rb", + "lib/sbdb/cursor.rb", + "lib/sbdb/db.rb", + "lib/sbdb/environment.rb", + "lib/sbdb/weakhash.rb" ] s.homepage = %q{http://github.com/DenisKnauf/bdb} s.rdoc_options = ["--charset=UTF-8"] @@ -31,9 +36,12 @@ Gem::Specification.new do |s| s.specification_version = 3 if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then + s.add_runtime_dependency(%q, [">= 0"]) else + s.add_dependency(%q, [">= 0"]) end else + s.add_dependency(%q, [">= 0"]) end end