jackowayed-rufus-tokyo 0.1.13.1 → 0.1.13.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,231 @@
1
+ #--
2
+ # Copyright (c) 2009, John Mettraux, jmettraux@gmail.com
3
+ #
4
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
5
+ # of this software and associated documentation files (the "Software"), to deal
6
+ # in the Software without restriction, including without limitation the rights
7
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8
+ # copies of the Software, and to permit persons to whom the Software is
9
+ # furnished to do so, subject to the following conditions:
10
+ #
11
+ # The above copyright notice and this permission notice shall be included in
12
+ # all copies or substantial portions of the Software.
13
+ #
14
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20
+ # THE SOFTWARE.
21
+ #
22
+ # Made in Japan.
23
+ #++
24
+
25
+
26
+ module Rufus::Tokyo
27
+
28
+ #
29
+ # The libtokyocabinet.so methods get bound to this module
30
+ #
31
+ module CabinetLib #:nodoc#
32
+ extend FFI::Library
33
+
34
+ #
35
+ # find Tokyo Cabinet lib
36
+
37
+ paths = Array(ENV['TOKYO_CABINET_LIB'] || %w{
38
+ /usr/lib/libtokyocabinet.so
39
+ /opt/local/lib/libtokyocabinet.dylib
40
+ /opt/local/lib/libtokyocabinet.so
41
+ /usr/local/lib/libtokyocabinet.dylib
42
+ /usr/local/lib/libtokyocabinet.so
43
+ })
44
+
45
+ begin
46
+
47
+ ffi_lib(*paths)
48
+
49
+ rescue LoadError => le
50
+ raise(
51
+ "didn't find Tokyo Cabinet libs on your system. " +
52
+ "Please install Tokyo Cabinet (http://tokyocabinet.sf.net) " +
53
+ "(see also http://openwferu.rubyforge.org/tokyo.html)"
54
+ )
55
+ end
56
+
57
+ class << self
58
+ alias :attfunc :attach_function
59
+ end
60
+
61
+ #
62
+ # maybe put that in a standalone c_lib.rb
63
+
64
+ # length of a string
65
+ #
66
+ attfunc :strlen, [ :string ], :int
67
+
68
+ # frees a mem zone (TC style)
69
+ #
70
+ attfunc :tcfree, [ :pointer ], :void
71
+
72
+ #
73
+ # tcadb functions
74
+ #
75
+ # http://tokyocabinet.sourceforge.net/spex-en.html#tcadbapi
76
+
77
+ attfunc :tcadbnew, [], :pointer
78
+
79
+ attfunc :tcadbopen, [ :pointer, :string ], :int
80
+ attfunc :abs_close, :tcadbclose, [ :pointer ], :int
81
+
82
+ attfunc :abs_del, :tcadbdel, [ :pointer ], :void
83
+
84
+ attfunc :abs_rnum, :tcadbrnum, [ :pointer ], :uint64
85
+ attfunc :abs_size, :tcadbsize, [ :pointer ], :uint64
86
+
87
+ attfunc :abs_put2, :tcadbput2, [ :pointer, :string, :string ], :int
88
+ attfunc :abs_get2, :tcadbget2, [ :pointer, :string ], :string
89
+ attfunc :abs_out2, :tcadbout2, [ :pointer, :string ], :int
90
+
91
+ attfunc :abs_iterinit, :tcadbiterinit, [ :pointer ], :int
92
+ attfunc :abs_iternext2, :tcadbiternext2, [ :pointer ], :string
93
+
94
+ attfunc :abs_vanish, :tcadbvanish, [ :pointer ], :int
95
+
96
+ attfunc :abs_sync, :tcadbsync, [ :pointer ], :int
97
+ attfunc :abs_copy, :tcadbcopy, [ :pointer, :string ], :int
98
+
99
+ attfunc :abs_fwmkeys2, :tcadbfwmkeys2, [ :pointer, :string, :int ], :pointer
100
+
101
+ attfunc :tcadbmisc, [ :pointer, :string, :pointer ], :pointer
102
+
103
+ attfunc :addint, :tcadbaddint, [ :pointer, :string, :int, :int ], :int
104
+ attfunc :adddouble, :tcadbadddouble, [ :pointer, :string, :int, :double ], :double
105
+
106
+ begin # since TC 1.4.13
107
+ attfunc :tcadbtranbegin, [ :pointer ], :int
108
+ attfunc :tcadbtrancommit, [ :pointer ], :int
109
+ attfunc :tcadbtranabort, [ :pointer ], :int
110
+ rescue FFI::NotFoundError => nfe
111
+ end
112
+
113
+ #
114
+ # tctdb functions
115
+ #
116
+ # http://tokyocabinet.sourceforge.net/spex-en.html#tctdbapi
117
+
118
+ attfunc :tctdbnew, [], :pointer
119
+ attfunc :tctdbsetmutex, [ :pointer ], :int
120
+ attfunc :tctdbtune, [ :pointer, :uint64, :uint8, :uint8, :uint8 ], :int
121
+ attfunc :tctdbsetcache, [ :pointer, :uint32, :uint32, :uint32 ], :int
122
+ attfunc :tctdbsetxmsiz, [ :pointer, :uint64 ], :int
123
+
124
+ begin # since TC 1.4.21
125
+ attfunc :tctdbsetdfunit, [ :pointer, :uint32 ], :int
126
+ rescue FFI::NotFoundError => nfe
127
+ end
128
+
129
+ attfunc :tctdbopen, [ :pointer, :string, :int ], :int
130
+
131
+ attfunc :tab_close, :tctdbclose, [ :pointer ], :int
132
+
133
+ attfunc :tab_genuid, :tctdbgenuid, [ :pointer ], :int64
134
+
135
+ attfunc :tab_get, :tctdbget, [ :pointer, :string, :int ], :pointer
136
+
137
+ attfunc :tab_iterinit, :tctdbiterinit, [ :pointer ], :int
138
+ attfunc :tab_iternext2, :tctdbiternext2, [ :pointer ], :string
139
+
140
+ attfunc :tab_put, :tctdbput, [ :pointer, :string, :int, :pointer ], :int
141
+
142
+ #attfunc :tctdbput3, [ :pointer, :string, :string ], :int
143
+ # not using it anymore, Ruby can turn an array into a hash so easily
144
+
145
+ attfunc :tab_out, :tctdbout, [ :pointer, :string, :int ], :int
146
+
147
+ attfunc :tab_ecode, :tctdbecode, [ :pointer ], :int
148
+ attfunc :tab_errmsg, :tctdberrmsg, [ :int ], :string
149
+
150
+ attfunc :tab_del, :tctdbdel, [ :pointer ], :void
151
+
152
+ attfunc :tab_rnum, :tctdbrnum, [ :pointer ], :uint64
153
+
154
+ attfunc :tab_vanish, :tctdbvanish, [ :pointer ], :int
155
+
156
+ attfunc :tab_setindex, :tctdbsetindex, [ :pointer, :string, :int ], :int
157
+
158
+ attfunc :tctdbtranbegin, [ :pointer ], :int
159
+ attfunc :tctdbtrancommit, [ :pointer ], :int
160
+ attfunc :tctdbtranabort, [ :pointer ], :int
161
+
162
+ attfunc :tab_fwmkeys2, :tctdbfwmkeys2, [ :pointer, :string, :int ], :pointer
163
+
164
+ #
165
+ # tctdbqry functions
166
+ #
167
+ # http://tokyocabinet.sourceforge.net/spex-en.html#tctdbapi
168
+
169
+ attfunc :qry_new, :tctdbqrynew, [ :pointer ], :pointer
170
+ attfunc :qry_del, :tctdbqrydel, [ :pointer ], :void
171
+
172
+ attfunc :qry_addcond, :tctdbqryaddcond, [ :pointer, :string, :int, :string ], :void
173
+ attfunc :qry_setorder, :tctdbqrysetorder, [ :pointer, :string, :int ], :void
174
+
175
+ begin # since TC 1.4.10
176
+ attfunc :qry_setmax, :tctdbqrysetmax, [ :pointer, :int ], :void
177
+ rescue FFI::NotFoundError => nfe
178
+ attfunc :qry_setlimit, :tctdbqrysetlimit, [ :pointer, :int, :int ], :void
179
+ end
180
+
181
+ attfunc :qry_search, :tctdbqrysearch, [ :pointer ], :pointer
182
+
183
+ begin # since TC 1.4.12
184
+ attfunc :qry_count, :tctdbqrycount, [ :pointer ], :int
185
+ rescue FFI::NotFoundError => nfe
186
+ end
187
+
188
+ #
189
+ # tcmap functions
190
+ #
191
+ # http://tokyocabinet.sourceforge.net/spex-en.html#tcutilapi
192
+
193
+ attfunc :tcmapnew, [], :pointer
194
+
195
+ attfunc :tcmapput2, [ :pointer, :string, :string ], :void
196
+ attfunc :tcmapout2, [ :pointer, :string ], :int
197
+ attfunc :tcmapclear, [ :pointer ], :void
198
+
199
+ attfunc :tcmapdel, [ :pointer ], :void
200
+
201
+ attfunc :tcmapget2, [ :pointer, :string ], :string
202
+
203
+ attfunc :tcmapiterinit, [ :pointer ], :void
204
+ attfunc :tcmapiternext2, [ :pointer ], :string
205
+
206
+ attfunc :tcmaprnum, [ :pointer ], :uint64
207
+
208
+ #
209
+ # tclist functions
210
+ #
211
+ # http://tokyocabinet.sourceforge.net/spex-en.html#tcutilapi
212
+
213
+ attfunc :tclistnew, [], :pointer
214
+
215
+ attfunc :tclistnum, [ :pointer ], :int
216
+ attfunc :tclistval2, [ :pointer, :int ], :string
217
+
218
+ attfunc :tclistpush2, [ :pointer, :string ], :void
219
+ attfunc :tclistpop2, [ :pointer ], :string
220
+ attfunc :tclistshift2, [ :pointer ], :string
221
+ attfunc :tclistunshift2, [ :pointer, :string ], :void
222
+ attfunc :tclistover2, [ :pointer, :int, :string ], :void
223
+
224
+ attfunc :tclistremove2, [ :pointer, :int ], :string
225
+ # beware, seems like have to free the return string self
226
+
227
+ attfunc :tclistdel, [ :pointer ], :void
228
+ end
229
+
230
+ end
231
+
@@ -0,0 +1,644 @@
1
+ #--
2
+ # Copyright (c) 2009, John Mettraux, jmettraux@gmail.com
3
+ #
4
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
5
+ # of this software and associated documentation files (the "Software"), to deal
6
+ # in the Software without restriction, including without limitation the rights
7
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8
+ # copies of the Software, and to permit persons to whom the Software is
9
+ # furnished to do so, subject to the following conditions:
10
+ #
11
+ # The above copyright notice and this permission notice shall be included in
12
+ # all copies or substantial portions of the Software.
13
+ #
14
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20
+ # THE SOFTWARE.
21
+ #
22
+ # Made in Japan.
23
+ #++
24
+
25
+
26
+ require 'rufus/tokyo/query'
27
+ require 'rufus/tokyo/config'
28
+ require 'rufus/tokyo/transactions'
29
+
30
+
31
+ module Rufus::Tokyo
32
+
33
+ #
34
+ # A 'table' a table database.
35
+ #
36
+ # http://alpha.mixi.co.jp/blog/?p=290
37
+ # http://tokyocabinet.sourceforge.net/spex-en.html#tctdbapi
38
+ #
39
+ # A short example :
40
+ #
41
+ # require 'rubygems'
42
+ # require 'rufus/tokyo/cabinet/table'
43
+ #
44
+ # t = Rufus::Tokyo::Table.new('table.tdb', :create, :write)
45
+ # # '.tdb' suffix is a must
46
+ #
47
+ # t['pk0'] = { 'name' => 'alfred', 'age' => '22' }
48
+ # t['pk1'] = { 'name' => 'bob', 'age' => '18' }
49
+ # t['pk2'] = { 'name' => 'charly', 'age' => '45' }
50
+ # t['pk3'] = { 'name' => 'doug', 'age' => '77' }
51
+ # t['pk4'] = { 'name' => 'ephrem', 'age' => '32' }
52
+ #
53
+ # p t.query { |q|
54
+ # q.add_condition 'age', :numge, '32'
55
+ # q.order_by 'age'
56
+ # q.limit 2
57
+ # }
58
+ # # => [ {"name"=>"ephrem", :pk=>"pk4", "age"=>"32"},
59
+ # # {"name"=>"charly", :pk=>"pk2", "age"=>"45"} ]
60
+ #
61
+ # t.close
62
+ #
63
+ class Table
64
+
65
+ include HashMethods
66
+ include CabinetConfig
67
+
68
+ include Transactions
69
+ # this class has tranbegin/trancommit/tranabort so let's include the
70
+ # transaction mixin
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')
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
+ #
82
+ # == parameters
83
+ #
84
+ # Parameters can be set in the path or via the optional params hash (like
85
+ # in Rufus::Tokyo::Cabinet)
86
+ #
87
+ # * :mode a set of chars ('r'ead, 'w'rite, 'c'reate, 't'runcate,
88
+ # 'e' non locking, 'f' non blocking lock), default is 'wc'
89
+ # * :opts a set of chars ('l'arge, 'd'eflate, 'b'zip2, 't'cbs)
90
+ # (usually empty or something like 'ld' or 'lb')
91
+ #
92
+ # * :bnum number of elements of the bucket array
93
+ # * :apow size of record alignment by power of 2 (defaults to 4)
94
+ # * :fpow maximum number of elements of the free block pool by
95
+ # power of 2 (defaults to 10)
96
+ # * :mutex when set to true, makes sure only 1 thread at a time
97
+ # accesses the table (well, Ruby, global thread lock, ...)
98
+ #
99
+ # * :rcnum specifies the maximum number of records to be cached.
100
+ # If it is not more than 0, the record cache is disabled.
101
+ # It is disabled by default.
102
+ # * :lcnum specifies the maximum number of leaf nodes to be cached.
103
+ # If it is not more than 0, the default value is specified.
104
+ # The default value is 2048.
105
+ # * :ncnum specifies the maximum number of non-leaf nodes to be
106
+ # cached. If it is not more than 0, the default value is
107
+ # specified. The default value is 512.
108
+ #
109
+ # * :xmsiz specifies the size of the extra mapped memory. If it is
110
+ # not more than 0, the extra mapped memory is disabled.
111
+ # The default size is 67108864.
112
+ #
113
+ # * :dfunit unit step number. If it is not more than 0,
114
+ # the auto defragmentation is disabled. (Since TC 1.4.21)
115
+ #
116
+ # Some examples :
117
+ #
118
+ # t = Rufus::Tokyo::Table.new('table.tdb')
119
+ # t = Rufus::Tokyo::Table.new('table.tdb#mode=r')
120
+ # t = Rufus::Tokyo::Table.new('table.tdb', :mode => 'r')
121
+ # t = Rufus::Tokyo::Table.new('table.tdb#opts=ld#mode=r')
122
+ # t = Rufus::Tokyo::Table.new('table.tdb', :opts => 'ld', :mode => 'r')
123
+ #
124
+ def initialize (path, params={})
125
+
126
+ conf = determine_conf(path, params, :table)
127
+
128
+ @db = lib.tctdbnew
129
+
130
+ #
131
+ # tune table
132
+
133
+ libcall(:tctdbsetmutex) if conf[:mutex]
134
+
135
+ libcall(:tctdbtune, conf[:bnum], conf[:apow], conf[:fpow], conf[:opts])
136
+
137
+ # TODO : set indexes here... well, there is already #set_index
138
+ #conf[:indexes]...
139
+
140
+ libcall(:tctdbsetcache, conf[:rcnum], conf[:lcnum], conf[:ncnum])
141
+
142
+ libcall(:tctdbsetxmsiz, conf[:xmsiz])
143
+
144
+ libcall(:tctdbsetdfunit, conf[:dfunit]) \
145
+ if lib.respond_to?(:tctdbsetdfunit) # TC >= 1.4.21
146
+
147
+ #
148
+ # open table
149
+
150
+ @path = conf[:path]
151
+
152
+ libcall(:tctdbopen, @path, conf[:mode])
153
+ end
154
+
155
+ # Using the cabinet lib
156
+ #
157
+ def lib
158
+ CabinetLib
159
+ end
160
+
161
+ # Returns the path to the table.
162
+ #
163
+ def path
164
+
165
+ @path
166
+ end
167
+
168
+ # Closes the table (and frees the datastructure allocated for it),
169
+ # returns true in case of success.
170
+ #
171
+ def close
172
+ result = lib.tab_close(@db)
173
+ lib.tab_del(@db)
174
+ (result == 1)
175
+ end
176
+
177
+ # Generates a unique id (in the context of this Table instance)
178
+ #
179
+ def generate_unique_id
180
+ lib.tab_genuid(@db)
181
+ end
182
+ alias :genuid :generate_unique_id
183
+
184
+ INDEX_TYPES = {
185
+ :lexical => 0,
186
+ :decimal => 1,
187
+ :void => 9999,
188
+ :remove => 9999,
189
+ :keep => 1 << 24
190
+ }
191
+
192
+ # Sets an index on a column of the table.
193
+ #
194
+ # Types maybe be :lexical or :decimal, use :keep to "add" and
195
+ # :remove (or :void) to "remove" an index.
196
+ #
197
+ # If column_name is :pk or "", the index will be set on the primary key.
198
+ #
199
+ # Returns true in case of success.
200
+ #
201
+ def set_index (column_name, *types)
202
+
203
+ column_name = '' if column_name == :pk
204
+
205
+ i = types.inject(0) { |i, t| i = i | INDEX_TYPES[t]; i }
206
+
207
+ (lib.tab_setindex(@db, column_name, i) == 1)
208
+ end
209
+
210
+ # Inserts a record in the table db
211
+ #
212
+ # table['pk0'] = [ 'name', 'fred', 'age', '45' ]
213
+ # table['pk1'] = { 'name' => 'jeff', 'age' => '46' }
214
+ #
215
+ # Accepts both a hash or an array (expects the array to be of the
216
+ # form [ key, value, key, value, ... ] else it will raise
217
+ # an ArgumentError)
218
+ #
219
+ # Raises an error in case of failure.
220
+ #
221
+ def []= (pk, h_or_a)
222
+
223
+ m = Rufus::Tokyo::Map[h_or_a]
224
+
225
+ r = lib.tab_put(@db, pk, CabinetLib.strlen(pk), m.pointer)
226
+
227
+ m.free
228
+
229
+ (r == 1) || raise_error # raising potential error after freeing map
230
+
231
+ h_or_a
232
+ end
233
+
234
+ # Removes an entry in the table
235
+ #
236
+ # (might raise an error if the delete itself failed, but returns nil
237
+ # if there was no entry for the given key)
238
+ #
239
+ def delete (k)
240
+ v = self[k]
241
+ return nil unless v
242
+ libcall(:tab_out, k, CabinetLib.strlen(k))
243
+ v
244
+ end
245
+
246
+ # Removes all records in this table database
247
+ #
248
+ def clear
249
+ libcall(:tab_vanish)
250
+ end
251
+
252
+ # Returns an array of all the primary keys in the table
253
+ #
254
+ # With no options given, this method will return all the keys (strings)
255
+ # in a Ruby array.
256
+ #
257
+ # :prefix --> returns only the keys who match a given string prefix
258
+ #
259
+ # :limit --> returns a limited number of keys
260
+ #
261
+ # :native --> returns an instance of Rufus::Tokyo::List instead of
262
+ # a Ruby Hash, you have to call #free on that List when done with it !
263
+ # Else you're exposing yourself to a memory leak.
264
+ #
265
+ def keys (options={})
266
+
267
+ if pref = options[:prefix]
268
+
269
+ l = lib.tab_fwmkeys2(@db, pref, options[:limit] || -1)
270
+ l = Rufus::Tokyo::List.new(l)
271
+ options[:native] ? l : l.release
272
+
273
+ else
274
+
275
+ limit = options[:limit] || -1
276
+ limit = nil if limit < 1
277
+
278
+ l = options[:native] ? Rufus::Tokyo::List.new : []
279
+
280
+ lib.tab_iterinit(@db)
281
+
282
+ while (k = (lib.tab_iternext2(@db) rescue nil))
283
+ break if limit and l.size >= limit
284
+ l << k
285
+ end
286
+
287
+ l
288
+ end
289
+ end
290
+
291
+ # Deletes all the entries whose key begin with the given prefix.
292
+ #
293
+ def delete_keys_with_prefix (prefix)
294
+
295
+ # TODO : use ...searchout
296
+
297
+ ks = lib.tab_fwmkeys2(@db, prefix, -1) # -1 for no limit
298
+ #Rufus::Tokyo::List.new(ks).release.each { |k| self.delete(k) }
299
+ begin
300
+ ks = Rufus::Tokyo::List.new(ks)
301
+ ks.each { |k| self.delete(k) }
302
+ ensure
303
+ ks.free
304
+ end
305
+ end
306
+
307
+ # No 'misc' methods for the table library, so this lget is equivalent
308
+ # to calling get for each key. Hoping later versions of TC will provide
309
+ # a mget method.
310
+ #
311
+ def lget (keys)
312
+
313
+ # TODO : maybe investigate a query on the column 'primary_key' ?
314
+
315
+ keys.inject({}) { |h, k| v = self[k]; h[k] = v if v; h }
316
+ end
317
+
318
+ # Returns the number of records in this table db
319
+ #
320
+ def size
321
+
322
+ lib.tab_rnum(@db)
323
+ end
324
+
325
+ # Prepares a query instance (block is optional)
326
+ #
327
+ def prepare_query (&block)
328
+
329
+ q = TableQuery.new(self)
330
+ block.call(q) if block
331
+ q
332
+ end
333
+
334
+ # Prepares and runs a query, returns a ResultSet instance
335
+ # (takes care of freeing the query structure)
336
+ #
337
+ def do_query (&block)
338
+ q = prepare_query(&block)
339
+ rs = q.run
340
+ q.free
341
+ rs
342
+ end
343
+
344
+ # Prepares and runs a query, returns an array of hashes (all Ruby)
345
+ # (takes care of freeing the query and the result set structures)
346
+ #
347
+ def query (&block)
348
+ rs = do_query(&block)
349
+ a = rs.to_a
350
+ rs.free
351
+ a
352
+ end
353
+
354
+ # Warning : this method is low-level, you probably only need
355
+ # to use #transaction and a block.
356
+ #
357
+ # Direct call for 'transaction begin'.
358
+ #
359
+ def tranbegin
360
+ libcall(:tctdbtranbegin)
361
+ end
362
+
363
+ # Warning : this method is low-level, you probably only need
364
+ # to use #transaction and a block.
365
+ #
366
+ # Direct call for 'transaction commit'.
367
+ #
368
+ def trancommit
369
+ libcall(:tctdbtrancommit)
370
+ end
371
+
372
+ # Warning : this method is low-level, you probably only need
373
+ # to use #transaction and a block.
374
+ #
375
+ # Direct call for 'transaction abort'.
376
+ #
377
+ def tranabort
378
+ libcall(:tctdbtranabort)
379
+ end
380
+
381
+ # Returns the actual pointer to the Tokyo Cabinet table
382
+ #
383
+ def pointer
384
+ @db
385
+ end
386
+
387
+ protected
388
+
389
+ # Returns the value (as a Ruby Hash) else nil
390
+ #
391
+ # (the actual #[] method is provided by HashMethods)
392
+ #
393
+ def get (k)
394
+ m = lib.tab_get(@db, k, CabinetLib.strlen(k))
395
+ return nil if m.address == 0 # :( too bad, but it works
396
+ Map.to_h(m) # which frees the map
397
+ end
398
+
399
+ def libcall (lib_method, *args)
400
+
401
+ #(lib.send(lib_method, @db, *args) == 1) or raise_error
402
+ # stack level too deep with JRuby 1.1.6 :(
403
+
404
+ (eval(%{ lib.#{lib_method}(@db, *args) }) == 1) or raise_error
405
+ # works with JRuby 1.1.6
406
+ end
407
+
408
+ # Obviously something got wrong, let's ask the db about it and raise
409
+ # a TokyoError
410
+ #
411
+ def raise_error
412
+
413
+ err_code = lib.tab_ecode(@db)
414
+ err_msg = lib.tab_errmsg(err_code)
415
+
416
+ raise TokyoError.new("(err #{err_code}) #{err_msg}")
417
+ end
418
+ end
419
+
420
+ #
421
+ # A query on a Tokyo Cabinet table db
422
+ #
423
+ class TableQuery
424
+
425
+ include QueryConstants
426
+
427
+ # Creates a query for a given Rufus::Tokyo::Table
428
+ #
429
+ # Queries are usually created via the #query (#prepare_query #do_query)
430
+ # of the Table instance.
431
+ #
432
+ # Methods of interest here are :
433
+ #
434
+ # * #add (or #add_condition)
435
+ # * #order_by
436
+ # * #limit
437
+ #
438
+ # also
439
+ #
440
+ # * #pk_only
441
+ # * #no_pk
442
+ #
443
+ def initialize (table)
444
+ @table = table
445
+ @query = @table.lib.qry_new(@table.pointer)
446
+ @opts = {}
447
+ end
448
+
449
+ def lib
450
+ @table.lib
451
+ end
452
+
453
+ # Adds a condition
454
+ #
455
+ # table.query { |q|
456
+ # q.add 'name', :equals, 'Oppenheimer'
457
+ # q.add 'age', :numgt, 35
458
+ # }
459
+ #
460
+ # Understood 'operators' :
461
+ #
462
+ # :streq # string equality
463
+ # :eq
464
+ # :eql
465
+ # :equals
466
+ #
467
+ # :strinc # string include
468
+ # :inc # string include
469
+ # :includes # string include
470
+ #
471
+ # :strbw # string begins with
472
+ # :bw
473
+ # :starts_with
474
+ # :strew # string ends with
475
+ # :ew
476
+ # :ends_with
477
+ #
478
+ # :strand # string which include all the tokens in the given exp
479
+ # :and
480
+ #
481
+ # :stror # string which include at least one of the tokens
482
+ # :or
483
+ #
484
+ # :stroreq # string which is equal to at least one token
485
+ #
486
+ # :strorrx # string which matches the given regex
487
+ # :regex
488
+ # :matches
489
+ #
490
+ # # numbers...
491
+ #
492
+ # :numeq # equal
493
+ # :numequals
494
+ # :numgt # greater than
495
+ # :gt
496
+ # :numge # greater or equal
497
+ # :ge
498
+ # :gte
499
+ # :numlt # greater or equal
500
+ # :lt
501
+ # :numle # greater or equal
502
+ # :le
503
+ # :lte
504
+ # :numbt # a number between two tokens in the given exp
505
+ # :bt
506
+ # :between
507
+ #
508
+ # :numoreq # number which is equal to at least one token
509
+ #
510
+ def add (colname, operator, val, affirmative=true, no_index=true)
511
+
512
+ op = operator.is_a?(Fixnum) ? operator : OPERATORS[operator]
513
+ op = op | TDBQCNEGATE unless affirmative
514
+ op = op | TDBQCNOIDX if no_index
515
+ lib.qry_addcond(@query, colname, op, val)
516
+ end
517
+ alias :add_condition :add
518
+
519
+ # Sets the max number of records to return for this query.
520
+ #
521
+ # (If you're using TC >= 1.4.10 the optional 'offset' (skip) parameter
522
+ # is accepted)
523
+ #
524
+ def limit (i, offset=-1)
525
+
526
+ lib.respond_to?(:qry_setlimit) ?
527
+ lib.qry_setlimit(@query, i, offset) :
528
+ lib.qry_setmax(@query, i)
529
+ end
530
+
531
+ # Sets the sort order for the result of the query
532
+ #
533
+ # The 'direction' may be :
534
+ #
535
+ # :strasc # string ascending
536
+ # :strdesc
537
+ # :asc # string ascending
538
+ # :desc
539
+ # :numasc # number ascending
540
+ # :numdesc
541
+ #
542
+ def order_by (colname, direction=:strasc)
543
+ lib.qry_setorder(@query, colname, DIRECTIONS[direction])
544
+ end
545
+
546
+ # When set to true, only the primary keys of the matching records will
547
+ # be returned.
548
+ #
549
+ def pk_only (on=true)
550
+ @opts[:pk_only] = on
551
+ end
552
+
553
+ # When set to true, the :pk (primary key) is not inserted in the record
554
+ # (hashes) returned
555
+ #
556
+ def no_pk (on=true)
557
+ @opts[:no_pk] = on
558
+ end
559
+
560
+ # Runs this query (returns a TableResultSet instance)
561
+ #
562
+ def run
563
+
564
+ @last_resultset =
565
+ TableResultSet.new(@table, lib.qry_search(@query), @opts)
566
+ end
567
+
568
+ # Gets the count of records returned by this query.
569
+ #
570
+ # Note : the 'real' impl is only available since TokyoCabinet 1.4.12.
571
+ #
572
+ def count
573
+
574
+ if lib.respond_to?(:qry_count)
575
+ lib.qry_count(@query)
576
+ else
577
+ @last_resultset ? @last_resultset.size : 0
578
+ end
579
+ end
580
+
581
+ # Frees this data structure
582
+ #
583
+ def free
584
+ lib.qry_del(@query)
585
+ @query = nil
586
+ end
587
+
588
+ alias :close :free
589
+ alias :destroy :free
590
+ end
591
+
592
+ #
593
+ # The thing queries return
594
+ #
595
+ class TableResultSet
596
+ include Enumerable
597
+
598
+ def initialize (table, list_pointer, query_opts)
599
+ @table = table
600
+ @list = list_pointer
601
+ @opts = query_opts
602
+ end
603
+
604
+ # Returns the count of element in this result set
605
+ #
606
+ def size
607
+ CabinetLib.tclistnum(@list)
608
+ end
609
+
610
+ alias :length :size
611
+
612
+ # The classical each
613
+ #
614
+ def each
615
+ (0..size-1).each do |i|
616
+ pk = CabinetLib.tclistval2(@list, i)
617
+ if @opts[:pk_only]
618
+ yield(pk)
619
+ else
620
+ val = @table[pk]
621
+ val[:pk] = pk unless @opts[:no_pk]
622
+ yield(val)
623
+ end
624
+ end
625
+ end
626
+
627
+ # Returns an array of hashes
628
+ #
629
+ def to_a
630
+ collect { |m| m }
631
+ end
632
+
633
+ # Frees this query (the underlying Tokyo Cabinet list structure)
634
+ #
635
+ def free
636
+ CabinetLib.tclistdel(@list)
637
+ @list = nil
638
+ end
639
+
640
+ alias :close :free
641
+ alias :destroy :free
642
+ end
643
+ end
644
+