pq: errors relatively painful to use

Unless I am missing something (and feel free to correct me) in order to determine what kind of error I’ve encountered I have to do something like this:

if err, ok := err.(*pq.Error); ok {
  switch err.Code.Name() {
    case "unique_violation":
      // ...
    case "deadlock_detected":
      // ....
     default:
       // ....
  }
}

Be much nicer if instead each error code had its own error type:

switch x := err.(type) {
case nil:
  // ignore
case pq.ConnectionFailure:
  // do something special.
case pq.Deadlock:
  // do something special.
case pq.UniqueViolation:
  // do something special.
default:
  // do something generic
}

example implementation: https://play.golang.org/p/AtzpqbMOp7

package main

import "fmt"
import "errors"

type PQError interface {
    error
    PQErrorDetails() Details
}

// Implements PQError interface
type Details struct {
    Severity string
    Code string
}

func (t Details) Error() string {
    return t.Severity + ": " + t.Code
}

func (t Details) PQErrorDetails() Details {
    return t
}
// internal function to lib/pq converts codes to their respective types.
func classify(d Details) PQError {
  switch d.Code {
   case "successful_completion":
     return nil
   case "unique_violation":
     return UniquenessViolation{d}
   case "deadlock_detected":
     return Deadlock{d}
   default:
     return d
  }
}

type UniquenessViolation struct { Details }
type Deadlock struct { Details }

func PrintError(err error) {
    switch err := err.(type) {
        case nil:
        fmt.Println("No Error Hurrah")
    case UniquenessViolation:
        fmt.Println("Uniqueness Error Severity:", err.Severity, "Code:", err.Code)
    case Deadlock:
        fmt.Println("Deadlock Error")
    case PQError:
        fmt.Println("Some other pq error")
    default:
        fmt.Println("Wowa wtf", err.Error())
    }
}

func HandlePQError(err PQError) PQError {
    fmt.Println("Handling PQ Error")
    return err  
}

func HandleGenericError(err error) error {
    if err != nil {
        fmt.Println("Handling generic error")
    } else {
        fmt.Println("No Error Hurrah!")
    }
    return err
}

func main() {
    successful_completion := classify(Details{Severity: "error", Code: "successful_completion"})
    unique_violation := classify(Details{Severity: "error", Code: "unique_violation"})
    deadlock := classify(Details{Severity: "error", Code: "deadlock_detected"})
    generic_pq := classify(Details{Severity: "warn", Code: "ghi"})
    PrintError(successful_completion)
    PrintError(unique_violation)
    PrintError(deadlock)
    PrintError(generic_pq)
    PrintError(errors.New("Something Really Unexpected Happened..."))
    HandleGenericError(HandlePQError(deadlock))
    HandleGenericError(successful_completion)
}
// Output
// No Error Hurrah
// Uniqueness Error Severity: error Code: unique_violation
// Deadlock Error
// Some other pq error
// Wowa wtf Something Really Unexpected Happened...
// Handling PQ Error
// Handling generic error
// No Error Hurrah!

If we wanted to get really clever we could use stringer to do the mapping from code to name and define all the codes as constants: https://godoc.org/golang.org/x/tools/cmd/stringer

About this issue

  • Original URL
  • State: closed
  • Created 9 years ago
  • Reactions: 1
  • Comments: 16 (7 by maintainers)

Most upvoted comments

Anyways I was just trying to offer up a better solution than what was there. If project isn’t interested its not interested ./shrug.

the point is if I as a developer using lib/pq have to do this every time I get an error from the driver:

err := somefunc()
switch err := err.(type) {
case err := *pq.Error:
  switch err.Code.Name() {
    case "unique_violation":
       // ....
    case "deadlock_detected":
       // ....
     default:
       // handle it generically (which I could be doing by the generic case below)
  }
case DifferentError:
   // ....
 case error:
   // generic
}

is very annoying / painful compared to

err := somefunc()
switch err := err.(type) {
case *pq.UniquenessViolation:
  // ....
case *pq.Deadlock
  // ....
case DifferentError:
   // ....
 case error:
   // generic
}

constants wouldn’t solve this problem. I’d still need to do the switch within a switch