@@ -17,6 +17,12 @@ defmodule Sqlitex.Server do
1717 {:ok, [%{a: 1, b: 1}]}
1818 iex> Sqlitex.Server.query_rows(:example, "SELECT * FROM t ORDER BY a LIMIT 2")
1919 {:ok, %{rows: [[1, 1], [2, 2]], columns: [:a, :b], types: [:INTEGER, :INTEGER]}}
20+ iex> Sqlitex.Server.prepare(:example, "SELECT * FROM t")
21+ {:ok, %{columns: [:a, :b], types: [:INTEGER, :INTEGER]}}
22+ # Subsequent queries using this exact statement will now operate more efficiently
23+ # because this statement has been cached.
24+ iex> Sqlitex.Server.prepare(:example, "INVALID SQL")
25+ {:error, {:sqlite_error, 'near "INVALID": syntax error'}}
2026 iex> Sqlitex.Server.stop(:example)
2127 :ok
2228 iex> :timer.sleep(10) # wait for the process to exit asynchronously
@@ -39,44 +45,68 @@ defmodule Sqlitex.Server do
3945
4046 use GenServer
4147
48+ alias Sqlitex.Statement
49+ alias Sqlitex.Server.StatementCache , as: Cache
50+
51+ @ doc """
52+ Starts a SQLite Server (GenServer) instance.
53+
54+ In addition to the options that are typically provided to `GenServer.start_link/3`,
55+ you can also specify `stmt_cache_size: (positive_integer)` to override the default
56+ limit (20) of statements that are cached when calling `prepare/3`.
57+ """
4258 def start_link ( db_path , opts \\ [ ] ) do
43- GenServer . start_link ( __MODULE__ , db_path , opts )
59+ stmt_cache_size = Keyword . get ( opts , :stmt_cache_size , 20 )
60+ GenServer . start_link ( __MODULE__ , { db_path , stmt_cache_size } , opts )
4461 end
4562
4663 ## GenServer callbacks
4764
48- def init ( db_path ) do
65+ def init ( { db_path , stmt_cache_size } )
66+ when is_integer ( stmt_cache_size ) and stmt_cache_size > 0
67+ do
4968 case Sqlitex . open ( db_path ) do
50- { :ok , db } -> { :ok , db }
69+ { :ok , db } -> { :ok , { db , __MODULE__ . StatementCache . new ( db , stmt_cache_size ) } }
5170 { :error , reason } -> { :stop , reason }
5271 end
5372 end
5473
55- def handle_call ( { :exec , sql } , _from , db ) do
74+ def handle_call ( { :exec , sql } , _from , { db , stmt_cache } ) do
5675 result = Sqlitex . exec ( db , sql )
57- { :reply , result , db }
76+ { :reply , result , { db , stmt_cache } }
77+ end
78+
79+ def handle_call ( { :query , sql , opts } , _from , { db , stmt_cache } ) do
80+ case query_impl ( sql , opts , stmt_cache ) do
81+ { :ok , result , new_cache } -> { :reply , { :ok , result } , { db , new_cache } }
82+ err -> { :reply , err , { db , stmt_cache } }
83+ end
5884 end
5985
60- def handle_call ( { :query , sql , opts } , _from , db ) do
61- rows = Sqlitex . query ( db , sql , opts )
62- { :reply , rows , db }
86+ def handle_call ( { :query_rows , sql , opts } , _from , { db , stmt_cache } ) do
87+ case query_rows_impl ( sql , opts , stmt_cache ) do
88+ { :ok , result , new_cache } -> { :reply , { :ok , result } , { db , new_cache } }
89+ err -> { :reply , err , { db , stmt_cache } }
90+ end
6391 end
6492
65- def handle_call ( { :query_rows , sql , opts } , _from , db ) do
66- rows = Sqlitex . query_rows ( db , sql , opts )
67- { :reply , rows , db }
93+ def handle_call ( { :prepare , sql } , _from , { db , stmt_cache } ) do
94+ case prepare_impl ( sql , stmt_cache ) do
95+ { :ok , result , new_cache } -> { :reply , { :ok , result } , { db , new_cache } }
96+ err -> { :reply , err , { db , stmt_cache } }
97+ end
6898 end
6999
70- def handle_call ( { :create_table , name , table_opts , cols } , _from , db ) do
100+ def handle_call ( { :create_table , name , table_opts , cols } , _from , { db , stmt_cache } ) do
71101 result = Sqlitex . create_table ( db , name , table_opts , cols )
72- { :reply , result , db }
102+ { :reply , result , { db , stmt_cache } }
73103 end
74104
75- def handle_cast ( :stop , db ) do
76- { :stop , :normal , db }
105+ def handle_cast ( :stop , { db , stmt_cache } ) do
106+ { :stop , :normal , { db , stmt_cache } }
77107 end
78108
79- def terminate ( _reason , db ) do
109+ def terminate ( _reason , { db , _stmt_cache } ) do
80110 Sqlitex . close ( db )
81111 :ok
82112 end
@@ -95,6 +125,28 @@ defmodule Sqlitex.Server do
95125 GenServer . call ( pid , { :query_rows , sql , opts } , timeout ( opts ) )
96126 end
97127
128+ @ doc """
129+ Prepares a SQL statement for future use.
130+
131+ This causes a call to [`sqlite3_prepare_v2`](https://sqlite.org/c3ref/prepare.html)
132+ to be executed in the Server process. To protect the reference to the corresponding
133+ [`sqlite3_stmt` struct](https://sqlite.org/c3ref/stmt.html) from misuse in other
134+ processes, that reference is not passed back. Instead, prepared statements are
135+ cached in the Server process. If a subsequent call to `query/3` or `query_rows/3`
136+ is made with a matching SQL statement, the prepared statement is reused.
137+
138+ Prepared statements are purged from the cache when the cache exceeds a pre-set
139+ limit (20 statements by default).
140+
141+ Returns summary information about the prepared statement
142+ `{:ok, %{columns: [:column1_name, :column2_name,... ], types: [:column1_type, ...]}}`
143+ on success or `{:error, {:reason_code, 'SQLite message'}}` if the statement
144+ could not be prepared.
145+ """
146+ def prepare ( pid , sql , opts \\ [ ] ) do
147+ GenServer . call ( pid , { :prepare , sql } , timeout ( opts ) )
148+ end
149+
98150 def create_table ( pid , name , table_opts \\ [ ] , cols ) do
99151 GenServer . call ( pid , { :create_table , name , table_opts , cols } )
100152 end
@@ -105,5 +157,26 @@ defmodule Sqlitex.Server do
105157
106158 ## Helpers
107159
160+ defp query_impl ( sql , opts , stmt_cache ) do
161+ with { % Cache { } = new_cache , stmt } <- Cache . prepare ( stmt_cache , sql ) ,
162+ { :ok , stmt } <- Statement . bind_values ( stmt , Keyword . get ( opts , :bind , [ ] ) ) ,
163+ { :ok , rows } <- Statement . fetch_all ( stmt , Keyword . get ( opts , :into , [ ] ) ) ,
164+ do: { :ok , rows , new_cache }
165+ end
166+
167+ defp query_rows_impl ( sql , opts , stmt_cache ) do
168+ with { % Cache { } = new_cache , stmt } <- Cache . prepare ( stmt_cache , sql ) ,
169+ { :ok , stmt } <- Statement . bind_values ( stmt , Keyword . get ( opts , :bind , [ ] ) ) ,
170+ { :ok , rows } <- Statement . fetch_all ( stmt , :raw_list ) ,
171+ do: { :ok ,
172+ % { rows: rows , columns: stmt . column_names , types: stmt . column_types } ,
173+ new_cache }
174+ end
175+
176+ defp prepare_impl ( sql , stmt_cache ) do
177+ with { % Cache { } = new_cache , stmt } <- Cache . prepare ( stmt_cache , sql ) ,
178+ do: { :ok , % { columns: stmt . column_names , types: stmt . column_types } , new_cache }
179+ end
180+
108181 defp timeout ( kwopts ) , do: Keyword . get ( kwopts , :timeout , 5000 )
109182end
0 commit comments