From 1a84a6de68485534c5fe6d0d827829d0e87b5b10 Mon Sep 17 00:00:00 2001 From: Grieve Date: Tue, 5 Nov 2024 18:55:24 +0800 Subject: [PATCH] fix(mssql): escape special characters for odbc (#889) --- ibis-server/app/model/data_source.py | 12 +++++++++--- ibis-server/tests/routers/v2/connector/test_mssql.py | 8 ++++++-- 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/ibis-server/app/model/data_source.py b/ibis-server/app/model/data_source.py index 28a834b04..848ad5e33 100644 --- a/ibis-server/app/model/data_source.py +++ b/ibis-server/app/model/data_source.py @@ -120,14 +120,16 @@ def get_druid_connection(info: DruidConnectionInfo) -> BaseBackend: path=info.path, ) - @staticmethod - def get_mssql_connection(info: MSSqlConnectionInfo) -> BaseBackend: + @classmethod + def get_mssql_connection(cls, info: MSSqlConnectionInfo) -> BaseBackend: return ibis.mssql.connect( host=info.host.get_secret_value(), port=info.port.get_secret_value(), database=info.database.get_secret_value(), user=info.user.get_secret_value(), - password=info.password.get_secret_value(), + password=cls._escape_special_characters_for_odbc( + info.password.get_secret_value() + ), driver=info.driver, TDS_Version=info.tds_version, **info.kwargs if info.kwargs else dict(), @@ -173,3 +175,7 @@ def get_trino_connection(info: TrinoConnectionInfo) -> BaseBackend: user=(info.user and info.user.get_secret_value()), password=(info.password and info.password.get_secret_value()), ) + + @staticmethod + def _escape_special_characters_for_odbc(value: str) -> str: + return "{" + value.replace("}", "}}") + "}" diff --git a/ibis-server/tests/routers/v2/connector/test_mssql.py b/ibis-server/tests/routers/v2/connector/test_mssql.py index 1e3061981..b2e4b73cc 100644 --- a/ibis-server/tests/routers/v2/connector/test_mssql.py +++ b/ibis-server/tests/routers/v2/connector/test_mssql.py @@ -1,4 +1,5 @@ import base64 +import urllib import orjson import pandas as pd @@ -77,7 +78,9 @@ def manifest_str(): @pytest.fixture(scope="module") def mssql(request) -> SqlServerContainer: mssql = SqlServerContainer( - "mcr.microsoft.com/mssql/server:2019-CU27-ubuntu-20.04", dialect="mssql+pyodbc" + "mcr.microsoft.com/mssql/server:2019-CU27-ubuntu-20.04", + dialect="mssql+pyodbc", + password="{R;3G1/8Al2AniRye", ).start() engine = sqlalchemy.create_engine( f"{mssql.get_connection_url()}?driver=ODBC+Driver+18+for+SQL+Server&TrustServerCertificate=YES" @@ -150,6 +153,7 @@ def test_query(manifest_str, mssql: SqlServerContainer): "bytea_column": "object", } + @pytest.mark.skip("Wait ibis handle special characters in connection string") def test_query_with_connection_url(manifest_str, mssql: SqlServerContainer): connection_url = _to_connection_url(mssql) response = client.post( @@ -389,4 +393,4 @@ def _to_connection_info(mssql: SqlServerContainer): def _to_connection_url(mssql: SqlServerContainer): info = _to_connection_info(mssql) - return f"mssql://{info['user']}:{info['password']}@{info['host']}:{info['port']}/{info['database']}?driver=ODBC+Driver+18+for+SQL+Server&TrustServerCertificate=YES" + return f"mssql://{info['user']}:{urllib.parse.quote_plus(info['password'])}@{info['host']}:{info['port']}/{info['database']}?driver=ODBC+Driver+18+for+SQL+Server&TrustServerCertificate=YES"