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