From 0f3c1c14630fda58363ffd7d3a942041ca2419eb Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Tue, 23 Nov 2010 18:52:08 -0800 Subject: add plain Hash database support Useful as a proof-of-concept and for benchmark base. --- lib/metropolis.rb | 4 +++ lib/metropolis/common.rb | 8 ++++++ lib/metropolis/hash.rb | 75 ++++++++++++++++++++++++++++++++++++++++++++++++ test/test_hash.rb | 40 ++++++++++++++++++++++++++ 4 files changed, 127 insertions(+) create mode 100644 lib/metropolis/hash.rb create mode 100644 test/test_hash.rb diff --git a/lib/metropolis.rb b/lib/metropolis.rb index 1bae3d3..a7cfc47 100644 --- a/lib/metropolis.rb +++ b/lib/metropolis.rb @@ -4,12 +4,16 @@ require 'uri' module Metropolis autoload :TC, 'metropolis/tc' + autoload :Hash, 'metropolis/hash' def self.new(opts = {}) opts = opts.dup rv = Object.new uri = opts[:uri] = URI.parse(opts[:uri]) case uri.scheme + when 'hash' + opts[:path] = uri.path if uri.path != '/' + rv.extend Metropolis::Hash when 'tc' opts[:path_pattern] = uri.path opts[:query] = Rack::Utils.parse_query(uri.query) if uri.query diff --git a/lib/metropolis/common.rb b/lib/metropolis/common.rb index 738a511..13a02d8 100644 --- a/lib/metropolis/common.rb +++ b/lib/metropolis/common.rb @@ -37,4 +37,12 @@ module Metropolis::Common r(405) end end + + # generic HEAD implementation, some databases can optimize this by + # not retrieving the value + def head(key) + r = get(key) + r[2].clear + r + end end diff --git a/lib/metropolis/hash.rb b/lib/metropolis/hash.rb new file mode 100644 index 0000000..698aef0 --- /dev/null +++ b/lib/metropolis/hash.rb @@ -0,0 +1,75 @@ +# -*- encoding: binary -*- +require 'tempfile' + +# use a Ruby hash as a plain data store +# It can unmarshal a hash from disk +module Metropolis::Hash + include Metropolis::Common + + def setup(opts) + super + if @path = opts[:path] + begin + @db = Marshal.load(File.open(@path, "rb") { |fp| fp.read }) + Hash === @db or raise ArgumentError, "#@path is not a marshaled Hash" + rescue Errno::ENOENT + @db = {} + end + else + @db = {} + end + if @readonly + extend Metropolis::Common::RO + else + args = [ @db, @path, !!opts[:fsync] ] + @clean_proc = Metropolis::Hash.finalizer_callback(args) + ObjectSpace.define_finalizer(self, @clean_proc) + end + end + + def close! + unless @readonly + @clean_proc.call + ObjectSpace.undefine_finalizer(self) + end + @db = @path = nil + end + + def get(key) + value = @db[key] or return r(404) + [ 200, { 'Content-Length' => value.size.to_s }.merge!(@headers), [ value ] ] + end + + def put(key, env) + value = env["rack.input"].read + case env['HTTP_X_TT_PDMODE'] + when '1' + @db.exists?(key) and r(409) + @db[key] = value + when '2' + (tmp = @db[key] ||= "") << value + else + @db[key] = value + end + r(201) + end + + def delete(key) + r(@db.delete(key) ? 200 : 404) + end + + def self.finalizer_callback(data) + lambda { + db, path, fsync = data + dir = File.dirname(path) + tmp = Tempfile.new('hash_save', dir) + tmp.binmode + tmp.sync = true + tmp.write(Marshal.dump(db)) + tmp.fsync if fsync + File.rename(tmp.path, path) + File.open(dir) { |d| d.fsync } if fsync + tmp.close! + } + end +end diff --git a/test/test_hash.rb b/test/test_hash.rb new file mode 100644 index 0000000..cf11e82 --- /dev/null +++ b/test/test_hash.rb @@ -0,0 +1,40 @@ +# -*- encoding: binary -*- +require './test/rack_read_write.rb' +$-w = true +require 'metropolis' + +class Test_Hash < Test::Unit::TestCase + attr_reader :tmp, :o, :uri + include TestRackReadWrite + + def setup + @tmp = Tempfile.new('hash') + File.unlink(@tmp) + @uri = "hash://#{@tmp.path}" + end + + def teardown + @tmp.close! + end + + def test_marshalled + File.open(@tmp, "wb") { |fp| fp.write(Marshal.dump({"x" => "y"})) } + app = Metropolis.new(:uri => @uri, :readonly => true) + o = { :lint => true, :fatal => true } + req = Rack::MockRequest.new(app) + + r = req.put("/x", o.merge(:input=>"ASDF")) + assert_equal 403, r.status + + r = req.get("/x") + assert_equal 200, r.status + assert_equal "y", r.body + + r = req.request("HEAD", "/x", {}) + assert_equal 200, r.status + assert_equal "", r.body + + r = req.delete("/x", {}) + assert_equal 403, r.status + end +end -- cgit v1.2.3-24-ge0c7