Browse Source

improve data type conversion (#236)

* improve data type conversion

* update doc
songmeizi 4 năm trước cách đây
mục cha
commit
4d53045c6b

+ 32 - 2
tools/goctl/model/sql/README.MD

@@ -78,8 +78,8 @@ goctl model 为go-zero下的工具模块中的组件之一,目前支持识别m
     		Password   string    `db:"password"` // 用户密码
     		Mobile     string    `db:"mobile"`   // 手机号
     		Gender     string    `db:"gender"`   // 男|女|未公开
-    		Nickname   string    `db:"nickname"` // 用户昵称
-    		CreateTime time.Time `db:"create_time"`
+    		Nickname   sql.NullString    `db:"nickname"` // 用户昵称
+    		CreateTime sql.NullTime `db:"create_time"`
     		UpdateTime time.Time `db:"update_time"`
     	}
     )
@@ -347,3 +347,33 @@ OPTIONS:
   
   目前,我认为除了基本的CURD外,其他的代码均属于<i>业务型</i>代码,这个我觉得开发人员根据业务需要进行编写更好。
 
+# 类型转换规则
+| mysql dataType | golang dataType | golang dataType(if null&&default null) |
+|----------------|-----------------|----------------------------------------|
+| bool           | int64           | sql.NullInt64                          |
+| boolean        | int64           | sql.NullInt64                          |
+| tinyint        | int64           | sql.NullInt64                          |
+| smallint       | int64           | sql.NullInt64                          |
+| mediumint      | int64           | sql.NullInt64                          |
+| int            | int64           | sql.NullInt64                          |
+| integer        | int64           | sql.NullInt64                          |
+| bigint         | int64           | sql.NullInt64                          |
+| float          | float64         | sql.NullFloat64                        |
+| double         | float64         | sql.NullFloat64                        |
+| decimal        | float64         | sql.NullFloat64                        |
+| date           | time.Time       | sql.NullTime                           |
+| datetime       | time.Time       | sql.NullTime                           |
+| timestamp      | time.Time       | sql.NullTime                           |
+| time           | string          | sql.NullString                         |
+| year           | time.Time       | sql.NullInt64                          |
+| char           | string          | sql.NullString                         |
+| varchar        | string          | sql.NullString                         |
+| binary         | string          | sql.NullString                         |
+| varbinary      | string          | sql.NullString                         |
+| tinytext       | string          | sql.NullString                         |
+| text           | string          | sql.NullString                         |
+| mediumtext     | string          | sql.NullString                         |
+| longtext       | string          | sql.NullString                         |
+| enum           | string          | sql.NullString                         |
+| set            | string          | sql.NullString                         |
+| json           | string          | sql.NullString                         |

+ 27 - 5
tools/goctl/model/sql/converter/types.go

@@ -41,12 +41,34 @@ var (
 	}
 )
 
