How could I create a new struct from an unpredictable db.Query()?

Issue

I’m using SELECT * in a db.query() to return columns from a table. Typically, I would fmt.Scan() the rows into a pre-declared struct{} for further manipulation, but in this case, the table columns change frequently so I’m not able to use a declared struct{} as part of my Scan().

I’ve been struggling to figure out how I might dynamically build a struct{} based on the column results of the db.query() which I could subsequently call on the use of for Scan(). I’ve read a little about reflect but I’m struggling to determine if this is right for my use-case or if I might have to think about something else.

Any pointers would be greatly appreciated.

Solution

you can get column names from resulting rowset and prepare a slice for the scan.

Example (https://go.dev/play/p/ilYmEIWBG5S) :

package main

import (
    "database/sql"
    "fmt"
    "log"

    "github.com/DATA-DOG/go-sqlmock"
)

func main() {
    // mock db
    db, mock, err := sqlmock.New()
    if err != nil {
        log.Fatal(err)
    }

    columns := []string{"id", "status"}

    mock.ExpectQuery("SELECT \\* FROM table").
        WillReturnRows(sqlmock.NewRows(columns).AddRow(1, "ok"))

    // actual code
    rows, err := db.Query("SELECT * FROM table")
    if err != nil {
        log.Fatal(err)
    }

    cols, err := rows.Columns()
    if err != nil {
        log.Fatal(err)
    }

    data := make([]interface{}, len(cols))
    strs := make([]sql.NullString, len(cols))
    for i := range data {
        data[i] = &strs[i]
    }

    for rows.Next() {
        if err := rows.Scan(data...); err != nil {
            log.Fatal(err)
        }
        for i, d := range data {
            fmt.Printf("%s = %+v\n", cols[i], d)
        }
    }
}

This example reads all columns into strings. To detect column type one can use rows.ColumnTypes method.

Answered By – serge-v

Answer Checked By – Terry (GoLangFix Volunteer)

Leave a Reply

Your email address will not be published.