oklahoma_mixer 0.1.0 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
data/CHANGELOG.rdoc CHANGED
@@ -2,6 +2,16 @@
2
2
 
3
3
  Below is a complete listing of changes for each revision of Oklahoma Mixer.
4
4
 
5
+ == 0.2.0
6
+
7
+ * Added a to_hash() iterator that can preserve defaults
8
+ * Added B+Tree Database support (order functions, key ranges,
9
+ and duplicate storages)
10
+ * Added Fixed-length Database (indexing and iterating by ID's, support for
11
+ special ID constants, and size limits)
12
+ * Fixed a bug that could cause Ruby to crash during GC after storing a value
13
+ with the duplication handler block
14
+
5
15
  == 0.1.0
6
16
 
7
17
  * Initial public release with Hash Database support
data/TODO.rdoc CHANGED
@@ -3,9 +3,9 @@
3
3
  The following is a list of planned expansions for Oklahoma Mixer in the order I
4
4
  intend to address them.
5
5
 
6
- 1. Add support for B+Tree and Fixed-length Databases
7
- 2. Add support for Table Databases
8
- 3. Ensure Ruby 1.9 compatibility
6
+ 1. Add support for Table Databases
7
+ 2. Ensure Ruby 1.9 compatibility
8
+ 3. Write API documentation
9
9
  4. Add support for Tokyo Tyrant
10
10
  5. Add support for Tokyo Dystopia
11
11
  6. Include some higher level abstractions like mixed tables, queues, and shards
@@ -7,18 +7,27 @@ require "oklahoma_mixer/extensible_string/c"
7
7
  require "oklahoma_mixer/extensible_string"
8
8
  require "oklahoma_mixer/array_list/c"
9
9
  require "oklahoma_mixer/array_list"
10
+ require "oklahoma_mixer/cursor/c"
11
+ require "oklahoma_mixer/cursor"
10
12
 
11
13
  require "oklahoma_mixer/hash_database/c"
12
14
  require "oklahoma_mixer/hash_database"
15
+ require "oklahoma_mixer/b_tree_database/c"
16
+ require "oklahoma_mixer/b_tree_database"
17
+ require "oklahoma_mixer/fixed_length_database/c"
18
+ require "oklahoma_mixer/fixed_length_database"
13
19
 
14
20
  module OklahomaMixer
15
- VERSION = "0.1.0"
21
+ VERSION = "0.2.0"
16
22
 
17
23
  def self.open(path, *args)
18
- db = case File.extname(path).downcase
19
- when ".tch" then HashDatabase.new(path, *args)
20
- else fail ArgumentError, "unsupported database type"
21
- end
24
+ db_class = case File.extname(path).downcase
25
+ when ".tch" then HashDatabase
26
+ when ".tcb" then BTreeDatabase
27
+ when ".tcf" then FixedLengthDatabase
28
+ else fail ArgumentError, "unsupported database type"
29
+ end
30
+ db = db_class.new(path, *args)
22
31
  if block_given?
23
32
  begin
24
33
  yield db
