Skip to content

Commit 95e130d

Browse files
committed
refactor metadata table into header field
1 parent 1eb15b5 commit 95e130d

File tree

12 files changed

+298
-36
lines changed

12 files changed

+298
-36
lines changed

lib/cubdb.ex

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -168,6 +168,7 @@ defmodule CubDB do
168168
@auto_compact_defaults {100, 0.25}
169169

170170
@type key :: any
171+
@type metadata_key :: atom
171172
@type value :: any
172173
@type entry :: {key, value}
173174
@type auto_compact :: {pos_integer, number} | boolean
@@ -317,6 +318,24 @@ defmodule CubDB do
317318
end)
318319
end
319320

321+
@spec get_metadata(server, metadata_key, value) :: value | {:error, :key_not_atom}
322+
323+
@doc """
324+
Gets the value associated to `key` from the database metadata table.
325+
326+
If no value is associated with `key`, `default` is returned (which is `nil`,
327+
unless specified otherwise).
328+
"""
329+
def get_metadata(db, key, default \\ nil)
330+
331+
def get_metadata(db, key, default) when is_atom(key) do
332+
with_snapshot(db, fn %Snapshot{btree: btree} ->
333+
Reader.get_metadata(btree, key, default)
334+
end)
335+
end
336+
337+
def get_metadata(_, _, _), do: {:error, :key_not_atom}
338+
320339
@spec fetch(server, key) :: {:ok, value} | :error
321340

322341
@doc """
@@ -784,6 +803,22 @@ defmodule CubDB do
784803
end)
785804
end
786805

806+
@spec put_metadata(server, metadata_key, value) :: :ok | {:error, :key_not_atom}
807+
808+
@doc """
809+
Writes an entry in the database metadata table, associating `key` to `value`,
810+
overwriting any existing entries associated with the same key.
811+
812+
Returns `{:error, :key_not_atom}` if `key` is not an atom.
813+
"""
814+
def put_metadata(db, key, value) when is_atom(key) do
815+
transaction(db, fn tx ->
816+
{:commit, Tx.put_metadata(tx, key, value), :ok}
817+
end)
818+
end
819+
820+
def put_metadata(_, _, _), do: {:error, :key_not_atom}
821+
787822
@spec delete(server, key) :: :ok
788823

789824
@doc """
@@ -797,6 +832,23 @@ defmodule CubDB do
797832
end)
798833
end
799834

835+
@spec delete_metadata(server, metadata_key) :: :ok | {:error, :key_not_atom}
836+
837+
@doc """
838+
Deletes the entry associated to `key` from the database metadata table.
839+
840+
If `key` was not present in the database, nothing is done.
841+
842+
Returns `{:error, :key_not_atom}` if `key` is not an atom.
843+
"""
844+
def delete_metadata(db, key) when is_atom(key) do
845+
transaction(db, fn tx ->
846+
{:commit, Tx.delete_metadata(tx, key), :ok}
847+
end)
848+
end
849+
850+
def delete_metadata(_, _), do: {:error, :key_not_atom}
851+
800852
@spec update(server, key, value, (value -> value)) :: :ok
801853

