2020-07-21 12:49:32 +02:00
|
|
|
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
|
|
|
|
|
|
|
|
# TODO: load the database in RAM at start-up
|
|
|
|
DODB::DataBase(V).new(@directory_name).each_with_index do |v, index|
|
|
|
|
puts "loading value #{v} 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
|
2024-05-07 13:05:01 +02:00
|
|
|
|
|
|
|
# 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).
|
2020-07-21 12:49:32 +02:00
|
|
|
def [](key : Int32) : V
|
2024-05-07 13:05:01 +02:00
|
|
|
@data[key] rescue raise MissingEntry.new(key)
|
2020-07-21 12:49:32 +02:00
|
|
|
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
|
2024-05-06 23:52:17 +02:00
|
|
|
remove_indexes index, old_value
|
2020-07-21 12:49:32 +02:00
|
|
|
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
|
|
|
|
# FIXME: Only intercept “no such file" errors
|
|
|
|
end
|
|
|
|
|
2024-05-06 23:52:17 +02:00
|
|
|
remove_indexes key, value
|
2020-07-21 12:49:32 +02:00
|
|
|
|
|
|
|
@data.delete key
|
|
|
|
value
|
|
|
|
end
|
|
|
|
|
|
|
|
private def remove_data!
|
|
|
|
super
|
|
|
|
@data = Hash(Int32, V).new
|
|
|
|
end
|
|
|
|
end
|