Skip to content

Proposal: align :mode open flags with sqlite3_open_v2 #347

Description

@Woody88

I would like to know if you are interested in aligning database open modes (:mode on Sqlite3.open, Connection and start_link) with sqlite3_open_v2 instead.

As of now, Exqlite.Sqlite3.open and Exqlite.Connection and Exqlite.start_link map to :mode in a way that mirrors legacy sqlite3_open function (read/write + create bundled), not the finer-grained sqlite3_open_v2 flags.

I'd be happy to implement a change if there's interest; this issue is only to align on direction.

Current behavior

  • :mode defaults to :readwrite, which sets SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE.
    defp do_flags_from_mode([:readwrite | tail], acc),
    do: do_flags_from_mode(tail, [:sqlite_open_readwrite, :sqlite_open_create | acc])
  • :readonly exists and requires the file to exist.
  • Connection.connect always calls mkdir_p on the parent directory before Sqlite3.open/2. -
    defp do_connect(database, options) do
    with {:ok, directory} <- resolve_directory(database),
    :ok <- mkdir_p(directory),
    {:ok, db} <- Sqlite3.open(database, options),

    I can see the comment here, but not sure of the reason. I'm asking wheter that should stay unconditional. SQLite does not do this even with URI mode=rwc. -
    # SQLITE_OPEN_CREATE will create the DB file if not existing, but
    # will not create intermediary directories if they are missing.
    # So let's preemptively create the intermediate directories here
    # before trying to open the DB file.
  • The project is compiled with SQLITE_USE_URI=1 (Makefile), so URI paths like file:path/to/db?mode=rw / mode=rwc / mode=ro can work, but this is not documented on Connection and is easy to miss when using :mode keywords.

Possible Approaches

Option A - Align :readwrite with SQLite (breaking)

Mode Flags
:readwrite READWRITE only
:readwrite_create READWRITE | CREATE

Option B - Adding a new mode (non-breaking)

Keep :readwrite (READWRITE \| CREATE).

Add a new mode, e.g. :readwrite_only or :readwrite_existing for READWRITE without CREATE.

mkdir_p

Should parent-directory creation remain unconditional on Connection, or become opt-in (e.g. ensure_path: true) for callers who want strict SQLite semantics?

Motivation

I went down this path because of kino_db SQLite smart cell in Livebook. I gave it a typo'd path and it silently created a new empty database instead of erroring, which then showed up as a confusing "no such table." I was expecting a wrong path failure instead of it fabricating the state - I was also not able to test it by setting the modes because it doesn't have a selector for it except for using the URI syntax. However, that is another topic for kino_db maintainers.

Questions

  1. Is aligning with sqlite3_open_v2 flag semantics a goal this library would pursue?
  2. Preference for Option A vs Option B?
  3. Should mkdir_p be part of the same change or handled separately?
  4. Any concerns for downstream users? (I'm thinking ecto_sqlite3)

I might be missing context, happy to adjust based on feedback.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions