dbmlite3 1.0.a1

Sign up to get free protection for your applications and to get access to all the features.
data/lib/dbmlite3.rb ADDED
@@ -0,0 +1,909 @@
1
+
2
+ require 'sqlite3'
3
+ require 'yaml'
4
+ require 'set'
5
+
6
+
7
+ module Lite3
8
+
9
+ # Exception class for errors specific `Lite3::DBM`. Note that
10
+ # `Lite3::*` methods may also throw `SQLite3` exceptions.
11
+ class Error < StandardError; end
12
+
13
+ #
14
+ # Private classes
15
+ #
16
+
17
+
18
+ # Wrapper around a SQLite3::Database object.
19
+ #
20
+ # We do this instead of using them directly because transactions
21
+ # happen at the handle level rather than the file level and this
22
+ # lets us share the transaction across multiple tables in the same
23
+ # file.
24
+ #
25
+ # In addition, we can use this to cache prepared SQL statements and
26
+ # to transparently close and reopen the underlying database file
27
+ # when (e.g.) forking the process.
28
+ #
29
+ # Instances contain references to DBM objects using them. When the
30
+ # set becomes empty, the handle is closed; adding a reference will
31
+ # ensure the handle is open.
32
+ class Handle
33
+ attr_reader :path
34
+ def initialize(path)
35
+ @path = path
36
+ @db = nil
37
+ @refs = Set.new
38
+ @statement_cache = {}
39
+
40
+ open!
41
+ end
42
+
43
+ def to_s
44
+ "<#{self.class}:0x#{object_id.to_s(16)} path=#{@path} open=#{!!@db}>"
45
+ end
46
+ alias inspect to_s
47
+
48
+ #
49
+ # Back-references to the DBM object(s) using this handle.
50
+ #
51
+ # `delref` will close the handle if there are no more references.
52
+ #
53
+
54
+ def addref(parent)
55
+ @refs.add parent
56
+ open!
57
+ end
58
+
59
+ def delref(parent)
60
+ @refs.delete parent
61
+ close! if @refs.empty?
62
+ end
63
+
64
+ # Return the list of active parents;
65
+ def refs
66
+ return @refs.to_a
67
+ end
68
+
69
+
70
+ #
71
+ # Opening and closing
72
+ #
73
+
74
+ def closed?
75
+ return @db == nil
76
+ end
77
+
78
+ # Close the underlying SQLite3::Database handle. Does *not*
79
+ # render `self` unusable; it will recreate the handle the next
80
+ # time it's needed.
81
+ def close!
82
+ return unless @db
83
+
84
+ @statement_cache.values.each{|ps| ps.close }
85
+ @statement_cache = {}
86
+
87
+ @db.close
88
+ @db = nil
89
+ end
90
+
91
+ private
92
+
93
+ # Open
94
+ def open!
95
+ return if @db # already open
96
+ @db = SQLite3::Database.new @path
97
+ end
98
+
99
+ public
100
+
101
+ # Perform &block in a transaction. See DBM.transaction.
102
+ #
103
+ # Always evalautes the block. If no transaction is in progress,
104
+ # starts one first and ends it afterward. In other words: always
105
+ # finishes what it starts and doesn't mess with things already in
106
+ # progress.
107
+ def transaction(&block)
108
+ open!
109
+
110
+ return block.call if @db.transaction_active?
111
+
112
+ begin
113
+ @db.transaction
114
+ result = block.call
115
+ return result
116
+
117
+ rescue => e
118
+ @db.rollback
119
+ raise e
120
+
121
+ ensure
122
+ @db.commit if @db.transaction_active?
123
+ end
124
+ end
125
+
126
+ # Test if there is currently a transaction in progress
127
+ def transaction_active?
128
+ return !closed? && @db.transaction_active?
129
+ end
130
+
131
+ # Execute query with bindvars. The corresponding Statement is cached
132
+ # and reused if present.
133
+ def sql(query, bindvars = [], &block)
134
+ open!
135
+ unless @statement_cache.has_key? query
136
+ stmt = @db.prepare(query)
137
+ @statement_cache[query] = stmt
138
+ end
139
+
140
+ return @statement_cache[query].execute!(bindvars, &block)
141
+ end
142
+ end
143
+
144
+
145
+ # Dummy `Handle` that throws an `Error` exception whenever something
146
+ # tries to treat it as an open handle. This replaces a `DBM`'s
147
+ # `Handle` object when `DBM.close` is called so that the error
148
+ # message will be useful if something tries to access a closed
149
+ # handle.
150
+ class ClosedHandle
151
+ def initialize(filename, table)
152
+ @filename, @table = [filename, table]
153
+ end
154
+
155
+ def closed?() return true; end
156
+
157
+ # We clone the rest of Handle's interface with methods that throw
158
+ # an Error.
159
+ Handle.instance_methods(false).each { |name|
160
+ next if method_defined? name
161
+ define_method(name) { |*args|
162
+ raise Error.new("Use of closed database at #{@filename}/#{@table}")
163
+ }
164
+ }
165
+ end
166
+
167
+
168
+ # Module to manage the collection of active Handle objects. See the
169
+ # docs for `Lite3::SQL` for an overview; this module hold the actual
170
+ # code and data.
171
+ module HandlePool
172
+ @@handles = {} # The hash of `Handle` objects keyed by filename
173
+
174
+ # Retrieve the `Handle` associated with `filename`, creating it
175
+ # first if necessary. `filename` is normalized with
176
+ # `File.realpath` before using as a key and so is as good or bad
177
+ # as that for detecting an existing file.
178
+ def self.get(filename)
179
+
180
+ # Scrub @@handles of all inactive Handles
181
+ self.gc
182
+
183
+ # We need to convert the filename to a canonical
184
+ # form. `File.realpath` does this for us but only if the file
185
+ # exists. If not, we use it on the parent directory instead and
186
+ # use `File.join` to create the full path.
187
+ if File.exist?(filename)
188
+ File.file?(filename) or
189
+ raise Error.new("Filename '#{filename}' exists but is not a file.")
190
+
191
+ filename = File.realpath(filename)
192
+ else
193
+ dn = File.dirname(filename)
194
+ File.directory?(dn) or
195
+ raise Error.new("Parent directory '#{dn}' nonexistant or " +
196
+ "not a directory.")
197
+
198
+ filename = File.join(File.realpath(dn), File.basename(filename))
199
+ end
200
+
201
+ @@handles[filename] = Handle.new(filename) unless
202
+ @@handles.has_key?(filename)
203
+
204
+ return @@handles[filename]
205
+ end
206
+
207
+ # Close all underlying SQLite3::Database handles.
208
+ def self.close_all
209
+ @@handles.values.each{|h| h.close!}
210
+ end
211
+
212
+ # Close and remove all Handle objects with no refs and return a
213
+ # hash mapping the filename for each live Handle to the DBM
214
+ # objects that currently reference it. Does **NOT** perform a
215
+ # Ruby GC.
216
+ def self.gc
217
+ results = {}
218
+ @@handles.select!{|path, handle|
219
+ refs = handle.refs
220
+ if refs.empty?
221
+ handle.close!
222
+ next false
223
+ end
224
+
225
+ results[path] = refs
226
+ true
227
+ }
228
+
229
+ return results
230
+ end
231
+ end
232
+
233
+
234
+ # This module provides some basic access to the underlying
235
+ # `SQLite3::Database` objects used by `Lite3::DBM` to actually store
236
+ # and retrieve data.
237
+ #
238
+ # Things you need to care about are:
239
+ #
240
+ # 1. Use `threadsafe?` to see if the underlying `SQLite3` lib was
241
+ # compiled to be threadsafe.
242
+ #
243
+ # 2. Invoke `Lite3::SQL.close_all` before forking the process if you
244
+ # have ever opened a `Lite3::DBM` object and intend on using
245
+ # `Lite3` in both the parent and the child process.
246
+ #
247
+ # More details:
248
+ #
249
+ # `Lite3` maintains a pool of private handle objects (private class
250
+ # `Lite3::Handle`) which in turn manage the `SQLite3::Database`
251
+ # objects that actually do the work. There is one handle per
252
+ # SQLite3 database file; since each `DBM` represents one table in a
253
+ # SQLite3 file, multiple `DBM` objects will use the same handle.
254
+ #
255
+ # Handle objects can themselves close and replace their
256
+ # `SQLite3::Database` objects transparently.
257
+ #
258
+ # The underlying system keeps track of which `DBM` objects reference
259
+ # which files and will close a file's `SQLite3::Database` when all
260
+ # of the `DBM`s using it have been closed. (It does **not** handle
261
+ # the case where a `DBM` object remains open and goes out of scope;
262
+ # that object will be kept around for the life of the process.)
263
+ #
264
+ # Mostly, you don't need to care about this. However, it affects
265
+ # you in two ways:
266
+ #
267
+ # 1. Transactions are done at the file level and not the table level.
268
+ # This means that you can access separate tables in the same
269
+ # transaction, which is a Very Good Thing.
270
+ #
271
+ # 2. You can safely fork the current process and keep using existing
272
+ # `DBM` objects in both processes, provided you've invoked
273
+ # `close_all` before the fork. This will have closed the actual
274
+ # database handles (which can't tolerate being carried across a
275
+ # fork) and opens new ones the next time they're needed.
276
+ #
277
+ # If you find yourself needing to be sure that you don't have any
278
+ # unexpected open file handles (e.g. before forking or if you need
279
+ # Windows to unlock it), you should call `close_all`.
280
+ #
281
+ # Otherwise, it's safe to ignore this stuff.
282
+ module SQL
283
+ # Test if the SQLite3 lib we are using has been compiled to be
284
+ # thread safe. Just a wrapper around `SQLite3.threadsafe?`
285
+ def self.threadsafe?
286
+ return SQLite3.threadsafe?
287
+ end
288
+
289
+ # Clean up lingering unused database handle metadata.
290
+ #
291
+ # This is usually not a significant amount of space unless you
292
+ # have opened and closed a lot of different database files, but
293
+ # it's here if you need it.
294
+ #
295
+ # Returns a hash mapping each remaining handle's canonical file
296
+ # path to a list of `DBM` objects that reference it. This is
297
+ # probably not useful to you; it's there for the unit tests.
298
+ def self.gc() return HandlePool.gc; end
299
+
300
+ # Close and remove the underlying `SQLite3::Database` associated
301
+ # with each `DBM`. A new `SQLite3::Database` will be created on
302
+ # the file the first time the `DBM` is used.
303
+ #
304
+ # This **should not** be called while a database operation is in
305
+ # progress. (E.g. do **not** call this from the block of
306
+ # `DBM.each`.)
307
+ def self.close_all() return HandlePool.close_all end
308
+ end
309
+
310
+
311
+ # Lite3::DBM encapsulates a single table in a single SQLite3
312
+ # database file and lets you access it as easily as a Hash:
313
+ #
314
+ # require 'dbmlite3'
315
+ #
316
+ # db = Lite3::DBM.new("database.sqlite3", "table")
317
+ # db["speed"] = 88
318
+ # db["date"] = Date.new(1955, 11, 5)
319
+ # db["power_threshold"] = 2.2
320
+ #
321
+ # db.each{|k, v| puts "#{k} -> #{v}"}
322
+ #
323
+ # double_speed = db["speed"] * 2
324
+ #
325
+ # db.close
326
+ #
327
+ # Note that keys must be Strings. As a special exception, Symbols
328
+ # are also allowed but are transparently converted to Strings
329
+ # first. This means that while something like this will work:
330
+ #
331
+ # db[:foo] = 42
332
+ #
333
+ # a subseqent
334
+ #
335
+ # db.keys.include?(:foo) or raise AbjectFailure.new
336
+ #
337
+ # will raise an exception because the key `:foo` was turned into a
338
+ # string. you will need to do this instead:
339
+ #
340
+ # db.keys.include?('foo') or raise AbjectFailure.new
341
+ #
342
+ # Unlike DBM, values may (optionally) be any serializable Ruby type.
343
+ #
344
+ # You can select the serialization method with an optional third
345
+ # constructor argument. Options are `YAML` (the default), `Marshal`
346
+ # or simple string conversion with `to_s`. Each of these methods will
347
+ # have their own pros and cons.
348
+ #
349
+ # The table name must be a valid name identifier (i.e. matches
350
+ # /^[a-zA-Z_]\w*$/).
351
+ #
352
+ # Lite3::DBM also provides access to SQLite3 transactions with the
353
+ # `transaction` method:
354
+ #
355
+ # db.transaction {
356
+ # (1..1000).each{ |n| db["key_#{n}"] = [n, n*2, n-1] }
357
+ # }
358
+ #
359
+ # Outside of a transaction statement, each Lite3::DBM method that accesses
360
+ # the database does so in a single transaction.
361
+ #
362
+ # Note that if you need to do a large number of database operations,
363
+ # there is often a significant performance advantage to grouping them
364
+ # together in a transaction.
365
+ #
366
+ # The underlying table will have the given name prefixed with the
367
+ # string `dbmlite3_`. In addition, the table `meta_dbmlite3` holds
368
+ # some related metadata. If your program also uses the SQLite3 gem to
369
+ # access the database directly, you should not touch these tables or
370
+ # expect their format to remain consistant.
371
+ class Lite3::DBM
372
+ include Enumerable
373
+
374
+ PREFIX = "dbmlite3_"
375
+ META = "meta_dbmlite3"
376
+ private_constant(:PREFIX, :META)
377
+
378
+ #
379
+ # Construction and setup
380
+ #
381
+
382
+
383
+ # Create a new `Lite3::DBM` object that opens database file
384
+ # `filename` and performs subsequent operations on `table`. Both
385
+ # the database file and the table will be created if they do not
386
+ # yet exist.
387
+ #
388
+ # The optional third argument `serializer` is used to choose the
389
+ # serialization method for converting Ruby values into storable
390
+ # strings. There are three options:
391
+ #
392
+ # * `:yaml` uses the `YAML` module.
393
+ # * `:marshal` uses the `Marshal` module.
394
+ # * `:string` simply uses the default `to_s` method, just like the
395
+ # stock `DBM`.
396
+ #
397
+ # Each of these will have their pros and cons. The default is
398
+ # `:yaml` because that is the most portable across Ruby versions.
399
+ # `:marshal` tends to be faster but is not stable across Ruby
400
+ # versions. Note that `DBM` does not check your Marshal version.
401
+ #
402
+ # Your serializer choice is registered in a metadata table when
403
+ # `tablename` is created in the SQLite3 file. Afterward, it is an
404
+ # error to attempt to open the table with a different serializer
405
+ # and will result in a Lite3::Error exception.
406
+ #
407
+ def initialize(filename, tablename, serializer = :yaml)
408
+ @filename = filename
409
+ @tablename = tablename
410
+ @valenc,
411
+ @valdec = value_encoders(serializer)
412
+ @handle = HandlePool.get(filename)
413
+
414
+ @handle.addref(self)
415
+
416
+ check("Malformed table name '#{tablename}'; must be a valid identifer") {
417
+ tablename =~ /^[a-zA-Z_]\w*$/
418
+ }
419
+
420
+ transaction {
421
+ register_serialization_scheme(serializer)
422
+ create_table_if_not_present()
423
+ }
424
+ rescue Error => e
425
+ self.close if @handle
426
+ raise e
427
+ end
428
+
429
+
430
+ # Identical to `initialize` except that if a block is provided, it
431
+ # is evaluated with a new Lite3::DBM which is then closed afterward.
432
+ # This is analagous to `File.open`.
433
+ def self.open(filename, tablename, serializer = :yaml, &block)
434
+ instance = self.new(filename, tablename, serializer)
435
+ return instance unless block
436
+
437
+ begin
438
+ return block.call(instance)
439
+ ensure
440
+ instance.close
441
+ end
442
+ end
443
+
444
+ private
445
+
446
+ # Return encode and decode procs for the requested serialization
447
+ # scheme.
448
+ def value_encoders(serializer)
449
+ case serializer
450
+ when :yaml
451
+ enc = proc{ |val| YAML.dump(val) }
452
+ dec = proc{ |val| YAML.load(val) }
453
+
454
+ when :marshal
455
+ enc = proc { |val| Marshal.dump(val) }
456
+ dec = proc { |val| Marshal.load(val) }
457
+
458
+ when :string
459
+ enc = proc { |val| val.to_s }
460
+ dec = proc { |val| val.to_s } # sqlite preserves some types
461
+
462
+ else
463
+ raise Error.new("Invalid serializer selected: '#{serializer}'")
464
+ end
465
+
466
+ return enc, dec
467
+ end
468
+
469
+ # Create @table if it does not exist yet.
470
+ def create_table_if_not_present
471
+ @handle.sql <<-SQL
472
+ create table if not exists #{actual_tbl} (
473
+ key string primary key,
474
+ value string
475
+ );
476
+ SQL
477
+ end
478
+
479
+ # Add the serialization scheme for this table to META
480
+ def register_serialization_scheme(req_ser)
481
+ @handle.sql <<-SQL
482
+ create table if not exists #{META} (
483
+ tbl string primary key,
484
+ serializer string
485
+ );
486
+ SQL
487
+
488
+ matched = false
489
+ @handle.sql("select * from #{META} where tbl = ?", [@tablename]) { |row|
490
+ tbl, ser = row
491
+
492
+ msg = "Table '#{@tablename}' uses serializer '#{ser}'; " +
493
+ "expecting '#{req_ser}'."
494
+ raise Error.new(msg) unless req_ser.to_s == ser
495
+
496
+ matched = true
497
+ }
498
+
499
+ matched or
500
+ @handle.sql("insert into #{META} (tbl, serializer) values (?,?)",
501
+ [@tablename,req_ser.to_s]);
502
+ end
503
+
504
+
505
+
506
+ #
507
+ # Helpers
508
+ #
509
+
510
+
511
+ # Return the actual table name we are using.
512
+ def actual_tbl() return "#{PREFIX}#{@tablename}"; end
513
+
514
+
515
+ public
516
+
517
+ def to_s
518
+ openstr = closed? ? 'CLOSED' : 'OPEN'
519
+ return "<#{self.class}:0x#{object_id.to_s(16)} file='#{@filename}'" +
520
+ " tablename='#{@tablename}' #{openstr}>"
521
+ end
522
+ alias inspect to_s
523
+
524
+ # Close `self`. Subsequent attempts at database operations will
525
+ # fail with an exception but `closed?` will still work.
526
+ #
527
+ # Note that the underlying file handle (via the
528
+ # `SQLite3::Database` object) will **only** be closed if there are
529
+ # no other `DBM` objects using that file.
530
+ def close
531
+ @handle.delref(self)
532
+ @handle = ClosedHandle.new(@filename, @tablename)
533
+ end
534
+
535
+ # Test if this object has been closed. This is safe to call on a
536
+ # closed `DBM`.
537
+ def closed?()
538
+ return @handle.is_a? ClosedHandle
539
+ end
540
+
541
+ # Test if the underlying `SQLite3::Database` is closed. You
542
+ # probably don't need to care about this method; it's mostly here
543
+ # to help with unit tests.
544
+ #
545
+ # This will **not** work if `self` has been closed.
546
+ def handle_closed?
547
+ return @handle.closed?
548
+ end
549
+
550
+
551
+ #
552
+ # Transactions
553
+ #
554
+
555
+
556
+ # Begins a transaction, evaluates the given block and then ends the
557
+ # transaction. If no error occurred, the transaction is committed;
558
+ # otherwise, it is rolled back.
559
+ #
560
+ # It is safe to call `DBM.transaction` within another
561
+ # `DBM.transaction` block's call chain because `DBM` will not
562
+ # start a new transaction on a database handle that already has
563
+ # one in progress. (It may be possible to trick `DBM` into trying
564
+ # via fibers or other flow control trickery; don't do that.)
565
+ #
566
+ # It is also not safe to mix `DBM` transactions and bare `SQLite3`
567
+ # transactions.
568
+ #
569
+ # Transactions are always executed in `:deferred` mode.
570
+ #
571
+ # @yield [db] The block takes a reference to the receiver as an
572
+ # argument.
573
+ #
574
+ def transaction(&block)
575
+ return @handle.transaction { block.call(self) }
576
+ end
577
+
578
+ # Test if there is currently a transaction in progress
579
+ def transaction_active?
580
+ return @handle.transaction_active?
581
+ end
582
+
583
+
584
+ #
585
+ # Basic hash-like access
586
+ #
587
+
588
+
589
+ # Store `value` at `key` in the database.
590
+ #
591
+ # `key` **must** be a String or a Symbol.
592
+ #
593
+ # `value` **must** be convertable to string by whichever
594
+ # serialization method you have chosen.
595
+ def []=(key, value)
596
+ key = check_key(key)
597
+ valstr = SQLite3::Blob.new( @valenc.call(value) )
598
+
599
+ insert = <<~SQL
600
+ insert into #{actual_tbl} (key, value) values (?,?)
601
+ on conflict(key) do update set value = ?;
602
+ SQL
603
+ transaction {
604
+ @handle.sql(insert, [key, valstr, valstr])
605
+ }
606
+
607
+ return value
608
+ end
609
+ alias store :'[]='
610
+
611
+ # Retrieve the value associated with `key` from the database or
612
+ # nil if it is not present.
613
+ def [](key)
614
+ return fetch(key, nil)
615
+ end
616
+
617
+ # Retrieve the value associated with `key`.
618
+ #
619
+ # If it is not present and a block is given, evaluate the block
620
+ # with the key as its argument and return that.
621
+ #
622
+ # If no block was given either but one extra parameter was given,
623
+ # that value is returned instead.
624
+ #
625
+ # Finally, if none of these was given, it throws an `IndexError`
626
+ # exception.
627
+ #
628
+ # It is an error if `fetch` is called with more than two arguments.
629
+ #
630
+ # @yield [key] The fallback block.
631
+ def fetch(key, *args, &default_block)
632
+
633
+ # Ensure there are no extra arguments
634
+ nargs = args.size + 1
635
+ check("Too many arguments for 'fetch'; expected 1 or 2; got #{nargs}") {
636
+ nargs <= 2
637
+ }
638
+
639
+ # Retrieve the value
640
+ key = check_key(key)
641
+ rows = @handle.sql("select * from #{actual_tbl} where key=?" +
642
+ " order by rowid;", [key])
643
+ check("Multiple matches for key '#{key}'!") { rows.size <= 1 }
644
+
645
+ # Return the value if found
646
+ return @valdec.call(rows[0][1]) unless rows.empty?
647
+
648
+ # Otherwise, evaluate the block and use its result if a block was
649
+ # given
650
+ return default_block.call(key) if default_block
651
+
652
+ # Next, see if we have a default value we can return
653
+ return args[0] if args.size > 0
654
+
655
+ # And if all else fails, raise an IndexError.
656
+ raise IndexError.new("key '#{key}' not found.")
657
+ end
658
+
659
+ # Return a new `Array` containing the values corresponding to the
660
+ # given keys.
661
+ def values_at(*keys)
662
+ return keys.map{|k| self[k]}
663
+ end
664
+
665
+ # Return an `Array` of all of the keys in the table.
666
+ #
667
+ # **WARNING:** since this list is being read from disk, it is possible
668
+ # that the result could exceed available memory.
669
+ def keys
670
+ keys = []
671
+ each_key{|k| keys.push k}
672
+ return keys
673
+ end
674
+
675
+ # Return an array of all values in the table.
676
+ #
677
+ # **WARNING:** since this list is being read from disk, it is possible
678
+ # that the result could exceed available memory.
679
+ def values
680
+ values = []
681
+ each_value {|val| values.push val }
682
+ return values
683
+ end
684
+
685
+ # Return `true` if the table contains `key`; otherwise, return
686
+ # `false`.
687
+ def has_key?(key)
688
+ return false unless key.class == String || key.class == Symbol
689
+ fetch( key ) { return false }
690
+ return true
691
+ end
692
+ alias include? has_key?
693
+ alias member? has_key?
694
+ alias key? has_key?
695
+
696
+ # Delete all entries from the table.
697
+ def clear
698
+ transaction { @handle.sql("delete from #{actual_tbl};", []) }
699
+ end
700
+
701
+ # Calls the given block with each key-value pair in the usual
702
+ # order, then return self. The entire call takes place in its own
703
+ # transaction.
704
+ #
705
+ # If no block is given, returns an Enumerator instead. The
706
+ # Enumerator does *not* start a transaction but individual
707
+ # accesses of it (e.g. calling `next`) each take place in their
708
+ # own transaction.
709
+ #
710
+ # @yield [key, value] The block to evaluate
711
+ def each(&block)
712
+ return self.to_enum(:nt_each) unless block
713
+ transaction { nt_each(&block) }
714
+ return self
715
+ end
716
+ alias each_pair each
717
+
718
+ private
719
+
720
+ # Back-end for `each`; does not explicitly start a transaction.
721
+ def nt_each(&block)
722
+ #return self.to_enum unless block
723
+
724
+ front = "select rowid, key, value from #{actual_tbl} "
725
+ back = " order by rowid limit 1;"
726
+
727
+ # We do enumeration by looking up each item in a separate query
728
+ # rather than attaching a block to the SQL query. This is so that
729
+ # it is safe to access `self` from inside the block.
730
+ get_row = proc do |first, last_id|
731
+ query = front
732
+ query += "where rowid > ?" unless first
733
+ query += back
734
+
735
+ vars = []
736
+ vars.push last_id unless first
737
+
738
+ row = @handle.sql(query, vars)
739
+ return self if row.empty?
740
+
741
+ rowid, key, value = row[0]
742
+ block.call(key, @valdec.call(value))
743
+
744
+ next rowid
745
+ end
746
+
747
+ rowid = get_row.call(true, nil)
748
+ while true
749
+ rowid = get_row.call(false, rowid)
750
+ end
751
+
752
+ return self # not reached
753
+ end
754
+
755
+ public
756
+
757
+ # Calls the given block with each key; returns self. Exactly like
758
+ # `each` except for the block argument.
759
+ #
760
+ # @yield [key] The block to evaluate
761
+ def each_key(&block)
762
+ return Enumerator.new{|y| nt_each{ |k,v| y << k } } unless block
763
+ return each{ |k,v| block.call(k) }
764
+ end
765
+
766
+ # Calls the given block with each value; returns self. Exactly like
767
+ # `each` except for the block argument.
768
+ #
769
+ # @yield [value] The block to evaluate
770
+ def each_value(&block)
771
+ return Enumerator.new{|y| nt_each{ |k,v| y << v } } unless block
772
+ return each{ |k,v| block.call(v) }
773
+ end
774
+
775
+ # Updates the database with multiple values from the specified
776
+ # object. Takes any object which implements the each_pair method,
777
+ # including `Hash` and `DBM` objects.
778
+ def update(hash)
779
+ transaction {
780
+ hash.each{|k, v| self[k] = v }
781
+ }
782
+ end
783
+
784
+ # Remove `key` and its associated value from `self`. If `key` is
785
+ # not present, does nothing.
786
+ def delete(key)
787
+ transaction {
788
+ @handle.sql("delete from #{actual_tbl} where key = ?", [key])
789
+ }
790
+ end
791
+
792
+ # Evaluate the block on each key-value pair in `self` end delete
793
+ # each entry for which the block returns true.
794
+ #
795
+ # @yield [value] The block to evaluate
796
+ def delete_if(&block)
797
+ transaction {
798
+ self.each{ |k, v| block.call(k,v) and delete(k) }
799
+ }
800
+ end
801
+ alias reject! delete_if
802
+
803
+ # Return the number of entries (key-value pairs) in `self`.
804
+ def size
805
+ @handle.sql("select count(*) from #{actual_tbl};", []) { |row|
806
+ return row[0]
807
+ }
808
+ check("count query failed!")
809
+ end
810
+ alias length size
811
+
812
+ # Test if `self` is empty.
813
+ def empty?
814
+ return size == 0
815
+ end
816
+
817
+
818
+ #
819
+ # Conversion to internal types
820
+ #
821
+
822
+
823
+ # Copies the table into a `Hash` and returns it.
824
+ #
825
+ # **WARNING:** it is possible for tables to be significantly larger
826
+ # than available RAM; in that case, this will likely crash your
827
+ # program.
828
+ def to_hash
829
+ result = {}
830
+ each{|k,v| result[k] = v}
831
+ return result
832
+ end
833
+
834
+
835
+ # Returns an `Array` of 2-element `Array` objects each containing a
836
+ # key-value pair from `self`.
837
+ #
838
+ # **WARNING:** it is possible for tables to be significantly larger
839
+ # than available RAM; in that case, this will likely crash your
840
+ # program.
841
+ def to_a
842
+ result = []
843
+ each { |k,v| result.push [k,v] }
844
+ return result
845
+ end
846
+
847
+
848
+ #
849
+ # Hacky odds and ends
850
+ #
851
+
852
+
853
+ # Test if `val` is one of the values in this table.
854
+ #
855
+ # Potentially very slow, especially on large tables.
856
+ def has_value?(val)
857
+ self.each{|k,v| return true if v == val}
858
+ return false
859
+ end
860
+ alias value? has_value?
861
+
862
+ # Return a `Hash` whose keys are the table's values and whose values
863
+ # are the table's keys.
864
+ #
865
+ # **WARNING:** it is possible for tables to be significantly larger
866
+ # than available RAM; in that case, this will likely crash your
867
+ # program.
868
+ def invert
869
+ result = {}
870
+ each{|k,v| result[v] = k}
871
+ return result
872
+ end
873
+
874
+ # Remove the first key/value pair from `self` and return it. "First"
875
+ # is defined by `self`'s row order, which is the order of insertion
876
+ # as determined by SQLite3.
877
+ def shift
878
+ transaction {
879
+ return nil if empty?
880
+
881
+ key, value = self.each.first
882
+ delete(key)
883
+
884
+ return [key, value]
885
+ }
886
+ end
887
+
888
+ private
889
+
890
+ # Attempt to turn 'key' to a valid key and raise an exception if
891
+ # that isn't possible.
892
+ def check_key(key)
893
+ key = key.to_s if key.class == Symbol
894
+ raise TypeError.new("Key '#{key}' is not a string or symbol!") unless
895
+ key.class == String
896
+
897
+ return key
898
+ end
899
+
900
+ # Error check: if block evaluates to false, raise a Lite3::DBM::Error
901
+ # with the given message.
902
+ def check(message, &block)
903
+ return if block && block.call
904
+ raise Error.new(message)
905
+ end
906
+ end
907
+
908
+ private_constant :Handle, :ClosedHandle, :HandlePool
909
+ end