zylon-ai/private-gpt · error · ValueError

MSSQL connection requires pyodbc scheme when no driver speci

Error message

MSSQL connection requires pyodbc scheme when no driver specified

What it means

Raised while preparing an MSSQL (TSQL dialect) connection string: when the URL has no explicit 'driver' query parameter, the code injects 'ODBC Driver 18 for SQL Server' — but only if the scheme starts with mssql+pyodbc. Any other scheme (mssql, mssql+pymssql, ...) cannot accept an ODBC driver parameter, so the code rejects it rather than building a broken connection string.

Source

Thrown at private_gpt/components/tabular/database_query_generator.py:435

            modified_query = f'CALL {proc_name}({params_with_cursor}); FETCH ALL FROM "{cursor_name}";'

            return modified_query

        return query

    def _prepare_connection_string(self) -> None:
        parsed = urlparse(self.connection_string)
        params = parse_qs(parsed.query, keep_blank_values=True)

        if self._dialect in [Dialects.TSQL]:
            # MSSQL specific adjustments
            if not any(k.lower() == "encrypt" for k in params):
                params["Encrypt"] = ["yes" if self.ssl else "no"]

            if not any(k.lower() == "driver" for k in params):
                if not parsed.scheme.startswith("mssql+pyodbc"):
                    raise ValueError(
                        "MSSQL connection requires pyodbc scheme when no driver specified"
                    )
                params["driver"] = ["ODBC Driver 18 for SQL Server"]

        elif self._dialect in [Dialects.MYSQL]:
            # MySQL specific adjustments
            if parsed.scheme == "mysql" or parsed.scheme == "mysql+mysqldb":
                parsed = parsed._replace(scheme="mysql+pymysql")
            elif not parsed.scheme.startswith("mysql+pymysql"):
                raise ValueError(
                    "MySQL connection requires mysql, mysql+mysqldb, or mysql+pymysql scheme"
                )
            if not any(k.lower() == "charset" for k in params):
                params["charset"] = ["utf8mb4"]

        flattened = {k: v[0] if len(v) == 1 else v for k, v in params.items()}
        new_parsed = parsed._replace(query=urlencode(flattened, doseq=True))
        self.connection_string = str(urlunparse(new_parsed))

View on GitHub (pinned to 4a030776a3)

Solutions

  1. Use the pyodbc scheme: mssql+pyodbc://user:pass@host:1433/db?driver=ODBC+Driver+18+for+SQL+Server
  2. Or keep your existing mssql:// scheme but add an explicit driver parameter — note non-pyodbc schemes still need the driver to be ODBC-compatible for SQLAlchemy
  3. URL-encode spaces in the driver value or use + (ODBC+Driver+18+for+SQL+Server)

Example fix

# before
connection_string = "mssql://sa:pass@db:1433/master"
# after
connection_string = (
    "mssql+pyodbc://sa:pass@db:1433/master"
    "?driver=ODBC+Driver+18+for+SQL+Server&Encrypt=yes"
)
Defensive patterns

Strategy: validation

Validate before calling

from urllib.parse import urlparse, parse_qs

parsed = urlparse(connection_string)
params = parse_qs(parsed.query, keep_blank_values=True)
has_driver = any(k.lower() == "driver" for k in params)
if not has_driver and not parsed.scheme.startswith("mssql+pyodbc"):
    raise ValueError(
        "MSSQL URL needs ?driver=... or the mssql+pyodbc scheme"
    )

Type guard

def is_valid_mssql_url(conn: str) -> bool:
    p = urlparse(conn)
    q = parse_qs(p.query, keep_blank_values=True)
    return p.scheme.startswith("mssql+pyodbc") or any(
        k.lower() == "driver" for k in q
    )

Prevention

When it happens

Trigger: Supplying a connection string like mssql://user:pass@host/db or mssql+pymssql://... without a ?driver=... parameter while the dialect resolves to TSQL.

Common situations: Copy-pasting an ADO or pymssql-style URL from another tool; defaulting encrypt/driver handling assumptions from SQL Server docs; switching a generic mssql:// URL into this component without rewriting the scheme.

Related errors


AI-assisted analysis of zylon-ai/private-gpt@4a030776a3 (2026-08-15). Data as JSON: /api/errors/154a839567bc7373. Report an issue: GitHub.