Documentation for basic indexes.

This commit is contained in:
Philippe PITTOLI 2024-05-21 01:59:39 +02:00
parent 3c0e1b7608
commit be1ec6a1db

View File

@ -40,7 +40,6 @@ class DODB::Index(V) < DODB::Indexer(V)
Dir.mkdir_p indexing_directory Dir.mkdir_p indexing_directory
end end
# :inherit:
def check!(key : String, value : V, old_value : V?) def check!(key : String, value : V, old_value : V?)
index_key = key_proc.call value index_key = key_proc.call value
@ -135,16 +134,17 @@ class DODB::Index(V) < DODB::Indexer(V)
internal_key = get_key(index).to_s internal_key = get_key(index).to_s
@storage.request_lock internal_key @storage.request_lock internal_key
yield get index begin
yield get index
rescue e
# On exception, returns the exception after releasing locks.
@storage.release_lock internal_key
@storage.release_lock @name, index
raise e
end
@storage.release_lock internal_key @storage.release_lock internal_key
@storage.release_lock @name, index @storage.release_lock @name, index
rescue e
# On exception, returns the exception after releasing locks.
@storage.release_lock internal_key
@storage.release_lock @name, index
raise e
end end
# Same as `#safe_get` but doesn't throw an exception on a missing value # Same as `#safe_get` but doesn't throw an exception on a missing value
@ -159,21 +159,35 @@ class DODB::Index(V) < DODB::Indexer(V)
yield nil yield nil
end end
# Reads the indexed symlink to find its related key.
#
# For example, for a car indexed by its name:
#
# ```
# storage
# ├── data
# │   └── 0000000343
# └── indices
#    └── by_name
#    └── Corvet -> ../../data/0000000343
# ```
#
# `#get_key_on_fs` reads the *storage/indices/by_name/Corvet* symlink and gets
# the name of the data file ("000000343") and converts it in an integer,
# which is the key in the database.
def get_key_on_fs(index : String) : Int32 def get_key_on_fs(index : String) : Int32
file_path = file_path_index index file_path = file_path_index index
raise MissingEntry.new(@name, index) unless ::File.symlink? file_path raise MissingEntry.new(@name, index) unless ::File.symlink? file_path
::File.readlink(file_path).sub(/^.*\//, "").to_i ::File.readlink(file_path).sub(/^.*\//, "").to_i
end end
def get_with_key(index : String) : Tuple(V, Int32) # Updates a value based on its indexed attribute (which must not have changed).
key = get_key index #
# ```
value = @storage[key] # # Update the car "corvet" in the database.
# car_by_name.update corvet
{value, key} # ```
end # WARNING: in case the indexed attribute has changed, use `#update(index, value)`.
# in case new_value hasn't changed its index
def update(new_value : V) def update(new_value : V)
index = key_proc.call new_value index = key_proc.call new_value
@ -182,30 +196,59 @@ class DODB::Index(V) < DODB::Indexer(V)
update index, new_value update index, new_value
end end
# Updates a value based on its indexed attribute (which may have changed).
#
# ```
# # Update the car "corvet" in the database.
# car_by_name.update "Corvet", corvet
# ```
# NOTE: in case the indexed attribute hasn't changed, you may prefer `#update(value)`.
def update(index : String, new_value : V) def update(index : String, new_value : V)
key = get_key index key = get_key index
@storage[key] = new_value @storage[key] = new_value
end end
# Updates a value. Creates it if necessary.
#
# ```
# # Update or create the car "corvet" in the database.
# car_by_name.update_or_create corvet
# ```
# WARNING: use `#update_or_create(index, value)` if the indexed value may have changed.
def update_or_create(new_value : V) def update_or_create(new_value : V)
update new_value update new_value
rescue MissingEntry rescue MissingEntry
@storage << new_value @storage << new_value
end end
# Same as `#update_or_create(value)` but handles changed indexes.
#
# ```
# # Update or create the car named "Corvet" in the database.
# # Its name may have changed in the object "corvet".
# car_by_name.update_or_create "Corvet", corvet
# ```
# NOTE: safe version in case the index has changed.
def update_or_create(index : String, new_value : V) def update_or_create(index : String, new_value : V)
update index, new_value update index, new_value
rescue MissingEntry rescue MissingEntry
@storage << new_value @storage << new_value
end end
# Deletes a value based on its index.
#
# ```
# # Deletes the car named "Corvet".
# car_by_name.delete "Corvet"
# ```
def delete(index : String) def delete(index : String)
key = get_key index key = get_key index
@storage.delete key @storage.delete key
end end
# :inherit:
def indexing_directory : String def indexing_directory : String
"#{@storage_root}/indices/by_#{@name}" "#{@storage_root}/indices/by_#{@name}"
end end
@ -215,6 +258,7 @@ class DODB::Index(V) < DODB::Indexer(V)
"#{indexing_directory}/#{index_key}" "#{indexing_directory}/#{index_key}"
end end
# Creates the relative path to the data from the indexing directory.
private def get_data_symlink_index(key : String) private def get_data_symlink_index(key : String)
"../../data/#{key}" "../../data/#{key}"
end end
@ -247,9 +291,11 @@ end
# NOTE: for fast operations without fs representation, see `RAMOnlyIndex`. # NOTE: for fast operations without fs representation, see `RAMOnlyIndex`.
class DODB::CachedIndex(V) < DODB::Index(V) class DODB::CachedIndex(V) < DODB::Index(V)
# This hash contains the relation between the index key and the data key. # This hash contains the relation between the index key and the data key.
#
# WARNING: used for internal operations, do not change its content or access it directly.
property data = Hash(String, Int32).new property data = Hash(String, Int32).new
def check!(key, value, old_value) def check!(key : String, value : V, old_value : V?)
index_key = key_proc.call value index_key = key_proc.call value
return if index_key.is_a? NoIndex return if index_key.is_a? NoIndex
@ -265,11 +311,16 @@ class DODB::CachedIndex(V) < DODB::Index(V)
end end
end end
# Clears the cache.
# :inherit:
def nuke_index def nuke_index
super super
data.clear data.clear
end end
# Indexes the value on the file-system as `DODB::Index#index` but also puts the index in a cache.
#
# NOTE: used for internal operations.
def index(key, value) def index(key, value)
index_key = key_proc.call value index_key = key_proc.call value
return if index_key.is_a? NoIndex return if index_key.is_a? NoIndex
@ -278,6 +329,9 @@ class DODB::CachedIndex(V) < DODB::Index(V)
@data[index_key] = key.to_i @data[index_key] = key.to_i
end end
# Removes the index of a value on the file-system as `DODB::Index#deindex` but also from the cache.
#
# NOTE: used for internal operations.
def deindex(key, value) def deindex(key, value)
index_key = key_proc.call value index_key = key_proc.call value
return if index_key.is_a? NoIndex return if index_key.is_a? NoIndex
@ -286,8 +340,10 @@ class DODB::CachedIndex(V) < DODB::Index(V)
@data.delete index_key @data.delete index_key
end end
# Get the key (ex: 343) for an entry in the DB. # Gets the key (ex: 343) for an entry in the DB.
# With caching, the key is probably stored in a hash, or we'll search in the FS. # With caching, the key is probably stored in a hash, or we'll search in the FS.
#
# NOTE: used for internal operations.
def get_key(index : String) : Int32 def get_key(index : String) : Int32
if k = @data[index]? if k = @data[index]?
k k
@ -311,20 +367,28 @@ end
# NOTE: reasonable amount of memory used since it's just an index. # NOTE: reasonable amount of memory used since it's just an index.
# NOTE: fast for all operations, but no file-system representation. # NOTE: fast for all operations, but no file-system representation.
class DODB::RAMOnlyIndex(V) < DODB::CachedIndex(V) class DODB::RAMOnlyIndex(V) < DODB::CachedIndex(V)
# Indexes a value in RAM, no file-system operation.
#
# NOTE: used for internal operations.
def index(key, value) def index(key, value)
index_key = key_proc.call value index_key = key_proc.call value
return if index_key.is_a? NoIndex return if index_key.is_a? NoIndex
@data[index_key] = key.to_i @data[index_key] = key.to_i
end end
# Removes the index of a value in RAM, no file-system operation.
#
# NOTE: used for internal operations.
def deindex(key, value) def deindex(key, value)
index_key = key_proc.call value index_key = key_proc.call value
return if index_key.is_a? NoIndex return if index_key.is_a? NoIndex
@data.delete index_key @data.delete index_key
end end
# Get the key (ex: 343) for an entry in the DB. # Gets the key (ex: 343) for an entry in the DB.
# With a RAM only index, the key is necessarily stored in the hash. # With a RAM-only index, the key is necessarily stored in the hash.
#
# NOTE: used for internal operations.
def get_key(index : String) : Int32 def get_key(index : String) : Int32
if k = @data[index]? if k = @data[index]?
k k