1
\$\begingroup\$

I'm adding db access to my go program, it looks like this:

package db_access

import (
    "database/sql"
    "fmt"
    "my_database"
)

type TheType struct {
    Name string 
}

func LoadTheTypeFromDb(db *sql.DB, id uint) (*TheType, error) {
    query := fmt.Sprintf(
        "SELECT %s FROM %s WHERE %s = ?",
        my_database.NAME,
        my_database.TABLE,
        my_database.ID,
    )

    var theType TheType

    err := db.QueryRow(query, id).Scan(&theType.Name)

    switch {
    case err == sql.ErrNoRows:
        n := my_database.NewNotFoundError(err)
        return nil, &n
    case err != nil:
        return nil, err
    default:
        return &theType, nil
    }
}

And some part of the my_database package:

package my_database

import (
    "database/sql"
    _ "github.com/go-sql-driver/mysql"
)

func GetDatabaseInstance(connectionString string) (*sql.DB, error) {
    db, err := sql.Open("mysql", connectionString)

    if err != nil {
        c := newConnectionError(err)
        return nil, &c
    }

    err = db.Ping()

    if err != nil {
        c := newConnectionError(err)
        return nil, &c
    }

    return db, nil
}

And the custom errors:

package my_database

import (
    "fmt"
    "reflect"
)

type ConnectionError struct {
    errorMessage string
}

func newConnectionError(err error) ConnectionError {
    return ConnectionError{fmt.Sprintf("Connection error %s %s", reflect.TypeOf(err), err)}
}

func (e *ConnectionError) Error() string {
    return e.errorMessage
}

type QueryError struct {
    errorMessage string
}

func newQueryError(err error) QueryError {
    return QueryError{fmt.Sprintf("Query error %s %s", reflect.TypeOf(err), err)}
}

func (e *QueryError) Error() string {
    return e.errorMessage
}

type NotFoundError struct {
    errorMessage string
}

func NewNotFoundError(err error) NotFoundError {
    return NotFoundError{fmt.Sprintf("Not found - %s", err)}
}

func (e *NotFoundError) Error() string {
    return e.errorMessage
}
  • I add a custom NotFound error, because when the row is not found, I don't want to leak sql.ErrNoRows outside, is that ok? I don't like that the constructor for NotFound error is public, unlike all other custom errors, furthermore, I think I would need to add that switch statement to every Load* method … also not so good.
  • The custom error code is very repetitive, is there a way to clean that up?
  • Any other comments are much appreciated.
\$\endgroup\$
0
\$\begingroup\$

Why don't you want to leak sql.ErrNoRows outside? I find it quite explicit.

It seems that you are wrapping your errors with some context: there is a very nice package for this: https://godoc.org/github.com/pkg/errors

Using it, your code would look like this:

package db_access

import (
    "database/sql"
    "fmt"
    "my_database"
)

type TheType struct {
    Name string 
}

// uses named returns
func LoadTheTypeFromDb(db *sql.DB, id uint) (row TheType, err error) {
    query := fmt.Sprintf(
        "SELECT %s FROM %s WHERE %s = ?",
        my_database.NAME,
        my_database.TABLE,
        my_database.ID,
    )

    err = db.QueryRow(query, id).Scan(&row.Name)
    return
}

And your "my_database"

package my_database

import (
    "database/sql"
    _ "github.com/go-sql-driver/mysql"
    "github.com/pkg/errors"
)

func GetDatabaseInstance(connectionString string) (db *sql.DB, err error) {
    db, err = sql.Open("mysql", connectionString)

    if err != nil {
        err = errors.Wrap("open failed", err)
        return
    }

    err = db.Ping()
    // if err is nil, errors.Wrap returns nil
    err = errors.Wrap("ping failed", err)

    return
}

Your custom error file should not be needed anymore!

\$\endgroup\$
  • \$\begingroup\$ Thanks for your feedback, I tend to agree, trying hard to wrap the error seems a bit overzealous. I will think about that. \$\endgroup\$ – Max Jul 18 '17 at 1:23
  • \$\begingroup\$ And since you already have a dependency on the sql package for the input, it shouldn't be a problem to output errors of the same package \$\endgroup\$ – oliverpool Jul 18 '17 at 5:55

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.