{"id":"96efdb77d4ac3355","repo":"psycopg/psycopg2","slug":"failed-to-parse-range-s","errorCode":null,"errorMessage":"failed to parse range: '{s}'","messagePattern":"failed to parse range: '(.+?)'","errorType":"exception","errorClass":"InterfaceError","httpStatus":null,"severity":"error","filePath":"lib/_range.py","lineNumber":443,"sourceCode":"        (?:                         # upper bound:\n          \" ( (?: [^\"] | \"\")* ) \"   #   - a quoted string\n          | ( [^\"\\)\\]]+ )           #   - or an unquoted string\n        )?                          #   - or empty (not catched)\n        ( \\)|\\] )                   # upper bound flag\n        \"\"\", re.VERBOSE)\n\n    _re_undouble = re.compile(r'([\"\\\\])\\1')\n\n    def parse(self, s, cur=None):\n        if s is None:\n            return None\n\n        if s == 'empty':\n            return self.range(empty=True)\n\n        m = self._re_range.match(s)\n        if m is None:\n            raise InterfaceError(f\"failed to parse range: '{s}'\")\n\n        lower = m.group(3)\n        if lower is None:\n            lower = m.group(2)\n            if lower is not None:\n                lower = self._re_undouble.sub(r\"\\1\", lower)\n\n        upper = m.group(5)\n        if upper is None:\n            upper = m.group(4)\n            if upper is not None:\n                upper = self._re_undouble.sub(r\"\\1\", upper)\n\n        if cur is not None:\n            lower = cur.cast(self.subtype_oid, lower)\n            upper = cur.cast(self.subtype_oid, upper)\n\n        bounds = m.group(1) + m.group(6)","sourceCodeStart":425,"sourceCodeEnd":461,"githubUrl":"https://github.com/psycopg/psycopg2/blob/3a6d9d6ddc6b53eaa80b712f5fa6b23abbdc38db/lib/_range.py#L425-L461","documentation":"Raised by RangeCaster.parse() (lib/_range.py:441-443) when a string returned by PostgreSQL for a range column does not match the expected range literal grammar (regex at lib/_range.py:418-430). PostgreSQL range output looks like '[1,10)', '(,5]', 'empty', or '[\"a\",\"z\")'. If the server sends malformed data or the typecaster is misregistered, parsing fails.","triggerScenarios":"Fetching from a range column whose value cannot be parsed by _re_range. This is almost always a misregistration (wrong subtype oid mapping a non-range column to a RangeCaster) rather than genuinely corrupt server data, because PostgreSQL itself validates range output.","commonSituations":"Manually creating a RangeCaster with the wrong oid that collides with another type, causing non-range strings to be routed to parse(). Also seen after a pg_dump/restore that changes OIDs while stale typecasters remain registered globally.","solutions":["Confirm the column is actually a range type and that the caster's oid matches it.","Register casters with connection-scoped (not global) scope to avoid oid collisions across databases.","Re-register the range type after a restore/migration that changes OIDs.","Inspect the raw value with the default caster to see what the server actually sent."],"exampleFix":"// before\n# global caster with wrong oid intercepts a text column\ncaster = RangeCaster('myrange', MyRange, oid=25, subtype_oid=25)\ncaster._register()  # oid 25 is text, not a range\n// after\n# register only on the specific connection with the correct oid\ncaster = register_range('myrange', MyRange, conn)","handlingStrategy":"try-catch","validationCode":"from psycopg2 import InterfaceError\n# No pre-call validation; parse() runs at fetch time. Verify caster oid matches column:\nwith conn.cursor() as c:\n    c.execute(\"SELECT atttypid FROM pg_attribute WHERE attrelid=%s::regclass AND attname=%s\", (tbl, col))\n    assert c.fetchone()[0] == expected_oid","typeGuard":null,"tryCatchPattern":"try:\n    rows = cur.fetchall()\nexcept InterfaceError as e:\n    if 'failed to parse range' in str(e):\n        # re-register the caster with the correct oid, then re-query\n        pass\n    else: raise","preventionTips":["Register range casters at connection scope, not globally, to avoid OID collisions.","Re-register casters after a database restore that changes OIDs.","Confirm the column's atttypid equals the caster's oid before fetching."],"tags":["range","parse","interface-error","typecaster","data-corruption"],"analyzedSha":"3a6d9d6ddc6b53eaa80b712f5fa6b23abbdc38db","analyzedAt":"2026-08-04T19:56:51.958Z","schemaVersion":2}