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
|
|
|
|
|
2024-05-08 20:29:53 +02:00
|
|
|
# Load the database in RAM at start-up.
|
2020-07-21 12:49:32 +02:00
|
|
|
DODB::DataBase(V).new(@directory_name).each_with_index do |v, index|
|
2024-05-08 20:29:53 +02:00
|
|
|
puts "\rloading data from #{@directory_name} at index #{index}"
|
2020-07-21 12:49:32 +02:00
|
|
|
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
|
2024-05-08 20:29:53 +02:00
|
|
|
rescue File::NotFoundError
|
2020-07-21 12:49:32 +02:00
|
|
|
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
|
2024-05-08 20:29:53 +02:00
|
|
|
|
|
|
|
# `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
|