{"id":"fb9adfc309f4981e","repo":"psycopg/psycopg2","slug":"sql-values-must-be-strings","errorCode":null,"errorMessage":"SQL values must be strings","messagePattern":"SQL values must be strings","errorType":"exception","errorClass":"TypeError","httpStatus":null,"severity":"error","filePath":"lib/sql.py","lineNumber":183,"sourceCode":"    names).\n\n    The *string* doesn't undergo any form of escaping, so it is not suitable to\n    represent variable identifiers or values: you should only use it to pass\n    constant strings representing templates or snippets of SQL statements; use\n    other objects such as `Identifier` or `Literal` to represent variable\n    parts.\n\n    Example::\n\n        >>> query = sql.SQL(\"select {0} from {1}\").format(\n        ...    sql.SQL(', ').join([sql.Identifier('foo'), sql.Identifier('bar')]),\n        ...    sql.Identifier('table'))\n        >>> print(query.as_string(conn))\n        select \"foo\", \"bar\" from \"table\"\n    \"\"\"\n    def __init__(self, string):\n        if not isinstance(string, str):\n            raise TypeError(\"SQL values must be strings\")\n        super().__init__(string)\n\n    @property\n    def string(self):\n        \"\"\"The string wrapped by the `!SQL` object.\"\"\"\n        return self._wrapped\n\n    def as_string(self, context):\n        return self._wrapped\n\n    def format(self, *args, **kwargs):\n        \"\"\"\n        Merge `Composable` objects into a template.\n\n        :param `Composable` args: parameters to replace to numbered\n            (``{0}``, ``{1}``) or auto-numbered (``{}``) placeholders\n        :param `Composable` kwargs: parameters to replace to named (``{name}``)\n            placeholders","sourceCodeStart":165,"sourceCodeEnd":201,"githubUrl":"https://github.com/psycopg/psycopg2/blob/3a6d9d6ddc6b53eaa80b712f5fa6b23abbdc38db/lib/sql.py#L165-L201","documentation":"Raised by SQL.__init__ (lib/sql.py:182-183) when the argument to sql.SQL(...) is not a str instance (bytes, int, None, or any other type). SQL wraps a constant snippet of SQL that is inserted verbatim (no escaping), so the input must be text; non-string input would be ambiguous to serialize and could break query encoding.","triggerScenarios":"Calling sql.SQL(b'SELECT 1'), sql.SQL(42), sql.SQL(None), or sql.SQL(some_object). Because SQL content is emitted unescaped, the library enforces str to prevent accidental injection of untrusted bytes/objects.","commonSituations":"Reading a query fragment from a file in binary mode and passing bytes. Passing a value that should be a Literal (escaped) but wrapping it in SQL (unescaped). Forgetting to decode a bytes value from mogrify or an external source.","solutions":["Ensure the argument to sql.SQL is a str; decode bytes first: sql.SQL(fragment.decode('utf8')).","If the value is user-supplied data (not a constant SQL snippet), use sql.Literal(value) instead of sql.SQL.","For identifiers (table/column names) use sql.Identifier, never sql.SQL with a raw name."],"exampleFix":"// before\nq = sql.SQL(b\"SELECT 1\")  # bytes\n// after\nq = sql.SQL(\"SELECT 1\")","handlingStrategy":"type-guard","validationCode":"if not isinstance(s, str):\n    raise TypeError('sql.SQL requires a str')\nq = sql.SQL(s)","typeGuard":"def is_sql_string(s) -> bool:\n    return isinstance(s, str)","tryCatchPattern":"try:\n    q = sql.SQL(s)\nexcept TypeError:\n    if isinstance(s, bytes):\n        q = sql.SQL(s.decode('utf8'))\n    else: raise","preventionTips":["Always pass a str to sql.SQL; decode bytes first.","Use sql.Literal for values and sql.Identifier for names, not sql.SQL.","Keep query fragments in text mode when reading from files."],"tags":["sql","composable","type-error","sql-injection","encoding"],"analyzedSha":"3a6d9d6ddc6b53eaa80b712f5fa6b23abbdc38db","analyzedAt":"2026-08-04T19:56:51.958Z","schemaVersion":2}