Repository navigation
Conversation
`new SQL({ adapter: "sqlite" })` took DATABASE_URL=postgres://... as its
file name, so the first query failed with SQLITE_CANTOPEN. Before #22260
the same call opened `:memory:`.
With the SQLite adapter, getConnectionDetailsFromEnvironment now skips
DATABASE_URL, DATABASEURL and TLS_DATABASE_URL when the value has the
scheme of another adapter, and reads SQLITE_URL next. A SQLite URL, a
path and a scheme that no adapter claims still apply. The other adapters
are not changed.
|
Updated 3:18 AM PT - Oct 13th, 2026
✅ @robobun, your commit 135b682fcc377d373746d52ff679dcb15bf8b665 passed in 🧪 To try this PR locally: bunx bun-pr 45080That installs a local version of the PR into your bun-45080 --bun |
|
Status: reproduced on the released 1.4.3 and on a debug build of main. With With this PR the SQLite adapter skips a PostgreSQL, MySQL or MariaDB URL in each of those three variables. It then reads the next variable, One question is open for a maintainer. It is at the top of the Notes in the description: which values of The server adapters are not changed. |
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info
WalkthroughSQLite environment lookup now skips shared URLs with recognized PostgreSQL, MySQL, or MariaDB schemes when SQLite is selected. Lookup continues to later environment variables. Tests and documentation cover URL and filename handling. ChangesSQLite environment URL selection
Suggested reviewers: Priority: ➖ Normal Merge Risk: ⚪ Minimal · up to
|
There was a problem hiding this comment.
Nothing blocking. The comments below are optional suggestions. There is no need to push a fix for them before merging.
Beyond the inline findings, I also checked the sslMode out-param left set to require after TLS_DATABASE_URL is skipped at src/js/internal/sql/shared.ts:1656 — the adapter === "sqlite" branch at shared.ts:1767 returns null for sslMode unconditionally, so the stale value has no consumer on the only path that can reach the reset.
Extended reasoning...
The change adds an isUrlForAnotherAdapter helper in src/js/internal/sql/shared.ts and gates the generic DATABASE_URL/DATABASEURL/TLS_DATABASE_URL early-return on it for the SQLite adapter only, plus a JSDoc line in packages/bun-types/sql.d.ts and a new describe block in test/js/sql/adapter-env-var-precedence.test.ts. It touches connection-string resolution (which database a process opens), not auth or crypto directly. Five inline findings were posted, so a human look is already signalled; this note only records the one out-param concern that was traced and ruled out.
…env-url-other-database
… URL of a server database - A DATABASE_URL that is skipped no longer hides DATABASEURL and TLS_DATABASE_URL. - `http`, `https` and `ftp` do not name a database, so such a URL stays a file name. - Whitespace before the scheme does not hide it, as in `new URL()`.
- `genericUrlFor(adapter, value)` wraps the three reads in the existing block. For the SQLite adapter it returns null for a PostgreSQL, MySQL or MariaDB URL. The other adapters get the value. - The doc comment of getConnectionDetailsFromEnvironment is as on main. - The `filename` JSDoc and the docs name the skipped URLs and `SQLITE_URL`. - Tests: a `file://` URL, the call forms with an undefined file name, and a URL with a second `://`.
Problem
DATABASE_URL=postgres://127.0.0.1:1/db,new SQL({ adapter: "sqlite" })uses that URL as its file name. The first query rejects withSQLITE_CANTOPEN. Before Fix: Make SQL connection string parsing more sensible #22260 (1.2.22) the call opened:memory:.getConnectionDetailsFromEnvironment(src/js/internal/sql/shared.ts:1634-1639) returnsDATABASE_URL,DATABASEURLorTLS_DATABASE_URLfirst, whatever adapter the options name.:memory:fail on main under such aDATABASE_URL.Fix
SQLITE_URL, and the:memory:default.libsql://,https://.test/js/sql/adapter-env-var-precedence.test.ts(45 new tests, 38 fail on main). Alsosqlite-sql,sqlite-url-parsing,adapter-override.Background
SQLITE_URLbelongs to SQLite.Downsides
DATABASE_URL, a call with nofilenameopens:memory:where it failed withSQLITE_CANTOPEN. That includes anundefinedfilenameand{ adapter: "sqlite", url }.://: 4 string calls, 2 short strings. Other adapters: 1 to 3 calls of 6 bytecode instructions per construct. Builtins: +511 bytes.Notes
Question for the reviewer (@alii, as the author of #21640 and #22260). With
adapter: "sqlite"and nofilename, what doDATABASE_URL,DATABASEURLandTLS_DATABASE_URLdo? Three rules are on record. Each cell is the file that the call opens:postgres://...sqlite://./app.db./app.dblibsql://...:memory::memory::memory::memory:docs/runtime/sql.mdx("when it contains a SQLite-compatible URL"), closed #22242:memory:app.db:memory::memory::memory:app.dbapp.dbSQLITE_CANTOPENSQLITE_CANTOPENapp.dbapp.dbSQLITE_CANTOPENThis PR takes (c) because it changes no row that works on main. (a) and (b) make a value that opens a file today a silent
:memory:. If you want (a), (b), or a throw that names the variable in place of the skip, say so and I will change it. (a) is about 2 lines. (b) is theparseDefinitelySqliteUrltest on each value. The helper gets no further special cases: the answer to the next spelling is this ruling.Origin. An automated fuzz report. There is no user report and no GitHub issue for this input. #22147, a user report on the same lookup, is about another input (an explicit
hostname). The bug reproduces on 1.4.3, 1.4.2, 1.3.14 and main. The repro script (new Bun.SQL({ adapter: "sqlite" })in a child process, one environment variable per row) printsfilename postgres://127.0.0.1:1/db | SQLITE_CANTOPENon main forDATABASE_URL,DATABASEURLandTLS_DATABASE_URL. On a debug build of this branch it printsfilename :memory: | opensfor the three, and exits 0.Who meets it. Code that did not set the variable: a tool or a test that opens a scratch database with
new SQL({ adapter: "sqlite" })in a project whose environment or.envhas a serverDATABASE_URL. The three tests from #21640 are such code:null with adapter defaults to :memory:andundefined with adapter defaults to :memory:(sqlite-url-parsing.test.ts) andshould handle null/undefined with adapter specified(sqlite-sql.test.ts) fail on 1.4.3 withDATABASE_URL=postgres://127.0.0.1:1/dbexported, and pass on this branch. On 1.2.22 to 1.4.3 the workaround isfilename: ":memory:"ornew SQL(":memory:").Regression. Before #22260 the options form did not read the environment for SQLite:
filenamewasoptions.filename || ":memory:". #22260 added the lookup for every adapter, with the generic variables first. Its doc comment says that with an adapter the lookup only reads the variables of that adapter. The docs say thatDATABASE_URLconfigures SQLite "when it contains a SQLite-compatible URL". #22242 had that rule in code and was closed as inactive.The rule. With
adapter: "sqlite", each ofDATABASE_URL,DATABASEURLandTLS_DATABASE_URLis checked by itself, in that order. A value counts as not set when the text before the first://ispostgres,postgresql,mysql,mysql2ormariadb. The scheme is compared without case and without the whitespace before it, asnew URL()reads it for the server adapters. The first variable that is left applies. If none is left,SQLITE_URLandSQLITEURLapply, and then the default:memory:. These values apply as before::memory:,sqlite://...,sqlite:...,file://...,file:...,://(mydb,./app.db,127.0.0.1:1/db),libsql://,mongodb://,mssql://, andhttp://,https://,ftp://. These stay file names and keep failing withSQLITE_CANTOPEN. On 1.2.21 they opened:memory:too. They are left as on main on purpose: a silent in-memory database is worse than that error, and the question above decides them. The resolver mapshttp,httpsandftpto Postgres when no adapter is given, but they name no database.Rows that change. A matrix of 401 constructor calls and environments, release build of main against the option resolver of this branch:
adapter: "sqlite". 25 resolve to:memory:, 2 to the file ofSQLITE_URL, and 2 to the SQLite file of the next generic variable.sslMode: 0 of 505 rows are lower or higher (the 401 rows and 104 rows built for TLS).Measurements (Linux x64, main is
7583003d82).StringindexOf,slice,trim,toLowerCase,startsWith,includescalls /URLconstructions), main to this branch:new SQL()withDATABASE_URL=postgres://: 8 / 7 / 1 to 8 / 7 / 1.{ adapter: "postgres" }withDATABASE_URL=postgres://: 8 / 3 / 1 to 8 / 3 / 1.{ adapter: "mariadb" }withDATABASE_URL=mysql://: 6 / 3 / 1 to 6 / 3 / 1.{ adapter: "sqlite" }withDATABASE_URL=sqlite://: 1 / 1 / 0 to 1 / 5 / 0.{ adapter: "sqlite" }withDATABASE_URL=:memory:: 1 / 1 / 0 to 1 / 2 / 0.{ adapter: "sqlite", filename }withDATABASE_URL=postgres://: 1 / 1 / 0 to 5 / 5 / 0. The lookup runs beforefilenameis read, and it now goes on to the four variables afterDATABASE_URL.{ adapter: "sqlite" }and no URL variable: 5 / 1 / 0 to 5 / 1 / 0.DATABASE_URLapplies, 2 whenDATABASEURLdoes, 3 in every other case. For an adapter that is notsqlitea call runs 6 bytecode instructions: the comparison of the adapter and the return. A string orURLargument does not reach the lookup.heapStats().objectCount, 20,000 calls): 2 strings (slice,toLowerCase) per generic variable whose value has://, and 3 when whitespace comes before the scheme. 0 for:memory:, for a path, for a variable that is not set, and for every other adapter.BUN_JSC_dumpGeneratedBytecodes=1, debug builds):getConnectionDetailsFromEnvironment156 to 168 instructions, 798 to 876 bytes. NewgenericUrlFor: 36 instructions, 258 bytes.bun scripts/build.ts --profile=release --mode=codegen,InternalModuleRegistryConstants.bin): 2,644,484 to 2,644,995 bytes.strace,perfandvalgrindare not installed in the build container.The server adapters.
{ adapter: "mysql" }withDATABASE_URL=postgres://...takes the host, the port, the user and the database from that URL, and the same holds for each pair of server adapters. This PR leaves that as it is, on purpose:new SQL("postgres://...", { adapter: "mysql" })and{ adapter: "mysql", url: "postgres://..." }give a MySQL client for that host too. A rule for the environment alone makes the forms disagree.sslModewhen the skipped URL carried?sslmode=requireor came fromTLS_DATABASE_URL, and{ adapter: "mariadb" }with amysql://URL has to keep working.adapterstill reads it.The helper takes the adapter as its first argument, so a rule for the server adapters is one more condition in it.
Not changed here.
adapter: "sqlite",SQLITE_URLis read only when each generic variable is not set or is skipped.DATABASE_URL=./a.dbbesideSQLITE_URL=./b.dbopensa.db, andDATABASE_URL=https://...besideSQLITE_URL=./b.dbfails. That is the order of main.{ adapter: "sqlite", url }still ignoresurl. Beside a serverDATABASE_URLit now resolves to:memory:, which is what main does with noDATABASE_URL. sql: do not read the environment connection URL when options name a target #41586 and sql(sqlite): honor options.url with adapter: "sqlite" and keep "?" in plain filenames #38427 own that case.DATABASE_URL=SQLITE://env.dbwithadapter: "sqlite"is still the file nameSQLITE://env.db: the prefix match ofparseDefinitelySqliteUrlhas a case.SQLITE_URL=postgres://...is still a file name. Existing tests pin that the name of an adapter-specific variable wins over the scheme.new SQL("postgres://h/db", { adapter: "sqlite" })) is still a file name.filenameor SQLITE_URL before a URL is built #44483 (afilenameorSQLITE_URLselects SQLite) lists this case as not changed there. Its hunks inshared.tsdo not touch this one. Both PRs add a paragraph to thefilenameJSDoc and adescribeat the same place of the test file, so the second one to land needs a rebase.no test proof · iteration 0 · platform-specific test(s) that do not run on this machine, deferring to CI, which covers all platforms: test/js/sql/adapter-env-var-precedence.test.ts