Go之Cobra实现sql转换工具

参考书籍《Go语言编程之旅》

书代码github:https://github.com/go-programming-tour-book/tour mygithub :
https://github.com/strive-after/cobra

在mysql中information_schema 提供了对数据库元数据的访问,可以获得mysql服务器自身相关的信息,如数据库、表名称、列数据类型、访问权限等
(1)

  • SCHMATA:提供有关数据库的信息,可与SHOW DATABASES语句等效
  • COLUMNS:提供有关表中列的信息,可与SHOW COLUMNS语句等效
  • TABLES:提供有关数据库中表的信息,可与SHOW FULL TABLES语句等效
  • STATISTICS:提供有关表索引的信息,可与SHOW INDEX 语句等效
  • USER_PRIVILEGS:
  • CHARACTER_SETS:
    (2)COLUMNS表
    COLUMNS表提供了整个数据库列的信息
  • TABLE_NAME:列所属的表名称
  • COLUMN_NAME:列的名称
  • COLUMN_DEFAULT:列的默认值
  • IS_NULLABLE:列是否允许为null 值为yes或no
  • DATA_TYPE:列的数据类型,仅包含类型信息
  • COLUMN_TYPE:列的数据类型 包含类型名称和可能的其他信息 例如 精度 长度 是否无符号等
  • COLUMN_KEY:列是否被索引
  • COLUMN_COMMENT:列的注释信息
    可以看到COLUMNS表中基本都具备了,他能直接帮助程序进行表到结构体的映射转换

在做结构体 转换的时候很少会出现 多层嵌套
在面对类型比较简单的基本转换,可以用go 的template来实现
template 提供了2个库
test/template 基于模板输出文本内容
html/template 基于模板输出安全的html格式的内容可以理解 为其进行了转移,以避免受某些注入攻击

package main

import (
	"os"
	"strings"
	"text/template"
)

/*
tempalte 模板定义
1.双层大括号{{}} 在template中所有actions 数据评估、控制流转都需要用标识符双层大括号包裹,其余模板内容军全部原样输出
2.点 会根据标识符进行模板变量的渲染 复杂的需要特殊处理 如果是指针调用会直接调用指针指向的值  如果生成类函数类型的值那么函数不会主动调用
3.函数调用 通过funcmap注册了title的自定义函数,在模板渲染中一共用了两类处理方法 直接用函数或者用| 传递给后面
 */
const templateText = `
OutPut 0: {{title .Name1}}
OutPut 1: {{title .Name2}}
OutPut 2: {{.Name3 |  title}}
`

func main() {
	/*
	首先调用text/template 的new 其根据我们给定的名称标识 创建一个全新的模板对象,接下来调用parse方法
	将常量templateText(预定义的带解析模板)解析为当前文本的模板主体内容,最后调用excute方法对模板进行
	渲染,简单来说就是将传入的data胴体参数渲染到模板标识位上 我们将输出制定了os Stdout
	 */
	funcMap := template.FuncMap{"title":strings.Title}
	//这里我们给定义新的template 然后func传入title 然后是吧首字母大写的函数 解析templateText
	tpl, _ := template.New("go").Funcs(funcMap).Parse(templateText)
	data := map[string]string{
		"Name1":"go",
		"Name2":"test",
		"Name3":"tour",
	}
	//把data传入
	tpl.Execute(os.Stdout,data)
}

我们切入正题
目录结构
在这里插入图片描述

// internal/sqlstruct/mysql.go
package sqlstruct


import (
"database/sql"
"errors"
"fmt"
//导入驱动
_ "github.com/go-sql-driver/mysql"
)
// 要获取表涨列的信息 需要访问数据库的information_schema数据库的COLUMNS表 做连接查询

