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

Commit 7280de7

Browse files
Connor RigbyConnorRigby
authored andcommitted
Finish implementation of decent api.
1 parent e269b46 commit 7280de7

File tree

6 files changed

+247
-71
lines changed

6 files changed

+247
-71
lines changed

lib/sqlite.ex

Lines changed: 53 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,6 @@ defmodule Sqlite do
33
SQLite3 driver for Elixir.
44
"""
55

6-
alias Sqlite.Query
7-
86
defmodule Connection do
97
@moduledoc false
108
defstruct [
@@ -73,19 +71,16 @@ defmodule Sqlite do
7371
Sqlite.query(conn, "INSERT INTO posts (title) VALUES ('my title')", [])
7472
Sqlite.query(conn, "SELECT title FROM posts", [])
7573
Sqlite.query(conn, "SELECT id FROM posts WHERE title like $1", ["%my%"])
76-
Sqlite.query(conn, "COPY posts TO STDOUT", [])
7774
"""
7875
@spec query(conn, iodata, list, Keyword.t()) ::
7976
{:ok, Sqlite.Result.t()} | {:error, Sqlite.Error.t()}
80-
def query(conn, statement, params, opts \\ []) do
81-
query = %Query{name: "", statement: statement}
77+
def query(conn, sql, params, opts \\ []) do
8278
opts = opts |> defaults()
8379

84-
GenServer.call(conn.pid, {:query, query, params, opts}, call_timeout(opts))
80+
GenServer.call(conn.pid, {:query, sql, params, opts}, call_timeout(opts))
8581
|> case do
8682
{:ok, %Sqlite.Result{}} = ok -> ok
8783
{:error, %Sqlite.Error{}} = ok -> ok
88-
err -> unexpected_response(err, :query)
8984
end
9085
end
9186

