Documentation, again.

This commit is contained in:
Philippe PITTOLI 2024-05-20 20:55:07 +02:00
parent 78d6db2cc4
commit 3c0e1b7608
2 changed files with 127 additions and 14 deletions

View File

@ -3,6 +3,29 @@ require "file_utils"
require "./exceptions.cr"
require "./indexer.cr"
# Basic indexes for 1-to-1 relations.
# Uncached version.
#
# This index provides a file-system representation, enabling the administrators to
# select a value based on its index. The following example presents an index named "id"
# with some data indexed by an UUID attribute.
#
# ```plain
# storage
# ├── data
# │   ├── 0000000000
# │   ├── 0000000001
# │   └── 0000000002
# ├── indices
# │   └── by_id <- this is an example of index named "id"
# │   ├── 6e109b82-25de-4250-9c67-e7e8415ad5a7 -> ../../data/0000000000
# │   ├── 2080131b-97d7-4300-afa9-55b93cdfd124 -> ../../data/0000000001
# │   └── 8b4e83e3-ef95-40dc-a6e5-e6e697ce6323 -> ../../data/0000000002
# ```
#
# NOTE: no cache, thus considered as *slow* for creation, deletion **and retrieval**.
# NOTE: see `CachedIndex` for a cached version, faster for retrieval.
# NOTE: for fast operations without fs representation, see `RAMOnlyIndex`.
class DODB::Index(V) < DODB::Indexer(V)
property name : String
property key_proc : Proc(V, String | NoIndex) | Proc(V, String)
@ -10,11 +33,15 @@ class DODB::Index(V) < DODB::Indexer(V)
@storage : DODB::Storage(V)
# To create an index from a database, use `DODB::Storage#new_index` to create
# a cached index, `DODB::Storage#new_uncached_index` for an uncached index or
# `DODB::Storage#new_RAM_index` for a RAM-only index.
def initialize(@storage, @storage_root, @name, @key_proc)
Dir.mkdir_p indexing_directory
end
def check!(key, value, old_value)
# :inherit:
def check!(key : String, value : V, old_value : V?)
index_key = key_proc.call value
return if index_key.is_a? NoIndex
@ -33,7 +60,8 @@ class DODB::Index(V) < DODB::Indexer(V)
end
end
def index(key, value)
# :inherit:
def index(key : String, value : V)
index_key = key_proc.call value
return if index_key.is_a? NoIndex
@ -44,7 +72,8 @@ class DODB::Index(V) < DODB::Indexer(V)
::File.symlink get_data_symlink_index(key), symlink
end
def deindex(key, value)
# :inherit:
def deindex (key : String, value : V)
index_key = key_proc.call value
return if index_key.is_a? NoIndex
@ -56,23 +85,51 @@ class DODB::Index(V) < DODB::Indexer(V)
end
end
# Get the key (ex: 343) for an entry in the DB.
# Without caching, it translates to walk the file-system in `db/indices/by_#{name}/<index>`.
# Gets the key (ex: 343) for an entry in the DB from an indexed value.
#
# Reads the link in `db/indices/by_#{name}/<index>`.
#
# Useful for internal purposes, to retrieve a value use `#get`.
#
# ```
# internal_database_key_for_the_corvet = cars_by_name.get_key "Corvet"
# ```
#
# NOTE: used for internal operations.
def get_key(index : String) : Int32
get_key_on_fs index
end
# Gets data from an indexed value (throws an exception on a missing entry).
#
# ```
# corvet = cars_by_name.get "Corvet"
# ```
#
# WARNING: throws an exception if the value isn't found.
# NOTE: for a safe version, use `#get?`.
def get(index : String) : V
@storage[get_key index]
end
# Gets data from an indexed value without throwing an exception on a missing entry.
#
# ```
# corvet = cars_by_name.get? "Corvet"
# ```
#
# NOTE: safe version of `#get`, returns a *nil* value in case of a missing entry instead of an exception.
def get?(index : String) : V?
get index
rescue MissingEntry
nil
end
# FIXME: Unlock on exception.
# Gets data from an indexed value (thread-safe via two file locks) and gives it to a provided block of code.
#
# WARNING: should be thread-safe only between other `#safe_get` and `#safe_get?` calls,
# index creations and deletions do not use the same locks!
# NOTE: on exception, releases all locks.
def safe_get(index : String) : Nil
@storage.request_lock @name, index
internal_key = get_key(index).to_s
@ -82,8 +139,20 @@ class DODB::Index(V) < DODB::Indexer(V)
@storage.release_lock internal_key
@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
# Same as `#safe_get` but doesn't throw an exception on a missing value
# (provided block of code receives a *nil* value).
#
# WARNING: should be thread-safe only between other `#safe_get` and `#safe_get?` calls,
# index creations and deletions do not use the same locks!
# NOTE: on exception, releases all locks.
def safe_get?(index : String, &block : Proc(V | Nil, Nil)) : Nil
safe_get index, &block
rescue MissingEntry
@ -151,6 +220,31 @@ class DODB::Index(V) < DODB::Indexer(V)
end
end
# Basic indexes for 1-to-1 relations.
# Cached version.
#
# The cache makes this index fast and since the index doesn't contain
# the full value but just an attribute and a key, memory usage is still reasonable.
#
# A few file-system operations are required on index creation and deletion,
# thus this version still is slow for both these operations.
#
# ```plain
# storage
# ├── data
# │   ├── 0000000000
# │   ├── 0000000001
# │   └── 0000000002
# ├── indices
# │   └── by_id <- this is an example of index named "id"
# │   ├── 6e109b82-25de-4250-9c67-e7e8415ad5a7 -> ../../data/0000000000
# │   ├── 2080131b-97d7-4300-afa9-55b93cdfd124 -> ../../data/0000000001
# │   └── 8b4e83e3-ef95-40dc-a6e5-e6e697ce6323 -> ../../data/0000000002
# ```
#
# NOTE: cached, reasonable amount of memory used since it's just an index.
# NOTE: fast for retrieval, slow for index creation and deletion (fs operations).
# NOTE: for fast operations without fs representation, see `RAMOnlyIndex`.
class DODB::CachedIndex(V) < DODB::Index(V)
# This hash contains the relation between the index key and the data key.
property data = Hash(String, Int32).new
@ -206,9 +300,16 @@ class DODB::CachedIndex(V) < DODB::Index(V)
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.
# Basic indexes for 1-to-1 relations.
# RAM-only version, no file-system representation.
#
# Since there is no file-system operations, all the operations are fast.
# `DODB::RAMOnlyIndex` enables the flexibility of indexes without a file-system representation
# for absolute efficiency.
# Exactly as easy to use as the other index implementations.
#
# NOTE: reasonable amount of memory used since it's just an index.
# NOTE: fast for all operations, but no file-system representation.
class DODB::RAMOnlyIndex(V) < DODB::CachedIndex(V)
def index(key, value)
index_key = key_proc.call value

View File

@ -2,24 +2,36 @@
# the indexes (basic indexes, partitions, tags, etc.).
abstract class DODB::Indexer(V)
# Indexes a value.
# Indexes a value, used for **internal operations**.
#
# NOTE: used for internal operations.
abstract def index (key : String, value : V)
# Removes the index of a value.
# Removes the index of a value, used for **internal operations**.
#
# NOTE: used for internal operations.
abstract def deindex (key : String, value : V)
# Verifies whether a new value will create a collision with the index of currently stored value.
# Verifies whether a new value will create a collision with the index of
# currently stored value, used for **internal operations**.
#
# NOTE: used for internal operations.
abstract def check! (key : String, value : V, old_value : V?)
# Name of the index, such as *id* or *color* for example.
# This is an arbitrary value, mostly to create the index directory.
#
# NOTE: used for internal operations.
abstract def name : String
# Directory where the values will be written.
#
# NOTE: used for internal operations.
abstract def indexing_directory : String
# Removes all the index entries.
# By default, removes the `#indexing_directory`.
# Removes all the index entries, removes the `#indexing_directory` by default.
#
# NOTE: used for internal operations.
def nuke_index
FileUtils.rm_rf indexing_directory
end