From 99983d94dcb19859405113aa94a578300a235049 Mon Sep 17 00:00:00 2001 From: Alexander Kireev Date: Fri, 3 Jul 2026 04:43:00 +0700 Subject: [PATCH] fix(sqlite): don't panic building URL for in-memory connect options build_url() percent-encodes the filename with a set that leaves ':' alone, so in-memory options (filename `:memory:` or the generated `file:sqlx-in-memory-N`) produce something like `sqlite://file:sqlx-in-memory-0`. The url crate reads that as a host:port authority and blows up with InvalidPort/EmptyHost instead of returning a URL. Encoding ':' too fixes it without touching how normal file paths get encoded. --- sqlx-sqlite/src/options/parse.rs | 35 +++++++++++++++++++++++++++++++- 1 file changed, 34 insertions(+), 1 deletion(-) diff --git a/sqlx-sqlite/src/options/parse.rs b/sqlx-sqlite/src/options/parse.rs index 0530f4204c..d6967b36bf 100644 --- a/sqlx-sqlite/src/options/parse.rs +++ b/sqlx-sqlite/src/options/parse.rs @@ -126,7 +126,12 @@ impl SqliteConnectOptions { .add(b'?') .add(b'`') .add(b'{') - .add(b'}'); + .add(b'}') + // Not part of the WHATWG path-percent-encode-set, but a raw `:` right after + // `sqlite://` gets parsed as a host:port separator instead of part of the path, + // which breaks in-memory filenames like `:memory:` or `file:sqlx-in-memory-0` + // (see `from_db_and_params`) with `EmptyHost`/`InvalidPort` errors. + .add(b':'); let filename_encoded = percent_encode( self.filename.as_os_str().as_encoded_bytes(), @@ -229,3 +234,31 @@ fn it_returns_the_parsed_url() -> Result<(), Error> { Ok(()) } + +// https://github.com/launchbadge/sqlx/issues/4327 +#[test] +fn build_url_does_not_panic_for_in_memory_db() -> Result<(), Error> { + // filename is `file:sqlx-in-memory-{seqno}`, which used to make the `url` crate + // interpret everything up to the next `/` as a `host:port` authority and choke on + // `sqlx-in-memory-0` not being a valid port. + let options: SqliteConnectOptions = "sqlite::memory:".parse()?; + let url = options.build_url(); + assert_eq!( + url.query_pairs().find(|(k, _)| k == "mode").unwrap().1, + "memory" + ); + + // the plain `:memory:` filename used by `SqliteConnectOptions::default()` hit the same + // problem (the leading `:` was parsed as an empty host). + let default_url = SqliteConnectOptions::default().build_url(); + assert_eq!( + default_url + .query_pairs() + .find(|(k, _)| k == "mode") + .unwrap() + .1, + "rw" + ); + + Ok(()) +}