为了账号安全,请及时绑定邮箱和手机立即绑定

使用 pq 和 Postgres 对约束进行适当的错误处理

使用 pq 和 Postgres 对约束进行适当的错误处理

Go
开心每一天1111 2021-12-07 15:12:31
我目前正在使用pqGo的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约束。如果有人试图输入现有的用户名,database/sql 将触发一个错误。我的问题是我不知道如何处理该错误并从(应该是)可恢复的错误中恢复。pq对此提供了一些支持,但返回的内容似乎仍然存在歧义。我可以看到两种解决方案,这两种解决方案对我来说都不是特别有吸引力:SERIALIZABLE在插入之前检查每个表单值的事务。或者,对 pq 错误结构进行某种形式的解析。是否有实现这样一个系统的通用模式?我希望能够对用户说Sorry that username exists而不是Sorry something bad happened作为旁注,PostgreSQL 文档指出:模式名称、表名称、列名称、数据类型名称和约束名称的字段仅针对有限数量的错误类型提供;见 附录 A。但是链接页面对于数据库对象中返回的值不是很有帮助。
查看完整描述

1 回答

?
幕布斯6054654

TA贡献1876条经验 获得超7个赞

如果验证正确完成,唯一可能真正出错的是 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)

}

这是输出。


严重性:错误

代码:23505

消息:重复键值违反唯一约束“test_username_key”

详细信息:密钥(用户名)=(msherrill)已经存在。

暗示: 

位置: 

内部位置: 

在哪里: 

架构:公共

表:测试

柱子: 

数据类型名称: 

约束: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;


查看完整回答
反对 回复 2021-12-07
  • 1 回答
  • 0 关注
  • 159 浏览
慕课专栏
更多

添加回答

举报

0/150
提交
取消
意见反馈 帮助中心 APP下载
官方微信