vitess.io/vitess@v0.16.2/go/test/endtoend/vtgate/prefixfanout/main_test.go (about) 1 /* 2 Copyright 2021 The Vitess Authors. 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 prefixfanout 16 17 import ( 18 "context" 19 "flag" 20 "fmt" 21 "os" 22 "testing" 23 24 "vitess.io/vitess/go/test/endtoend/utils" 25 26 "github.com/stretchr/testify/assert" 27 "github.com/stretchr/testify/require" 28 29 "vitess.io/vitess/go/mysql" 30 "vitess.io/vitess/go/test/endtoend/cluster" 31 ) 32 33 var ( 34 clusterInstance *cluster.LocalProcessCluster 35 cell = "zone1" 36 hostname = "localhost" 37 38 sKs = "cfc_testing" 39 sSchema = ` 40 CREATE TABLE t1 ( 41 c1 VARCHAR(20) NOT NULL, 42 c2 varchar(40) NOT NULL, 43 PRIMARY KEY (c1) 44 ) ENGINE=Innodb; 45 ` 46 sVSchema = ` 47 { 48 "sharded": true, 49 "vindexes": { 50 "cfc": { 51 "type": "cfc" 52 } 53 }, 54 "tables": { 55 "t1": { 56 "column_vindexes": [ 57 { 58 "column": "c1", 59 "name": "cfc" 60 } 61 ], 62 "columns": [ 63 { 64 "name": "c2", 65 "type": "VARCHAR" 66 } 67 ] 68 } 69 } 70 }` 71 72 sKsMD5 = `cfc_testing_md5` 73 sSchemaMD5 = ` 74 CREATE TABLE t2 ( 75 c1 VARCHAR(20) NOT NULL, 76 c2 varchar(40) NOT NULL, 77 PRIMARY KEY (c1) 78 ) ENGINE=Innodb;` 79 80 sVSchemaMD5 = ` 81 { 82 "sharded": true, 83 "vindexes": { 84 "cfc_md5": { 85 "type": "cfc", 86 "params": { 87 "hash": "md5", 88 "offsets": "[2]" 89 } 90 } 91 }, 92 "tables": { 93 "t2": { 94 "column_vindexes": [ 95 { 96 "column": "c1", 97 "name": "cfc_md5" 98 } 99 ], 100 "columns": [ 101 { 102 "name": "c2", 103 "type": "VARCHAR" 104 } 105 ] 106 } 107 } 108 }` 109 ) 110 111 func TestMain(m *testing.M) { 112 defer cluster.PanicHandler(nil) 113 flag.Parse() 114 115 exitCode := func() int { 116 clusterInstance = cluster.NewCluster(cell, hostname) 117 defer clusterInstance.Teardown() 118 119 // Start topo server 120 if err := clusterInstance.StartTopo(); err != nil { 121 return 1 122 } 123 124 // Start keyspace 125 sKeyspace := &cluster.Keyspace{ 126 Name: sKs, 127 SchemaSQL: sSchema, 128 VSchema: sVSchema, 129 } 130 // cfc_testing 131 if err := clusterInstance.StartKeyspace(*sKeyspace, []string{"-41", "41-4180", "4180-42", "42-"}, 0, false); err != nil { 132 return 1 133 } 134 // cfc_testing_md5 135 if err := clusterInstance.StartKeyspace( 136 cluster.Keyspace{ 137 Name: sKsMD5, 138 SchemaSQL: sSchemaMD5, 139 VSchema: sVSchemaMD5, 140 }, []string{"-c2", "c2-c20a80", "c20a80-d0", "d0-"}, 0, false); err != nil { 141 return 1 142 } 143 144 // Start vtgate 145 // This waits for the vtgate process to be healthy 146 if err := clusterInstance.StartVtgate(); err != nil { 147 return 1 148 } 149 150 // Wait for the cluster to be running and healthy 151 if err := clusterInstance.WaitForTabletsToHealthyInVtgate(); err != nil { 152 return 1 153 } 154 155 return m.Run() 156 }() 157 os.Exit(exitCode) 158 } 159 160 func TestCFCPrefixQueryNoHash(t *testing.T) { 161 defer cluster.PanicHandler(t) 162 ctx := context.Background() 163 vtParams := clusterInstance.GetVTParams(sKs) 164 conn, err := mysql.Connect(ctx, &vtParams) 165 require.Nil(t, err) 166 defer conn.Close() 167 168 utils.Exec(t, conn, "delete from t1") 169 defer utils.Exec(t, conn, "delete from t1") 170 // prepare the sentinel rows, i.e. every shard stores a row begins with letter A. 171 // hex ascii code of 'A' is 41. For a given primary key, e.g. 'AA' here, it should 172 // only legally belong to a single shard. We insert into all shards with different 173 // `c2` value so that we can test if a query fans out to all or not. Based on the 174 // following shard layout only "41-4180", "4180-42" should serve the rows staring with 'A'. 175 shards := []string{"-41", "41-4180", "4180-42", "42-"} 176 for i, s := range shards { 177 utils.Exec(t, conn, fmt.Sprintf("use `%s:%s`", sKs, s)) 178 utils.Exec(t, conn, fmt.Sprintf("insert into t1 values('AA', 'shard-%d')", i)) 179 } 180 utils.Exec(t, conn, "use cfc_testing") 181 qr := utils.Exec(t, conn, "select c2 from t1 where c1 like 'A%' order by c2") 182 assert.Equal(t, 2, len(qr.Rows)) 183 // should only target a subset of shards serving rows starting with 'A'. 184 assert.EqualValues(t, `[[VARCHAR("shard-1")] [VARCHAR("shard-2")]]`, fmt.Sprintf("%v", qr.Rows)) 185 // should only target a subset of shards serving rows starting with 'AA', 186 // the shards to which 'AA' maps to. 187 qr = utils.Exec(t, conn, "select c2 from t1 where c1 like 'AA'") 188 assert.Equal(t, 1, len(qr.Rows)) 189 assert.EqualValues(t, `[[VARCHAR("shard-1")]]`, fmt.Sprintf("%v", qr.Rows)) 190 // fan out to all when there is no prefix 191 qr = utils.Exec(t, conn, "select c2 from t1 where c1 like '%A' order by c2") 192 assert.Equal(t, 4, len(qr.Rows)) 193 for i, r := range qr.Rows { 194 assert.Equal(t, fmt.Sprintf("shard-%d", i), r[0].ToString()) 195 } 196 } 197 198 func TestCFCPrefixQueryWithHash(t *testing.T) { 199 defer cluster.PanicHandler(t) 200 ctx := context.Background() 201 vtParams := clusterInstance.GetVTParams(sKsMD5) 202 203 conn, err := mysql.Connect(ctx, &vtParams) 204 require.Nil(t, err) 205 defer conn.Close() 206 207 utils.Exec(t, conn, "delete from t2") 208 defer utils.Exec(t, conn, "delete from t2") 209 210 shards := []string{"-c2", "c2-c20a80", "c20a80-d0", "d0-"} 211 // same idea of sentinel rows as above. Even though each row legally belongs to 212 // only one shard, we insert into all shards with different info to test our fan out. 213 for i, s := range shards { 214 utils.Exec(t, conn, fmt.Sprintf("use `%s:%s`", sKsMD5, s)) 215 utils.Exec(t, conn, fmt.Sprintf("insert into t2 values('12AX', 'shard-%d')", i)) 216 utils.Exec(t, conn, fmt.Sprintf("insert into t2 values('12BX', 'shard-%d')", i)) 217 utils.Exec(t, conn, fmt.Sprintf("insert into t2 values('27CX', 'shard-%d')", i)) 218 } 219 220 utils.Exec(t, conn, fmt.Sprintf("use `%s`", sKsMD5)) 221 // The prefix is ('12', 'A') 222 // md5('12') -> c20ad4d76fe97759aa27a0c99bff6710 223 // md5('A') -> 7fc56270e7a70fa81a5935b72eacbe29 224 // so keyspace id is c20a7f, which means shards "c2-c20a80" 225 qr := utils.Exec(t, conn, "select c2 from t2 where c1 like '12A%' order by c2") 226 assert.Equal(t, 1, len(qr.Rows)) 227 assert.Equal(t, `[[VARCHAR("shard-1")]]`, fmt.Sprintf("%v", qr.Rows)) 228 // The prefix is ('12') 229 // md5('12') -> c20ad4d76fe97759aa27a0c99bff6710 so the corresponding 230 // so keyspace id is c20a, which means shards "c2-c20a80", "c20a80-d0" 231 qr = utils.Exec(t, conn, "select c2 from t2 where c1 like '12%' order by c2") 232 assert.Equal(t, 4, len(qr.Rows)) 233 assert.Equal(t, `[[VARCHAR("shard-1")] [VARCHAR("shard-1")] [VARCHAR("shard-2")] [VARCHAR("shard-2")]]`, fmt.Sprintf("%v", qr.Rows)) 234 // in vschema the prefix length is defined as 2 bytes however only 1 byte 235 // is provided here so the query fans out to all. 236 qr = utils.Exec(t, conn, "select c2 from t2 where c1 like '2%' order by c2") 237 assert.Equal(t, 4, len(qr.Rows)) 238 assert.Equal(t, `[[VARCHAR("shard-0")] [VARCHAR("shard-1")] [VARCHAR("shard-2")] [VARCHAR("shard-3")]]`, fmt.Sprintf("%v", qr.Rows)) 239 } 240 241 func TestCFCInsert(t *testing.T) { 242 defer cluster.PanicHandler(t) 243 ctx := context.Background() 244 245 vtParams := clusterInstance.GetVTParams(sKs) 246 conn, err := mysql.Connect(ctx, &vtParams) 247 require.Nil(t, err) 248 defer conn.Close() 249 250 utils.Exec(t, conn, "delete from t1") 251 defer utils.Exec(t, conn, "delete from t1") 252 253 utils.Exec(t, conn, "insert into t1 (c1, c2) values ('AAA', 'BBB')") 254 qr := utils.Exec(t, conn, "select c2 from t1 where c1 like 'A%'") 255 assert.Equal(t, 1, len(qr.Rows)) 256 shards := []string{"-41", "4180-42", "42-"} 257 for _, s := range shards { 258 utils.Exec(t, conn, fmt.Sprintf("use `cfc_testing:%s`", s)) 259 qr = utils.Exec(t, conn, "select * from t1") 260 assert.Equal(t, 0, len(qr.Rows)) 261 } 262 // 'AAA' belongs to 41-4180 263 utils.Exec(t, conn, "use `cfc_testing:41-4180`") 264 qr = utils.Exec(t, conn, "select c2 from t1") 265 assert.Equal(t, 1, len(qr.Rows)) 266 assert.Equal(t, `[[VARCHAR("BBB")]]`, fmt.Sprintf("%v", qr.Rows)) 267 }