Class: LibCDB::CDB

Inherits:
Object
  • Object
show all
Extended by:
Forwardable
Defined in:
lib/libcdb.rb,
lib/libcdb/version.rb,
ext/libcdb/ruby_cdb.c

Defined Under Namespace

Modules: Version Classes: Reader, Writer

Constant Summary collapse

MODE_READ =

:nodoc:

'r'.freeze
MODE_WRITE =

:nodoc:

'w'.freeze
VERSION =
Version.to_s
LIBCDB_VERSION =

The TinyCDB library version.

rb_str_new2(libcdb_version)

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(io, mode = MODE_WRITE) ⇒ CDB

call-seq:

CDB.new(io[, mode]) -> aCDB

Creates a new CDB object to interface with io. mode must be the same mode io was opened in, either r or w. Responds to both Reader and Writer methods interchangeably by reopening io in the corresponding mode and instantiating a new Reader or Writer object with it. Note that io will be truncated each time it’s opened for writing.



256
257
258
259
260
261
262
263
264
# File 'lib/libcdb.rb', line 256

def initialize(io, mode = MODE_WRITE)
  @io, @mode = io, mode

  case mode
    when MODE_READ  then open_read
    when MODE_WRITE then open_write
    else raise ArgumentError, "illegal access mode #{mode.inspect}"
  end
end

Instance Attribute Details

#ioObject (readonly)

The underlying IO object.



267
268
269
# File 'lib/libcdb.rb', line 267

def io
  @io
end

#modeObject (readonly)

The current IO mode, either r or w.



270
271
272
# File 'lib/libcdb.rb', line 270

def mode
  @mode
end

Class Method Details

.dump(path, target = '', separator = $\ || $/) ⇒ Object

call-seq:

CDB.dump(path[, target[, separator]]) -> target

Opens path for reading and shovels each record dump into target (followed by separator, if present). Returns target.



106
107
108
109
110
111
112
113
114
115
# File 'lib/libcdb.rb', line 106

def dump(path, target = '', separator = $\ || $/)
  open(path) { |cdb|
    cdb.each_dump { |dump|
      target << dump
      target << separator if separator
    }

    target
  }
end

.foreach(path, *key) ⇒ Object

call-seq:

CDB.foreach(path) { |key, val| ... }
CDB.foreach(path, key) { |val| ... }

Opens path for reading and iterates over each key/value pair, or, if key is given, each value for key.



97
98
99
# File 'lib/libcdb.rb', line 97

def foreach(path, *key)
  open(path) { |cdb| cdb.each(*key) { |val| yield val } }
end

.load(path, dump) ⇒ Object

call-seq:

CDB.load(path, dump) -> aCDB

Opens path for writing and loads dump into the database. dump may be a string or an IO object. Returns the (unclosed) CDB object.



122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
# File 'lib/libcdb.rb', line 122

def load(path, dump)
  require 'strscan'

  s, n, e = nil, 0, lambda { |m| s.eos? ?
    raise("Unexpected end of input (#{m} at #{n}).") :
    raise("#{m} at #{n}:#{s.pos}: #{s.peek(16).inspect}") }

  cdb = open(path, 'w+')

  dump.each_line { |line|
    n += 1

    s = StringScanner.new(line)

    e['Record identifier expected'] unless s.scan(/\+/)

    e['Key length expected'] unless s.scan(/\d+/)
    klen = s.matched.to_i

    e['Length separator expected'] unless s.scan(/,/)

    e['Value length expected'] unless s.scan(/\d+/)
    vlen = s.matched.to_i

    e['Key separator expected'] unless s.scan(/:/)

    key = ''
    klen.times { key << s.get_byte }

    e['Value separator expected'] unless s.scan(/->/)

    value = ''
    vlen.times { value << s.get_byte }

    e['Record terminator expected'] unless s.scan(/\n/)
    e['Unexpected data'] unless s.eos?

    cdb.store(key, value)
  }

  cdb
end

.load_file(path, file) ⇒ Object

call-seq:

CDB.load_file(path, file) -> aCDB

