Packages
exqlite
0.3.2
0.39.0
0.38.0
0.37.0
0.36.0
0.35.0
0.34.0
0.33.1
0.33.0
0.32.1
0.32.0
0.31.0
0.30.1
0.30.0
0.29.0
0.28.0
0.27.1
0.27.0
0.26.0
0.25.0
0.24.2
0.24.1
0.24.0
0.23.0
0.22.0
0.21.0
0.20.0
0.19.0
0.18.0
0.17.0
0.16.2
0.16.1
0.16.0
0.15.0
0.14.0
0.13.15
0.13.14
0.13.13
0.13.12
0.13.11
0.13.10
0.13.9
0.13.8
0.13.7
0.13.6
0.13.5
0.13.4
0.13.3
0.13.2
0.13.1
0.13.0
0.12.0
0.11.9
0.11.8
0.11.7
0.11.6
0.11.5
0.11.4
0.11.3
0.11.2
0.11.1
0.11.0
0.10.3
0.10.2
0.10.1
0.10.0
0.9.3
0.9.2
0.9.1
0.9.0
0.8.7
0.8.6
0.8.5
0.8.4
0.8.3
0.8.2
0.8.1
0.8.0
0.7.9
0.7.8
0.7.7
0.7.6
0.7.5
0.7.4
0.7.3
0.7.2
0.7.1
0.7.0
0.6.4
0.6.3
0.6.2
0.6.1
0.6.0
0.5.11
0.5.10
0.5.9
0.5.8
0.5.7
0.5.6
0.5.5
0.5.4
0.5.3
0.5.2
0.5.1
0.5.0
0.4.9
0.4.8
0.4.7
0.4.6
0.4.5
0.4.4
0.4.2
0.4.1
0.4.0
0.3.6
0.3.5
0.3.4
0.3.3
0.3.2
0.3.1
0.3.0
0.2.2
0.2.1
0.2.0
0.1.1
0.1.0
An Elixir SQLite3 library
Current section
Files
Jump to
Current section
Files
lib/exqlite/queries.ex
defmodule Exqlite.Queries do
@moduledoc """
The interface to manage cached prepared queries.
"""
#
# TODO: We should probably do some tracking on the number of statements being
# generated and culling the oldest cached value (LRU). In its current
# implementation, this could just have a run away memory leak if we are
# not careful.
#
alias Exqlite.Query
defstruct [:queries, :timestamps, :limit]
@type t() :: %__MODULE__{
queries: ETS.Set.t(),
timestamps: ETS.Set.t(),
limit: integer()
}
@type reason() :: String.t() | atom()
@doc """
Constructs a new prepared query cache with the specified limit. The cache uses
a least recently used caching mechanism.
"""
@spec new(atom()) :: t()
def new(limit \\ 50) do
with {:ok, queries} <- ETS.Set.new(protection: :public),
{:ok, timestamps} <- ETS.Set.new(ordered: true, protection: :public) do
%__MODULE__{
queries: queries,
timestamps: timestamps,
limit: limit
}
end
end
@spec put(t(), Query.t()) :: {:ok, t()}
def put(cache, %Query{name: ""}), do: {:ok, cache}
@spec put(t(), Query.t()) :: {:ok, t()}
def put(cache, %Query{name: nil}), do: {:ok, cache}
@spec put(t(), Query.t()) :: {:ok, t()}
def put(cache, %Query{ref: nil}), do: {:ok, cache}
@spec put(t(), Query.t()) :: {:ok, t()} | {:error, reason()}
def put(cache, %Query{name: query_name, ref: ref}) do
with timestamp <- current_timestamp(),
{:ok, timestamps} <- ETS.Set.put(cache.timestamps, {timestamp, query_name}),
{:ok, queries} <- ETS.Set.put(cache.queries, {query_name, timestamp, ref}) do
clean(%{cache | timestamps: timestamps, queries: queries})
end
end
@spec destroy(nil) :: :ok
def destroy(nil), do: :ok
@doc """
Completely delete the cache.
"""
@spec destroy(t()) :: :ok
def destroy(cache) do
with {:ok, _} <- ETS.Set.delete(cache.queries),
{:ok, _} <- ETS.Set.delete(cache.timestamps) do
:ok
end
end
@spec delete(t(), Query.t()) :: {:ok, t()}
def delete(cache, %Query{name: nil}), do: {:ok, cache}
@spec delete(t(), Query.t()) :: {:ok, t()}
def delete(cache, %Query{name: ""}), do: {:ok, cache}
@spec delete(t(), Query.t()) :: {:ok, t()} | {:error, reason()}
def delete(cache, %Query{name: query_name}) do
with {:ok, {_, timestamp, _}} <- ETS.Set.get(cache.queries, query_name),
{:ok, timestamps} <- ETS.Set.delete(cache.timestamps, timestamp),
{:ok, queries} <- ETS.Set.delete(cache.queries, query_name) do
{:ok, %{cache | timestamps: timestamps, queries: queries}}
end
end
@spec get(t(), Query.t()) :: {:ok, nil}
def get(_cache, %Query{name: nil}), do: {:ok, nil}
@spec get(t(), Query.t()) :: {:ok, nil}
def get(_cache, %Query{name: ""}), do: {:ok, nil}
@doc """
Gets an existing prepared query if it exists. Otherwise `nil` is returned.
"""
@spec get(t(), Query.t()) :: {:ok, Query.t() | nil} | {:error, reason()}
def get(cache, %Query{name: query_name} = query) do
with {:ok, {_, timestamp, ref}} <- ETS.Set.get(cache.queries, query_name),
{:ok, _} <- ETS.Set.delete(cache.timestamps, timestamp),
timestamp <- current_timestamp(),
{:ok, _} <- ETS.Set.put(cache.timestamps, {timestamp, query_name}),
{:ok, _} <- ETS.Set.put(cache.queries, {query_name, timestamp, ref}) do
{:ok, %{query | ref: ref}}
else
{:ok, nil} -> {:ok, nil}
{:error, reason} -> {:error, reason}
_ -> {:error, :unknown_error}
end
end
@doc """
Clears all of the cached prepared statements.
"""
@spec clear(t()) :: {:ok, t()}
def clear(cache) do
with {:ok, queries} <- ETS.Set.delete_all(cache.queries),
{:ok, timestamps} <- ETS.Set.delete_all(cache.timestamps) do
{:ok, %{cache | queries: queries, timestamps: timestamps}}
end
end
@spec size(t()) :: integer()
def size(cache) do
case ETS.Set.info(cache.queries, true) do
{:ok, info} -> Keyword.get(info, :size, 0)
_ -> 0
end
end
##
## Helpers
##
defp current_timestamp(), do: :erlang.unique_integer([:monotonic])
defp clean(cache) do
if size(cache) > cache.limit do
with {:ok, timestamp} <- ETS.Set.first(cache.timestamps),
{:ok, {_, query_name}} <- ETS.Set.get(cache.timestamps, timestamp),
{:ok, timestamps} <- ETS.Set.delete(cache.timestamps, timestamp),
{:ok, queries} <- ETS.Set.delete(cache.queries, query_name) do
{:ok, %{cache | timestamps: timestamps, queries: queries}}
else
{:ok, nil} -> {:ok, cache}
_ -> {:ok, cache}
end
else
{:ok, cache}
end
end
end