Skip to content

Commit 08a0a36

Browse files
authored
onboard transaction.exs suite, various transaction fixes (#112)
* fix issue around not properly tracking transaction state, not properly rollingback when closing connection, onboard transaction.exs tests * use make_atom instead
1 parent e67d395 commit 08a0a36

File tree

5 files changed

+57
-23
lines changed

5 files changed

+57
-23
lines changed

c_src/sqlite3_nif.c

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,7 @@ exqlite_close(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
145145
assert(env);
146146

147147
connection_t* conn = NULL;
148+
int rc = SQLITE_OK;
148149

149150
if (argc != 1) {
150151
return enif_make_badarg(env);
@@ -154,7 +155,21 @@ exqlite_close(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
154155
return make_error_tuple(env, "invalid_connection");
155156
}
156157

158+
int autocommit = sqlite3_get_autocommit(conn->db);
159+
if (autocommit == 0) {
160+
rc = sqlite3_exec(conn->db, "ROLLBACK;", NULL, NULL, NULL);
161+
if (rc != SQLITE_OK) {
162+
return make_sqlite3_error_tuple(env, rc, conn->db);
163+
}
164+
}
165+
166+
// note: _v2 may not fully close the connection, hence why we check if
167+
// any transaction is open above, to make sure other connections aren't blocked.
168+
// v1 is guaranteed to close or error, but will return error if any
169+
// unfinalized statements, which we likely have, as we rely on the destructors
170+
// to later run to clean those up
157171
sqlite3_close_v2(conn->db);
172+
158173
conn->db = NULL;
159174

160175
return make_atom(env, "ok");
@@ -539,6 +554,27 @@ exqlite_last_insert_rowid(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
539554
return make_ok_tuple(env, enif_make_int64(env, last_rowid));
540555
}
541556

557+
static ERL_NIF_TERM
558+
exqlite_transaction_status(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
559+
{
560+
assert(env);
561+
562+
connection_t* conn = NULL;
563+
564+
if (argc != 1) {
565+
return enif_make_badarg(env);
566+
}
567+
568+
if (!enif_get_resource(env, argv[0], connection_type, (void**)&conn)) {
569+
return make_error_tuple(env, "invalid_connection");
570+
}
571+
572+
int autocommit = sqlite3_get_autocommit(conn->db);
573+
return make_ok_tuple(
574+
env,
575+
autocommit == 0 ? make_atom(env, "transaction") : make_atom(env, "idle"));
576+
}
577+
542578
static void
543579
connection_type_destructor(ErlNifEnv* env, void* arg)
544580
{
@@ -611,6 +647,7 @@ static ErlNifFunc nif_funcs[] = {
611647
{"step", 2, exqlite_step, ERL_NIF_DIRTY_JOB_IO_BOUND},
612648
{"columns", 2, exqlite_columns, ERL_NIF_DIRTY_JOB_IO_BOUND},
613649
{"last_insert_rowid", 1, exqlite_last_insert_rowid, ERL_NIF_DIRTY_JOB_IO_BOUND},
650+
{"transaction_status", 1, exqlite_transaction_status, ERL_NIF_DIRTY_JOB_IO_BOUND},
614651
};
615652

616653
ERL_NIF_INIT(Elixir.Exqlite.Sqlite3NIF, nif_funcs, on_load, NULL, NULL, NULL)

integration_test/exqlite/all_test.exs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ Code.require_file "#{ecto_sql}/integration_test/sql/sandbox.exs", __DIR__
2222
Code.require_file "#{ecto_sql}/integration_test/sql/sql.exs", __DIR__
2323
Code.require_file "#{ecto_sql}/integration_test/sql/stream.exs", __DIR__
2424
Code.require_file "#{ecto_sql}/integration_test/sql/subquery.exs", __DIR__
25-
# Code.require_file "#{ecto_sql}/integration_test/sql/transaction.exs", __DIR__
25+
Code.require_file "#{ecto_sql}/integration_test/sql/transaction.exs", __DIR__
2626

2727
# added :modify_column and :alter_foreign_key
2828
Code.require_file "./ecto_sql/migration.exs", __DIR__

lib/exqlite/connection.ex

Lines changed: 13 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -437,6 +437,7 @@ defmodule Exqlite.Connection do
437437
with {:ok, query} <- bind_params(query, params, state),
438438
{:ok, columns} <- get_columns(query, state),
439439
{:ok, rows} <- get_rows(query, state),
440+
{:ok, transaction_status} <- Sqlite3.transaction_status(state.db),
440441
changes <- maybe_changes(state.db, query) do
441442
case query.command do
442443
command when command in [:delete, :insert, :update] ->
@@ -448,7 +449,7 @@ defmodule Exqlite.Connection do
448449
num_rows: changes,
449450
rows: maybe_rows(rows)
450451
),
451-
state
452+
%{state | transaction_status: transaction_status}
452453
}
453454

454455
_ ->
@@ -461,7 +462,7 @@ defmodule Exqlite.Connection do
461462
rows: rows,
462463
num_rows: Enum.count(rows)
463464
),
464-
state
465+
%{state | transaction_status: transaction_status}
465466
}
466467
end
467468
end
@@ -489,26 +490,16 @@ defmodule Exqlite.Connection do
489490
end
490491

491492
defp handle_transaction(call, statement, state) do
492-
case Sqlite3.execute(state.db, statement) do
493-
:ok ->
494-
result = %Result{
495-
command: call,
496-
rows: [],
497-
columns: [],
498-
num_rows: 0
499-
}
500-
501-
case call do
502-
:rollback ->
503-
{:ok, result, %{state | transaction_status: :idle}}
504-
505-
:commit ->
506-
{:ok, result, %{state | transaction_status: :idle}}
507-
508-
_ ->
509-
{:ok, result, %{state | transaction_status: :transaction}}
510-
end
511-
493+
with :ok <- Sqlite3.execute(state.db, statement),
494+
{:ok, transaction_status} <- Sqlite3.transaction_status(state.db) do
495+
result = %Result{
496+
command: call,
497+
rows: [],
498+
columns: [],
499+
num_rows: 0
500+
}
501+
{:ok, result, %{state | transaction_status: transaction_status}}
502+
else
512503
{:error, reason} ->
513504
{:disconnect, %Error{message: reason}, state}
514505
end

lib/exqlite/sqlite3.ex

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,9 @@ defmodule Exqlite.Sqlite3 do
8181
@spec last_insert_rowid(db()) :: {:ok, integer()}
8282
def last_insert_rowid(conn), do: Sqlite3NIF.last_insert_rowid(conn)
8383

84+
@spec transaction_status(db()) :: {:ok, :idle | :transaction}
85+
def transaction_status(conn), do: Sqlite3NIF.transaction_status(conn)
86+
8487
@doc """
8588
Causes the database connection to free as much memory as it can. This is
8689
useful if you are on a memory restricted system.

lib/exqlite/sqlite3_nif.ex

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,5 +43,8 @@ defmodule Exqlite.Sqlite3NIF do
4343
@spec last_insert_rowid(db()) :: {:ok, integer()}
4444
def last_insert_rowid(_conn), do: :erlang.nif_error(:not_loaded)
4545

46+
@spec transaction_status(db()) :: {:ok, :idle | :transaction}
47+
def transaction_status(_conn), do: :erlang.nif_error(:not_loaded)
48+
4649
# TODO: add statement inspection tooling https://sqlite.org/c3ref/expanded_sql.html
4750
end

0 commit comments

Comments
 (0)