defmodule SData.MerkleSearchTree do @moduledoc""" A Merkle search tree. A node of the tree is { level, hash_of_node | nil, [ { k_low_bound, v, hash_of_node | nil }, { k_low_bound, v, hash_of_node | nil }, ... } } """ alias SData.PageStore, as: Store defstruct [:root, :store] defmodule Page do defstruct [:level, :low, :list] end defimpl SData.Page, for: Page do def refs(page) do refs = for {_, _, h} <- page.list, h != nil, do: h if page.low != nil do [ page.low | refs ] else refs end end end def new(store \\ nil) do store = store || SData.LocalStore.new %SData.MerkleSearchTree{ root: nil, store: store } end def insert(state, key, value) do level = calc_level(key) {hash, store} = insert_at(state.store, state.root, key, level, value) %{ state | root: hash, store: store } end def remove(_state, _key) do # TODO end defp insert_at(store, root, key, level, value) do {new_page, store} = if root == nil do { %Page{ level: level, low: nil, list: [ { key, value, nil } ] }, store } else %Page{ level: plevel, low: low, list: lst } = Store.get(store, root) [ { k0, _, _} | _ ] = lst cond do plevel < level -> {low, high, store} = split(store, root, key) { %Page{ level: level, low: low, list: [ { key, value, high } ] }, store } plevel == level -> store = Store.free(store, root) if key < k0 do {low2a, low2b, store} = split(store, low, key) { %Page{ level: level, low: low2a, list: [ { key, value, low2b } | lst] }, store } else {new_lst, store} = aux_insert_after_first(store, lst, key, value) { %Page{ level: plevel, low: low, list: new_lst }, store } end plevel > level -> store = Store.free(store, root) if key < k0 do {new_low, store} = insert_at(store, low, key, level, value) { %Page{ level: plevel, low: new_low, list: lst }, store } else {new_lst, store} = aux_insert_sub_after_first(store, lst, key, level, value) { %Page{ level: plevel, low: low, list: new_lst }, store } end end end Store.put(store, new_page) # returns {hash, store} end defp split(store, hash, key) do if hash == nil do {nil, nil, store} else %Page{ level: level, low: low, list: lst } = Store.get(store, hash) store = Store.free(store, hash) [ { k0, _, _} | _ ] = lst cond do key < k0 -> {lowlow, lowhi, store} = split(store, low, key) newp2 = %Page{ level: level, low: lowhi, list: lst } {newp2h, store} = Store.put(store, newp2) {lowlow, newp2h, store} key > k0 -> {lst1, p2, store} = split_aux(store, lst, key, level) newp1 = %Page{ level: level, low: low, list: lst1} {newp1h, store} = Store.put(store, newp1) {newp1h, p2, store} end end end defp split_aux(store, lst, key, level) do case lst do [ {k1, _, _} | _ ] when k1 == key -> raise "Bad logic" [ {k1, v1, r1} ] -> {r1l, r1h, store} = split(store, r1, key) { [{k1, v1, r1l}], r1h, store } [ {k1, v1, r1}, {k2, v2, r2} | rst ] when key < k2-> {r1l, r1h, store} = split(store, r1, key) p2 = %Page{ level: level, low: r1h, list: [ {k2, v2, r2} | rst ] } {p2h, store} = Store.put(store, p2) { [{k1, v1, r1l}], p2h, store } [ fst | rst ] -> {rst2, hi, store} = split_aux(store, rst, key, level) { [ fst | rst2 ], hi, store } end end defp aux_insert_after_first(store, lst, key, value) do case lst do [ {k1, _old_value, r1} | rst ] when k1 == key -> { [ {k1, value, r1} | rst ], store } [ {k1, v1, r1} ] -> {r1a, r1b, new_store} = split(store, r1, key) { [ {k1, v1, r1a}, {key, value, r1b} ], new_store } [ {k1, v1, r1}, {k2, v2, r2} | rst ] when key < k2-> {r1a, r1b, new_store} = split(store, r1, key) { [ {k1, v1, r1a}, {key, value, r1b}, {k2, v2, r2} | rst ], new_store } [ fst | rst ] -> {rst2, new_store} = aux_insert_after_first(store, rst, key, value) { [ fst | rst2 ], new_store } end end defp aux_insert_sub_after_first(store, lst, key, level, value) do case lst do [ {k1, _, _} | _ ] when k1 == key -> raise "Bad logic" [ {k1, v1, r1} ] -> {r1new, store_new} = insert_at(store, r1, key, level, value) { [{k1, v1, r1new}], store_new } [ {k1, v1, r1}, {k2, v2, r2} | rst ] when key < k2-> {r1new, store_new} = insert_at(store, r1, key, level, value) { [{k1, v1, r1new}, {k2, v2, r2} | rst], store_new } [ fst | rst ] -> {rst2, new_store} = aux_insert_sub_after_first(store, rst, key, level, value) { [ fst | rst2 ], new_store } end end def get(state, key) do get(state.store, state.root, key) end defp get(store, root, key) do case root do nil -> nil _ -> %Page{ level: _, low: low, list: lst } = Store.get(store, root) get_aux(store, low, lst, key) end end defp get_aux(store, low, lst, key) do case lst do [] -> get(store, low, key) [ {k, v, _} | _ ] when key == k -> v [ {k, _, _} | _ ] when key < k -> get(store, low, key) [ {k, _, low2} | rst ] when key > k -> get_aux(store, low2, rst, key) end end def dump(state) do dump(state.store, state.root, "") end defp dump(store, root, lvl) do case root do nil -> IO.puts(lvl <> "nil") _ -> %Page{ level: level, low: low, list: lst} = Store.get(store, root) IO.puts(lvl <> "#{root|>Base.encode16} (#{level})") dump(store, low, lvl <> " ") for {k, v, r} <- lst do IO.puts(lvl<>"- #{inspect k} => #{inspect v}") dump(store, r, lvl <> " ") end end end defp calc_level(key) do key |> SData.term_hash |> Base.encode16 |> String.to_charlist |> count_leading_zeroes end defp count_leading_zeroes('0' ++ rest) do 1 + count_leading_zeroes(rest) end defp count_leading_zeroes(_) do 0 end end