Skip to content

Commit 1242c1e

Browse files
committed
feat(connection): handle_execute + prepare
1 parent 99f4f09 commit 1242c1e

File tree

1 file changed

+73
-13
lines changed

1 file changed

+73
-13
lines changed

lib/exqlite/connection.ex

Lines changed: 73 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
defmodule Exqlite.Connection do
2-
3-
@doc """
2+
@moduledoc """
43
This module imlements connection details as defined in DBProtocol.
54
65
Notes:
@@ -13,6 +12,7 @@ defmodule Exqlite.Connection do
1312
alias Exqlite.Sqlite3
1413
alias Exqlite.Error
1514
alias Exqlite.Result
15+
alias Exqlite.Query
1616

1717
defstruct [
1818
:db
@@ -53,17 +53,21 @@ defmodule Exqlite.Connection do
5353
{:ok, state}
5454
end
5555

56-
###----------------------------------
56+
### ----------------------------------
5757
# handle_* implementations
58-
###----------------------------------
58+
### ----------------------------------
5959

6060
@impl true
61-
def handle_prepare(query, _opts, state) do
61+
def handle_prepare(%Query{} = query, _opts, state) do
6262
# TODO: we may want to cache prepared queries like Myxql does
6363
# for now we just invoke sqlite directly
64-
case Sqlite3.prepare(state.db, query) do
65-
{:ok, statement} -> {:ok, statement, state}
66-
{:error, reason} -> %Error{message: reason}
64+
prepare(query, state)
65+
end
66+
67+
@impl true
68+
def handle_execute(%Query{} = query, params, _opts, state) do
69+
with {:ok, query, state} <- maybe_prepare(query, state) do
70+
execute(query, params, state)
6771
end
6872
end
6973

@@ -82,9 +86,14 @@ defmodule Exqlite.Connection do
8286

8387
# TODO: handle/track SAVEPOINT
8488
case Keyword.get(opts, :mode, :deferred) do
85-
:immediate -> handle_transaction(:begin, "BEGIN IMMEDIATE TRANSACTION", state)
86-
:exclusive -> handle_transaction(:begin, "BEGIN EXCLUSIVE TRANSACTION", state)
87-
mode when mode in [nil, :deferred] -> handle_transaction(:begin, "BEGIN TRANSACTION", state)
89+
:immediate ->
90+
handle_transaction(:begin, "BEGIN IMMEDIATE TRANSACTION", state)
91+
92+
:exclusive ->
93+
handle_transaction(:begin, "BEGIN EXCLUSIVE TRANSACTION", state)
94+
95+
mode when mode in [nil, :deferred] ->
96+
handle_transaction(:begin, "BEGIN TRANSACTION", state)
8897
end
8998
end
9099

@@ -105,9 +114,9 @@ defmodule Exqlite.Connection do
105114
handle_transaction(:rollback, "ROLLBACK", state)
106115
end
107116

108-
###----------------------------------
117+
### ----------------------------------
109118
# Internal functions and helpers
110-
###----------------------------------
119+
### ----------------------------------
111120

112121
defp do_connect(path) do
113122
case Sqlite3.open(path) do
@@ -123,6 +132,57 @@ defmodule Exqlite.Connection do
123132
end
124133
end
125134

135+
defp maybe_prepare(%Query{ref: ref} = query, state) when ref != nil, do: {:ok, query, state}
136+
defp maybe_prepare(%Query{} = query, state), do: prepare(query, state)
137+
138+
defp prepare(%Query{statement: statement} = query, state) do
139+
case Sqlite3.prepare(state.db, statement) do
140+
{:ok, ref} -> {:ok, %{query | ref: ref}, state}
141+
{:error, reason} -> {:error, %Error{message: reason}}
142+
end
143+
end
144+
145+
defp execute(%Query{} = query, params, state) do
146+
with {:ok, query} <- bind_params(query, params, state) do
147+
do_execute(query, state, %Result{})
148+
end
149+
end
150+
151+
defp do_execute(%Query{ref: ref} = query, state, %Result{} = result) do
152+
case Sqlite3.step(state.db, query.ref) do
153+
:done ->
154+
# TODO: this query may fail, we need to properly propagate this
155+
{:ok, columns} = Sqlite3.columns(state.db, ref)
156+
157+
# TODO: this may fail, we need to properly propagate this
158+
Sqlite3.close(ref)
159+
{:ok, %{result | columns: columns}}
160+
161+
{:row, row} ->
162+
# TODO: we need something better than simply appending rows
163+
do_execute(query, state, %{result | rows: result.rows ++ [row]})
164+
165+
:busy ->
166+
{:error, %Error{message: "Database busy"}}
167+
end
168+
end
169+
170+
defp bind_params(%Query{ref: ref} = query, params, state) do
171+
# TODO:
172+
# - Add parameter translation to sqlite types. See e.g.
173+
# https://github.com/elixir-sqlite/sqlitex/blob/master/lib/sqlitex/statement.ex#L274
174+
# - Do we do anything special to distinguish the different types of
175+
# parameters? See https://www.sqlite.org/lang_expr.html#varparam and
176+
# https://www.sqlite.org/c3ref/bind_blob.html E.g. we can accept a map of params
177+
# that binds values to named params. We can look up their indices via
178+
# https://www.sqlite.org/c3ref/bind_parameter_index.html
179+
case Sqlite3.bind(state.db, ref, params) do
180+
:ok -> {:ok, query}
181+
{:error, {code, reason}} -> {:error, %Error{message: "#{reason}. Code: #{code}"}}
182+
{:error, reason} -> {:error, %Error{message: reason}}
183+
end
184+
end
185+
126186
defp handle_transaction(call, statement, state) do
127187
case Sqlite3.execute(state.db, statement) do
128188
:ok ->

0 commit comments

Comments
 (0)