:ticket:`10050`
+.. _change_11234:
+
+URL stringify and parse now supports URL escaping for the "database" portion
+----------------------------------------------------------------------------
+
+A URL that includes URL-escaped characters in the database portion will
+now parse with conversion of those escaped characters::
+
+ >>> from sqlalchemy import make_url
+ >>> u = make_url("driver://user:pass@host/database%3Fname")
+ >>> u.database
+ 'database?name'
+
+Previously, such characters would not be unescaped::
+
+ >>> # pre-2.1 behavior
+ >>> from sqlalchemy import make_url
+ >>> u = make_url("driver://user:pass@host/database%3Fname")
+ >>> u.database
+ 'database%3Fname'
+
+This change also applies to the stringify side; most special characters in
+the database name will be URL escaped, omitting a few such as plus signs and
+slashes::
+
+ >>> from sqlalchemy import URL
+ >>> u = URL.create("driver", database="a?b=c")
+ >>> str(u)
+ 'driver:///a%3Fb%3Dc'
+
+Where the above URL correctly round-trips to itself::
+
+ >>> make_url(str(u))
+ driver:///a%3Fb%3Dc
+ >>> make_url(str(u)).database == u.database
+ True
+
+
+Whereas previously, special characters applied programmatically would not
+be escaped in the result, leading to a URL that does not represent the
+original database portion. Below, `b=c` is part of the query string and
+not the database portion::
+
+ >>> from sqlalchemy import URL
+ >>> u = URL.create("driver", database="a?b=c")
+ >>> str(u)
+ 'driver:///a?b=c'
+
+:ticket:`11234`
.. _change_11250:
--- /dev/null
+.. change::
+ :tags: bug, engine
+ :tickets: 11234
+
+ Adjusted URL parsing and stringification to apply url quoting to the
+ "database" portion of the URL. This allows a URL where the "database"
+ portion includes special characters such as question marks to be
+ accommodated.
+
+ .. seealso::
+
+ :ref:`change_11234`
if self.port is not None:
s += ":" + str(self.port)
if self.database is not None:
- s += "/" + self.database
+ s += "/" + quote(self.database, safe=" +/")
if self.query:
keys = list(self.query)
keys.sort()
query = None
components["query"] = query
- if components["username"] is not None:
- components["username"] = unquote(components["username"])
-
- if components["password"] is not None:
- components["password"] = unquote(components["password"])
+ for comp in "username", "password", "database":
+ if components[comp] is not None:
+ components[comp] = unquote(components[comp])
ipv4host = components.pop("ipv4host")
ipv6host = components.pop("ipv6host")
),
(
"DRIVER={foob};Server=somehost%3BPORT%3D50001;"
- "Database=somedb%3BPORT%3D50001;UID={someuser;PORT=50001};"
+ "Database={somedb;PORT=50001};UID={someuser;PORT=50001};"
"PWD={some{strange}}pw;PORT=50001}",
),
),
"/database?foo=bar",
"dbtype://username:password@[2001:da8:2004:1000:202:116:160:90]:80"
"/database?foo=bar",
- "dbtype://username:password@hostspec/test database with@atsign",
+ "dbtype://username:password@hostspec/test+database with%40atsign",
+ "dbtype://username:password@hostspec/db%3Fwith%3Dqmark",
+ "dbtype://username:password@hostspec/test database with spaces",
"dbtype://username:password@hostspec?query=but_no_db",
"dbtype://username:password@hostspec:450?query=but_no_db",
"dbtype://username:password with spaces@hostspec:450?query=but_no_db",
), u.host
assert u.database in (
"database",
- "test database with@atsign",
+ "test+database with@atsign",
+ "test database with spaces",
"/usr/local/_xtest@example.com/members.db",
"/usr/db_file.db",
":memory:",
"",
"foo/bar/im/a/file",
"E:/work/src/LEM/db/hello.db",
+ "db?with=qmark",
None,
), u.database
eq_(url.make_url(u.render_as_string(hide_password=False)), u)
+ def test_dont_urlescape_slashes(self):
+ """supplemental test for #11234 where we want to not escape slashes
+ as this causes problems for alembic tests that deliver paths into
+ configparser format"""
+
+ u = url.make_url("dbtype:///path/with/slashes")
+ eq_(str(u), "dbtype:///path/with/slashes")
+ eq_(u.database, "path/with/slashes")
+
def test_rfc1738_password(self):
u = url.make_url("dbtype://user:pass word + other%3Awords@host/dbname")
eq_(u.password, "pass word + other:words")