{"id":"c192191da337a072","repo":"psycopg/psycopg2","slug":"unsupported-format-character-s","errorCode":null,"errorMessage":"unsupported format character: '%s'","messagePattern":"unsupported format character: '(.+?)'","errorType":"exception","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"lib/extras.py","lineNumber":1329,"sourceCode":"    \"\"\"\n    curr = pre = []\n    post = []\n    tokens = _re.split(br'(%.)', sql)\n    for token in tokens:\n        if len(token) != 2 or token[:1] != b'%':\n            curr.append(token)\n            continue\n\n        if token[1:] == b's':\n            if curr is pre:\n                curr = post\n            else:\n                raise ValueError(\n                    \"the query contains more than one '%s' placeholder\")\n        elif token[1:] == b'%':\n            curr.append(b'%')\n        else:\n            raise ValueError(\"unsupported format character: '%s'\"\n                % token[1:].decode('ascii', 'replace'))\n\n    if curr is pre:\n        raise ValueError(\"the query doesn't contain any '%s' placeholder\")\n\n    return pre, post\n\n\n# ascii except alnum and underscore\n_re_clean = _re.compile(\n    '[' + _re.escape(' !\"#$%&\\'()*+,-./:;<=>?@[\\\\]^`{|}~') + ']')\n","sourceCodeStart":1311,"sourceCodeEnd":1341,"githubUrl":"https://github.com/psycopg/psycopg2/blob/3a6d9d6ddc6b53eaa80b712f5fa6b23abbdc38db/lib/extras.py#L1311-L1341","documentation":"Raised by _split_sql() (lib/extras.py:1329-1330) when the query passed to execute_values contains a percent-format token whose character is neither 's' (placeholder) nor '%' (literal). For example '%d', '%f', '%(', '%y'. psycopg2's mogrify only supports '%s' and named '%(name)s' for execute_values templates; other printf-style conversions are unsupported.","triggerScenarios":"Passing a query like execute_values(cur, \"INSERT INTO t VALUES %d\", data) or any query containing a stray '%' followed by an unsupported character. Even a literal '%' in a LIKE clause (e.g. 'LIKE %foo%') triggers it if not doubled.","commonSituations":"Including a literal percent sign in a default/LIKE/string without escaping it as '%%'. Borrowing a query from a logging/printf context that uses %d/%f. The error message substitutes the offending character for the '%s' in the message itself.","solutions":["Escape every literal '%' as '%%' in the execute_values query string.","Replace printf-style format tokens (%d, %f) with '%s' and pass properly typed values, or use psycopg2.sql composition.","Audit LIKE clauses: 'WHERE name LIKE 'a%%' instead of 'WHERE name LIKE 'a%'."],"exampleFix":"// before\nexecute_values(cur, \"INSERT INTO log(msg) VALUES %s WHERE ts > 100%%\", data)\n// after\nexecute_values(cur, \"INSERT INTO log(msg) VALUES %s\", data)","handlingStrategy":"validation","validationCode":"import re\nbad = re.findall(r'%(?!s|%\\?)', sql)  # crude check for unsupported tokens\n# Better: ensure only %s and %% appear:\nassert all(t in ('%s', '%%') for t in re.findall(r'%.', sql)), 'unsupported format token in query'","typeGuard":"def query_uses_only_valid_tokens(sql: str) -> bool:\n    import re\n    return all(t == '%s' or t == '%%' for t in re.findall(r'%.', sql))","tryCatchPattern":"try:\n    execute_values(cur, sql, data)\nexcept ValueError as e:\n    if 'unsupported format character' in str(e):\n        sql = sql.replace('%', '%%')  # escape literals, then re-add one %s\n    else: raise","preventionTips":["Always escape literal '%' as '%%' in any query passed to execute_values.","Never reuse printf-style strings (%d, %f) in psycopg2 query templates."],"tags":["execute-values","placeholder","format-string","value-error","escaping"],"analyzedSha":"3a6d9d6ddc6b53eaa80b712f5fa6b23abbdc38db","analyzedAt":"2026-08-04T19:56:51.958Z","schemaVersion":2}