github.com/ecodeclub/eorm@v0.0.2-0.20231001112437-dae71da914d0/internal/model/model_test.go (about)

     1  // Copyright 2021 ecodeclub
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  // http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package model
    16  
    17  import (
    18  	"fmt"
    19  	"reflect"
    20  	"testing"
    21  
    22  	"github.com/ecodeclub/eorm/internal/errs"
    23  
    24  	"github.com/stretchr/testify/assert"
    25  )
    26  
    27  func TestTagMetaRegistry(t *testing.T) {
    28  
    29  	testCases := []struct {
    30  		name     string
    31  		wantMeta *TableMeta
    32  		wantErr  error
    33  		input    interface{}
    34  	}{
    35  		{
    36  			// 普通
    37  			name: "normal model",
    38  			wantMeta: tableMetaBuilder{
    39  				TableName: "test_model",
    40  				Columns: []*ColumnMeta{
    41  					{
    42  						ColumnName:   "id",
    43  						FieldName:    "Id",
    44  						Typ:          reflect.TypeOf(int64(0)),
    45  						IsPrimaryKey: true,
    46  						FieldIndexes: []int{0},
    47  					},
    48  					{
    49  						ColumnName:   "first_name",
    50  						FieldName:    "FirstName",
    51  						Typ:          reflect.TypeOf(""),
    52  						Offset:       8,
    53  						FieldIndexes: []int{1},
    54  					},
    55  					{
    56  						ColumnName:   "age",
    57  						FieldName:    "Age",
    58  						Typ:          reflect.TypeOf(int8(0)),
    59  						Offset:       24,
    60  						FieldIndexes: []int{2},
    61  					},
    62  					{
    63  						ColumnName:   "last_name",
    64  						FieldName:    "LastName",
    65  						Typ:          reflect.TypeOf((*string)(nil)),
    66  						Offset:       32,
    67  						FieldIndexes: []int{3},
    68  					},
    69  				},
    70  				Typ: reflect.TypeOf(&TestModel{}),
    71  			}.build(),
    72  			input: &TestModel{},
    73  		},
    74  	}
    75  
    76  	for _, tc := range testCases {
    77  		t.Run(tc.name, func(t *testing.T) {
    78  			registry := &tagMetaRegistry{}
    79  			meta, err := registry.Register(tc.input)
    80  			assert.Equal(t, tc.wantErr, err)
    81  			if err != nil {
    82  				return
    83  			}
    84  			assert.Equal(t, tc.wantMeta, meta)
    85  		})
    86  	}
    87  }
    88  func TestTagMetaRegistry_Combination(t *testing.T) {
    89  
    90  	testCases := []struct {
    91  		name     string
    92  		wantMeta *TableMeta
    93  		wantErr  error
    94  		input    interface{}
    95  	}{
    96  		// 普通组合
    97  		{
    98  			name: "普通组合",
    99  			wantMeta: tableMetaBuilder{
   100  				TableName: "test_combined_model",
   101  				Columns: []*ColumnMeta{
   102  					{
   103  						ColumnName:   "create_time",
   104  						FieldName:    "CreateTime",
   105  						Typ:          reflect.TypeOf(uint64(0)),
   106  						IsPrimaryKey: false,
   107  						Offset:       0,
   108  						FieldIndexes: []int{0, 0},
   109  					}, {
   110  						ColumnName:   "update_time",
   111  						FieldName:    "UpdateTime",
   112  						Typ:          reflect.TypeOf(uint64(0)),
   113  						IsPrimaryKey: false,
   114  						Offset:       8,
   115  						FieldIndexes: []int{0, 1},
   116  					},
   117  					{
   118  						ColumnName:   "id",
   119  						FieldName:    "Id",
   120  						Typ:          reflect.TypeOf(int64(0)),
   121  						IsPrimaryKey: true,
   122  						Offset:       16,
   123  						FieldIndexes: []int{1},
   124  					},
   125  					{
   126  						ColumnName:   "first_name",
   127  						FieldName:    "FirstName",
   128  						Typ:          reflect.TypeOf(""),
   129  						Offset:       24,
   130  						FieldIndexes: []int{2},
   131  					},
   132  					{
   133  						ColumnName:   "age",
   134  						FieldName:    "Age",
   135  						Typ:          reflect.TypeOf(int8(0)),
   136  						Offset:       40,
   137  						FieldIndexes: []int{3},
   138  					},
   139  					{
   140  						ColumnName:   "last_name",
   141  						FieldName:    "LastName",
   142  						Typ:          reflect.TypeOf((*string)(nil)),
   143  						Offset:       48,
   144  						FieldIndexes: []int{4},
   145  					},
   146  				},
   147  				Typ: reflect.TypeOf(&TestCombinedModel{}),
   148  			}.build(),
   149  			input: &TestCombinedModel{},
   150  		},
   151  		// 指针组合
   152  		{
   153  			name:    "指针组合",
   154  			input:   &TestCombinedModelPtr{},
   155  			wantErr: errs.ErrCombinationIsNotStruct,
   156  		},
   157  		// 忽略组合
   158  		{
   159  			name: "忽略组合",
   160  			wantMeta: tableMetaBuilder{
   161  				TableName: "test_combined_model_ignore",
   162  				Columns: []*ColumnMeta{
   163  					{
   164  						ColumnName:   "id",
   165  						FieldName:    "Id",
   166  						Typ:          reflect.TypeOf(int64(0)),
   167  						IsPrimaryKey: true,
   168  						Offset:       16,
   169  						FieldIndexes: []int{1},
   170  					},
   171  					{
   172  						ColumnName:   "first_name",
   173  						FieldName:    "FirstName",
   174  						Typ:          reflect.TypeOf(""),
   175  						Offset:       24,
   176  						FieldIndexes: []int{2},
   177  					},
   178  					{
   179  						ColumnName:   "age",
   180  						FieldName:    "Age",
   181  						Typ:          reflect.TypeOf(int8(0)),
   182  						Offset:       40,
   183  						FieldIndexes: []int{3},
   184  					},
   185  					{
   186  						ColumnName:   "last_name",
   187  						FieldName:    "LastName",
   188  						Typ:          reflect.TypeOf((*string)(nil)),
   189  						Offset:       48,
   190  						FieldIndexes: []int{4},
   191  					},
   192  				},
   193  				Typ: reflect.TypeOf(&TestCombinedModelIgnore{}),
   194  			}.build(),
   195  			input: &TestCombinedModelIgnore{},
   196  		},
   197  		// 多重组合
   198  		{
   199  			name: "多重组合",
   200  			wantMeta: tableMetaBuilder{
   201  				TableName: "test_combined_model_multi",
   202  				Columns: []*ColumnMeta{
   203  					{
   204  						ColumnName:   "create_time",
   205  						FieldName:    "CreateTime",
   206  						Typ:          reflect.TypeOf(uint64(0)),
   207  						IsPrimaryKey: false,
   208  						Offset:       0,
   209  						FieldIndexes: []int{0, 0},
   210  					},
   211  					{
   212  						ColumnName:   "update_time",
   213  						FieldName:    "UpdateTime",
   214  						Typ:          reflect.TypeOf(uint64(0)),
   215  						IsPrimaryKey: false,
   216  						Offset:       8,
   217  						FieldIndexes: []int{0, 1},
   218  					},
   219  					{
   220  						ColumnName:   "id",
   221  						FieldName:    "Id",
   222  						Typ:          reflect.TypeOf(int64(0)),
   223  						IsPrimaryKey: true,
   224  						Offset:       16,
   225  						FieldIndexes: []int{1},
   226  					},
   227  					{
   228  						ColumnName:   "first_name",
   229  						FieldName:    "FirstName",
   230  						Typ:          reflect.TypeOf(""),
   231  						Offset:       24,
   232  						FieldIndexes: []int{2},
   233  					},
   234  					{
   235  						ColumnName:   "age",
   236  						FieldName:    "Age",
   237  						Typ:          reflect.TypeOf(int8(0)),
   238  						Offset:       40,
   239  						FieldIndexes: []int{3},
   240  					},
   241  					{
   242  						ColumnName:   "last_name",
   243  						FieldName:    "LastName",
   244  						Typ:          reflect.TypeOf((*string)(nil)),
   245  						Offset:       48,
   246  						FieldIndexes: []int{4},
   247  					},
   248  					{
   249  						ColumnName:   "phone",
   250  						FieldName:    "Phone",
   251  						Typ:          reflect.TypeOf(""),
   252  						Offset:       56,
   253  						FieldIndexes: []int{5, 0},
   254  					},
   255  					{
   256  						ColumnName:   "address",
   257  						FieldName:    "Address",
   258  						Typ:          reflect.TypeOf(""),
   259  						Offset:       72,
   260  						FieldIndexes: []int{5, 1},
   261  					},
   262  				},
   263  				Typ: reflect.TypeOf(&TestCombinedModelMulti{}),
   264  			}.build(),
   265  			input: &TestCombinedModelMulti{},
   266  		},
   267  		// 嵌套组合
   268  		{
   269  			name: "嵌套组合",
   270  			wantMeta: tableMetaBuilder{
   271  				TableName: "test_combined_model_nested",
   272  				Columns: []*ColumnMeta{
   273  					{
   274  						ColumnName:   "create_time",
   275  						FieldName:    "CreateTime",
   276  						Typ:          reflect.TypeOf(uint64(0)),
   277  						IsPrimaryKey: false,
   278  						Offset:       0,
   279  						FieldIndexes: []int{0, 0, 0},
   280  					}, {
   281  						ColumnName:   "update_time",
   282  						FieldName:    "UpdateTime",
   283  						Typ:          reflect.TypeOf(uint64(0)),
   284  						IsPrimaryKey: false,
   285  						Offset:       8,
   286  						FieldIndexes: []int{0, 0, 1},
   287  					},
   288  					{
   289  						ColumnName:   "id",
   290  						FieldName:    "Id",
   291  						Typ:          reflect.TypeOf(int64(0)),
   292  						IsPrimaryKey: true,
   293  						Offset:       16,
   294  						FieldIndexes: []int{0, 1},
   295  					},
   296  					{
   297  						ColumnName:   "first_name",
   298  						FieldName:    "FirstName",
   299  						Typ:          reflect.TypeOf(""),
   300  						Offset:       24,
   301  						FieldIndexes: []int{0, 2},
   302  					},
   303  					{
   304  						ColumnName:   "age",
   305  						FieldName:    "Age",
   306  						Typ:          reflect.TypeOf(int8(0)),
   307  						Offset:       40,
   308  						FieldIndexes: []int{0, 3},
   309  					},
   310  					{
   311  						ColumnName:   "last_name",
   312  						FieldName:    "LastName",
   313  						Typ:          reflect.TypeOf((*string)(nil)),
   314  						Offset:       48,
   315  						FieldIndexes: []int{0, 4},
   316  					},
   317  				},
   318  				Typ: reflect.TypeOf(&TestCombinedModelNested{}),
   319  			}.build(),
   320  			input: &TestCombinedModelNested{},
   321  		},
   322  		// 组合字段冲突
   323  		{
   324  			name:    "组合字段冲突",
   325  			input:   &TestCombinedModelConflict{},
   326  			wantErr: errs.NewFieldConflictError("TestCombinedModelConflict.Id"),
   327  		},
   328  	}
   329  
   330  	for _, tc := range testCases {
   331  		t.Run(tc.name, func(t *testing.T) {
   332  			registry := &tagMetaRegistry{}
   333  			meta, err := registry.Register(tc.input)
   334  			assert.Equal(t, tc.wantErr, err)
   335  			if err != nil {
   336  				return
   337  			}
   338  			assert.Equal(t, tc.wantMeta, meta)
   339  		})
   340  	}
   341  }
   342  
   343  func TestIgnoreFieldsOption(t *testing.T) {
   344  	tm := &TestIgnoreModel{}
   345  	registry := &tagMetaRegistry{}
   346  	meta, err := registry.Register(tm, IgnoreFieldsOption("Id", "FirstName"))
   347  	if err != nil {
   348  		t.Fatal(err)
   349  	}
   350  	assert.Equal(t, 1, len(meta.Columns))
   351  	assert.Equal(t, 1, len(meta.FieldMap))
   352  	assert.Equal(t, reflect.TypeOf(tm), meta.Typ)
   353  	assert.Equal(t, "test_ignore_model", meta.TableName)
   354  
   355  	_, hasId := meta.FieldMap["Id"]
   356  	assert.False(t, hasId)
   357  
   358  	_, hasFirstName := meta.FieldMap["FirstName"]
   359  	assert.False(t, hasFirstName)
   360  
   361  	_, hasAge := meta.FieldMap["Age"]
   362  	assert.False(t, hasAge)
   363  
   364  	_, hasLastName := meta.FieldMap["LastName"]
   365  	assert.True(t, hasLastName)
   366  }
   367  
   368  type TestIgnoreModel struct {
   369  	Id        int64 `eorm:"auto_increment,primary_key,-"`
   370  	FirstName string
   371  	Age       int8 `eorm:"-"`
   372  	LastName  string
   373  }
   374  
   375  func ExampleMetaRegistry_Get() {
   376  	tm := &TestModel{}
   377  	registry := &tagMetaRegistry{}
   378  	meta, _ := registry.Get(tm)
   379  	fmt.Printf("table name: %v\n", meta.TableName)
   380  
   381  	// Output:
   382  	// table name: test_model
   383  }
   384  
   385  func ExampleMetaRegistry_Register() {
   386  	// case1 without TableMetaOption
   387  	tm := &TestModel{}
   388  	registry := &tagMetaRegistry{}
   389  	meta, _ := registry.Register(tm)
   390  	fmt.Printf(`
   391  case1:
   392  	table name:%s
   393  	column names:%s,%s,%s,%s
   394  `, meta.TableName, meta.Columns[0].ColumnName, meta.Columns[1].ColumnName, meta.Columns[2].ColumnName, meta.Columns[3].ColumnName)
   395  
   396  	// case2 use Tag to ignore field
   397  	tim := &TestIgnoreModel{}
   398  	registry = &tagMetaRegistry{}
   399  	meta, _ = registry.Register(tim)
   400  	fmt.Printf(`
   401  case2:
   402  	table name:%s
   403  	column names:%s,%s
   404  `, meta.TableName, meta.Columns[0].ColumnName, meta.Columns[1].ColumnName)
   405  
   406  	// case3 use IgnoreFieldOption to ignore field
   407  	tim = &TestIgnoreModel{}
   408  	registry = &tagMetaRegistry{}
   409  	meta, _ = registry.Register(tim, IgnoreFieldsOption("FirstName"))
   410  	fmt.Printf(`
   411  case3:
   412  	table name:%s
   413  	column names:%s
   414  `, meta.TableName, meta.Columns[0].ColumnName)
   415  
   416  	//	// case4 use IgnoreFieldOption to ignore field and sharding key
   417  	//	tim = &TestIgnoreModel{}
   418  	//	registry = &tagMetaRegistry{}
   419  	//	meta, _ = registry.Register(tim, IgnoreFieldsOption("FirstName"),
   420  	//		WithShardingKey("Id"))
   421  	//	fmt.Printf(`
   422  	//case4:
   423  	//	table name:%s
   424  	//	column names:%s
   425  	//	sharding key name:%s
   426  	//`, meta.TableName, meta.Columns[0].ColumnName, meta.ShardingKey)
   427  	//
   428  	//	// case5 use IgnoreFieldOption to ignore field and db ShardingFunc
   429  	//	tim = &TestIgnoreModel{}
   430  	//	registry = &tagMetaRegistry{}
   431  	//	meta, _ = registry.Register(tim, IgnoreFieldsOption("FirstName"),
   432  	//		WithDBShardingFunc(func(skVal any) (string, error) {
   433  	//			db := skVal.(int) / 100
   434  	//			return fmt.Sprintf("order_db_%d", db), nil
   435  	//		}))
   436  	//	dbName, _ := meta.DBShardingFunc(123)
   437  	//	fmt.Printf(`
   438  	//case5:
   439  	//	table name:%s
   440  	//	column names:%s
   441  	//	db sharding name:%s
   442  	//`, meta.TableName, meta.Columns[0].ColumnName, dbName)
   443  	//
   444  	//	// case6 use IgnoreFieldOption to ignore field and sharding key and table ShardingFunc
   445  	//	tim = &TestIgnoreModel{}
   446  	//	registry = &tagMetaRegistry{}
   447  	//	meta, _ = registry.Register(tim, IgnoreFieldsOption("FirstName"),
   448  	//		WithTableShardingFunc(func(skVal any) (string, error) {
   449  	//			tbl := skVal.(int) % 10
   450  	//			return fmt.Sprintf("order_tab_%d", tbl), nil
   451  	//		}))
   452  	//	tbName, _ := meta.TableShardingFunc(123)
   453  	//	fmt.Printf(`
   454  	//case6:
   455  	//	table name:%s
   456  	//	column names:%s
   457  	//	table sharding name:%s
   458  	//`, meta.TableName, meta.Columns[0].ColumnName, tbName)
   459  
   460  	// Output:
   461  	// case1:
   462  	// 	table name:test_model
   463  	// 	column names:id,first_name,age,last_name
   464  	//
   465  	// case2:
   466  	// 	table name:test_ignore_model
   467  	// 	column names:first_name,last_name
   468  	//
   469  	// case3:
   470  	//	table name:test_ignore_model
   471  	//	column names:last_name
   472  }
   473  
   474  type tableMetaBuilder struct {
   475  	TableName string
   476  	Columns   []*ColumnMeta
   477  	Typ       reflect.Type
   478  }
   479  
   480  func (t tableMetaBuilder) build() *TableMeta {
   481  	res := &TableMeta{
   482  		TableName: t.TableName,
   483  		Columns:   t.Columns,
   484  		Typ:       t.Typ,
   485  	}
   486  	n := len(t.Columns)
   487  	fieldMap := make(map[string]*ColumnMeta, n)
   488  	columnMap := make(map[string]*ColumnMeta, n)
   489  	for _, columnMeta := range t.Columns {
   490  		fieldMap[columnMeta.FieldName] = columnMeta
   491  		columnMap[columnMeta.ColumnName] = columnMeta
   492  	}
   493  	res.FieldMap = fieldMap
   494  	res.ColumnMap = columnMap
   495  	return res
   496  }
   497  
   498  type TestModel struct {
   499  	Id        int64 `eorm:"primary_key"`
   500  	FirstName string
   501  	Age       int8
   502  	LastName  *string
   503  }
   504  
   505  type BaseEntity struct {
   506  	CreateTime uint64
   507  	UpdateTime uint64
   508  }
   509  
   510  type BaseEntity2 struct {
   511  	Id         int64 `eorm:"primary_key"`
   512  	CreateTime uint64
   513  	UpdateTime uint64
   514  }
   515  
   516  type Contact struct {
   517  	Phone   string
   518  	Address string
   519  }
   520  
   521  type TestCombinedModel struct {
   522  	BaseEntity
   523  	Id        int64 `eorm:"primary_key"`
   524  	FirstName string
   525  	Age       int8
   526  	LastName  *string
   527  }
   528  
   529  type TestCombinedModelPtr struct {
   530  	*BaseEntity
   531  	Id        int64 `eorm:"primary_key"`
   532  	FirstName string
   533  	Age       int8
   534  	LastName  *string
   535  }
   536  
   537  type TestCombinedModelIgnore struct {
   538  	BaseEntity `eorm:"-"`
   539  	Id         int64 `eorm:"primary_key"`
   540  	FirstName  string
   541  	Age        int8
   542  	LastName   *string
   543  }
   544  
   545  type TestCombinedModelMulti struct {
   546  	BaseEntity
   547  	Id        int64 `eorm:"primary_key"`
   548  	FirstName string
   549  	Age       int8
   550  	LastName  *string
   551  	Contact
   552  }
   553  
   554  type TestCombinedModelNested struct {
   555  	TestCombinedModel
   556  }
   557  
   558  type TestCombinedModelConflict struct {
   559  	BaseEntity2
   560  	Id        int64 `eorm:"primary_key"`
   561  	FirstName string
   562  	Age       int8
   563  	LastName  *string
   564  }