dbmlite3 1.0.a1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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