Skip to content

sql(sqlite): honor options.url with adapter: "sqlite" and keep "?" in plain filenames - #38427

Open
robobun wants to merge 1 commit into
mainfrom
farm/62aa28be/sql-sqlite-url-and-filename-query
Open

robobun wants to merge 1 commit into
mainfrom
farm/62aa28be/sql-sqlite-url-and-filename-query

Conversation

@robobun

@robobun robobun commented Aug 14, 2026

Copy link
Copy Markdown
Collaborator

Problem

  • new SQL({ adapter: "sqlite", url: "sqlite://a.db" }) opens :memory: (sql.options.filename === ":memory:"), so everything written is gone at close and a.db is never created. The same object without adapter, and { adapter: "sqlite" } plus DATABASE_URL=sqlite://a.db, both open a.db. A URL instance or a bare path in url is dropped the same way.
  • Cause: parseConnectionDetailsFromOptionsOrEnvironment (src/js/internal/sql/shared.ts:1626) only reads options.filename when the adapter is sqlite, so url is never looked at and the filename falls through to the :memory: default.
  • new SQL({ adapter: "sqlite", filename: "what?.db" }) opens a file named what (sql.options.filename === "what"); new SQL("what?.db", { adapter: "sqlite" }) does the same. bun:sqlite's new Database("what?.db") opens what?.db.
  • Cause: parseSQLiteOptions (shared.ts:1467) cuts every input at the first ? to pull out ?mode=, whether or not the input is a URL. Before Fix: Make SQL connection string parsing more sensible #22260 only protocol-prefixed strings were cut.

Fix

  • The sqlite branch of the adapter check now resolves filename, then url, which is exactly what the adapter-less branch already did (the two branches are merged). Non-sqlite adapters are unchanged and still read only url. filename still wins when both are given, and url overrides the first constructor argument the same way it does for postgres/mysql/mariadb.
  • parseSQLiteOptions only splits off a query string when the input is a URL instance or starts with sqlite://, sqlite:, file:// or file:. Plain paths reach SQLite unchanged. ?mode= handling for URL-shaped input is the same as before (the 28-case query matrix in sqlite-url-parsing.test.ts still passes).
  • Types and docs are left as they are: filename stays the documented sqlite option; this change stops url from being silently discarded when it is passed anyway.
  • Verified:
    • test/js/sql/sqlite-sql.test.ts: new tests should open options.url when adapter is sqlite, should accept the same url shapes with adapter: sqlite as without it, should keep ? in a plain filename, should open the file named by a plain filename containing ? (skipped on Windows, where ? is not a legal filename character), plus should prefer options.filename over options.url pinning the precedence. The four fail on the released bun (:memory: / file not created) and pass with this change; the whole file passes (244).
    • test/js/sql/sqlite-url-parsing.test.ts, adapter-env-var-precedence.test.ts, adapter-override.test.ts: 272 pass.

