dodb.cr/src/fsdb.cr

164 lines
3.2 KiB
Crystal
Raw Normal View History

2019-07-24 02:19:57 +02:00
require "file_utils"
2018-11-19 21:06:36 +01:00
require "json"
2019-12-11 18:10:09 +01:00
require "./fsdb/*"
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
2018-11-22 14:28:41 +01:00
def []?(key : K) : V?
2019-12-11 22:08:26 +01:00
self[key]
rescue MissingEntry
# FIXME: Only rescue JSON and “no such file” errors.
return nil
2018-11-19 21:06:36 +01:00
end
2018-11-22 14:28:41 +01:00
def [](key : K) : V
2019-12-11 22:08:26 +01:00
raise MissingEntry.new(key) unless ::File.exists? file_path key
2018-11-19 21:06:36 +01:00
read file_path key
end
2018-11-22 14:28:41 +01:00
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
2018-11-19 23:35:49 +01:00
# Avoids corruption in case the application crashes while writing.
2019-07-24 02:19:57 +02:00
file_path(key).tap do |path|
::File.write "#{path}.new", value.to_json
::FileUtils.mv "#{path}.new", path
end
2018-11-19 23:35:49 +01:00
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)
2018-11-19 21:06:36 +01:00
end
2018-11-22 14:28:41 +01:00
def delete(key : K)
2018-11-19 21:06:36 +01:00
value = self[key]?
return if value.nil?
2018-11-19 21:06:36 +01:00
begin
2019-07-12 16:00:56 +02:00
::File.delete file_path key
2018-11-19 21:06:36 +01:00
rescue
# FIXME: Only intercept “no such file" errors
end
remove_partitions key, value
2018-11-19 23:35:49 +01:00
value
end
2018-11-19 23:35:49 +01:00
def remove_partitions(key : K, value : V)
@indexers.each &.deindex(key, value)
2018-11-19 21:06:36 +01:00
end
2018-11-19 23:35:49 +01:00
##
# CAUTION: Very slow. Try not to use.
# Can be useful for making dumps or to restore a database, however.
2018-11-19 21:06:36 +01:00
def each
2019-07-26 12:34:37 +02:00
dirname = data_path
Dir.each_child dirname do |child|
2018-11-19 23:35:49 +01:00
next if child.match /^\./
2019-07-26 12:34:37 +02:00
full_path = "#{dirname}/#{child}"
2018-11-19 21:06:36 +01:00
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
2019-01-01 17:36:17 +01:00
##
# 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
2019-07-26 12:34:37 +02:00
private def data_path
"#{@directory_name}/data"
end
2018-11-19 21:06:36 +01:00
private def file_path(key : K)
2019-07-26 12:34:37 +02:00
"#{data_path}/#{key.to_s}.json"
end
2018-11-19 21:06:36 +01:00
private def read(file_path : String)
2019-07-12 16:00:56 +02:00
V.from_json ::File.read file_path
2018-11-19 21:06:36 +01:00
end
end