TODO: RAMOnlyTags + tests tests tests.

This commit is contained in:
Philippe PITTOLI 2024-05-08 20:29:53 +02:00
parent 401578c77d
commit 9b897ac62b
5 changed files with 221 additions and 6 deletions

View File

@ -30,9 +30,9 @@ class DODB::CachedDataBase(V) < DODB::Storage(V)
self.last_index = -1 self.last_index = -1
end end
# TODO: load the database in RAM at start-up # Load the database in RAM at start-up.
DODB::DataBase(V).new(@directory_name).each_with_index do |v, index| DODB::DataBase(V).new(@directory_name).each_with_index do |v, index|
puts "loading value #{v} at index #{index}" puts "\rloading data from #{@directory_name} at index #{index}"
self[index] = v self[index] = v
end end
end end
@ -98,8 +98,7 @@ class DODB::CachedDataBase(V) < DODB::Storage(V)
begin begin
::File.delete file_path key ::File.delete file_path key
rescue rescue File::NotFoundError
# FIXME: Only intercept “no such file" errors
end end
remove_indexes key, value remove_indexes key, value
@ -113,3 +112,56 @@ class DODB::CachedDataBase(V) < DODB::Storage(V)
@data = Hash(Int32, V).new @data = Hash(Int32, V).new
end end
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

View File

@ -135,6 +135,18 @@ abstract class DODB::Storage(V)
end end
end end
def new_RAM_index(name : String, &block : Proc(V, String))
RAMOnlyIndex(V).new(self, @directory_name, name, block).tap do |indexer|
@indexers << indexer
end
end
def new_nilable_RAM_index(name : String, &block : Proc(V, String | DODB::NoIndex))
RAMOnlyIndex(V).new(self, @directory_name, name, block).tap do |indexer|
@indexers << indexer
end
end
def get_index(name : String, key) def get_index(name : String, key)
index = @indexers.find &.name.==(name) index = @indexers.find &.name.==(name)
@ -155,6 +167,12 @@ abstract class DODB::Storage(V)
end end
end end
def new_RAM_partition(name : String, &block : Proc(V, String))
RAMOnlyPartition(V).new(self, @directory_name, name, block).tap do |table|
@indexers << table
end
end
def get_partition(table_name : String, partition_name : String) def get_partition(table_name : String, partition_name : String)
partition = @indexers.find &.name.==(table_name) partition = @indexers.find &.name.==(table_name)
@ -177,6 +195,12 @@ abstract class DODB::Storage(V)
end end
end end
def new_RAM_tags(name : String, &block : Proc(V, Array(String)))
RAMOnlyTags(V).new(self, @directory_name, name, block).tap do |tags|
@indexers << tags
end
end
def get_tags(name, key : String) def get_tags(name, key : String)
tag = @indexers.find &.name.==(name) tag = @indexers.find &.name.==(name)

View File

@ -200,3 +200,30 @@ class DODB::CachedIndex(V) < DODB::Index(V)
end end
end end
end end
# `DODB::RAMOnlyIndex` enables the flexibility of indexes without a file-system representation.
# Absolute efficiency, exactly as easy to use as the other index implementations.
class DODB::RAMOnlyIndex(V) < DODB::CachedIndex(V)
def index(key, value)
index_key = key_proc.call value
return if index_key.is_a? NoIndex
@data[index_key] = key.to_i
end
def deindex(key, value)
index_key = key_proc.call value
return if index_key.is_a? NoIndex
@data.delete index_key
end
# Get the key (ex: 343) for an entry in the DB.
# With a RAM only index, the key is necessarily stored in the hash.
def get_key(index : String) : Int32
if k = @data[index]?
k
else
raise MissingEntry.new(@name, index)
end
end
end

View File

@ -130,7 +130,7 @@ class DODB::CachedPartition(V) < DODB::Partition(V)
end end
end end
def get(partition) def get_with_indexes(partition) : Array(Tuple(V, Int32))
r_value = Array(Tuple(V, Int32)).new r_value = Array(Tuple(V, Int32)).new
if keys = @data[partition]? if keys = @data[partition]?
@ -148,6 +148,66 @@ class DODB::CachedPartition(V) < DODB::Partition(V)
@data[partition] = r_value.map &.[1] @data[partition] = r_value.map &.[1]
end end
r_value.map &.[0] r_value
end
def get(partition) : Array(V)
get_with_indexes(partition).map &.[0]
end
def delete(partition, &matcher)
# Use `get_with_indexes` to retrieve data on-disk, if necessary.
new_partition = get_with_indexes(partition).map(&.[1]).select do |key|
item = @storage[key]
! yield item
end
@data[partition] = new_partition
super(partition, matcher)
end
end
class DODB::RAMOnlyPartition(V) < DODB::CachedPartition(V)
def index(key, value)
partition = key_proc.call value
array = if v = @data[partition]?
v
else
Array(Int32).new
end
array << key.to_i
@data[partition] = array
end
def deindex(key, value)
partition = key_proc.call value
if v = @data[partition]?
v.delete key.to_i
@data[partition] = v
end
end
def get_with_indexes(partition) : Array(Tuple(V, Int32))
r_value = Array(Tuple(V, Int32)).new
if keys = @data[partition]?
keys.each do |data_key|
r_value << { @storage[data_key], data_key }
end
end
r_value
end
def delete(partition, &matcher)
new_partition = @data[partition].select do |key|
item = @storage[key]
! yield item
end
@data[partition] = new_partition
end end
end end

View File

@ -172,3 +172,55 @@ class DODB::CachedTags(V) < DODB::Tags(V)
r_value r_value
end end
end end
# TODO
class DODB::RAMOnlyTags(V) < DODB::CachedTags(V)
def index(key, value)
indices = key_proc.call value
indices.each do |tag|
array = if v = @data[tag]?
v
else
Array(Int32).new
end
array << key.to_i
@data[tag] = array
end
end
def deindex(key, value)
super(key, value)
indices = key_proc.call value
indices.each do |tag|
if v = @data[tag]?
v.delete key.to_i
@data[tag] = v
end
end
end
def get_with_indice(tag : String) : Array(Tuple(V, Int32))
r_value = Array(Tuple(V, Int32)).new
if keys = @data[tag]?
keys.each do |data_key|
r_value << { @storage[data_key], data_key }
end
else
# Get the key from the database representation on the file-system.
tag_directory = indexing_directory tag
raise MissingEntry.new(@name, tag) unless Dir.exists? tag_directory
Dir.each_child tag_directory do |child|
r_value << { @storage[get_key child], get_key child }
end
@data[tag] = r_value.map &.[1]
end
r_value
end
end