@@ -94,46 +89,78 @@ defmodule Sqlite do
9489
there was an error. See `query/3`.
9590
"""
9691
@spec query!(conn, iodata, list, Keyword.t()) :: Sqlite.Result.t()
97-
def query!(conn, statement, params, opts \\ []) do
98-
case query(conn, statement, params, opts) do
92+
def query!(conn, sql, params, opts \\ []) do
93+
case query(conn, sql, params, opts) do
9994
{:ok, result} -> result
100-
{:error, reason} -> raise Sqlite.Error, %{reason: reason}
95+
{:error, reason} -> raise Sqlite.Error, reason.message
10196
end
10297
end
10398

10499
@doc """
105100
Prepares an (extended) query and returns the result as
106101
`{:ok, %Sqlite.Query{}}` or `{:error, %Sqlite.Error{}}` if there was an
107102
error. Parameters can be set in the query as `$1` embedded in the query
108-
string. To execute the query call `execute/4`. To close the prepared query
109-
call `close/3`. See `Sqlite.Query` for the query data.
103+
string. To execute the query call `execute/4`.
110104
111105
## Examples
112-
Sqlite.prepare(conn, "", "CREATE TABLE posts (id serial, title text)")
106+
Sqlite.prepare(conn, "CREATE TABLE posts (id serial, title text)")
113107
"""
114-
@spec prepare(conn, iodata, iodata, Keyword.t()) ::
115-
{:ok, Sqlite.Query.t()} | {:error, Sqlite.Error.t()}
116-
def prepare(conn, name, statement, opts \\ []) do
117-
query = %Query{name: name, statement: statement}
108+
@spec prepare(conn, iodata, Keyword.t()) :: {:ok, Sqlite.Query.t()} | {:error, Sqlite.Error.t()}
109+
def prepare(conn, sql, opts \\ []) do
118110
opts = opts |> defaults()
119111

120-
GenServer.call(conn.pid, {:prepare, query, opts}, call_timeout(opts))
112+
GenServer.call(conn.pid, {:prepare, sql, opts}, call_timeout(opts))
121113
|> case do
122114
{:ok, %Sqlite.Query{}} = ok -> ok
123115
{:error, %Sqlite.Error{}} = ok -> ok
124-
err -> unexpected_response(err, :prepare)
125116
end
126117
end
127118

128119
@doc """
129120
Prepares an (extended) query and returns the prepared query or raises
130-
`Sqlite.Error` if there was an error. See `prepare/4`.
121+
`Sqlite.Error` if there was an error. See `prepare/3`.
131122
"""
132-
@spec prepare!(conn, iodata, iodata, Keyword.t()) :: Sqlite.Query.t()
133-
def prepare!(conn, name, statement, opts \\ []) do
134-
case prepare(conn, name, statement, opts) do
123+
@spec prepare!(conn, iodata, Keyword.t()) :: Sqlite.Query.t()
124+
def prepare!(conn, sql, opts \\ []) do
125+
case prepare(conn, sql, opts) do
135126
{:ok, result} -> result
136-
{:error, reason} -> raise Sqlite.Error, %{reason: reason}
127+
{:error, reason} -> raise Sqlite.Error, reason.message
128+
end
129+
end
130+
131+
@doc """
132+
Releases an (extended) query.
133+
134+
## Examples
135+
query = Sqlite.prepare!(conn, "CREATE TABLE posts (id serial, title text)")
136+
Sqlite.release_query(query)
137+
"""
138+
@spec release_query(conn, Sqlite.Query.t(), Keyword.t()) :: :ok | {:error, Sqlite.Error.t()}
139+
def release_query(conn, query, opts \\ []) do
140+
opts = opts |> defaults()
141+
142+
GenServer.call(conn.pid, {:release_query, query, opts}, call_timeout(opts))
143+
|> case do
144+
:ok -> :ok
145+
{:error, %Sqlite.Error{}} = ok -> ok
146+
end
147+
end
148+
149+
@doc """
150+
Releases an (extended) query or raises
151+
`Sqlite.Error` if there was an error. See `release_query/3`.
152+
153+
## Examples
154+
query = Sqlite.prepare!(conn, "CREATE TABLE posts (id serial, title text)")
155+
Sqlite.release_query(query)
156+
"""
157+
@spec release_query!(conn, Sqlite.Query.t(), Keyword.t()) :: :ok
158+
def release_query!(conn, query, opts \\ []) do
159+
opts = opts |> defaults()
160+
161+
case release_query(conn, query, opts) do
162+
:ok -> :ok
163+
{:error, reason} -> raise Sqlite.Error, reason.message
137164
end
138165
end
139166

@@ -160,7 +187,6 @@ defmodule Sqlite do
160187
|> case do
161188
{:ok, %Sqlite.Result{}} = ok -> ok
162189
{:error, %Sqlite.Error{}} = ok -> ok
163-
err -> unexpected_response(err, :execute)
164190
end
165191
end
166192

@@ -172,7 +198,7 @@ defmodule Sqlite do
172198
def execute!(conn, query, params, opts \\ []) do
173199
case execute(conn, query, params, opts) do
174200
{:ok, result} -> result
175-
{:error, reason} -> raise Sqlite.Error, %{reason: reason}
201+
{:error, reason} -> raise Sqlite.Error, reason.message
176202
end
177203
end
178204

@@ -182,23 +208,11 @@ defmodule Sqlite do
182208
@spec close(conn, Keyword.t()) :: :ok | {:error, Sqlite.Error.t()}
183209
def close(conn, opts \\ []) when is_list(opts) do
184210
opts = defaults(opts)
211+
185212
GenServer.call(conn.pid, {:close, opts}, call_timeout(opts))
186213
|> case do
187214
:ok -> :ok
188215
{:error, %Sqlite.Error{}} = ok -> ok
189-
err -> unexpected_response(err, :close)
190-
end
191-
end
192-
193-
@doc """
194-
Closes an (extended) prepared query and returns `:ok` or raises
195-
`Sqlite.Error` if there was an error. See `close/3`.
196-
"""
197-
@spec close!(conn, Keyword.t()) :: :ok
198-
def close!(conn, opts \\ []) do
199-
case close(conn, opts) do
200-
:ok -> :ok
201-
{:error, reason} -> raise Sqlite.Error, %{reason: reason}
202216
end
203217
end
204218

@@ -218,8 +232,4 @@ defmodule Sqlite do
218232

219233
Keyword.merge(defaults, opts)
220234
end
221-
222-
defp unexpected_response(err, fun) do
223-
raise "Unexpected response to #{fun}: #{inspect(err)}"
224-
end
225235
end

lib/sqlite/error.ex

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,9 @@ defmodule Sqlite.Error do
44
defexception [:message]
55

66
@typedoc "Various SQLite error."
7-
@type t :: %Sqlite.Error{}
7+
@type t :: %Sqlite.Error{
8+
message: binary
9+
}
810

9-
def exception(%{reason: message}) do
10-
%Sqlite.Error{message: message}
11-
end
12-
13-
def message(e) do
14-
e.message
15-
end
11+
def message(e), do: e.message
1612
end

lib/sqlite/query.ex

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,26 @@
11
defmodule Sqlite.Query do
22
@moduledoc "This module handles creation of SQL queries."
33

4-
defstruct [:name, :statement]
4+
defstruct [
5+
:statement,
6+
:column_types,
7+
:column_names,
8+
:sql
9+
]
510

611
@typedoc "Sqlite Query for execution."
712
@type t :: %__MODULE__{
8-
name: String.t(),
9-
statement: iodata
13+
statement: Esqlite3.prepared_statement(),
14+
sql: iodata,
15+
column_names: [iodata] | nil,
16+
column_types: [atom] | nil
1017
}
18+
19+
defimpl Inspect, for: __MODULE__ do
20+
def inspect(%{statement: {:statement, ref, _}}, _) when is_reference(ref) do
21+
String.replace(inspect(ref), "Reference", "Statement")
22+
end
23+
24+
def inspect(_, _), do: exit("Tried to inspect empty query!")
25+
end
1126
end

lib/sqlite/result.ex

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ defmodule Sqlite.Result do
99

1010
@type t :: %Sqlite.Result{
1111
columns: [String.t()],
12-
rows: [[term] | binary],
12+
rows: [[term]],
1313
num_rows: integer
1414
}
1515
end

lib/sqlite/server.ex

Lines changed: 86 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
defmodule Sqlite.Server do
22
@moduledoc "GenServer implementation for Sqlite."
33
use GenServer
4-
# alias Sqlite.{Query, Error, Result}
4+
alias Sqlite.Query
55

66
defmodule State do
77
@moduledoc false
@@ -37,27 +37,102 @@ defmodule Sqlite.Server do
3737
end
3838

3939
@impl GenServer
40-
def handle_call({:query, query, params, _opts}, _from, state) do
41-
Esqlite3.q(query.statement, params, state.database) |> IO.inspect()
42-
{:reply, {:error, %Sqlite.Error{message: "Not implemented"}}, state}
40+
def handle_call({:query, sql, params, opts}, _from, state) do
41+
try do
42+
with {:ok, %Query{} = q} <- build_query(sql, opts, state.database),
43+
:ok <- Esqlite3.bind(q.statement, params) do
44+
r = Esqlite3.fetchall(q.statement) |> build_result(q, state)
45+
{:reply, r, state}
46+
else
47+
err -> {:reply, error(err, state), state}
48+
end
49+
catch
50+
err -> {:reply, error(err, state), state}
51+
end
4352
end
4453

45-
def handle_call({:prepare, query, opts}, _from, state) do
46-
Esqlite3.prepare(query.statement, state.database, opts[:timeout]) |> IO.inspect()
47-
{:reply, {:error, %Sqlite.Error{message: "Not implemented"}}, state}
54+
def handle_call({:release_query, query, opts}, _from, state) do
55+
try do
56+
case Esqlite3.reset(query.statement, opts[:timeout]) do
57+
:ok -> {:reply, :ok, state}
58+
err -> {:reply, error(err, state), state}
59+
end
60+
catch
61+
err -> {:reply, error(err, state), state}
62+
end
63+
end
64+
65+
def handle_call({:prepare, sql, opts}, _from, state) do
66+
try do
67+
case build_query(sql, opts, state.database) do
68+
{:ok, %Query{} = q} ->
69+
{:reply, {:ok, q}, state}
70+
err ->
71+
{:reply, error(err, state), state}
72+
end
73+
catch
74+
err -> {:reply, error(err, state), state}
75+
end
4876
end
4977

5078
def handle_call({:execute, query, params, opts}, _from, state) do
51-
Esqlite3.exec(query.statement, params, state.database, opts[:timeout]) |> IO.inspect()
52-
{:reply, {:error, %Sqlite.Error{message: "Not implemented"}}, state}
79+
try do
80+
case Esqlite3.bind(query.statement, params, opts[:timeout]) do
81+
:ok ->
82+
r = Esqlite3.fetchall(query.statement) |> build_result(query, state)
83+
{:reply, r, state}
84+
85+
err ->
86+
{:reply, error(err, state), state}
87+
end
88+
catch
89+
err -> {:reply, error(err, state), state}
90+
end
5391
end
5492

55-
def handle_call({:close, _opts}, _from, state) do
56-
case Esqlite3.close(state.database) do
93+
def handle_call({:close, opts}, _from, state) do
94+
case Esqlite3.close(state.database, opts[:timeout]) do
5795
:ok -> {:stop, :normal, :ok, %{state | database: :closed}}
5896
{:error, reason} -> {:stop, reason, error(reason, state), state}
5997
end
6098
end
6199

100+
@spec error(any, State.t()) :: {:error, Sqlite.Error.t()}
101+
102+
defp error({:error, {:sqlite_error, msg}}, _state),
103+
do: {:error, %Sqlite.Error{message: to_string(msg)}}
104+
105+
defp error({:error, {:constraint, msg}}, _state),
106+
do: {:error, %Sqlite.Error{message: to_string(msg)}}
107+
108+
defp error({:error, msg}, _state) when is_atom(msg),
109+
do: {:error, %Sqlite.Error{message: to_string(msg)}}
110+
62111
defp error(reason, _state), do: {:error, %Sqlite.Error{message: reason}}
112+
113+
@spec build_query(iodata, Keyword.t(), Esqlite3.connection()) ::
114+
{:ok, Sqlite.Query.t()} | {:error, term}
115+
defp build_query(sql, opts, database) do
116+
timeout = opts[:timeout]
117+
118+
case Esqlite3.prepare(sql, database, timeout) do
119+
{:ok, statement} ->
120+
cn = Esqlite3.column_names(statement, timeout) |> Tuple.to_list()
121+
ct = Esqlite3.column_types(statement, timeout) |> Tuple.to_list()
122+
{:ok, %Sqlite.Query{column_names: cn, column_types: ct, statement: statement, sql: sql}}
123+
124+
err ->
125+
err
126+
end
127+
end
128+
129+
@spec build_result(any, Sqlite.Query.t(), State.t()) ::
130+
{:ok, Sqlite.Result.t()} | {:error, Sqlite.Error.t()}
131+
defp build_result({:error, _} = err, _q, state), do: error(err, state)
132+
133+
defp build_result(result, %Query{} = q, _state) when is_list(result) do
134+
rows = Enum.map(result, &Tuple.to_list(&1))
135+
num_rows = Enum.count(rows)
136+
{:ok, %Sqlite.Result{rows: rows, num_rows: num_rows, columns: q.column_names}}
137+
end
63138
end

0 commit comments

Comments
 (0)