Подтвердить что ты не робот

Как вызвать функцию сканирования переменной с помощью отражения

Я пытаюсь вызвать функцию 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>]
4b9b3361

Ответ 1

Вот решение, к которому я пришел. Он не получает типы перед прохождением данных и поэтому не знает перед рукой тип каждого значения перед извлечением значений через 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)
        }
    }
}

Ответ 2

Для lucidquiet: вы также можете назначить интерфейс вместо создания фрагмента

Следующий код работает хорошо:

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

Возможно, я не понимаю, что вы хотите делать!

Ответ 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;

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/info/17376207/how-to-share-mysql-connection-between-http-goroutines
        db, err = sql.Open("mysql", "MyUser:[email protected](localhost:3306)/MyDB")
        //db, err = sql.Open("mysql", "MyUser:[email protected](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)
}

Пример вывода:

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