Current section

Files

Jump to
fsdb lib fsdb.ex
Raw

lib/fsdb.ex

defmodule Fsdb do
@moduledoc """
Fsdb module.
```elixir
#run with: mix sample_direct
db = Path.expand("_build/dev/.fsdb")
:ok = Fsdb.init(db)
#drop to start over
:ok = Fsdb.drop(db, "table1")
:ok = Fsdb.create(db, "table1")
#insert generates and returns an autoincrementing id
{:ok, 1} = Fsdb.insert(db, "table1", "row1")
{:ok, "row1"} = Fsdb.fetch(db, "table1", 1)
#operation on nonexisting ids return not found
:nf = Fsdb.delete(db, "table1", 5)
:nf = Fsdb.update(db, "table1", 5, "row5+")
#save overrides and updates id generator
:ok = Fsdb.save(db, "table1", 5, "row5")
:ok = Fsdb.save(db, "table1", 3, "row3")
#continue +1 of largest id used/generated before
{:ok, 6} = Fsdb.insert(db, "table1", "row6")
#operations that get back previous value
{:ok, "row5"} = Fsdb.update(db, "table1", 5, "row5+")
{:ok, "row5+"} = Fsdb.save(db, "table1", 5, "row5++")
{:ok, "row5++"} = Fsdb.delete(db, "table1", 5)
#tuples may not be id ordered
[{1, "row1"}, {3, "row3"}, {6, "row6"}] = Fsdb.list(db, "table1")
```
"""
@rowre ~r/row\.[0-9]+/
@rowid "row.id"
def init(path) do
File.mkdir_p!(path)
end
def create(path, table) do
folder = Path.join(path, table)
if !File.exists?(folder) do
File.mkdir_p!(folder)
file = Path.join(folder, @rowid)
:ok = dump(file, 0)
end
:ok
end
def drop(path, table) do
folder = Path.join(path, table)
File.rm_rf!(folder)
:ok #rm returns list of deleted files
end
def list(path, table) do
folder = Path.join(path, table)
for file <- File.ls!(folder),
Regex.match?(@rowre, file) do
parse(Path.join(folder, file))
end
end
defp parse(file) do
txt = File.read!(file) |> :erlang.binary_to_list
{:ok, tokens, _} = :erl_scan.string(txt)
{:ok, row} = :erl_parse.parse_term(tokens)
row
end
def insert(path, table, row) do
folder = Path.join(path, table)
id = nextid(folder)
file = Path.join(folder, rowid(id))
:ok = dump(file, {id, row})
{:ok, id}
end
def save(path, table, id, row) do
folder = Path.join(path, table)
file = Path.join(folder, rowid(id))
if File.exists?(file) do
{^id, crow} = parse(file)
:ok = dump(file, {id, row})
maxid(folder, id)
{:ok, crow}
else
:ok = dump(file, {id, row})
maxid(folder, id)
:ok
end
end
defp nextid(folder) do
file = Path.join(folder, @rowid)
id = parse(file) + 1
:ok = dump(file, id)
id
end
defp maxid(folder, id) do
file = Path.join(folder, @rowid)
cid = parse(file)
mid = if id > cid, do: id, else: cid
:ok = dump(file, mid)
mid
end
defp dump(file, row) do
File.write! file, :io_lib.format("~p.", [row])
end
def fetch(path, table, id) do
folder = Path.join(path, table)
file = Path.join(folder, rowid(id))
if File.exists?(file) do
{^id, row} = parse(file)
{:ok, row}
else
:nf
end
end
defp rowid(id) do
fid = Integer.to_string(id) |> String.pad_leading(6, "0")
"row.#{fid}"
end
def update(path, table, id, row) do
folder = Path.join(path, table)
file = Path.join(folder, rowid(id))
if File.exists?(file) do
{^id, crow} = parse(file)
:ok = dump(file, {id, row})
{:ok, crow}
else
:nf
end
end
def delete(path, table, id) do
folder = Path.join(path, table)
file = Path.join(folder, rowid(id))
if File.exists?(file) do
{^id, row} = parse(file)
File.rm!(file)
{:ok, row}
else
:nf
end
end
end