Introduction

When writing or updating data using methods like Fields/Data/Scan, if the given parameter is of map/struct type, the key names/property names of the parameter will automatically be case-insensitively and character-independently mapped to the fields of the data table.

ORM Senior - Field Mapping - 图1tip

This is why the SHOW FULL COLUMNS FROM 'xxx' statement appears when performing database operations with database components. This statement is executed only once per table and the results are then cached in memory.

Examples of matching rules:

  1. Map Key Field Name Match
  2. nickname nickname match
  3. NICKNAME nickname match
  4. Nick-Name nickname match
  5. nick_name nickname match
  6. nick name nickname match
  7. NickName nickname match
  8. Nick-name nickname match
  9. nick_name nickname match
  10. nick name nickname match

Important Notes

Interface Design

This feature depends on the TableFields interface defined in DB. If this interface is not implemented, the upper-level business needs to maintain the mapping relationship between property/key names and table fields, which incurs a considerable overhead unrelated to business logic. The goal of the framework is to let developers focus as much as possible on the business, hence adopting automation in places where framework components can be automated. The driver implementations integrated with the framework currently all support this interface.

  1. // TableFields retrieves and returns the fields' information of specified table of current
  2. // schema.
  3. //
  4. // The parameter `link` is optional, if given nil it automatically retrieves a raw sql connection
  5. // as its link to proceed necessary sql query.
  6. //
  7. // Note that it returns a map containing the field name and its corresponding fields.
  8. // As a map is unsorted, the TableField struct has an "Index" field marks its sequence in
  9. // the fields.
  10. //
  11. // It's using cache feature to enhance the performance, which is never expired until the
  12. // process restarts.
  13. func (db DB) TableFields(ctx context.Context, table string, schema ...string) (fields map[string]*TableField, err error)

Field Cache

The field information of each data table is queried and cached in memory during the first operation on the table. If you need to manually refresh the field cache, you can do so with the following methods:

  1. // ClearTableFields removes certain cached table fields of current configuration group.
  2. func (c *Core) ClearTableFields(ctx context.Context, table string, schema ...string) (err error)
  3. // ClearTableFieldsAll removes all cached table fields of current configuration group.
  4. func (c *Core) ClearTableFieldsAll(ctx context.Context) (err error)

The introductions of the methods are as in the comments. As can be seen, these two methods are mounted on the Core object, and the underlying Core object is already exposed through the DB interface, thus we can obtain the Core object like this:

  1. g.DB().GetCore()

Usage Example

Let’s look at an example where we implement an interface for querying basic user information, with the user being a doctor.

  1. We have two tables, a user table with about 30 fields, and a doctor_user table with over 80 fields.

  2. user is the basic user table containing the most basic user information, while doctor_user is a business extension table based on the user table for a specific user role, forming a one-to-one relationship with the user table.

  3. We have a GRPC interface with the following definition (for demonstration purposes, some simplifications are made here):

  4. GetDoctorInfoRes

  1. // Query interface return data structure
  2. type GetDoctorInfoRes struct {
  3. UserInfo *UserInfo `protobuf:"bytes,1,opt,name=UserInfo,proto3" json:"UserInfo,omitempty"`
  4. DoctorInfo *DoctorInfo `protobuf:"bytes,2,opt,name=DoctorInfo,proto3" json:"DoctorInfo,omitempty"`
  5. XXX_NoUnkeyedLiteral struct{} `json:"-"`
  6. XXX_unrecognized []byte `json:"-"`
  7. XXX_sizecache int32 `json:"-"`
  8. }
  1. UserInfo
  1. // Basic user information
  2. type UserInfo struct {
  3. Id uint32 `protobuf:"varint,1,opt,name=id,proto3" json:"id,omitempty"`
  4. Avatar string `protobuf:"bytes,2,opt,name=avatar,proto3" json:"avatar,omitempty"`
  5. Name string `protobuf:"bytes,3,opt,name=name,proto3" json:"name,omitempty"`
  6. Sex int32 `protobuf:"varint,4,opt,name=sex,proto3" json:"sex,omitempty"`
  7. XXX_NoUnkeyedLiteral struct{} `json:"-"`
  8. XXX_unrecognized []byte `json:"-"`
  9. XXX_sizecache int32 `json:"-"`
  10. }
  1. DoctorInfo
  1. // Doctor information
  2. type DoctorInfo struct {
  3. Id uint32 `protobuf:"varint,1,opt,name=id,proto3" json:"id,omitempty"`
  4. Name string `protobuf:"bytes,3,opt,name=name,proto3" json:"name,omitempty"`
  5. Hospital string `protobuf:"bytes,4,opt,name=hospital,proto3" json:"hospital,omitempty"`
  6. Section string `protobuf:"bytes,6,opt,name=section,proto3" json:"section,omitempty"`
  7. Title string `protobuf:"bytes,8,opt,name=title,proto3" json:"title,omitempty"`
  8. XXX_NoUnkeyedLiteral struct{} `json:"-"`
  9. XXX_unrecognized []byte `json:"-"`
  10. XXX_sizecache int32 `json:"-"`
  11. }
  1. Query interface implementation code
  1. // Query doctor information
  2. func (s *Service) GetDoctorInfo(ctx context.Context, req *pb.GetDoctorInfoReq) (res *pb.GetDoctorInfoRes, err error) {
  3. // Protobuf return data structure
  4. res = &pb.GetDoctorInfoRes{}
  5. // Query doctor information
  6. // SELECT `id`,`avatar`,`name`,`sex` FROM `user` WHERE `user_id`=xxx
  7. err = dao.PrimaryDoctorUser.
  8. Ctx(ctx).
  9. Fields(res.DoctorInfo).
  10. Where(dao.PrimaryDoctorUser.Columns.UserId, req.Id).
  11. Scan(&res.DoctorInfo)
  12. if err != nil {
  13. return
  14. }
  15. // Query basic user information
  16. // SELECT `id`,`name`,`hospital`,`section`,`title` FROM `doctor_user` WHERE `id`=xxx
  17. err = dao.PrimaryUser.
  18. Ctx(ctx).
  19. Fields(res.DoctorInfo).
  20. Where(dao.PrimaryUser.Columns.Id, req.Id).
  21. Scan(&res.UserInfo)
  22. return res, err
  23. }

When we call GetDoctorInfo to execute the query, two SQL queries will be sent to the database, for instance:

  1. SELECT `id`,`avatar`,`name`,`sex` FROM `user` WHERE `user_id`=1
  2. SELECT `id`,`name`,`hospital`,`section`,`title` FROM `doctor_user` WHERE `id`=1

As can be seen:

  • When using the Fields method, the parameter type is a struct or *struct, and the ORM will automatically map the property names of the struct to the field names of the data table. When the mapping is successful, only specific field data is queried, and non-existent property fields will be automatically filtered.
  • When using the Scan method (also applicable with Struct/Structs), the parameter type is a *struct or **struct, and the query results will automatically map to the properties of the struct. When the mapping is successful, conversion and assignment are automatically done; otherwise, no processing is done on the attributes of the parameters.