Skip to content

Accept a list of modes to open db with nomutex option. #284

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Apr 23, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 6 additions & 3 deletions lib/exqlite/connection.ex
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ defmodule Exqlite.Connection do

@type connection_opt() ::
{:database, String.t()}
| {:mode, Sqlite3.open_opt()}
| {:journal_mode, journal_mode()}
| {:temp_store, temp_store()}
| {:synchronous, synchronous()}
Expand Down Expand Up @@ -91,8 +92,10 @@ defmodule Exqlite.Connection do
* `:database` - The path to the database. In memory is allowed. You can use
`:memory` or `":memory:"` to designate that.
* `:mode` - use `:readwrite` to open the database for reading and writing
or `:readonly` to open it in read-only mode. `:readwrite` will also create
, `:readonly` to open it in read-only mode or `[:readonly | :readwrite, :nomutex]`
to open it with no mutex mode. `:readwrite` will also create
the database if it doesn't already exist. Defaults to `:readwrite`.
Note: [:readwrite, :nomutex] is not recommended.
* `:journal_mode` - Sets the journal mode for the sqlite connection. Can be
one of the following `:delete`, `:truncate`, `:persist`, `:memory`,
`:wal`, or `:off`. Defaults to `:delete`. It is recommended that you use
Expand Down Expand Up @@ -159,8 +162,8 @@ defmodule Exqlite.Connection do
"./priv/sqlite/\#{arch_dir}/vss0"
]
```
* `:before_disconnect` - A function to run before disconnect, either a
2-arity fun or `{module, function, args}` with the close reason and
* `:before_disconnect` - A function to run before disconnect, either a
2-arity fun or `{module, function, args}` with the close reason and
`t:Exqlite.Connection.t/0` prepended to `args` or `nil` (default: `nil`)

For more information about the options above, see [sqlite documentation][1]
Expand Down
38 changes: 33 additions & 5 deletions lib/exqlite/sqlite3.ex
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@ defmodule Exqlite.Sqlite3 do
@type statement() :: reference()
@type reason() :: atom() | String.t()
@type row() :: list()
@type open_opt :: {:mode, :readwrite | :readonly}
@type open_mode :: :readwrite | :readonly | :nomutex
@type open_opt :: {:mode, :readwrite | :readonly | [open_mode()]}

@doc """
Opens a new sqlite database at the Path provided.
Expand All @@ -29,26 +30,53 @@ defmodule Exqlite.Sqlite3 do
## Options

* `:mode` - use `:readwrite` to open the database for reading and writing
or `:readonly` to open it in read-only mode. `:readwrite` will also create
, `:readonly` to open it in read-only mode or `[:readonly | :readwrite, :nomutex]`
to open it with no mutex mode. `:readwrite` will also create
the database if it doesn't already exist. Defaults to `:readwrite`.
Note: [:readwrite, :nomutex] is not recommended.
"""
@spec open(String.t(), [open_opt()]) :: {:ok, db()} | {:error, reason()}
def open(path, opts \\ []) do
mode = Keyword.get(opts, :mode, :readwrite)
Sqlite3NIF.open(String.to_charlist(path), flags_from_mode(mode))
end

defp flags_from_mode(:nomutex) do
raise ArgumentError,
"expected mode to be `:readwrite` or `:readonly`, can't use a single :nomutex mode"
end

defp flags_from_mode(:readwrite),
do: Flags.put_file_open_flags([:sqlite_open_readwrite, :sqlite_open_create])
do: do_flags_from_mode([:readwrite], [])

defp flags_from_mode(:readonly),
do: Flags.put_file_open_flags([:sqlite_open_readonly])
do: do_flags_from_mode([:readonly], [])

defp flags_from_mode([_ | _] = modes),
do: do_flags_from_mode(modes, [])

defp flags_from_mode(mode) do
raise ArgumentError,
"expected mode to be `:readwrite` or `:readonly`, but received #{inspect(mode)}"
"expected mode to be `:readwrite`, `:readonly` or list of modes, but received #{inspect(mode)}"
end

defp do_flags_from_mode([:readwrite | tail], acc),
do: do_flags_from_mode(tail, [:sqlite_open_readwrite, :sqlite_open_create | acc])

defp do_flags_from_mode([:readonly | tail], acc),
do: do_flags_from_mode(tail, [:sqlite_open_readonly | acc])

defp do_flags_from_mode([:nomutex | tail], acc),
do: do_flags_from_mode(tail, [:sqlite_open_nomutex | acc])

defp do_flags_from_mode([mode | _tail], _acc) do
raise ArgumentError,
"expected mode to be `:readwrite`, `:readonly` or `:nomutex`, but received #{inspect(mode)}"
end

defp do_flags_from_mode([], acc),
do: Flags.put_file_open_flags(acc)

@doc """
Closes the database and releases any underlying resources.
"""
Expand Down
45 changes: 44 additions & 1 deletion test/exqlite/sqlite3_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -53,16 +53,59 @@ defmodule Exqlite.Sqlite3Test do
Sqlite3.execute(ro_conn, insert_value_query)
end

test "opens a database in a list of mode" do
# Create database with readwrite connection
{:ok, path} = Temp.path()
{:ok, rw_conn} = Sqlite3.open(path)

create_table_query = "create table test (id integer primary key, stuff text)"
:ok = Sqlite3.execute(rw_conn, create_table_query)

insert_value_query = "insert into test (stuff) values ('This is a test')"
:ok = Sqlite3.execute(rw_conn, insert_value_query)

# Read from database with a readonly connection
{:ok, ro_conn} = Sqlite3.open(path, mode: [:readonly, :nomutex])

select_query = "select id, stuff from test order by id asc"
{:ok, statement} = Sqlite3.prepare(ro_conn, select_query)
{:row, columns} = Sqlite3.step(ro_conn, statement)

assert [1, "This is a test"] == columns
end

test "opens a database with invalid mode" do
{:ok, path} = Temp.path()

msg =
"expected mode to be `:readwrite` or `:readonly`, but received :notarealmode"
"expected mode to be `:readwrite`, `:readonly` or list of modes, but received :notarealmode"

assert_raise ArgumentError, msg, fn ->
Sqlite3.open(path, mode: :notarealmode)
end
end

test "opens a database with invalid single nomutex mode" do
{:ok, path} = Temp.path()

msg =
"expected mode to be `:readwrite` or `:readonly`, can't use a single :nomutex mode"

assert_raise ArgumentError, msg, fn ->
Sqlite3.open(path, mode: :nomutex)
end
end

test "opens a database with invalid list of mode" do
{:ok, path} = Temp.path()

msg =
"expected mode to be `:readwrite`, `:readonly` or `:nomutex`, but received :notarealmode"

assert_raise ArgumentError, msg, fn ->
Sqlite3.open(path, mode: [:notarealmode])
end
end
end

describe ".close/2" do
Expand Down