diff --git a/src/dodb/index.cr b/src/dodb/index.cr index abf5eaf..239bee4 100644 --- a/src/dodb/index.cr +++ b/src/dodb/index.cr @@ -40,7 +40,6 @@ class DODB::Index(V) < DODB::Indexer(V) Dir.mkdir_p indexing_directory end - # :inherit: def check!(key : String, value : V, old_value : V?) index_key = key_proc.call value @@ -135,16 +134,17 @@ class DODB::Index(V) < DODB::Indexer(V) internal_key = get_key(index).to_s @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 @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 @@ -159,21 +159,35 @@ class DODB::Index(V) < DODB::Indexer(V) yield nil 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 file_path = file_path_index index raise MissingEntry.new(@name, index) unless ::File.symlink? file_path ::File.readlink(file_path).sub(/^.*\//, "").to_i end - def get_with_key(index : String) : Tuple(V, Int32) - key = get_key index - - value = @storage[key] - - {value, key} - end - - # in case new_value hasn't changed its index + # Updates a value based on its indexed attribute (which must not have changed). + # + # ``` + # # Update the car "corvet" in the database. + # car_by_name.update corvet + # ``` + # WARNING: in case the indexed attribute has changed, use `#update(index, value)`. def update(new_value : V) index = key_proc.call new_value @@ -182,30 +196,59 @@ class DODB::Index(V) < DODB::Indexer(V) update index, new_value 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) key = get_key index @storage[key] = new_value 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) update new_value rescue MissingEntry @storage << new_value 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) update index, new_value rescue MissingEntry @storage << new_value end + # Deletes a value based on its index. + # + # ``` + # # Deletes the car named "Corvet". + # car_by_name.delete "Corvet" + # ``` def delete(index : String) key = get_key index @storage.delete key end + # :inherit: def indexing_directory : String "#{@storage_root}/indices/by_#{@name}" end @@ -215,6 +258,7 @@ class DODB::Index(V) < DODB::Indexer(V) "#{indexing_directory}/#{index_key}" end + # Creates the relative path to the data from the indexing directory. private def get_data_symlink_index(key : String) "../../data/#{key}" end @@ -247,9 +291,11 @@ end # 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. + # + # WARNING: used for internal operations, do not change its content or access it directly. 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 return if index_key.is_a? NoIndex @@ -265,11 +311,16 @@ class DODB::CachedIndex(V) < DODB::Index(V) end end + # Clears the cache. + # :inherit: def nuke_index super data.clear 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) index_key = key_proc.call value return if index_key.is_a? NoIndex @@ -278,6 +329,9 @@ class DODB::CachedIndex(V) < DODB::Index(V) @data[index_key] = key.to_i 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) index_key = key_proc.call value return if index_key.is_a? NoIndex @@ -286,8 +340,10 @@ class DODB::CachedIndex(V) < DODB::Index(V) @data.delete index_key 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. + # + # NOTE: used for internal operations. def get_key(index : String) : Int32 if k = @data[index]? k @@ -311,20 +367,28 @@ end # 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) + # Indexes a value in RAM, no file-system operation. + # + # NOTE: used for internal operations. def index(key, value) index_key = key_proc.call value return if index_key.is_a? NoIndex @data[index_key] = key.to_i end + # Removes the index of a value in RAM, no file-system operation. + # + # NOTE: used for internal operations. 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. + # Gets the key (ex: 343) for an entry in the DB. + # With a RAM-only index, the key is necessarily stored in the hash. + # + # NOTE: used for internal operations. def get_key(index : String) : Int32 if k = @data[index]? k