Skip to content

Commit 2c97668

Browse files
greg-rychlewskihkrutzerjosevalimGreg Rychlewski
authored
Reuse prepared statements in prepare: :unnamed (#146)
Co-authored-by: Hans Krutzer <[email protected]> Co-authored-by: José Valim <[email protected]> Co-authored-by: Greg Rychlewski <[email protected]>
1 parent 1014f1c commit 2c97668

File tree

3 files changed

+87
-14
lines changed

3 files changed

+87
-14
lines changed

lib/myxql/connection.ex

Lines changed: 27 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ defmodule MyXQL.Connection do
1313
prepare: :named,
1414
queries: nil,
1515
transaction_status: :idle,
16-
last_ref: nil
16+
last_query: nil
1717
]
1818

1919
@impl true
@@ -29,7 +29,7 @@ defmodule MyXQL.Connection do
2929
prepare: prepare,
3030
disconnect_on_error_codes: Keyword.fetch!(opts, :disconnect_on_error_codes),
3131
ping_timeout: ping_timeout,
32-
queries: queries_new()
32+
queries: queries_new(prepare)
3333
}
3434

3535
{:ok, state}
@@ -64,7 +64,8 @@ defmodule MyXQL.Connection do
6464
query = rename_query(state, query)
6565

6666
if cached_query = queries_get(state, query) do
67-
{:ok, cached_query, %{state | last_ref: cached_query.ref}}
67+
%{ref: ref, statement_id: statement_id} = cached_query
68+
{:ok, cached_query, %{state | last_query: {ref, statement_id}}}
6869
else
6970
case prepare(query, state) do
7071
{:ok, _, _} = ok ->
@@ -469,14 +470,21 @@ defmodule MyXQL.Connection do
469470
ref = make_ref()
470471
query = %{query | num_params: num_params, statement_id: statement_id, ref: ref}
471472
queries_put(state, query)
472-
{:ok, query, %{state | last_ref: ref}}
473+
{:ok, query, %{state | last_query: {ref, statement_id}}}
473474

474475
result ->
475476
result(result, query, state)
476477
end
477478
end
478479

479-
defp maybe_reprepare(%{ref: ref} = query, %{last_ref: ref} = state), do: {:ok, query, state}
480+
defp maybe_reprepare(%{ref: ref} = query, %{last_query: {ref, _statement_id}} = state) do
481+
{:ok, query, state}
482+
end
483+
484+
defp maybe_reprepare(query, %{queries: nil, last_query: {_ref, statement_id}} = state) do
485+
Client.com_stmt_close(state.client, statement_id)
486+
prepare(query, state)
487+
end
480488

481489
defp maybe_reprepare(query, state) do
482490
if query_member?(state, query) do
@@ -490,12 +498,16 @@ defmodule MyXQL.Connection do
490498
%{state | cursors: Map.delete(state.cursors, cursor.ref)}
491499
end
492500

493-
# Close unnamed queries after executing them
501+
# When prepare is :named, close unnamed queries after executing them.
502+
# When prepare is :unnamed, queries will be closed the next time a
503+
# query is prepared or a different query is executed. This allows us
504+
# to re-execute the same unnamed query without preparing it again.
505+
defp maybe_close(_query, %{queries: nil} = state), do: {:ok, state}
494506
defp maybe_close(%{name: ""} = query, state), do: close(query, state)
495507
defp maybe_close(_query, state), do: {:ok, state}
496508

497-
defp close(%{ref: ref} = query, %{last_ref: ref} = state) do
498-
close(query, %{state | last_ref: nil})
509+
defp close(%{ref: ref} = query, %{last_query: {ref, _statement_id}} = state) do
510+
close(query, %{state | last_query: nil})
499511
end
500512

501513
defp close(query, state) do
@@ -516,7 +528,8 @@ defmodule MyXQL.Connection do
516528

517529
## Cache query handling
518530

519-
defp queries_new(), do: :ets.new(__MODULE__, [:set, :public])
531+
defp queries_new(:unnamed), do: nil
532+
defp queries_new(_), do: :ets.new(__MODULE__, [:set, :public])
520533

521534
defp queries_put(%{queries: nil}, _), do: :ok
522535
defp queries_put(_state, %{name: ""}), do: :ok
@@ -570,7 +583,11 @@ defmodule MyXQL.Connection do
570583
end
571584
end
572585

573-
defp queries_get(%{queries: nil}, _), do: nil
586+
defp queries_get(%{queries: nil, last_query: {_ref, statement_id}} = state, _query) do
587+
Client.com_stmt_close(state.client, statement_id)
588+
nil
589+
end
590+
574591
defp queries_get(_state, %{name: ""}), do: nil
575592

576593
defp queries_get(state, %{cache: :reference, name: name}) do

test/myxql/sync_test.exs

