aboutsummaryrefslogblamecommitdiff
path: root/lib/data/merklesearchtree.ex
blob: 963653e4b592813aac6261bc5684cc958b8ea021 (plain) (tree)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16















                                               

                                  

                           

















                                                        

                            
                  














                                                                         
                                                                               
        
                                                                          



                                                      
                                                                                    
                          
                                         

                                                          
                                                                                              

                                                                             
                                                                      

                         
                                         

                                                                       
                                                                      

                                                                                        
                                                                      


             
                                                         





                                 

                                                                         



                                                         

                                                              


                                                               

                                                            













                                                          

                                                                          








                                                             
                                                      




























                                                                                     







                                     
                                                                       
















                                            








                                     
                                                                          
























                                                           
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