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