Removes a lot of duplicated code.

This commit is contained in:
Philippe PITTOLI 2024-05-22 05:05:18 +02:00
parent 2b24fbc8a0
commit 7a342f8391
2 changed files with 72 additions and 177 deletions

View File

@ -96,28 +96,45 @@ class DODB::Partition(V) < DODB::Indexer(V)
# WARNING: throws an exception if no value is found. # WARNING: throws an exception if no value is found.
# NOTE: for a safe version, use `#get?`. # NOTE: for a safe version, use `#get?`.
def get(partition : String) : Array(V) def get(partition : String) : Array(V)
partition_directory = indexing_directory partition get_with_keys(partition).map &.[0]
raise MissingEntry.new(@name, partition) unless Dir.exists? partition_directory
r_value = Array(V).new
Dir.each_child partition_directory do |child|
r_value << @storage[get_key child]
end
r_value
end end
# Safe version of `#get`, gets data and returns a *nil* value in case of # Safe version of `#get`, gets data and returns *an empty array* in case of
# a missing entry instead of an exception. # a missing entry instead of an exception.
# #
# ``` # ```
# red_cars = cars_by_color.get? "red" # No red cars = nil. # red_cars = cars_by_color.get? "red"
# ``` # ```
def get?(partition : String) : Array(V)? def get?(partition : String) : Array(V)?
get partition get partition
rescue MissingEntry rescue MissingEntry
nil Array(V).new
end
# Gets partition entries (and their keys) from the file-system representation.
#
# ```
# # Gets all red cars.
# cars_by_color.get "red"
# # Returns something like:
# # [ (@storage[42], 42)
# # , (@storage[91], 91)
# # ]
# # Each tuple is composed of a car and its key in the database.
# ```
# WARNING: throws a MissingEntry exception on non-existing partition.
def get_with_keys(partition : String) : Array(Tuple(V, Int32))
partition_directory = indexing_directory partition
raise MissingEntry.new(@name, partition) unless Dir.exists? partition_directory
r_value = Array(Tuple(V, Int32)).new
Dir.each_child partition_directory do |child|
key = get_key child
r_value << { @storage[key], key }
end
r_value
end end
# Deletes all entries within the provided partition. # Deletes all entries within the provided partition.
@ -125,6 +142,7 @@ class DODB::Partition(V) < DODB::Indexer(V)
# ``` # ```
# cars_by_color.delete "red" # Deletes all red cars. # cars_by_color.delete "red" # Deletes all red cars.
# ``` # ```
# WARNING: throws a MissingEntry exception on non-existing partition.
def delete(partition : String) def delete(partition : String)
delete partition, do true end delete partition, do true end
end end
@ -137,17 +155,10 @@ class DODB::Partition(V) < DODB::Indexer(V)
# car.name == "Corvet" # car.name == "Corvet"
# end # end
# ``` # ```
# TODO: in case the partition is left empty, should the partition's directory be removed? # WARNING: throws a MissingEntry exception on non-existing partition.
def delete(partition, &matcher : Proc(V, Bool)) def delete(partition : String, &matcher : Proc(V, Bool))
partition_directory = indexing_directory partition get_with_keys(partition).each do |entry, key|
if yield entry
return unless Dir.exists? partition_directory
Dir.each_child partition_directory do |child|
key = get_key child
item = @storage[key]
if yield item
@storage.delete key @storage.delete key
end end
end end
@ -213,12 +224,6 @@ class DODB::CachedPartition(V) < DODB::Partition(V)
# WARNING: used for internal operations, do not change its content or access it directly. # WARNING: used for internal operations, do not change its content or access it directly.
property data = Hash(String, Array(Int32)).new property data = Hash(String, Array(Int32)).new
# Clears the cache and removes the `#indexing_directory`.
def nuke_index
super
data.clear
end
# Indexes the value on the file-system as `DODB::Partition#index` but also puts the index in a cache. # Indexes the value on the file-system as `DODB::Partition#index` but also puts the index in a cache.
# #
# NOTE: used for internal operations. # NOTE: used for internal operations.
@ -253,6 +258,9 @@ class DODB::CachedPartition(V) < DODB::Partition(V)
end end
# Gets partition entries and the database key for each entry. # Gets partition entries and the database key for each entry.
# In `DODB::CachedPartition`, `#get_with_keys(partition : String)` is modified to retrieve data keys from
# the index cache.
# In case the data isn't already in the cache, it is retrieved from the file-system.
# #
# ``` # ```
# # For example, get all red cars. # # For example, get all red cars.
@ -263,6 +271,7 @@ class DODB::CachedPartition(V) < DODB::Partition(V)
# # ] # # ]
# # Each tuple is composed of a car and its key in the database. # # Each tuple is composed of a car and its key in the database.
# ``` # ```
# WARNING: throws a MissingEntry exception on non-existing partition.
def get_with_keys(partition : String) : Array(Tuple(V, Int32)) def get_with_keys(partition : String) : Array(Tuple(V, Int32))
r_value = Array(Tuple(V, Int32)).new r_value = Array(Tuple(V, Int32)).new
@ -272,73 +281,17 @@ class DODB::CachedPartition(V) < DODB::Partition(V)
r_value << { @storage[data_key], data_key } r_value << { @storage[data_key], data_key }
end end
else else
# Get the key from the database representation on the file-system. # Gets data from the database representation on the file-system.
partition_directory = indexing_directory partition r_value = super(partition)
raise MissingEntry.new(@name, partition) unless Dir.exists? partition_directory
Dir.each_child partition_directory do |child|
r_value << { @storage[get_key child], get_key child }
end
@data[partition] = r_value.map &.[1] @data[partition] = r_value.map &.[1]
end end
r_value r_value
end end
# Gets partition entries from the cache or the file-system representation. # Clears the cache and removes the `#indexing_directory`.
# def nuke_index
# ``` super
# # For example, get all red cars. data.clear
# cars_by_color.get "red"
# ```
# NOTE: returns an empty list on empty or non-existing partition.
def get(partition : String) : Array(V)
r_value = Array(V).new
# In case the partition is cached.
if keys = @data[partition]?
keys.each do |data_key|
r_value << @storage[data_key]
end
else
# The keys to put in the partition.
p_value = Array(Int32).new
# Get the key from the database representation on the file-system.
partition_directory = indexing_directory partition
raise MissingEntry.new(@name, partition) unless Dir.exists? partition_directory
Dir.each_child partition_directory do |child|
key = get_key child
r_value << @storage[key]
p_value << key
end
@data[partition] = p_value
end
r_value
end
# Deletes entries within the provided partition and matching the provided block of code,
# both from the file-system representation and from the cache.
#
# ```
# # Deletes all red Corvets.
# cars_by_color.delete "red", do |car|
# car.name == "Corvet"
# end
# ```
# TODO: in case the partition is left empty, should the partition be removed from the cache?
def delete(partition : String, &matcher : Proc(V, Bool))
# Use `get_with_keys` to retrieve data on-disk, if necessary.
new_partition = get_with_keys(partition).map(&.[1]).select do |key|
item = @storage[key]
! yield item
end
@data[partition] = new_partition
super(partition, &matcher)
end end
end end
@ -393,52 +346,19 @@ class DODB::RAMOnlyPartition(V) < DODB::CachedPartition(V)
# # ] # # ]
# # Each tuple is composed of a car and its key in the database. # # Each tuple is composed of a car and its key in the database.
# ``` # ```
# WARNING: FOR CONSISTENCY, throws a MissingEntry exception on non-existing partition.
def get_with_keys(partition : String) : Array(Tuple(V, Int32)) def get_with_keys(partition : String) : 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]?
keys.each do |data_key| keys.each do |data_key|
r_value << { @storage[data_key], data_key } r_value << { @storage[data_key], data_key }
end end
else
raise MissingEntry.new(@name, partition)
end end
r_value r_value
end end
# Gets partition entries from the in-memory partition cache.
#
# ```
# # Get all red cars.
# cars_by_color.get "red"
# ```
# NOTE: returns an empty list on empty or non-existing partition.
def get(partition : String) : Array(V)
r_value = Array(V).new
if keys = @data[partition]?
keys.each do |data_key|
r_value << @storage[data_key]
end
end
r_value
end
# Deletes entries within the provided partition and matching the provided block of code.
#
# ```
# # Deletes all red Corvets.
# cars_by_color.delete "red", do |car|
# car.name == "Corvet"
# end
# ```
# TODO: in case the partition is left empty, should it be removed from the cache?
def delete(partition : String, &matcher : Proc(V, Bool))
if keys = @data[partition]?
new_partition = keys.select do |key|
item = @storage[key]
! yield item
end
@data[partition] = new_partition
end
end
# Clears the cache. # Clears the cache.
def nuke_index def nuke_index
data.clear data.clear

