From 7a342f8391d8399783a646ca061d1ad113b35eee Mon Sep 17 00:00:00 2001 From: Philippe PITTOLI Date: Wed, 22 May 2024 05:05:18 +0200 Subject: [PATCH] Removes a lot of duplicated code. --- src/dodb/partition.cr | 176 ++++++++++++------------------------------ src/dodb/tags.cr | 73 ++++++------------ 2 files changed, 72 insertions(+), 177 deletions(-) diff --git a/src/dodb/partition.cr b/src/dodb/partition.cr index 29c8c42..a7352ed 100644 --- a/src/dodb/partition.cr +++ b/src/dodb/partition.cr @@ -96,28 +96,45 @@ class DODB::Partition(V) < DODB::Indexer(V) # WARNING: throws an exception if no value is found. # NOTE: for a safe version, use `#get?`. def get(partition : String) : Array(V) - partition_directory = indexing_directory partition - 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 + get_with_keys(partition).map &.[0] 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. # # ``` - # red_cars = cars_by_color.get? "red" # No red cars = nil. + # red_cars = cars_by_color.get? "red" # ``` def get?(partition : String) : Array(V)? get partition 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 # 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. # ``` + # WARNING: throws a MissingEntry exception on non-existing partition. def delete(partition : String) delete partition, do true end end @@ -137,17 +155,10 @@ class DODB::Partition(V) < DODB::Indexer(V) # car.name == "Corvet" # end # ``` - # TODO: in case the partition is left empty, should the partition's directory be removed? - def delete(partition, &matcher : Proc(V, Bool)) - partition_directory = indexing_directory partition - - return unless Dir.exists? partition_directory - - Dir.each_child partition_directory do |child| - key = get_key child - item = @storage[key] - - if yield item + # WARNING: throws a MissingEntry exception on non-existing partition. + def delete(partition : String, &matcher : Proc(V, Bool)) + get_with_keys(partition).each do |entry, key| + if yield entry @storage.delete key 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. 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. # # NOTE: used for internal operations. @@ -253,6 +258,9 @@ class DODB::CachedPartition(V) < DODB::Partition(V) end # 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. @@ -263,6 +271,7 @@ class DODB::CachedPartition(V) < DODB::Partition(V) # # ] # # 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)) 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 } end else - # 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| - r_value << { @storage[get_key child], get_key child } - end - + # Gets data from the database representation on the file-system. + r_value = super(partition) @data[partition] = r_value.map &.[1] end r_value end - # Gets partition entries from the cache or the file-system representation. - # - # ``` - # # For example, 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 - - # 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) + # Clears the cache and removes the `#indexing_directory`. + def nuke_index + super + data.clear 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. # ``` + # WARNING: FOR CONSISTENCY, throws a MissingEntry exception on non-existing partition. def get_with_keys(partition : String) : 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 + else + raise MissingEntry.new(@name, partition) end r_value 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. def nuke_index data.clear diff --git a/src/dodb/tags.cr b/src/dodb/tags.cr index 86103d8..a80c956 100644 --- a/src/dodb/tags.cr +++ b/src/dodb/tags.cr @@ -64,14 +64,12 @@ class DODB::Tags(V) < DODB::Indexer(V) end def index(key : String, value : V) - indices = key_proc.call(value) - return if indices.is_a? NoIndex + tags = key_proc.call(value) + return if tags.is_a? NoIndex - indices.each do |i| - symlink = get_tagged_entry_path(i, key) + tags.each do |tag| + symlink = get_tagged_entry_path(tag, key) 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 end end @@ -79,11 +77,11 @@ class DODB::Tags(V) < DODB::Indexer(V) # :inherit: # TODO: in case the tag is left empty, should the tag directory be removed? def deindex(key : String, value : V) - indices = key_proc.call(value) - return if indices.is_a? NoIndex + tags = key_proc.call(value) + return if tags.is_a? NoIndex - indices.each do |i| - symlink = get_tagged_entry_path(i, key) + tags.each do |tag| + symlink = get_tagged_entry_path(tag, key) begin ::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. # # ``` - # # Get all slow cars. + # # Gets all slow cars. # cars_by_keywords.get "slow" # # Returns something like: # # [ (@storage[42], 42) @@ -273,11 +271,11 @@ class DODB::CachedTags(V) < DODB::Tags(V) property data = Hash(String, Array(Int32)).new def index(key : String, value : V) - indices = key_proc.call value - return if indices.is_a? NoIndex + tags = key_proc.call value + return if tags.is_a? NoIndex super(key, value) - indices.each do |tag| + tags.each do |tag| array = if v = @data[tag]? v else @@ -292,11 +290,11 @@ class DODB::CachedTags(V) < DODB::Tags(V) # :inherit: # TODO: in case the tag is left empty, should it be removed from the cache? def deindex(key : String, value : V) - indices = key_proc.call value - return if indices.is_a? NoIndex + tags = key_proc.call value + return if tags.is_a? NoIndex super(key, value) - indices.each do |tag| + tags.each do |tag| if v = @data[tag]? v.delete key.to_i @data[tag] = v @@ -320,22 +318,16 @@ class DODB::CachedTags(V) < DODB::Tags(V) def get_with_keys(tag : String) : Array(Tuple(V, Int32)) r_value = Array(Tuple(V, Int32)).new + # In case the tag is cached. 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 - + # Gets data from the database representation on the file-system. + r_value = super(tag) @data[tag] = r_value.map &.[1] end - r_value end @@ -363,10 +355,10 @@ end # NOTE: for an fs representation but still fast for retrieval, see `CachedTags`. class DODB::RAMOnlyTags(V) < DODB::CachedTags(V) def index(key : String, value : V) - indices = key_proc.call value - return if indices.is_a? NoIndex + tags = key_proc.call value + return if tags.is_a? NoIndex - indices.each do |tag| + tags.each do |tag| array = if v = @data[tag]? v else @@ -379,10 +371,10 @@ class DODB::RAMOnlyTags(V) < DODB::CachedTags(V) end def deindex(key : String, value : V) - indices = key_proc.call value - return if indices.is_a? NoIndex + tags = key_proc.call value + return if tags.is_a? NoIndex - indices.each do |tag| + tags.each do |tag| if v = @data[tag]? v.delete key.to_i @data[tag] = v @@ -414,23 +406,6 @@ class DODB::RAMOnlyTags(V) < DODB::CachedTags(V) r_value 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. def nuke_index data.clear