Loads the dump at file into the database at path (see #load).



169
170
171
# File 'lib/libcdb.rb', line 169

def load_file(path, file)
  File.open(file, 'rb') { |f| self.load(path, f) }
end

.open(path, mode = MODE_READ) ⇒ Object

call-seq:

CDB.open(path[, mode]) -> aReader | aWriter | aCDB
CDB.open(path[, mode]) { |cdb| ... }

Opens path with mode. If a block is given, yields a cdb object according to mode (see below) and returns the return value of the block; the object is closed afterwards. Otherwise just returns the object.

r

Reader

w

Writer

r+

CDB (initially opened for reading)

w+

CDB (initially opened for writing)



62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
# File 'lib/libcdb.rb', line 62

def open(path, mode = MODE_READ)
  klass, args = _open_args(path, mode)

  cdb = begin
    klass.new(*args)
  rescue
    args.first.close
    raise
  end

  if block_given?
    begin
      yield cdb
    ensure
      err = $!

      begin
        cdb.close
      rescue
        raise unless err
      end

      raise err if err
    end
  else
    cdb
  end
end

call-seq:

CDB.print_stats(path) -> aHash

Prints the #stats on path.



207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
# File 'lib/libcdb.rb', line 207

def print_stats(path)
  stats(path).tap { |s|
    r, k, v, h = s.values_at(:records, :keys, :values, :hash)

    v1, v2 = [:min, :avg, :max], [:tables, :entries, :collisions]

    puts 'number of records: %d'                    % r
    puts 'key min/avg/max length: %d/%d/%d'         % k.values_at(*v1)
    puts 'val min/avg/max length: %d/%d/%d'         % v.values_at(*v1)

    # TODO: hash table stats
=begin
    puts 'hash tables/entries/collisions: %d/%d/%d' % h.values_at(*v2)
    puts 'hash table min/avg/max length: %d/%d/%d'  % h.values_at(*v1)
    puts 'hash table distances:'

    d = h[:distances]
    0.upto(9) { |i| puts ' d%d: %6d %2d%%' % [i, *d[i]] }
    puts ' >9: %6d %2d%%' % d[-1]
=end
  }
end

.stats(path) ⇒ Object

call-seq:

CDB.stats(path) -> aHash

Returns a hash with the stats on path.



177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
# File 'lib/libcdb.rb', line 177

def stats(path)
  {}.tap { |stats| open(path) { |cdb|
    stats[:records] = cnt = cdb.total

    stats[:keys]   = khash = { min: Float::INFINITY, avg: 0, max: 0 }
    stats[:values] = vhash = khash.dup

    stats[:hash] = Hash.new(0).update(distances: Hash.new([0, 0]))

    khash[:min] = vhash[:min] = 0 and break if cnt.zero?

    ktot, vtot, update = 0, 0, lambda { |h, s| s.bytesize.tap { |l|
      h[:min] = l if l < h[:min]
      h[:max] = l if l > h[:max]
    } }

    cdb.each_key   { |k| ktot += update[khash, k] }
    cdb.each_value { |v| vtot += update[vhash, v] }

    khash[:avg] = (ktot + cnt / 2) / cnt
    vhash[:avg] = (vtot + cnt / 2) / cnt

    # TODO: hash table stats
  } }
end

Instance Method Details

#closeObject

call-seq:

cdb.close -> nil

Closes both the #reader and the #writer, as well as #io. Doesn’t raise an IOError if either of them is already closed.



366
367
368
369
370
# File 'lib/libcdb.rb', line 366

def close
  close_read(false)
  close_write(false)
  io.close unless io.closed?
end

#close_read(strict = true) ⇒ Object

call-seq:

cdb.close_read([strict]) -> nil

If cdb is currently opened for reading, closes the #reader (and #io with it). Otherwise, if strict is true, raises an IOError.



338
339
340
341
342
343
344
345
# File 'lib/libcdb.rb', line 338

def close_read(strict = true)
  if read?
    @reader.close
    @reader = nil
  elsif strict
    raise IOError, 'not opened for reading'
  end
end

#close_write(strict = true) ⇒ Object

call-seq:

cdb.close_write([strict]) -> nil

If cdb is currently opened for writing, closes the #writer (and #io with it). Otherwise, if strict is true, raises an IOError.



352
353
354
355
356
357
358
359
# File 'lib/libcdb.rb', line 352

def close_write(strict = true)
  if write?
    @writer.close
    @writer = nil
  elsif strict
    raise IOError, 'not opened for writing'
  end
end

#closed?Boolean

call-seq:

cdb.closed? -> true | false | nil

Whether cdb is closed. See #closed_read? and #closed_write?.

Returns:

  • (Boolean)


392
393
394
# File 'lib/libcdb.rb', line 392

def closed?
  read? ? closed_read? : write? ? closed_write? : nil
end

#closed_read?Boolean

call-seq:

cdb.closed_read? -> true | false | nil

Whether #reader is closed if cdb is currently opened for reading.

Returns:

  • (Boolean)


376
377
378
# File 'lib/libcdb.rb', line 376

def closed_read?
  reader.closed? if read?
end

#closed_write?Boolean

call-seq:

cdb.closed_write? -> true | false | nil

Whether #writer is closed if cdb is currently opened for writing.

Returns:

  • (Boolean)


384
385
386
# File 'lib/libcdb.rb', line 384

def closed_write?
  writer.closed? if write?
end

#open_readObject

call-seq:

cdb.open_read -> aReader

Opens cdb for reading and reopens #io accordingly. Closes #writer if open.



318
319
320
321
# File 'lib/libcdb.rb', line 318

def open_read
  close_write(false)
  @reader = Reader.new(reopen(MODE_READ))
end

#open_writeObject

call-seq:

cdb.open_write -> aWriter

Opens cdb for writing and reopens #io accordingly. Closes #reader if open. Note that #io will be truncated.



328
329
330
331
# File 'lib/libcdb.rb', line 328

def open_write
  close_read(false)
  @writer = Writer.new(reopen(MODE_WRITE))
end

#read?Boolean

call-seq:

cdb.read? -> true | false

Whether cdb is currently opened for reading.

Returns:

  • (Boolean)


301
302
303
# File 'lib/libcdb.rb', line 301

def read?
  !!@reader
end

#readerObject

call-seq:

cdb.reader -> aReader

The Reader object associated with cdb.



276
277
278
# File 'lib/libcdb.rb', line 276

def reader
  @reader ||= open_read
end

#write?Boolean

call-seq:

cdb.write? -> true | false

Whether cdb is currently opened for writing.

Returns:

  • (Boolean)


309
310
311
# File 'lib/libcdb.rb', line 309

def write?
  !!@writer
end

#writerObject

call-seq:

cdb.writer -> aWriter

The Writer object associated with cdb.



284
285
286
# File 'lib/libcdb.rb', line 284

def writer
  @writer ||= open_write
end