Skip to content

Proposal: align :mode open flags with sqlite3_open_v2 #347

@Woody88

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
    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