-func ConvertDataType(dataBaseType string) (goDataType string, err error) {
+func ConvertDataType(dataBaseType string, isDefaultNull bool) (string, error) {
 	tp, ok := commonMysqlDataTypeMap[strings.ToLower(dataBaseType)]
 	if !ok {
-		err = fmt.Errorf("unexpected database type: %s", dataBaseType)
-		return
+		return "", fmt.Errorf("unexpected database type: %s", dataBaseType)
+	}
+
+	return mayConvertNullType(tp, isDefaultNull), nil
+}
+
+func mayConvertNullType(goDataType string, isDefaultNull bool) string {
+	if !isDefaultNull {
+		return goDataType
+	}
+
+	switch goDataType {
+	case "int64":
+		return "sql.NullInt64"
+	case "int32":
+		return "sql.NullInt32"
+	case "float64":
+		return "sql.NullFloat64"
+	case "bool":
+		return "sql.NullBool"
+	case "string":
+		return "sql.NullString"
+	case "time.Time":
+		return "sql.NullTime"
+	default:
+		return goDataType
 	}
-	goDataType = tp
-	return
 }

+ 11 - 3
tools/goctl/model/sql/converter/types_test.go

@@ -7,14 +7,22 @@ import (
 )
 
 func TestConvertDataType(t *testing.T) {
-	v, err := ConvertDataType("tinyint")
+	v, err := ConvertDataType("tinyint", false)
 	assert.Nil(t, err)
 	assert.Equal(t, "int64", v)
 
-	v, err = ConvertDataType("timestamp")
+	v, err = ConvertDataType("tinyint", true)
+	assert.Nil(t, err)
+	assert.Equal(t, "sql.NullInt64", v)
+
+	v, err = ConvertDataType("timestamp", false)
 	assert.Nil(t, err)
 	assert.Equal(t, "time.Time", v)
 
-	_, err = ConvertDataType("float32")
+	v, err = ConvertDataType("timestamp", true)
+	assert.Nil(t, err)
+	assert.Equal(t, "sql.NullTime", v)
+
+	_, err = ConvertDataType("float32", false)
 	assert.NotNil(t, err)
 }

+ 9 - 0
tools/goctl/model/sql/example/sql/user.sql

@@ -15,3 +15,12 @@ CREATE TABLE `user` (
   UNIQUE KEY `mobile_index` (`mobile`)
 ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
 
+CREATE TABLE `student` (
+  `id` bigint NOT NULL AUTO_INCREMENT,
+  `name` varchar(255) COLLATE utf8mb4_bin NOT NULL DEFAULT '',
+  `age` tinyint DEFAULT NULL,
+  `score` float(10,0) DEFAULT NULL,
+  `create_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
+  `update_time` timestamp NULL DEFAULT NULL,
+  PRIMARY KEY (`id`) USING BTREE
+) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;

+ 0 - 15
tools/goctl/model/sql/example/sql/user_1.sql

@@ -1,15 +0,0 @@
--- 用户表 --
-CREATE TABLE `user1` (
-  `id` bigint(10) NOT NULL AUTO_INCREMENT,
-  `name` varchar(255) COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '用户名称',
-  `password` varchar(255) COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '用户密码',
-  `mobile` varchar(255) COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '手机号',
-  `gender` char(5) COLLATE utf8mb4_general_ci NOT NULL COMMENT '男|女|未公开',
-  `nickname` varchar(255) COLLATE utf8mb4_general_ci DEFAULT '' COMMENT '用户昵称',
-  `create_time` timestamp NULL DEFAULT CURRENT_TIMESTAMP,
-  `update_time` timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
-  PRIMARY KEY (`id`),
-  UNIQUE KEY `name_index` (`name`),
-  UNIQUE KEY `mobile_index` (`mobile`)
-) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
-

+ 8 - 6
tools/goctl/model/sql/model/informationschemamodel.go

@@ -9,11 +9,13 @@ type (
 		conn sqlx.SqlConn
 	}
 	Column struct {
-		Name     string `db:"COLUMN_NAME"`
-		DataType string `db:"DATA_TYPE"`
-		Key      string `db:"COLUMN_KEY"`
-		Extra    string `db:"EXTRA"`
-		Comment  string `db:"COLUMN_COMMENT"`
+		Name          string      `db:"COLUMN_NAME"`
+		DataType      string      `db:"DATA_TYPE"`
+		Key           string      `db:"COLUMN_KEY"`
+		Extra         string      `db:"EXTRA"`
+		Comment       string      `db:"COLUMN_COMMENT"`
+		ColumnDefault interface{} `db:"COLUMN_DEFAULT"`
+		IsNullAble    string      `db:"IS_NULLABLE"`
 	}
 )
 
@@ -33,7 +35,7 @@ func (m *InformationSchemaModel) GetAllTables(database string) ([]string, error)
 }
 
 func (m *InformationSchemaModel) FindByTableName(db, table string) ([]*Column, error) {
-	querySql := `select COLUMN_NAME,DATA_TYPE,COLUMN_KEY,EXTRA,COLUMN_COMMENT from COLUMNS where TABLE_SCHEMA = ? and TABLE_NAME = ?`
+	querySql := `select COLUMN_NAME,COLUMN_DEFAULT,IS_NULLABLE,DATA_TYPE,COLUMN_KEY,EXTRA,COLUMN_COMMENT from COLUMNS where TABLE_SCHEMA = ? and TABLE_NAME = ?`
 	var reply []*Column
 	err := m.conn.QueryRows(&reply, querySql, db, table)
 	return reply, err

+ 15 - 3
tools/goctl/model/sql/parser/parser.go

@@ -112,7 +112,17 @@ func Parse(ddl string) (*Table, error) {
 		if column.Type.Comment != nil {
 			comment = string(column.Type.Comment.Val)
 		}
-		dataType, err := converter.ConvertDataType(column.Type.Type)
+		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, err
 		}
@@ -170,7 +180,8 @@ func ConvertColumn(db, table string, in []*model.Column) (*Table, error) {
 	}
 
 	primaryColumn := primaryColumns[0]
-	primaryFt, err := converter.ConvertDataType(primaryColumn.DataType)
+	isDefaultNull := primaryColumn.ColumnDefault == nil && primaryColumn.IsNullAble == "YES"
+	primaryFt, err := converter.ConvertDataType(primaryColumn.DataType, isDefaultNull)
 	if err != nil {
 		return nil, err
 	}
@@ -189,7 +200,8 @@ func ConvertColumn(db, table string, in []*model.Column) (*Table, error) {
 	}
 	for key, columns := range keyMap {
 		for _, item := range columns {
-			dt, err := converter.ConvertDataType(item.DataType)
+			isColumnDefaultNull := item.ColumnDefault == nil && item.IsNullAble == "YES"
+			dt, err := converter.ConvertDataType(item.DataType, isColumnDefaultNull)
 			if err != nil {
 				return nil, err
 			}

+ 1 - 0
tools/goctl/model/sql/parser/parser_test.go

@@ -79,6 +79,7 @@ func TestConvertColumn(t *testing.T) {
 	for _, item := range table.Fields {
 		if item.Name.Source() == "mobile" {
 			assert.True(t, item.IsUniqueKey)
+			break
 		}
 	}
 }