github.com/ecodeclub/eorm@v0.0.2-0.20231001112437-dae71da914d0/sharding_update_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 eorm 16 17 import ( 18 "context" 19 "database/sql" 20 "fmt" 21 "regexp" 22 "testing" 23 24 "go.uber.org/multierr" 25 26 "github.com/DATA-DOG/go-sqlmock" 27 "github.com/ecodeclub/eorm/internal/datasource" 28 "github.com/ecodeclub/eorm/internal/datasource/cluster" 29 "github.com/ecodeclub/eorm/internal/datasource/masterslave" 30 "github.com/ecodeclub/eorm/internal/datasource/shardingsource" 31 "github.com/ecodeclub/eorm/internal/errs" 32 "github.com/ecodeclub/eorm/internal/model" 33 "github.com/ecodeclub/eorm/internal/sharding" 34 "github.com/ecodeclub/eorm/internal/sharding/hash" 35 _ "github.com/go-sql-driver/mysql" 36 "github.com/stretchr/testify/assert" 37 "github.com/stretchr/testify/require" 38 "github.com/stretchr/testify/suite" 39 ) 40 41 func TestShardingUpdater_Build(t *testing.T) { 42 r := model.NewMetaRegistry() 43 dbBase, tableBase := 2, 3 44 orderDBPattern, orderTablePattern := "order_db_%d", "order_tab_%d" 45 dsPattern := "0.db.cluster.company.com:3306" 46 _, err := r.Register(&Order{}, 47 model.WithTableShardingAlgorithm(&hash.Hash{ 48 ShardingKey: "UserId", 49 DBPattern: &hash.Pattern{Name: orderDBPattern, Base: dbBase}, 50 TablePattern: &hash.Pattern{Name: orderTablePattern, Base: tableBase}, 51 DsPattern: &hash.Pattern{Name: dsPattern, NotSharding: true}, 52 })) 53 require.NoError(t, err) 54 r2 := model.NewMetaRegistry() 55 _, err = r2.Register(&OrderDetail{}, 56 model.WithTableShardingAlgorithm(&hash.Hash{ 57 ShardingKey: "OrderId", 58 DBPattern: &hash.Pattern{Name: "order_detail_db_%d", Base: dbBase}, 59 TablePattern: &hash.Pattern{Name: "order_detail_tab_%d", Base: tableBase}, 60 DsPattern: &hash.Pattern{Name: dsPattern, NotSharding: true}, 61 })) 62 require.NoError(t, err) 63 m := map[string]*masterslave.MasterSlavesDB{ 64 "order_db_0": MasterSlavesMemoryDB(), 65 "order_db_1": MasterSlavesMemoryDB(), 66 "order_db_2": MasterSlavesMemoryDB(), 67 "order_detail_db_0": MasterSlavesMemoryDB(), 68 "order_detail_db_1": MasterSlavesMemoryDB(), 69 "order_detail_db_2": MasterSlavesMemoryDB(), 70 } 71 clusterDB := cluster.NewClusterDB(m) 72 ds := map[string]datasource.DataSource{ 73 "0.db.cluster.company.com:3306": clusterDB, 74 } 75 shardingDB, err := OpenDS("sqlite3", 76 shardingsource.NewShardingDataSource(ds), DBWithMetaRegistry(r)) 77 require.NoError(t, err) 78 shardingDB2, err := OpenDS("sqlite3", 79 shardingsource.NewShardingDataSource(ds), DBWithMetaRegistry(r2)) 80 require.NoError(t, err) 81 testCases := []struct { 82 name string 83 builder sharding.QueryBuilder 84 wantQs []sharding.Query 85 wantErr error 86 }{ 87 { 88 name: "where eq", 89 builder: NewShardingUpdater[Order](shardingDB).Update(&Order{ 90 UserId: 1, OrderId: 1, Content: "1", Account: 1.0, 91 }).Where(C("UserId").EQ(1)), 92 wantQs: []sharding.Query{ 93 { 94 SQL: fmt.Sprintf("UPDATE %s.%s SET `order_id`=?,`content`=?,`account`=? WHERE `user_id`=?;", "`order_db_1`", "`order_tab_1`"), 95 Args: []any{int64(1), "1", 1.0, 1}, 96 DB: "order_db_1", 97 Datasource: "0.db.cluster.company.com:3306", 98 }, 99 }, 100 }, 101 { 102 name: "not where", 103 builder: NewShardingUpdater[Order](shardingDB).Update(&Order{ 104 Content: "1", Account: 1.0, 105 }).Set(C("Content"), C("Account")), 106 wantQs: func() []sharding.Query { 107 var res []sharding.Query 108 sql := "UPDATE `%s`.`%s` SET `content`=?,`account`=?;" 109 for i := 0; i < dbBase; i++ { 110 dbName := fmt.Sprintf(orderDBPattern, i) 111 for j := 0; j < tableBase; j++ { 112 tableName := fmt.Sprintf(orderTablePattern, j) 113 res = append(res, sharding.Query{ 114 SQL: fmt.Sprintf(sql, dbName, tableName), 115 Args: []any{"1", 1.0}, 116 DB: dbName, 117 Datasource: dsPattern, 118 }) 119 } 120 } 121 return res 122 }(), 123 }, 124 { 125 name: "where eq ignore zero val", 126 builder: NewShardingUpdater[OrderDetail](shardingDB2).Update(&OrderDetail{ 127 UsingCol1: "Jack", UsingCol2: &sql.NullString{String: "Jerry", Valid: true}, 128 }).SkipZeroValue().Where(C("OrderId").EQ(1)), 129 wantQs: []sharding.Query{ 130 { 131 SQL: fmt.Sprintf("UPDATE %s.%s SET `using_col1`=?,`using_col2`=? WHERE `order_id`=?;", "`order_detail_db_1`", "`order_detail_tab_1`"), 132 Args: []any{"Jack", &sql.NullString{String: "Jerry", Valid: true}, 1}, 133 DB: "order_detail_db_1", 134 Datasource: "0.db.cluster.company.com:3306", 135 }, 136 }, 137 }, 138 { 139 name: "where eq ignore nil val", 140 builder: NewShardingUpdater[OrderDetail](shardingDB2).Update(&OrderDetail{ 141 UsingCol1: "Jack", ItemId: 11, 142 }).SkipNilValue().Where(C("OrderId").EQ(1)), 143 wantQs: []sharding.Query{ 144 { 145 SQL: fmt.Sprintf("UPDATE %s.%s SET `item_id`=?,`using_col1`=? WHERE `order_id`=?;", "`order_detail_db_1`", "`order_detail_tab_1`"), 146 Args: []any{11, "Jack", 1}, 147 DB: "order_detail_db_1", 148 Datasource: "0.db.cluster.company.com:3306", 149 }, 150 }, 151 }, 152 { 153 name: "where or", 154 builder: NewShardingUpdater[Order](shardingDB).Update(&Order{ 155 Content: "1", Account: 1.0, 156 }).Set(Columns("Content", "Account")). 157 Where(C("UserId").EQ(123).Or(C("UserId").EQ(234))), 158 wantQs: []sharding.Query{ 159 { 160 SQL: fmt.Sprintf("UPDATE %s.%s SET `content`=?,`account`=? WHERE (`user_id`=?) OR (`user_id`=?);", "`order_db_1`", "`order_tab_0`"), 161 Args: []any{"1", 1.0, 123, 234}, 162 DB: "order_db_1", 163 Datasource: "0.db.cluster.company.com:3306", 164 }, 165 { 166 SQL: fmt.Sprintf("UPDATE %s.%s SET `content`=?,`account`=? WHERE (`user_id`=?) OR (`user_id`=?);", "`order_db_0`", "`order_tab_0`"), 167 Args: []any{"1", 1.0, 123, 234}, 168 DB: "order_db_0", 169 Datasource: "0.db.cluster.company.com:3306", 170 }, 171 }, 172 }, 173 { 174 name: "where or broadcast", 175 builder: NewShardingUpdater[Order](shardingDB).Update(&Order{ 176 Content: "1", Account: 1.0, 177 }).Set(Columns("Content", "Account")). 178 Where(C("UserId").EQ(123).Or(C("OrderId").EQ(int64(2)))), 179 wantQs: func() []sharding.Query { 180 var res []sharding.Query 181 sql := "UPDATE `%s`.`%s` SET `content`=?,`account`=? WHERE (`user_id`=?) OR (`order_id`=?);" 182 for i := 0; i < dbBase; i++ { 183 dbName := fmt.Sprintf(orderDBPattern, i) 184 for j := 0; j < tableBase; j++ { 185 tableName := fmt.Sprintf(orderTablePattern, j) 186 res = append(res, sharding.Query{ 187 SQL: fmt.Sprintf(sql, dbName, tableName), 188 Args: []any{"1", 1.0, 123, int64(2)}, 189 DB: dbName, 190 Datasource: dsPattern, 191 }) 192 } 193 } 194 return res 195 }(), 196 }, 197 { 198 name: "where and empty", 199 builder: NewShardingUpdater[Order](shardingDB).Update(&Order{ 200 Content: "1", Account: 1.0, 201 }).Set(Columns("Content", "Account")). 202 Where(C("UserId").EQ(123).And(C("UserId").EQ(234))), 203 wantQs: []sharding.Query{}, 204 }, 205 { 206 name: "where and or", 207 builder: NewShardingUpdater[Order](shardingDB).Update(&Order{ 208 Content: "1", Account: 1.0, 209 }).Set(Columns("Content", "Account")). 210 Where(C("UserId").EQ(123).And(C("OrderId").EQ(int64(12))). 211 Or(C("UserId").EQ(234))), 212 wantQs: []sharding.Query{ 213 { 214 SQL: fmt.Sprintf("UPDATE %s.%s SET `content`=?,`account`=? WHERE ((`user_id`=?) AND (`order_id`=?)) OR (`user_id`=?);", "`order_db_1`", "`order_tab_0`"), 215 Args: []any{"1", 1.0, 123, int64(12), 234}, 216 DB: "order_db_1", 217 Datasource: "0.db.cluster.company.com:3306", 218 }, 219 { 220 SQL: fmt.Sprintf("UPDATE %s.%s SET `content`=?,`account`=? WHERE ((`user_id`=?) AND (`order_id`=?)) OR (`user_id`=?);", "`order_db_0`", "`order_tab_0`"), 221 Args: []any{"1", 1.0, 123, int64(12), 234}, 222 DB: "order_db_0", 223 Datasource: "0.db.cluster.company.com:3306", 224 }, 225 }, 226 }, 227 { 228 name: "where or-and", 229 builder: NewShardingUpdater[Order](shardingDB).Update(&Order{ 230 Content: "1", Account: 1.0, 231 }).Set(Columns("Content", "Account")). 232 Where(C("UserId").EQ(123). 233 Or(C("UserId").EQ(181).And(C("UserId").EQ(234)))), 234 wantQs: []sharding.Query{ 235 { 236 SQL: fmt.Sprintf("UPDATE %s.%s SET `content`=?,`account`=? WHERE (`user_id`=?) OR ((`user_id`=?) AND (`user_id`=?));", "`order_db_1`", "`order_tab_0`"), 237 Args: []any{"1", 1.0, 123, 181, 234}, 238 DB: "order_db_1", 239 Datasource: "0.db.cluster.company.com:3306", 240 }, 241 }, 242 }, 243 { 244 name: "where lt", 245 builder: NewShardingUpdater[Order](shardingDB).Update(&Order{ 246 Content: "1", Account: 1.0, 247 }).Set(Columns("Content", "Account")). 248 Where(C("UserId").LT(123)), 249 wantQs: func() []sharding.Query { 250 var res []sharding.Query 251 sql := "UPDATE `%s`.`%s` SET `content`=?,`account`=? WHERE `user_id`<?;" 252 for i := 0; i < dbBase; i++ { 253 dbName := fmt.Sprintf(orderDBPattern, i) 254 for j := 0; j < tableBase; j++ { 255 tableName := fmt.Sprintf(orderTablePattern, j) 256 res = append(res, sharding.Query{ 257 SQL: fmt.Sprintf(sql, dbName, tableName), 258 Args: []any{"1", 1.0, 123}, 259 DB: dbName, 260 Datasource: dsPattern, 261 }) 262 } 263 } 264 return res 265 }(), 266 }, 267 { 268 name: "where lt eq", 269 builder: NewShardingUpdater[Order](shardingDB).Update(&Order{ 270 Content: "1", Account: 1.0, 271 }).Set(Columns("Content", "Account")). 272 Where(C("UserId").LTEQ(123)), 273 wantQs: func() []sharding.Query { 274 var res []sharding.Query 275 sql := "UPDATE `%s`.`%s` SET `content`=?,`account`=? WHERE `user_id`<=?;" 276 for i := 0; i < dbBase; i++ { 277 dbName := fmt.Sprintf(orderDBPattern, i) 278 for j := 0; j < tableBase; j++ { 279 tableName := fmt.Sprintf(orderTablePattern, j) 280 res = append(res, sharding.Query{ 281 SQL: fmt.Sprintf(sql, dbName, tableName), 282 Args: []any{"1", 1.0, 123}, 283 DB: dbName, 284 Datasource: dsPattern, 285 }) 286 } 287 } 288 return res 289 }(), 290 }, 291 { 292 name: "where gt", 293 builder: NewShardingUpdater[Order](shardingDB).Update(&Order{ 294 Content: "1", Account: 1.0, 295 }).Set(Columns("Content", "Account")).Where(C("UserId").GT(123)), 296 wantQs: func() []sharding.Query { 297 var res []sharding.Query 298 sql := "UPDATE `%s`.`%s` SET `content`=?,`account`=? WHERE `user_id`>?;" 299 for i := 0; i < dbBase; i++ { 300 dbName := fmt.Sprintf(orderDBPattern, i) 301 for j := 0; j < tableBase; j++ { 302 tableName := fmt.Sprintf(orderTablePattern, j) 303 res = append(res, sharding.Query{ 304 SQL: fmt.Sprintf(sql, dbName, tableName), 305 Args: []any{"1", 1.0, 123}, 306 DB: dbName, 307 Datasource: dsPattern, 308 }) 309 } 310 } 311 return res 312 }(), 313 }, 314 { 315 name: "where gt eq", 316 builder: NewShardingUpdater[Order](shardingDB).Update(&Order{ 317 Content: "1", Account: 1.0, 318 }).Set(Columns("Content", "Account")).Where(C("UserId").GTEQ(123)), 319 wantQs: func() []sharding.Query { 320 var res []sharding.Query 321 sql := "UPDATE `%s`.`%s` SET `content`=?,`account`=? WHERE `user_id`>=?;" 322 for i := 0; i < dbBase; i++ { 323 dbName := fmt.Sprintf(orderDBPattern, i) 324 for j := 0; j < tableBase; j++ { 325 tableName := fmt.Sprintf(orderTablePattern, j) 326 res = append(res, sharding.Query{ 327 SQL: fmt.Sprintf(sql, dbName, tableName), 328 Args: []any{"1", 1.0, 123}, 329 DB: dbName, 330 Datasource: dsPattern, 331 }) 332 } 333 } 334 return res 335 }(), 336 }, 337 { 338 name: "where eq and lt or gt", 339 builder: NewShardingUpdater[Order](shardingDB).Update(&Order{ 340 Content: "1", Account: 1.0, 341 }).Set(Columns("Content", "Account")). 342 Where(C("UserId").EQ(12).And(C("UserId"). 343 LT(133)).Or(C("UserId").GT(234))), 344 wantQs: func() []sharding.Query { 345 var res []sharding.Query 346 sql := "UPDATE `%s`.`%s` SET `content`=?,`account`=? WHERE ((`user_id`=?) AND (`user_id`<?)) OR (`user_id`>?);" 347 for i := 0; i < dbBase; i++ { 348 dbName := fmt.Sprintf(orderDBPattern, i) 349 for j := 0; j < tableBase; j++ { 350 tableName := fmt.Sprintf(orderTablePattern, j) 351 res = append(res, sharding.Query{ 352 SQL: fmt.Sprintf(sql, dbName, tableName), 353 Args: []any{"1", 1.0, 12, 133, 234}, 354 DB: dbName, 355 Datasource: dsPattern, 356 }) 357 } 358 } 359 return res 360 }(), 361 }, 362 { 363 name: "where in", 364 builder: NewShardingUpdater[Order](shardingDB). 365 Update(&Order{Content: "1", Account: 1.0}). 366 Set(Columns("Content", "Account")). 367 Where(C("UserId").In(12, 35, 101)), 368 wantQs: []sharding.Query{ 369 { 370 SQL: fmt.Sprintf("UPDATE %s.%s SET `content`=?,`account`=? WHERE `user_id` IN (?,?,?);", "`order_db_1`", "`order_tab_2`"), 371 Args: []any{"1", 1.0, 12, 35, 101}, 372 DB: "order_db_1", 373 Datasource: "0.db.cluster.company.com:3306", 374 }, 375 { 376 SQL: fmt.Sprintf("UPDATE %s.%s SET `content`=?,`account`=? WHERE `user_id` IN (?,?,?);", "`order_db_0`", "`order_tab_0`"), 377 Args: []any{"1", 1.0, 12, 35, 101}, 378 DB: "order_db_0", 379 Datasource: "0.db.cluster.company.com:3306", 380 }, 381 }, 382 }, 383 { 384 name: "where in and eq", 385 builder: NewShardingUpdater[Order](shardingDB). 386 Update(&Order{Content: "1", Account: 1.0}). 387 Set(Columns("Content", "Account")). 388 Where(C("UserId").In(12, 35, 101).And(C("UserId").EQ(234))), 389 wantQs: []sharding.Query{ 390 { 391 SQL: fmt.Sprintf("UPDATE %s.%s SET `content`=?,`account`=? WHERE (`user_id` IN (?,?,?)) AND (`user_id`=?);", "`order_db_0`", "`order_tab_0`"), 392 Args: []any{"1", 1.0, 12, 35, 101, 234}, 393 DB: "order_db_0", 394 Datasource: "0.db.cluster.company.com:3306", 395 }, 396 }, 397 }, 398 { 399 name: "where in or eq", 400 builder: NewShardingUpdater[Order](shardingDB). 401 Update(&Order{Content: "1", Account: 1.0}). 402 Set(Columns("Content", "Account")). 403 Where(C("UserId").In(12, 35, 101).Or(C("UserId").EQ(531))), 404 wantQs: []sharding.Query{ 405 { 406 SQL: "UPDATE `order_db_1`.`order_tab_2` SET `content`=?,`account`=? WHERE (`user_id` IN (?,?,?)) OR (`user_id`=?);", 407 Args: []any{"1", 1.0, 12, 35, 101, 531}, 408 DB: "order_db_1", 409 Datasource: "0.db.cluster.company.com:3306", 410 }, 411 { 412 SQL: "UPDATE `order_db_1`.`order_tab_0` SET `content`=?,`account`=? WHERE (`user_id` IN (?,?,?)) OR (`user_id`=?);", 413 Args: []any{"1", 1.0, 12, 35, 101, 531}, 414 DB: "order_db_1", 415 Datasource: "0.db.cluster.company.com:3306", 416 }, 417 { 418 SQL: "UPDATE `order_db_0`.`order_tab_0` SET `content`=?,`account`=? WHERE (`user_id` IN (?,?,?)) OR (`user_id`=?);", 419 Args: []any{"1", 1.0, 12, 35, 101, 531}, 420 DB: "order_db_0", 421 Datasource: "0.db.cluster.company.com:3306", 422 }, 423 }, 424 }, 425 { 426 name: "where not in", 427 builder: NewShardingUpdater[Order](shardingDB). 428 Update(&Order{Content: "1", Account: 1.0}). 429 Set(Columns("Content", "Account")). 430 Where(C("UserId").NotIn(12, 35, 101)), 431 wantQs: func() []sharding.Query { 432 var res []sharding.Query 433 sql := "UPDATE `%s`.`%s` SET `content`=?,`account`=? WHERE `user_id` NOT IN (?,?,?);" 434 for i := 0; i < dbBase; i++ { 435 dbName := fmt.Sprintf(orderDBPattern, i) 436 for j := 0; j < tableBase; j++ { 437 tableName := fmt.Sprintf(orderTablePattern, j) 438 res = append(res, sharding.Query{ 439 SQL: fmt.Sprintf(sql, dbName, tableName), 440 Args: []any{"1", 1.0, 12, 35, 101}, 441 DB: dbName, 442 Datasource: dsPattern, 443 }) 444 } 445 } 446 return res 447 }(), 448 }, 449 { 450 name: "where not gt", 451 builder: NewShardingUpdater[Order](shardingDB). 452 Update(&Order{Content: "1", Account: 1.0}). 453 Set(Columns("Content", "Account")). 454 Where(Not(C("UserId").GT(101))), 455 wantQs: func() []sharding.Query { 456 var res []sharding.Query 457 sql := "UPDATE `%s`.`%s` SET `content`=?,`account`=? WHERE NOT (`user_id`>?);" 458 for i := 0; i < dbBase; i++ { 459 dbName := fmt.Sprintf(orderDBPattern, i) 460 for j := 0; j < tableBase; j++ { 461 tableName := fmt.Sprintf(orderTablePattern, j) 462 res = append(res, sharding.Query{ 463 SQL: fmt.Sprintf(sql, dbName, tableName), 464 Args: []any{"1", 1.0, 101}, 465 DB: dbName, 466 Datasource: dsPattern, 467 }) 468 } 469 } 470 return res 471 }(), 472 }, 473 { 474 name: "where not lt", 475 builder: NewShardingUpdater[Order](shardingDB). 476 Update(&Order{Content: "1", Account: 1.0}). 477 Set(Columns("Content", "Account")). 478 Where(Not(C("UserId").LT(101))), 479 wantQs: func() []sharding.Query { 480 var res []sharding.Query 481 sql := "UPDATE `%s`.`%s` SET `content`=?,`account`=? WHERE NOT (`user_id`<?);" 482 for i := 0; i < dbBase; i++ { 483 dbName := fmt.Sprintf(orderDBPattern, i) 484 for j := 0; j < tableBase; j++ { 485 tableName := fmt.Sprintf(orderTablePattern, j) 486 res = append(res, sharding.Query{ 487 SQL: fmt.Sprintf(sql, dbName, tableName), 488 Args: []any{"1", 1.0, 101}, 489 DB: dbName, 490 Datasource: dsPattern, 491 }) 492 } 493 } 494 return res 495 }(), 496 }, 497 { 498 name: "where not gt eq", 499 builder: NewShardingUpdater[Order](shardingDB). 500 Update(&Order{Content: "1", Account: 1.0}). 501 Set(Columns("Content", "Account")). 502 Where(Not(C("UserId").GTEQ(101))), 503 wantQs: func() []sharding.Query { 504 var res []sharding.Query 505 sql := "UPDATE `%s`.`%s` SET `content`=?,`account`=? WHERE NOT (`user_id`>=?);" 506 for i := 0; i < dbBase; i++ { 507 dbName := fmt.Sprintf(orderDBPattern, i) 508 for j := 0; j < tableBase; j++ { 509 tableName := fmt.Sprintf(orderTablePattern, j) 510 res = append(res, sharding.Query{ 511 SQL: fmt.Sprintf(sql, dbName, tableName), 512 Args: []any{"1", 1.0, 101}, 513 DB: dbName, 514 Datasource: dsPattern, 515 }) 516 } 517 } 518 return res 519 }(), 520 }, 521 { 522 name: "where not lt eq", 523 builder: NewShardingUpdater[Order](shardingDB). 524 Update(&Order{Content: "1", Account: 1.0}). 525 Set(Columns("Content", "Account")). 526 Where(Not(C("UserId").LTEQ(101))), 527 wantQs: func() []sharding.Query { 528 var res []sharding.Query 529 sql := "UPDATE `%s`.`%s` SET `content`=?,`account`=? WHERE NOT (`user_id`<=?);" 530 for i := 0; i < dbBase; i++ { 531 dbName := fmt.Sprintf(orderDBPattern, i) 532 for j := 0; j < tableBase; j++ { 533 tableName := fmt.Sprintf(orderTablePattern, j) 534 res = append(res, sharding.Query{ 535 SQL: fmt.Sprintf(sql, dbName, tableName), 536 Args: []any{"1", 1.0, 101}, 537 DB: dbName, 538 Datasource: dsPattern, 539 }) 540 } 541 } 542 return res 543 }(), 544 }, 545 { 546 name: "where not eq", 547 builder: NewShardingUpdater[Order](shardingDB). 548 Update(&Order{Content: "1", Account: 1.0}). 549 Set(Columns("Content", "Account")). 550 Where(Not(C("UserId").EQ(101))), 551 wantQs: func() []sharding.Query { 552 var res []sharding.Query 553 sql := "UPDATE `%s`.`%s` SET `content`=?,`account`=? WHERE NOT (`user_id`=?);" 554 for i := 0; i < dbBase; i++ { 555 dbName := fmt.Sprintf(orderDBPattern, i) 556 for j := 0; j < tableBase; j++ { 557 tableName := fmt.Sprintf(orderTablePattern, j) 558 res = append(res, sharding.Query{ 559 SQL: fmt.Sprintf(sql, dbName, tableName), 560 Args: []any{"1", 1.0, 101}, 561 DB: dbName, 562 Datasource: dsPattern, 563 }) 564 } 565 } 566 return res 567 }(), 568 }, 569 { 570 name: "where not neq", 571 builder: NewShardingUpdater[Order](shardingDB). 572 Update(&Order{Content: "1", Account: 1.0}). 573 Set(Columns("Content", "Account")). 574 Where(Not(C("UserId").NEQ(101))), 575 wantQs: []sharding.Query{ 576 { 577 SQL: fmt.Sprintf("UPDATE %s.%s SET `content`=?,`account`=? WHERE NOT (`user_id`!=?);", "`order_db_1`", "`order_tab_2`"), 578 Args: []any{"1", 1.0, 101}, 579 DB: "order_db_1", 580 Datasource: "0.db.cluster.company.com:3306", 581 }, 582 }, 583 }, 584 { 585 name: "where between", 586 builder: NewShardingUpdater[Order](shardingDB). 587 Update(&Order{Content: "1", Account: 1.0}). 588 Set(Columns("Content", "Account")). 589 Where(C("UserId").GTEQ(12).And(C("UserId").LTEQ(531))), 590 wantQs: func() []sharding.Query { 591 var res []sharding.Query 592 sql := "UPDATE `%s`.`%s` SET `content`=?,`account`=? WHERE (`user_id`>=?) AND (`user_id`<=?);" 593 for i := 0; i < dbBase; i++ { 594 dbName := fmt.Sprintf(orderDBPattern, i) 595 for j := 0; j < tableBase; j++ { 596 tableName := fmt.Sprintf(orderTablePattern, j) 597 res = append(res, sharding.Query{ 598 SQL: fmt.Sprintf(sql, dbName, tableName), 599 Args: []any{"1", 1.0, 12, 531}, 600 DB: dbName, 601 Datasource: dsPattern, 602 }) 603 } 604 } 605 return res 606 }(), 607 }, 608 } 609 for _, tc := range testCases { 610 t.Run(tc.name, func(t *testing.T) { 611 qs, err := tc.builder.Build(context.Background()) 612 require.Equal(t, tc.wantErr, err) 613 if err != nil { 614 return 615 } 616 assert.ElementsMatch(t, tc.wantQs, qs) 617 }) 618 } 619 } 620 621 func TestShardingUpdater_Build_Error(t *testing.T) { 622 r := model.NewMetaRegistry() 623 dbBase, tableBase, dsBase := 2, 3, 2 624 dbPattern, tablePattern, dsPattern := "order_db_%d", "order_tab_%d", "0.db.cluster.company.com:3306" 625 _, err := r.Register(&Order{}, 626 model.WithTableShardingAlgorithm(&hash.Hash{ 627 ShardingKey: "UserId", 628 DBPattern: &hash.Pattern{Name: dbPattern, Base: dbBase}, 629 TablePattern: &hash.Pattern{Name: tablePattern, Base: tableBase}, 630 DsPattern: &hash.Pattern{Name: dsPattern, Base: dsBase, NotSharding: true}, 631 })) 632 require.NoError(t, err) 633 m := map[string]*masterslave.MasterSlavesDB{ 634 "order_db_0": MasterSlavesMemoryDB(), 635 "order_db_1": MasterSlavesMemoryDB(), 636 "order_db_2": MasterSlavesMemoryDB(), 637 } 638 clusterDB := cluster.NewClusterDB(m) 639 ds := map[string]datasource.DataSource{ 640 "0.db.cluster.company.com:3306": clusterDB, 641 } 642 shardingDB, err := OpenDS("sqlite3", 643 shardingsource.NewShardingDataSource(ds), DBWithMetaRegistry(r)) 644 require.NoError(t, err) 645 testCases := []struct { 646 name string 647 builder sharding.QueryBuilder 648 wantQs []sharding.Query 649 wantErr error 650 }{ 651 { 652 name: "err update sharding key unsupported Columns", 653 builder: NewShardingUpdater[Order](shardingDB). 654 Update(&Order{UserId: 12, Content: "1", Account: 1.0}). 655 Set(Columns("UserId", "Content", "Account")), 656 wantErr: errs.NewErrUpdateShardingKeyUnsupported("UserId"), 657 }, 658 { 659 name: "err update sharding key unsupported Column", 660 builder: NewShardingUpdater[Order](shardingDB). 661 Update(&Order{UserId: 12, Content: "1", Account: 1.0}). 662 Set(C("UserId"), C("Content"), C("Account")), 663 wantErr: errs.NewErrUpdateShardingKeyUnsupported("UserId"), 664 }, 665 { 666 name: "not or left too complex operator", 667 builder: NewShardingUpdater[Order](shardingDB). 668 Update(&Order{Content: "1", Account: 1.0}). 669 Set(Columns("Content", "Account")). 670 Where(Not(C("Content").Like("%kfc").Or(C("OrderId").EQ(101)))), 671 wantErr: errs.NewUnsupportedOperatorError(opLike.Text), 672 }, 673 { 674 name: "not and right too complex operator", 675 builder: NewShardingUpdater[Order](shardingDB). 676 Update(&Order{Content: "1", Account: 1.0}). 677 Set(Columns("Content", "Account")). 678 Where(Not(C("OrderId").EQ(101).And(C("Content").Like("%kfc")))), 679 wantErr: errs.NewUnsupportedOperatorError(opLike.Text), 680 }, 681 { 682 name: "not or right too complex operator", 683 builder: NewShardingUpdater[Order](shardingDB). 684 Update(&Order{Content: "1", Account: 1.0}). 685 Set(Columns("Content", "Account")). 686 Where(Not(C("OrderId").EQ(101).Or(C("Content").Like("%kfc")))), 687 wantErr: errs.NewUnsupportedOperatorError(opLike.Text), 688 }, 689 { 690 name: "invalid field err", 691 builder: NewShardingUpdater[Order](shardingDB). 692 Set(Columns("Content", "ccc")), 693 wantErr: errs.NewInvalidFieldError("ccc"), 694 }, 695 { 696 name: "pointer only err", 697 builder: NewShardingUpdater[int64](shardingDB). 698 Set(Columns("Content", "Account")). 699 Where(Not(C("OrderId").EQ(101).And(C("Content").Like("%kfc")))), 700 wantErr: errs.ErrPointerOnly, 701 }, 702 { 703 name: "too complex operator", 704 builder: NewShardingUpdater[Order](shardingDB). 705 Update(&Order{Content: "1", Account: 1.0}). 706 Set(Columns("Content", "Account")).Where(C("Content").Like("%kfc")), 707 wantErr: errs.NewUnsupportedOperatorError(opLike.Text), 708 }, 709 { 710 name: "too complex expr", 711 builder: NewShardingUpdater[Order](shardingDB). 712 Update(&Order{Content: "1", Account: 1.0}). 713 Set(Columns("Content", "Account")).Where(Avg("UserId").EQ(1)), 714 wantErr: errs.ErrUnsupportedTooComplexQuery, 715 }, 716 { 717 name: "miss sharding key err", 718 builder: func() sharding.QueryBuilder { 719 reg := model.NewMetaRegistry() 720 meta, err := reg.Register(&Order{}, 721 model.WithTableShardingAlgorithm(&hash.Hash{})) 722 require.NoError(t, err) 723 require.NotNil(t, meta.ShardingAlgorithm) 724 db, err := OpenDS("sqlite3", 725 shardingsource.NewShardingDataSource(map[string]datasource.DataSource{ 726 "0.db.cluster.company.com:3306": MasterSlavesMemoryDB(), 727 }), 728 DBWithMetaRegistry(reg)) 729 require.NoError(t, err) 730 s := NewShardingUpdater[Order](db). 731 Update(&Order{Content: "1", Account: 1.0}). 732 Set(Columns("Content", "Account")).Where(C("UserId").EQ(123)) 733 return s 734 }(), 735 wantErr: errs.ErrMissingShardingKey, 736 }, 737 } 738 for _, tc := range testCases { 739 t.Run(tc.name, func(t *testing.T) { 740 qs, err := tc.builder.Build(context.Background()) 741 require.Equal(t, tc.wantErr, err) 742 if err != nil { 743 return 744 } 745 assert.ElementsMatch(t, tc.wantQs, qs) 746 }) 747 } 748 } 749 750 type ShardingUpdaterSuite struct { 751 suite.Suite 752 mock01 sqlmock.Sqlmock 753 mockDB01 *sql.DB 754 mock02 sqlmock.Sqlmock 755 mockDB02 *sql.DB 756 } 757 758 func (s *ShardingUpdaterSuite) SetupSuite() { 759 t := s.T() 760 var err error 761 s.mockDB01, s.mock01, err = sqlmock.New() 762 if err != nil { 763 t.Fatal(err) 764 } 765 s.mockDB02, s.mock02, err = sqlmock.New() 766 if err != nil { 767 t.Fatal(err) 768 } 769 770 } 771 772 func (s *ShardingUpdaterSuite) TearDownTest() { 773 _ = s.mockDB01.Close() 774 _ = s.mockDB02.Close() 775 } 776 777 func (s *ShardingUpdaterSuite) TestShardingUpdater_Exec() { 778 t := s.T() 779 r := model.NewMetaRegistry() 780 dbBase, tableBase := 2, 3 781 dbPattern, tablePattern, dsPattern := "order_db_%d", "order_tab_%d", "0.db.cluster.company.com:3306" 782 _, err := r.Register(&Order{}, 783 model.WithTableShardingAlgorithm(&hash.Hash{ 784 ShardingKey: "UserId", 785 DBPattern: &hash.Pattern{Name: dbPattern, Base: dbBase}, 786 TablePattern: &hash.Pattern{Name: tablePattern, Base: tableBase}, 787 DsPattern: &hash.Pattern{Name: dsPattern, NotSharding: true}, 788 })) 789 require.NoError(t, err) 790 m := map[string]*masterslave.MasterSlavesDB{ 791 "order_db_0": MasterSlavesMockDB(s.mockDB01), 792 "order_db_1": MasterSlavesMockDB(s.mockDB02), 793 } 794 clusterDB := cluster.NewClusterDB(m) 795 ds := map[string]datasource.DataSource{ 796 "0.db.cluster.company.com:3306": clusterDB, 797 } 798 shardingDB, err := OpenDS("sqlite3", 799 shardingsource.NewShardingDataSource(ds), DBWithMetaRegistry(r)) 800 require.NoError(t, err) 801 testCases := []struct { 802 name string 803 exec sharding.Executor 804 mockDB func() 805 wantAffectedRows int64 806 wantErr error 807 }{ 808 { 809 name: "invalid field err", 810 exec: NewShardingUpdater[Order](shardingDB).Update(&Order{ 811 Content: "1", Account: 1.0, 812 }).Set(Columns("Content", "ccc")).Where(C("UserId").EQ(1)), 813 mockDB: func() {}, 814 wantErr: multierr.Combine(errs.NewInvalidFieldError("ccc")), 815 }, 816 { 817 name: "update fail", 818 exec: NewShardingUpdater[Order](shardingDB).Update(&Order{ 819 UserId: 1, OrderId: 1, Content: "1", Account: 1.0, 820 }).Where(C("UserId").EQ(1)), 821 mockDB: func() { 822 s.mock02.ExpectExec(regexp.QuoteMeta("UPDATE `order_db_1`.`order_tab_1` SET `order_id`=?,`content`=?,`account`=? WHERE `user_id`=?;")). 823 WithArgs(int64(1), "1", 1.0, 1).WillReturnError(newMockErr("db")) 824 }, 825 wantErr: multierr.Combine(newMockErr("db")), 826 }, 827 { 828 name: "where eq", 829 exec: NewShardingUpdater[Order](shardingDB).Update(&Order{ 830 UserId: 1, OrderId: 1, Content: "1", Account: 1.0, 831 }).Where(C("UserId").EQ(1)), 832 mockDB: func() { 833 s.mock02.ExpectExec(regexp.QuoteMeta("UPDATE `order_db_1`.`order_tab_1` SET `order_id`=?,`content`=?,`account`=? WHERE `user_id`=?;")). 834 WithArgs(int64(1), "1", 1.0, 1).WillReturnResult(sqlmock.NewResult(1, 1)) 835 }, 836 wantAffectedRows: 1, 837 }, 838 { 839 name: "where or", 840 exec: NewShardingUpdater[Order](shardingDB).Update(&Order{ 841 Content: "1", Account: 1.0, 842 }).Set(Columns("Content", "Account")). 843 Where(C("UserId").EQ(123).Or(C("UserId").EQ(234))), 844 mockDB: func() { 845 s.mock02.ExpectExec(regexp.QuoteMeta("UPDATE `order_db_1`.`order_tab_0` SET `content`=?,`account`=? WHERE (`user_id`=?) OR (`user_id`=?);")). 846 WithArgs("1", 1.0, 123, 234).WillReturnResult(sqlmock.NewResult(1, 2)) 847 s.mock01.ExpectExec(regexp.QuoteMeta("UPDATE `order_db_0`.`order_tab_0` SET `content`=?,`account`=? WHERE (`user_id`=?) OR (`user_id`=?);")). 848 WithArgs("1", 1.0, 123, 234).WillReturnResult(sqlmock.NewResult(1, 2)) 849 }, 850 wantAffectedRows: 4, 851 }, 852 } 853 for _, tc := range testCases { 854 t.Run(tc.name, func(t *testing.T) { 855 tc.mockDB() 856 res := tc.exec.Exec(context.Background()) 857 require.Equal(t, tc.wantErr, res.Err()) 858 if res.Err() != nil { 859 return 860 } 861 862 affectRows, err := res.RowsAffected() 863 require.NoError(t, err) 864 assert.Equal(t, tc.wantAffectedRows, affectRows) 865 }) 866 } 867 } 868 869 func TestShardingUpdaterSuite(t *testing.T) { 870 suite.Run(t, &ShardingUpdaterSuite{}) 871 } 872 873 func ExampleShardingUpdater_SkipNilValue() { 874 r := model.NewMetaRegistry() 875 _, _ = r.Register(&OrderDetail{}, 876 model.WithTableShardingAlgorithm(&hash.Hash{ 877 ShardingKey: "OrderId", 878 DBPattern: &hash.Pattern{Name: "order_detail_db_%d", Base: 2}, 879 TablePattern: &hash.Pattern{Name: "order_detail_tab_%d", Base: 3}, 880 DsPattern: &hash.Pattern{Name: "0.db.cluster.company.com:3306", NotSharding: true}, 881 })) 882 m := map[string]*masterslave.MasterSlavesDB{ 883 "order_detail_db_1": MasterSlavesMemoryDB(), 884 } 885 clusterDB := cluster.NewClusterDB(m) 886 ds := map[string]datasource.DataSource{ 887 "0.db.cluster.company.com:3306": clusterDB, 888 } 889 shardingDB, _ := OpenDS("sqlite3", 890 shardingsource.NewShardingDataSource(ds), DBWithMetaRegistry(r)) 891 query, _ := NewShardingUpdater[OrderDetail](shardingDB).Update(&OrderDetail{ 892 UsingCol1: "Jack", ItemId: 11, 893 }).SkipNilValue().Where(C("OrderId").EQ(1)).Build(context.Background()) 894 fmt.Println(query[0].String()) 895 896 // Output: 897 // SQL: UPDATE `order_detail_db_1`.`order_detail_tab_1` SET `item_id`=?,`using_col1`=? WHERE `order_id`=?; 898 // Args: []interface {}{11, "Jack", 1} 899 } 900 901 func ExampleShardingUpdater_SkipZeroValue() { 902 r := model.NewMetaRegistry() 903 _, _ = r.Register(&OrderDetail{}, 904 model.WithTableShardingAlgorithm(&hash.Hash{ 905 ShardingKey: "OrderId", 906 DBPattern: &hash.Pattern{Name: "order_detail_db_%d", Base: 2}, 907 TablePattern: &hash.Pattern{Name: "order_detail_tab_%d", Base: 3}, 908 DsPattern: &hash.Pattern{Name: "0.db.cluster.company.com:3306", NotSharding: true}, 909 })) 910 m := map[string]*masterslave.MasterSlavesDB{ 911 "order_detail_db_1": MasterSlavesMemoryDB(), 912 } 913 clusterDB := cluster.NewClusterDB(m) 914 ds := map[string]datasource.DataSource{ 915 "0.db.cluster.company.com:3306": clusterDB, 916 } 917 shardingDB, _ := OpenDS("sqlite3", 918 shardingsource.NewShardingDataSource(ds), DBWithMetaRegistry(r)) 919 query, _ := NewShardingUpdater[OrderDetail](shardingDB).Update(&OrderDetail{ 920 UsingCol1: "Jack", 921 }).SkipZeroValue().Where(C("OrderId").EQ(1)).Build(context.Background()) 922 fmt.Println(query[0].String()) 923 924 // Output: 925 // SQL: UPDATE `order_detail_db_1`.`order_detail_tab_1` SET `using_col1`=? WHERE `order_id`=?; 926 // Args: []interface {}{"Jack", 1} 927 } 928 929 type OrderDetail struct { 930 OrderId int `eorm:"auto_increment,primary_key"` 931 ItemId int 932 UsingCol1 string 933 UsingCol2 *sql.NullString 934 }