diff --git a/lang/ruby/stasis.rb b/lang/ruby/stasis.rb new file mode 100644 index 0000000..4e42bfe --- /dev/null +++ b/lang/ruby/stasis.rb @@ -0,0 +1,22 @@ +require 'stasis/string' +require 'stasis/hash' +require 'stasis/string_iterator' + +class Stasis + Stasis.init_count = 0; + def initialize(xid=nil, rid=nil) + if Stasis.init_count == 0 then + Raw.Tinit + end + Stasis.init_count++; + if rid == nil then + rid = Raw.ROOT_RID + end + if xid == nil then + @xid = -1 + @autoxact = true + else + @xid = xid + @autoxact = false + end + end diff --git a/lang/ruby/stasis/ffi.rb b/lang/ruby/stasis/ffi.rb new file mode 100644 index 0000000..614a1c3 --- /dev/null +++ b/lang/ruby/stasis/ffi.rb @@ -0,0 +1,99 @@ +require 'ffi' + +module Stasis + class Native + extend FFI::Library + + ## Stasis typedefs + + typedef :uchar, :byte + typedef :int, :xid + typedef :int64, :lsn + typedef :int64, :pageid + typedef :int32, :slotid + typedef :int16, :pageoff + typedef :int16, :pagetype + typedef :pointer, :iterator + typedef :pointer, :bytes + + # Not a managed struct, won't be GC'ed + class RecordId < FFI::Struct + layout :page, :pageid, + :slot, :slotid, + :size, :int64, + end + typedef RecordId.by_value, :recordid + + ## Functions exported by libstasis.so + + ffi_lib "stasis" + attach_function 'Tinit', [ ], :int + attach_function 'Tdeinit', [ ], :int + attach_function 'Tbegin', [ ], :xid + attach_function 'Tcommit', [ :xid ], :int + attach_function 'TsoftCommit', [ :xid ], :int + attach_function 'TforceCommits', [ ], :void + attach_function 'Tabort', [ :xid ], :int + attach_function 'Tprepare', [ :xid ], :int + attach_function 'Talloc', [ :xid, :int ], :recordid + attach_function 'TrecordType', [ :xid, :recordid ], :int + attach_function 'TrecordSize', [ :xid, :recordid ], :int + attach_function 'Tset', [ :xid, :recordid, :bytes ], :int + # the string is an out parameter + attach_function 'Tread', [ :xid, :recordid, :bytes ], :int + + # The second two parameters should be -1. + attach_function 'ThashCreate', [ :xid, :int, :int ], :recordid + attach_function 'ThashInsert', [ :xid, :recordid, + :bytes, :int, + :bytes, :int ], :int + attach_function 'ThashRemove', [ :xid, :recordid, + :bytes, :int ], :int + ## Note: The pointer is an OUT param + attach_function 'ThashLookup', [ :xid, :recordid, + :bytes, :int, :pointer ], :int + + attach_function 'ThashGenericIterator', [ :xid, :recordid ], :iterator + + attach_function 'Titerator_next', [:xid, :iterator], :int + ## Note: THe pointer is an OUT param + attach_function 'Titerator_value', [:xid, :iterator, :pointer], :int + attach_function 'Titerator_tupleDone', [:xid, :iterator ], :void + attach_function 'Titerator_close', [:xid, :iterator], :void + + # XXX move to a different class! + + def Native.ThashLookupHelper(xid, rid, string) + objptrptr = MemoryPointer.new :pointer + len = Stasis.ThashLookup(xid, rid, string, objptrptr); + objptr = objptr.get_pointer(0) + str = objptr.null ? nil : objptr.read_string + objptr.free + return str + end + + + def Native.TallocString(xid, str) + rid = Talloc(xid, str.length+1) ## XXX str.length is wrong. + ## want the size of the underlying buffer. + Tset(xid, rid, str) + return rid + end + + def Native.TgetString(xid, rid) + ret = " " * (TrecordSize(xid, rid)-1) + Tread(xid, rid, ret) + return ret + end + + def Native.TsetString(xid, rid, str) + if(str.length + 1 > TrecordSize(xid, rid)-1) + return false # throw exception? + else + Tset(xid, rid, str) + return true + end + end + end +end + diff --git a/lang/ruby/stasis/hash.rb b/lang/ruby/stasis/hash.rb new file mode 100644 index 0000000..9b4c621 --- /dev/null +++ b/lang/ruby/stasis/hash.rb @@ -0,0 +1,53 @@ +require 'stasis/raw' + +module Stasis + class Hash + extend FFI::Library + ffi_lib FFI::Library::LIBC + attach_function 'free', [:pointer], :void # called by Hash.lookup + + def Hash.alloc(xid) + return Raw.ThashCreate xid, -1, -1 + end + + def Hash.insert(xid, rid, key, val) + keylen = key.length+1 + vallen = val.length+1 + keyptr = FFI::MemoryPointer.new :char, keylen + valptr = FFI::MemoryPointer.new :char, vallen + keyptr.put_string(0, key) + valptr.put_string(0, val) + + ret = Raw.ThashInsert xid, rid, keyptr, keylen, valptr, vallen; + + keyptr.free + valptr.free + return ret + end + + def Hash.remove(xid, rid, key) + keylen = key.length+1 + keyptr = FFI::MemoryPointer.new :char, keylen + ret = Raw.ThashRemove xid, rid, keyptr, keylen + keyptr.free + return ret + end + + def Hash.iterator(xid, rid) + return Raw.ThashGenericIterator xid, rid + end + + def Hash.lookup(xid, rid, key) + keylen = key.length+1 + keyptr = FFI::MemoryPointer.new :char, keylen + keyptr.put_string(0, key) + objptrptr = FFI::MemoryPointer.new :pointer + len = Raw.ThashLookup(xid, rid, keyptr, keylen, objptrptr); + objptr = objptrptr.get_pointer(0) + str = objptr.null? ? nil : objptr.get_string(0) + keyptr.free + free objptr + return str + end + end +end diff --git a/lang/ruby/stasis/raw.rb b/lang/ruby/stasis/raw.rb new file mode 100644 index 0000000..ac68f02 --- /dev/null +++ b/lang/ruby/stasis/raw.rb @@ -0,0 +1,77 @@ +require 'ffi' + +module Stasis + class Raw + extend FFI::Library + + ## Stasis typedefs + + typedef :uchar, :byte + typedef :int, :xid + typedef :int64, :lsn + typedef :int64, :pageid + typedef :int32, :slotid + typedef :int16, :pageoff + typedef :int16, :pagetype + typedef :pointer, :iterator + typedef :pointer, :bytes + + # Not a managed struct, won't be GC'ed + class RecordId < FFI::Struct + layout :page, :pageid, + :slot, :slotid, + :size, :int64, + end + + Stasis::ROOT_RID = RecordId.new + Stasis::ROOT_RID[:page] = 1 + Stasis::ROOT_RID[:slot] = 0 + Stasis::ROOT_RID[:size] = -1 + + Stasis::NULLRID = RecordId.new + Stasis::NULLRID[:page] = 0 + Stasis::NULLRID[:slot] = 0 + Stasis::NULLRID[:size] = -1 + + typedef RecordId.by_value, :recordid + + ## Functions exported by libstasis.so + + ffi_lib "stasis" + attach_function 'Tinit', [ ], :int + attach_function 'Tdeinit', [ ], :int + attach_function 'Tbegin', [ ], :xid + attach_function 'Tcommit', [ :xid ], :int + attach_function 'TsoftCommit', [ :xid ], :int + attach_function 'TforceCommits', [ ], :void + attach_function 'Tabort', [ :xid ], :int + attach_function 'Tprepare', [ :xid ], :int + attach_function 'Talloc', [ :xid, :int ], :recordid + attach_function 'TrecordType', [ :xid, :recordid ], :int + attach_function 'TrecordSize', [ :xid, :recordid ], :int + attach_function 'Tset', [ :xid, :recordid, :bytes ], :int + # the string is an out parameter + attach_function 'Tread', [ :xid, :recordid, :bytes ], :int + + # The second two parameters should be -1. + attach_function 'ThashCreate', [ :xid, :int, :int ], :recordid + attach_function 'ThashInsert', [ :xid, :recordid, + :bytes, :int, + :bytes, :int ], :int + attach_function 'ThashRemove', [ :xid, :recordid, + :bytes, :int ], :int + ## Note: The pointer is an OUT param + attach_function 'ThashLookup', [ :xid, :recordid, + :bytes, :int, :pointer ], :int + + attach_function 'ThashGenericIterator', [ :xid, :recordid ], :iterator + + attach_function 'Titerator_next', [:xid, :iterator], :int + ## Note: The pointer is an OUT param + attach_function 'Titerator_key', [:xid, :iterator, :pointer], :int + ## Note: The pointer is an OUT param + attach_function 'Titerator_value', [:xid, :iterator, :pointer], :int + attach_function 'Titerator_tupleDone', [:xid, :iterator ], :void + attach_function 'Titerator_close', [:xid, :iterator], :void + end +end diff --git a/lang/ruby/stasis/string.rb b/lang/ruby/stasis/string.rb new file mode 100644 index 0000000..30a350d --- /dev/null +++ b/lang/ruby/stasis/string.rb @@ -0,0 +1,41 @@ +require 'stasis/raw' + +module Stasis + class String + + def String.alloc(xid, str) + ## str.length is apparently the size of the underlying buffer. + rid = Raw.Talloc(xid, str.length+1) + return rid + end + + def String.put_new(xid, str) + rid = String.alloc(xid, str) + if(String.set(xid, rid, str)) + return rid + else + return nil # This will surely crash the interpreter. Oh well. + end + end + + def String.get(xid, rid) + objptr = FFI::MemoryPointer.new :char, Raw.TrecordSize(xid, rid); + Raw.Tread(xid, rid, objptr); + str = objptr.get_string(0) + objptr.free + return str + end + + def String.set(xid, rid, str) + if(str.length + 1 > Raw.TrecordSize(xid, rid)) + return false # throw exception? + else + objptr = FFI::MemoryPointer.new :char, str.length+1 + objptr.put_string(0, str); + Raw.Tset(xid, rid, objptr) + objptr.free + return true + end + end + end +end diff --git a/lang/ruby/stasis/string_iterator.rb b/lang/ruby/stasis/string_iterator.rb new file mode 100644 index 0000000..c8ca121 --- /dev/null +++ b/lang/ruby/stasis/string_iterator.rb @@ -0,0 +1,30 @@ +require 'stasis/raw' + +module Stasis + class StringIterator + def StringIterator.value(xid, it) + objptrptr = FFI::MemoryPointer.new :pointer + len = Raw.Titerator_value(xid, it, objptrptr) + if(len == -1) then return nil end + + objptr = objptrptr.get_pointer(0) + # The slice is for bounds checking. + str = objptr.null? ? nil : objptr.slice(0, len).get_string(0) + # The iterator frees objptr during tupleDone. + objptrptr.free + return str + end + def StringIterator.key(xid, it) + objptrptr = FFI::MemoryPointer.new :pointer + len = Raw.Titerator_key(xid, it, objptrptr) + if(len == -1) then return nil end + + objptr = objptrptr.get_pointer(0) + # The slice is for bounds checking. + str = objptr.null? ? nil : objptr.slice(0, len).get_string(0) + # The iterator frees objptr during tupleDone. + objptrptr.free + return str + end + end +end diff --git a/lang/ruby/stasis/test/tc_hash.rb b/lang/ruby/stasis/test/tc_hash.rb new file mode 100644 index 0000000..455df00 --- /dev/null +++ b/lang/ruby/stasis/test/tc_hash.rb @@ -0,0 +1,39 @@ +require 'stasis/hash' +require 'test/unit' +module Stasis + class TestHash < Test::Unit::TestCase + + def setup + `rm -f storefile.txt logfile.txt` + end + + def teardown + `rm -f storefile.txt logfile.txt` + end + + def test_combined + assert_equal(0, Raw.Tinit) + xid = Raw.Tbegin + rid = Hash.alloc(xid) + assert_equal(0, Hash.insert(xid, rid, "foo", "bar")) + assert_equal("bar", Hash.lookup(xid, rid, "foo")) + Raw.Tcommit xid + xid = Raw.Tbegin + assert_equal(1, Hash.insert(xid, rid, "foo", "baz")) + assert_equal(0, Hash.insert(xid, rid, "bar", "bat")) + assert_equal("baz", Hash.lookup(xid, rid, "foo")) + assert_equal("bat", Hash.lookup(xid, rid, "bar")) + Raw.Tabort xid + assert_equal("bar", Hash.lookup( -1, rid, "foo")) + assert_equal(nil, Hash.lookup( -1, rid, "bar")) + assert_equal(0, Raw.Tdeinit) + assert_equal(0, Raw.Tinit) + assert_equal("bar", Hash.lookup( -1, rid, "foo")) + assert_equal(nil, Hash.lookup( -1, rid, "bar")) + xid = Raw.Tbegin + assert_equal(0, Hash.insert(xid, rid, "bar", "bat")) + assert_equal("bat", Hash.lookup(xid,rid,"bar")) + assert_equal(0, Raw.Tdeinit) + end + end +end diff --git a/lang/ruby/stasis/test/tc_iterator.rb b/lang/ruby/stasis/test/tc_iterator.rb new file mode 100644 index 0000000..94fbf33 --- /dev/null +++ b/lang/ruby/stasis/test/tc_iterator.rb @@ -0,0 +1,39 @@ +require 'stasis/hash' +require 'stasis/string_iterator' +require 'test/unit' + +module Stasis + class TestIterator < Test::Unit::TestCase + def setup + `rm -f storefile.txt logfile.txt` + end + def teardown + `rm -f storefile.txt logfile.txt` + end + + def test_hash_iterator + assert_equal(0, Raw.Tinit) + xid = Raw.Tbegin + rid = Hash.alloc(xid) + h = {} + (0..100).each { + |x| + assert_equal(0, Hash.insert(xid, rid, x.to_s, (x*10).to_s)) + h[x.to_s] = (x*10).to_s + } + + i = {} + it = Hash.iterator(xid, rid) + + while(0 != Raw.Titerator_next(xid, it)) + i[StringIterator.key(xid, it)] = StringIterator.value(xid, it) + end + + Raw.Titerator_close(xid, it) + assert_equal(h, i) + + Raw.Tcommit xid + Raw.Tdeinit + end + end +end diff --git a/lang/ruby/stasis/test/tc_string.rb b/lang/ruby/stasis/test/tc_string.rb new file mode 100644 index 0000000..cf70558 --- /dev/null +++ b/lang/ruby/stasis/test/tc_string.rb @@ -0,0 +1,52 @@ +require 'stasis/string' +require 'test/unit' + +class TestStrings < Test::Unit::TestCase + + def setup + `rm -f storefile.txt logfile.txt` + end + + def teardown + `rm -f storefile.txt logfile.txt` + end + + def test_abort + assert_equal(0, Stasis::Raw.Tinit) + xid = Stasis::Raw.Tbegin + rid = Stasis::String.put_new xid, "Hello world" + str = Stasis::String.get xid, rid + assert_equal("Hello world", str) + Stasis::Raw.Tcommit xid; + xid = Stasis::Raw.Tbegin + assert(Stasis::String.set xid, rid, "G'bye world") + str = Stasis::String.get xid, rid + assert_equal("G'bye world", str) + Stasis::Raw.Tabort xid; + str = Stasis::String.get(-1, rid) + assert_equal("Hello world", str) + assert_equal(0, Stasis::Raw.Tdeinit) + end + + + def test_recover + assert_equal(0, Stasis::Raw.Tinit) + xid = Stasis::Raw.Tbegin + rid = Stasis::String.put_new xid, "Hello world" + str = Stasis::String.get xid, rid + assert_equal("Hello world", str) + Stasis::Raw.Tcommit xid; + xid = Stasis::Raw.Tbegin + assert(Stasis::String.set xid, rid, "G'bye world") + str = Stasis::String.get xid, rid + assert_equal("G'bye world", str) + assert_equal(0, Stasis::Raw.Tdeinit) + assert_equal(0, Stasis::Raw.Tinit) + str = Stasis::String.get(-1, rid) + assert_equal("Hello world", str) + assert_equal(0, Stasis::Raw.Tdeinit) + end + + ### XXX check for memory leaks... +end + diff --git a/lang/ruby/stasis/test/ts_stasis.rb b/lang/ruby/stasis/test/ts_stasis.rb new file mode 100644 index 0000000..09f53ac --- /dev/null +++ b/lang/ruby/stasis/test/ts_stasis.rb @@ -0,0 +1,3 @@ +require 'stasis/test/tc_string' +require 'stasis/test/tc_hash' +require 'stasis/test/tc_iterator'