rufus-tokyo 0.1.2 → 0.1.3

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.
@@ -0,0 +1,3 @@
1
+
2
+ require 'rufus/tokyo/cabinet/abstract'
3
+
@@ -0,0 +1,237 @@
1
+ #
2
+ #--
3
+ # Copyright (c) 2009, John Mettraux, jmettraux@gmail.com
4
+ #
5
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ # of this software and associated documentation files (the "Software"), to deal
7
+ # in the Software without restriction, including without limitation the rights
8
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ # copies of the Software, and to permit persons to whom the Software is
10
+ # furnished to do so, subject to the following conditions:
11
+ #
12
+ # The above copyright notice and this permission notice shall be included in
13
+ # all copies or substantial portions of the Software.
14
+ #
15
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ # THE SOFTWARE.
22
+ #++
23
+ #
24
+
25
+ #
26
+ # "made in Japan"
27
+ #
28
+ # jmettraux@gmail.com
29
+ #
30
+
31
+ require 'rufus/tokyo/hmethods'
32
+ require 'rufus/tokyo/cabinet/lib'
33
+
34
+
35
+ module Rufus::Tokyo
36
+
37
+ #
38
+ # A 'cabinet', ie a Tokyo Cabinet [abstract] database.
39
+ #
40
+ # Follows the abstract API described at :
41
+ #
42
+ # http://tokyocabinet.sourceforge.net/spex-en.html#tcadbapi
43
+ #
44
+ # An usage example :
45
+ #
46
+ # db = Rufus::Tokyo::Cabinet.new('test_data.tch')
47
+ # db['pillow'] = 'Shonagon'
48
+ #
49
+ # db.size # => 1
50
+ # db['pillow'] # => 'Shonagon'
51
+ #
52
+ # db.delete('pillow') # => 'Shonagon'
53
+ # db.size # => 0
54
+ #
55
+ # db.close
56
+ #
57
+ class Cabinet
58
+ include CabinetLibMixin
59
+ include HashMethods
60
+ include Enumerable
61
+
62
+ #
63
+ # Creates/opens the cabinet, raises an exception in case of
64
+ # creation/opening failure.
65
+ #
66
+ # This method accepts a 'name' parameter and an optional 'params' hash
67
+ # parameter.
68
+ #
69
+ # 'name' follows the syntax described at
70
+ #
71
+ # http://tokyocabinet.sourceforge.net/spex-en.html#tcadbapi
72
+ #
73
+ # under tcadbopen(). For example :
74
+ #
75
+ # db = Rufus::Tokyo::Cabinet.new('casket.tch#bnum=100000#opts=ld')
76
+ #
77
+ # will open (eventually create) a hash database backed in the file
78
+ # 'casket.tch' with a bucket number of 100000 and the 'large' and
79
+ # 'deflate' options (opts) turned on.
80
+ #
81
+ # == :hash or :tree
82
+ #
83
+ # Setting the name to :hash or :tree simply will create a in-memory hash
84
+ # or tree respectively (see #new_tree and #new_hash).
85
+ #
86
+ # == tuning parameters
87
+ #
88
+ # It's ok to use the optional params hash to pass tuning parameters and
89
+ # options, thus
90
+ #
91
+ # db = Rufus::Tokyo::Cabinet.new('casket.tch#bnum=100000#opts=ld')
92
+ #
93
+ # and
94
+ #
95
+ # db = Rufus::Tokyo::Cabinet.new(
96
+ # 'casket.tch', :bnum => 100000, :opts => 'ld')
97
+ #
98
+ # are equivalent.
99
+ #
100
+ # == mode
101
+ #
102
+ # To open a db in read-only mode :
103
+ #
104
+ # db = Rufus::Tokyo::Cabinet.new('casket.tch#mode=r')
105
+ # db = Rufus::Tokyo::Cabinet.new('casket.tch', :mode => 'r')
106
+ #
107
+ def initialize (name, params={})
108
+
109
+ @db = lib.tcadbnew
110
+
111
+ name = '*' if name == :hash # in memory hash database
112
+ name = '+' if name == :tree # in memory B+ tree database
113
+
114
+ name = name + params.collect { |k, v| "##{k}=#{v}" }.join('')
115
+
116
+ (lib.tcadbopen(@db, name) == 1) ||
117
+ raise("failed to open/create db '#{name}'")
118
+
119
+ self.default = params[:default]
120
+ @default_proc ||= params[:default_proc]
121
+ end
122
+
123
+ #
124
+ # Returns a new in-memory hash. Accepts the same optional params hash
125
+ # as new().
126
+ #
127
+ def self.new_hash (params={})
128
+ self.new(:hash, params)
129
+ end
130
+
131
+ #
132
+ # Returns a new in-memory B+ tree. Accepts the same optional params hash
133
+ # as new().
134
+ #
135
+ def self.new_tree (params={})
136
+ self.new(:tree, params)
137
+ end
138
+
139
+ #
140
+ # No comment
141
+ #
142
+ def []= (k, v)
143
+ lib.tcadbput2(@db, k, v)
144
+ end
145
+
146
+ #
147
+ # (The actual #[] method is provided by HashMethods
148
+ #
149
+ def get (k)
150
+ lib.tcadbget2(@db, k) rescue nil
151
+ end
152
+ protected :get
153
+
154
+ #
155
+ # Removes a record from the cabinet, returns the value if successful
156
+ # else nil.
157
+ #
158
+ def delete (k)
159
+ v = self[k]
160
+ (lib.tcadbout2(@db, k) == 1) ? v : nil
161
+ end
162
+
163
+ #
164
+ # Returns the number of records in the 'cabinet'
165
+ #
166
+ def size
167
+ lib.tcadbrnum(@db)
168
+ end
169
+
170
+ #
171
+ # Removes all the records in the cabinet (use with care)
172
+ #
173
+ # Returns self (like Ruby's Hash does).
174
+ #
175
+ def clear
176
+ lib.tcadbvanish(@db)
177
+ self
178
+ end
179
+
180
+ #
181
+ # Returns the 'weight' of the db (in bytes)
182
+ #
183
+ def weight
184
+ lib.tcadbsize(@db)
185
+ end
186
+
187
+ #
188
+ # Closes the cabinet (and frees the datastructure allocated for it),
189
+ # returns true in case of success.
190
+ #
191
+ def close
192
+ result = lib.tcadbclose(@db)
193
+ lib.tcadbdel(@db)
194
+ (result == 1)
195
+ end
196
+
197
+ #
198
+ # Copies the current cabinet to a new file.
199
+ #
200
+ # Returns true if it was successful.
201
+ #
202
+ def copy (target_path)
203
+ (lib.tcadbcopy(@db, target_path) == 1)
204
+ end
205
+
206
+ #
207
+ # Copies the current cabinet to a new file.
208
+ #
209
+ # Does it by copying each entry afresh to the target file. Spares some
210
+ # space, hence the 'compact' label...
211
+ #
212
+ def compact_copy (target_path)
213
+ @other_db = Rufus::Tokyo::Cabinet.new(target_path)
214
+ self.each { |k, v| @other_db[k] = v }
215
+ @other_db.close
216
+ end
217
+
218
+ #
219
+ # "synchronize updated contents of an abstract database object with
220
+ # the file and the device"
221
+ #
222
+ def sync
223
+ (lib.tcadbsync(@db) == 1)
224
+ end
225
+
226
+ #
227
+ # Returns an array with all the keys in the databse
228
+ #
229
+ def keys
230
+ a = []
231
+ lib.tcadbiterinit(@db)
232
+ while (k = (lib.tcadbiternext2(@db) rescue nil)); a << k; end
233
+ a
234
+ end
235
+ end
236
+ end
237
+
@@ -0,0 +1,179 @@
1
+ #
2
+ #--
3
+ # Copyright (c) 2009, John Mettraux, jmettraux@gmail.com
4
+ #
5
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ # of this software and associated documentation files (the "Software"), to deal
7
+ # in the Software without restriction, including without limitation the rights
8
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ # copies of the Software, and to permit persons to whom the Software is
10
+ # furnished to do so, subject to the following conditions:
11
+ #
12
+ # The above copyright notice and this permission notice shall be included in
13
+ # all copies or substantial portions of the Software.
14
+ #
15
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ # THE SOFTWARE.
22
+ #++
23
+ #
24
+
25
+ #
26
+ # "made in Japan"
27
+ #
28
+ # jmettraux@gmail.com
29
+ #
30
+
31
+ require 'rufus/tokyo/base'
32
+
33
+
34
+ module Rufus::Tokyo
35
+
36
+ #
37
+ # The libtokyocabinet.so methods get bound to this module
38
+ #
39
+ module CabinetLib #:nodoc#
40
+ extend FFI::Library
41
+
42
+ Rufus::Tokyo.generate_lib_mixin(self)
43
+ # module Rufus::Tokyo::CabinetLibMixin
44
+
45
+ #
46
+ # find Tokyo Cabinet lib
47
+
48
+ paths = Array(ENV['TOKYO_CABINET_LIB'] || %w{
49
+ /opt/local/lib/libtokyocabinet.dylib
50
+ /usr/local/lib/libtokyocabinet.dylib
51
+ /usr/local/lib/libtokyocabinet.so
52
+ })
53
+
54
+ ffi_lib(paths.find { |path| File.exist?(path) })
55
+
56
+ #
57
+ # maybe put that in a standalone c_lib.rb
58
+
59
+ # length of a string
60
+ #
61
+ attach_function :strlen, [ :string ], :int
62
+
63
+ # frees a mem zone (TC style)
64
+ #
65
+ attach_function :tcfree, [ :pointer ], :void
66
+
67
+ #
68
+ # tcadb functions
69
+ #
70
+ # http://tokyocabinet.sourceforge.net/spex-en.html#tcadbapi
71
+
72
+ attach_function :tcadbnew, [], :pointer
73
+
74
+ attach_function :tcadbopen, [ :pointer, :string ], :int
75
+ attach_function :tcadbclose, [ :pointer ], :int
76
+
77
+ attach_function :tcadbdel, [ :pointer ], :void
78
+
79
+ attach_function :tcadbrnum, [ :pointer ], :uint64
80
+ attach_function :tcadbsize, [ :pointer ], :uint64
81
+
82
+ attach_function :tcadbput2, [ :pointer, :string, :string ], :int
83
+ attach_function :tcadbget2, [ :pointer, :string ], :string
84
+ attach_function :tcadbout2, [ :pointer, :string ], :int
85
+
86
+ attach_function :tcadbiterinit, [ :pointer ], :int
87
+ attach_function :tcadbiternext2, [ :pointer ], :string
88
+
89
+ attach_function :tcadbvanish, [ :pointer ], :int
90
+
91
+ attach_function :tcadbsync, [ :pointer ], :int
92
+ attach_function :tcadbcopy, [ :pointer, :string ], :int
93
+
94
+ #
95
+ # tctdb functions
96
+ #
97
+ # http://tokyocabinet.sourceforge.net/spex-en.html#tctdbapi
98
+
99
+ attach_function :tctdbnew, [], :pointer
100
+
101
+ attach_function :tctdbopen, [ :pointer, :string, :int ], :int
102
+
103
+ attach_function :tctdbgenuid, [ :pointer ], :int64
104
+
105
+ attach_function :tctdbget, [ :pointer, :string, :int ], :pointer
106
+
107
+ attach_function :tctdbput, [ :pointer, :string, :int, :pointer ], :int
108
+ attach_function :tctdbput3, [ :pointer, :string, :string ], :int
109
+ attach_function :tctdbout2, [ :pointer, :string ], :int
110
+
111
+ attach_function :tctdbecode, [ :pointer ], :int
112
+ attach_function :tctdberrmsg, [ :int ], :string
113
+
114
+ attach_function :tctdbclose, [ :pointer ], :int
115
+ attach_function :tctdbdel, [ :pointer ], :void
116
+
117
+ attach_function :tctdbrnum, [ :pointer ], :uint64
118
+
119
+ attach_function :tctdbvanish, [ :pointer ], :int
120
+
121
+ #
122
+ # tctdbqry functions
123
+ #
124
+ # http://tokyocabinet.sourceforge.net/spex-en.html#tctdbapi
125
+
126
+ attach_function :tctdbqrynew, [ :pointer ], :pointer
127
+ attach_function :tctdbqrydel, [ :pointer ], :void
128
+
129
+ attach_function :tctdbqryaddcond, [ :pointer, :string, :int, :string ], :void
130
+ attach_function :tctdbqrysetorder, [ :pointer, :string, :int ], :void
131
+ attach_function :tctdbqrysetmax, [ :pointer, :int ], :void
132
+
133
+ attach_function :tctdbqrysearch, [ :pointer ], :pointer
134
+
135
+ attach_function :tctdbqrydel, [ :pointer ], :void
136
+
137
+ #
138
+ # tcmap functions
139
+ #
140
+ # http://tokyocabinet.sourceforge.net/spex-en.html#tcutilapi
141
+
142
+ attach_function :tcmapnew, [], :pointer
143
+
144
+ attach_function :tcmapput2, [ :pointer, :string, :string ], :void
145
+ attach_function :tcmapout2, [ :pointer, :string ], :int
146
+ attach_function :tcmapclear, [ :pointer ], :void
147
+
148
+ attach_function :tcmapdel, [ :pointer ], :void
149
+
150
+ attach_function :tcmapget2, [ :pointer, :string ], :string
151
+
152
+ attach_function :tcmapiterinit, [ :pointer ], :void
153
+ attach_function :tcmapiternext2, [ :pointer ], :string
154
+
155
+ attach_function :tcmaprnum, [ :pointer ], :uint64
156
+
157
+ #
158
+ # tclist functions
159
+ #
160
+ # http://tokyocabinet.sourceforge.net/spex-en.html#tcutilapi
161
+
162
+ attach_function :tclistnew, [], :pointer
163
+
164
+ attach_function :tclistnum, [ :pointer ], :int
165
+ attach_function :tclistval2, [ :pointer, :int ], :string
166
+
167
+ attach_function :tclistpush2, [ :pointer, :string ], :void
168
+ attach_function :tclistpop2, [ :pointer ], :string
169
+ attach_function :tclistshift2, [ :pointer ], :string
170
+ attach_function :tclistunshift2, [ :pointer, :string ], :void
171
+ attach_function :tclistover2, [ :pointer, :int, :string ], :void
172
+
173
+ attach_function :tclistremove2, [ :pointer, :int ], :string
174
+ # beware, seems like have to free the return string self
175
+
176
+ attach_function :tclistdel, [ :pointer ], :void
177
+ end
178
+ end
179
+
@@ -0,0 +1,528 @@
1
+ #
2
+ #--
3
+ # Copyright (c) 2009, John Mettraux, jmettraux@gmail.com
4
+ #
5
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ # of this software and associated documentation files (the "Software"), to deal
7
+ # in the Software without restriction, including without limitation the rights
8
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ # copies of the Software, and to permit persons to whom the Software is
10
+ # furnished to do so, subject to the following conditions:
11
+ #
12
+ # The above copyright notice and this permission notice shall be included in
13
+ # all copies or substantial portions of the Software.
14
+ #
15
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ # THE SOFTWARE.
22
+ #++
23
+ #
24
+
25
+ #
26
+ # "made in Japan"
27
+ #
28
+ # jmettraux@gmail.com
29
+ #
30
+
31
+ require 'rufus/tokyo/cabinet/lib'
32
+ require 'rufus/tokyo/cabinet/util'
33
+
34
+
35
+ module Rufus::Tokyo
36
+
37
+ #
38
+ # A 'table' a table database.
39
+ #
40
+ # http://alpha.mixi.co.jp/blog/?p=290
41
+ # http://tokyocabinet.sourceforge.net/spex-en.html#tctdbapi
42
+ #
43
+ # A short example :
44
+ #
45
+ # require 'rubygems'
46
+ # require 'rufus/tokyo/cabinet/table'
47
+ #
48
+ # t = Rufus::Tokyo::Table.new('table.tdb', :create, :write)
49
+ # # '.tdb' suffix is a must
50
+ #
51
+ # t['pk0'] = { 'name' => 'alfred', 'age' => '22' }
52
+ # t['pk1'] = { 'name' => 'bob', 'age' => '18' }
53
+ # t['pk2'] = { 'name' => 'charly', 'age' => '45' }
54
+ # t['pk3'] = { 'name' => 'doug', 'age' => '77' }
55
+ # t['pk4'] = { 'name' => 'ephrem', 'age' => '32' }
56
+ #
57
+ # p t.query { |q|
58
+ # q.add_condition 'age', :numge, '32'
59
+ # q.order_by 'age'
60
+ # q.limit 2
61
+ # }
62
+ # # => [ {"name"=>"ephrem", :pk=>"pk4", "age"=>"32"},
63
+ # # {"name"=>"charly", :pk=>"pk2", "age"=>"45"} ]
64
+ #
65
+ # t.close
66
+ #
67
+ class Table
68
+ include CabinetLibMixin
69
+ include TokyoContainerMixin
70
+
71
+ #
72
+ # Creates a Table instance (creates or opens it depending on the args)
73
+ #
74
+ # For example,
75
+ #
76
+ # t = Rufus::Tokyo::Table.new('table.tdb', :create, :write)
77
+ # # '.tdb' suffix is a must
78
+ #
79
+ # will create the table.tdb (or simply open it if already present)
80
+ # and make sure we have write access to it.
81
+ # Note that the suffix (.tdc) is relevant to Tokyo Cabinet, using another
82
+ # will result in a Tokyo Cabinet error.
83
+ #
84
+ def initialize (*args)
85
+
86
+ path = args.first # car
87
+ params = args[1..-1] # cdr
88
+
89
+ mode = compute_open_mode(params)
90
+
91
+ @db = self.lib.tctdbnew
92
+
93
+ (lib.tctdbopen(@db, path, compute_open_mode(params)) == 1 ) || raise_error
94
+ end
95
+
96
+ #
97
+ # Closes the table (and frees the datastructure allocated for it),
98
+ # returns true in case of success.
99
+ #
100
+ def close
101
+ result = lib.tctdbclose(@db)
102
+ lib.tctdbdel(@db)
103
+ (result == 1)
104
+ end
105
+
106
+ #
107
+ # Generates a unique id (in the context of this Table instance)
108
+ #
109
+ def generate_unique_id
110
+ lib.tctdbgenuid(@db)
111
+ end
112
+ alias :genuid :generate_unique_id
113
+
114
+ #
115
+ # Accepts a variable number of arguments, at least two. First one
116
+ # is the primary key of the record, the others are the columns.
117
+ #
118
+ # One can also directly write
119
+ #
120
+ # table['one'] = [ 'name', 'toto', 'age', '33' ]
121
+ # table['two'] = [ 'name', 'fred', 'age', '45' ]
122
+ #
123
+ # instead of
124
+ #
125
+ # table.tabbed_put('one', 'name', 'toto', 'age', '33')
126
+ # table.tabbed_put('two', 'name', 'fred', 'age', '45')
127
+ #
128
+ # beware : inserting an array uses a tab separator...
129
+ #
130
+ def tabbed_put (pk, *args)
131
+
132
+ cols = args.collect { |e| e.to_s }.join("\t")
133
+
134
+ (lib.tctdbput3(@db, pk, cols) == 1) || raise_error
135
+
136
+ args
137
+ end
138
+
139
+ #
140
+ # Inserts a record in the table db
141
+ #
142
+ # table['pk0'] = [ 'name', 'fred', 'age', '45' ]
143
+ # table['pk1'] = { 'name' => 'jeff', 'age' => '46' }
144
+ #
145
+ def []= (pk, h_or_a)
146
+
147
+ return tabbed_put(pk, *h_or_a) if h_or_a.is_a?(Array)
148
+
149
+ pklen = lib.strlen(pk)
150
+
151
+ m = Rufus::Tokyo::Map.from_h(h_or_a)
152
+
153
+ r = lib.tctdbput(@db, pk, pklen, m.pointer)
154
+
155
+ m.free
156
+
157
+ (r == 1) || raise_error
158
+
159
+ h_or_a
160
+ end
161
+
162
+ #
163
+ # Removes an entry in the table
164
+ #
165
+ # (might raise an error if the delete itself failed, but returns nil
166
+ # if there was no entry for the given key)
167
+ #
168
+ def delete (k)
169
+ v = self[k]
170
+ return nil unless v
171
+ (lib.tctdbout2(@db, k) == 1) || raise_error
172
+ v
173
+ end
174
+
175
+ #
176
+ # Removes all records in this table database
177
+ #
178
+ def clear
179
+ (lib.tctdbvanish(@db) == 1) || raise_error
180
+ end
181
+
182
+ #
183
+ # Returns the value (as a Ruby Hash) else nil
184
+ #
185
+ def [] (k)
186
+ m = lib.tctdbget(@db, k, lib.strlen(k))
187
+ return nil if m.address == 0 # :( too bad, but it works
188
+ Rufus::Tokyo::Map.to_h(m) # which frees the map
189
+ end
190
+
191
+ #
192
+ # Returns the number of records in this table db
193
+ #
194
+ def size
195
+ lib.tctdbrnum(@db)
196
+ end
197
+
198
+ #
199
+ # Prepares a query instance (block is optional)
200
+ #
201
+ def prepare_query (&block)
202
+ q = TableQuery.new(self)
203
+ block.call(q) if block
204
+ q
205
+ end
206
+
207
+ #
208
+ # Prepares and runs a query, returns a ResultSet instance
209
+ # (takes care of freeing the query structure)
210
+ #
211
+ def do_query (&block)
212
+ q = prepare_query(&block)
213
+ rs = q.run
214
+ q.free
215
+ rs
216
+ end
217
+
218
+ #
219
+ # Prepares and runs a query, returns an array of hashes (all Ruby)
220
+ # (takes care of freeing the query and the result set structures)
221
+ #
222
+ def query (&block)
223
+ rs = do_query(&block)
224
+ a = rs.to_a
225
+ rs.free
226
+ a
227
+ end
228
+
229
+ #
230
+ # Returns the actual pointer to the Tokyo Cabinet table
231
+ #
232
+ def pointer
233
+ @db
234
+ end
235
+
236
+ protected
237
+
238
+ #
239
+ # Obviously something got wrong, let's ask the db about it and raise
240
+ # a TokyoError
241
+ #
242
+ def raise_error
243
+
244
+ err_code = lib.tctdbecode(@db)
245
+ err_msg = lib.tctdberrmsg(err_code)
246
+
247
+ raise TokyoError, "(err #{err_code}) #{err_msg}"
248
+ end
249
+ end
250
+
251
+ #
252
+ # A query on a Tokyo Cabinet table db
253
+ #
254
+ class TableQuery
255
+ include CabinetLibMixin
256
+
257
+ OPERATORS = {
258
+
259
+ # strings...
260
+
261
+ :streq => 0, # string equality
262
+ :eq => 0,
263
+ :eql => 0,
264
+ :equals => 0,
265
+
266
+ :strinc => 1, # string include
267
+ :inc => 1, # string include
268
+ :includes => 1, # string include
269
+
270
+ :strbw => 2, # string begins with
271
+ :bw => 2,
272
+ :starts_with => 2,
273
+ :strew => 3, # string ends with
274
+ :ew => 3,
275
+ :ends_with => 3,
276
+
277
+ :strand => 4, # string which include all the tokens in the given exp
278
+ :and => 4,
279
+
280
+ :stror => 5, # string which include at least one of the tokens
281
+ :or => 5,
282
+
283
+ :stroreq => 6, # string which is equal to at least one token
284
+
285
+ :strorrx => 7, # string which matches the given regex
286
+ :regex => 7,
287
+ :matches => 7,
288
+
289
+ # numbers...
290
+
291
+ :numeq => 8, # equal
292
+ :numequals => 8,
293
+ :numgt => 9, # greater than
294
+ :gt => 9,
295
+ :numge => 10, # greater or equal
296
+ :ge => 10,
297
+ :gte => 10,
298
+ :numlt => 11, # greater or equal
299
+ :lt => 11,
300
+ :numle => 12, # greater or equal
301
+ :le => 12,
302
+ :lte => 12,
303
+ :numbt => 13, # a number between two tokens in the given exp
304
+ :bt => 13,
305
+ :between => 13,
306
+
307
+ :numoreq => 14 # number which is equal to at least one token
308
+ }
309
+
310
+ TDBQCNEGATE = 1 << 24
311
+ TDBQCNOIDX = 1 << 25
312
+
313
+ DIRECTIONS = {
314
+ :strasc => 0,
315
+ :strdesc => 1,
316
+ :asc => 0,
317
+ :desc => 1,
318
+ :numasc => 2,
319
+ :numdesc => 3
320
+ }
321
+
322
+ #
323
+ # Creates a query for a given Rufus::Tokyo::Table
324
+ #
325
+ # Queries are usually created via the #query (#prepare_query #do_query)
326
+ # of the Table instance.
327
+ #
328
+ # Methods of interest here are :
329
+ #
330
+ # * #add (or #add_condition)
331
+ # * #order_by
332
+ # * #limit
333
+ #
334
+ # also
335
+ #
336
+ # * #pk_only
337
+ # * #no_pk
338
+ #
339
+ def initialize (table)
340
+ @table = table
341
+ @query = lib.tctdbqrynew(@table.pointer)
342
+ @opts = {}
343
+ end
344
+
345
+ #
346
+ # Adds a condition
347
+ #
348
+ # table.query { |q|
349
+ # q.add 'name', :equals, 'Oppenheimer'
350
+ # q.add 'age', :numgt, 35
351
+ # }
352
+ #
353
+ # Understood 'operators' :
354
+ #
355
+ # :streq # string equality
356
+ # :eq
357
+ # :eql
358
+ # :equals
359
+ #
360
+ # :strinc # string include
361
+ # :inc # string include
362
+ # :includes # string include
363
+ #
364
+ # :strbw # string begins with
365
+ # :bw
366
+ # :starts_with
367
+ # :strew # string ends with
368
+ # :ew
369
+ # :ends_with
370
+ #
371
+ # :strand # string which include all the tokens in the given exp
372
+ # :and
373
+ #
374
+ # :stror # string which include at least one of the tokens
375
+ # :or
376
+ #
377
+ # :stroreq # string which is equal to at least one token
378
+ #
379
+ # :strorrx # string which matches the given regex
380
+ # :regex
381
+ # :matches
382
+ #
383
+ # # numbers...
384
+ #
385
+ # :numeq # equal
386
+ # :numequals
387
+ # :numgt # greater than
388
+ # :gt
389
+ # :numge # greater or equal
390
+ # :ge
391
+ # :gte
392
+ # :numlt # greater or equal
393
+ # :lt
394
+ # :numle # greater or equal
395
+ # :le
396
+ # :lte
397
+ # :numbt # a number between two tokens in the given exp
398
+ # :bt
399
+ # :between
400
+ #
401
+ # :numoreq # number which is equal to at least one token
402
+ #
403
+ def add (colname, operator, val, affirmative=true, no_index=true)
404
+ op = operator.is_a?(Fixnum) ? operator : OPERATORS[operator]
405
+ op = op | TDBQCNEGATE unless affirmative
406
+ op = op | TDBQCNOIDX if no_index
407
+ lib.tctdbqryaddcond(@query, colname, op, val)
408
+ end
409
+ alias :add_condition :add
410
+
411
+ #
412
+ # Sets the max number of records to return for this query.
413
+ #
414
+ # (sorry no 'offset' as of now)
415
+ #
416
+ def limit (i)
417
+ lib.tctdbqrysetmax(@query, i)
418
+ end
419
+
420
+ #
421
+ # Sets the sort order for the result of the query
422
+ #
423
+ # The 'direction' may be :
424
+ #
425
+ # :strasc # string ascending
426
+ # :strdesc
427
+ # :asc # string ascending
428
+ # :desc
429
+ # :numasc # number ascending
430
+ # :numdesc
431
+ #
432
+ def order_by (colname, direction=:strasc)
433
+ lib.tctdbqrysetorder(@query, colname, DIRECTIONS[direction])
434
+ end
435
+
436
+ #
437
+ # When set to true, only the primary keys of the matching records will
438
+ # be returned.
439
+ #
440
+ def pk_only (on=true)
441
+ @opts[:pk_only] = on
442
+ end
443
+
444
+ #
445
+ # When set to true, the :pk (primary key) is not inserted in the record
446
+ # (hashes) returned
447
+ #
448
+ def no_pk (on=true)
449
+ @opts[:no_pk] = on
450
+ end
451
+
452
+ #
453
+ # Runs this query (returns a TableResultSet instance)
454
+ #
455
+ def run
456
+ TableResultSet.new(@table, lib.tctdbqrysearch(@query), @opts)
457
+ end
458
+
459
+ #
460
+ # Frees this data structure
461
+ #
462
+ def free
463
+ lib.tctdbqrydel(@query)
464
+ @query = nil
465
+ end
466
+
467
+ alias :close :free
468
+ alias :destroy :free
469
+ end
470
+
471
+ #
472
+ # The thing queries return
473
+ #
474
+ class TableResultSet
475
+ include CabinetLibMixin
476
+ include Enumerable
477
+
478
+ def initialize (table, list_pointer, query_opts)
479
+ @table = table
480
+ @list = list_pointer
481
+ @opts = query_opts
482
+ end
483
+
484
+ #
485
+ # Returns the count of element in this result set
486
+ #
487
+ def size
488
+ lib.tclistnum(@list)
489
+ end
490
+
491
+ alias :length :size
492
+
493
+ #
494
+ # The classical each
495
+ #
496
+ def each
497
+ (0..size-1).each do |i|
498
+ pk = lib.tclistval2(@list, i)
499
+ if @opts[:pk_only]
500
+ yield(pk)
501
+ else
502
+ val = @table[pk]
503
+ val[:pk] = pk unless @opts[:no_pk]
504
+ yield(val)
505
+ end
506
+ end
507
+ end
508
+
509
+ #
510
+ # Returns an array of hashes
511
+ #
512
+ def to_a
513
+ collect { |m| m }
514
+ end
515
+
516
+ #
517
+ # Frees this query (the underlying Tokyo Cabinet list structure)
518
+ #
519
+ def free
520
+ lib.tclistdel(@list)
521
+ @list = nil
522
+ end
523
+
524
+ alias :close :free
525
+ alias :destroy :free
526
+ end
527
+ end
528
+