32

我正在寻找使用反射调用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>]
4

4 回答 4

56

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

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

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)
        }
    }
}
于 2013-07-26T15:43:17.347 回答
9

清醒地说:您也可以分配一个接口而不是制作一个切片

以下代码效果很好:

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)
    }
}
于 2015-06-19T17:33:01.203 回答
3

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

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

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

于 2013-07-24T22:16:50.223 回答
2

以下解决方案允许您通过字段名称而不是索引来引用字段。它更像是 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;

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)
}

样本输出:

# 运行 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"
 }
]
于 2016-05-07T04:06:04.987 回答