diff --git a/dbt/adapters/sqlserver/sqlserver_helpers.py b/dbt/adapters/sqlserver/sqlserver_helpers.py index 83d940a5..4d40cade 100644 --- a/dbt/adapters/sqlserver/sqlserver_helpers.py +++ b/dbt/adapters/sqlserver/sqlserver_helpers.py @@ -210,11 +210,17 @@ def build_server_arg(credentials: SQLServerCredentials) -> str: def format_connection_string_value(value: Optional[str], mssql_python_backend: bool) -> str: - """Format a connection-string value for the requested backend.""" + """Format a connection-string value for the requested backend. + + Inside a brace-quoted ODBC value a literal ``}`` must be doubled to ``}}``; + otherwise the driver treats it as the closing brace and misparses the rest + of the connection string. + """ if mssql_python_backend: return escape_connection_string_value(value) - return "{" + ("" if value is None else value) + "}" + text = "" if value is None else str(value) + return "{" + text.replace("}", "}}") + "}" def format_pyodbc_driver_value(value: Optional[str]) -> str: diff --git a/tests/unit/adapters/mssql/test_sqlserver_connection_manager.py b/tests/unit/adapters/mssql/test_sqlserver_connection_manager.py index fb61e875..58fa2db2 100644 --- a/tests/unit/adapters/mssql/test_sqlserver_connection_manager.py +++ b/tests/unit/adapters/mssql/test_sqlserver_connection_manager.py @@ -38,6 +38,7 @@ from dbt.adapters.sqlserver.sqlserver_helpers import ( bool_to_connection_string_arg, escape_connection_string_value, + format_connection_string_value, is_mssql_python_backend, sanitize_connection_string_for_logging, validate_connection_requirements, @@ -349,6 +350,18 @@ def test_escape_connection_string_value_quotes_only_when_needed() -> None: assert escape_connection_string_value("trailing ") == "{trailing }" +def test_format_connection_string_value_doubles_braces_for_pyodbc() -> None: + assert format_connection_string_value("plain", mssql_python_backend=False) == "{plain}" + assert format_connection_string_value("pa}ss", mssql_python_backend=False) == "{pa}}ss}" + assert format_connection_string_value("}{", mssql_python_backend=False) == "{}}{}" + assert format_connection_string_value(None, mssql_python_backend=False) == "{}" + + +def test_format_connection_string_value_delegates_for_mssql_python() -> None: + assert format_connection_string_value("plain", mssql_python_backend=True) == "plain" + assert format_connection_string_value("pa}ss", mssql_python_backend=True) == "{pa}}ss}" + + def test_sanitize_connection_string_for_logging_redacts_common_secret_fields() -> None: sanitized = sanitize_connection_string_for_logging( "SERVER=fake;UID=user@example.com;User Id=another@example.com;"