//连接mysql的核心对象
type DBModel struct {
	DBEngine *sql.DB
	DBInfo   *DBInfo
}
//存放mysql的基础信息
type DBInfo struct {
	DBType   string
	Host     string
	UserName string
	Password string
	Charset  string
}
//村粗COLUMNS表中我们需要的一些字段
type TableColumn struct {
	ColumnName    string
	DataType      string
	IsNullable    string
	ColumnKey     string
	ColumnType    string
	ColumnComment string
}
//由于datatype字段类型与go结构体不是完全一致的 如varcher等 所以要座一层简单的类型转换 这里用枚举然后用map做映射获取
var DBTypeToStructType = map[string]string{
	"int":        "int32",
	"tinyint":    "int8",
	"smallint":   "int",
	"mediumint":  "int64",
	"bigint":     "int64",
	"bit":        "int",
	"bool":       "bool",
	"enum":       "string",
	"set":        "string",
	"varchar":    "string",
	"char":       "string",
	"tinytext":   "string",
	"mediumtext": "string",
	"text":       "string",
	"longtext":   "string",
	"blob":       "string",
	"tinyblob":   "string",
	"mediumblob": "string",
	"longblob":   "string",
	"date":       "time.Time",
	"datetime":   "time.Time",
	"timestamp":  "time.Time",
	"time":       "time.Time",
	"float":      "float64",
	"double":     "float64",
}

func NewDBModel(info *DBInfo) *DBModel {
	return &DBModel{DBInfo: info}
}

func (m *DBModel) Connect() error {
	var err error
	s := "%s:%s@tcp(%s)/information_schema?" +
		"charset=%s&parseTime=True&loc=Local"
	dsn := fmt.Sprintf(
		s,
		m.DBInfo.UserName,
		m.DBInfo.Password,
		m.DBInfo.Host,
		m.DBInfo.Charset,
	)
	m.DBEngine, err = sql.Open(m.DBInfo.DBType, dsn)
	if err != nil {
		return err
	}

	return nil
}
//需要针对COLUMNS进行查询和数据组装
func (m *DBModel) GetColumns(dbName, tableName string) ([]*TableColumn, error) {
	query := "SELECT COLUMN_NAME, DATA_TYPE, COLUMN_KEY, " +
		"IS_NULLABLE, COLUMN_TYPE, COLUMN_COMMENT " +
		"FROM COLUMNS WHERE TABLE_SCHEMA = ? AND TABLE_NAME = ? "
	rows, err := m.DBEngine.Query(query, dbName, tableName)
	if err != nil {
		return nil, err
	}
	if rows == nil {
		return nil, errors.New("没有数据")
	}
	defer rows.Close()

	var columns []*TableColumn
	for rows.Next() {
		var column TableColumn
		err := rows.Scan(&column.ColumnName, &column.DataType, &column.ColumnKey, &column.IsNullable, &column.ColumnType, &column.ColumnComment)
		if err != nil {
			return nil, err
		}

		columns = append(columns, &column)
	}

	return columns, nil
}
package sqlstruct
// internal/sqlstruct/template.go
import (
	"fmt"
	"os"
	"text/template"

	"test/internal/word"
)
//模板渲染 基本结构由一个go结构体 和其所属的tablename方法组成
/*
生成结果大致如下
type 大写驼峰的表的名称struct {
	//注释
	字段名 字段类型
	...	...
}
func (model 大写驼峰的表名称) TableName() string {
	return "表名称"
}

 */
const strcutTpl = `type {{.TableName | ToCamelCase}} struct {
{{range .Columns}}	{{ $length := len .Comment}} {{ if gt $length 0 }}// {{.Comment}} {{else}}// {{.Name}} {{ end }}
	{{ $typeLen := len .Type }} {{ if gt $typeLen 0 }}{{.Name | ToCamelCase}}	{{.Type}}	{{.Tag}}{{ else }}{{.Name}}{{ end }}
{{end}}}

func (model {{.TableName | ToCamelCase}}) TableName() string {
	return "{{.TableName}}"
}`

type StructTemplate struct {
	strcutTpl string
}
//用来存储转换后的go结构体中的所有字段信息 
type StructColumn struct {
	Name    string
	Type    string
	Tag     string
	Comment string
}
//用来存储最终用于渲染的模板对象信息
type StructTemplateDB struct {
	TableName string
	Columns   []*StructColumn
}

