去pq和Postgres适当的错误处理约束

Sam*_*Sam 2 postgresql validation error-handling go

我当前正在使用pqGo 的lib与我的PostgreSQL数据库进行通信。事实证明,错误检查比预期的要困难一些。描述我的问题的最简单方法是通过一个示例场景。

想象一个网络表单:

Username  ________
Email     ________
Voucher   ________
Password  ________
Run Code Online (Sandbox Code Playgroud)

粗略的模式:

username VARCHAR(255) UNIQUE NOT NULL,
email VARCHAR(255) UNIQUE NOT NULL,
voucher VARCHAR(255) UNIQUE NOT NULL,
password VARCHAR(255) NOT NULL
Run Code Online (Sandbox Code Playgroud)

现在忽略假定的纯文本密码。如果有人提交表格,我可以进行所有验证以验证约束,例如长度/允许的字符/等。

现在将其放入数据库中,因此我们编写一个准备好的语句并执行它。如果验证正确完成,唯一会出错的就是UNIQUE约束。如果有人尝试输入现有的用户名,则数据库/ sql将回发错误。

我的问题是我不知道如何处理该错误并从(应该是)可恢复的错误中恢复。pq 为此提供了一些支持,但是返回的内容似乎仍然模棱两可。

我可以看到两个解决方案,这两个听起来都不是特别吸引我:一种SERIALIZABLE在插入之前检查每个表单值的事务。或者,对pq错误结构进行某种形式的解析。

有没有实现这种系统的通用模式?我想对用户说,Sorry that username exists而不是说Sorry something bad happened

附带说明,PostgreSQL文档指出:

仅为有限数量的错误类型提供模式名称,表名称,列名称,数据类型名称和约束名称的字段。参见 附录A。

但是链接页面对于数据库对象中返回的值不是很有帮助。

Mik*_*ll' 5

如果验证正确完成,唯一会出错的就是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');
Run Code Online (Sandbox Code Playgroud)

这个快速又肮脏的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)
}
Run Code Online (Sandbox Code Playgroud)

这是输出。

严重性:ERROR
代号:23505
消息:重复的键值违反了唯一约束“ test_username_key”
详细信息:密钥(用户名)=(msherrill)已存在。
暗示: 
位置: 
内部位置: 
哪里: 
架构:公开
表:测试
柱: 
DataTypeName: 
约束:test_username_key
档案:nbtinsert.c
线:406
例行程序:_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;
Run Code Online (Sandbox Code Playgroud)