Expected behavior
Expected behavior
Passwords containing URL-safe special characters (like +, %, @) should survive the URL → connect round-trip unchanged:
trino://user:pass+word@host/catalog → BasicAuthentication('user', 'pass+word')
The password should not be modified beyond SQLAlchemy's own URL decoding.
Actual behavior
Version
trino 0.337.0
Bug location
trino/sqlalchemy/dialect.py — TrinoDialect.create_connect_args
Root cause
url.password and url.username are already decoded by SQLAlchemy's make_url(). The dialect then applies unquote_plus again:
trino://user:pass%2Bword@host/catalog
→ SQLAlchemy make_url decodes %2B → +
url.password = 'pass+word'
→ create_connect_args calls unquote_plus('pass+word') → 'pass word' ✗
Impact
Any password containing a literal + character gets corrupted to a space, causing "Access Denied: Invalid credentials" (401).
Precedent
SQLAlchemy itself fixed the exact same issue 11 years ago:
sqlalchemy/sqlalchemy#2873
Their fix: use unquote for passwords, not unquote_plus.
Fix
Replace unquote_plus(url.username) and unquote_plus(url.password) with url.username / url.password directly — they're already decoded.
(Alternatively, use urllib.parse.unquote instead of unquote_plus for these fields.)
Workaround
Monkey-patch before creating the engine:
import trino.sqlalchemy.dialect
from urllib.parse import unquote
trino.sqlalchemy.dialect.unquote_plus = unquote
### Steps To Reproduce
```python
from sqlalchemy import create_engine
from trino.auth import BasicAuthentication
# Password contains a literal '+' character
url = "trino://user:pass+word@localhost:8080/catalog"
engine = create_engine(url)
# The dialect creates BasicAuthentication with corrupted password:
# expected: BasicAuthentication('user', 'pass+word')
# actual: BasicAuthentication('user', 'pass word')
# the '+' became a space ↑
Or test the dialect directly:
from sqlalchemy.engine.url import make_url
from trino.sqlalchemy.dialect import TrinoDialect
url = make_url("trino://user:pass+word@localhost:8080/catalog")
dialect = TrinoDialect()
_, kwargs = dialect.create_connect_args(url)
auth = kwargs["auth"]
print(auth._username) # 'user'
print(auth._password) # 'pass word' ← should be 'pass+word'
Result: connection fails with error 401: Access Denied: Invalid credentials.
Log output
No response
Operating System
Debian GNU/Linux 12 (bookworm)
Trino Python client version
0.337.0
Trino Server version
480
Python version
python3.10
Are you willing to submit PR?
Expected behavior
Expected behavior
Passwords containing URL-safe special characters (like
+,%,@) should survive the URL → connect round-trip unchanged:trino://user:pass+word@host/catalog → BasicAuthentication('user', 'pass+word')
The password should not be modified beyond SQLAlchemy's own URL decoding.
Actual behavior
Version
trino 0.337.0
Bug location
trino/sqlalchemy/dialect.py—TrinoDialect.create_connect_argsRoot cause
url.passwordandurl.usernameare already decoded by SQLAlchemy'smake_url(). The dialect then appliesunquote_plusagain:trino://user:pass%2Bword@host/catalog
→ SQLAlchemy
make_urldecodes%2B→+url.password = 'pass+word'→
create_connect_argscallsunquote_plus('pass+word')→'pass word'✗Impact
Any password containing a literal
+character gets corrupted to a space, causing "Access Denied: Invalid credentials" (401).Precedent
SQLAlchemy itself fixed the exact same issue 11 years ago:
sqlalchemy/sqlalchemy#2873
Their fix: use
unquotefor passwords, notunquote_plus.Fix
Replace
unquote_plus(url.username)andunquote_plus(url.password)withurl.username/url.passworddirectly — they're already decoded.(Alternatively, use
urllib.parse.unquoteinstead ofunquote_plusfor these fields.)Workaround
Monkey-patch before creating the engine:
Log output
No response
Operating System
Debian GNU/Linux 12 (bookworm)
Trino Python client version
0.337.0
Trino Server version
480
Python version
python3.10
Are you willing to submit PR?