我目前正在使用Go的pq
lib与我的PostgreSQL数据库进行通信。事实证明,错误检查比预期的要困难一些。描述我的问题的最简单方法是通过一个示例场景。
想象一下网络表单:
Username ________
Email ________
Voucher ________
Password ________
粗略的架构:
username VARCHAR(255) UNIQUE NOT NULL,
email VARCHAR(255) UNIQUE NOT NULL,
voucher VARCHAR(255) UNIQUE NOT NULL,
password VARCHAR(255) NOT NULL
暂时忽略假定的纯文本密码。如果一个人提交表单,我可以进行所有验证以验证约束,例如长度/允许的字符/等。
现在把它放在数据库中,所以我们编写一个准备好的语句并执行它。如果验证正确完成,唯一真正出错的是UNIQUE
约束。如果有人试图输入现有用户名,则数据库/ sql将重新发送错误。
我的问题是我不知道如何处理该错误并从(应该是)可恢复的错误中恢复。 pq为此提供了some support,但对于返回的内容似乎仍然存在歧义。
我可以看到两种解决方案,这些解决方案对我来说都不是特别有吸引力:
一个SERIALIZABLE
事务,它在插入之前检查每个表单值。或者,在pq错误结构上进行某种形式的解析。
是否有实施此类系统的通用模式?我希望能够对用户Sorry that username exists
而不是Sorry something bad happened
作为旁注,PostgreSQL文档指出:
的字段 模式名称,表名称,列名称,数据类型名称和约束 name仅针对有限数量的错误类型提供;看到 Appendix A.
但链接页面对于数据库对象中返回的值非常有帮助。
答案 0 :(得分:3)
如果验证正确完成,唯一真正出错的是UNIQUE约束。
不,客户端可能缺少足够的权限,客户端可能输入的密码不是正确的密码,客户端可能已输入属于其他客户端的有效凭证等。
使用“在插入前检查每个表单值的SERIALIZABLE事务”没有意义。只需插入数据并捕获错误。
至少,您的代码需要检查并响应C(代码)字段,该字段始终存在于错误结构中。您不需要解析错误结构,但您需要阅读它。
如果违反了唯一约束,PostgreSQL将在“代码”字段中返回SQL状态23505。它还将返回违反的第一个约束的名称。它不返回列名,可能是因为唯一约束可以包含多个列。
您可以通过查询information_schema视图来选择约束所引用的列。
这是你桌子的简单版本。
create table test (
username VARCHAR(255) UNIQUE NOT NULL,
email VARCHAR(255) UNIQUE NOT NULL,
voucher VARCHAR(255) UNIQUE NOT NULL,
password VARCHAR(255) NOT NULL
);
insert into test values ('msherrill', 'me@example.com', 'a', 'wibble');
这个快速又脏的go程序再次插入同一行。它违反了每个唯一约束。
package main
import (
"github.com/lib/pq"
"database/sql"
"fmt"
"log"
)
func main() {
db, err := sql.Open("postgres", "host=localhost port=5435 user=postgres password=xxxxxxxx dbname=scratch sslmode=disable")
if err != nil {
log.Fatal(err)
}
rows, err := db.Exec("insert into public.test values ('msherrill', 'me@example.com', 'a', 'wibble');")
if err, ok := err.(*pq.Error); ok {
fmt.Println("Severity:", err.Severity)
fmt.Println("Code:", err.Code)
fmt.Println("Message:", err.Message)
fmt.Println("Detail:", err.Detail)
fmt.Println("Hint:", err.Hint)
fmt.Println("Position:", err.Position)
fmt.Println("InternalPosition:", err.InternalPosition)
fmt.Println("Where:", err.Where)
fmt.Println("Schema:", err.Schema)
fmt.Println("Table:", err.Table)
fmt.Println("Column:", err.Column)
fmt.Println("DataTypeName:", err.DataTypeName)
fmt.Println("Constraint:", err.Constraint)
fmt.Println("File:", err.File)
fmt.Println("Line:", err.Line)
fmt.Println("Routine:", err.Routine)
}
fmt.Println(rows)
}
这是输出。
Severity: ERROR Code: 23505 Message: duplicate key value violates unique constraint "test_username_key" Detail: Key (username)=(msherrill) already exists. Hint: Position: InternalPosition: Where: Schema: public Table: test Column: DataTypeName: Constraint: test_username_key File: nbtinsert.c Line: 406 Routine: _bt_check_unique
您拥有架构,表和约束名称。您可能也知道数据库(目录)名称。使用这些值从information_schema视图中选择模式,表和列名称。你很幸运;在这种情况下,您只需要一个视图。
select table_catalog, table_schema, table_name, column_name
from information_schema.key_column_usage
where
table_catalog = 'scratch' and -- Database name
table_schema = 'public' and -- value returned by err.Schema
table_name = 'test' and -- value returned by err.Table
constraint_name = 'test_username_key' -- value returned by err.Constraint
order by constraint_catalog, constraint_schema, constraint_name, ordinal_position;