802854
@doc """

lib/cubdb/btree.ex

Lines changed: 109 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ defmodule CubDB.Btree do
3030
@type btree_size :: non_neg_integer
3131
@type dirt :: non_neg_integer
3232
@type location :: non_neg_integer
33+
@type metadata :: keyword
3334
@type capacity :: pos_integer
3435
@type child_pointer :: {key, location}
3536
@type leaf_node :: record(:leaf, children: [child_pointer])
@@ -51,34 +52,53 @@ defmodule CubDB.Btree do
5152
size: btree_size,
5253
dirt: dirt,
5354
store: Store.t(),
54-
capacity: capacity
55+
capacity: capacity,
56+
metadata: metadata
5557
}
5658

5759
@default_capacity 32
58-
@enforce_keys [:root, :root_loc, :size, :dirt, :store, :capacity]
60+
@enforce_keys [:root, :root_loc, :size, :dirt, :store, :capacity, :metadata]
5961
defstruct @enforce_keys
6062

61-
@spec header(size: btree_size, location: location, dirt: dirt) :: Macro.t()
63+
@spec header(size: btree_size, location: location, dirt: dirt, metadata: metadata) :: Macro.t()
6264

63-
defmacro header(size: size, location: location, dirt: dirt) do
65+
defmacro header(size: size, location: location, dirt: dirt, metadata: metadata) do
6466
quote do
65-
{unquote(size), unquote(location), unquote(dirt)}
67+
{unquote(size), unquote(location), unquote(dirt), unquote(metadata)}
6668
end
6769
end
6870

6971
@spec new(Store.t(), capacity) :: Btree.t()
7072

7173
def new(store, cap \\ @default_capacity) do
7274
case Store.get_latest_header(store) do
73-
{_, header(size: s, location: loc, dirt: dirt)} ->
75+
{_, header(size: s, location: loc, dirt: dirt, metadata: metadata)} ->
7476
root = Store.get_node(store, loc)
75-
%Btree{root: root, root_loc: loc, dirt: dirt, size: s, capacity: cap, store: store}
77+
78+
%Btree{
79+
root: root,
80+
root_loc: loc,
81+
dirt: dirt,
82+
size: s,
83+
capacity: cap,
84+
store: store,
85+
metadata: metadata
86+
}
7687

7788
nil ->
7889
root = leaf()
7990
loc = Store.put_node(store, root)
80-
Store.put_header(store, header(size: 0, location: loc, dirt: 0))
81-
%Btree{root: root, root_loc: loc, dirt: 0, size: 0, capacity: cap, store: store}
91+
Store.put_header(store, header(size: 0, location: loc, dirt: 0, metadata: []))
92+
93+
%Btree{
94+
root: root,
95+
root_loc: loc,
96+
dirt: 0,
97+
size: 0,
98+
capacity: cap,
99+
store: store,
100+
metadata: []
101+
}
82102
end
83103
end
84104

@@ -94,6 +114,8 @@ defmodule CubDB.Btree do
94114
unless Store.blank?(store),
95115
do: raise(ArgumentError, message: "cannot load into non-empty store")
96116

117+
metadata = if match?(%Btree{}, enum), do: enum.metadata, else: []
118+
97119
{st, count} =
98120
Enum.reduce(enum, {[], 0}, fn {k, v}, {st, count} ->
99121
{load_node(store, k, value(val: v), st, 1, cap), count + 1}
@@ -103,8 +125,17 @@ defmodule CubDB.Btree do
103125
new(store, cap)
104126
else
105127
{root, root_loc} = finalize_load(store, st, 1, cap)
106-
Store.put_header(store, header(size: count, location: root_loc, dirt: 0))
107-
%Btree{root: root, root_loc: root_loc, capacity: cap, store: store, size: count, dirt: 0}
128+
Store.put_header(store, header(size: count, location: root_loc, dirt: 0, metadata: []))
129+
130+
%Btree{
131+
root: root,
132+
root_loc: root_loc,
133+
capacity: cap,
134+
store: store,
135+
size: count,
136+
dirt: 0,
137+
metadata: metadata
138+
}
108139
end
109140
end
110141

@@ -127,6 +158,17 @@ defmodule CubDB.Btree do
127158
end
128159
end
129160

161+
@spec fetch_metadata(Btree.t(), key) :: {:ok, val} | :error
162+
163+
# `fetch_metadata/2` returns `{:ok, value}` if an entry with key `key` is present
164+
# in the Btree metadata table, otherwise `:error`.
165+
def fetch_metadata(%Btree{metadata: metadata}, key) do
166+
case Keyword.get(metadata, key) do
167+
nil -> :error
168+
value -> {:ok, value}
169+
end
170+
end
171+
130172
@spec written_since?(Btree.t(), key, Btree.t()) ::
131173
boolean | {:maybe, :not_found} | {:maybe, :different_store}
132174

@@ -174,13 +216,37 @@ defmodule CubDB.Btree do
174216
insert_terminal_node(btree, key, value(val: value), false)
175217
end
176218

219+
@spec insert_metadata(Btree.t(), key, val) :: Btree.t()
220+
221+
# `insert_metadata/3` inserts the key-value pair into the Btree metadata table,
222+
# overwriting any existing values associated with the same key. It does not commit
223+
# the operation, so `commit/1` must be explicitly called to commit the insertion.
224+
def insert_metadata(btree, key, value) do
225+
%Btree{metadata: metadata} = btree
226+
metadata = Keyword.put(metadata, key, value)
227+
%{btree | metadata: metadata}
228+
end
229+
230+
@spec delete_metadata(Btree.t(), key) :: Btree.t()
231+
232+
# `delete_metadata/2` deletes the entry associated to `key` in the Btree metadata
233+
# table, if it exists, otherwise it does nothing. It does not commit the operation,
234+
# so `commit/1` must be explicitly called to commit the deletion.
235+
def delete_metadata(btree, key) do
236+
%Btree{metadata: metadata} = btree
237+
metadata = Keyword.delete(metadata, key)
238+
%{btree | metadata: metadata}
239+
end
240+
177241
@spec delete(Btree.t(), key) :: Btree.t()
178242

179243
# `delete/2` deletes the entry associated to `key` in the Btree, if existing.
180244
# It does not commit the operation, so `commit/1` must be explicitly called to
181245
# commit the deletion.
182246
def delete(btree, key) do
183-
%Btree{root: root, store: store, capacity: cap, size: s, dirt: dirt} = btree
247+
%Btree{root: root, store: store, capacity: cap, size: s, dirt: dirt, metadata: metadata} =
248+
btree
249+
184250
{leaf = {@leaf, children}, path} = lookup_leaf(root, store, key, [])
185251

186252
case List.keyfind(children, key, 0) do
@@ -199,7 +265,8 @@ defmodule CubDB.Btree do
199265
capacity: cap,
200266
store: store,
201267
size: size,
202-
dirt: dirt + 1
268+
dirt: dirt + 1,
269+
metadata: metadata
203270
}
204271

205272
nil ->
@@ -227,11 +294,20 @@ defmodule CubDB.Btree do
227294
@spec clear(Btree.t()) :: Btree.t()
228295

229296
def clear(btree) do
230-
%Btree{store: store, capacity: cap, dirt: dirt} = btree
297+
%Btree{store: store, capacity: cap, dirt: dirt, metadata: metadata} = btree
231298

232299
root = leaf()
233300
loc = Store.put_node(store, root)
234-
%Btree{root: root, root_loc: loc, size: 0, dirt: dirt + 1, capacity: cap, store: store}
301+
302+
%Btree{
303+
root: root,
304+
root_loc: loc,
305+
size: 0,
306+
dirt: dirt + 1,
307+
capacity: cap,
308+
store: store,
309+
metadata: metadata
310+
}
235311
end
236312

237313
@spec commit(Btree.t()) :: Btree.t()
@@ -243,8 +319,20 @@ defmodule CubDB.Btree do
243319
# If one or more updates are performed, but `commit/1` is not called, the
244320
# updates won't be committed to the database and will be lost in case of a
245321
# restart.
246-
def commit(tree = %Btree{store: store, size: size, root_loc: root_loc, dirt: dirt}) do
247-
Store.put_header(store, header(size: size, location: root_loc, dirt: dirt + 1))
322+
def commit(
323+
tree = %Btree{
324+
store: store,
325+
size: size,
326+
root_loc: root_loc,
327+
dirt: dirt,
328+
metadata: metadata
329+
}
330+
) do
331+
Store.put_header(
332+
store,
333+
header(size: size, location: root_loc, dirt: dirt + 1, metadata: metadata)
334+
)
335+
248336
tree
249337
end
250338

@@ -293,7 +381,8 @@ defmodule CubDB.Btree do
293381
Btree.t() | {:error, :exists}
294382

295383
defp insert_terminal_node(btree, key, terminal_node, overwrite \\ true) do
296-
%Btree{root: root, store: store, capacity: cap, size: s, dirt: dirt} = btree
384+
%Btree{root: root, store: store, capacity: cap, size: s, dirt: dirt, metadata: metadata} =
385+
btree
297386

298387
{leaf = {@leaf, children}, path} = lookup_leaf(root, store, key, [])
299388
was_set = child_is_set?(store, children, key)
@@ -317,7 +406,8 @@ defmodule CubDB.Btree do
317406
capacity: cap,
318407
store: store,
319408
size: size,
320-
dirt: dirt + 1
409+
dirt: dirt + 1,
410+
metadata: metadata
321411
}
322412
end
323413
end

lib/cubdb/reader.ex

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,15 @@ defmodule CubDB.Reader do
1616
end
1717
end
1818

19+
@spec get_metadata(Btree.t(), CubDB.key(), any) :: any
20+
21+
def get_metadata(btree, key, default) do
22+
case Btree.fetch_metadata(btree, key) do
23+
{:ok, value} -> value
24+
:error -> default
25+
end
26+
end
27+
1928
@spec get_multi(Btree.t(), [CubDB.key()]) :: %{CubDB.key() => CubDB.value()}
2029

2130
def get_multi(btree, keys) do

lib/cubdb/store/file.ex

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -222,6 +222,7 @@ defimpl CubDB.Store, for: CubDB.Store.File do
222222

223223
defp read_header(file, location) do
224224
case read_term(file, location) do
225+
{:ok, {size, loc, dirt}} -> {location, {size, loc, dirt, []}}
225226
{:ok, term} -> {location, term}
226227
{:error, _} -> get_latest_good_header(file, location - 1)
227228
end

lib/cubdb/transaction.ex

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,11 @@ defmodule CubDB.Tx do
4747
Reader.get(btree, key, default)
4848
end
4949

50+
def get_metadata(tx = %Tx{btree: btree}, key, default \\ nil) do
51+
validate_transaction!(tx)
52+
Reader.get_metadata(btree, key, default)
53+
end
54+
5055
@spec fetch(Tx.t(), CubDB.key()) :: {:ok, CubDB.value()} | :error
5156

5257
@doc """
@@ -252,6 +257,11 @@ defmodule CubDB.Tx do
252257
end
253258
end
254259

260+
def put_metadata(tx = %Tx{btree: btree}, key, value) do
261+
validate_transaction!(tx)
262+
%{tx | btree: Btree.insert_metadata(btree, key, value)}
263+
end
264+
255265
@spec delete(Tx.t(), CubDB.key()) :: Tx.t()
256266

257267
@doc """
@@ -272,6 +282,11 @@ defmodule CubDB.Tx do
272282
end
273283
end
274284

285+
def delete_metadata(tx = %Tx{btree: btree}, key) do
286+
validate_transaction!(tx)
287+
%{tx | btree: Btree.delete_metadata(btree, key)}
288+
end
289+
275290
@spec clear(Tx.t()) :: Tx.t()
276291

277292
@doc """

mix.exs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ defmodule CubDB.Mixfile do
22
use Mix.Project
33

44
@source_url "https://github.com/lucaong/cubdb"
5-
@version "2.0.2"
5+
@version "2.0.3"
66

77
def project do
88
[

0 commit comments

Comments
 (0)