Я передаю структуру в функцию как interface{}. Затем внутри я работаю с ним, используя reflect для получения атрибутов структуры. Вот код:

func (db *DB) Migrate(domain ...interface{}) {
    // statement := "CREATE TABLE IF NOT EXISTS %s (%s, %s, %s, %s, %s)"
    for _,i := range domain {
        params := BindStruct(&i)
        statement := CreateStatement("create", len(params))
        _,err := db.Exec(fmt.Sprintf(statement, params...))
        if err != nil {
            log.Fatal("Error migrating database schema - ", err)
            break
        }
    }
} 

func BindStruct(domain interface{}) (params []interface{}) {
    tableName := reflect.TypeOf(domain).Elem().Name()
    params = append(params, tableName)

    val := reflect.ValueOf(domain).Elem()
    for i:=0; i < val.NumField(); i++ {
        field := val.Type().Field(i)
        tag := field.Tag

        fieldName := field.Name
        fieldType := tag.Get("sql_type")
        fieldTags := tag.Get("sql_tag")

        paramstring := fieldName + " " + fieldType + " " + fieldTags
        params = append(params, paramstring)
    }
    return params
}

У меня ошибка в строке for i:=0; i < val.NumField(); i++ { функции BindStruct. Сообщение об ошибке:

паника: отразить: вызов функции Reflect.Value.NumField в интерфейсе Value

Если я удалю & из params := BindStruct(&i) превращения params := BindStruct(i) в функцию миграции, я получаю следующую ошибку:

паника: ошибка времени выполнения: неверный адрес памяти или нулевой указатель

разыменование [сигнал SIGSEGV: код нарушения сегментации = 0x1 адрес = 0x0 pc = 0x4ff457]

Что дает?

6
blue panther 13 Мар 2018 в 04:02

2 ответа

Лучший ответ

Когда вы вызываете BindStruct(&i), вы передаете указатель на интерфейс. так что эта строка:

val := reflect.ValueOf(domain).Elem()

Установит val на отражение. Значение, представляющее ваш интерфейс, потому что reflect.ValueOf(domain) получает указатель, затем .Elem() разрешает интерфейс - что приводит к вашей первой ошибке, поскольку это действительно значение интерфейса (а у них нет поля):

паника: отразить: вызов функции Reflect.Value.NumField в интерфейсе Value

Таким образом, вызов params := BindStruct(i) всегда будет правильным, поскольку вам нужно передать фактический интерфейс, а не указатель на него.

Вы не разъясняете, какие типы данных передаются в Migrate() - это значения или указатели? Это важно знать, например, чтобы проверить структурные теги с помощью отражения, нам нужно вернуться к типу структурных значений, поэтому цепочка выглядит следующим образом:

интерфейс -> (указатель?) -> значение -> тип

Я подозреваю, что вы используете значения, как если бы interface был значением, а затем строкой:

val := reflect.ValueOf(domain).Elem()

Можно ожидать паники, поскольку reflect.ValueOf(domain) разрешит значение, а затем .Elem() попытается отменить ссылку на значение.

На всякий случай проверим Kind () входящего значения, чтобы убедиться, что у нас есть структура:

https://play.golang.org/p/6lPOwTd1Q0O

func BindStruct(domain interface{}) (params []interface{}) {

    val := reflect.ValueOf(domain) // could be any underlying type

    // if its a pointer, resolve its value
    if val.Kind() == reflect.Ptr {
        val = reflect.Indirect(val)
    }

    // should double check we now have a struct (could still be anything)
    if val.Kind() != reflect.Struct {
         log.Fatal("unexpected type")
    }

    // now we grab our values as before (note: I assume table name should come from the struct type)
    structType := val.Type()  
    tableName := structType.Name()
    params = append(params, tableName)

    for i:=0; i < structType.NumField(); i++ {
        field := structType.Field(i)
        tag := field.Tag

        fieldName := field.Name
        fieldType := tag.Get("sql_type")
        fieldTags := tag.Get("sql_tag")

        paramstring := fieldName + " " + fieldType + " " + fieldTags
        params = append(params, paramstring)
    }
    return params
}
6
SwiftD 13 Мар 2018 в 03:25

Удалите & и Elem (), как показано ниже:

func (db *DB) Migrate(domain ...interface{}) {
    // statement := "CREATE TABLE IF NOT EXISTS %s (%s, %s, %s, %s, %s)"
    for _,i := range domain {
        params := BindStruct(i)
        statement := CreateStatement("create", len(params))
        _,err := db.Exec(fmt.Sprintf(statement, params...))
        if err != nil {
            log.Fatal("Error migrating database schema - ", err)
            break
        }
    }
} 

func BindStruct(domain interface{}) (params []interface{}) {
    tableName := reflect.TypeOf(domain).Name()
    params = append(params, tableName)

    val := reflect.ValueOf(domain)
    for i:=0; i < val.NumField(); i++ {
        field := val.Type().Field(i)
        tag := field.Tag

        fieldName := field.Name
        fieldType := tag.Get("sql_type")
        fieldTags := tag.Get("sql_tag")

        paramstring := fieldName + " " + fieldType + " " + fieldTags
        params = append(params, paramstring)
    }
    return params
}
2
No_20 13 Мар 2018 в 02:27