func NewStructTemplate() *StructTemplate {
	return &StructTemplate{strcutTpl: strcutTpl}
}
// 通过查询COLUMNS表所组装得到的tbColums进一步转换
func (t *StructTemplate) AssemblyColumns(tbColumns []*TableColumn) []*StructColumn {
	tplColumns := make([]*StructColumn, 0, len(tbColumns))
	for _, column := range tbColumns {
		tag := fmt.Sprintf("`"+"json:"+"\"%s\""+"`", column.ColumnName)
		tplColumns = append(tplColumns, &StructColumn{
			Name:    column.ColumnName,
			Type:    DBTypeToStructType[column.DataType],
			Tag:     tag,
			Comment: column.ColumnComment,
		})
	}

	return tplColumns
}
//对模板自定义函数和模块对象进行处理
func (t *StructTemplate) Generate(tableName string, tplColumns []*StructColumn) error {
	//声明sqlstruct模板对象  定义tocamelcase函数与word.UndersoreToUpperCamelCase 做绑定 等同于我们在模板中使用ToCamelCase函数就等于使用word.UndersoreToUpperCamelCase
	tpl := template.Must(template.New("sql2struct").Funcs(template.FuncMap{
		"ToCamelCase": word.UndersoreToUpperCamelCase,
	}).Parse(t.strcutTpl))

	tplDB := StructTemplateDB{
		TableName: tableName,
		Columns:   tplColumns,
	}
	//最后调用excute进行渲染
	err := tpl.Execute(os.Stdout, tplDB)
	if err != nil {
		return err
	}
	return nil
}
package cmd
// cmd/sql.go


import (
"log"

"test/internal/sqlstruct"
"github.com/spf13/cobra"
)
var (
	username string
	password string
	host string
	charset string
	dbType string
	dbName string
 	tableName string
	)



var sqlCmd = &cobra.Command{
	Use:   "sql",
	Short: "sql转换和处理",
	Long:  "sql转换和处理",
	Run:   func(cmd *cobra.Command, args []string) {},
}

var sql2structCmd = &cobra.Command{
	Use:   "struct",
	Short: "sql转换",
	Long:  "sql转换",
	Run: func(cmd *cobra.Command, args []string) {
		dbInfo := &sqlstruct.DBInfo{
			DBType:   dbType,
			Host:     host,
			UserName: username,
			Password: password,
			Charset:  charset,
		}
		dbModel := sqlstruct.NewDBModel(dbInfo)
		err := dbModel.Connect()
		if err != nil {
			log.Fatalf("dbModel.Connect err: %v", err)
		}
		columns, err := dbModel.GetColumns(dbName, tableName)
		if err != nil {
			log.Fatalf("dbModel.GetColumns err: %v", err)
		}

		template := sqlstruct.NewStructTemplate()
		templateColumns := template.AssemblyColumns(columns)
		err = template.Generate(tableName, templateColumns)
		if err != nil {
			log.Fatalf("template.Generate err: %v", err)
		}
	},
}

func init() {
	sqlCmd.AddCommand(sql2structCmd)
	sql2structCmd.Flags().StringVarP(&username, "username", "", "", "请输入数据库的账号")
	sql2structCmd.Flags().StringVarP(&password, "password", "", "", "请输入数据库的密码")
	sql2structCmd.Flags().StringVarP(&host, "host", "", "127.0.0.1:3306", "请输入数据库的HOST")
	sql2structCmd.Flags().StringVarP(&charset, "charset", "", "utf8mb4", "请输入数据库的编码")
	sql2structCmd.Flags().StringVarP(&dbType, "type", "", "mysql", "请输入数据库实例类型")
	sql2structCmd.Flags().StringVarP(&dbName, "db", "", "", "请输入数据库名称")
	sql2structCmd.Flags().StringVarP(&tableName, "table", "", "", "请输入表名称")
}

猜你喜欢

转载自blog.csdn.net/weixin_45413603/article/details/107573259
今日推荐