dodb.cr/src/cached.cr

168 lines
3.6 KiB
Crystal

require "file_utils"
require "json"
class Hash(K,V)
def reverse
rev = Array(Tuple(K,V)).new
keys = Array(K).new
each_key do |k|
keys << k
end
keys.reverse.each do |k|
rev << {k, self.[k]}
end
rev
end
end
class DODB::CachedDataBase(V) < DODB::Storage(V)
@indexers = [] of Indexer(V)
property data = Hash(Int32, V).new
def initialize(@directory_name : String)
Dir.mkdir_p data_path
Dir.mkdir_p locks_directory
begin
self.last_index
rescue
self.last_index = -1
end
# Load the database in RAM at start-up.
DODB::DataBase(V).new(@directory_name).each_with_index do |v, index|
puts "\rloading data from #{@directory_name} at index #{index}"
self[index] = v
end
end
# Getting data from the hash in RAM.
def []?(key : Int32) : V?
@data[key]
rescue e
# FIXME: rescues any error the same way.
return nil
end
# WARNING: data isn't cloned.
# You have to do it yourself in case you modify any value,
# otherwise you may encounter problems (at least with indexes).
def [](key : Int32) : V
@data[key] rescue raise MissingEntry.new(key)
end
def []=(index : Int32, value : V)
old_value = self.[index]?
check_collisions! index, value, old_value
# Removes any old indices or partitions pointing to a value about
# to be replaced.
if old_value
remove_indexes index, old_value
end
# Avoids corruption in case the application crashes while writing.
file_path(index).tap do |path|
::File.write "#{path}.new", value.to_json
::FileUtils.mv "#{path}.new", path
end
write_partitions index, value
if index > last_index
self.last_index = index
end
@data[index] = value
end
##
# Can be useful for making dumps or to restore a database.
def each_with_index(reversed : Bool = false, start_offset = 0, end_offset : Int32? = nil)
i = -1 # do not trust key to be the right index
(reversed ? @data.reverse : @data).each do |index, v|
i += 1
next if start_offset > i
break unless end_offset.nil? || i <= end_offset
yield v, index
end
end
def delete(key : Int32)
value = self[key]?
return if value.nil?
begin
::File.delete file_path key
rescue File::NotFoundError
end
remove_indexes key, value
@data.delete key
value
end
private def remove_data!
super
@data = Hash(Int32, V).new
end
end
# `DODB::RAMOnlyDataBase` is a database without a file-system representation,
# enabling the use of DODB to store data which have the same lifetime as the application.
# Indexing (indexes, partitions, tags) will behave the same way.
class DODB::RAMOnlyDataBase(V) < DODB::CachedDataBase(V)
# Initialization still uses a directory name and creates a few paths.
# This is an implementation detail to re-use code of `DODB::Storage` and to get the indexers to work.
def initialize(@directory_name : String)
Dir.mkdir_p data_path
Dir.mkdir_p locks_directory
self.last_index = -1
end
# WARNING: takes `[]?` and `[]` implementations from `CachedDataBase`.
# This will lead to errors in case the implementations change, be aware.
def []=(index : Int32, value : V)
old_value = self.[index]?
check_collisions! index, value, old_value
# Removes any old indices or partitions pointing to a value about
# to be replaced.
if old_value
remove_indexes index, old_value
end
write_partitions index, value
if index > last_index
self.last_index = index
end
@data[index] = value
end
def delete(key : Int32)
value = self[key]?
return if value.nil?
remove_indexes key, value
@data.delete key
value
end
private def remove_data!
super
@data = Hash(Int32, V).new
end
end