rufus-tokyo 0.1.7 → 0.1.8

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,134 @@
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 'tokyotyrant'
32
+
33
+ require 'rufus/edo/error'
34
+ require 'rufus/edo/tabcore'
35
+ require 'rufus/tokyo/stats'
36
+
37
+
38
+ module Rufus::Edo
39
+
40
+ #
41
+ # A Tokyo Cabinet table, but remote...
42
+ #
43
+ # require 'rufus/edo/ntyrant'
44
+ # t = Rufus::Edo::NetTyrant.new('127.0.0.1', 44001)
45
+ # t['toto'] = { 'name' => 'toto the first', 'age' => '34' }
46
+ # t['toto']
47
+ # # => { 'name' => 'toto the first', 'age' => '34' }
48
+ #
49
+ # NOTE : The advantage of this class is that it leverages the TokyoTyrant.rb
50
+ # provided by Hirabayashi-san. It's pure Ruby, it's slow but works everywhere
51
+ # without the need for Tokyo Cabinet and Tyrant C libraries.
52
+ #
53
+ class NetTyrantTable
54
+
55
+ include Rufus::Edo::TableCore
56
+ include Rufus::Tokyo::TyrantStats
57
+
58
+ attr_reader :host, :port
59
+
60
+ #
61
+ # Connects to the Tyrant table listening at the given host and port.
62
+ #
63
+ # You start such a Tyrant with :
64
+ #
65
+ # ttserver -port 44502 data.tct
66
+ #
67
+ # and then :
68
+ #
69
+ # require 'rufus/edo/ntyrant'
70
+ # t = Rufus::Edo::NetTyrantTable.new('127.0.0.1', 44502)
71
+ # t['client0'] = { 'name' => 'Heike no Kyomori', 'country' => 'jp' }
72
+ # t.close
73
+ #
74
+ #
75
+ # You can start a Tokyo Tyrant and make it listen to a unix socket (not TCP)
76
+ # with :
77
+ #
78
+ # ttserver -host /tmp/table_socket -port 0 data.tct
79
+ #
80
+ # then :
81
+ #
82
+ # require 'rufus/edo/ntyrant'
83
+ # t = Rufus::Edo::NetTyrantTable.new('/tmp/table_socket')
84
+ # t['client0'] = { 'name' => 'Theodore Roosevelt', 'country' => 'usa' }
85
+ # t.close
86
+ #
87
+ def initialize (host, port=0)
88
+
89
+ @host = host
90
+ @port = port
91
+
92
+ @db = TokyoTyrant::RDBTBL.new
93
+ @db.open(host, port) || raise_error
94
+
95
+ if self.stat['type'] != 'table'
96
+
97
+ @db.close
98
+
99
+ raise ArgumentError.new(
100
+ "tyrant at #{host}:#{port} is not a table, " +
101
+ "use Rufus::Edo::NetTyrant instead to access it.")
102
+ end
103
+ end
104
+
105
+ def transaction #:nodoc#
106
+ raise NoMethodError.new("NetTyrant : transactions not supported")
107
+ end
108
+ def abort #:nodoc#
109
+ raise NoMethodError.new("NetTyrant : transactions not supported")
110
+ end
111
+ def tranbegin #:nodoc#
112
+ raise NoMethodError.new("NetTyrant : transactions not supported")
113
+ end
114
+ def trancommit #:nodoc#
115
+ raise NoMethodError.new("NetTyrant : transactions not supported")
116
+ end
117
+ def tranabort #:nodoc#
118
+ raise NoMethodError.new("NetTyrant : transactions not supported")
119
+ end
120
+
121
+ protected
122
+
123
+ def table_query_class #:nodoc#
124
+
125
+ TokyoTyrant::RDBQRY
126
+ end
127
+
128
+ def do_stat #:nodoc#
129
+
130
+ @db.stat
131
+ end
132
+ end
133
+ end
134
+
@@ -0,0 +1,506 @@
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/query'
32
+ require 'rufus/tokyo/transactions'
33
+
34
+
35
+ module Rufus::Edo
36
+
37
+ module TableCore
38
+
39
+ include Rufus::Tokyo::HashMethods
40
+ include Rufus::Tokyo::Transactions
41
+
42
+ #
43
+ # Closes the table (and frees the datastructure allocated for it),
44
+ # raises an exception in case of failure.
45
+ #
46
+ def close
47
+ @db.close || raise_error
48
+ end
49
+
50
+ #
51
+ # Generates a unique id (in the context of this Table instance)
52
+ #
53
+ def generate_unique_id
54
+ @db.genuid
55
+ end
56
+ alias :genuid :generate_unique_id
57
+
58
+ INDEX_TYPES = {
59
+ :lexical => 0,
60
+ :decimal => 1,
61
+ :void => 9999,
62
+ :remove => 9999,
63
+ :keep => 1 << 24
64
+ }
65
+
66
+ #
67
+ # Sets an index on a column of the table.
68
+ #
69
+ # Types maybe be :lexical or :decimal, use :keep to "add" and
70
+ # :remove (or :void) to "remove" an index.
71
+ #
72
+ # If column_name is :pk or "", the index will be set on the primary key.
73
+ #
74
+ # Raises an exception in case of failure.
75
+ #
76
+ def set_index (column_name, *types)
77
+
78
+ column_name = '' if column_name == :pk
79
+
80
+ i = types.inject(0) { |i, t| i = i | INDEX_TYPES[t]; i }
81
+
82
+ @db.setindex(column_name, i) || raise_error
83
+ end
84
+
85
+ #
86
+ # Inserts a record in the table db
87
+ #
88
+ # table['pk0'] = [ 'name', 'fred', 'age', '45' ]
89
+ # table['pk1'] = { 'name' => 'jeff', 'age' => '46' }
90
+ #
91
+ # Accepts both a hash or an array (expects the array to be of the
92
+ # form [ key, value, key, value, ... ] else it will raise
93
+ # an ArgumentError)
94
+ #
95
+ # Raises an error in case of failure.
96
+ #
97
+ def []= (pk, h_or_a)
98
+
99
+ m = h_or_a.is_a?(Hash) ? h_or_a : Hash[*h_or_a]
100
+
101
+ verify_value(m)
102
+
103
+ @db.put(pk, m) || raise_error
104
+ end
105
+
106
+ #
107
+ # Removes an entry in the table
108
+ #
109
+ # (might raise an error if the delete itself failed, but returns nil
110
+ # if there was no entry for the given key)
111
+ #
112
+ # Raises an error if something went wrong
113
+ #
114
+ def delete (k)
115
+
116
+ # have to work around... :(
117
+
118
+ val = @db[k]
119
+ return nil unless val
120
+
121
+ @db.out(k) || raise_error
122
+ val
123
+ end
124
+
125
+ #
126
+ # Removes all records in this table database
127
+ #
128
+ # Raises an error if something went wrong
129
+ #
130
+ def clear
131
+
132
+ @db.vanish || raise_error
133
+ end
134
+
135
+ #
136
+ # Returns an array of all the primary keys in the table
137
+ #
138
+ # With no options given, this method will return all the keys (strings)
139
+ # in a Ruby array.
140
+ #
141
+ # :prefix --> returns only the keys who match a given string prefix
142
+ #
143
+ # :limit --> returns a limited number of keys
144
+ #
145
+ def keys (options={})
146
+
147
+ if pref = options[:prefix]
148
+
149
+ @db.fwmkeys(pref, options[:limit] || -1)
150
+
151
+ else
152
+
153
+ limit = options[:limit] || -1
154
+ limit = nil if limit < 1
155
+
156
+ @db.iterinit
157
+
158
+ l = []
159
+
160
+ while (k = @db.iternext)
161
+ break if limit and l.size >= limit
162
+ l << k
163
+ end
164
+
165
+ l
166
+ end
167
+ end
168
+
169
+ #
170
+ # Deletes all the entries whose key begin with the given prefix.
171
+ #
172
+ def delete_keys_with_prefix (prefix)
173
+
174
+ ks = @db.fwmkeys(prefix, -1) # -1 for no limit
175
+ ks.each { |k| self.delete(k) }
176
+ end
177
+
178
+ #
179
+ # Returns the number of records in this table db
180
+ #
181
+ def size
182
+
183
+ @db.rnum
184
+ end
185
+
186
+ #
187
+ # Prepares a query instance (block is optional)
188
+ #
189
+ def prepare_query (&block)
190
+ q = TableQuery.new(table_query_class, self)
191
+ block.call(q) if block
192
+ q
193
+ end
194
+
195
+ #
196
+ # Prepares and runs a query, returns an array of hashes (all Ruby)
197
+ # (takes care of freeing the query and the result set structures)
198
+ #
199
+ def query (&block)
200
+
201
+ prepare_query(&block).run
202
+ end
203
+
204
+ #
205
+ # Warning : this method is low-level, you probably only need
206
+ # to use #transaction and a block.
207
+ #
208
+ # Direct call for 'transaction begin'.
209
+ #
210
+ def tranbegin
211
+
212
+ @db.tranbegin || raise_error
213
+ end
214
+
215
+ #
216
+ # Warning : this method is low-level, you probably only need
217
+ # to use #transaction and a block.
218
+ #
219
+ # Direct call for 'transaction commit'.
220
+ #
221
+ def trancommit
222
+
223
+ @db.trancommit || raise_error
224
+ end
225
+
226
+ #
227
+ # Warning : this method is low-level, you probably only need
228
+ # to use #transaction and a block.
229
+ #
230
+ # Direct call for 'transaction abort'.
231
+ #
232
+ def tranabort
233
+
234
+ @db.tranabort || raise_error
235
+ end
236
+
237
+ #
238
+ # Returns the underlying 'native' Ruby object (of the class devised by
239
+ # Hirabayashi-san)
240
+ #
241
+ def original
242
+
243
+ @db
244
+ end
245
+
246
+ protected
247
+
248
+ #
249
+ # Returns the value (as a Ruby Hash) else nil
250
+ #
251
+ # (the actual #[] method is provided by HashMethods)
252
+ #
253
+ def get (k)
254
+
255
+ @db.get(k)
256
+ end
257
+
258
+ #
259
+ # Obviously something went wrong, let's ask the db about it and raise
260
+ # an EdoError
261
+ #
262
+ def raise_error
263
+
264
+ err_code = @db.ecode
265
+ err_msg = @db.errmsg(err_code)
266
+
267
+ raise EdoError.new("(err #{err_code}) #{err_msg}")
268
+ end
269
+
270
+ def verify_value (h)
271
+
272
+ h.each { |k, v|
273
+
274
+ next if k.is_a?(String) and v.is_a?(String)
275
+
276
+ raise ArgumentError.new(
277
+ "only String keys and values are accepted " +
278
+ "( #{k.inspect} => #{v.inspect} )")
279
+ }
280
+ end
281
+ end
282
+
283
+ #
284
+ # A query on a Tokyo Cabinet table db
285
+ #
286
+ class TableQuery
287
+
288
+ include Rufus::Tokyo::QueryConstants
289
+
290
+ #
291
+ # Creates a query for a given Rufus::Tokyo::Table
292
+ #
293
+ # Queries are usually created via the #query (#prepare_query #do_query)
294
+ # of the Table instance.
295
+ #
296
+ # Methods of interest here are :
297
+ #
298
+ # * #add (or #add_condition)
299
+ # * #order_by
300
+ # * #limit
301
+ #
302
+ # also
303
+ #
304
+ # * #pk_only
305
+ # * #no_pk
306
+ #
307
+ def initialize (query_class, table)
308
+
309
+ @table = table
310
+ @query = query_class.new(table.original)
311
+
312
+ @opts = {}
313
+ end
314
+
315
+ #
316
+ # Adds a condition
317
+ #
318
+ # table.query { |q|
319
+ # q.add 'name', :equals, 'Oppenheimer'
320
+ # q.add 'age', :numgt, 35
321
+ # }
322
+ #
323
+ # Understood 'operators' :
324
+ #
325
+ # :streq # string equality
326
+ # :eq
327
+ # :eql
328
+ # :equals
329
+ #
330
+ # :strinc # string include
331
+ # :inc # string include
332
+ # :includes # string include
333
+ #
334
+ # :strbw # string begins with
335
+ # :bw
336
+ # :starts_with
337
+ # :strew # string ends with
338
+ # :ew
339
+ # :ends_with
340
+ #
341
+ # :strand # string which include all the tokens in the given exp
342
+ # :and
343
+ #
344
+ # :stror # string which include at least one of the tokens
345
+ # :or
346
+ #
347
+ # :stroreq # string which is equal to at least one token
348
+ #
349
+ # :strorrx # string which matches the given regex
350
+ # :regex
351
+ # :matches
352
+ #
353
+ # # numbers...
354
+ #
355
+ # :numeq # equal
356
+ # :numequals
357
+ # :numgt # greater than
358
+ # :gt
359
+ # :numge # greater or equal
360
+ # :ge
361
+ # :gte
362
+ # :numlt # greater or equal
363
+ # :lt
364
+ # :numle # greater or equal
365
+ # :le
366
+ # :lte
367
+ # :numbt # a number between two tokens in the given exp
368
+ # :bt
369
+ # :between
370
+ #
371
+ # :numoreq # number which is equal to at least one token
372
+ #
373
+ def add (colname, operator, val, affirmative=true, no_index=true)
374
+
375
+ op = operator.is_a?(Fixnum) ? operator : OPERATORS[operator]
376
+ op = op | TDBQCNEGATE unless affirmative
377
+ op = op | TDBQCNOIDX if no_index
378
+
379
+ @query.addcond(colname, op, val)
380
+ end
381
+ alias :add_condition :add
382
+
383
+ #
384
+ # Sets the max number of records to return for this query.
385
+ #
386
+ # (sorry no 'offset' as of now)
387
+ #
388
+ def limit (i)
389
+
390
+ @query.setmax(i)
391
+ end
392
+
393
+ #
394
+ # Sets the sort order for the result of the query
395
+ #
396
+ # The 'direction' may be :
397
+ #
398
+ # :strasc # string ascending
399
+ # :strdesc
400
+ # :asc # string ascending
401
+ # :desc
402
+ # :numasc # number ascending
403
+ # :numdesc
404
+ #
405
+ def order_by (colname, direction=:strasc)
406
+
407
+ @query.setorder(colname, DIRECTIONS[direction])
408
+ end
409
+
410
+ #
411
+ # When set to true, only the primary keys of the matching records will
412
+ # be returned.
413
+ #
414
+ def pk_only (on=true)
415
+
416
+ @opts[:pk_only] = on
417
+ end
418
+
419
+ #
420
+ # When set to true, the :pk (primary key) is not inserted in the record
421
+ # (hashes) returned
422
+ #
423
+ def no_pk (on=true)
424
+
425
+ @opts[:no_pk] = on
426
+ end
427
+
428
+ #
429
+ # Runs this query (returns a TableResultSet instance)
430
+ #
431
+ def run
432
+ TableResultSet.new(@table, @query.search, @opts)
433
+ end
434
+
435
+ #
436
+ # Frees this data structure
437
+ #
438
+ def free
439
+
440
+ # nothing ... :( I hope there's no memory leak
441
+ end
442
+
443
+ alias :close :free
444
+ alias :destroy :free
445
+ end
446
+
447
+ #
448
+ # The thing queries return
449
+ #
450
+ class TableResultSet
451
+ include Enumerable
452
+
453
+ def initialize (table, primary_keys, query_opts)
454
+
455
+ @table = table
456
+ @keys = primary_keys
457
+ @opts = query_opts
458
+ end
459
+
460
+ #
461
+ # Returns the count of element in this result set
462
+ #
463
+ def size
464
+
465
+ @keys.size
466
+ end
467
+
468
+ alias :length :size
469
+
470
+ #
471
+ # The classical each
472
+ #
473
+ def each
474
+
475
+ @keys.each do |pk|
476
+ if @opts[:pk_only]
477
+ yield(pk)
478
+ else
479
+ val = @table[pk]
480
+ val[:pk] = pk unless @opts[:no_pk]
481
+ yield(val)
482
+ end
483
+ end
484
+ end
485
+
486
+ #
487
+ # Returns an array of hashes
488
+ #
489
+ def to_a
490
+
491
+ self.collect { |m| m }
492
+ end
493
+
494
+ #
495
+ # Frees this query (the underlying Tokyo Cabinet list structure)
496
+ #
497
+ def free
498
+
499
+ # nothing to do, kept for similarity with Rufus::Tokyo
500
+ end
501
+
502
+ alias :close :free
503
+ alias :destroy :free
504
+ end
505
+ end
506
+