diff --git a/src/fs.cr b/src/fs.cr index 23f4df7..7c549ac 100644 --- a/src/fs.cr +++ b/src/fs.cr @@ -104,9 +104,13 @@ class FS::Hash(K, V) end def []=(key : K, value : V) + old_value = self.[key]? + + check_collisions! key, value, old_value + # Removes any old indices or partitions pointing to a value about # to be replaced. - self.[key]?.try do |old_value| + if old_value remove_partitions key, old_value end @@ -119,16 +123,36 @@ class FS::Hash(K, V) write_partitions key, value end + def check_collisions!(key : K, value : V, old_value : V?) + @partitions.each do |index| + case index + when IndexData + index_key = index.key_proc.call value + + symlink = file_path_indexes(index_key.to_s, index.name) + # FIXME: Check it’s not pointing to “old_value”, if any. + + pp! symlink + + if ::File.exists? symlink + raise IndexOverload.new "Index '#{index.name}' is overloaded for key '#{key}'" + end + end + end + end + def write_partitions(key : K, value : V) @partitions.each do |index| index_key = index.key_proc.call value case index when IndexData - symlink = file_path_indexes(key.to_s, index.name) + symlink = file_path_indexes(index_key, index.name) Dir.mkdir_p ::File.dirname symlink + # FIXME: A check_collisions! is done a bit higher. Is this + # still required? if ::File.exists? symlink raise Exception.new "symlink already exists: #{symlink}" end @@ -183,7 +207,7 @@ class FS::Hash(K, V) case index when IndexData - symlink = file_path_indexes(key.to_s, index.name) + symlink = file_path_indexes(index_key, index.name) ::File.delete symlink when PartitionData @@ -260,8 +284,8 @@ class FS::Hash(K, V) "#{@directory_name}/tags/by_#{name}" end - private def file_path_indexes(key : String, index_name : String) - "#{dir_path_indexes index_name}/#{key}.json" + private def file_path_indexes(index_key : String, index_name : String) + "#{dir_path_indexes index_name}/#{index_key}.json" end private def file_path_partition(key : String, index_name : String, index_key : String) @@ -289,3 +313,6 @@ class FS::Hash(K, V) end end +class FS::IndexOverload < Exception +end + diff --git a/test-index.cr b/test-index.cr new file mode 100644 index 0000000..3d14723 --- /dev/null +++ b/test-index.cr @@ -0,0 +1,44 @@ +require "json" +require "./src/fs.cr" +require "uuid" + +# This test basically works if no data is obtained when fetching "broken" +# partitions/indices/tags. + +class Ship + JSON.mapping({ + id: String, + class: String, + name: String, + tags: Array(String) + }) + + def initialize(@name, @class = @name, @tags = [] of String) + @id = UUID.random.to_s + end + + getter name + getter id +end + +ships = FS::Hash(String, Ship).new "test-index" +by_name = ships.new_index "name", &.name +by_class = ships.new_partition "class", &.class +by_id = ships.new_index "id", &.id +by_tags = ships.new_tags "tags", &.tags + +ship = Ship.new "Mutsuki", "Mutsuki", tags: ["kuchikukan"] +ships[ship.id] = ship + +begin + ship = Ship.new "Mutsuki", "broken", tags: ["kuchikukan"] + ships[ship.id] = ship +rescue FS::IndexOverload + puts "rescue: Adding an entry that would overload an index has been prevented." + # Should happen, ignore it. +else + puts "ERROR: No IndexOverload exception was raised on index overload." +end + +pp! ships.get_index("name").map &.name +