@@ -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
0 commit comments