Postgres SQLException消息生成及相关技术问题问询
我们项目近期从Informix迁移到PostgreSQL,有个依赖我方API的客户端需要通过SQLException的完整错误消息做验证。两款数据库驱动抛出的消息唯一区别是PostgreSQL会在消息开头加"ERROR:",示例如下:
ERROR: insert or update on table "bonus" violates foreign key constraint "bonus_cust_id_fkey"
Detail: Key (cust_id)=(1998) is not present in table "customer"
我调试过org.postgresql.core.v3.QueryExecutorImpl来定位查询执行失败时错误消息的生成位置,但没找到——因为驱动会先把查询、消息转成字节数组再解码。我在PGStream类里找到这段负责错误消息最后生成步骤的代码:
public EncodingPredictor.DecodeResult receiveErrorString(int len) throws IOException { if (!this.pgInput.ensureBytes(len)) { throw new EOFException(); } else { EncodingPredictor.DecodeResult res; try { String value = this.encoding.decode(this.pgInput.getBuffer(), this.pgInput.getIndex(), len); res = new EncodingPredictor.DecodeResult(value, (String)null); } catch (IOException var6) { res = EncodingPredictor.decode(this.pgInput.getBuffer(), this.pgInput.getIndex(), len); if (res == null) { Encoding enc = Encoding.defaultEncoding(); String value = enc.decode(this.pgInput.getBuffer(), this.pgInput.getIndex(), len); res = new EncodingPredictor.DecodeResult(value, enc.name()); } } this.pgInput.skip((long)len); return res; } }
之后我了解到错误消息生成的部分逻辑,参考这段源码片段:
ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), /* translator: first %s is typically the translation of "syntax error" */ errmsg("%s at or near \"%s\"", _(message), loc), lexer_errposition()));
问题列表
- 错误消息是否始终以"ERROR:"开头?有没有配置能把前缀改成WARN、DEBUG这类(推测前缀代表日志级别)?
- 上面提到的那段PostgreSQL后端源码是否是生成错误消息的代码位置?
- 如果问题2答案是否,能否告知postgresql-42.7.3.jar里生成错误消息的具体类和方法?
解答
问题1
PostgreSQL抛出的错误消息(通过JDBC驱动返回的SQLException)始终以"ERROR:"开头,这个前缀是数据库后端生成的,代表错误的严重级别(ERROR是最高级别的错误之一,会终止当前事务/语句)。
目前没有配置项可以修改这个前缀——因为这是PostgreSQL后端错误报告机制的固定格式,不同严重级别的消息会对应不同前缀(比如WARNING会以"WARNING:"开头),但ERROR级别的消息必然带"ERROR:"前缀。如果需要适配客户端,建议在JDBC层捕获SQLException后,手动去除开头的"ERROR:"前缀,再返回给客户端。
问题2
你提到的那段后端源码是语法错误相关的错误消息生成位置,但不是所有错误消息的生成处。PostgreSQL中所有错误消息都是通过ereport宏触发的,这个宏在整个后端代码中被广泛调用(比如约束违反、权限错误、IO错误等都会调用),不同类型的错误对应不同模块的ereport调用,那段源码只是语法解析阶段生成错误的其中一处。
问题3
在postgresql-42.7.3.jar中,错误消息的处理和组装是在org.postgresql.core.v3.QueryExecutorImpl类里完成的,具体来说是processErrorResponse方法。这个方法会读取后端返回的错误响应包(包含错误级别、消息、详情等字段),然后将这些字段组装成完整的错误消息字符串,最终封装成SQLException抛出。
你之前调试QueryExecutorImpl没找到,是因为错误消息的原始内容是后端返回的字节流,receiveErrorString只是负责解码字节流为字符串,而processErrorResponse才是把各个字段(比如级别前缀"ERROR:"、主消息、Detail等)拼接成最终错误消息的地方。
内容的提问来源于stack exchange,提问作者Abhinash Jha