Lines changed: 45 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,36 @@ defmodule MyXQL.SyncTest do
88
{:ok, conn} = MyXQL.start_link(@opts ++ [prepare: :unnamed])
99
assert prepared_stmt_count() == 0
1010

11+
# Multiple queries do not increase statement count
1112
MyXQL.query!(conn, "SELECT 42", [], cache_statement: "42")
13+
assert prepared_stmt_count() == 1
14+
15+
MyXQL.query!(conn, "SELECT 43", [], cache_statement: "43")
16+
assert prepared_stmt_count() == 1
17+
18+
# Multiple preparations don't increase statement count
19+
{:ok, _query} = MyXQL.prepare(conn, "1", "SELECT 1")
20+
assert prepared_stmt_count() == 1
21+
22+
{:ok, _query} = MyXQL.prepare(conn, "2", "SELECT 2")
23+
assert prepared_stmt_count() == 1
24+
end
25+
26+
test "do not leak statements with prepare: :named" do
27+
{:ok, conn} = MyXQL.start_link(@opts)
1228
assert prepared_stmt_count() == 0
29+
30+
MyXQL.query!(conn, "SELECT 42", [], cache_statement: "42")
31+
assert prepared_stmt_count() == 1
32+
33+
MyXQL.query!(conn, "SELECT 1337", [], cache_statement: "1337")
34+
assert prepared_stmt_count() == 2
35+
36+
MyXQL.query!(conn, "SELECT 42", [], cache_statement: "42")
37+
assert prepared_stmt_count() == 2
38+
39+
MyXQL.query!(conn, "SELECT 43", [])
40+
assert prepared_stmt_count() == 2
1341
end
1442

1543
test "do not leak statements with rebound :cache_statement" do
@@ -23,15 +51,31 @@ defmodule MyXQL.SyncTest do
2351
assert prepared_stmt_count() == 1
2452
end
2553

26-
test "do not leak statements with insert and failed insert" do
54+
test "do not leak statements with insert and failed insert with prepare: :named" do
2755
{:ok, conn} = MyXQL.start_link(@opts)
2856
assert prepared_stmt_count() == 0
57+
2958
{:ok, _} = MyXQL.query(conn, "INSERT INTO uniques(a) VALUES (1)")
3059
assert prepared_stmt_count() == 0
60+
3161
{:error, _} = MyXQL.query(conn, "INSERT INTO uniques(a) VALUES (1)")
3262
assert prepared_stmt_count() == 0
3363
end
3464

65+
test "do not leak statements with insert and failed insert with prepare: :unnamed" do
66+
{:ok, conn} = MyXQL.start_link(@opts ++ [prepare: :unnamed])
67+
assert prepared_stmt_count() == 0
68+
69+
{:ok, _} = MyXQL.query(conn, "INSERT INTO uniques(a) VALUES (2)")
70+
assert prepared_stmt_count() == 1
71+
72+
{:error, _} = MyXQL.query(conn, "INSERT INTO uniques(a) VALUES (2)")
73+
assert prepared_stmt_count() == 1
74+
75+
MyXQL.query!(conn, "SELECT 123", [], cache_statement: "123")
76+
assert prepared_stmt_count() == 1
77+
end
78+
3579
test "do not leak statements on multiple executions of the same name in prepare_execute" do
3680
{:ok, conn} = MyXQL.start_link(@opts)
3781
{:ok, _, _} = MyXQL.prepare_execute(conn, "foo", "SELECT 42")

test/myxql_test.exs

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -205,14 +205,26 @@ defmodule MyXQLTest do
205205
assert query == query3
206206
end
207207

208-
test ":unnamed" do
208+
test ":unnamed re-executes last query without preparing" do
209209
{:ok, pid} = MyXQL.start_link(@opts ++ [prepare: :unnamed])
210210
{:ok, query} = MyXQL.prepare(pid, "1", "SELECT 1")
211211
{:ok, query2, _} = MyXQL.execute(pid, query, [])
212212
assert query == query2
213213
{:ok, query3, _} = MyXQL.execute(pid, query, [])
214-
assert query2.ref != query3.ref
215-
assert query2.statement_id != query3.statement_id
214+
assert query2 == query3
215+
end
216+
217+
test ":unnamed re-prepares if last query is not the same" do
218+
{:ok, pid} = MyXQL.start_link(@opts ++ [prepare: :unnamed])
219+
220+
{:ok, query1} = MyXQL.prepare(pid, "1", "SELECT 1")
221+
{:ok, query2} = MyXQL.prepare(pid, "2", "SELECT 2")
222+
223+
{:ok, query1b, _} = MyXQL.execute(pid, query1, [])
224+
{:ok, query2b, _} = MyXQL.execute(pid, query2, [])
225+
226+
assert query1 != query1b
227+
assert query2 != query2b
216228
end
217229

218230
test ":force_named" do

0 commit comments

Comments
 (0)