github.com/ecodeclub/eorm@v0.0.2-0.20231001112437-dae71da914d0/internal/integration/sharding_suite_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 //go:build e2e 16 17 package integration 18 19 import ( 20 "context" 21 "database/sql" 22 "database/sql/driver" 23 "fmt" 24 "log" 25 "testing" 26 "time" 27 28 "github.com/ecodeclub/eorm" 29 "github.com/ecodeclub/eorm/internal/datasource" 30 "github.com/ecodeclub/eorm/internal/datasource/cluster" 31 "github.com/ecodeclub/eorm/internal/datasource/masterslave" 32 "github.com/ecodeclub/eorm/internal/datasource/masterslave/slaves" 33 "github.com/ecodeclub/eorm/internal/datasource/masterslave/slaves/roundrobin" 34 "github.com/ecodeclub/eorm/internal/datasource/shardingsource" 35 "github.com/ecodeclub/eorm/internal/model" 36 operator "github.com/ecodeclub/eorm/internal/operator" 37 "github.com/ecodeclub/eorm/internal/sharding" 38 "github.com/ecodeclub/eorm/internal/sharding/hash" 39 "github.com/ecodeclub/eorm/internal/test" 40 "github.com/stretchr/testify/require" 41 "github.com/stretchr/testify/suite" 42 ) 43 44 type ShardingSuite struct { 45 suite.Suite 46 slaves slaves.Slaves 47 clusters *clusterDrivers 48 shardingDB *eorm.DB 49 algorithm sharding.Algorithm 50 dataSources map[string]datasource.DataSource 51 driver string 52 53 DBPattern string 54 DsPattern string 55 TablePattern string 56 ShardingKey string 57 } 58 59 func newDefaultShardingSuite() ShardingSuite { 60 m := []*masterSalvesDriver{ 61 { 62 masterdsn: "root:root@tcp(localhost:13307)/order_detail_db_0?multiStatements=true&interpolateParams=true", 63 slavedsns: []string{"root:root@tcp(localhost:13308)/order_detail_db_0?multiStatements=true&interpolateParams=true"}, 64 }, 65 { 66 masterdsn: "root:root@tcp(localhost:13307)/order_detail_db_1?multiStatements=true&interpolateParams=true", 67 slavedsns: []string{"root:root@tcp(localhost:13308)/order_detail_db_1?multiStatements=true&interpolateParams=true"}, 68 }, 69 } 70 clusterDr := &clusterDriver{msDrivers: m} 71 dbPattern := "order_detail_db_%d" 72 dsPattern := "root:root@tcp(localhost:13307).%d" 73 tablePattern := "order_detail_tab_%d" 74 return ShardingSuite{ 75 driver: "mysql", 76 DBPattern: dbPattern, 77 DsPattern: dsPattern, 78 TablePattern: tablePattern, 79 clusters: &clusterDrivers{ 80 clDrivers: []*clusterDriver{clusterDr}, 81 }, 82 } 83 } 84 85 // TearDownSuite 会把所有的表清空 86 func (s *ShardingSuite) TearDownSuite() { 87 t := s.T() 88 dsts := s.algorithm.Broadcast(context.Background()) 89 for _, dst := range dsts { 90 tbl := fmt.Sprintf("`%s`.`%s`", dst.DB, dst.Table) 91 source, ok := s.dataSources[dst.Name] 92 require.True(t, ok) 93 _, err := source.Exec(context.Background(), 94 datasource.Query{ 95 SQL: fmt.Sprintf("TRUNCATE TABLE %s", tbl), 96 DB: dst.DB, 97 }) 98 if err != nil { 99 t.Fatal(err) 100 } 101 } 102 } 103 104 func (s *ShardingSuite) openDB(dvr, dsn string) (*sql.DB, error) { 105 db, err := sql.Open(dvr, dsn) 106 err = db.Ping() 107 for err == driver.ErrBadConn { 108 log.Printf("等待数据库启动...") 109 err = db.Ping() 110 time.Sleep(time.Second) 111 } 112 return db, err 113 } 114 115 func (s *ShardingSuite) initDB(r model.MetaRegistry) (*eorm.DB, error) { 116 clDrivers := s.clusters.clDrivers 117 sourceMap := make(map[string]datasource.DataSource, len(clDrivers)) 118 for i, clus := range clDrivers { 119 msMap := make(map[string]*masterslave.MasterSlavesDB, 8) 120 for j, d := range clus.msDrivers { 121 master, err := s.openDB(s.driver, d.masterdsn) 122 if err != nil { 123 return nil, err 124 } 125 ss := make([]*sql.DB, 0, len(d.slavedsns)) 126 for _, slavedsn := range d.slavedsns { 127 slave, err := s.openDB(s.driver, slavedsn) 128 if err != nil { 129 return nil, err 130 } 131 ss = append(ss, slave) 132 } 133 sl, err := roundrobin.NewSlaves(ss...) 134 require.NoError(s.T(), err) 135 s.slaves = &testBaseSlaves{Slaves: sl} 136 masterSlaveDB := masterslave.NewMasterSlavesDB( 137 master, 138 masterslave.MasterSlavesWithSlaves(s.slaves), 139 ) 140 dbName := fmt.Sprintf(s.DBPattern, j) 141 msMap[dbName] = masterSlaveDB 142 } 143 sourceName := fmt.Sprintf(s.DsPattern, i) 144 sourceMap[sourceName] = cluster.NewClusterDB(msMap) 145 } 146 s.dataSources = sourceMap 147 dataSource := shardingsource.NewShardingDataSource(sourceMap) 148 return eorm.OpenDS(s.driver, dataSource, eorm.DBWithMetaRegistry(r)) 149 } 150 151 func (s *ShardingSuite) SetupSuite() { 152 t := s.T() 153 r := model.NewMetaRegistry() 154 sk := "OrderId" 155 s.algorithm = &hash.Hash{ 156 ShardingKey: sk, 157 DBPattern: &hash.Pattern{Name: s.DBPattern, Base: 2}, 158 TablePattern: &hash.Pattern{Name: s.TablePattern, Base: 3}, 159 DsPattern: &hash.Pattern{Name: "root:root@tcp(localhost:13307).0", NotSharding: true}, 160 } 161 s.ShardingKey = sk 162 _, err := r.Register(&test.OrderDetail{}, 163 model.WithTableShardingAlgorithm(s.algorithm)) 164 db, err := s.initDB(r) 165 require.NoError(t, err) 166 s.shardingDB = db 167 } 168 169 type ShardingSelectUpdateInsertSuite struct { 170 ShardingSuite 171 data []*test.OrderDetail 172 } 173 174 func newShardingSelectUpdateInsertSuite() ShardingSelectUpdateInsertSuite { 175 return ShardingSelectUpdateInsertSuite{ 176 ShardingSuite: newDefaultShardingSuite(), 177 data: []*test.OrderDetail{ 178 {OrderId: 8, ItemId: 6, UsingCol1: "Kobe", UsingCol2: "Bryant"}, 179 {OrderId: 11, ItemId: 8, UsingCol1: "James", UsingCol2: "Harden"}, 180 {OrderId: 123, ItemId: 10, UsingCol1: "LeBron", UsingCol2: "James"}, 181 {OrderId: 234, ItemId: 12, UsingCol1: "Kevin", UsingCol2: "Durant"}, 182 {OrderId: 253, ItemId: 8, UsingCol1: "Stephen", UsingCol2: "Curry"}, 183 {OrderId: 181, ItemId: 11, UsingCol1: "Kawhi", UsingCol2: "Leonard"}, 184 }, 185 } 186 } 187 188 func (s *ShardingSelectUpdateInsertSuite) SetupSuite() { 189 t := s.T() 190 s.ShardingSuite.SetupSuite() 191 for _, item := range s.data { 192 shardingRes, err := s.algorithm.Sharding( 193 context.Background(), sharding.Request{Op: operator.OpEQ, SkValues: map[string]any{s.ShardingKey: item.OrderId}}) 194 require.NoError(t, err) 195 require.NotNil(t, shardingRes.Dsts) 196 for _, dst := range shardingRes.Dsts { 197 tbl := fmt.Sprintf("`%s`.`%s`", dst.DB, dst.Table) 198 sql := fmt.Sprintf("INSERT INTO %s (`order_id`,`item_id`,`using_col1`,`using_col2`) VALUES(?,?,?,?);", tbl) 199 args := []any{item.OrderId, item.ItemId, item.UsingCol1, item.UsingCol2} 200 source, ok := s.dataSources[dst.Name] 201 require.True(t, ok) 202 _, err := source.Exec(context.Background(), datasource.Query{SQL: sql, Args: args, DB: dst.DB}) 203 if err != nil { 204 t.Fatal(err) 205 } 206 } 207 } 208 // 防止主从延迟 209 time.Sleep(1) 210 } 211 212 func (s *ShardingSelectUpdateInsertSuite) findTgt(t *testing.T, values []*test.OrderDetail) []*test.OrderDetail { 213 od := values[0] 214 pre := eorm.C(s.ShardingKey).EQ(od.OrderId) 215 for i := 1; i < len(values); i++ { 216 od = values[i] 217 pre = pre.Or(eorm.C(s.ShardingKey).EQ(od.OrderId)) 218 } 219 querySet, err := eorm.NewShardingSelector[test.OrderDetail](s.shardingDB). 220 Where(pre).GetMulti(masterslave.UseMaster(context.Background())) 221 require.NoError(t, err) 222 return querySet 223 } 224 225 func (s *ShardingSelectUpdateInsertSuite) TearDownSuite() { 226 t := s.T() 227 for _, item := range s.data { 228 shardingRes, err := s.algorithm.Sharding( 229 context.Background(), sharding.Request{Op: operator.OpEQ, SkValues: map[string]any{"OrderId": item.OrderId}}) 230 require.NoError(t, err) 231 require.NotNil(t, shardingRes.Dsts) 232 for _, dst := range shardingRes.Dsts { 233 tbl := fmt.Sprintf("`%s`.`%s`", dst.DB, dst.Table) 234 sql := fmt.Sprintf("DELETE FROM %s", tbl) 235 source, ok := s.dataSources[dst.Name] 236 require.True(t, ok) 237 _, err := source.Exec(context.Background(), datasource.Query{SQL: sql, DB: dst.DB}) 238 if err != nil { 239 t.Fatal(err) 240 } 241 } 242 } 243 }