How to call the Scan variadic function using reflection

后端 未结 4 1283
暖寄归人
暖寄归人 2020-12-23 01:42

I\'m looking to call the Rows.Scan() function using reflection. However it takes a variable number of pointers, but there are not a lot of source examples. I need to use r

4条回答
  •  北海茫月
    2020-12-23 02:39

    The following solution allows you to refer to the field by field name instead of index. It's more like PHP style:

    Table definition:

    CREATE TABLE `salesOrder` (
      `idOrder` int(10) unsigned NOT NULL AUTO_INCREMENT,
      `uid` int(10) unsigned NOT NULL,
      `changed` datetime NOT NULL,
      PRIMARY KEY (`idOrder`)
    ) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8;
    

    main.go:

    package main
    
    import (
            "database/sql"
            "encoding/json"
            "fmt"
            _ "github.com/go-sql-driver/mysql"
            "log"
            "reflect"
            "strings"
    )
    
    var (
            db *sql.DB
    )
    
    func initDB() {
            var err error
    
            // The database/sql package manages the connection pooling automatically for you.
            // sql.Open(..) returns a handle which represents a connection pool, not a single connection.
            // The database/sql package automatically opens a new connection if all connections in the pool are busy.
            // Reference: http://stackoverflow.com/questions/17376207/how-to-share-mysql-connection-between-http-goroutines
            db, err = sql.Open("mysql", "MyUser:MyPassword@tcp(localhost:3306)/MyDB")
            //db, err = sql.Open("mysql", "MyUser:MyPassword@tcp(localhost:3306)/MyDB?tx_isolation='READ-COMMITTED'") // optional
    
            if err != nil {
                    log.Fatalf("Error on initializing database connection: %v", err.Error())
            }
    
            // Open doesn't open a connection. Validate DSN data:
            err = db.Ping()
    
            if err != nil {
                    log.Fatalf("Error on opening database connection: %v", err.Error())
            }
    }
    
    func StrutToSliceOfFieldAddress(s interface{}) []interface{} {
            fieldArr := reflect.ValueOf(s).Elem()
    
            fieldAddrArr := make([]interface{}, fieldArr.NumField())
    
            for i := 0; i < fieldArr.NumField(); i++ {
                    f := fieldArr.Field(i)
                    fieldAddrArr[i] = f.Addr().Interface()
            }
    
            return fieldAddrArr
    }
    
    func testSelectMultipleRowsV3(optArr map[string]interface{}) {
            // queries
            query := []string{}
            param := []interface{}{}
    
            if val, ok := optArr["idOrder"]; ok {
                    query = append(query, "salesOrder.idOrder >= ?")
                    param = append(param, val)
            }
    
            // The first character of the field name must be in upper case. Otherwise, you would get:
            // panic: reflect.Value.Interface: cannot return value obtained from unexported field or method
            var sqlField = struct {
                    IdOrder int
                    Uid     int
                    Changed string
            }{}
    
            var rowArr []interface{}
    
            sqlFieldArrPtr := StrutToSliceOfFieldAddress(&sqlField)
    
            sql := "SELECT "
            sql += "  salesOrder.idOrder "
            sql += ", salesOrder.uid "
            sql += ", salesOrder.changed "
            sql += "FROM salesOrder "
            sql += "WHERE " + strings.Join(query, " AND ") + " "
            sql += "ORDER BY salesOrder.idOrder "
    
            stmt, err := db.Prepare(sql)
            if err != nil {
                    log.Printf("Error: %v", err)
            }
            defer stmt.Close()
    
            rows, err := stmt.Query(param...)
    
            if err != nil {
                    log.Printf("Error: %v", err)
            }
    
            defer rows.Close()
    
            if err != nil {
                    log.Printf("Error: %v", err)
            }
    
            //sqlFields, err := rows.Columns()
    
            for rows.Next() {
                    err := rows.Scan(sqlFieldArrPtr...)
    
                    if err != nil {
                            log.Printf("Error: %v", err)
                    }
    
                    // Show the type of each struct field
                    f1 := reflect.TypeOf(sqlField.IdOrder)
                    f2 := reflect.TypeOf(sqlField.Uid)
                    f3 := reflect.TypeOf(sqlField.Changed)
                    fmt.Printf("Type: %v\t%v\t%v\n", f1, f2, f3)
    
                    // Show the value of each field
                    fmt.Printf("Row: %v\t%v\t%v\n\n", sqlField.IdOrder, sqlField.Uid, sqlField.Changed)
    
                    rowArr = append(rowArr, sqlField)
            }
    
            if err := rows.Err(); err != nil {
                    log.Printf("Error: %v", err)
            }
    
            // produces neatly indented output
            if data, err := json.MarshalIndent(rowArr, "", " "); err != nil {
                    log.Fatalf("JSON marshaling failed: %s", err)
            } else {
                    fmt.Printf("json.MarshalIndent:\n%s\n\n", data)
            }
    }
    
    func main() {
            initDB()
            defer db.Close()
    
            // this example shows how to dynamically assign a list of field name to the rows.Scan() function.
            optArr := map[string]interface{}{}
            optArr["idOrder"] = 1
            testSelectMultipleRowsV3(optArr)
    }
    

    Sample output:

    # go run main.go

    Type: int       int     string
    Row: 1  1       2016-05-06 20:41:06
    
    Type: int       int     string
    Row: 2  2       2016-05-06 20:41:35
    
    json.MarshalIndent:
    [
     {
      "IdOrder": 1,
      "Uid": 1,
      "Changed": "2016-05-06 20:41:06"
     },
     {
      "IdOrder": 2,
      "Uid": 2,
      "Changed": "2016-05-06 20:41:35"
     }
    ]
    

提交回复
热议问题