From 4ff5c0c78189f09c94878ccd45cf920916e3b5cc Mon Sep 17 00:00:00 2001 From: Luka Vandervelden Date: Thu, 12 Dec 2019 00:44:05 +0100 Subject: [PATCH] Files moved to match name update. --- src/dodb.cr | 177 +++++++++++++++++++++++++++++++ src/{fsdb => dodb}/exceptions.cr | 0 src/{fsdb => dodb}/index.cr | 0 src/{fsdb => dodb}/indexer.cr | 0 src/{fsdb => dodb}/partition.cr | 0 src/{fsdb => dodb}/tags.cr | 0 src/fsdb.cr | 2 +- 7 files changed, 178 insertions(+), 1 deletion(-) create mode 100644 src/dodb.cr rename src/{fsdb => dodb}/exceptions.cr (100%) rename src/{fsdb => dodb}/index.cr (100%) rename src/{fsdb => dodb}/indexer.cr (100%) rename src/{fsdb => dodb}/partition.cr (100%) rename src/{fsdb => dodb}/tags.cr (100%) diff --git a/src/dodb.cr b/src/dodb.cr new file mode 100644 index 0000000..8849590 --- /dev/null +++ b/src/dodb.cr @@ -0,0 +1,177 @@ +require "file_utils" +require "json" + +require "./dodb/*" + +class DODB::DataBase(K, V) + @indexers = [] of Indexer(V) + + def initialize(@directory_name : String) + Dir.mkdir_p data_path + end + + ## + # name is the name that will be used on the file system. + def new_partition(name : String, &block : Proc(V, String)) + Partition(V).new(@directory_name, name, block).tap do |table| + @indexers << table + end + end + + ## + # name is the name that will be used on the file system. + def new_index(name : String, &block : Proc(V, String)) + Index(V).new(@directory_name, name, block).tap do |indexer| + @indexers << indexer + end + end + + def new_tags(name : String, &block : Proc(V, Array(String))) + Tags(V).new(@directory_name, name, block).tap do |tags| + @indexers << tags + end + end + + def get_index(name : String, key) + index = @indexers.find &.name.==(name) + + index.not_nil!.as(DODB::Index).get key + end + + # FIXME: Is this “key” really a K, not just a String? + def get_partition(table_name : String, partition_name : String) + partition = @indexers.find &.name.==(table_name) + + partition.not_nil!.as(DODB::Partition).get partition_name + end + + def get_tags(name, key : K) + partition = @indexers.find &.name.==(name) + + partition.not_nil!.as(DODB::Tags).get name, key + end + + def []?(key : K) : V? + self[key] + rescue MissingEntry + # FIXME: Only rescue JSON and “no such file” errors. + return nil + end + + def [](key : K) : V + raise MissingEntry.new(key) unless ::File.exists? file_path key + + read file_path key + 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. + if old_value + remove_partitions key, old_value + end + + # 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 check_collisions!(key : K, value : V, old_value : V?) + @indexers.each &.check!(key, value, old_value) + end + + def write_partitions(key : K, value : V) + @indexers.each &.index(key, value) + end + + 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 + + remove_partitions key, value + + value + end + + def remove_partitions(key : K, value : V) + @indexers.each &.deindex(key, value) + end + + ## + # CAUTION: Very slow. Try not to use. + # Can be useful for making dumps or to restore a database, however. + def each + dirname = data_path + Dir.each_child dirname do |child| + next if child.match /^\./ + + full_path = "#{dirname}/#{child}" + + begin + # FIXME: Only intercept JSON parsing errors. + field = read full_path + rescue + next + end + + # FIXME: Will only work for String. :( + key = child.gsub /\.json$/, "" + + yield key, field + end + end + + ## + # CAUTION: Very slow. Try not to use. + def to_h + hash = ::Hash(K, V).new + + each do |key, value| + hash[key] = value + end + + hash + end + + private def data_path + "#{@directory_name}/data" + end + + private def file_path(key : K) + "#{data_path}/#{key.to_s}.json" + end + + private def read(file_path : String) + V.from_json ::File.read file_path + end + + # A very slow operation that removes all indices and then rewrites + # them all. + def reindex_everything! + old_data = to_h + + old_data.each do |key, value| + self.delete key + end + + old_data.each do |key, value| + self[key] = value + end + end +end + diff --git a/src/fsdb/exceptions.cr b/src/dodb/exceptions.cr similarity index 100% rename from src/fsdb/exceptions.cr rename to src/dodb/exceptions.cr diff --git a/src/fsdb/index.cr b/src/dodb/index.cr similarity index 100% rename from src/fsdb/index.cr rename to src/dodb/index.cr diff --git a/src/fsdb/indexer.cr b/src/dodb/indexer.cr similarity index 100% rename from src/fsdb/indexer.cr rename to src/dodb/indexer.cr diff --git a/src/fsdb/partition.cr b/src/dodb/partition.cr similarity index 100% rename from src/fsdb/partition.cr rename to src/dodb/partition.cr diff --git a/src/fsdb/tags.cr b/src/dodb/tags.cr similarity index 100% rename from src/fsdb/tags.cr rename to src/dodb/tags.cr diff --git a/src/fsdb.cr b/src/fsdb.cr index b71bcb7..8849590 100644 --- a/src/fsdb.cr +++ b/src/fsdb.cr @@ -1,7 +1,7 @@ require "file_utils" require "json" -require "./fsdb/*" +require "./dodb/*" class DODB::DataBase(K, V) @indexers = [] of Indexer(V)