doupin1073 2016-01-23 11:48
浏览 205
已采纳

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

I'm currently using the pq lib for Go to communicate with my PostgreSQL database. Error checking is proving to be a little more difficult than anticipated. The easiest way to describe my question is through an example scenario.

Imagine a web form:

Username  ________
Email     ________
Voucher   ________
Password  ________

A rough schema:

username VARCHAR(255) UNIQUE NOT NULL,
email VARCHAR(255) UNIQUE NOT NULL,
voucher VARCHAR(255) UNIQUE NOT NULL,
password VARCHAR(255) NOT NULL

Ignore the presumed plain text password for now. If a person submits the form, I can do all of my validation to verify constraints such as length/allowed characters/etc.

Now it comes to putting it in the database, so we write a prepared statement and execute it. If the validation was done correctly, the only thing that can really go wrong is the UNIQUE constraints. In the event that someone attempts to enter an existing username, database/sql is going to fire back an error.

My problem is that I have no idea what to do with that error and recover from (what should be) a recoverable error. pq provides some support for this, but there still appears to be come ambiguity to what's returned.

I can see two solutions, neither of which sound particularly appealing to me: A SERIALIZABLE transaction which checks every single form value prior to insertion. Alternatively, some form of parsing on the pq error struct.

Is there a common pattern for implementing such a system? I'd like to be able to say to a user Sorry that username exists rather than Sorry something bad happened

As a sidenote, the PostgreSQL documentation states:

The fields for schema name, table name, column name, data type name, and constraint name are supplied only for a limited number of error types; see Appendix A.

but the linked page isn't very helpful with respect to values returned in the database object.

  • 写回答

1条回答 默认 最新

  • doumeng3345 2016-01-23 14:32
    关注

    If the validation was done correctly, the only thing that can really go wrong is the UNIQUE constraints.

    No, the client could lack sufficient privileges, the client might have entered a valid password that's not the right password, the client might have entered a valid voucher that belongs to a different client, etc.

    Using "A SERIALIZABLE transaction which checks every single form value prior to insertion" doesn't make sense. Just insert data, and trap errors.

    At the very least, your code needs to examine and respond to the C (Code) field, which is always present in the error struct. You don't need to parse the error struct, but you do need to read it.

    If you violate a unique constraint, PostgreSQL will return SQL state 23505 in the Code field. It will also return the name of the first constraint that's violated. It doesn't return the column name, probably because a unique constraint can include more than one column.

    You can select the column(s) the constraint refers to by querying the information_schema views.

    Here's a simple version of your table.

    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');
    

    This quick and dirty go program inserts the same row again. It violates every unique constraint.

    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)
    }
    

    Here's the output.

    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
    

    You have the schema, table, and constraint names. You presumably know the database (catalog) name, too. Use these values to select the schema, table, and column names from information_schema views. You're lucky; in this case you need only one view.

    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;
    
    本回答被题主选为最佳回答 , 对您是否有帮助呢?
    评论

报告相同问题?

悬赏问题

  • ¥15 如何在scanpy上做差异基因和通路富集?
  • ¥20 关于#硬件工程#的问题,请各位专家解答!
  • ¥15 关于#matlab#的问题:期望的系统闭环传递函数为G(s)=wn^2/s^2+2¢wn+wn^2阻尼系数¢=0.707,使系统具有较小的超调量
  • ¥15 FLUENT如何实现在堆积颗粒的上表面加载高斯热源
  • ¥30 截图中的mathematics程序转换成matlab
  • ¥15 动力学代码报错,维度不匹配
  • ¥15 Power query添加列问题
  • ¥50 Kubernetes&Fission&Eleasticsearch
  • ¥15 報錯:Person is not mapped,如何解決?
  • ¥15 c++头文件不能识别CDialog