jackc / pgx

PostgreSQL driver and toolkit for Go
MIT License
10.83k stars 845 forks source link

feat: add dedicated method to extract error from errRow #2154

Open aerialls opened 1 month ago

aerialls commented 1 month ago

Add a dedicated method to extract error from errRow. This is already present for errRows. This allows the use case to extract the error to retry if necessary.

type errRow interface {
    pgx.Row
    Err() error
}

func (r *RetriableDBTX) QueryRow(ctx context.Context, sql string, args ...any) pgx.Row {
    row := r.dbtx.QueryRow(ctx, sql, args...)
    if er, ok := row.(errRow); ok {
        // Custom logic to retry
    }

    return row
}
jackc commented 1 month ago

I'm not sure I understand the use case. In your example I would expect you to call Scan and handle the returned error.

The reason errRows has an Err() method is to satisfy the Rows interface. And the reason errRow doesn't is because the Row interface doesn't.

aerialls commented 4 weeks ago

I'm using sqlc and trying to add a global retry mechanism for a read-only connection. sqlc is using the following interface as an entrypoint.

type DBTX interface {
    Exec(context.Context, string, ...interface{}) (pgconn.CommandTag, error)
    Query(context.Context, string, ...interface{}) (pgx.Rows, error)
    QueryRow(context.Context, string, ...interface{}) pgx.Row
}

My initial idea was to create a RetriableDBTX with the same interface as an entry-point for sqlc and to retry the method in case of an error. It's working for Exec and Query but I don't have any error for QueryRow and so my idea to be able to retrieve the error if we have an Err() error method. The Scan method is executed afterward by sqlc for each SQL query so it's much harder to perform the retry logic here.

jackc commented 4 weeks ago

This change won't do what you want. And what you are doing with Query() isn't reliable either.

Query() will only return an error in limited circumstances such as a network failure sending the request. The returned Rows has to be closed and Err() checked to know if it successfully ran.

QueryRow() is a very simple wrapper around Query(). The result isn't actually read until Scan() is called.