1
1
defmodule Exqlite.Connection do
2
-
3
- @ doc """
2
+ @ moduledoc """
4
3
This module imlements connection details as defined in DBProtocol.
5
4
6
5
Notes:
@@ -13,6 +12,7 @@ defmodule Exqlite.Connection do
13
12
alias Exqlite.Sqlite3
14
13
alias Exqlite.Error
15
14
alias Exqlite.Result
15
+ alias Exqlite.Query
16
16
17
17
defstruct [
18
18
:db
@@ -53,17 +53,21 @@ defmodule Exqlite.Connection do
53
53
{ :ok , state }
54
54
end
55
55
56
- ###----------------------------------
56
+ ### ----------------------------------
57
57
# handle_* implementations
58
- ###----------------------------------
58
+ ### ----------------------------------
59
59
60
60
@ impl true
61
- def handle_prepare ( query , _opts , state ) do
61
+ def handle_prepare ( % Query { } = query , _opts , state ) do
62
62
# TODO: we may want to cache prepared queries like Myxql does
63
63
# 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 )
67
71
end
68
72
end
69
73
@@ -82,9 +86,14 @@ defmodule Exqlite.Connection do
82
86
83
87
# TODO: handle/track SAVEPOINT
84
88
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 )
88
97
end
89
98
end
90
99
@@ -105,9 +114,9 @@ defmodule Exqlite.Connection do
105
114
handle_transaction ( :rollback , "ROLLBACK" , state )
106
115
end
107
116
108
- ###----------------------------------
117
+ ### ----------------------------------
109
118
# Internal functions and helpers
110
- ###----------------------------------
119
+ ### ----------------------------------
111
120
112
121
defp do_connect ( path ) do
113
122
case Sqlite3 . open ( path ) do
@@ -123,6 +132,57 @@ defmodule Exqlite.Connection do
123
132
end
124
133
end
125
134
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
+
126
186
defp handle_transaction ( call , statement , state ) do
127
187
case Sqlite3 . execute ( state . db , statement ) do
128
188
:ok ->
0 commit comments