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  }