@@ -78,10 +78,10 @@ impl PostgresUrl {
7878 Ok ( Self :: WebSocket ( PostgresWebSocketUrl :: new ( url, api_key) ) )
7979 }
8080
81- pub fn dbname ( & self ) -> & str {
81+ pub fn dbname ( & self ) -> Cow < ' _ , str > {
8282 match self {
8383 Self :: Native ( url) => url. dbname ( ) ,
84- Self :: WebSocket ( url) => url. dbname ( ) ,
84+ Self :: WebSocket ( url) => Cow :: Borrowed ( url. dbname ( ) ) ,
8585 }
8686 }
8787
@@ -193,11 +193,20 @@ impl PostgresNativeUrl {
193193 is_url_localhost ( & self . url )
194194 }
195195
196- /// Name of the database connected . Defaults to `postgres`.
197- pub fn dbname ( & self ) -> & str {
196+ /// decoded database name . Defaults to `postgres`.
197+ pub fn dbname ( & self ) -> Cow < ' _ , str > {
198198 match self . url . path_segments ( ) {
199- Some ( mut segments) => segments. next ( ) . unwrap_or ( "postgres" ) ,
200- None => "postgres" ,
199+ Some ( mut segments) => {
200+ let segment = segments. next ( ) . unwrap_or ( "postgres" ) ;
201+ match percent_decode ( segment. as_bytes ( ) ) . decode_utf8 ( ) {
202+ Ok ( dbname) => dbname,
203+ Err ( _) => {
204+ tracing:: warn!( "Couldn't decode dbname to UTF-8, using the non-decoded version." ) ;
205+ segment. into ( )
206+ }
207+ }
208+ }
209+ None => Cow :: Borrowed ( "postgres" ) ,
201210 }
202211 }
203212
@@ -595,6 +604,31 @@ mod tests {
595604 assert_eq ! ( "/var/run/postgresql" , url. host( ) ) ;
596605 }
597606
607+ #[ test]
608+ fn should_decode_percent_encoded_dbname ( ) {
609+ // Chinese characters: 测试库 (test database)
610+ let url = PostgresNativeUrl :: new (
611+ Url :: parse ( "postgresql://user:pass@localhost:5432/%E6%B5%8B%E8%AF%95%E5%BA%93" ) . unwrap ( ) ,
612+ )
613+ . unwrap ( ) ;
614+ assert_eq ! ( "测试库" , url. dbname( ) ) ;
615+ }
616+
617+ #[ test]
618+ fn should_decode_dbname_with_spaces ( ) {
619+ let url =
620+ PostgresNativeUrl :: new ( Url :: parse ( "postgresql://user:pass@localhost:5432/my%20database" ) . unwrap ( ) ) . unwrap ( ) ;
621+ assert_eq ! ( "my database" , url. dbname( ) ) ;
622+ }
623+
624+ #[ test]
625+ fn should_decode_dbname_with_special_characters ( ) {
626+ // test-db_name
627+ let url = PostgresNativeUrl :: new ( Url :: parse ( "postgresql://user:pass@localhost:5432/test%2Ddb%5Fname" ) . unwrap ( ) )
628+ . unwrap ( ) ;
629+ assert_eq ! ( "test-db_name" , url. dbname( ) ) ;
630+ }
631+
598632 #[ test]
599633 fn should_allow_changing_of_cache_size ( ) {
600634 let url =
@@ -815,4 +849,48 @@ mod tests {
815849 // CRDB does NOT support setting the search_path via a connection parameter if the identifier is unsafe.
816850 assert_eq ! ( config. get_search_path( ) , None ) ;
817851 }
852+
853+ /// Tests that connecting to a database with a percent-encoded name works correctly.
854+ ///
855+ /// This test verifies:
856+ /// 1. A database with Chinese characters (测试库) can be created
857+ /// 2. The percent-encoded URL correctly connects to the database
858+ /// 3. The `dbname()` function returns the decoded database name
859+ #[ tokio:: test]
860+ async fn should_connect_to_db_with_percent_encoded_name ( ) {
861+ use percent_encoding:: { NON_ALPHANUMERIC , utf8_percent_encode} ;
862+
863+ let base_url = Url :: parse ( & CONN_STR ) . unwrap ( ) ;
864+ let conn = Quaint :: new ( base_url. as_str ( ) ) . await . unwrap ( ) ;
865+
866+ // Create a database with Chinese characters: 测试库 (meaning "test database")
867+ let test_db_name = "测试库" ;
868+ let _ = conn
869+ . raw_cmd ( & format ! ( r#"DROP DATABASE IF EXISTS "{test_db_name}""# ) )
870+ . await ;
871+ conn. raw_cmd ( & format ! ( r#"CREATE DATABASE "{test_db_name}""# ) )
872+ . await
873+ . unwrap ( ) ;
874+
875+ // Build URL with percent-encoded database name
876+ // 测试库 -> %E6%B5%8B%E8%AF%95%E5%BA%93
877+ let encoded_db_name = utf8_percent_encode ( test_db_name, NON_ALPHANUMERIC ) . to_string ( ) ;
878+ let mut test_url = base_url. clone ( ) ;
879+ test_url. set_path ( & format ! ( "/{encoded_db_name}" ) ) ;
880+
881+ // Connect using percent-encoded URL and verify dbname() returns decoded value
882+ let test_conn = Quaint :: new ( test_url. as_str ( ) ) . await . unwrap ( ) ;
883+ let pg_url = PostgresNativeUrl :: new ( test_url) . unwrap ( ) ;
884+ assert_eq ! ( test_db_name, pg_url. dbname( ) ) ;
885+
886+ // Verify the connection actually works by executing a simple query
887+ let result = test_conn. query_raw ( "SELECT 1 as test" , & [ ] ) . await . unwrap ( ) ;
888+ assert_eq ! ( 1 , result. len( ) ) ;
889+
890+ // Cleanup: drop the test database
891+ drop ( test_conn) ;
892+ conn. raw_cmd ( & format ! ( r#"DROP DATABASE IF EXISTS "{test_db_name}""# ) )
893+ . await
894+ . unwrap ( ) ;
895+ }
818896}
0 commit comments