rufus-tokyo 0.1.2 → 0.1.3

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
+