Background

  • Bun.SQL accepts a connection string, a URL, or an options object, and parseOptions in shared.ts picks an adapter (postgres, mysql, mariadb or sqlite) and normalizes the input into that adapter's options. parseConnectionDetailsFromOptionsOrEnvironment decides which option (filename, url, the first argument, or an env var) is the connection target; parseSQLiteOptions then turns that target into a bun:sqlite filename plus readonly/create flags.
  • For sqlite the target can be a URL (sqlite://data.db?mode=ro, file:///data.db) or a plain path (./data.db). Only the URL form has a query string; ?mode=ro|rw|rwc maps onto the open flags. A plain path is handed to SQLite verbatim, and ? is an ordinary character in a path.
  • Related but separate: sql(sqlite): make ?mode=rw open without creating the database #38417 changes what ?mode=rw maps to; it edits a different part of parseSQLiteOptions.
Before/after on the original report
// released bun 1.4.0
(a) { adapter: "sqlite", url: "sqlite://a.db" }        -> filename ":memory:"
(a) { adapter: "sqlite", url: new URL("sqlite://a.db") } -> filename ":memory:"
(a) { adapter: "sqlite", url: "a.db" }                 -> filename ":memory:"
(b) { adapter: "sqlite", filename: "what?.db" }        -> filename "what"
(b) new SQL("what?.db", { adapter: "sqlite" })         -> filename "what"

// with this change
(a) all three                                           -> filename "a.db", file created
(b) both                                                -> filename "what?.db", file created
{ url: "sqlite://d.db?mode=rwc" }                        -> "d.db", create: true   (unchanged)
{ adapter: "sqlite", filename: "sqlite://e.db?mode=ro" } -> "e.db", readonly: true (unchanged)
{ adapter: "sqlite", filename: "file:f.db?mode=ro" }     -> "f.db", readonly: true (unchanged)
{ adapter: "sqlite", filename: "g-file.db", url: "sqlite://g-url.db" } -> "g-file.db" (unchanged)

Not changed here: { filename: "./dev.db" } with no adapter still resolves to postgres; sqlite:// paths are still not percent-decoded and keep #, as the existing url-parsing tests require.

… plain filenames

parseConnectionDetailsFromOptionsOrEnvironment only read `filename` when
the adapter was sqlite, so { adapter: "sqlite", url: "sqlite://a.db" }
fell through to the ":memory:" default while { url: "sqlite://a.db" }
opened the file. The sqlite branch now resolves filename, then url, the
same as the adapter-less branch.

parseSQLiteOptions split every input at the first "?", so a plain path
such as "what?.db" opened "what". The query string is now only split off
URL instances and sqlite:/file: strings; plain paths reach SQLite
unchanged, matching bun:sqlite.
@robobun

robobun commented Aug 14, 2026 •

Copy link
Copy Markdown
Collaborator Author

Status: reproduced on the released bun 1.4.0 with new SQL({ adapter: "sqlite", url: "sqlite://a.db" }) (opens :memory:) and new SQL({ adapter: "sqlite", filename: "what?.db" }) (opens what); see the before/after block in the description. Fix in src/js/internal/sql/shared.ts, tests in test/js/sql/sqlite-sql.test.ts.

CI (build 95683) finished with 177 of 179 jobs passed, including the new tests on every Linux and Windows lane. The build is marked failed only because the darwin 14 aarch64 - test-bun pair never ran: those two jobs expired in the test-darwin queue four times waiting for an agent (the queue has held 240 to 310 jobs for six agents all day) and were not re-queued after the fourth expiry. No job failed. Nothing in this change is platform specific (JS option parsing plus tests); retrying the two expired jobs on build 95683 is the quickest way to get the darwin result, since a new build would go to the back of the same queue.

@coderabbitai

coderabbitai Bot commented Aug 14, 2026

Copy link
Copy Markdown
Contributor

Warning

Review limit reached

@robobun, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 16 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 783ce6b7-eb7d-42d5-bbb6-87928951341e

📥 Commits

Reviewing files that changed from the base of the PR and between 01aa7cd and 10e779c.

📒 Files selected for processing (2)
  • src/js/internal/sql/shared.ts
  • test/js/sql/sqlite-sql.test.ts

Comment @coderabbitai help to get the list of available commands.

@claude claude Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM — focused fix for two clear option-parsing bugs in the SQL sqlite adapter.

What was reviewed:

  • parseConnectionDetailsFromOptionsOrEnvironment: merging the sqlite branch into the unknown-adapter branch is behavior-preserving for the unknown and non-sqlite paths; only sqlite gains the url fallback.
  • parseSQLiteOptions: verified the ?mode= query matrix in sqlite-url-parsing.test.ts:82 uses only the four sqliteProtocols prefixes, so hasSqliteProtocol covers every existing case; URL instances still split via the instanceof check.
  • Tests: use tempDir/await using, assert actual file creation (not just options.filename), the ?-in-filename open test is correctly skipIf(isWindows), and precedence of filename over url is pinned.
Extended reasoning...

Overview

Two changes in src/js/internal/sql/shared.ts, both in the Bun.SQL option-parsing path for the sqlite adapter:

  1. parseConnectionDetailsFromOptionsOrEnvironment (~line 1625): the three-way if on options.adapter collapses the === "sqlite" and !options.adapter branches into one, so { adapter: "sqlite", url } now reads url after filename, exactly as the adapter-less branch already did. The non-sqlite branch is byte-identical to before (only reads url). The unknown-adapter branch is unchanged. This fixes the silent fallback to :memory: when only url is given.
  2. parseSQLiteOptions (~line 1460): the redundant originalUrl local and the always-true typeof filename === "string" guards are removed, and the ?-split now only runs when the input is a URL instance or matches one of the four sqliteProtocols prefixes via a new hasSqliteProtocol helper (which reuses the existing sqliteProtocols table — single source of truth). Plain paths reach SQLite verbatim, matching bun:sqlite's new Database().

Five new tests land in the existing test/js/sql/sqlite-sql.test.ts in the appropriate describe blocks.

Security risks

None. This is TypeScript option-normalization code with no shell/SQL interpolation, no auth, no network. The change narrows (not widens) what gets treated as a URL query string, and the resulting filename is handed to bun:sqlite the same way a direct new Database(filename) call would be.

Level of scrutiny

Low-medium. Pure JS/TS logic in a builtin module, no native code touched. The primary risk with option-parsing changes is silently altering behavior for existing inputs; I checked the two mechanisms:

  • The branch merge: for adapter === "sqlite", the only new path is falling through to options.url when filename is absent — previously that fell through to :memory:, which is the bug being fixed. filename still wins when present (pinned by the new precedence test).
  • The ?-split gate: I verified sqlite-url-parsing.test.ts:82's protocolsWithQuery array is exactly the four prefixes in sqliteProtocols, so every existing ?mode= test case still splits. The only inputs that stop splitting are un-prefixed plain paths, which is the second bug being fixed.

Other factors

  • Tests follow repo conventions: tempDir + using, await using on SQL handles, assertions on real filesystem effects (existsSync(dbPath), negative check on the truncated name), Windows skip for the ?-in-filename open test with a stated reason.
  • The PR description states sqlite-url-parsing.test.ts, adapter-env-var-precedence.test.ts, and adapter-override.test.ts all pass (272 total), and the four new bug-repro tests fail on the released bun — satisfying the "fails for the right reason" requirement.
  • No outstanding reviewer comments; only the robobun status comment is on the timeline.

@robobun

robobun commented Aug 14, 2026

Copy link
Copy Markdown
Collaborator Author
Updated 4:05 AM PT - Aug 14th, 2026

@robobun, your commit 10e779c is building: #95683

@robobun

robobun commented Aug 14, 2026 •

Copy link
Copy Markdown
Collaborator Author
Updated 5:05 AM PT - Aug 14th, 2026

❌ @robobun, your commit 10e779c has some failures in Build #95683 (All Failures)


🧪   To try this PR locally:

bunx bun-pr 38427

That installs a local version of the PR into your bun-38427 executable, so you can run:

bun-38427 --bun

@robobun

robobun commented Sep 6, 2026

Copy link
Copy Markdown
Collaborator Author

Heads-up: #41586 adopts the same filename-then-url resolution for adapter: "sqlite" (the parseConnectionDetailsFromOptionsOrEnvironment hunk here), because it stops reading the environment connection URL whenever the options name a target. If #41586 lands first, this PR reduces to the query-string handling for plain filenames. The two hunks are textually close, so whichever lands second needs a small rebase.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants