jmoiron / sqlx

general purpose extensions to golang's database/sql
http://jmoiron.github.io/sqlx/
MIT License
16.3k stars 1.09k forks source link

The db.MapperFunc is not called properly #937

Open luxiangqing opened 3 months ago

luxiangqing commented 3 months ago

Because different types of databases default return column name case inconsistency, oracle default uppercase, mysql default lowercase, I want to adapt to different databases struct field mapping, try using db.MapperFunc function in oracle can not work properly, StructScan return missing destination name error

var DB *sqlx.DB

func InitDB() {
    //db, err := sqlx.Connect("mysql", "xxxx")
    db, err := sqlx.Connect("godror", "xxxxx")
    if err != nil {
        panic(err)
    }
    DB = db
    DB.MapperFunc(DBMapper)
}

func DBMapper(s string) string {
    switch DB.DriverName() {
    case "mysql":
        return strings.ToLower(s)
    case "godror":
        return strings.ToUpper(s)
    case "sqlserver":
        return strings.ToLower(s)
    default:
        return strings.ToLower(s)
    }
}

type User struct {
    Id       int    `db:"id"`
    Username string `db:"username"`
    Nickname string `db:"nickname"`
    Password string `db:"password"`
    Phone    string `db:"phone"`
    Email    string `db:"email"`
}

func (u *User) ListByName(name string, db *sqlx.DB) (User, error) {
    query := "select * from tuser where username = ?"
    query = db.Rebind(query)
    var user User
    if err := db.QueryRowx(query, name).StructScan(&user); err != nil {
        return user, err
    }
    return user, nil
}

I checked the source code and found that when scanning the data into the structure filed, the parseName function call mapFunc not correctly, souce code as follows:

func parseName(field reflect.StructField, tagName string, mapFunc, tagMapFunc mapf) (tag, fieldName string) {
    // first, set the fieldName to the field's name
    fieldName = field.Name
    // if a mapFunc is set, use that to override the fieldName
    if mapFunc != nil {
        fieldName = mapFunc(fieldName)
    }

    // if there's no tag to look for, return the field name
    if tagName == "" {
        return "", fieldName
    }

    // if this tag is not set using the normal convention in the tag,
    // then return the fieldname..  this check is done because according
    // to the reflect documentation:
    //    If the tag does not have the conventional format,
    //    the value returned by Get is unspecified.
    // which doesn't sound great.
    if !strings.Contains(string(field.Tag), tagName+":") {
        return "", fieldName
    }

    // at this point we're fairly sure that we have a tag, so lets pull it out
    tag = field.Tag.Get(tagName)

    // if we have a mapper function, call it on the whole tag
    // XXX: this is a change from the old version, which pulled out the name
    // before the tagMapFunc could be run, but I think this is the right way
    if tagMapFunc != nil {
        tag = tagMapFunc(tag)
    }

    // finally, split the options from the name
    parts := strings.Split(tag, ",")
@   fieldName = parts[0]           //here mapFunc  is not called

    return tag, fieldName
}