Browse Source

修改bug

yjp 1 year ago
parent
commit
b6168c6151
5 changed files with 401 additions and 249 deletions
  1. 43 29
      sql/parse_table_row.go
  2. 151 94
      sql/sql.go
  3. 48 24
      sql/sql_mapping.go
  4. 46 22
      sql/sql_result.go
  5. 113 80
      test/sdk_test.go

+ 43 - 29
sql/parse_table_row.go

@@ -82,27 +82,51 @@ func ParseSqlTableRow(input any, output any) error {
 		outputEntityValue := reflect.New(outputElemType).Elem().Addr()
 		outputEntity := outputEntityValue.Interface()
 
-		sqlResult, err := ParseSqlResult(outputEntity)
+		err := formOutputEntity(tableRow, outputEntity)
 		if err != nil {
 			return err
 		}
 
-		for fieldName, sqlColumn := range sqlResult.ColumnMap {
-			tableRowValue, ok := tableRow[sqlColumn.Name]
+		// 保存输出实体
+		outputEntities = reflect.Append(outputEntities, outputEntityValue.Elem())
+	}
+
+	// 将输出实体赋值给输出指针变量
+	outputValue := reflect.Indirect(reflect.ValueOf(output))
+	if outputType.Kind() == reflect.Slice {
+		outputValue.Set(outputEntities)
+	} else {
+		fmt.Println(outputValue.Type().String())
+		outputValue.Set(outputEntities.Index(0))
+	}
+
+	return nil
+}
+
+func formOutputEntity(tableRow map[string]any, outputEntity any) error {
+	sqlResult, err := ParseSqlResult(outputEntity)
+	if err != nil {
+		return err
+	}
+
+	for fieldName, resultElement := range sqlResult.ResultElement {
+		switch element := resultElement.(type) {
+		case *ResultStruct:
+			err := formOutputEntity(tableRow, element.FieldValueElem.Addr().Interface())
+			if err != nil {
+				return err
+			}
+		case *ResultColumn:
+			tableRowValue, ok := tableRow[element.Name]
 			if !ok {
 				continue
 			}
 
 			// 构造结构字段,如果结构字段是指针且为nil,需要构造元素
-			fieldValue := sqlColumn.OriginFieldValue
-			if fieldValue.Type().Kind() == reflect.Ptr {
-				if fieldValue.IsValid() {
-					fieldValue.Set(reflect.New(sqlColumn.FieldTypeElem))
-				}
-
-				fieldValue = fieldValue.Elem()
+			fieldValue := element.FieldValueElem
+			if !fieldValue.CanSet() {
+				fmt.Println(fieldValue.String())
 			}
-
 			outputKind := reflectutils.GroupValueKind(fieldValue)
 
 			switch outputKind {
@@ -114,15 +138,15 @@ func ParseSqlTableRow(input any, output any) error {
 			case reflect.String:
 				strValue := tableRowValue.(string)
 
-				if strutils.IsStringNotEmpty(sqlColumn.ParseTime) {
+				if strutils.IsStringNotEmpty(element.ParseTime) {
 					parsedTime, err := parseSqlTableRowTimeStr(strValue)
 					if err != nil {
 						return err
 					}
 
-					strValue = parsedTime.Format(sqlColumn.ParseTime)
-				} else if strutils.IsStringNotEmpty(sqlColumn.AESKey) {
-					decryptedValue, err := encoding.AESDecrypt(strValue, sqlColumn.AESKey)
+					strValue = parsedTime.Format(element.ParseTime)
+				} else if strutils.IsStringNotEmpty(element.AESKey) {
+					decryptedValue, err := encoding.AESDecrypt(strValue, element.AESKey)
 					if err != nil {
 						return err
 					}
@@ -161,24 +185,14 @@ func ParseSqlTableRow(input any, output any) error {
 				}
 
 				return fmt.Errorf("字段: %s 列: %s 不支持的类型: %s",
-					fieldName, sqlColumn.Name, reflect.TypeOf(tableRowValue).String())
+					fieldName, element.Name, reflect.TypeOf(tableRowValue).String())
 			default:
 				return fmt.Errorf("字段: %s 列: %s 不支持的类型: %s",
-					fieldName, sqlColumn.Name, reflect.TypeOf(tableRowValue).String())
+					fieldName, element.Name, reflect.TypeOf(tableRowValue).String())
 			}
+		default:
+			return errors.New("不支持的元素类型")
 		}
-
-		// 保存输出实体
-		outputEntities = reflect.Append(outputEntities, outputEntityValue.Elem())
-	}
-
-	// 将输出实体赋值给输出指针变量
-	outputValue := reflect.Indirect(reflect.ValueOf(output))
-	if outputType.Kind() == reflect.Slice {
-		outputValue.Set(outputEntities)
-	} else {
-		fmt.Println(outputValue.Type().String())
-		outputValue.Set(outputEntities.Index(0))
 	}
 
 	return nil

+ 151 - 94
sql/sql.go

@@ -33,41 +33,16 @@ func InsertEntity[T any](executor Executor, tableName string, e T) error {
 		return errors.New("没有传递实体")
 	}
 
-	sqlMapping, err := ParseSqlMapping(e)
+	tableRows := sql_tpl.NewTableRows()
+
+	err := formInsertTableRow(e, tableRows)
 	if err != nil {
 		return err
 	}
 
-	tableRow := sql_tpl.NewTableRows()
-
-	now := time.Now()
-
-	for fieldName, sqlColumn := range sqlMapping.ColumnMap {
-		fieldType := sqlColumn.FieldTypeElem
-
-		// 有值取值,没有值构造零值
-		value := reflect.Zero(fieldType).Interface()
-		if !sqlColumn.FieldValueElem.IsZero() {
-			value = sqlColumn.FieldValueElem.Interface()
-		}
-
-		// 自动添加创建时间和更新时间
-		if (fieldName == createdTimeFieldName || fieldName == lastUpdatedTimeFieldName) &&
-			fieldType.String() == "time.Time" && value.(time.Time).IsZero() {
-			value = now
-		}
-
-		var opts []sql_tpl.AfterParsedStrValueOption
-		if strutils.IsStringNotEmpty(sqlColumn.AESKey) {
-			opts = append(opts, sql_tpl.WithAESKey(sqlColumn.AESKey))
-		}
-
-		tableRow.Add(sqlColumn.Name, value, opts...)
-	}
-
 	executeParamsMap, err := sql_tpl.InsertExecuteParams{
 		TableName: tableName,
-		TableRows: tableRow,
+		TableRows: tableRows,
 	}.Map()
 	if err != nil {
 		return err
@@ -85,6 +60,50 @@ func InsertEntity[T any](executor Executor, tableName string, e T) error {
 	return nil
 }
 
+func formInsertTableRow(e any, tableRows *sql_tpl.TableRows) error {
+	sqlMapping, err := ParseSqlMapping(e)
+	if err != nil {
+		return err
+	}
+
+	now := time.Now()
+
+	for fieldName, mappingElement := range sqlMapping.MappingElement {
+		switch element := mappingElement.(type) {
+		case *MappingStruct:
+			err := formInsertTableRow(element.FieldValueElem.Addr().Interface(), tableRows)
+			if err != nil {
+				return err
+			}
+		case *MappingColumn:
+			fieldType := element.FieldTypeElem
+
+			// 有值取值,没有值构造零值
+			value := reflect.Zero(fieldType).Interface()
+			if !element.FieldValueElem.IsZero() {
+				value = element.FieldValueElem.Interface()
+			}
+
+			// 自动添加创建时间和更新时间
+			if (fieldName == createdTimeFieldName || fieldName == lastUpdatedTimeFieldName) &&
+				fieldType.String() == "time.Time" && value.(time.Time).IsZero() {
+				value = now
+			}
+
+			var opts []sql_tpl.AfterParsedStrValueOption
+			if strutils.IsStringNotEmpty(element.AESKey) {
+				opts = append(opts, sql_tpl.WithAESKey(element.AESKey))
+			}
+
+			tableRows.Add(element.Name, value, opts...)
+		default:
+			return errors.New("不支持的元素类型")
+		}
+	}
+
+	return nil
+}
+
 func DeleteEntity[T any](executor Executor, tableName string, e T) error {
 	if executor == nil {
 		return errors.New("没有传递执行器")
@@ -98,30 +117,11 @@ func DeleteEntity[T any](executor Executor, tableName string, e T) error {
 		return errors.New("没有传递实体")
 	}
 
-	sqlMapping, err := ParseSqlMapping(e)
-	if err != nil {
-		return err
-	}
-
 	conditions := sql_tpl.NewConditions()
 
-	for _, sqlColumn := range sqlMapping.ColumnMap {
-		// 不是键,字段跳过
-		if !sqlColumn.IsKey {
-			continue
-		}
-
-		// 键字段没有赋值
-		if sqlColumn.FieldValueElem.IsZero() {
-			return errors.New("键字段没有传值")
-		}
-
-		var opts []sql_tpl.AfterParsedStrValueOption
-		if strutils.IsStringNotEmpty(sqlColumn.AESKey) {
-			opts = append(opts, sql_tpl.WithAESKey(sqlColumn.AESKey))
-		}
-
-		conditions.Equal(sqlColumn.Name, sqlColumn.FieldValueElem.Interface(), opts...)
+	err := formDeleteConditions(e, conditions)
+	if err != nil {
+		return err
 	}
 
 	executeParamsMap, err := sql_tpl.DeleteExecuteParams{
@@ -140,6 +140,44 @@ func DeleteEntity[T any](executor Executor, tableName string, e T) error {
 	return nil
 }
 
+func formDeleteConditions(e any, conditions *sql_tpl.Conditions) error {
+	sqlMapping, err := ParseSqlMapping(e)
+	if err != nil {
+		return err
+	}
+
+	for _, mappingElement := range sqlMapping.MappingElement {
+		switch element := mappingElement.(type) {
+		case *MappingStruct:
+			err := formDeleteConditions(element.FieldValueElem.Addr().Interface(), conditions)
+			if err != nil {
+				return err
+			}
+		case *MappingColumn:
+			// 不是键,字段跳过
+			if !element.IsKey {
+				continue
+			}
+
+			// 键字段没有赋值
+			if element.FieldValueElem.IsZero() {
+				return errors.New("键字段没有传值")
+			}
+
+			var opts []sql_tpl.AfterParsedStrValueOption
+			if strutils.IsStringNotEmpty(element.AESKey) {
+				opts = append(opts, sql_tpl.WithAESKey(element.AESKey))
+			}
+
+			conditions.Equal(element.Name, element.FieldValueElem.Interface(), opts...)
+		default:
+			return errors.New("不支持的元素类型")
+		}
+	}
+
+	return nil
+}
+
 func UpdateEntity[T any](executor Executor, tableName string, e T) error {
 	if executor == nil {
 		return errors.New("没有传递执行器")
@@ -153,52 +191,12 @@ func UpdateEntity[T any](executor Executor, tableName string, e T) error {
 		return errors.New("没有传递实体")
 	}
 
-	sqlMapping, err := ParseSqlMapping(e)
-	if err != nil {
-		return err
-	}
-
 	tableRows := sql_tpl.NewTableRows()
 	conditions := sql_tpl.NewConditions()
 
-	now := time.Now()
-
-	for fieldName, sqlColumn := range sqlMapping.ColumnMap {
-		if sqlColumn.IsKey {
-			// 键字段但是没有赋值
-			if sqlColumn.FieldValueElem.IsZero() {
-				return errors.New("键字段没有传值")
-			}
-		} else {
-			// 不是键字段
-			// 不更新的字段或者字段为空且不能清空,跳过
-			if !sqlColumn.CanUpdate || (sqlColumn.FieldValueElem.IsZero() && !sqlColumn.CanUpdateClear) {
-				continue
-			}
-		}
-
-		fieldType := sqlColumn.FieldTypeElem
-
-		value := reflect.Zero(fieldType).Interface()
-		if !sqlColumn.FieldValueElem.IsZero() {
-			value = sqlColumn.FieldValueElem.Interface()
-		}
-
-		if fieldName == lastUpdatedTimeFieldName &&
-			fieldType.String() == "time.Time" && value.(time.Time).IsZero() {
-			value = now
-		}
-
-		var opts []sql_tpl.AfterParsedStrValueOption
-		if strutils.IsStringNotEmpty(sqlColumn.AESKey) {
-			opts = append(opts, sql_tpl.WithAESKey(sqlColumn.AESKey))
-		}
-
-		if sqlColumn.IsKey {
-			conditions.Equal(sqlColumn.Name, value, opts...)
-		} else {
-			tableRows.Add(sqlColumn.Name, value, opts...)
-		}
+	err := formUpdateTableRowsAndConditions(e, tableRows, conditions)
+	if err != nil {
+		return err
 	}
 
 	executeParamsMap, err := sql_tpl.UpdateExecuteParams{
@@ -218,6 +216,65 @@ func UpdateEntity[T any](executor Executor, tableName string, e T) error {
 	return nil
 }
 
+func formUpdateTableRowsAndConditions(e any, tableRows *sql_tpl.TableRows, conditions *sql_tpl.Conditions) error {
+	sqlMapping, err := ParseSqlMapping(e)
+	if err != nil {
+		return err
+	}
+
+	now := time.Now()
+
+	for fieldName, mappingElement := range sqlMapping.MappingElement {
+		switch element := mappingElement.(type) {
+		case *MappingStruct:
+			err := formUpdateTableRowsAndConditions(element.FieldValueElem.Addr().Interface(), tableRows, conditions)
+			if err != nil {
+				return err
+			}
+		case *MappingColumn:
+			if element.IsKey {
+				// 键字段但是没有赋值
+				if element.FieldValueElem.IsZero() {
+					return errors.New("键字段没有传值")
+				}
+			} else {
+				// 不是键字段
+				// 不更新的字段或者字段为空且不能清空,跳过
+				if !element.CanUpdate || (element.FieldValueElem.IsZero() && !element.CanUpdateClear) {
+					continue
+				}
+			}
+
+			fieldType := element.FieldTypeElem
+
+			value := reflect.Zero(fieldType).Interface()
+			if !element.FieldValueElem.IsZero() {
+				value = element.FieldValueElem.Interface()
+			}
+
+			if fieldName == lastUpdatedTimeFieldName &&
+				fieldType.String() == "time.Time" && value.(time.Time).IsZero() {
+				value = now
+			}
+
+			var opts []sql_tpl.AfterParsedStrValueOption
+			if strutils.IsStringNotEmpty(element.AESKey) {
+				opts = append(opts, sql_tpl.WithAESKey(element.AESKey))
+			}
+
+			if element.IsKey {
+				conditions.Equal(element.Name, value, opts...)
+			} else {
+				tableRows.Add(element.Name, value, opts...)
+			}
+		default:
+			return errors.New("不支持的元素类型")
+		}
+	}
+
+	return nil
+}
+
 func Insert(executor Executor, executeParams *sql_tpl.InsertExecuteParams) error {
 	if executor == nil {
 		return errors.New("没有传递执行器")

+ 48 - 24
sql/sql_mapping.go

@@ -5,6 +5,7 @@ import (
 	"github.com/iancoleman/strcase"
 	"reflect"
 	"strings"
+	"time"
 )
 
 const (
@@ -24,7 +25,7 @@ const (
 )
 
 type Mapping struct {
-	ColumnMap map[string]MappingColumn
+	MappingElement map[string]any
 }
 
 func ParseSqlMapping(e any) (*Mapping, error) {
@@ -47,28 +48,32 @@ func ParseSqlMapping(e any) (*Mapping, error) {
 	}
 
 	sqlMapping := new(Mapping)
-	sqlMapping.ColumnMap = make(map[string]MappingColumn)
+	sqlMapping.MappingElement = make(map[string]any)
 
 	fieldNum := entityType.NumField()
 	for i := 0; i < fieldNum; i++ {
 		field := entityType.Field(i)
 		fieldValue := entityValue.Field(i)
 
-		column, err := parseSqlMappingColumn(field, fieldValue)
+		element, err := parseSqlMappingElement(field, fieldValue)
 		if err != nil {
 			return nil, err
 		}
 
-		if column == nil {
+		if element == nil {
 			continue
 		}
 
-		sqlMapping.ColumnMap[field.Name] = *column
+		sqlMapping.MappingElement[field.Name] = element
 	}
 
 	return sqlMapping, nil
 }
 
+type MappingStruct struct {
+	MappingTypesAndValues
+}
+
 type MappingColumn struct {
 	Name           string
 	IsKey          bool
@@ -76,6 +81,10 @@ type MappingColumn struct {
 	CanUpdateClear bool
 	AESKey         string
 
+	MappingTypesAndValues
+}
+
+type MappingTypesAndValues struct {
 	// 原字段的反射结构
 	OriginFieldType  reflect.Type
 	OriginFieldValue reflect.Value
@@ -85,30 +94,45 @@ type MappingColumn struct {
 	FieldValueElem reflect.Value
 }
 
-func parseSqlMappingColumn(field reflect.StructField, fieldValue reflect.Value) (*MappingColumn, error) {
-	valueFieldType := field.Type
-	valueFieldValue := fieldValue
-
-	if valueFieldType.Kind() == reflect.Ptr {
-		valueFieldType = valueFieldType.Elem()
+func parseSqlMappingElement(field reflect.StructField, fieldValue reflect.Value) (any, error) {
+	fieldValueTypeElem := field.Type
+	if field.Type.Kind() == reflect.Ptr {
+		fieldValueTypeElem = field.Type.Elem()
+	}
 
-		if !valueFieldValue.IsValid() || valueFieldValue.IsNil() || valueFieldValue.IsZero() {
-			valueFieldValue = reflect.Zero(valueFieldType)
-		} else {
-			valueFieldValue = fieldValue.Elem()
+	fieldValueElem := fieldValue
+	if fieldValue.Kind() == reflect.Ptr {
+		if !fieldValue.IsValid() || fieldValue.IsNil() {
+			fieldValue.Set(reflect.New(fieldValueTypeElem).Elem().Addr())
 		}
+
+		fieldValueElem = fieldValue.Elem()
+	}
+
+	if fieldValueTypeElem.Kind() == reflect.Struct && fieldValueTypeElem != reflect.TypeOf(time.Time{}) {
+		return &MappingStruct{
+			MappingTypesAndValues: MappingTypesAndValues{
+				OriginFieldType:  field.Type,
+				OriginFieldValue: fieldValue,
+				FieldTypeElem:    fieldValueTypeElem,
+				FieldValueElem:   fieldValueElem,
+			},
+		}, nil
 	}
 
 	sqlColumn := &MappingColumn{
-		Name:             strcase.ToSnake(field.Name),
-		IsKey:            false,
-		CanUpdate:        true,
-		CanUpdateClear:   false,
-		AESKey:           "",
-		OriginFieldType:  field.Type,
-		OriginFieldValue: fieldValue,
-		FieldTypeElem:    valueFieldType,
-		FieldValueElem:   valueFieldValue,
+		Name:           strcase.ToSnake(field.Name),
+		IsKey:          false,
+		CanUpdate:      true,
+		CanUpdateClear: false,
+		AESKey:         "",
+
+		MappingTypesAndValues: MappingTypesAndValues{
+			OriginFieldType:  field.Type,
+			OriginFieldValue: fieldValue,
+			FieldTypeElem:    fieldValueTypeElem,
+			FieldValueElem:   fieldValueElem,
+		},
 	}
 
 	if sqlColumn.Name == defaultKeyColumnName {

+ 46 - 22
sql/sql_result.go

@@ -5,6 +5,7 @@ import (
 	"github.com/iancoleman/strcase"
 	"reflect"
 	"strings"
+	"time"
 )
 
 const (
@@ -21,7 +22,7 @@ const (
 )
 
 type Result struct {
-	ColumnMap map[string]ResultColumn
+	ResultElement map[string]any
 }
 
 func ParseSqlResult(e any) (*Result, error) {
@@ -44,33 +45,41 @@ func ParseSqlResult(e any) (*Result, error) {
 	}
 
 	sqlResult := new(Result)
-	sqlResult.ColumnMap = make(map[string]ResultColumn)
+	sqlResult.ResultElement = make(map[string]any)
 
 	fieldNum := entityType.NumField()
 	for i := 0; i < fieldNum; i++ {
 		field := entityType.Field(i)
 		fieldValue := entityValue.Field(i)
 
-		column, err := parseSqlResultColumn(field, fieldValue)
+		element, err := parseSqlResultElement(field, fieldValue)
 		if err != nil {
 			return nil, err
 		}
 
-		if column == nil {
+		if element == nil {
 			continue
 		}
 
-		sqlResult.ColumnMap[field.Name] = *column
+		sqlResult.ResultElement[field.Name] = element
 	}
 
 	return sqlResult, nil
 }
 
+type ResultStruct struct {
+	ResultTypesAndValues
+}
+
 type ResultColumn struct {
 	Name      string
 	ParseTime string
 	AESKey    string
 
+	ResultTypesAndValues
+}
+
+type ResultTypesAndValues struct {
 	// 原字段的反射结构
 	OriginFieldType  reflect.Type
 	OriginFieldValue reflect.Value
@@ -80,28 +89,43 @@ type ResultColumn struct {
 	FieldValueElem reflect.Value
 }
 
-func parseSqlResultColumn(field reflect.StructField, fieldValue reflect.Value) (*ResultColumn, error) {
-	valueFieldType := field.Type
-	valueFieldValue := fieldValue
-
-	if valueFieldType.Kind() == reflect.Ptr {
-		valueFieldType = valueFieldType.Elem()
+func parseSqlResultElement(field reflect.StructField, fieldValue reflect.Value) (any, error) {
+	fieldValueTypeElem := field.Type
+	if field.Type.Kind() == reflect.Ptr {
+		fieldValueTypeElem = field.Type.Elem()
+	}
 
-		if !valueFieldValue.IsValid() || valueFieldValue.IsNil() || valueFieldValue.IsZero() {
-			valueFieldValue = reflect.Zero(valueFieldType)
-		} else {
-			valueFieldValue = fieldValue.Elem()
+	fieldValueElem := fieldValue
+	if fieldValue.Kind() == reflect.Ptr {
+		if !fieldValue.IsValid() || fieldValue.IsNil() {
+			fieldValue.Set(reflect.New(fieldValueTypeElem).Elem().Addr())
 		}
+
+		fieldValueElem = fieldValue.Elem()
+	}
+
+	if fieldValueTypeElem.Kind() == reflect.Struct && fieldValueTypeElem != reflect.TypeOf(time.Time{}) {
+		return &ResultStruct{
+			ResultTypesAndValues: ResultTypesAndValues{
+				OriginFieldType:  field.Type,
+				OriginFieldValue: fieldValue,
+				FieldTypeElem:    fieldValueTypeElem,
+				FieldValueElem:   fieldValueElem,
+			},
+		}, nil
 	}
 
 	sqlColumn := &ResultColumn{
-		Name:             strcase.ToSnake(field.Name),
-		ParseTime:        "",
-		AESKey:           "",
-		OriginFieldType:  field.Type,
-		OriginFieldValue: fieldValue,
-		FieldTypeElem:    valueFieldType,
-		FieldValueElem:   valueFieldValue,
+		Name:      strcase.ToSnake(field.Name),
+		ParseTime: "",
+		AESKey:    "",
+
+		ResultTypesAndValues: ResultTypesAndValues{
+			OriginFieldType:  field.Type,
+			OriginFieldValue: fieldValue,
+			FieldTypeElem:    fieldValueTypeElem,
+			FieldValueElem:   fieldValueElem,
+		},
 	}
 
 	sqlResultTag, ok := field.Tag.Lookup(sqlResultTagKey)

+ 113 - 80
test/sdk_test.go

@@ -14,15 +14,27 @@ import (
 	"time"
 )
 
+type IDField struct {
+	ID string
+}
+
+type TimeFields struct {
+	CreatedTime     *time.Time
+	LastUpdatedTime time.Time
+}
+
+type GraduatedTimeTestField struct {
+	GraduatedTimeTest *string `sqlmapping:"-" sqlresult:"column:graduated_time;parseTime:2006-01-02 15:04:05"`
+}
+
 type Class struct {
-	ID                string
-	Name              string `sqlmapping:"updateClear;aes:@MKU^AHYCN$:j76J<TAHCVD#$XZSWQ@L;" sqlresult:"aes:@MKU^AHYCN$:j76J<TAHCVD#$XZSWQ@L;"`
-	StudentNum        int    `sqlmapping:"column:student_num;notUpdate;" sqlresult:"column:student_num_alias"`
-	GraduatedTime     time.Time
-	CreatedTime       *time.Time
-	LastUpdatedTime   time.Time
-	Ignored           string `sqlmapping:"-" sqlresult:"-"`
-	GraduatedTimeTest string `sqlmapping:"-" sqlresult:"column:graduated_time;parseTime:2006-01-02 15:04:05"`
+	IDField
+	Name          string `sqlmapping:"updateClear;aes:@MKU^AHYCN$:j76J<TAHCVD#$XZSWQ@L;" sqlresult:"aes:@MKU^AHYCN$:j76J<TAHCVD#$XZSWQ@L;"`
+	StudentNum    int    `sqlmapping:"column:student_num;notUpdate;" sqlresult:"column:student_num_alias"`
+	GraduatedTime *time.Time
+	TimeFields
+	Ignored string `sqlmapping:"-" sqlresult:"-"`
+	*GraduatedTimeTestField
 }
 
 const (
@@ -338,104 +350,124 @@ func TestRawSqlTemplate(t *testing.T) {
 }
 
 func TestSqlMapping(t *testing.T) {
-	sqlMapping, err := sql.ParseSqlMapping(&Class{})
+	checkSqlMapping(t, &Class{})
+}
+
+func checkSqlMapping(t *testing.T, e any) {
+	sqlMapping, err := sql.ParseSqlMapping(e)
 	if err != nil {
 		t.Fatal(err)
 	}
 
-	for fieldName, sqlColumn := range sqlMapping.ColumnMap {
-		if fieldName != "ID" && fieldName != "Name" &&
-			fieldName != "StudentNum" && fieldName != "GraduatedTime" &&
-			fieldName != "CreatedTime" && fieldName != "LastUpdatedTime" {
-			t.Fatal("字段名不正确")
-		}
+	for fieldName, mappingElement := range sqlMapping.MappingElement {
+		switch element := mappingElement.(type) {
+		case *sql.MappingStruct:
+			checkSqlMapping(t, element.FieldValueElem.Addr().Interface())
+		case *sql.MappingColumn:
+			if fieldName != "ID" && fieldName != "Name" &&
+				fieldName != "StudentNum" && fieldName != "GraduatedTime" &&
+				fieldName != "CreatedTime" && fieldName != "LastUpdatedTime" {
+				t.Fatal("字段名不正确")
+			}
 
-		if sqlColumn.Name != "id" && sqlColumn.Name != "name" &&
-			sqlColumn.Name != "student_num" && sqlColumn.Name != "graduated_time" &&
-			sqlColumn.Name != "created_time" && sqlColumn.Name != "last_updated_time" {
-			t.Fatal("列名不正确")
-		}
+			if element.Name != "id" && element.Name != "name" &&
+				element.Name != "student_num" && element.Name != "graduated_time" &&
+				element.Name != "created_time" && element.Name != "last_updated_time" {
+				t.Fatal("列名不正确")
+			}
 
-		if sqlColumn.Name != strcase.ToSnake(fieldName) {
-			t.Fatal("列名不正确")
-		}
+			if element.Name != strcase.ToSnake(fieldName) {
+				t.Fatal("列名不正确")
+			}
 
-		if sqlColumn.Name == "id" {
-			if !sqlColumn.IsKey || sqlColumn.CanUpdate || sqlColumn.CanUpdateClear ||
-				strutils.IsStringNotEmpty(sqlColumn.AESKey) {
-				t.Fatal("id字段Tag不正确")
+			if element.Name == "id" {
+				if !element.IsKey || element.CanUpdate || element.CanUpdateClear ||
+					strutils.IsStringNotEmpty(element.AESKey) {
+					t.Fatal("id字段Tag不正确")
+				}
 			}
-		}
 
-		if sqlColumn.Name == "name" {
-			if sqlColumn.IsKey || !sqlColumn.CanUpdate || !sqlColumn.CanUpdateClear ||
-				strutils.IsStringEmpty(sqlColumn.AESKey) || sqlColumn.AESKey != "@MKU^AHYCN$:j76J<TAHCVD#$XZSWQ@L" {
-				t.Fatal("name字段Tag不正确")
+			if element.Name == "name" {
+				if element.IsKey || !element.CanUpdate || !element.CanUpdateClear ||
+					strutils.IsStringEmpty(element.AESKey) || element.AESKey != "@MKU^AHYCN$:j76J<TAHCVD#$XZSWQ@L" {
+					t.Fatal("name字段Tag不正确")
+				}
 			}
-		}
 
-		if sqlColumn.Name == "student_num" {
-			if sqlColumn.IsKey || sqlColumn.CanUpdate || sqlColumn.CanUpdateClear ||
-				strutils.IsStringNotEmpty(sqlColumn.AESKey) {
-				t.Fatal("student_num字段Tag不正确")
+			if element.Name == "student_num" {
+				if element.IsKey || element.CanUpdate || element.CanUpdateClear ||
+					strutils.IsStringNotEmpty(element.AESKey) {
+					t.Fatal("student_num字段Tag不正确")
+				}
 			}
+		default:
+			t.Fatal("不支持的元素类型")
 		}
 	}
 }
 
 func TestSqlResult(t *testing.T) {
-	sqlResult, err := sql.ParseSqlResult(&Class{})
+	checkSqlResult(t, &Class{})
+}
+
+func checkSqlResult(t *testing.T, e any) {
+	sqlResult, err := sql.ParseSqlResult(e)
 	if err != nil {
 		t.Fatal(err)
 	}
 
-	for fieldName, sqlColumn := range sqlResult.ColumnMap {
-		if fieldName != "ID" && fieldName != "Name" &&
-			fieldName != "StudentNum" && fieldName != "GraduatedTime" &&
-			fieldName != "CreatedTime" && fieldName != "LastUpdatedTime" &&
-			fieldName != "GraduatedTimeTest" {
-			t.Fatal("字段名不正确")
-		}
+	for fieldName, mappingElement := range sqlResult.ResultElement {
+		switch element := mappingElement.(type) {
+		case *sql.ResultStruct:
+			checkSqlResult(t, element.FieldValueElem.Addr().Interface())
+		case *sql.ResultColumn:
+			if fieldName != "ID" && fieldName != "Name" &&
+				fieldName != "StudentNum" && fieldName != "GraduatedTime" &&
+				fieldName != "CreatedTime" && fieldName != "LastUpdatedTime" &&
+				fieldName != "GraduatedTimeTest" {
+				t.Fatal("字段名不正确")
+			}
 
-		if sqlColumn.Name != "id" && sqlColumn.Name != "name" &&
-			sqlColumn.Name != "student_num_alias" && sqlColumn.Name != "graduated_time" &&
-			sqlColumn.Name != "created_time" && sqlColumn.Name != "last_updated_time" &&
-			sqlColumn.Name != "graduated_time_test" {
-			t.Fatal("列名不正确")
-		}
+			if element.Name != "id" && element.Name != "name" &&
+				element.Name != "student_num_alias" && element.Name != "graduated_time" &&
+				element.Name != "created_time" && element.Name != "last_updated_time" &&
+				element.Name != "graduated_time_test" {
+				t.Fatal("列名不正确")
+			}
 
-		if sqlColumn.Name != "student_num_alias" &&
-			sqlColumn.Name != "graduated_time" &&
-			sqlColumn.Name != strcase.ToSnake(fieldName) {
-			t.Fatal("列名不正确")
-		}
+			if element.Name != "student_num_alias" &&
+				element.Name != "graduated_time" &&
+				element.Name != strcase.ToSnake(fieldName) {
+				t.Fatal("列名不正确")
+			}
 
-		if sqlColumn.Name == "id" {
-			if strutils.IsStringNotEmpty(sqlColumn.ParseTime) ||
-				strutils.IsStringNotEmpty(sqlColumn.AESKey) {
-				t.Fatal("id字段Tag不正确")
+			if element.Name == "id" {
+				if strutils.IsStringNotEmpty(element.ParseTime) ||
+					strutils.IsStringNotEmpty(element.AESKey) {
+					t.Fatal("id字段Tag不正确")
+				}
 			}
-		}
 
-		if sqlColumn.Name == "name" {
-			if strutils.IsStringNotEmpty(sqlColumn.ParseTime) ||
-				strutils.IsStringEmpty(sqlColumn.AESKey) ||
-				sqlColumn.AESKey != "@MKU^AHYCN$:j76J<TAHCVD#$XZSWQ@L" {
-				t.Fatal("name字段Tag不正确")
+			if element.Name == "name" {
+				if strutils.IsStringNotEmpty(element.ParseTime) ||
+					strutils.IsStringEmpty(element.AESKey) ||
+					element.AESKey != "@MKU^AHYCN$:j76J<TAHCVD#$XZSWQ@L" {
+					t.Fatal("name字段Tag不正确")
+				}
 			}
-		}
 
-		if sqlColumn.Name == "student_num" {
-			if strutils.IsStringNotEmpty(sqlColumn.ParseTime) ||
-				strutils.IsStringNotEmpty(sqlColumn.AESKey) {
-				t.Fatal("student_num字段Tag不正确")
+			if element.Name == "student_num" {
+				if strutils.IsStringNotEmpty(element.ParseTime) ||
+					strutils.IsStringNotEmpty(element.AESKey) {
+					t.Fatal("student_num字段Tag不正确")
+				}
 			}
-		}
 
-		if sqlColumn.Name == "graduate_time" {
-			if strutils.IsStringEmpty(sqlColumn.ParseTime) ||
-				strutils.IsStringNotEmpty(sqlColumn.AESKey) {
-				t.Fatal("graduate_time字段Tag不正确")
+			if element.Name == "graduate_time" {
+				if strutils.IsStringEmpty(element.ParseTime) ||
+					strutils.IsStringNotEmpty(element.AESKey) {
+					t.Fatal("graduate_time字段Tag不正确")
+				}
 			}
 		}
 	}
@@ -448,6 +480,7 @@ func TestSql(t *testing.T) {
 	newClassName := strutils.SimpleUUID()
 	newStudentNum := rand.Int31n(100)
 	now := time.Now()
+	newNow := time.Now()
 
 	insertExecuteParams, err := sql_tpl.InsertExecuteParams{
 		TableName: tableName,
@@ -468,18 +501,18 @@ func TestSql(t *testing.T) {
 	}
 
 	class := &Class{
-		ID:            classID,
+		IDField:       IDField{ID: classID},
 		Name:          className,
 		StudentNum:    int(studentNum),
-		GraduatedTime: time.Now(),
+		GraduatedTime: &newNow,
 		Ignored:       "",
 	}
 
 	newClass := &Class{
-		ID:            classID,
+		IDField:       IDField{ID: classID},
 		Name:          newClassName,
 		StudentNum:    int(newStudentNum),
-		GraduatedTime: time.Now(),
+		GraduatedTime: &newNow,
 		Ignored:       "",
 	}