Go sqlx framework handles structure fields with underscore nomenclature

about sqlx

sqlx is a Golang database operation library, which provides a set of extended API based database/sqlon , 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:

  1. 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 .
  2. 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.

  1. Write the underscore nomenclature NameMapper function.
  2. 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.ToLowerthe 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 gormof (by default, gorm uses the underscore naming method to generate database fields) to extract toDBNamethe 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 NameMapperthat 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.

  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
  1. Next, define a structure called testTable in the Go program, where MissCounterthe 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
}
  1. 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")
}
  1. 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)}
  1. 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.

Guess you like

Origin blog.csdn.net/yes169yes123/article/details/121066019