@@ -0,0 +1,185 @@
1
+ module OklahomaMixer
2
+ class BTreeDatabase < HashDatabase
3
+ ###################
4
+ ### File System ###
5
+ ###################
6
+
7
+ def optimize(options)
8
+ try( options[:tune] ? :tune : :optimize,
9
+ options.fetch(:lmemb, 0).to_i,
10
+ options.fetch(:nmemb, 0).to_i,
11
+ options.fetch(:bnum, 0).to_i,
12
+ options.fetch(:apow, -1).to_i,
13
+ options.fetch(:fpow, -1).to_i,
14
+ to_enum_int(options.fetch(:opts, 0xFF), :opt) )
15
+ end
16
+
17
+ ################################
18
+ ### Getting and Setting Keys ###
19
+ ################################
20
+
21
+ def store(key, value, mode = nil)
22
+ if mode == :dup
23
+ try(:putdup, cast_key_in(key), cast_value_in(value))
24
+ value
25
+ else
26
+ super
27
+ end
28
+ end
29
+
30
+ def keys(options = { })
31
+ if options.include? :range
32
+ warn "range supersedes prefix" if options[:prefix]
33
+ range = options[:range]
34
+ fail ArgumentError, "Range expected" unless range.is_a? Range
35
+ start = cast_key_in(range.first)
36
+ include_start = !options.fetch(:exclude_start, false)
37
+ finish = cast_key_in(range.last)
38
+ include_finish = !range.exclude_end?
39
+ limit = options.fetch(:limit, -1)
40
+ begin
41
+ list = ArrayList.new( lib.range( @db,
42
+ *[ start, include_start,
43
+ finish, include_finish,
44
+ limit ].flatten ) )
45
+ list.to_a
46
+ ensure
47
+ list.free if list
48
+ end
49
+ else
50
+ super
51
+ end
52
+ end
53
+
54
+ def values(key = nil)
55
+ if key.nil?
56
+ super()
57
+ else
58
+ begin
59
+ pointer = try( :get4, cast_key_in(key),
60
+ :failure => lambda { |ptr| ptr.address.zero? },
61
+ :no_error => {22 => nil} )
62
+ if pointer.nil?
63
+ [ ]
64
+ else
65
+ list = ArrayList.new(pointer)
66
+ list.to_a
67
+ end
68
+ ensure
69
+ list.free if list
70
+ end
71
+ end
72
+ end
73
+
74
+ def delete(key, mode = nil, &missing_handler)
75
+ if mode == :dup
76
+ values = values(key)
77
+ if try(:out3, cast_key_in(key), :no_error => {22 => false})
78
+ values
79
+ else
80
+ missing_handler ? missing_handler[key] : values
81
+ end
82
+ else
83
+ super(key, &missing_handler)
84
+ end
85
+ end
86
+
87
+ def size(key = nil)
88
+ if key.nil?
89
+ super()
90
+ else
91
+ try(:vnum, cast_key_in(key), :failure => 0, :no_error => {22 => 0})
92
+ end
93
+ end
94
+ alias_method :length, :size
95
+
96
+ #################
97
+ ### Iteration ###
98
+ #################
99
+
100
+ def each_key(start = nil)
101
+ cursor_in_loop(start) do |iterator|
102
+ throw(:finish_iteration) unless key = iterator.key
103
+ yield key
104
+ end
105
+ end
106
+
107
+ def each(start = nil)
108
+ cursor_in_loop(start) do |iterator|
109
+ throw(:finish_iteration) unless key_and_value = iterator.key_and_value
110
+ yield key_and_value
111
+ end
112
+ end
113
+ alias_method :each_pair, :each
114
+
115
+ def reverse_each(start = nil)
116
+ cursor_in_loop(start, :reverse) do |iterator|
117
+ throw(:finish_iteration) unless key_and_value = iterator.key_and_value
118
+ yield key_and_value
119
+ end
120
+ end
121
+
122
+ def each_value(start = nil)
123
+ cursor_in_loop(start) do |iterator|
124
+ throw(:finish_iteration) unless value = iterator.value
125
+ yield value
126
+ end
127
+ end
128
+
129
+ def delete_if(start = nil)
130
+ cursor(start) do |iterator|
131
+ loop do
132
+ break unless key_and_value = iterator.key_and_value
133
+ break unless iterator.send(yield(*key_and_value) ? :delete : :next)
134
+ end
135
+ end
136
+ end
137
+
138
+ #######
139
+ private
140
+ #######
141
+
142
+ def tune(options)
143
+ super
144
+ if cmpfunc = options[:cmpfunc]
145
+ callback = lambda { |a_pointer, a_size, b_pointer, b_size, _|
146
+ a = a_pointer.get_bytes(0, a_size)
147
+ b = b_pointer.get_bytes(0, b_size)
148
+ cmpfunc[a, b]
149
+ }
150
+ try(:setcmpfunc, callback, nil)
151
+ end
152
+ if options.values_at(:lmemb, :nmemb, :bnum, :apow, :fpow, :opts).any?
153
+ optimize(options.merge(:tune => true))
154
+ end
155
+ if options.values_at(:lcnum, :ncnum).any?
156
+ setcache(options)
157
+ end
158
+ end
159
+
160
+ def setcache(options)
161
+ try( :setcache,
162
+ options.fetch(:lcnum, 0).to_i,
163
+ options.fetch(:ncnum, 0).to_i )
164
+ end
165
+
166
+ def cursor(start = nil, reverse = false)
167
+ cursor = Cursor.new(@db, start.nil? ? start : cast_key_in(start), reverse)
168
+ yield cursor
169
+ self
170
+ ensure
171
+ cursor.free if cursor
172
+ end
173
+
174
+ def cursor_in_loop(start = nil, reverse = false)
175
+ cursor(start, reverse) do |iterator|
176
+ catch(:finish_iteration) do
177
+ loop do
178
+ yield iterator
179
+ break unless iterator.next
180
+ end
181
+ end
182
+ end
183
+ end
184
+ end
185
+ end
@@ -0,0 +1,45 @@
1
+ module OklahomaMixer
2
+ class BTreeDatabase < HashDatabase
3
+ module C # :nodoc:
4
+ extend OklahomaMixer::Utilities::FFIDSL
5
+
6
+ prefix :tcbdb
7
+
8
+ def_hash_database_consts_and_funcs
9
+
10
+ call :name => :TCCMP,
11
+ :args => [:pointer, :int, :pointer, :int, :pointer],
12
+ :returns => :int
13
+ func :name => :setcmpfunc,
14
+ :args => [:pointer, :TCCMP, :pointer],
15
+ :returns => :bool
16
+ func :name => :tune,
17
+ :args => [:pointer, :int32, :int32, :int64, :int8, :int8, OPTS],
18
+ :returns => :bool
19
+ func :name => :setcache,
20
+ :args => [:pointer, :int32, :int32],
21
+ :returns => :bool
22
+ func :name => :optimize,
23
+ :args => [:pointer, :int32, :int32, :int64, :int8, :int8, OPTS],
24
+ :returns => :bool
25
+
26
+ func :name => :putdup,
27
+ :args => [:pointer, :pointer, :int, :pointer, :int],
28
+ :returns => :bool
29
+ func :name => :out3,
30
+ :args => [:pointer, :pointer, :int],
31
+ :returns => :bool
32
+ func :name => :get4,
33
+ :args => [:pointer, :pointer, :int],
34
+ :returns => :pointer
35
+ func :name => :vnum,
36
+ :args => [:pointer, :pointer, :int],
37
+ :returns => :int
38
+
39
+ func :name => :range,
40
+ :args => [:pointer, :pointer, :int, :bool, :pointer, :int, :bool,
41
+ :int],
42
+ :returns => :pointer
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,43 @@
1
+ module OklahomaMixer
2
+ class Cursor # :nodoc:
3
+ def initialize(b_tree_pointer, start = nil, reverse = false)
4
+ @pointer = C.new(b_tree_pointer)
5
+ @reverse = reverse
6
+ if start
7
+ C.jump(@pointer, *start)
8
+ else
9
+ C.send(@reverse ? :last : :first, @pointer)
10
+ end
11
+ end
12
+
13
+ def key
14
+ C.read_from_func(:key, @pointer)
15
+ end
16
+
17
+ def value
18
+ C.read_from_func(:val, @pointer)
19
+ end
20
+
21
+ def key_and_value
22
+ Utilities.temp_xstr do |key|
23
+ Utilities.temp_xstr do |value|
24
+ if C.rec(@pointer, key.pointer, value.pointer)
25
+ [key.to_s, value.to_s]
26
+ end
27
+ end
28
+ end
29
+ end
30
+
31
+ def next
32
+ C.send(@reverse ? :prev : :next, @pointer)
33
+ end
34
+
35
+ def delete
36
+ C.out(@pointer)
37
+ end
38
+
39
+ def free
40
+ C.del(@pointer)
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,45 @@
1
+ module OklahomaMixer
2
+ class Cursor
3
+ module C # :nodoc:
4
+ extend OklahomaMixer::Utilities::FFIDSL
5
+
6
+ prefix :tcbdbcur
7
+
8
+ func :name => :new,
9
+ :args => :pointer,
10
+ :returns => :pointer
11
+ func :name => :del,
12
+ :args => :pointer
13
+
14
+ func :name => :first,
15
+ :args => :pointer,
16
+ :returns => :bool
17
+ func :name => :last,
18
+ :args => :pointer,
19
+ :returns => :bool
20
+ func :name => :jump,
21
+ :args => [:pointer, :pointer, :int],
22
+ :returns => :bool
23
+ func :name => :next,
24
+ :args => :pointer,
25
+ :returns => :bool
26
+ func :name => :prev,
27
+ :args => :pointer,
28
+ :returns => :bool
29
+
30
+ func :name => :key,
31
+ :args => [:pointer, :pointer],
32
+ :returns => :pointer
33
+ func :name => :val,
34
+ :args => [:pointer, :pointer],
35
+ :returns => :pointer
36
+ func :name => :rec,
37
+ :args => [:pointer, :pointer, :pointer],
38
+ :returns => :bool
39
+
40
+ func :name => :out,
41
+ :args => :pointer,
42
+ :returns => :bool
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,95 @@
1
+ module OklahomaMixer
2
+ class FixedLengthDatabase < HashDatabase
3
+ ###################
4
+ ### File System ###
5
+ ###################
6
+
7
+ def optimize(options)
8
+ try( options[:tune] ? :tune : :optimize,
9
+ options.fetch(:width, 0).to_i,
10
+ options.fetch(:limsiz, 0).to_i )
11
+ end
12
+
13
+ def defrag(steps = 0)
14
+ # do nothing: not needed, but provided for a consistent interface
15
+ end
16
+
17
+ ################################
18
+ ### Getting and Setting Keys ###
19
+ ################################
20
+
21
+ def keys(options = { })
22
+ if options.include? :range
23
+ warn "range supersedes prefix" if options[:prefix]
24
+ range = options[:range]
25
+ unless range.respond_to?(:first) and range.respond_to?(:last)
26
+ fail ArgumentError, "Range or two element Array expected"
27
+ end
28
+ start = cast_key_in(range.first)
29
+ include_start = !options.fetch(:exclude_start, false)
30
+ finish = cast_key_in(range.last)
31
+ include_finish = !( range.respond_to?(:exclude_end?) ?
32
+ range.exclude_end? :
33
+ options.fetch(:exclude_end, false) )
34
+ else
35
+ fail ArgumentError, "prefix not supported" if options[:prefix]
36
+ start = cast_key_in(:min)
37
+ include_start = true
38
+ finish = cast_key_in(:max)
39
+ include_finish = true
40
+ end
41
+ limit = options.fetch(:limit, -1)
42
+ Utilities.temp_int do |count|
43
+ begin
44
+ list = lib.range(@db, start, finish, limit, count)
45
+ array = list.get_array_of_uint64(0, count.get_int(0))
46
+ array.shift if array.first == start and not include_start
47
+ array.pop if array.last == finish and not include_finish
48
+ array
49
+ ensure
50
+ Utilities.free(list) if list
51
+ end
52
+ end
53
+ end
54
+
55
+ #################
56
+ ### Iteration ###
57
+ #################
58
+
59
+ def each_key
60
+ try(:iterinit)
61
+ loop do
62
+ return self unless key = try( :iternext,
63
+ :failure => 0,
64
+ :no_error => {22 => nil} )
65
+ yield key
66
+ end
67
+ end
68
+
69
+ def each
70
+ each_key do |key|
71
+ yield [key, self[key]]
72
+ end
73
+ end
74
+ alias_method :each_pair, :each
75
+
76
+ #######
77
+ private
78
+ #######
79
+
80
+ def tune(options)
81
+ if options.values_at(:width, :limsiz).any?
82
+ optimize(options.merge(:tune => true))
83
+ end
84
+ end
85
+
86
+ def cast_key_in(key)
87
+ case key
88
+ when :min, :max, :prev, :next, "min", "max", "prev", "next"
89
+ C::IDS["FDBID#{key.to_s.upcase}".to_sym]
90
+ else
91
+ key.to_i
92
+ end
93
+ end
94
+ end
95
+ end