oklahoma_mixer 0.1.0 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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