Instance generation with unexported fields. database/sql.Row

70 views Asked by At

I have a challenging SWE issue regarding the sql.Row of database/sql package.

I have the following interface:

type SqlQuerier interface {
    QueryRowContext(ctx context.Context, query string, args ...interface{}) *sql.Row
}

The SqlQuerier is an interface that has the QueryRowContext function signature. That function is implemented by various sql db clients, like Tx, SQLite3.

I have my own type:

func (db *XDB) QueryRowContext(ctx context.Context, query string, args ...interface{}) *sql.Row {
     // operation
     // check if the operation is successful, if not return error
     // otherwise return sql.Row result
     return db.innerDB.QueryRowContext(ctx, query, args...)
}

XDB is an infrastructure wrapper to sqlx.DB (db.innerDB) which in case the first operation was successful calls its own implementation of QueryRowContext.

My issue is that I can't implant Error inside a sql.Row result because:

  • There isn't a factory function like NewRow or setters/getters to its unexported fields.
  • Returning nil isn't a quite good habit and don't have a quite indicator about the error reason.
  • Returning an empty sql.Row{} struct is problematic since it'll panic when trying to Scan its values afterwards .Scan(...).
  • Can't set values to unexported fields via reflection.
  • Another limitation is that I can't change the function signature to return (*sql.Row, error), because of its various dependencies on the other clients.

I'm open to any suggestions.

1

There are 1 answers

0
dave On

Caveat - this is ugly, and not ideal, and I would personally figure out a different way, but you could implement a convention where if the last argument you pass is a *error, you set the error it points to be the error you care about

    func (db *XDB) QueryRowContext(ctx context.Context, query string, args ...interface{}) *sql.Row {
        last := args[len(args)-1]
        argsErr, hasArgsErr := last.(*error)
        if hasArgsErr {
            args = args[:len(args)-1]
        }
        // operation
        // check if the operation is successful, if not return error
        // otherwise return sql.Row result
        if (err != nil && hasArgsErr) {
            (*argsErr) = err
        }
        return db.innerDB.QueryRowContext(ctx, query, args...)
    }

and then you can do

var queryRowErr error
row := xdb.QueryRowContext(ctx, query, arg1, arg2, &queryRowErr)
if queryRowErr != nil {
    // do something with the error
}