I'm learning Golang and want to understand the "Go way" of solving this problem.
Specifically, I'm using the sql
package and I'm seeing some redundant functionality in my code that I'd like to pull out into a function.
My Original Code
I have, 1) a user struct:
type User struct {
ID int
FirstName string
LastName string
}
2) a function for getting one user by ID from the database (Postgresql):
func GetUserById(id int) (user User) {
sql := `
SELECT id, first_name, last_name
FROM users
WHERE id = $1
`
row := db.QueryRow(sql, id)
err := row.Scan(&user.ID, &user.FirstName, &user.LastName)
if err != nil {
panic(err)
}
return
}
and, 3) a function for getting all users from the database:
func GetUsers() (users []User) {
sql := `
SELECT id, first_name, last_name
FROM users
ORDER BY last_name
`
rows, err := db.Query(sql)
if err != nil {
panic(err)
}
for rows.Next() {
user := User{}
err := rows.Scan(&user.ID, &user.FirstName, &user.LastName)
if err != nil {
panic(err)
}
users = append(users, user)
}
rows.Close()
return
}
Desired Refactor
With only 3 fields in the user record, this is a trivial example. But, with a few more fields, the rows.Scan(...)
piece of both data access functions would be nice to move to a function that both could call:
func ScanUserFromRow(row *sql.Row) (user User) {
err := row.Scan(&user.ID, &user.FirstName, &user.LastName)
if err != nil {
panic(err)
}
return
}
Then the updated database access functions would look something like:
func GetUserById(id int) (user User) {
sql := `
SELECT id, first_name, last_name
FROM users
WHERE id = $1
`
row := db.QueryRow(sql, id)
user = ScanUserFromRow(row)
return
}
func GetUsers() (users []User) {
sql := `
SELECT id, first_name, last_name
FROM users
ORDER BY last_name
`
rows, err := db.Query(sql)
if err != nil {
panic(err)
}
for rows.Next() {
user := ScanUserFromRow(rows)
users = append(users, user)
}
rows.Close()
return
}
However, in the case of the GetUserById
function, I'm dealing with an *sql.Row
struct pointer. In the case of the GetUsers
function, I'm dealing with an *sql.Rows
struct pointer. The two are different... obviously, but similar in that they both have a Scan
method.
It seems the type system won't let me create a method that will accept one or the other. Is there a way to do this utilizing interface{}
, or is there some other more idiomatic Go solution for this?
Update/Solution
With this question, I'm saying both sql.Row
and sql.Rows
are ducks that "quack" with Scan
. How can I use a function argument that allows both?
@seh provided an answer below that allows the sort of duck-typing I was hoping for by making the argument a custom interface. Here is the resulting code:
type rowScanner interface {
Scan(dest ...interface{}) error
}
func ScanPlayerFromRow(rs rowScanner) (u User) {
err := rs.Scan(&u.ID, &u.FirstName, &u.LastName)
if err != nil {
panic(err)
}
return
}
...or, as @Kaveh pointed out below, the definition of the interface can be inlined in the function argument:
func ScanPlayerFromRow(rs interface {
Scan(des ...interface{}) error
}) (u User) {
err := rs.Scan(&u.ID, &u.FirstName, &u.LastName)
if err != nil {
panic(err)
}
return
}