如何处理PostgreSQL中的infinity timestamptz类型值?
我完全懂你踩的这个坑!RDS的PostgreSQL确实会把部分系统角色的rolvaliduntil字段设为'infinity',而本地Docker的postgres镜像大概率默认用NULL,这就导致psycopg的默认时间解析器直接炸锅——毕竟它不支持处理超过10000年的时间,infinity自然也在黑名单里。
给你几个实用的解决思路,按需选就行:
1. 最省心:查询时直接把infinity转成NULL
直接在SQL层面处理,从数据库拿出来的就是NULL,不用改任何代码逻辑,简单粗暴见效快:
SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, CASE WHEN rolvaliduntil = 'infinity' THEN NULL ELSE rolvaliduntil END AS rolvaliduntil, rolconfig, oid FROM pg_roles;
核心就是用CASE语句判断rolvaliduntil是否为'infinity',是的话就返回NULL,其他情况保持原值。
2. 一劳永逸:自定义psycopg类型加载器
如果你的代码里有很多地方要查带timestamptz类型的字段,不想每个SQL都改,那可以给psycopg写个自定义加载器,自动把'infinity'转成Python的None:
from psycopg import sql from psycopg_binary.types.datetime import TimestamptzLoader from psycopg import DataError class InfinitySafeTimestamptzLoader(TimestamptzLoader): def cload(self, data): # 检查传入的字节数据是否是'infinity' if data == b'infinity': return None # 其他正常时间用默认逻辑解析 try: return super().cload(data) except DataError: # 万一碰到其他超大时间,也返回None兜底 return None # 给你的数据库连接注册这个加载器 async def setup_safe_connection(conn): # 先设置时区(可选,根据你的需求来) await conn.execute(sql.SQL("SET TIME ZONE 'UTC'")) # 替换默认的timestamptz加载器 conn.adapters.register_loader("timestamptz", InfinitySafeTimestamptzLoader)
之后每次获取数据库连接后,调用这个setup_safe_connection函数,后续所有查询碰到'infinity'的timestamptz值都会自动转成None。
3. 不推荐:修改RDS的角色字段
理论上你可以把系统角色的rolvaliduntil改成NULL,但RDS对系统对象的权限限制很严,大概率不让你改系统角色的属性,而且改系统配置风险也高,所以这个方法只提一嘴,不建议用。
至于为什么本地Docker没问题?就是因为官方postgres镜像默认给rolvaliduntil设的是NULL,而RDS为了默认角色永久有效的逻辑,硬设成了'infinity',这就是两者的差异所在。
总结一下:优先用方法1,快速解决当前问题;如果有大量类似查询,再用方法2统一处理,省心省力。
备注:内容来源于stack exchange,提问作者user48956

