pythia-package 是一个用于 Oracle PL/SQL 对象设计的技能,适用于设计或编写新的包、存储过程、函数、触发器,或对它们进行重构的场景。其核心主张是:先设计规范(spec),再编写实现(body)。
用途与定位 该技能在开始时要求声明“Using pythia-package — designing the spec before the body.”,流程分为 Learn → Ask → Do 三个阶段:先了解相邻对象与调用方,再确认契约,最后将 spec 与 body 作为两个文件分别编写。它补充的是代码库本身可能没有体现的内容:一个单元应当如何设计,才能让调用方信任它。当代码库既有做法与标准不一致时,以代码库为准,优先参考 .pythia/conventions.md,其余未决定的事项参考 reference/plsql-standards.md。
核心原则 技能提出“铁律”:规范即契约,先设计,且绝不静默破坏。包规范变更会一次性使所有依赖对象失效,而包体变更不会使任何对象失效,这种不对称性决定了大部分设计取舍。
工作流程
- 学习:使用 pythia similar <NAME> 和 pythia src 查看前两三个相似对象;对每个将要调用的单元使用 pythia args;包已存在时用 pythia impact <PKG> 评估影响;对涉及的每张表使用 pythia cols。
- 确定契约:除非代码库本身使用独立单元,否则应使用包而非独立单元,以获得封装、私有辅助过程和不会使他人失效的包体变更。公共接口只包含调用方需要的内容,其余保持私有、仅存在于包体中。新增公共项放在规范末尾,以便在 11g+ 的细粒度依赖跟踪下让现有依赖对象保持 VALID。参数使用 %TYPE 锚定;默认使用 IN;OUT 仅用于过程;大型 IN OUT 集合和 LOB 使用 NOCOPY;默认值仅用于真正可选的输入。函数只返回一个值,不设 OUT 参数,且在可从 SQL 调用时不做 DML。错误契约方面,在规范中定义命名异常,或为每个包约定一个 -20xxx 范围,使调用方能区分“未找到”与“出错”。事务契约方面,由最外层调用方提交;自行提交的单元需在名称或规范注释中说明,因为它无法被组合进更大的事务。日志与埋点通过项目既有 logger 进行;长时间运行的入口点使用 DBMS_APPLICATION_INFO.SET_MODULE/SET_ACTION,便于 DBA 观察。安全方面,除非规范需要调用者权限,否则使用 AUTHID DEFINER;内部辅助过程使用 ACCESSIBLE BY(12c);任何进入动态 SQL 的标识符使用 DBMS_ASSERT;代码中不得包含凭据。
- 编写两个文件:先 spec 后 body,每个文件一个对象,末尾加 /,越南语消息使用 pythia unistr;模板见 reference/plsql-standards.md,注释通过 pythia-comment。
- 触发器保持轻量:仅用于派生和审计,不承载业务流程、不 COMMIT、不调用远程系统。同一张表有多个触发时点时应使用一个复合触发器(也是 ORA-04091 变异表错误的解决方式)。业务规则应放在 API 包中,而不是触发器里。
- 检查:运行 pythia lint spec.sql body.sql,然后 pythia-review。
- 落地:先 spec 后 body,可用 pythia deploy spec.sql body.sql(一次审批、按顺序)或两次 pythia apply。退出码 3 表示失败,绝不视为完成。
- 验证:使用 pythia errors <PKG>、pythia invalid 对照基线、pythia plscope <PKG> 检查未使用或从未赋值的标识符。
红线提示 技能列出若干需要停止的思维误区:给现有过程加参数会破坏或重编译所有调用方,应改为重载或在规范末尾新增单元;WHEN OTHERS THEN log 而不 RAISE 会让调用方误以为成功,应记录后重新抛出;在末尾 COMMIT 会使该单元无法被组合进调用方事务,提交应由调用方负责;把规则放进触发器最难发现、测试和绕过,应放入 API 包;用 PRAGMA AUTONOMOUS_TRANSACTION 处理变异表会隐藏错误并读取过期数据,应使用复合触发器;拼接表名即使看似内部也应使用 DBMS_ASSERT.SQL_OBJECT_NAME;标准与代码库冲突时以代码库为准,另行提出标准变更。
不适用场景 对现有单元的小修改使用 pythia-write 即可;表设计使用 pythia-table;性能问题使用 pythia-tune;审查他人单元使用 pythia-review。