about sqlx
sqlx is a Golang database operation library, which provides a set of extended API based database/sql
on , which is a superset of the native library API.
By default, sqlx will obtain the corresponding database fields when performing structure parsing and scanning, and the priorities are as follows:
- If there is a db tag in the tag, get the corresponding field name of the database according to the tag, see sqlx.go#43 for the source code .
- If there is no db tag in the tag, all lowercase field names will be used as the corresponding field names of the database. See sqlx.go#27 for the source code .
According to the TiDB SQL Development Specification and Basic Principles - Object Naming Specification "It is recommended to use meaningful English words for naming, separated by underscores in the middle of the words", we need to change the second case "all lowercase" to "underscore naming method" .
It takes two steps to modify the sqlx database field generation function.
- Write the underscore nomenclature NameMapper function.
- Modify the default NameMapper function.
Write the underscore naming method NameMapper function
According to the source code, we know that by default, sqlx uses the method of strings.ToLower
the native library strings for field processing. This function only has a parameter named name and type string.
Next, we can refer to the source code gorm
of (by default, gorm uses the underscore naming method to generate database fields) to extract toDBName
the function, see gorm - schema/naming.go:117 for details .
The following is the underscore nomenclature field generation function proposed by the author toDBName
:
import "strings"
var commonInitialismsReplacer = newCommonInitialismsReplacer()
func newCommonInitialismsReplacer() *strings.Replacer {
// https://github.com/golang/lint/blob/master/lint.go#L770
commonInitialisms := []string{
"API", "ASCII", "CPU", "CSS", "DNS", "EOF", "GUID", "HTML", "HTTP", "HTTPS", "ID", "IP", "JSON", "LHS", "QPS", "RAM", "RHS", "RPC", "SLA", "SMTP", "SSH", "TLS", "TTL", "UID", "UI", "UUID", "URI", "URL", "UTF8", "VM", "XML", "XSRF", "XSS"}
commonInitialismsForReplacer := make([]string, 0, len(commonInitialisms))
for _, initialism := range commonInitialisms {
commonInitialismsForReplacer = append(commonInitialismsForReplacer, initialism, strings.Title(strings.ToLower(initialism)))
}
replacer := strings.NewReplacer(commonInitialismsForReplacer...)
return replacer
}
func toDBName(name string) string {
if name == "" {
return ""
}
var (
value = commonInitialismsReplacer.Replace(name)
buf strings.Builder
lastCase, nextCase, nextNumber bool // upper case == true
curCase = value[0] <= 'Z' && value[0] >= 'A'
)
for i, v := range value[:len(value)-1] {
nextCase = value[i+1] <= 'Z' && value[i+1] >= 'A'
nextNumber = value[i+1] >= '0' && value[i+1] <= '9'
if curCase {
if lastCase && (nextCase || nextNumber) {
buf.WriteRune(v + 32)
} else {
if i > 0 && value[i-1] != '_' && value[i+1] != '_' {
buf.WriteByte('_')
}
buf.WriteRune(v + 32)
}
} else {
buf.WriteRune(v)
}
lastCase = curCase
curCase = nextCase
}
if curCase {
if !lastCase && len(value) > 1 {
buf.WriteByte('_')
}
buf.WriteByte(value[len(value)-1] + 32)
} else {
buf.WriteByte(value[len(value)-1])
}
ret := buf.String()
return ret
}
Modify the default NameMapper function
According to the sqlx source code, we know NameMapper
that is a global variable of sqlx, so we only need to modify the variable during the program initialization process.
func init() {
sqlx.NameMapper = toDBName
}
Show results
The demo will show the difference between using all lowercase and replacing underscore nomenclature functions.
It is worth noting that the test environment uses golang v1.17, sqlx v1.3.4 and TiDB v1.6.1.
- First we need to create a data table testTable for demonstration.
CREATE TABLE `test_table` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`name` varchar(256) NOT NULL,
`sex` varchar(256) NOT NULL,
`counter` bigint(20) NOT NULL,
`create_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
`update_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
`miss_counter` bigint(20) NOT NULL DEFAULT '0',
PRIMARY KEY (`id`),
UNIQUE KEY `test_table_name_uindex` (`name`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin AUTO_INCREMENT=30001
- Next, define a structure called testTable in the Go program, where
MissCounter
the fields will be affected by NameMapper.
type testTable struct {
ID uint64
Name string // unique index
Sex string
Counter uint64 // 命中计数字段
MissCounter uint64 // 非命中计数字段
CreateAt time.Time
UpdateAt time.Time
}
- Next we need to write an inserter code to demonstrate the impact of different database field generation functions.
func main(){
tx, err := db.BeginTxx(ctx, &sql.TxOptions{
})
if err != nil {
log.Error(err)
return
}
const insertSQL = "INSERT INTO test_table (name, sex, counter, miss_counter) VALUES (:name, :sex, :counter, :miss_counter);"
stmt, errPrepare := tx.PrepareNamedContext(ctx, insertSQL)
if errPrepare != nil {
log.Errorf("failed to prepare, err: %v", errPrepare)
_ = tx.Rollback()
return
}
for _, row := range []*testTable{
{
Name: "小李", Sex: "男", Counter: uint64(rand.Int63()), MissCounter: uint64(rand.Int63())},
{
Name: "小白", Sex: "女", Counter: uint64(rand.Int63()), MissCounter: uint64(rand.Int63())},
{
Name: "小黑", Sex: "男", Counter: uint64(rand.Int63()), MissCounter: uint64(rand.Int63())},
{
Name: "小天", Sex: "女", Counter: uint64(rand.Int63()), MissCounter: uint64(rand.Int63())},
} {
res, errExec := stmt.ExecContext(ctx, row)
if errExec != nil {
// for duplicate err
if me, isMySQLErr := errExec.(*mysql.MySQLError); isMySQLErr && me.Number == 1062 /*duplicate is 1062*/ {
log.Infof("skip duplicate err, err: %v, row: %+v", me, row)
continue
}
log.Errorf("failed to stmt exec, err: %v", errExec)
_ = tx.Rollback()
return
}
insertID, _ := res.LastInsertId()
log.Infof("success to stmt exec, row: %+v, id: %d", row, insertID)
}
if errCommit := tx.Commit(); errCommit != nil {
log.Error(errCommit)
return
}
log.Infof("finish")
}
- In the case of using all lowercase NameMapper, the program execution will report an error
could not find name xxx
.
2021-10-31T16:29:15.029+0800 ERROR transaction/main.go:76 failed to stmt exec, err: could not find name miss_counter in &main.testTable{
ID:0x0, Name:"小李, Sex:"男", Counter:0x4354432fe0f37484, MissCounter:0x45afa115903b7669, CreateAt:time.Date(1, time.January, 1, 0, 0, 0, 0, time.UTC), UpdateAt:time.Date(1, tim.January, 1, 0, 0, 0, 0, time.UTC)}
- In the case of a NameMapper that uses the underscore notation, the program executes smoothly.
2021-10-31T16:30:27.841+0800 INFO transaction/main.go:81 success to stmt exec, row: &{
ID:0 Name:小李 Sex:男 Counter:6239523519032355576 MissCounter:180608923895024 CreateAt:0001-01-01 00:00:00 +0000 UTC UpdateAt:0001-01-01 00:00:00 +0000 UTC}, id: 23
2021-10-31T16:30:27.842+0800 INFO transaction/main.go:81 success to stmt exec, row: &{
ID:0 Name:小白 Sex:女 Counter:2433768853952726858 MissCounter:7832533413053117 CreateAt:0001-01-01 00:00:00 +0000 UTC UpdateAt:0001-01-01 00:00:00 +0000 UTC}, id: 24
2021-10-31T16:30:27.844+0800 INFO transaction/main.go:81 success to stmt exec, row: &{
ID:0 Name:小黑 Sex:男 Counter:8462818185278118914 MissCounter:1149211500697067 CreateAt:0001-01-01 00:00:00 +0000 UTC UpdateAt:0001-01-01 00:00:00 +0000 UTC}, id: 25
2021-10-31T16:30:27.845+0800 INFO transaction/main.go:81 success to stmt exec, row: &{
ID:0 Name:小天 Sex:女 Counter:23243666993166910 MissCounter:9005766596808865 CreateAt:0001-01-01 00:00:00 +0000 UTC UpdateAt:0001-01-01 00:00:00 +0000 UTC}, id: 26
2021-10-31T16:30:27.848+0800 INFO transaction/main.go:87 finish
If this article is helpful to you, you can give the author a like, thank you.