lib/sqlcache.ex

defmodule Sqlcache do
  alias Exqlite.Basic
  alias Sqlcache.Compressor
  use GenServer

  def data_dir do
    Application.get_env(:sqlcache, :datadir, Sqlcache.Config.default_datadir())
  end

  def init(arg) do
    {:ok, conn} = Exqlite.Basic.open(Path.join(data_dir(), "sqlcache.db"))
    init_schema(conn)
    :persistent_term.put(:cache_conn, conn)
    {:ok, arg}
  end

  def init_schema(conn) do
    [
      "create table if not exists cache (ns string, key string, value string, ts string, PRIMARY KEY ( ns, key))",
      "create index if not exists cache_ns_idx on cache(ns);"
    ]
    |> Enum.each(fn stmt -> :ok = Exqlite.Sqlite3.execute(conn.db, stmt) end)
  end

  def conn do
    :persistent_term.get(:cache_conn)
  end

  def start_link(arg) do
    GenServer.start_link(__MODULE__, arg, name: __MODULE__)
  end

  def put(ns, key, value) do
    value = Compressor.compress(value)
    ts = DateTime.utc_now() |> DateTime.to_unix(:microsecond)

    case query("insert or replace into cache(ns, key, value, ts) values (?, ?, ?, ?)", [
           ns,
           key,
           value,
           ts
         ]) do
      {:ok, []} -> :ok
    end
  end

  def get(ns, key) do
    case query("select value from cache where ns =  ? and key = ?", [ns, key]) do
      {:ok, []} -> {:error, nil}
      {:ok, [%{"value" => val}]} -> {:ok, Compressor.uncompress(val)}
    end
  end

  def del(ns, key) do
    case query("delete from cache where ns =  ? and key = ?", [ns, key]) do
      {:ok, []} -> :ok
    end
  end

  def clear_kind(ns), do: clear_namespace(ns)

  def clear_namespace(ns) do
    case query("delete from cache where ns = ?", [ns]) do
      {:ok, []} ->
        :ok
    end
  end

  def debug() do
    query("select * from cache limit 100")
  end

  def query(sql, args \\ []) do
    res = Basic.exec(conn(), sql, args) |> Basic.rows()

    case res do
      {:ok, columns, rows} -> {:ok, as_maps(columns, rows)}
    end
  end

  defp as_maps(rows, columns) do
    rows
    |> Enum.map(fn row ->
      Enum.zip(columns, row) |> Enum.into(%{})
    end)
  end
end