Golang uses builder generator mode to assemble sql statements

I. Introduction

1. Background

      When I was there code review, I found the following code in the project:

// GetUpdateFieldStr 组装更新的sql和参数
func GetUpdateFieldStr(ctx context.Context, updateFields *infra.UpdateFeilds) (sqlUpdate string, args []interface{
    
    }) {
    
    
	// 拼接update字段
	log.Debugf("getUpdateFieldStr: (%#v)", updateFields)
	sqlUpdate = ""
	// 拼接update字段
	if updateFields.MapId != "" {
    
    
		sqlUpdate += " map_id = ?,"
		args = append(args, updateFields.MapId)
	}
	if updateFields.ObjBase != "" {
    
    
		sqlUpdate += " obj_base = ?,"
		args = append(args, updateFields.ObjBase)
	}
....

	log.Debugf("sqlUpdate : %s,args:(%#v)", sqlUpdate, args)
	return
}

// GetSqlWhereStr 组装where条件和参数
func GetSqlWhereStr(ctx context.Context, wheres *infra.WhereFields) (sqlWhere string, args []interface{
    
    }) {
    
    }

      The one used in the project is sqlxfor adding, deleting, modifying, and checking. Generally speaking, sqlxit ormis relatively primitive. You have to write it yourself sqlor encapsulate the method to splice it sql. However, there are several problems with the above code.

1. The more table fields, the longer the function, and the greater the error probability.
2. If there are multiple tables, these two methods will continue to increase and become redundant.
3. If you want to update the fields to empty, such as string types. Field, the goal is to update to ""an empty string, the above method is obviously inconsistent.

      For the above code, a better way is to use builderthe generator pattern to construct an builderobject, continuously splice fields through chain calls, and finally build()output it through methods. If you have nothing to do on the weekend, let’s refactor it.

2. Generator mode

Reference:
golang implements generator pattern 1
golang implements generator pattern 2
golang implements generator pattern 3

Goals of generator pattern:

  1. Separate the construction of complex objects from their representation.
  2. Allows the same build process to create different representations.
  3. Encapsulate construction logic in a separate builder class.
  4. Allows the creation of objects with many optional or changing parts.

2. Builder generates sql statement

1. Define the objects and builders to be generated

package mysql

import (
	"strings"
)

// SqlClause 定义要生成的对象,组装表sql
type SqlClause struct {
    
    
	table string
	sql   string
	args  []interface{
    
    }
}
// 为了节省空间,具体的SelectBuilder暂时不写
type SelectInterface interface {
    
    
	Select(field string) SelectInterface
	Count(field string) SelectInterface
	Build() (sql string, args []interface{
    
    })
}

// 定义update语句的接口
type UpdateInterface interface {
    
    
	Update(field string, value interface{
    
    }) UpdateInterface
	Build() (sql string, args []interface{
    
    })
}

// 定义where语句的接口
type WhereInterface interface {
    
    
	Where(field string, value interface{
    
    }) WhereInterface
	In(field string, valStr string) WhereInterface
	Build() (sql string, args []interface{
    
    })
}

// UpdateBuilder update语句生成器
type UpdateBuilder struct {
    
    
	UpdateSql *SqlClause
	WhereSql  *WhereBuilder
}

func NewUpdateBuilder(table string) *UpdateBuilder {
    
    
	return &UpdateBuilder{
    
    
		UpdateSql: &SqlClause{
    
    
			table: table,
			sql:   "update " + table + " set ",
			args:  make([]interface{
    
    }, 0),
		},
		WhereSql: NewWhereBuilder(),
	}
}

// 通过update语句组装
func (u *UpdateBuilder) Update(field string, value interface{
    
    }) UpdateInterface {
    
    
	u.UpdateSql.sql += " " + field + " = ?,"
	u.UpdateSql.args = append(u.UpdateSql.args, value)
	return u
}

// 输出完整的update语句
func (u *UpdateBuilder) Build() (sql string, args []interface{
    
    }) {
    
    
	if len(u.UpdateSql.args) < 1 {
    
    
		return
	}
	if len(u.WhereSql.sqlClause.args) < 1 {
    
    
		return
	}
	// 拼接sql
	sql = strings.TrimRight(u.UpdateSql.sql, ", ")
	args = append(args, u.UpdateSql.args...)

	sql += strings.TrimRight(u.WhereSql.sqlClause.sql, "and ")
	args = append(args, u.WhereSql.sqlClause.args...)
	return
}

// WhereBuilder where语句生成器
type WhereBuilder struct {
    
    
	sqlClause *SqlClause
}

func NewWhereBuilder() *WhereBuilder {
    
    
	return &WhereBuilder{
    
    
		sqlClause: &SqlClause{
    
    
			sql:  " where ",
			args: make([]interface{
    
    }, 0),
		},
	}
}

// 通用where条件组装
func (w *WhereBuilder) Where(field string, value interface{
    
    }) WhereInterface {
    
    
	w.sqlClause.sql += " " + field + " = ? and "
	w.sqlClause.args = append(w.sqlClause.args, value)
	return w
}

// where语句的in查询
func (w *WhereBuilder) In(field string, valStr string) WhereInterface {
    
    
	fieldSlice := strings.Split(valStr, ",")
	var inStr string
	for i, v := range fieldSlice {
    
    
		if i == 0 {
    
    
			inStr = "?"
		} else {
    
    
			inStr += ", ?"
		}
		w.sqlClause.args = append(w.sqlClause.args, v)
	}
	w.sqlClause.sql += " " + field + " in ( " + inStr + ") and "
	return w
}

