news 2026/9/19 4:54:27

psycopg2 参数化建表,把 AsIs 写法交给走 TaoToken 的 Codex 复查

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
psycopg2 参数化建表,把 AsIs 写法交给走 TaoToken 的 Codex 复查

psycopg2 的 AsIs 建表写法,交给 Codex 复查前先接 TaoToken,打开 https://taotoken.net/?utm_source=taotoken_aicg_blog_end 拿一把 Key,再把 Codex 的执行通道换到兼容地址,就能让它逐行读那段 execute。原文的诉求其实很清楚:PostgreSQL 创建表时,schema 和表名都想当参数传进来,不在代码里写死public.user这种字符串。为了达到这个效果,作者用了AsIs(schema)AsIs(table),把两个变量原样拼进create table %s.%s(name text)。这版代码能跑通,但「参数化」这三个字在这里被理解偏了——psycopg2 的%s占位符只对生效,对表名、schema、列名这类标识符不生效。这篇不重写你的业务流程,只做一件事:把那段 execute 送进 Codex 做一次结构化复查,顺带把接入配置讲透。

1. AsIs 把 schema 和表名当参数,究竟绕过了什么

1.1 psycopg2 的 %s 只管值,不管标识符

写惯了cur.execute("insert into t values(%s)", (v,))的人,很容易顺推出「表名也能这么传」。但 PostgreSQL 的协议层面,参数化只覆盖数据值,不覆盖 SQL 语法树里的对象名。你写select * from %s,驱动不会把%s替换成一个被正确引用的表名,它会直接报语法错误,因为解析器在看到%s那一刻还没拿到参数。

所以标识符要动态化,只有两条路:要么自己拼字符串并负责加引号、转义,要么用驱动提供的标识符包装类型。psycopg2 给了两个:sql.Identifierextensions.AsIs。名字上AsIs更短、用起来更直接,于是很多示例代码都选了它。

1.2 AsIs 的行为是「原样塞进去」,不是「安全地传进去」

看原始写法:

from psycopg2.extensions import AsIs cur.execute( "create table %s.%s(name text)", (AsIs(schema), AsIs(table)), )

AsIs的名字就是它的全部行为:as-is,原样。它会把这个 Python 字符串直接写进 SQL 文本,不加双引号,不做标识符转义。这意味着三件事同时成立:

  • 拼接结果完全由schematable的取值决定,只要这两个值来自外部(比如租户配置表、前端参数、URL 路径),注入面就敞开了;
  • 大小写敏感问题回来了。PostgreSQL 会把未加引号的标识符折叠成小写,你的create table TenantA.UserB实际建出来的是tenanta.userb,事后按原样去查会找不到;
  • 保留字冲突。表名恰好是orderusergroup这类词时,语句直接语法错误。

1.3 多租户建表更容易放大这个问题

单库单 schema 的项目,表名通常是常量,AsIs看起来只是「省了个拼接」。真正危险的是多租户场景:schema 一般等于租户 ID,表名往往跟着业务模板走,这两者经常来自配置文件或数据库里的元数据表。你信任元数据表的内容,但元数据表本身也可能被写入脏数据;一旦某个租户的 schema 名里带了分号或者双引号,这条create table就跑出你预期之外的东西了。

这不是说AsIs完全不能用,而是说用它等于自己接管了「标识符引用」这件事,而绝大多数人并没有真的接管——只是把风险藏起来了。

2. 在 ~/.codex/config.toml 里把 Codex 接到 TaoToken

2.1 先去官网创建一把 API Key

复查代码这件事,最省事的执行工具是 Codex:给它贴一段代码和一条明确的问题,它能把改写理由和可运行版本一起给你。前提是它得有个能稳定调到模型的通道。

打开 https://taotoken.net/?utm_source=taotoken_aicg_blog_end 注册,进控制台创建一把 API Key,复制出来备用。这个 Key 后面要填进 Codex 的环境变量,不要在代码里硬编码,也不要提交进仓库。顺手在模型广场记一下你打算用的模型 ID,后面填进配置。如果你想先确认 Key 可用,可以在 TaoToken 模型对话 里发一条测试消息。

2.2 config.toml 里的 model_provider 与 base_url

Codex 的配置文件是~/.codex/config.toml。这里要改的是供应商地址,不是别的东西——注意 Codex 不读ANTHROPIC_BASE_URL那套环境变量,别把 Claude Code 的配置照搬过来。

model = "YOUR_MODEL_ID" model_provider = "taotoken" [model_providers.taotoken] name = "TaoToken" base_url = "https://taotoken.net/api" env_key = "TAOTOKEN_API_KEY" wire_api = "chat"

几点说明:

  • base_urlhttps://taotoken.net/api,末尾不要带/v1。很多 404 都是因为在这里多敲了一段路径,工具再去拼/v1/chat/completions就拼重了;
  • model的取值以 https://taotoken.net/?utm_source=taotoken_aicg_blog_end 模型广场当时的列表为准,不要凭印象写。模型 ID 写错了通常不会给你一句人话,只会返回一个参数错误;
  • env_key只是告诉 Codex「从这个环境变量里取密钥」,键名你自己定,保持一致即可。

