report setzt Zellwerte in <!befehl!> maskiert ein, screen verlangt gültige Variablennamen, lock/unlock nutzen /tmp/rdb-locks-<UID> mit flock, vilock sperrt per Hardlink. EDITOR darf Argumente haben. Doku und Tests ergänzt.
538 lines
19 KiB
Ruby
538 lines
19 KiB
Ruby
# frozen_string_literal: true
|
|
|
|
# Finding, adding, changing and locking records. Many of these take the name
|
|
# of the table instead of reading it on stdin, because they change the file.
|
|
|
|
require 'stringio'
|
|
require_relative '../fast'
|
|
|
|
# Parse the common fast-access options. Stops at the first non-option, which
|
|
# is left in args; returns the option hash.
|
|
def fast_opts(args)
|
|
o = { m: 's', h: false, l: false, s: false, x: false, exact: false, a: false, v: false, o: nil }
|
|
until args.empty?
|
|
a = args.first
|
|
case a
|
|
when /\A-m([bhirs]?)\z/ then o[:m] = Regexp.last_match(1).empty? ? 's' : Regexp.last_match(1)
|
|
when '+x' then o[:exact] = true
|
|
when '-o' then args.shift; o[:o] = args.first
|
|
when /\A-[hslxav]+\z/ then a[1..].each_char { |c| o[c.to_sym] = true }
|
|
else break
|
|
end
|
|
args.shift
|
|
end
|
|
o
|
|
end
|
|
|
|
def need_index(table, method)
|
|
suffix = Rdb::Fast::SUFFIX[method]
|
|
return unless suffix && !File.exist?(table + suffix)
|
|
|
|
Rdb.die("no index #{table}#{suffix}: run index -m#{method} #{table}")
|
|
end
|
|
|
|
# The key columns and, unless keys come from the terminal, the keys. Without key
|
|
# columns on the command line a key table names them in its head line
|
|
# (handbook p. 105: "search -mh mailtable < name"); otherwise the first column.
|
|
def key_columns(args, data)
|
|
return [args.empty? ? [data.head[0]] : args, nil] if $stdin.tty?
|
|
|
|
text = $stdin.read.to_s
|
|
lines = text.lines.map { |l| l.chomp("\n") }
|
|
cols = args
|
|
if cols.empty?
|
|
keyed = lines.size >= 2 && Rdb::Table.dash_line?(lines[1].split("\t", -1))
|
|
cols = keyed ? lines[0].split("\t", -1).map(&:strip) : [data.head[0]]
|
|
end
|
|
[cols, Rdb::Fast.read_keys(StringIO.new(text), cols.size)]
|
|
end
|
|
|
|
def each_key(ncols, keys = nil, &block)
|
|
if keys
|
|
keys.each(&block)
|
|
elsif $stdin.tty?
|
|
$stdin.each_line { |l| yield l.chomp.split("\t", -1).first(ncols) }
|
|
else
|
|
Rdb::Fast.read_keys($stdin, ncols).each { |k| yield k }
|
|
end
|
|
end
|
|
|
|
Rdb.register('index', 'Set up table for search', 'index [-m[bhirs]] [-x] [-l] tableorlist [keycol ...]') do |args|
|
|
o = fast_opts(args)
|
|
table = args.shift or die 'usage: index [-m[bhirs]] table [keycolumn]'
|
|
data = Rdb::Fast::Data.new(table)
|
|
key = args.first || data.head[0]
|
|
data.column(key)
|
|
Rdb::Fast.build(table, o[:m], key, fold: o[:x])
|
|
if o[:l]
|
|
Rdb::Fast::Data.new(table).rows.each { |r| warn "#{r.from} #{r.to} 0 0" }
|
|
end
|
|
end
|
|
|
|
Rdb.register('search', 'Search a table',
|
|
'search [-m[bhirs]] [-hsnx +x] [-l] tableorlist [keycolumn ...] [< keytable]') do |args|
|
|
o = fast_opts(args)
|
|
table = args.shift or die 'usage: search [-m[bhirs]] table [keycolumn ...] < keys'
|
|
data = Rdb::Fast::Data.new(table)
|
|
need_index(table, o[:m])
|
|
keycols, keys = key_columns(args, data)
|
|
puts data.head.join("\t"), Rdb::Table.dash_row(data.head) unless o[:h]
|
|
each_key(keycols.size, keys) do |key|
|
|
hits = Rdb::Fast.search(data, o[:m], keycols, key, first: o[:s] || o[:l], partial: o[:x], exact: o[:exact])
|
|
hits.each do |h|
|
|
puts h.row.line
|
|
warn "#{h.row.from} #{h.row.to} #{h.xfrom} #{h.xto}" if o[:l]
|
|
end
|
|
$stdout.flush
|
|
end
|
|
end
|
|
|
|
Rdb.register('seek', 'Return the beginning and ending offset of a row', 'seek [-m[bihrs]] [-o outfile] tableorlist [keycol...] < file') do |args|
|
|
o = fast_opts(args)
|
|
table = args.shift or die 'usage: seek [-m] [-o outfile] table [keycolumn ...]'
|
|
data = Rdb::Fast::Data.new(table)
|
|
need_index(table, o[:m])
|
|
keycols, keys = key_columns(args, data)
|
|
each_key(keycols.size, keys) do |key|
|
|
Rdb::Fast.search(data, o[:m], keycols, key, first: true).each do |h|
|
|
puts "#{h.row.from} #{h.row.to} #{h.xfrom} #{h.xto}"
|
|
if o[:o]
|
|
File.write(o[:o], data.head.join("\t") + "\n" + Rdb::Table.dash_row(data.head) + "\n" + h.row.line + "\n")
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
Rdb.register('append', 'Add a row to a table and update index tables', 'append [-h -m[bhirs]] table [keycolumn ...] < tableorrow') do |args|
|
|
o = fast_opts(args)
|
|
table = args.shift or die 'usage: append [-h] [-m] table [keycolumn] < tableorrow'
|
|
data = Rdb::Fast::Data.new(table)
|
|
need_index(table, o[:m])
|
|
keycol = args.first || data.head[0]
|
|
text = $stdin.read
|
|
rows =
|
|
if o[:h] then text.lines.map { |l| l.chomp("\n").split("\t", -1) }
|
|
else Rdb::Table.parse(text).rows
|
|
end
|
|
rows.each { |cells| Rdb::Fast.append_row(table, cells, o[:m], keycol) }
|
|
end
|
|
|
|
Rdb.register('delete', 'Blank record and update index file', 'delete [-m[bhirs]] table keycolumn ... < keytable') do |args|
|
|
o = fast_opts(args)
|
|
table = args.shift or die 'usage: delete [-m] table keycolumn ... < keytable'
|
|
need_index(table, o[:m])
|
|
keycols = args.empty? ? [Rdb::Fast::Data.new(table).head[0]] : args
|
|
each_key(keycols.size) do |key|
|
|
data = Rdb::Fast::Data.new(table)
|
|
Rdb::Fast.search(data, o[:m], keycols, key).each { |h| Rdb::Fast.delete_hit(table, h, o[:m]) }
|
|
end
|
|
end
|
|
|
|
Rdb.register('replace', 'Insert a record into a file at specified location',
|
|
'replace [-a] [-m[bhirs]] tableorlist from to [xfrom xto] < intableorlist') do |args|
|
|
o = fast_opts(args)
|
|
table = args.shift or die 'usage: replace [-a] [-m] table from to [xfrom xto] < row'
|
|
from, to, xfrom, xto = args.map(&:to_i)
|
|
die 'usage: replace [-a] [-m] table from to [xfrom xto] < row' unless to
|
|
|
|
text = $stdin.read
|
|
lines = text.lines.map { |l| l.chomp("\n") }
|
|
lines = lines.drop(2) if lines.size >= 2 && Rdb::Table.dash_line?(lines[1].split("\t", -1))
|
|
die 'replace: no input row' if lines.empty?
|
|
|
|
keycol = Rdb::Fast::Data.new(table).head[0]
|
|
newfrom = Rdb::Fast.replace_row(table, from, to, lines.first.split("\t", -1), o[:m], xfrom.to_i, xto.to_i, keycol,
|
|
allow_append: !o[:a])
|
|
die 'replace: record too big for its slot (-a)' unless newfrom
|
|
end
|
|
|
|
Rdb.register('enter', 'Add rows to a table or list file without an editor', 'enter [-limit] tableorlist') do |args|
|
|
limit = args.first&.match?(/\A-\d+\z/) ? args.shift[1..].to_i : nil
|
|
file = args.first or die 'usage: enter [-limit] tableorlist'
|
|
t = Rdb::Table.open(file)
|
|
count = 0
|
|
loop do
|
|
break if limit && count >= limit
|
|
|
|
vals = []
|
|
i = 0
|
|
eof = false
|
|
while i < t.head.size
|
|
$stdout.print "#{t.head[i]}: "
|
|
$stdout.flush
|
|
line = $stdin.gets
|
|
if line.nil?
|
|
eof = true
|
|
break
|
|
end
|
|
line = line.chomp
|
|
if line == '^'
|
|
i = [i - 1, 0].max
|
|
next
|
|
end
|
|
vals[i] = line
|
|
i += 1
|
|
end
|
|
break if eof || vals.first.to_s.empty?
|
|
|
|
if t.list
|
|
File.open(file, 'a') { |f| t.head.each_with_index { |h, j| f.puts "#{h}\t#{vals[j]}" }; f.puts }
|
|
else
|
|
Rdb::Fast.append_row(file, vals, 's', t.head[0])
|
|
end
|
|
count += 1
|
|
end
|
|
end
|
|
|
|
require 'digest'
|
|
require 'shellwords'
|
|
|
|
# ---- locks -------------------------------------------------------------------
|
|
|
|
# Lock files live in a private per-user directory, not loose in the shared
|
|
# /tmp, so other users can neither forge nor delete them.
|
|
def lock_dir
|
|
dir = File.join(Dir.tmpdir, "rdb-locks-#{Process.uid}")
|
|
Dir.mkdir(dir, 0o700) unless File.directory?(dir)
|
|
st = File.lstat(dir)
|
|
Rdb.die("#{dir}: not a private directory of yours") if st.symlink? || st.uid != Process.uid || (st.mode & 0o077) != 0
|
|
dir
|
|
end
|
|
|
|
def lock_file(name)
|
|
File.join(lock_dir, "L#{File.basename(name)}.#{Digest::MD5.hexdigest(File.expand_path(name))[0, 8]}")
|
|
end
|
|
|
|
# Read-modify-write of a lock file under an exclusive flock, so two lock or
|
|
# unlock calls cannot interleave. The block gets the held locks and returns
|
|
# the new list of lines, or nil to leave the file alone.
|
|
def with_lock_file(name)
|
|
File.open(lock_file(name), File::RDWR | File::CREAT, 0o600) do |f|
|
|
f.flock(File::LOCK_EX)
|
|
held = f.read.lines(chomp: true).map { |l| l.split.map(&:to_i) }
|
|
lines = yield held
|
|
next unless lines
|
|
|
|
f.rewind
|
|
f.truncate(0)
|
|
f.write(lines.map { |l| "#{l}\n" }.join)
|
|
end
|
|
end
|
|
|
|
Rdb.register('lock', 'Lock a record or field of a file', 'lock filename processid from to indexfrom indexto') do |args|
|
|
die 'usage: lock filename processid from to indexfrom indexto' if args.size < 4
|
|
name, pid, from, to = args
|
|
clash = nil
|
|
with_lock_file(name) do |held|
|
|
clash = held.find { |p, f, t| p != pid.to_i && f < to.to_i && from.to_i < t }
|
|
clash ? nil : held.map { |l| l.join(' ') } << args.drop(1).join(' ')
|
|
end
|
|
if clash
|
|
warn "lock: #{name}: bytes #{from}-#{to} are locked by process #{clash[0]}"
|
|
exit 1
|
|
end
|
|
end
|
|
|
|
Rdb.register('unlock', 'Unlock a record or field of a file', 'unlock tableorlist processid from to indexfrom indexto') do |args|
|
|
die 'usage: unlock filename processid from to indexfrom indexto' if args.size < 4
|
|
name = args.shift
|
|
next unless File.exist?(lock_file(name))
|
|
|
|
with_lock_file(name) do |held|
|
|
held.map { |l| l.join(' ') }.reject { |l| l.split.first(3) == args.first(3) }
|
|
end
|
|
lf = lock_file(name)
|
|
File.delete(lf) if File.zero?(lf)
|
|
end
|
|
|
|
Rdb.register('vilock', 'Lock a table before editing, unlock afterward', 'vilock tableorlist') do |args|
|
|
file = args.first or die 'usage: vilock tableorlist'
|
|
locked = File.join(File.dirname(file), "LOCK#{File.basename(file)}")
|
|
die "#{file} is locked or missing" unless File.exist?(file)
|
|
|
|
# link fails if LOCK<file> exists, so exactly one caller gets the table
|
|
# (File.exist? followed by rename would leave a race in between).
|
|
begin
|
|
File.link(file, locked)
|
|
rescue Errno::EEXIST
|
|
die "#{file} is locked or missing"
|
|
end
|
|
File.unlink(file)
|
|
begin
|
|
system(*Shellwords.split(ENV['EDITOR'] || 'vi'), locked)
|
|
ensure
|
|
File.rename(locked, file)
|
|
end
|
|
end
|
|
|
|
# ---- interactive editing --------------------------------------------------------
|
|
|
|
def show_record(data_head, cells)
|
|
data_head.each_with_index { |h, i| puts "#{i + 1} #{h}\t#{cells[i].to_s.strip}" }
|
|
end
|
|
|
|
def edit_cells(head, cells)
|
|
require 'tempfile'
|
|
Tempfile.create('rdb-edit') do |f|
|
|
f.write Rdb::Table.new(head, [cells], list: true).to_s
|
|
f.flush
|
|
system(*Shellwords.split(ENV['EDITOR'] || 'vi'), f.path)
|
|
t = Rdb::Table.parse(File.read(f.path))
|
|
return nil if t.rows.empty?
|
|
|
|
head.map { |h| (j = t.head.index(h)) ? t.rows[0][j] : '' }
|
|
end
|
|
end
|
|
|
|
Rdb.register('update.rdb', 'Display and edit records in any sized file',
|
|
'update.rdb [-l -m[bhirs] -v] tableorlist [keycolumn ... ]') do |args|
|
|
o = fast_opts(args)
|
|
table = args.shift or die 'usage: update.rdb [-l -m -v] table [keycolumn]'
|
|
need_index(table, o[:m])
|
|
keycols = args.empty? ? [Rdb::Fast::Data.new(table).head[0]] : args
|
|
cur = nil
|
|
help = lambda do
|
|
puts ' <Return> or n next record p or - previous record',
|
|
' number go to that record /pattern next record matching pattern',
|
|
' e edit this record d delete this record',
|
|
' anything else look up by key q or CTRL-d quit'
|
|
end
|
|
loop do
|
|
data = Rdb::Fast::Data.new(table)
|
|
rows = Rdb::Fast.live_rows(data)
|
|
print '> '
|
|
$stdout.flush
|
|
line = $stdin.gets or break
|
|
cmd = line.strip
|
|
case cmd
|
|
when '', /\An/i then cur = cur ? cur + 1 : 0
|
|
when /\A-/, /\Ap/i then cur = [(cur || 1) - 1, 0].max
|
|
when /\Aq/i then break
|
|
when /\Ah/i then help.call; next
|
|
when /\A\d+\z/ then cur = cmd.to_i - 1
|
|
when %r{\A/(.*)}
|
|
re = Regexp.new(Regexp.last_match(1), Regexp::IGNORECASE)
|
|
found = rows.each_index.find { |i| i > (cur || -1) && rows[i].line.match?(re) }
|
|
found ? cur = found : (puts 'not found'; next)
|
|
when /\Ae/i
|
|
next puts('no current record') unless cur && rows[cur]
|
|
|
|
r = rows[cur]
|
|
cells = r.cells
|
|
Rdb::Fast.splice(table, r.from, r.to, Rdb::Fast.blanked(r.line) + "\n") unless o[:l]
|
|
new = edit_cells(data.head, cells.map(&:strip))
|
|
if new
|
|
hit = Rdb::Fast::Hit.new(r, 0, 0)
|
|
xf, xt = 0, 0
|
|
if Rdb::Fast::SUFFIX[o[:m]]
|
|
h = Rdb::Fast.search(Rdb::Fast::Data.new(table), o[:m], keycols, cells.values_at(*keycols.map { |c| data.column(c) }), first: true).first
|
|
xf, xt = h.xfrom, h.xto if h
|
|
end
|
|
Rdb::Fast.replace_row(table, hit.row.from, hit.row.to, new, o[:m], xf, xt, keycols[0])
|
|
puts 'record replaced'
|
|
else
|
|
Rdb::Fast.splice(table, r.from, r.to, r.line + "\n")
|
|
puts 'unchanged'
|
|
end
|
|
next
|
|
when /\Ad/i
|
|
next puts('no current record') unless cur && rows[cur]
|
|
|
|
h = Rdb::Fast::Hit.new(rows[cur], 0, 0)
|
|
if Rdb::Fast::SUFFIX[o[:m]]
|
|
h = Rdb::Fast.search(data, o[:m], keycols, rows[cur].cells.values_at(*keycols.map { |c| data.column(c) }), first: true).first || h
|
|
end
|
|
Rdb::Fast.delete_hit(table, h, o[:m])
|
|
puts 'record deleted'
|
|
next
|
|
else
|
|
key = cmd.split("\t", -1)
|
|
hit = Rdb::Fast.search(data, o[:m], keycols, key, first: true).first
|
|
next puts('not found') unless hit
|
|
|
|
cur = rows.index { |r| r.from == hit.row.from }
|
|
end
|
|
if cur && rows[cur]
|
|
puts "-- record #{cur + 1} of #{rows.size}"
|
|
show_record(data.head, rows[cur].cells)
|
|
else
|
|
puts 'no such record'
|
|
cur = nil
|
|
end
|
|
end
|
|
end
|
|
|
|
Rdb.register('ve', 'Visually edit a table', 've [table [-n[n] -fc -mc -i -s [file] -h [file] -a [file] -v [file]]] [-b]') do |args|
|
|
file = args.shift or die 'usage: ve table (a plain prompt-driven editor; see update.rdb for fast access)'
|
|
auto = args.find { |a| a.match?(/\A-n\d*\z/) }
|
|
unless File.exist?(file)
|
|
head = Rdb::Table.new(%w[Item], []).head
|
|
print 'Column names for the new table, tab separated: '
|
|
head = ($stdin.gets || '').chomp.split("\t")
|
|
File.write(file, Rdb::Table.new(head, []).to_s)
|
|
end
|
|
cur = 0
|
|
loop do
|
|
t = Rdb::Table.open(file)
|
|
rows = t.rows
|
|
puts "-- #{file}: record #{rows.empty? ? 0 : cur + 1} of #{rows.size}"
|
|
show_record(t.head, rows[cur]) if rows[cur]
|
|
print '[n]ext [p]rev [g]o N [e]dit [a]dd [d]elete [q]uit > '
|
|
$stdout.flush
|
|
line = $stdin.gets or break
|
|
case line.strip
|
|
when 'n', '' then cur = [cur + 1, rows.size - 1].min
|
|
when 'p' then cur = [cur - 1, 0].max
|
|
when /\Ag\s*(\d+)/ then cur = [[Regexp.last_match(1).to_i - 1, 0].max, rows.size - 1].min
|
|
when 'e'
|
|
next unless rows[cur]
|
|
|
|
puts 'field number, then the new value:'
|
|
print 'field> '
|
|
f = ($stdin.gets || '').to_i - 1
|
|
next puts('no such field') unless (0...t.head.size).cover?(f)
|
|
|
|
print "#{t.head[f]}> "
|
|
rows[cur][f] = ($stdin.gets || '').chomp
|
|
File.write(file, t.to_s)
|
|
when 'a'
|
|
row = t.head.each_with_index.map do |h, i|
|
|
if i.zero? && auto
|
|
start = auto[2..].to_i
|
|
next ([rows.map { |r| r[0].to_i }.max.to_i + 1, start].max).to_s
|
|
end
|
|
print "#{h}> "
|
|
($stdin.gets || '').chomp
|
|
end
|
|
rows << row
|
|
File.write(file, t.to_s)
|
|
cur = rows.size - 1
|
|
when 'd'
|
|
rows.delete_at(cur) if rows[cur]
|
|
File.write(file, t.to_s)
|
|
cur = [cur, rows.size - 1].min
|
|
cur = 0 if cur.negative?
|
|
when 'q' then break
|
|
end
|
|
end
|
|
end
|
|
|
|
# ---- checking data ------------------------------------------------------------
|
|
|
|
Rdb.register('domain', 'Display invalid values in a column', 'domain domaintable [string ...] [< one-column-table]') do |args|
|
|
dom = args.shift or die 'usage: domain domaintable [string ...]'
|
|
valid = open_table(dom).rows.map { |r| r[0].strip }.to_h { |v| [v, true] }
|
|
if args.any?
|
|
args.reject { |s| valid[s.strip] }.each { |s| puts s }
|
|
else
|
|
t = table_in
|
|
t.with(rows: t.rows.reject { |r| valid[r[0].strip] }).write
|
|
end
|
|
end
|
|
|
|
Rdb.register('validate', 'Find invalid data', "validate 'pattern { action } ...' < tableorlist") do |args|
|
|
die "usage: validate 'pattern { action } ...' < table" unless args.size == 1
|
|
t = table_in
|
|
puts t.head.join("\t"), Rdb::Table.dash_row(t.head)
|
|
prog = "BEGIN{OFS=\"\\t\"} #{to_awk(args[0], t.head)} {print}"
|
|
out, ok = awk_text(prog, t.rows)
|
|
$stdout.write out
|
|
exit 1 unless ok
|
|
end
|
|
|
|
Rdb.register('fd', 'Test for functional dependency of columns', 'fd determinecolumn dependcolumn < table') do |args|
|
|
die 'usage: fd determinecolumn dependcolumn < table' unless args.size == 2
|
|
t = table_in
|
|
a = t.index_of(args[0])
|
|
b = t.index_of(args[1])
|
|
seen = {}
|
|
ok = t.rows.all? { |r| (seen[r[a]] ||= r[b]) == r[b] }
|
|
puts ok ? 'true' : 'false'
|
|
exit(ok ? 0 : 1)
|
|
end
|
|
|
|
Rdb.register('searchtree', 'Seek a string node in tree table', 'searchtree [-m[bhirs]] table root goal column1 column2') do |args|
|
|
fast_opts(args)
|
|
die 'usage: searchtree table root goal column1 column2' unless args.size == 5
|
|
file, root, goal, c1, c2 = args
|
|
t = Rdb::Table.open(file)
|
|
i1 = t.index_of(c1)
|
|
i2 = t.index_of(c2)
|
|
seen = { root => true }
|
|
queue = [root]
|
|
found = root == goal
|
|
until found || queue.empty?
|
|
node = queue.shift
|
|
t.rows.each do |r|
|
|
next unless r[i1].strip == node
|
|
|
|
child = r[i2].strip
|
|
next if seen[child]
|
|
|
|
seen[child] = true
|
|
found ||= child == goal
|
|
queue << child
|
|
end
|
|
end
|
|
puts found ? 'TRUE' : 'FALSE'
|
|
exit(found ? 0 : 1)
|
|
end
|
|
|
|
Rdb.register('vindex', 'Create and display ve look-up tables', 'vindex table [ key-column [: decode-column ] ... ]') do |args|
|
|
file = args.shift or die 'usage: vindex table [key-column [: decode-column] ...]'
|
|
t = Rdb::Table.open(file)
|
|
if args.empty?
|
|
Dir["#{File.dirname(file)}/*-[AB]"].sort.each { |f| puts "== #{File.basename(f)}"; $stdout.write File.read(f) }
|
|
next
|
|
end
|
|
until args.empty?
|
|
key = args.shift
|
|
decode = nil
|
|
if args.first == ':'
|
|
args.shift
|
|
decode = args.shift
|
|
end
|
|
k = t.index_of(key)
|
|
prefix = key.tr(' ', '_')[0, 14]
|
|
vals = t.rows.map { |r| [r[k].strip, decode ? r[t.index_of(decode)].strip : nil] }.reject { |v, _| v.empty? }.uniq.sort
|
|
File.write(File.join(File.dirname(file), "#{prefix}-A"), Rdb::Table.new([key], vals.map { |v, _| [v] }.uniq).to_s)
|
|
File.write(File.join(File.dirname(file), "#{prefix}-B"), Rdb::Table.new([key, decode], vals.map { |v, d| [v, d] }).to_s) if decode
|
|
end
|
|
end
|
|
|
|
# ---- exercising the fast-access methods ---------------------------------------------
|
|
|
|
def sample_table(path, n = 2000)
|
|
rows = (1..n).map { |i| [i.to_s, "name#{i * 7919 % n}", "city#{i % 97}"] }
|
|
File.write(path, Rdb::Table.new(%w[Id Name City], rows).to_s)
|
|
end
|
|
|
|
def time_methods(n = 2000)
|
|
Dir.mktmpdir do |dir|
|
|
path = File.join(dir, 'unixtable')
|
|
sample_table(path, n)
|
|
%w[s b h i r].each do |m|
|
|
t0 = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
|
Rdb::Fast.build(path, m, 'Name') unless m == 's'
|
|
t1 = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
|
data = Rdb::Fast::Data.new(path)
|
|
hits = 0
|
|
(1..200).each do |i|
|
|
key = m == 'r' ? i.to_s : "name#{i * 13 % n}"
|
|
hits += Rdb::Fast.search(data, m, ['Name'], key).size
|
|
end
|
|
t2 = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
|
yield m, t1 - t0, t2 - t1, hits
|
|
end
|
|
end
|
|
end
|
|
|
|
Rdb.register('timesearch', 'Time the fast-access methods', 'timesearch [ > timesearch.new ]') do |_args|
|
|
puts 'method index(s) 200 searches(s) rows found'
|
|
time_methods { |m, ti, ts, hits| puts format('-m%-5s %8.4f %15.4f %10d', m, ti, ts, hits) }
|
|
end
|
|
|
|
Rdb.register('testsearch', 'Test the fast-access methods', 'testsearch') do |_args|
|
|
time_methods(500) { |m, _ti, _ts, hits| puts "-m#{m}: #{hits.positive? ? 'ok' : 'FAILED'} (#{hits} rows found)" }
|
|
end
|