From cc91cdcd14c4c3a6dc9731bdbaa4c9be8107697b Mon Sep 17 00:00:00 2001 From: Justin Balthrop Date: Thu, 19 Nov 2009 22:00:54 -0800 Subject: [PATCH] add missing files --- lib/bdb/base.rb | 60 ++++++++++++++++++++++++++ lib/bdb/partitioned_database.rb | 74 +++++++++++++++++++++++++++++++++ lib/bdb/result_set.rb | 41 ++++++++++++++++++ 3 files changed, 175 insertions(+) create mode 100644 lib/bdb/base.rb create mode 100644 lib/bdb/partitioned_database.rb create mode 100644 lib/bdb/result_set.rb diff --git a/lib/bdb/base.rb b/lib/bdb/base.rb new file mode 100644 index 0000000..b56f538 --- /dev/null +++ b/lib/bdb/base.rb @@ -0,0 +1,60 @@ +require 'bdb' +require 'tuple' +require 'bdb/environment' +require 'bdb/result_set' + +class Bdb::Base + def initialize(opts) + @config = Bdb::Environment.config.merge(opts) + @indexes = {} + end + attr_reader :indexes + + def config(config = {}) + @config.merge!(config) + end + + def index_by(field, opts = {}) + raise "index on #{field} already exists" if indexes[field] + indexes[field] = opts + end + + def environment + @environment ||= Bdb::Environment.new(config[:path], self) + end + + def transaction(nested = true, &block) + environment.transaction(nested, &block) + end + + def synchronize(&block) + environment.synchronize(&block) + end + + def checkpoint(opts = {}) + environment.synchronize(opts) + end + +private + + def get_field(field, value) + value.kind_of?(Hash) ? value[field] : value.send(field) + end +end + +class Object + attr_accessor :bdb_locator_key +end + +# Array comparison should try Tuple comparison first. +class Array + cmp = instance_method(:<=>) + + define_method(:<=>) do |other| + begin + Tuple.dump(self) <=> Tuple.dump(other) + rescue TypeError => e + cmp.bind(self).call(other) + end + end +end diff --git a/lib/bdb/partitioned_database.rb b/lib/bdb/partitioned_database.rb new file mode 100644 index 0000000..90cba17 --- /dev/null +++ b/lib/bdb/partitioned_database.rb @@ -0,0 +1,74 @@ +require 'bdb/base' + +class Bdb::PartitionedDatabase < Bdb::Base + SEPARATOR = '__' + PARTITION_PATTERN = /^[-\w]*$/ + + def initialize(base_name, opts = {}) + @base_name = base_name + @partition_by = opts.delete(:partition_by) + super(opts) + end + attr_reader :base_name, :partition_by, :partition + + def databases + @databases ||= {} + end + + def database(partition = nil) + partition ||= self.partition + raise 'partition value required' if partition.nil? + partition = partition.to_s + raise "invalid partition value: #{partition}" unless partition =~ PARTITION_PATTERN + + databases[partition] ||= begin + name = [partition, base_name].join(SEPARATOR) + database = Bdb::Database.new(name, config) + indexes.each do |field, opts| + database.index_by(field, opts) + end + database + end + end + + def partitions + Dir[environment.path + "/*#{SEPARATOR}#{base_name}"].collect do |file| + File.basename(file).split(SEPARATOR).first + end + end + + def with_partition(partition) + @partition, old_partition = partition, @partition + yield + ensure + @partition = old_partition + end + + def close + databases.each do |partition, database| + database.close + end + @databases.clear + end + + def get(*keys, &block) + opts = keys.last.kind_of?(Hash) ? keys.last : {} + database(opts[partition_by]).get(*keys, &block) + end + + def set(key, value, opts = {}) + partition = get_field(partition_by, value) + database(partition).set(key, value, opts) + end + + def delete(key, opts = {}) + database(opts[partition_by]).delete(key) + end + + # Deletes all records in the database. Beware! + def truncate! + partitions.each do |partition| + database(partition).truncate! + end + end +end diff --git a/lib/bdb/result_set.rb b/lib/bdb/result_set.rb new file mode 100644 index 0000000..ccb8156 --- /dev/null +++ b/lib/bdb/result_set.rb @@ -0,0 +1,41 @@ +class Bdb::ResultSet + class LimitReached < Exception; end + + def initialize(opts, &block) + @block = block + @count = 0 + @limit = opts[:limit] || opts[:per_page] + @limit = @limit.to_i if @limit + @offset = opts[:offset] || (opts[:page] ? @limit * (opts[:page] - 1) : 0) + @offset = @offset.to_i if @offset + + if @group = opts[:group] + raise 'block not supported with group' if @block + @results = hash_class.new + else + @results = [] + end + end + attr_reader :count, :group, :limit, :offset, :results + + def hash_class + @hash_class ||= defined?(ActiveSupport::OrderedHash) ? ActiveSupport::OrderedHash : Hash + end + + def <<(item) + @count += 1 + return if count <= offset + + raise LimitReached if limit and count > limit + offset + + if group + key = item.bdb_locator_key + group_key = group.is_a?(Fixnum) ? key[0,group] : key + (results[group_key] ||= []) << item + elsif @block + @block.call(item) + else + results << item + end + end +end