【问题标题】:How to call the Scan variadic function using reflection如何使用反射调用扫描可变参数函数
【发布时间】:2013-07-24 14:44:25
【问题描述】:

我希望使用反射调用Rows.Scan() 函数。但是它需要可变数量的指针,但没有很多源示例。我需要使用反射,因为我计划用 Query 调用中的值填充切片。所以基本上使用rows.Columns() 来获取行的长度,然后使用make() 的一部分[]interface{} 来填充通常使用传递给Scan() 函数的指针填充的数据点。

基本上是这样的代码:

col := rows.Columns()
vals := make([]interface{}, len(cols))
rows.Scan(&vals)

任何人都有调用可变参数函数的示例,该函数使用反射获取指针,我可以看看?

编辑: 似乎没有做我所追求的示例代码。

package main

import (
    _ "github.com/lib/pq"
    "database/sql"
    "fmt"
)


func main() {

    db, _ := sql.Open(
        "postgres",
        "user=postgres dbname=Go_Testing password=ssap sslmode=disable")

    rows, _ := db.Query("SELECT * FROM _users;")

    cols, _ := rows.Columns()

    for rows.Next() {

        data := make([]interface{}, len(cols))

        rows.Scan(data...)

        fmt.Println(data)
    }

}

结果:

[<nil> <nil> <nil> <nil> <nil>]
[<nil> <nil> <nil> <nil> <nil>]
[<nil> <nil> <nil> <nil> <nil>]
[<nil> <nil> <nil> <nil> <nil>]
[<nil> <nil> <nil> <nil> <nil>]
[<nil> <nil> <nil> <nil> <nil>]

【问题讨论】:

    标签: database reflection go


    【解决方案1】:

    这是我得出的解决方案。它在遍历数据之前没有得到类型,因此在通过Scan() 提取值之前事先不知道每个值的类型,但关键是不必事先知道类型。

    诀窍是创建 2 个切片,一个用于值切片,另一个用于保存与值切片并行的指针。然后,一旦使用指针填充数据,values 数组实际上会填充数据,然后可以使用这些数据填充其他数据结构。

    package main
    
    import (
        "fmt"
        _ "github.com/lib/pq"
        "database/sql"
    )
    
    func main() {
        db, _ := sql.Open(
            "postgres",
            "user=postgres dbname=go_testing password=pass sslmode=disable")
    
        rows, _ := db.Query("SELECT * FROM _user;")
    
        columns, _ := rows.Columns()
        count := len(columns)
        values := make([]interface{}, count)
        valuePtrs := make([]interface{}, count)
    
        for rows.Next() {
            for i := range columns {
                valuePtrs[i] = &values[i]
            }
    
            rows.Scan(valuePtrs...)
    
            for i, col := range columns {
                val := values[i]
    
                b, ok := val.([]byte)
                var v interface{}
                if (ok) {
                    v = string(b)
                } else {
                    v = val
                }
    
                fmt.Println(col, v)
            }
        }
    }
    

    【讨论】:

    • 创建命令行终端查询云端数据库。这很好地作为一个起点......谢谢!
    • 有人在 go 1.4.2 或更高版本中尝试过这个吗?我正在获取字符串类型的所有字段
    • 这是一个非常好的主意。我无法想出这个
    【解决方案2】:

    为了清醒:你也可以分配一个接口而不是做一个切片

    以下代码运行良好:

    var sql = "select * from table"
    rows, err := db.Query(sql)
    columns, err = rows.Columns()
    colNum := len(columns)
    
    var values = make([]interface{}, colNum)
    for i, _ := range values {
        var ii interface{}
        values[i] = &ii
    }
    
    for rows.Next() {
        err := rows.Scan(values...)
        for i, colName := range columns {
            var raw_value = *(values[i].(*interface{}))
            var raw_type = reflect.TypeOf(raw_value)
    
            fmt.Println(colName,raw_type,raw_value)
        }
    }
    

    【讨论】:

      【解决方案3】:

      我认为您不需要对此进行反射 - 您可以使用切片和 ... 运算符将多个值传递给可变参数函数。

      col := rows.Columns()
      vals := make([]interface{}, col)
      rows.Scan(vals...)
      

      我可能误解了你想要做什么!

      【讨论】:

      • 嗯,这似乎可行。我已经用示例代码更新了我的问题。将此代码与... 运算符一起使用会生成invalid memory address or nil pointer dereference。我觉得我需要用指针或其他东西来初始化数据数组......
      • 从头开始,内存地址问题是我的 sql 而不是运算符的使用。我现在可以使用修复程序编译和运行代码,但是我没有得到任何数据,数据中的每个条目(或在您的示例 vals 中)都是零。所以,最终这并没有如我所愿。
      • -1: Rows.Columns() 返回([]string, error);此外,如果您不传入指针,Scan() 将失败(并且它会抱怨 []interface{} 不是指针数组,除非您将其值初始化为指向接口的指针,这是公认的答案)。
      【解决方案4】:

      以下解决方案允许您通过字段名称而不是索引来引用字段。更像是 PHP 风格:

      表定义:

      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;
      

      ma​​in.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)
      }
      

      示例输出:

      # 运行 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"
       }
      ]
      

      【讨论】:

      • 我不确定这是否确实回答了 OP 问题,但我不得不说,您节省了我在网上搜索如何从 SQL 数据库和 JSONify 中检索行数据的时间它......在 Go 中很容易,但你必须知道如何!所以,非常感谢! :)
      猜你喜欢
      • 2011-02-05
      • 1970-01-01
      • 1970-01-01
      • 2018-12-10
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-01-13
      相关资源
      最近更新 更多