You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
248 lines
5.7 KiB
Go
248 lines
5.7 KiB
Go
package parser
|
|
|
|
import (
|
|
"fmt"
|
|
"strings"
|
|
|
|
"github.com/tal-tech/go-zero/tools/goctl/model/sql/converter"
|
|
"github.com/tal-tech/go-zero/tools/goctl/model/sql/model"
|
|
"github.com/tal-tech/go-zero/tools/goctl/util/stringx"
|
|
"github.com/xwb1989/sqlparser"
|
|
)
|
|
|
|
const (
|
|
_ = iota
|
|
primary
|
|
unique
|
|
normal
|
|
spatial
|
|
)
|
|
|
|
const timeImport = "time.Time"
|
|
|
|
type (
|
|
// Table describes a mysql table
|
|
Table struct {
|
|
Name stringx.String
|
|
PrimaryKey Primary
|
|
Fields []Field
|
|
}
|
|
|
|
// Primary describes a primary key
|
|
Primary struct {
|
|
Field
|
|
AutoIncrement bool
|
|
}
|
|
|
|
// Field describes a table field
|
|
Field struct {
|
|
Name stringx.String
|
|
DataBaseType string
|
|
DataType string
|
|
IsPrimaryKey bool
|
|
IsUniqueKey bool
|
|
Comment string
|
|
}
|
|
|
|
// KeyType types alias of int
|
|
KeyType int
|
|
)
|
|
|
|
// Parse parses ddl into golang structure
|
|
func Parse(ddl string) (*Table, error) {
|
|
stmt, err := sqlparser.ParseStrictDDL(ddl)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
ddlStmt, ok := stmt.(*sqlparser.DDL)
|
|
if !ok {
|
|
return nil, errUnsupportDDL
|
|
}
|
|
|
|
action := ddlStmt.Action
|
|
if action != sqlparser.CreateStr {
|
|
return nil, fmt.Errorf("expected [CREATE] action,but found: %s", action)
|
|
}
|
|
|
|
tableName := ddlStmt.NewName.Name.String()
|
|
tableSpec := ddlStmt.TableSpec
|
|
if tableSpec == nil {
|
|
return nil, errTableBodyNotFound
|
|
}
|
|
|
|
columns := tableSpec.Columns
|
|
indexes := tableSpec.Indexes
|
|
keyMap, err := getIndexKeyType(indexes)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
fields, primaryKey, err := convertFileds(columns, keyMap)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return &Table{
|
|
Name: stringx.From(tableName),
|
|
PrimaryKey: primaryKey,
|
|
Fields: fields,
|
|
}, nil
|
|
}
|
|
|
|
func convertFileds(columns []*sqlparser.ColumnDefinition, keyMap map[string]KeyType) ([]Field, Primary, error) {
|
|
var fields []Field
|
|
var primaryKey Primary
|
|
for _, column := range columns {
|
|
if column == nil {
|
|
continue
|
|
}
|
|
var comment string
|
|
if column.Type.Comment != nil {
|
|
comment = string(column.Type.Comment.Val)
|
|
}
|
|
var isDefaultNull = true
|
|
if column.Type.NotNull {
|
|
isDefaultNull = false
|
|
} else {
|
|
if column.Type.Default == nil {
|
|
isDefaultNull = false
|
|
} else if string(column.Type.Default.Val) != "null" {
|
|
isDefaultNull = false
|
|
}
|
|
}
|
|
dataType, err := converter.ConvertDataType(column.Type.Type, isDefaultNull)
|
|
if err != nil {
|
|
return nil, primaryKey, err
|
|
}
|
|
|
|
var field Field
|
|
field.Name = stringx.From(column.Name.String())
|
|
field.DataBaseType = column.Type.Type
|
|
field.DataType = dataType
|
|
field.Comment = comment
|
|
key, ok := keyMap[column.Name.String()]
|
|
if ok {
|
|
field.IsPrimaryKey = key == primary
|
|
field.IsUniqueKey = key == unique
|
|
if field.IsPrimaryKey {
|
|
primaryKey.Field = field
|
|
if column.Type.Autoincrement {
|
|
primaryKey.AutoIncrement = true
|
|
}
|
|
}
|
|
}
|
|
fields = append(fields, field)
|
|
}
|
|
return fields, primaryKey, nil
|
|
}
|
|
|
|
func getIndexKeyType(indexes []*sqlparser.IndexDefinition) (map[string]KeyType, error) {
|
|
keyMap := make(map[string]KeyType)
|
|
for _, index := range indexes {
|
|
info := index.Info
|
|
if info == nil {
|
|
continue
|
|
}
|
|
if info.Primary {
|
|
if len(index.Columns) > 1 {
|
|
return nil, errPrimaryKey
|
|
}
|
|
|
|
keyMap[index.Columns[0].Column.String()] = primary
|
|
continue
|
|
}
|
|
// can optimize
|
|
if len(index.Columns) > 1 {
|
|
continue
|
|
}
|
|
column := index.Columns[0]
|
|
columnName := column.Column.String()
|
|
camelColumnName := stringx.From(columnName).ToCamel()
|
|
// by default, createTime|updateTime findOne is not used.
|
|
if camelColumnName == "CreateTime" || camelColumnName == "UpdateTime" {
|
|
continue
|
|
}
|
|
if info.Unique {
|
|
keyMap[columnName] = unique
|
|
} else if info.Spatial {
|
|
keyMap[columnName] = spatial
|
|
} else {
|
|
keyMap[columnName] = normal
|
|
}
|
|
}
|
|
return keyMap, nil
|
|
}
|
|
|
|
// ContainsTime determines whether the table field contains time.Time
|
|
func (t *Table) ContainsTime() bool {
|
|
for _, item := range t.Fields {
|
|
if item.DataType == timeImport {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
// ConvertColumn provides type conversion for mysql clolumn, primary key lookup
|
|
func ConvertColumn(db, table string, in []*model.Column) (*Table, error) {
|
|
var reply Table
|
|
reply.Name = stringx.From(table)
|
|
keyMap := make(map[string][]*model.Column)
|
|
|
|
for _, column := range in {
|
|
keyMap[column.Key] = append(keyMap[column.Key], column)
|
|
}
|
|
primaryColumns := keyMap["PRI"]
|
|
if len(primaryColumns) == 0 {
|
|
return nil, fmt.Errorf("database:%s, table %s: missing primary key", db, table)
|
|
}
|
|
|
|
if len(primaryColumns) > 1 {
|
|
return nil, fmt.Errorf("database:%s, table %s: only one primary key expected", db, table)
|
|
}
|
|
|
|
primaryColumn := primaryColumns[0]
|
|
isDefaultNull := primaryColumn.ColumnDefault == nil && primaryColumn.IsNullAble == "YES"
|
|
primaryFt, err := converter.ConvertDataType(primaryColumn.DataType, isDefaultNull)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
primaryField := Field{
|
|
Name: stringx.From(primaryColumn.Name),
|
|
DataBaseType: primaryColumn.DataType,
|
|
DataType: primaryFt,
|
|
IsUniqueKey: true,
|
|
IsPrimaryKey: true,
|
|
Comment: primaryColumn.Comment,
|
|
}
|
|
reply.PrimaryKey = Primary{
|
|
Field: primaryField,
|
|
AutoIncrement: strings.Contains(primaryColumn.Extra, "auto_increment"),
|
|
}
|
|
for key, columns := range keyMap {
|
|
for _, item := range columns {
|
|
isColumnDefaultNull := item.ColumnDefault == nil && item.IsNullAble == "YES"
|
|
dt, err := converter.ConvertDataType(item.DataType, isColumnDefaultNull)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
f := Field{
|
|
Name: stringx.From(item.Name),
|
|
DataBaseType: item.DataType,
|
|
DataType: dt,
|
|
IsPrimaryKey: primaryColumn.Name == item.Name,
|
|
Comment: item.Comment,
|
|
}
|
|
if key == "UNI" {
|
|
f.IsUniqueKey = true
|
|
}
|
|
reply.Fields = append(reply.Fields, f)
|
|
}
|
|
}
|
|
|
|
return &reply, nil
|
|
}
|