2.3 把 Key 放进环境变量再启动

export TAOTOKEN_API_KEY=YOUR_API_KEY codex

如果你用的是把凭据存在~/.codex/auth.json的版本,那就把文件里对应的凭据字段换成这把 Key,其余结构别动。两种方式选一种,不要同时配,否则排查起来会分不清到底读的是哪份。

启动后在终端里问一句「你当前用的是哪个 model provider」,它能正确回出配置里的名字,就说明通道通了。这一步没通之前,不要急着贴建表代码——不然你分不清是代码问题还是配置问题。

3. 把那段 execute 原样贴给 Codex,问题清单怎么问

3.1 一条能拿到可运行版本的提问模板

贴代码的时候,把上下文一起给出来,Codex 的结论会准得多。建议这样组织:

下面这段是 psycopg2 在 PostgreSQL 里动态建表的代码,schema 和 table 都来自 外部配置,目标是表名不写死。请判断: 1) AsIs 拼接在这里是否存在注入或标识符引用问题; 2) 是否应改用 psycopg2.sql.Identifier,为什么; 3) 给出一个可以直接运行的改写版本,保留不写死表名的目标; 4) 列出改写后行为上会变化的地方。 from psycopg2.extensions import AsIs cur.execute("create table %s.%s(name text)", (AsIs(schema), AsIs(table)))

关键是把「目标」讲清楚:不写死表名。如果你只说「帮我看看这段代码」,很容易收到一句「建议使用参数化查询」就结束,那对你没帮助。

3.2 Codex 大概率会点出的三处

把上面那段贴过去,通常能收到三类反馈,正好对应第 1 节里的分析:

第一,AsIs不做标识符引用,等价于字符串拼接,只是换了个类型名。外部可控的 schema 或表名进来,注入面没有消失。

第二,不加引号的标识符会被 PostgreSQL 折叠成小写,含大写字母或保留字的表名在建立和查询之间会产生不一致。这一点在实际项目里比注入更常见,因为它不报错,只是「查不到」。

第三,真正的标识符参数化工具是psycopg2.sql.Identifier,它会在拼接时调用quote_ident,该加引号的地方加引号,该转义的地方转义。

3.3 sql.Identifier 版本,照着改

复查之后,把建表函数改成这样:

from psycopg2 import sql def create_table(cur, schema: str, table: str) -> None: stmt = sql.SQL( "create table if not exists {}.{}(name text)" ).format( sql.Identifier(schema), sql.Identifier(table), ) cur.execute(stmt)

AsIs版本对比,差异是结构性的:sql.SQL负责承载固定骨架,sql.Identifier负责承载动态的标识符,两者用.format()组合,不是靠%s占位符。这样既保留了「表名和 schema 都从参数来」的原始目标,又不用你手动处理引号。

顺带补了两点:if not exists让重复执行不炸,函数签名带上类型标注,方便后续在调用处看出入参来源。

如果你还需要动态列名,思路一样,再加一个sql.Identifier即可;但如果列名也多到需要循环拼接,更该考虑的是把建表语句做成模板文件,而不是在 Python 里越拼越长。

3.4 在本地执行,把结果贴回对话

这里划一条线:Codex 负责读代码、给改写版本、解释差异;真正连库执行的是你本地的 Python 脚本或 psql。不要指望它替你连上数据库跑建表语句,它也不该拿到你的库连接信息。

你可以在本地这样验证:

python -c " import psycopg2 from psycopg2 import sql conn = psycopg2.connect('dbname=test user=postgres') cur = conn.cursor() cur.execute(sql.SQL('create table if not exists {}.{}(name text)').format( sql.Identifier('tenant_a'), sql.Identifier('UserB'))) conn.commit() print('ok') "

跑完去 psql 里对一下:

\dt tenant_a.* select schemaname, tablename from pg_tables where schemaname = 'tenant_a';

看到UserB是带引号保留大写的形式建出来的,说明引号逻辑生效了;如果看到的是userb,说明还有地方没走Identifier。把pg_tables的查询结果贴回 Codex 的对话,让它对着真实结果再确认一次,比在脑子里推演靠谱。

4. 复查和运行阶段会撞上的报错

4.1 Codex 侧:401 与模型不存在

贴代码之前 Codex 就报错的,先看通道。返回 401 一般是 Key 有问题:复制时带了空格、Key 被删除、或者环境变量名和env_key写的不一致。回到 https://taotoken.net/?utm_source=taotoken_aicg_blog_end 控制台重新创建一把,再export一次,注意重新开一个终端会话。

如果返回的是模型相关错误,大概率是model字段的取值和模型广场对不上。这类错误不会自我修复,去模型广场复制一次准确 ID 最省时间。

4.2 base_url 末尾的 /v1 是最容易被忽视的一处