View File

@ -64,14 +64,12 @@ class DODB::Tags(V) < DODB::Indexer(V)
end end
def index(key : String, value : V) def index(key : String, value : V)
indices = key_proc.call(value) tags = key_proc.call(value)
return if indices.is_a? NoIndex return if tags.is_a? NoIndex
indices.each do |i| tags.each do |tag|
symlink = get_tagged_entry_path(i, key) symlink = get_tagged_entry_path(tag, key)
Dir.mkdir_p ::File.dirname symlink Dir.mkdir_p ::File.dirname symlink
# FIXME: Should not happen anymore. Should we remove this?
::File.delete symlink if ::File.exists? symlink
::File.symlink get_data_symlink(key), symlink ::File.symlink get_data_symlink(key), symlink
end end
end end
@ -79,11 +77,11 @@ class DODB::Tags(V) < DODB::Indexer(V)
# :inherit: # :inherit:
# TODO: in case the tag is left empty, should the tag directory be removed? # TODO: in case the tag is left empty, should the tag directory be removed?
def deindex(key : String, value : V) def deindex(key : String, value : V)
indices = key_proc.call(value) tags = key_proc.call(value)
return if indices.is_a? NoIndex return if tags.is_a? NoIndex
indices.each do |i| tags.each do |tag|
symlink = get_tagged_entry_path(i, key) symlink = get_tagged_entry_path(tag, key)
begin begin
::File.delete symlink ::File.delete symlink
@ -95,7 +93,7 @@ class DODB::Tags(V) < DODB::Indexer(V)
# Gets tag entries (and their keys) from the file-system representation of the tag. # Gets tag entries (and their keys) from the file-system representation of the tag.
# #
# ``` # ```
# # Get all slow cars. # # Gets all slow cars.
# cars_by_keywords.get "slow" # cars_by_keywords.get "slow"
# # Returns something like: # # Returns something like:
# # [ (@storage[42], 42) # # [ (@storage[42], 42)
@ -273,11 +271,11 @@ class DODB::CachedTags(V) < DODB::Tags(V)
property data = Hash(String, Array(Int32)).new property data = Hash(String, Array(Int32)).new
def index(key : String, value : V) def index(key : String, value : V)
indices = key_proc.call value tags = key_proc.call value
return if indices.is_a? NoIndex return if tags.is_a? NoIndex
super(key, value) super(key, value)
indices.each do |tag| tags.each do |tag|
array = if v = @data[tag]? array = if v = @data[tag]?
v v
else else
@ -292,11 +290,11 @@ class DODB::CachedTags(V) < DODB::Tags(V)
# :inherit: # :inherit:
# TODO: in case the tag is left empty, should it be removed from the cache? # TODO: in case the tag is left empty, should it be removed from the cache?
def deindex(key : String, value : V) def deindex(key : String, value : V)
indices = key_proc.call value tags = key_proc.call value
return if indices.is_a? NoIndex return if tags.is_a? NoIndex
super(key, value) super(key, value)
indices.each do |tag| tags.each do |tag|
if v = @data[tag]? if v = @data[tag]?
v.delete key.to_i v.delete key.to_i
@data[tag] = v @data[tag] = v
@ -320,22 +318,16 @@ class DODB::CachedTags(V) < DODB::Tags(V)
def get_with_keys(tag : String) : Array(Tuple(V, Int32)) def get_with_keys(tag : String) : Array(Tuple(V, Int32))
r_value = Array(Tuple(V, Int32)).new r_value = Array(Tuple(V, Int32)).new
# In case the tag is cached.
if keys = @data[tag]? if keys = @data[tag]?
keys.each do |data_key| keys.each do |data_key|
r_value << { @storage[data_key], data_key } r_value << { @storage[data_key], data_key }
end end
else else
# Get the key from the database representation on the file-system. # Gets data from the database representation on the file-system.
tag_directory = indexing_directory tag r_value = super(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] @data[tag] = r_value.map &.[1]
end end
r_value r_value
end end
@ -363,10 +355,10 @@ end
# NOTE: for an fs representation but still fast for retrieval, see `CachedTags`. # NOTE: for an fs representation but still fast for retrieval, see `CachedTags`.
class DODB::RAMOnlyTags(V) < DODB::CachedTags(V) class DODB::RAMOnlyTags(V) < DODB::CachedTags(V)
def index(key : String, value : V) def index(key : String, value : V)
indices = key_proc.call value tags = key_proc.call value
return if indices.is_a? NoIndex return if tags.is_a? NoIndex
indices.each do |tag| tags.each do |tag|
array = if v = @data[tag]? array = if v = @data[tag]?
v v
else else
@ -379,10 +371,10 @@ class DODB::RAMOnlyTags(V) < DODB::CachedTags(V)
end end
def deindex(key : String, value : V) def deindex(key : String, value : V)
indices = key_proc.call value tags = key_proc.call value
return if indices.is_a? NoIndex return if tags.is_a? NoIndex
indices.each do |tag| tags.each do |tag|
if v = @data[tag]? if v = @data[tag]?
v.delete key.to_i v.delete key.to_i
@data[tag] = v @data[tag] = v
@ -414,23 +406,6 @@ class DODB::RAMOnlyTags(V) < DODB::CachedTags(V)
r_value r_value
end end
# Gets tag entries from the in-memory tag cache.
#
# ```
# # Get all slow cars.
# cars_by_keywords.get "slow"
# ```
# NOTE: returns an empty list on empty or non-existing tag.
def get(tag : String) : Array(V)
r_value = Array(V).new
if keys = @data[tag]?
keys.each do |data_key|
r_value << @storage[data_key]
end
end
r_value
end
# Clears the cache. # Clears the cache.
def nuke_index def nuke_index
data.clear data.clear