Skip to content
This repository was archived by the owner on Dec 17, 2018. It is now read-only.

Commit c269397

Browse files
connor rigbyConnorRigby
connor rigby
authored andcommitted
Add a benchmark, update readme.
1 parent 0c2af3d commit c269397

File tree

9 files changed

+113
-19
lines changed

9 files changed

+113
-19
lines changed

.formatter.exs

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
# Used by "mix format"
22
[
3-
inputs: ["mix.exs", "{config,lib,test}/**/*.{ex,exs}"]
3+
inputs: ["mix.exs", "{config,lib,test,bench}/**/*.{ex,exs}"]
44
]

README.md

+31-5
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,8 @@
33
[![Inline docs](http://inch-ci.org/github/connorrigby/esqlite.svg?branch=master)](http://inch-ci.org/github/connorrigby/esqlite)
44
[![Deps Status](https://beta.hexfaktor.org/badge/all/github/ConnorRigby/esqlite.svg)](https://beta.hexfaktor.org/github/ConnorRigby/esqlite)
55

6-
An Erlang nif library for sqlite3.
7-
8-
Introduction
9-
------------
10-
6+
# Sqlite
7+
Elixir API for interacting with SQLite databases.
118
This library allows you to use the accelent sqlite engine from
129
erlang. The library is implemented as a nif library, which allows for
1310
the fastest access to a sqlite database. This can be risky, as a bug
@@ -19,3 +16,32 @@ Special care has been taken not to block the scheduler of the calling
1916
process. This is done by handling all commands from erlang within a
2017
lightweight thread. The erlang scheduler will get control back when
2118
the command has been added to the command-queue of the thread.
19+
20+
# Usage
21+
```elixir
22+
{:ok, database} = Sqlite.open(database: "/tmp/database.sqlite3")
23+
{:ok, statement} = Sqlite.prepare(database, "CREATE TABLE data (id int, data text)")
24+
{:ok, _} = Sqlite.execute(database, statement, [])
25+
{:ok, statement} = Sqlite.prepare(database, "INSERT INTO data (data) VALUES ($1)")
26+
{:ok, _} = Sqlite.execute(database, statement, ["neat!"])
27+
{:ok, %Sqlite.Result{columns: [:data], num_rows: 1, rows: [["neat!"]]}} = Sqlite.query(database, "SELECT data FROM data", [])
28+
```
29+
30+
# Tests
31+
Since this project was originally an Erlang package, I chose to maintain the
32+
original module name (as an alias) and it's tests to try to maintain
33+
backwards compatibility. By default these tests get ran by default.
34+
`mix test` will execute them.
35+
36+
# Benchmarks
37+
There is also a benchmark suite located in the `bench` directory.
38+
It does not get ran with the test suite since it can take quite a while.
39+
You can run the benchmarks with
40+
```bash
41+
mix test --include bench
42+
```
43+
44+
# Thanks and License
45+
This project is originally a fork of [esqlite](https://github.com/mmzeeman/esqlite)
46+
Which was originally an Erlang implementation. The underlying NIF code (in `c_src`),
47+
and the test file in `erl_test` both retain the original Apache v2 license.

bench/big_data_test.exs

+55
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
defmodule BigDataTest do
2+
use ExUnit.Case
3+
@timeout :infinity
4+
5+
@tag :bench
6+
@tag timeout: @timeout
7+
test "BIG DATA" do
8+
{:ok, conn} = Sqlite.open(database: ":memory:")
9+
10+
column_names_and_types =
11+
"a int, b int, c int, d int, e int, f int, g int, h int, i int, j int, " <>
12+
"k int, l int, m int, n int, o int, p int, q int, r int, s int, t int, " <>
13+
"u int, v int, w int, x int, y int, z int"
14+
15+
column_names = "a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z"
16+
17+
subs =
18+
"$1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19, $20, $21, $22, $23, $24, $25, $26"
19+
20+
{:ok, q} = Sqlite.prepare(conn, "CREATE TABLE posts (#{column_names_and_types})")
21+
{:ok, _} = Sqlite.execute(conn, q, [])
22+
23+
{:ok, statement} =
24+
Sqlite.prepare(conn, "INSERT INTO posts (#{column_names}) VALUES (#{subs})")
25+
26+
{:ok, _} = Sqlite.execute(conn, statement, Enum.to_list(1..26))
27+
range = 0..800_000
28+
29+
inserts_fun = fn ->
30+
{time, _} =
31+
:timer.tc(fn ->
32+
for _i <- range do
33+
{:ok, _} = Sqlite.execute(conn, statement, Enum.to_list(1..26))
34+
end
35+
end)
36+
37+
IO.puts("#{Enum.count(range)} INSERT's took #{time} µs to execute.")
38+
end
39+
40+
query_fun = fn ->
41+
{time, res} =
42+
:timer.tc(fn ->
43+
Sqlite.query!(conn, "SELECT * FROM posts;", [], timeout: @timeout)
44+
end)
45+
46+
IO.puts("Query took: #{time} µs.")
47+
assert match?(%Sqlite.Result{}, res)
48+
end
49+
50+
insert_task = Task.async(inserts_fun)
51+
Task.await(insert_task, @timeout)
52+
query_task = Task.async(query_fun)
53+
Task.await(query_task, @timeout)
54+
end
55+
end

bench/test_helper.exs

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+

erl_test/test_helper.exs

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
:code.ensure_loaded(:esqlite_test)
2+
:esqlite_test.test()

lib/esqlite3/esqlite3.ex

+2-1
Original file line numberDiff line numberDiff line change
@@ -327,7 +327,8 @@ defmodule Esqlite3 do
327327
end
328328
end
329329

330-
defp receive_answer(ref, timeout) when is_reference(ref) and is_integer(timeout) do
330+
defp receive_answer(ref, timeout)
331+
when is_reference(ref) and (is_integer(timeout) or timeout == :infinity) do
331332
start = :os.timestamp()
332333

333334
receive do

lib/sqlite.ex

+19-11
Original file line numberDiff line numberDiff line change
@@ -29,14 +29,21 @@ defmodule Sqlite do
2929
@typedoc "Connection identifier for your Sqlite instance."
3030
@opaque conn :: Connection.t()
3131

32+
@default_start_opts [
33+
timeout: Application.get_env(:esqlite, :default_timeout, 5000)
34+
]
35+
3236
@doc """
3337
Start the connection process and connect to sqlite.
3438
## Options
3539
* `:database` -> Databse uri.
40+
* `:timeout` -> Max amount of time for commands to take. (default: 5000)
41+
## GenServer opts
42+
These get passed directly to [GenServer](GenServer.html)
3643
## Examples
3744
iex> {:ok, pid} = Sqlite.open(database: "sqlite.db")
3845
{:ok, #PID<0.69.0>}
39-
iex> {:ok, pid} = Sqlite.open(database: ":memory:")
46+
iex> {:ok, pid} = Sqlite.open(database: ":memory:", timeout: 6000)
4047
{:ok, #PID<0.69.0>}
4148
"""
4249
@spec open(Keyword.t(), GenServer.options()) :: {:ok, conn} | {:error, term}
@@ -77,8 +84,8 @@ defmodule Sqlite do
7784
{:ok, Sqlite.Result.t()} | {:error, Sqlite.Error.t()}
7885
def query(conn, sql, params, opts \\ []) do
7986
opts = opts |> defaults()
80-
81-
r = GenServer.call(conn.pid, {:query, sql, params, opts}, call_timeout(opts))
87+
call = {:query, sql, params, opts}
88+
r = GenServer.call(conn.pid, call, call_timeout(opts))
8289

8390
case r do
8491
{:ok, %Sqlite.Result{}} = ok -> ok
@@ -110,8 +117,8 @@ defmodule Sqlite do
110117
@spec prepare(conn, iodata, Keyword.t()) :: {:ok, Sqlite.Query.t()} | {:error, Sqlite.Error.t()}
111118
def prepare(conn, sql, opts \\ []) do
112119
opts = opts |> defaults()
113-
114-
r = GenServer.call(conn.pid, {:prepare, sql, opts}, call_timeout(opts))
120+
call = {:prepare, sql, opts}
121+
r = GenServer.call(conn.pid, call, call_timeout(opts))
115122

116123
case r do
117124
{:ok, %Sqlite.Query{}} = ok -> ok
@@ -141,8 +148,8 @@ defmodule Sqlite do
141148
@spec release_query(conn, Sqlite.Query.t(), Keyword.t()) :: :ok | {:error, Sqlite.Error.t()}
142149
def release_query(conn, query, opts \\ []) do
143150
opts = opts |> defaults()
144-
145-
r = GenServer.call(conn.pid, {:release_query, query, opts}, call_timeout(opts))
151+
call = {:release_query, query, opts}
152+
r = GenServer.call(conn.pid, call, call_timeout(opts))
146153

147154
case r do
148155
:ok -> :ok
@@ -186,8 +193,8 @@ defmodule Sqlite do
186193
{:ok, Sqlite.Result.t()} | {:error, Sqlite.Error.t()}
187194
def execute(conn, query, params, opts \\ []) do
188195
opts = defaults(opts)
189-
190-
r = GenServer.call(conn.pid, {:execute, query, params, opts}, call_timeout(opts))
196+
call = {:execute, query, params, opts}
197+
r = GenServer.call(conn.pid, call, call_timeout(opts))
191198

192199
case r do
193200
{:ok, %Sqlite.Result{}} = ok -> ok
@@ -239,8 +246,9 @@ defmodule Sqlite do
239246
Keyword.merge(defaults, opts)
240247
end
241248

249+
@doc false
242250
@spec default_opts(Keyword.t()) :: Keyword.t()
243-
def default_opts(opts) do
244-
Keyword.merge([timeout: Application.get_env(:esqlite, :default_timeout, 5000)], opts)
251+
defp default_opts(opts) do
252+
Keyword.merge(@default_start_opts, opts)
245253
end
246254
end

mix.exs

+1
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ defmodule Esqlite.MixProject do
1313
plt_add_apps: [],
1414
dialyzer: [flags: [:unmatched_returns, :race_conditions, :no_unused]],
1515
erlc_paths: erlc_paths(Mix.env()),
16+
test_paths: ["test", "bench", "erl_test"],
1617
start_permanent: Mix.env() == :prod,
1718
test_coverage: [tool: ExCoveralls],
1819
preferred_cli_env: [

test/test_helper.exs

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
{:module, Esqlite3Nif} = :code.ensure_loaded(Esqlite3Nif)
22
{:module, mod} = :code.ensure_loaded(:esqlite_test)
33
:eunit.test({:inparallel, mod})
4-
ExUnit.start()
4+
ExUnit.start(exclude: :bench)

0 commit comments

Comments
 (0)