// 输出结果
func (w *WhereBuilder) Build() (sql string, args []interface{
    
    }) {
    
    
	if len(w.sqlClause.args) < 1 {
    
    
		return
	}
	// 拼接where sql
	sqlWhere := strings.TrimRight(w.sqlClause.sql, "and ")
	sql += sqlWhere
	args = append(args, w.sqlClause.args...)
	return
}


2. Each table implements details based on its own fields.

// =========================== table级别方法  =================================
// MappingTaskBuilder map表的builder,组装select,update等sql,调用方式如下:
// 完整的update sql : MappingTaskBuilder.Update().UpdatemapId().WhereTaskId().Build()
// 完整的select sql : MappingTaskBuilder.Select().SelectFields().WhereTaskId().Build()
// 单独的where sql : MappingTaskBuilder.WhereCodes().WhereTaskId().BuildWhereSql()
type MappingTaskBuilder struct {
    
    
	updateBuilder *mysql.UpdateBuilder
	selectBuilder *mysql.SelectBuilder
	whereBuilder  *mysql.WhereBuilder
}

func NewMappingTaskBuilder() *MappingTaskBuilder {
    
    
	return &MappingTaskBuilder{
    
    
		whereBuilder: mysql.NewWhereBuilder(),
	}
}

func (m *MappingTaskBuilder) Update(table string) *MappingTaskBuilder {
    
    
	m.updateBuilder = mysql.NewUpdateBuilder(table)
	return m
}

func (m *MappingTaskBuilder) Select(table string) *MappingTaskBuilder {
    
    
	m.selectBuilder = mysql.NewSelectBuilder(table)
	return m
}

// ------- update条件,定义具体字段所属函数是为了编码的清晰
// map_id
func (m *MappingTaskBuilder) UpdateMapId(field string, value interface{
    
    }) *MappingTaskBuilder {
    
    
	m.updateBuilder.Update(field, value)
	return m
}

// obj_base
func (m *MappingTaskBuilder) UpdateObjBase(field string, value interface{
    
    }) *MappingTaskBuilder {
    
    
	m.updateBuilder.Update(field, value)
	return m
}

// ---- select条件,可以传单个字段,也可以一次性传输

// Select 组装select语句,selectStr为空则返回
func (m *MappingTaskBuilder) SelectFields(selectStr string) *MappingTaskBuilder {
    
    
	if selectStr == "" {
    
    
		return m
	}
	m.selectBuilder.Select(selectStr)
	return m
}

// ---- where条件

// codes
func (m *MappingTaskBuilder) WhereCodes(field string, valStr string) *MappingTaskBuilder {
    
    
	m.whereBuilder.In(field, valStr)
	return m
}

// task_id
func (m *MappingTaskBuilder) WhereTaskId(field string, value interface{
    
    }) *MappingTaskBuilder {
    
    
	m.whereBuilder.Where(field, value)
	return m
}

// --------- 输出
// 输出完整update语句
func (m *MappingTaskBuilder) BuildUpdateSql() (sql string, args []interface{
    
    }) {
    
    
	// where语句赋值
	m.updateBuilder.WhereSql = m.whereBuilder
	sql, args = m.updateBuilder.Build()
	return
}

// 输出完整select语句
func (m *MappingTaskBuilder) BuildSelectSql() (sql string, args []interface{
    
    }) {
    
    
	// where语句赋值
	m.selectBuilder.WhereSql = m.whereBuilder
	sql, args = m.selectBuilder.Build()
	return
}

// 输出 where的sql
func (m *MappingTaskBuilder) BuildWhereSql() (sql string, args []interface{
    
    }) {
    
    
	sql, args = m.whereBuilder.Build()
	return
}

// 输出完整的sql
func (m *MappingTaskBuilder) Build() (sql string, args []interface{
    
    }) {
    
    
	if m.selectBuilder == nil && m.updateBuilder == nil {
    
    
		return
	}

	if m.selectBuilder == nil {
    
    
		return m.BuildUpdateSql()
	}

	if m.updateBuilder == nil {
    
    
		return m.BuildSelectSql()
	}
	// 没有设置update和select则只输出where sql语句
	return m.BuildWhereSql()
}

// Reset 重置
func (m *MappingTaskBuilder) Reset() {
    
    
	m = nil
}

3. Calling method

	builder := xengine.NewMappingTaskBuilder()
	sqlUpdate, args = builder.Update("mapping_task").
		UpdateMapId("map_id", updateFields.MapId).
    	UpdateObjBase("obj_base", updateFields.ObjBase).
		WhereMapId("map_id", wheres.MapId).
    	WhereDel("del", wheres.Del).
    	Builder()

3. Some thoughts

      After writing the above code, I found that it is ormsomewhat similar. Each table is an object, assembled and executed through chain calls sql... Our above encapsulation is the simplest assembly sql.

      In a sense, it also shows that it is a matter of technology selection. When the business is not that complicated, although the selection is orma little heavier, at least the code is easy to write and maintain. It is still possible in sqlxcomplex projects such as statistics, but it is a bit painful to start with dozens of lines.sqlsqlorm

      Unfortunately, the project has already taken shape. Changing sqlthe engine is too big a change, so we have to continuously improve the code level, otherwise code reviewit will be a kind of torture. .

======== 20230715 update =============

      Recently, the above code has been iterated, including adding new hashsupport for field value coverage and buildunified function assembly. As well as support wherein order by,limit,group byetc., overall ormthe operation is becoming more and more biased. . . Therefore, the benevolent sees benevolence and the wise see wisdom, and it is recommended that the matter be settled simply and sqldirectly orm.

end

Guess you like

Origin blog.csdn.net/LJFPHP/article/details/131627549