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 }