base_url = "https://taotoken.net/api/v1"这种写法在别的工具里可能是对的,但在 Codex 这里会拼出重复路径,典型表现是 404 或「接口不存在」。改回https://taotoken.net/api,末尾不加/v1,不要加斜杠。切换供应商时顺手检查这一行,能省掉一轮排查。

4.3 psycopg2 侧:语法错误与表不存在

报错常见原因处理方向
SyntaxError: syntax error at or near "."%s占位符被当成标识符用,或表名撞上保留字sql.Identifier,并给保留字加引号
relation "public.user" does not exist建表时未加引号被折叠成小写,查询时按原样查统一两边的大小写,或统一走Identifier
InvalidSchemaName: schema "x" does not existschema 没提前建,或名字大小写不一致create schema,或补if not exists
InsufficientPrivilege: permission denied for schema当前连接角色没有该 schema 的建表权限换角色或补授权,不要在代码里绕

这些报错里有相当一部分不是AsIs造成的,但都会被误记到它头上。把报错原文整段贴回 Codex 对话,让它对着你的 SQL 和pg_tables结果做一次对照,比逐条试要快。

4.4 search_path 会掩盖一部分问题

如果你的连接串里配了search_pathcreate table tenant_a.userb(...)在某些情况下会落到默认 schema 里去,尤其是在 schema 名和当前用户同名时。验证阶段建议显式带上 schema 前缀,别依赖search_path。这一点在改写前后都成立,但用Identifier之后行为更可预测,因为它不会因为引号缺失而发生变化。

5. 改完之后,用同一把 Key 复测并回控制台对账

5.1 在模型对话里复测一次改写结果

本地跑通之后,把改写后的create_table函数和pg_tables的查询结果一起贴给 Codex,让它确认三件事:改写是否覆盖了原来AsIs的行为、有没有引入新的引号问题、if not exists会不会掩盖 schema 不存在的错误。最后一条容易被忽略——if not exists只管表,不管 schema。

如果你换了模型想再测一遍同样的提问,去 TaoToken 模型对话 里用同一把 Key 发一轮,方便对比不同模型对sql.Identifier的解释是否一致。

5.2 回控制台看这次调用有没有记上账

Codex 跑完几轮之后,去 控制台 API Keys 对照一下调用记录。如果刚才在 Config 和对话里都发了请求,这里应该能看到对应的量。看不到就说明请求根本没走到这条通道,那就回到 4.1 和 4.2 重新看配置,而不是去怀疑建表代码。

长期用 Codex 做这类代码复查,调用频次比随手问一句高不少,可以先看 Coding Plan 的套餐能不能覆盖你的日常量,再决定要不要换。Codex 在config.toml里的字段对照,官方文档里也有更细的说明,配置项多的时候对着看比凭记忆改稳。

回到最初那段cur.execute("create table %s.%s(name text)", (AsIs(schema), AsIs(table)))——它不是不能用,只是它把「安全引用标识符」这件事留给了调用者。让 Codex 复查的价值不在于它替你做决定,而在于它会明确告诉你:你省下的那点拼接代码,换来的是自己承担quote_ident的全部责任。想清楚这笔账,再决定是保留AsIs并补上自己的转义函数,还是直接换成sql.Identifier

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/19 4:53:54

MuJoCo 木块自由落体:Codex 走 TaoToken 生成并跑通 01_mujoco_helloworld.py

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/19 4:53:12

基于深度学习的鸟类识别检测系统:YOLO实战全解析

1. 项目概述与需求拆解1.1 为什么鸟类识别适合做毕设每年到了毕设选题季,总有学弟学妹来问我:"学长,深度学习方向的题目到底选什么好?"我的回答一直很明确:选一个数据好找、场景直观、算法成熟但又有优化空间…

作者头像 李华
网站建设 2026/9/19 4:52:55

Rust 打造 OpenObserve:替代 Elasticsearch 和 Prometheus 的可观测性实战

1. 为什么我又把日志和指标系统折腾了一遍如果你运维过中等规模的线上环境,大概率经历过这样的场景:Elasticsearch 集群的 JVM 堆内存三天两头告警,Prometheus 的 TSDB 在高峰期写入延迟飙升,Grafana 面板加载慢得让人想砸键盘。更…

作者头像 李华
网站建设 2026/9/19 4:52:18

SpringBoot打造高校双创服务平台架构与实践

1. 项目背景与核心价值在高校创新创业教育蓬勃发展的当下,一个真正好用的大学生双创服务平台应该长什么样?去年我参与指导某高校创业学院信息化建设时,发现现有平台普遍存在三个痛点:赛事信息分散在十几个微信群、优秀案例展示停留…

作者头像 李华
网站建设 2026/9/19 4:50:44

OpenClaw Skill技术架构与自动化开发实践

1. OpenClaw Skill 20 篇系列博客核心价值解析作为一个长期关注自动化技术发展的从业者,我完整跟踪了OpenClaw Skill系列的全部20篇技术博客。这个系列最令人印象深刻的是它构建了一套完整的技能开发体系,从基础概念到企业级部署,形成了一个闭…

作者头像 李华