2013-07-24 19 views
21

Szukam funkcji Rows.Scan() za pomocą refleksji. Jednak zajmuje on zmienną liczbę wskaźników, ale jestem nowy w Golang i nie ma wielu przykładów źródłowych. Potrzebuję użyć refleksji, ponieważ planuję wypełnienie wycinka wartościami z wywołania zapytania. Więc w zasadzie używając rows.Columns(), aby uzyskać długość rzędu, a następnie make(), plasterek []interface{}, aby wypełnić punkty danych, które normalnie byłyby wypełnione przy użyciu wskaźników przekazanych do funkcji Scan().Jak wywołać funkcję variadic skanowania w Golang za pomocą odbicia?

Zasadniczo coś jak ten kod:

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

ktoś ma przykład wywołanie zmiennej liczbie argumentów funkcji, które ma wskazówek pomocą refleksji, że mogę spojrzeć na?

Edytuj: Przykładowy kod, który wydaje się nie robić tego, co chcę.

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

} 

Wyniki:

[<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>] 

Odpowiedz

38

Oto rozwiązanie, do którego dotarłem. Nie przechwytuje typów przed przechodzeniem przez dane, dlatego nie zna z góry rodzaju każdej wartości przed wyciągnięciem wartości przez Scan(), ale chodzi o to, aby nie znać typów przed ręką.

Sztuką było utworzenie 2 plasterków, jednej dla wartości i jednej zawierającej wskaźniki równolegle do wycinka wartości. Następnie, gdy wskaźniki są używane do wypełnienia danych, tablica wartości jest faktycznie wypełniona danymi, które następnie można wykorzystać do wypełnienia innych struktur danych.

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 { 

      var v interface{} 

      val := values[i] 

      b, ok := val.([]byte) 

      if (ok) { 
       v = string(b) 
      } else { 
       v = val 
      } 

      fmt.Println(col, v) 
     } 
    } 
} 
+0

Tworzenie terminala wiersza poleceń do wysyłania zapytań do baz danych w chmurze. To działało dobrze jako punkt wyjścia ... Dzięki! – openwonk

+0

Ktoś próbował tego w wersji 1.4.2 lub wyższej ?. Otrzymuję wszystkie pola w rodzaju ciągu znaków – Anuruddha

+0

Właśnie to, czego potrzebowałem, uwielbiam tę społeczność – Mauricio

4

Nie sądzę, trzeba refleksji na to - można użyć kawałek i operatora ... przekazać wiele wartości do zmiennej liczbie argumentów funkcji.

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

Być może nie rozumiem, co chcesz robić!

+0

Cóż, wygląda na to, że zadziała. Zaktualizowałem moje pytanie za pomocą przykładowego kodu. Użycie operatora '...' z tym kodem powoduje powstanie 'nieprawidłowego adresu pamięci lub zerowego wskaźnika dereferencji'. Czuję, że muszę zainicjować tablicę danych za pomocą wskaźników lub czegoś ... – lucidquiet

+0

Zarys tego, że problem z adresem pamięci był z moim sql, a nie z wykorzystaniem operatora. Mogę skompilować i uruchomić kod z poprawką teraz, ale nie otrzymuję danych, każde wejście do danych (lub w przykładowych vals) jest zerowe. Ostatecznie to nie działało tak, jak bym chciał. – lucidquiet

+3

-1: 'Rows.Columns()' zwraca '([] łańcuch, błąd)'; ponadto, 'Scan()' nie działa, jeśli nie przekażesz wskaźników (i będzie narzekał, że '[] interface {}' nie jest tablicą wskaźników, chyba że zainicjujesz jego wartości jako wskaźniki do interfejsów a la zaakceptowana odpowiedź). – weberc2

1

Aby lucidquiet: można również przypisać interfejs zamiast dokonywania kawałek

Poniższy kod działa dobrze:

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

Następujące rozwiązanie pozwala odnieść się do pola przez pole nazwa zamiast indeksu. Jest to bardziej jak styl PHP: definicja

Tabela:

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: wyjście

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

Próbka:

# go uruchomić główny.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" 
} 
] 
+0

Nie jestem pewien, czy to odpowiada na pytanie OP, ale muszę powiedzieć, że zaoszczędzisz mi godzin czasu na szukaniu Internet o tym, jak pobrać dane wiersza z bazy danych SQL i JSONify go ... to jest tak łatwe w Go, ale musisz wiedzieć jak! Dziękuję bardzo! :) –

Powiązane problemy