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  }