From 2f00b56f0c606449e62b7fd1d3a0b827bd1264e4 Mon Sep 17 00:00:00 2001 From: Luka Vandervelden Date: Tue, 10 Dec 2019 20:28:49 +0100 Subject: [PATCH] Proper replacement of values. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit It’s no longer needed to `hash.delete old_data`, `hash[id] = new_data`. Previously existing data are properly de-indexed, then replaced by the new data, which are then properly re-indexed. As you’d expect from a database thingy. --- src/fs.cr | 56 ++++++++++++++++++++++++++++------------------- test-edit.cr | 61 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 95 insertions(+), 22 deletions(-) create mode 100644 test-edit.cr diff --git a/src/fs.cr b/src/fs.cr index 444e64c..c78f212 100644 --- a/src/fs.cr +++ b/src/fs.cr @@ -104,14 +104,22 @@ class FS::Hash(K, V) end def []=(key : K, value : V) - # FIXME: Update partitions pointing to previous value (if any) + # Removes any old indices or partitions pointing to a value about + # to be replaced. + self.[key]?.try do |old_value| + remove_partitions key, old_value + end - # avoid corruption in case of crash during file writing + # Avoids corruption in case the application crashes while writing. file_path(key).tap do |path| ::File.write "#{path}.new", value.to_json ::FileUtils.mv "#{path}.new", path end + write_partitions key, value + end + + def write_partitions(key : K, value : V) @partitions.each do |index| index_key = index.key_proc.call value @@ -156,40 +164,44 @@ class FS::Hash(K, V) def delete(key : K) value = self[key]? + return if value.nil? + begin ::File.delete file_path key rescue # FIXME: Only intercept “no such file" errors end - unless value.nil? - @partitions.each do |index| - index_key = index.key_proc.call value + remove_partitions key, value - case index - when IndexData - symlink = file_path_indexes(key.to_s, index.name) + value + end - ::File.delete symlink - when PartitionData - symlink = file_path_partition(key, index.name, index_key) + def remove_partitions(key : K, value : V) + @partitions.each do |index| + index_key = index.key_proc.call value - ::File.delete symlink - end - end + case index + when IndexData + symlink = file_path_indexes(key.to_s, index.name) - @nn_partitions.each do |nn| - indices = nn.key_proc.call value + ::File.delete symlink + when PartitionData + symlink = file_path_partition(key, index.name, index_key) - indices.each do |index_key| - symlink = file_path_nn(key.to_s, nn.name, index_key) - - ::File.delete symlink - end + ::File.delete symlink end end - value + @nn_partitions.each do |nn| + indices = nn.key_proc.call value + + indices.each do |index_key| + symlink = file_path_nn(key.to_s, nn.name, index_key) + + ::File.delete symlink + end + end end ## diff --git a/test-edit.cr b/test-edit.cr new file mode 100644 index 0000000..e9f1c44 --- /dev/null +++ b/test-edit.cr @@ -0,0 +1,61 @@ +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-edit" +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_nn_partition "tags", &.tags + +ship = Ship.new "Satsuki", "Mutsuki", tags: ["kuchikukan"] +ships[ship.id] = ship + +ship = Ship.new "Mutsuki", "Mutsuki", tags: ["kuchikukan"] +ships[ship.id] = ship + +ship = Ship.new "Kisaragi", "broken", tags: ["broken"] +kisaragi = ship +ships[ship.id] = ship + +ship = Ship.new "Kisaragi", "Mutsuki", tags: ["kuchikukan"] +ship.id = kisaragi.id # Overwriting the “broken” Kisaragi entry. +ships[ship.id] = ship + +puts "Database entries" +ships.each do |id, ship| + p "#{ship.name} (#{ship.class}) [#{ship.tags.join ", "}]" +end + +no_broken = Array(Array(Ship)).new +puts +puts "Partitions/indices" +pp! ships.get_partition("class", "Mutsuki").map &.name +pp! ships.get_nn_partition("tags", "kuchikukan").map &.name + +pp! no_broken << ships.get_partition("class", "broken") +pp! no_broken << ships.get_nn_partition("tags", "broken") + +if no_broken.flatten.size > 0 + puts "ERROR: the test failed" +end +