vitess.io/vitess@v0.16.2/go/vt/wrangler/vdiff_test.go (about) 1 /* 2 Copyright 2019 The Vitess Authors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package wrangler 18 19 import ( 20 "strings" 21 "testing" 22 "time" 23 24 "vitess.io/vitess/go/mysql/collations" 25 "vitess.io/vitess/go/vt/sqlparser" 26 "vitess.io/vitess/go/vt/topo" 27 "vitess.io/vitess/go/vt/vtgate/engine" 28 29 "context" 30 31 "github.com/stretchr/testify/assert" 32 "github.com/stretchr/testify/require" 33 34 "vitess.io/vitess/go/sqltypes" 35 binlogdatapb "vitess.io/vitess/go/vt/proto/binlogdata" 36 tabletmanagerdatapb "vitess.io/vitess/go/vt/proto/tabletmanagerdata" 37 ) 38 39 func TestVDiffPlanSuccess(t *testing.T) { 40 schm := &tabletmanagerdatapb.SchemaDefinition{ 41 TableDefinitions: []*tabletmanagerdatapb.TableDefinition{{ 42 Name: "t1", 43 Columns: []string{"c1", "c2"}, 44 PrimaryKeyColumns: []string{"c1"}, 45 Fields: sqltypes.MakeTestFields("c1|c2", "int64|int64"), 46 }, { 47 Name: "nonpktext", 48 Columns: []string{"c1", "textcol"}, 49 PrimaryKeyColumns: []string{"c1"}, 50 Fields: sqltypes.MakeTestFields("c1|textcol", "int64|varchar"), 51 }, { 52 Name: "pktext", 53 Columns: []string{"textcol", "c2"}, 54 PrimaryKeyColumns: []string{"textcol"}, 55 Fields: sqltypes.MakeTestFields("textcol|c2", "varchar|int64"), 56 }, { 57 Name: "multipk", 58 Columns: []string{"c1", "c2"}, 59 PrimaryKeyColumns: []string{"c1", "c2"}, 60 Fields: sqltypes.MakeTestFields("c1|c2", "int64|int64"), 61 }, { 62 Name: "aggr", 63 Columns: []string{"c1", "c2", "c3", "c4"}, 64 PrimaryKeyColumns: []string{"c1"}, 65 Fields: sqltypes.MakeTestFields("c1|c2|c3|c4", "int64|int64|int64|int64"), 66 }, { 67 Name: "datze", 68 Columns: []string{"id", "dt"}, 69 PrimaryKeyColumns: []string{"id"}, 70 Fields: sqltypes.MakeTestFields("id|dt", "int64|datetime"), 71 }}, 72 } 73 74 testcases := []struct { 75 input *binlogdatapb.Rule 76 table string 77 td *tableDiffer 78 sourceTimeZone string 79 }{{ 80 input: &binlogdatapb.Rule{ 81 Match: "t1", 82 }, 83 table: "t1", 84 td: &tableDiffer{ 85 targetTable: "t1", 86 sourceExpression: "select c1, c2 from t1 order by c1 asc", 87 targetExpression: "select c1, c2 from t1 order by c1 asc", 88 compareCols: []compareColInfo{{0, collations.Collation(nil), true}, {1, collations.Collation(nil), false}}, 89 comparePKs: []compareColInfo{{0, collations.Collation(nil), true}}, 90 pkCols: []int{0}, 91 selectPks: []int{0}, 92 sourcePrimitive: newMergeSorter(nil, []compareColInfo{{0, collations.Collation(nil), true}}), 93 targetPrimitive: newMergeSorter(nil, []compareColInfo{{0, collations.Collation(nil), true}}), 94 }, 95 }, { 96 input: &binlogdatapb.Rule{ 97 Match: "t1", 98 Filter: "-80", 99 }, 100 table: "t1", 101 td: &tableDiffer{ 102 targetTable: "t1", 103 sourceExpression: "select c1, c2 from t1 order by c1 asc", 104 targetExpression: "select c1, c2 from t1 order by c1 asc", 105 compareCols: []compareColInfo{{0, collations.Collation(nil), true}, {1, collations.Collation(nil), false}}, 106 comparePKs: []compareColInfo{{0, collations.Collation(nil), true}}, 107 pkCols: []int{0}, 108 selectPks: []int{0}, 109 sourcePrimitive: newMergeSorter(nil, []compareColInfo{{0, collations.Collation(nil), true}}), 110 targetPrimitive: newMergeSorter(nil, []compareColInfo{{0, collations.Collation(nil), true}}), 111 }, 112 }, { 113 input: &binlogdatapb.Rule{ 114 Match: "t1", 115 Filter: "select * from t1", 116 }, 117 table: "t1", 118 td: &tableDiffer{ 119 targetTable: "t1", 120 sourceExpression: "select c1, c2 from t1 order by c1 asc", 121 targetExpression: "select c1, c2 from t1 order by c1 asc", 122 compareCols: []compareColInfo{{0, collations.Collation(nil), true}, {1, collations.Collation(nil), false}}, 123 comparePKs: []compareColInfo{{0, collations.Collation(nil), true}}, 124 pkCols: []int{0}, 125 selectPks: []int{0}, 126 sourcePrimitive: newMergeSorter(nil, []compareColInfo{{0, collations.Collation(nil), true}}), 127 targetPrimitive: newMergeSorter(nil, []compareColInfo{{0, collations.Collation(nil), true}}), 128 }, 129 }, { 130 input: &binlogdatapb.Rule{ 131 Match: "t1", 132 Filter: "select c2, c1 from t1", 133 }, 134 table: "t1", 135 td: &tableDiffer{ 136 targetTable: "t1", 137 sourceExpression: "select c2, c1 from t1 order by c1 asc", 138 targetExpression: "select c2, c1 from t1 order by c1 asc", 139 compareCols: []compareColInfo{{0, collations.Collation(nil), false}, {1, collations.Collation(nil), true}}, 140 comparePKs: []compareColInfo{{1, collations.Collation(nil), true}}, 141 pkCols: []int{1}, 142 selectPks: []int{1}, 143 sourcePrimitive: newMergeSorter(nil, []compareColInfo{{1, collations.Collation(nil), true}}), 144 targetPrimitive: newMergeSorter(nil, []compareColInfo{{1, collations.Collation(nil), true}}), 145 }, 146 }, { 147 input: &binlogdatapb.Rule{ 148 Match: "t1", 149 Filter: "select c0 as c1, c2 from t2", 150 }, 151 table: "t1", 152 td: &tableDiffer{ 153 targetTable: "t1", 154 sourceExpression: "select c0 as c1, c2 from t2 order by c1 asc", 155 targetExpression: "select c1, c2 from t1 order by c1 asc", 156 compareCols: []compareColInfo{{0, collations.Collation(nil), true}, {1, collations.Collation(nil), false}}, 157 comparePKs: []compareColInfo{{0, collations.Collation(nil), true}}, 158 pkCols: []int{0}, 159 selectPks: []int{0}, 160 sourcePrimitive: newMergeSorter(nil, []compareColInfo{{0, collations.Collation(nil), true}}), 161 targetPrimitive: newMergeSorter(nil, []compareColInfo{{0, collations.Collation(nil), true}}), 162 }, 163 }, { 164 // non-pk text column. 165 input: &binlogdatapb.Rule{ 166 Match: "nonpktext", 167 Filter: "select c1, textcol from nonpktext", 168 }, 169 table: "nonpktext", 170 td: &tableDiffer{ 171 targetTable: "nonpktext", 172 sourceExpression: "select c1, textcol from nonpktext order by c1 asc", 173 targetExpression: "select c1, textcol from nonpktext order by c1 asc", 174 compareCols: []compareColInfo{{0, collations.Collation(nil), true}, {1, collations.Collation(nil), false}}, 175 comparePKs: []compareColInfo{{0, collations.Collation(nil), true}}, 176 pkCols: []int{0}, 177 selectPks: []int{0}, 178 sourcePrimitive: newMergeSorter(nil, []compareColInfo{{0, collations.Collation(nil), true}}), 179 targetPrimitive: newMergeSorter(nil, []compareColInfo{{0, collations.Collation(nil), true}}), 180 }, 181 }, { 182 // non-pk text column, different order. 183 input: &binlogdatapb.Rule{ 184 Match: "nonpktext", 185 Filter: "select textcol, c1 from nonpktext", 186 }, 187 table: "nonpktext", 188 td: &tableDiffer{ 189 targetTable: "nonpktext", 190 sourceExpression: "select textcol, c1 from nonpktext order by c1 asc", 191 targetExpression: "select textcol, c1 from nonpktext order by c1 asc", 192 compareCols: []compareColInfo{{0, collations.Collation(nil), false}, {1, collations.Collation(nil), true}}, 193 comparePKs: []compareColInfo{{1, collations.Collation(nil), true}}, 194 pkCols: []int{1}, 195 selectPks: []int{1}, 196 sourcePrimitive: newMergeSorter(nil, []compareColInfo{{1, collations.Collation(nil), true}}), 197 targetPrimitive: newMergeSorter(nil, []compareColInfo{{1, collations.Collation(nil), true}}), 198 }, 199 }, { 200 // pk text column. 201 input: &binlogdatapb.Rule{ 202 Match: "pktext", 203 Filter: "select textcol, c2 from pktext", 204 }, 205 table: "pktext", 206 td: &tableDiffer{ 207 targetTable: "pktext", 208 sourceExpression: "select textcol, c2 from pktext order by textcol asc", 209 targetExpression: "select textcol, c2 from pktext order by textcol asc", 210 compareCols: []compareColInfo{{0, collations.Collation(nil), true}, {1, collations.Collation(nil), false}}, 211 comparePKs: []compareColInfo{{0, collations.Collation(nil), true}}, 212 pkCols: []int{0}, 213 selectPks: []int{0}, 214 sourcePrimitive: newMergeSorter(nil, []compareColInfo{{0, collations.Collation(nil), false}}), 215 targetPrimitive: newMergeSorter(nil, []compareColInfo{{0, collations.Collation(nil), false}}), 216 }, 217 }, { 218 // pk text column, different order. 219 input: &binlogdatapb.Rule{ 220 Match: "pktext", 221 Filter: "select c2, textcol from pktext", 222 }, 223 table: "pktext", 224 td: &tableDiffer{ 225 targetTable: "pktext", 226 sourceExpression: "select c2, textcol from pktext order by textcol asc", 227 targetExpression: "select c2, textcol from pktext order by textcol asc", 228 compareCols: []compareColInfo{{0, collations.Collation(nil), false}, {1, collations.Collation(nil), true}}, 229 comparePKs: []compareColInfo{{1, collations.Collation(nil), true}}, 230 pkCols: []int{1}, 231 selectPks: []int{1}, 232 sourcePrimitive: newMergeSorter(nil, []compareColInfo{{1, collations.Collation(nil), false}}), 233 targetPrimitive: newMergeSorter(nil, []compareColInfo{{1, collations.Collation(nil), false}}), 234 }, 235 }, { 236 // text column as expression. 237 input: &binlogdatapb.Rule{ 238 Match: "pktext", 239 Filter: "select c2, a+b as textcol from pktext", 240 }, 241 table: "pktext", 242 td: &tableDiffer{ 243 targetTable: "pktext", 244 sourceExpression: "select c2, a + b as textcol from pktext order by textcol asc", 245 targetExpression: "select c2, textcol from pktext order by textcol asc", 246 compareCols: []compareColInfo{{0, collations.Collation(nil), false}, {1, collations.Collation(nil), true}}, 247 comparePKs: []compareColInfo{{1, collations.Collation(nil), true}}, 248 pkCols: []int{1}, 249 selectPks: []int{1}, 250 sourcePrimitive: newMergeSorter(nil, []compareColInfo{{1, collations.Collation(nil), false}}), 251 targetPrimitive: newMergeSorter(nil, []compareColInfo{{1, collations.Collation(nil), false}}), 252 }, 253 }, { 254 input: &binlogdatapb.Rule{ 255 Match: "multipk", 256 }, 257 table: "multipk", 258 td: &tableDiffer{ 259 targetTable: "multipk", 260 sourceExpression: "select c1, c2 from multipk order by c1 asc, c2 asc", 261 targetExpression: "select c1, c2 from multipk order by c1 asc, c2 asc", 262 compareCols: []compareColInfo{{0, collations.Collation(nil), true}, {1, collations.Collation(nil), true}}, 263 comparePKs: []compareColInfo{{0, collations.Collation(nil), true}, {1, collations.Collation(nil), true}}, 264 pkCols: []int{0, 1}, 265 selectPks: []int{0, 1}, 266 sourcePrimitive: newMergeSorter(nil, []compareColInfo{{0, collations.Collation(nil), true}, {1, collations.Collation(nil), true}}), 267 targetPrimitive: newMergeSorter(nil, []compareColInfo{{0, collations.Collation(nil), true}, {1, collations.Collation(nil), true}}), 268 }, 269 }, { 270 // in_keyrange 271 input: &binlogdatapb.Rule{ 272 Match: "t1", 273 Filter: "select * from t1 where in_keyrange('-80')", 274 }, 275 table: "t1", 276 td: &tableDiffer{ 277 targetTable: "t1", 278 sourceExpression: "select c1, c2 from t1 order by c1 asc", 279 targetExpression: "select c1, c2 from t1 order by c1 asc", 280 compareCols: []compareColInfo{{0, collations.Collation(nil), true}, {1, collations.Collation(nil), false}}, 281 comparePKs: []compareColInfo{{0, collations.Collation(nil), true}}, 282 pkCols: []int{0}, 283 selectPks: []int{0}, 284 sourcePrimitive: newMergeSorter(nil, []compareColInfo{{0, collations.Collation(nil), true}}), 285 targetPrimitive: newMergeSorter(nil, []compareColInfo{{0, collations.Collation(nil), true}}), 286 }, 287 }, { 288 // in_keyrange on RHS of AND. 289 // This is currently not a valid construct, but will be supported in the future. 290 input: &binlogdatapb.Rule{ 291 Match: "t1", 292 Filter: "select * from t1 where c2 = 2 and in_keyrange('-80')", 293 }, 294 table: "t1", 295 td: &tableDiffer{ 296 targetTable: "t1", 297 sourceExpression: "select c1, c2 from t1 where c2 = 2 order by c1 asc", 298 targetExpression: "select c1, c2 from t1 order by c1 asc", 299 compareCols: []compareColInfo{{0, collations.Collation(nil), true}, {1, collations.Collation(nil), false}}, 300 comparePKs: []compareColInfo{{0, collations.Collation(nil), true}}, 301 pkCols: []int{0}, 302 selectPks: []int{0}, 303 sourcePrimitive: newMergeSorter(nil, []compareColInfo{{0, collations.Collation(nil), true}}), 304 targetPrimitive: newMergeSorter(nil, []compareColInfo{{0, collations.Collation(nil), true}}), 305 }, 306 }, { 307 // in_keyrange on LHS of AND. 308 // This is currently not a valid construct, but will be supported in the future. 309 input: &binlogdatapb.Rule{ 310 Match: "t1", 311 Filter: "select * from t1 where in_keyrange('-80') and c2 = 2", 312 }, 313 table: "t1", 314 td: &tableDiffer{ 315 targetTable: "t1", 316 sourceExpression: "select c1, c2 from t1 where c2 = 2 order by c1 asc", 317 targetExpression: "select c1, c2 from t1 order by c1 asc", 318 compareCols: []compareColInfo{{0, collations.Collation(nil), true}, {1, collations.Collation(nil), false}}, 319 comparePKs: []compareColInfo{{0, collations.Collation(nil), true}}, 320 pkCols: []int{0}, 321 selectPks: []int{0}, 322 sourcePrimitive: newMergeSorter(nil, []compareColInfo{{0, collations.Collation(nil), true}}), 323 targetPrimitive: newMergeSorter(nil, []compareColInfo{{0, collations.Collation(nil), true}}), 324 }, 325 }, { 326 // in_keyrange on cascaded AND expression 327 // This is currently not a valid construct, but will be supported in the future. 328 input: &binlogdatapb.Rule{ 329 Match: "t1", 330 Filter: "select * from t1 where c2 = 2 and c1 = 1 and in_keyrange('-80')", 331 }, 332 table: "t1", 333 td: &tableDiffer{ 334 targetTable: "t1", 335 sourceExpression: "select c1, c2 from t1 where c2 = 2 and c1 = 1 order by c1 asc", 336 targetExpression: "select c1, c2 from t1 order by c1 asc", 337 compareCols: []compareColInfo{{0, collations.Collation(nil), true}, {1, collations.Collation(nil), false}}, 338 comparePKs: []compareColInfo{{0, collations.Collation(nil), true}}, 339 pkCols: []int{0}, 340 selectPks: []int{0}, 341 sourcePrimitive: newMergeSorter(nil, []compareColInfo{{0, collations.Collation(nil), true}}), 342 targetPrimitive: newMergeSorter(nil, []compareColInfo{{0, collations.Collation(nil), true}}), 343 }, 344 }, { 345 // in_keyrange parenthesized 346 // This is currently not a valid construct, but will be supported in the future. 347 input: &binlogdatapb.Rule{ 348 Match: "t1", 349 Filter: "select * from t1 where (c2 = 2 and in_keyrange('-80'))", 350 }, 351 table: "t1", 352 td: &tableDiffer{ 353 targetTable: "t1", 354 sourceExpression: "select c1, c2 from t1 where c2 = 2 order by c1 asc", 355 targetExpression: "select c1, c2 from t1 order by c1 asc", 356 compareCols: []compareColInfo{{0, collations.Collation(nil), true}, {1, collations.Collation(nil), false}}, 357 comparePKs: []compareColInfo{{0, collations.Collation(nil), true}}, 358 pkCols: []int{0}, 359 selectPks: []int{0}, 360 sourcePrimitive: newMergeSorter(nil, []compareColInfo{{0, collations.Collation(nil), true}}), 361 targetPrimitive: newMergeSorter(nil, []compareColInfo{{0, collations.Collation(nil), true}}), 362 }, 363 }, { 364 // group by 365 input: &binlogdatapb.Rule{ 366 Match: "t1", 367 Filter: "select * from t1 group by c1", 368 }, 369 table: "t1", 370 td: &tableDiffer{ 371 targetTable: "t1", 372 sourceExpression: "select c1, c2 from t1 group by c1 order by c1 asc", 373 targetExpression: "select c1, c2 from t1 order by c1 asc", 374 compareCols: []compareColInfo{{0, collations.Collation(nil), true}, {1, collations.Collation(nil), false}}, 375 comparePKs: []compareColInfo{{0, collations.Collation(nil), true}}, 376 pkCols: []int{0}, 377 selectPks: []int{0}, 378 sourcePrimitive: newMergeSorter(nil, []compareColInfo{{0, collations.Collation(nil), true}}), 379 targetPrimitive: newMergeSorter(nil, []compareColInfo{{0, collations.Collation(nil), true}}), 380 }, 381 }, { 382 // aggregations 383 input: &binlogdatapb.Rule{ 384 Match: "aggr", 385 Filter: "select c1, c2, count(*) as c3, sum(c4) as c4 from t1 group by c1", 386 }, 387 table: "aggr", 388 td: &tableDiffer{ 389 targetTable: "aggr", 390 sourceExpression: "select c1, c2, count(*) as c3, sum(c4) as c4 from t1 group by c1 order by c1 asc", 391 targetExpression: "select c1, c2, c3, c4 from aggr order by c1 asc", 392 compareCols: []compareColInfo{{0, collations.Collation(nil), true}, {1, collations.Collation(nil), false}, {2, collations.Collation(nil), false}, {3, collations.Collation(nil), false}}, 393 comparePKs: []compareColInfo{{0, collations.Collation(nil), true}}, 394 pkCols: []int{0}, 395 selectPks: []int{0}, 396 sourcePrimitive: &engine.OrderedAggregate{ 397 Aggregates: []*engine.AggregateParams{{ 398 Opcode: engine.AggregateSum, 399 Col: 2, 400 }, { 401 Opcode: engine.AggregateSum, 402 Col: 3, 403 }}, 404 GroupByKeys: []*engine.GroupByParams{{KeyCol: 0, WeightStringCol: -1}}, 405 Input: newMergeSorter(nil, []compareColInfo{{0, collations.Collation(nil), true}}), 406 }, 407 targetPrimitive: newMergeSorter(nil, []compareColInfo{{0, collations.Collation(nil), true}}), 408 }, 409 }, { 410 input: &binlogdatapb.Rule{ 411 Match: "datze", 412 }, 413 sourceTimeZone: "US/Pacific", 414 table: "datze", 415 td: &tableDiffer{ 416 targetTable: "datze", 417 sourceExpression: "select id, dt from datze order by id asc", 418 targetExpression: "select id, convert_tz(dt, 'UTC', 'US/Pacific') as dt from datze order by id asc", 419 compareCols: []compareColInfo{{0, collations.Collation(nil), true}, {1, collations.Collation(nil), false}}, 420 comparePKs: []compareColInfo{{0, collations.Collation(nil), true}}, 421 pkCols: []int{0}, 422 selectPks: []int{0}, 423 sourcePrimitive: newMergeSorter(nil, []compareColInfo{{0, collations.Collation(nil), true}}), 424 targetPrimitive: newMergeSorter(nil, []compareColInfo{{0, collations.Collation(nil), true}}), 425 }, 426 }} 427 428 for _, tcase := range testcases { 429 t.Run(tcase.input.Filter, func(t *testing.T) { 430 filter := &binlogdatapb.Filter{Rules: []*binlogdatapb.Rule{tcase.input}} 431 df := &vdiff{sourceTimeZone: tcase.sourceTimeZone, targetTimeZone: "UTC"} 432 err := df.buildVDiffPlan(context.Background(), filter, schm, nil) 433 require.NoError(t, err, tcase.input) 434 require.Equal(t, 1, len(df.differs), tcase.input) 435 assert.Equal(t, tcase.td, df.differs[tcase.table], tcase.input) 436 }) 437 } 438 } 439 440 func TestVDiffPlanFailure(t *testing.T) { 441 schm := &tabletmanagerdatapb.SchemaDefinition{ 442 TableDefinitions: []*tabletmanagerdatapb.TableDefinition{{ 443 Name: "t1", 444 Columns: []string{"c1", "c2"}, 445 PrimaryKeyColumns: []string{"c1"}, 446 Fields: sqltypes.MakeTestFields("c1|c2", "int64|int64"), 447 }}, 448 } 449 450 testcases := []struct { 451 input *binlogdatapb.Rule 452 err string 453 }{{ 454 input: &binlogdatapb.Rule{ 455 Match: "t1", 456 Filter: "bad query", 457 }, 458 err: "syntax error at position 4 near 'bad'", 459 }, { 460 input: &binlogdatapb.Rule{ 461 Match: "t1", 462 Filter: "update t1 set c1=2", 463 }, 464 err: "unexpected: update t1 set c1 = 2", 465 }, { 466 input: &binlogdatapb.Rule{ 467 Match: "t1", 468 Filter: "select c1+1 from t1", 469 }, 470 err: "expression needs an alias: c1 + 1", 471 }, { 472 input: &binlogdatapb.Rule{ 473 Match: "t1", 474 Filter: "select next 2 values from t1", 475 }, 476 err: "unexpected: select next 2 values from t1", 477 }, { 478 input: &binlogdatapb.Rule{ 479 Match: "t1", 480 Filter: "select c3 from t1", 481 }, 482 err: "column c3 not found in table t1", 483 }} 484 for _, tcase := range testcases { 485 filter := &binlogdatapb.Filter{Rules: []*binlogdatapb.Rule{tcase.input}} 486 df := &vdiff{} 487 err := df.buildVDiffPlan(context.Background(), filter, schm, nil) 488 assert.EqualError(t, err, tcase.err, tcase.input) 489 } 490 } 491 492 func TestVDiffUnsharded(t *testing.T) { 493 env := newTestVDiffEnv(t, []string{"0"}, []string{"0"}, "", nil) 494 defer env.close() 495 496 schm := &tabletmanagerdatapb.SchemaDefinition{ 497 TableDefinitions: []*tabletmanagerdatapb.TableDefinition{{ 498 Name: "t1", 499 Columns: []string{"c1", "c2"}, 500 PrimaryKeyColumns: []string{"c1"}, 501 Fields: sqltypes.MakeTestFields("c1|c2", "int64|int64"), 502 }}, 503 } 504 env.tmc.schema = schm 505 506 fields := sqltypes.MakeTestFields( 507 "c1|c2", 508 "int64|int64", 509 ) 510 511 testcases := []struct { 512 id string 513 source []*sqltypes.Result 514 target []*sqltypes.Result 515 dr *DiffReport 516 onlyPks bool 517 debug bool 518 }{{ 519 id: "1", 520 source: sqltypes.MakeTestStreamingResults(fields, 521 "1|3", 522 "2|4", 523 "---", 524 "3|1", 525 ), 526 target: sqltypes.MakeTestStreamingResults(fields, 527 "1|3", 528 "---", 529 "2|4", 530 "3|1", 531 ), 532 dr: &DiffReport{ 533 ProcessedRows: 3, 534 MatchingRows: 3, 535 TableName: "t1", 536 }, 537 }, { 538 id: "2", 539 source: sqltypes.MakeTestStreamingResults(fields, 540 "1|3", 541 ), 542 target: sqltypes.MakeTestStreamingResults(fields, 543 "1|3", 544 "---", 545 "2|4", 546 "3|1", 547 ), 548 dr: &DiffReport{ 549 ProcessedRows: 3, 550 MatchingRows: 1, 551 ExtraRowsTarget: 2, 552 TableName: "t1", 553 ExtraRowsTargetDiffs: []*RowDiff{ 554 { 555 Row: map[string]sqltypes.Value{ 556 "c1": sqltypes.NewInt64(2), 557 "c2": sqltypes.NewInt64(4), 558 }, 559 Query: "", 560 }, 561 }, 562 }, 563 }, { 564 id: "3", 565 source: sqltypes.MakeTestStreamingResults(fields, 566 "1|3", 567 "---", 568 "2|4", 569 "3|1", 570 ), 571 target: sqltypes.MakeTestStreamingResults(fields, 572 "1|3", 573 ), 574 dr: &DiffReport{ 575 ProcessedRows: 3, 576 MatchingRows: 1, 577 ExtraRowsSource: 2, 578 TableName: "t1", 579 ExtraRowsSourceDiffs: []*RowDiff{ 580 { 581 Row: map[string]sqltypes.Value{ 582 "c1": sqltypes.NewInt64(2), 583 "c2": sqltypes.NewInt64(4), 584 }, 585 Query: "", 586 }, 587 }, 588 }, 589 }, { 590 id: "4", 591 source: sqltypes.MakeTestStreamingResults(fields, 592 "1|3", 593 "---", 594 "2|4", 595 "3|1", 596 ), 597 target: sqltypes.MakeTestStreamingResults(fields, 598 "1|3", 599 "---", 600 "3|1", 601 ), 602 dr: &DiffReport{ 603 ProcessedRows: 3, 604 MatchingRows: 2, 605 ExtraRowsSource: 1, 606 TableName: "t1", 607 ExtraRowsSourceDiffs: []*RowDiff{ 608 { 609 Row: map[string]sqltypes.Value{ 610 "c1": sqltypes.NewInt64(2), 611 "c2": sqltypes.NewInt64(4), 612 }, 613 Query: "", 614 }, 615 }, 616 }, 617 }, { 618 id: "5", 619 source: sqltypes.MakeTestStreamingResults(fields, 620 "1|3", 621 "---", 622 "3|1", 623 ), 624 target: sqltypes.MakeTestStreamingResults(fields, 625 "1|3", 626 "---", 627 "2|4", 628 "3|1", 629 ), 630 dr: &DiffReport{ 631 ProcessedRows: 3, 632 MatchingRows: 2, 633 ExtraRowsTarget: 1, 634 TableName: "t1", 635 ExtraRowsTargetDiffs: []*RowDiff{ 636 { 637 Row: map[string]sqltypes.Value{ 638 "c1": sqltypes.NewInt64(2), 639 "c2": sqltypes.NewInt64(4), 640 }, 641 Query: "", 642 }, 643 }, 644 }, 645 }, { 646 id: "6", 647 source: sqltypes.MakeTestStreamingResults(fields, 648 "1|3", 649 "---", 650 "2|3", 651 "3|1", 652 ), 653 target: sqltypes.MakeTestStreamingResults(fields, 654 "1|3", 655 "---", 656 "2|4", 657 "3|1", 658 ), 659 dr: &DiffReport{ 660 ProcessedRows: 3, 661 MatchingRows: 2, 662 MismatchedRows: 1, 663 TableName: "t1", 664 MismatchedRowsSample: []*DiffMismatch{ 665 { 666 Source: &RowDiff{Row: map[string]sqltypes.Value{ 667 "c1": sqltypes.NewInt64(2), 668 "c2": sqltypes.NewInt64(3), 669 }, 670 Query: "", 671 }, 672 Target: &RowDiff{Row: map[string]sqltypes.Value{ 673 "c1": sqltypes.NewInt64(2), 674 "c2": sqltypes.NewInt64(4), 675 }, 676 Query: "", 677 }, 678 }, 679 }, 680 }, 681 }, { 682 id: "7", 683 onlyPks: true, 684 source: sqltypes.MakeTestStreamingResults(fields, 685 "1|3", 686 "---", 687 "2|3", 688 "3|1", 689 ), 690 target: sqltypes.MakeTestStreamingResults(fields, 691 "1|3", 692 "---", 693 "2|4", 694 "3|1", 695 ), 696 dr: &DiffReport{ 697 ProcessedRows: 3, 698 MatchingRows: 2, 699 MismatchedRows: 1, 700 TableName: "t1", 701 MismatchedRowsSample: []*DiffMismatch{ 702 { 703 Source: &RowDiff{Row: map[string]sqltypes.Value{ 704 "c1": sqltypes.NewInt64(2), 705 }, 706 Query: "", 707 }, 708 Target: &RowDiff{Row: map[string]sqltypes.Value{ 709 "c1": sqltypes.NewInt64(2), 710 }, 711 Query: "", 712 }, 713 }, 714 }, 715 }, 716 }, { 717 id: "8", 718 debug: true, 719 source: sqltypes.MakeTestStreamingResults(fields, 720 "1|3", 721 "---", 722 "2|3", 723 "3|1", 724 ), 725 target: sqltypes.MakeTestStreamingResults(fields, 726 "1|3", 727 "---", 728 "2|4", 729 "3|1", 730 ), 731 dr: &DiffReport{ 732 ProcessedRows: 3, 733 MatchingRows: 2, 734 MismatchedRows: 1, 735 TableName: "t1", 736 MismatchedRowsSample: []*DiffMismatch{ 737 { 738 Source: &RowDiff{Row: map[string]sqltypes.Value{ 739 "c1": sqltypes.NewInt64(2), 740 "c2": sqltypes.NewInt64(3), 741 }, 742 Query: "select c1, c2 from t1 where c1=2;", 743 }, 744 Target: &RowDiff{Row: map[string]sqltypes.Value{ 745 "c1": sqltypes.NewInt64(2), 746 "c2": sqltypes.NewInt64(4), 747 }, 748 Query: "select c1, c2 from t1 where c1=2;", 749 }, 750 }, 751 }, 752 }, 753 }} 754 755 for _, tcase := range testcases { 756 t.Run(tcase.id, func(t *testing.T) { 757 env.tablets[101].setResults("select c1, c2 from t1 order by c1 asc", vdiffSourceGtid, tcase.source) 758 env.tablets[201].setResults("select c1, c2 from t1 order by c1 asc", vdiffTargetPrimaryPosition, tcase.target) 759 760 dr, err := env.wr.VDiff(context.Background(), "target", env.workflow, env.cell, env.cell, "replica", 30*time.Second, "", 100, "", tcase.debug, tcase.onlyPks, 100) 761 require.NoError(t, err) 762 assert.Equal(t, tcase.dr, dr["t1"], tcase.id) 763 }) 764 } 765 } 766 767 func TestVDiffSharded(t *testing.T) { 768 // Also test that highest position ""MariaDB/5-456-892" will be used 769 // if lower positions are found. 770 env := newTestVDiffEnv(t, []string{"-40", "40-"}, []string{"-80", "80-"}, "", map[string]string{ 771 "-40-80": "MariaDB/5-456-890", 772 "40-80-": "MariaDB/5-456-891", 773 }) 774 defer env.close() 775 776 schm := &tabletmanagerdatapb.SchemaDefinition{ 777 TableDefinitions: []*tabletmanagerdatapb.TableDefinition{{ 778 Name: "t1", 779 Columns: []string{"c1", "c2"}, 780 PrimaryKeyColumns: []string{"c1"}, 781 Fields: sqltypes.MakeTestFields("c1|c2", "int64|int64"), 782 }, 783 { 784 Name: "_t1_gho", 785 Columns: []string{"c1", "c2", "c3"}, 786 PrimaryKeyColumns: []string{"c2"}, 787 Fields: sqltypes.MakeTestFields("c1|c2|c3", "int64|int64|int64"), 788 }}, 789 } 790 791 env.tmc.schema = schm 792 793 query := "select c1, c2 from t1 order by c1 asc" 794 fields := sqltypes.MakeTestFields( 795 "c1|c2", 796 "int64|int64", 797 ) 798 799 env.tablets[101].setResults( 800 query, 801 vdiffSourceGtid, 802 sqltypes.MakeTestStreamingResults(fields, 803 "1|3", 804 "2|4", 805 ), 806 ) 807 env.tablets[111].setResults( 808 query, 809 vdiffSourceGtid, 810 sqltypes.MakeTestStreamingResults(fields, 811 "3|4", 812 ), 813 ) 814 env.tablets[201].setResults( 815 query, 816 vdiffTargetPrimaryPosition, 817 sqltypes.MakeTestStreamingResults(fields, 818 "1|3", 819 ), 820 ) 821 env.tablets[211].setResults( 822 query, 823 vdiffTargetPrimaryPosition, 824 sqltypes.MakeTestStreamingResults(fields, 825 "2|4", 826 "3|4", 827 ), 828 ) 829 830 dr, err := env.wr.VDiff(context.Background(), "target", env.workflow, env.cell, env.cell, "replica", 30*time.Second, "", 100, "", false /*debug*/, false /*onlyPks*/, 100) 831 require.NoError(t, err) 832 wantdr := &DiffReport{ 833 ProcessedRows: 3, 834 MatchingRows: 3, 835 TableName: "t1", 836 } 837 assert.Equal(t, wantdr, dr["t1"]) 838 } 839 840 func TestVDiffAggregates(t *testing.T) { 841 env := newTestVDiffEnv(t, []string{"-40", "40-"}, []string{"-80", "80-"}, "select c1, count(*) c2, sum(c3) c3 from t group by c1", nil) 842 defer env.close() 843 844 schm := &tabletmanagerdatapb.SchemaDefinition{ 845 TableDefinitions: []*tabletmanagerdatapb.TableDefinition{{ 846 Name: "t1", 847 Columns: []string{"c1", "c2", "c3"}, 848 PrimaryKeyColumns: []string{"c1"}, 849 Fields: sqltypes.MakeTestFields("c1|c2|c3", "int64|int64|int64"), 850 }}, 851 } 852 env.tmc.schema = schm 853 854 sourceQuery := "select c1, count(*) as c2, sum(c3) as c3 from t group by c1 order by c1 asc" 855 fields := sqltypes.MakeTestFields( 856 "c1|c2|c3", 857 "int64|int64|int64", 858 ) 859 860 env.tablets[101].setResults( 861 sourceQuery, 862 vdiffSourceGtid, 863 sqltypes.MakeTestStreamingResults(fields, 864 "1|3|4", 865 "2|4|5", 866 "4|5|6", 867 ), 868 ) 869 env.tablets[111].setResults( 870 sourceQuery, 871 vdiffSourceGtid, 872 sqltypes.MakeTestStreamingResults(fields, 873 "1|1|1", 874 "3|2|2", 875 "5|3|3", 876 ), 877 ) 878 targetQuery := "select c1, c2, c3 from t1 order by c1 asc" 879 env.tablets[201].setResults( 880 targetQuery, 881 vdiffTargetPrimaryPosition, 882 sqltypes.MakeTestStreamingResults(fields, 883 "1|4|5", 884 "5|3|3", 885 ), 886 ) 887 env.tablets[211].setResults( 888 targetQuery, 889 vdiffTargetPrimaryPosition, 890 sqltypes.MakeTestStreamingResults(fields, 891 "2|4|5", 892 "3|2|2", 893 "4|5|6", 894 ), 895 ) 896 897 dr, err := env.wr.VDiff(context.Background(), "target", env.workflow, env.cell, env.cell, "replica", 30*time.Second, "", 100, "", false /*debug*/, false /*onlyPks*/, 100) 898 require.NoError(t, err) 899 wantdr := &DiffReport{ 900 ProcessedRows: 5, 901 MatchingRows: 5, 902 TableName: "t1", 903 } 904 assert.Equal(t, wantdr, dr["t1"]) 905 } 906 907 func TestVDiffDefaults(t *testing.T) { 908 env := newTestVDiffEnv(t, []string{"0"}, []string{"0"}, "", nil) 909 defer env.close() 910 911 schm := &tabletmanagerdatapb.SchemaDefinition{ 912 TableDefinitions: []*tabletmanagerdatapb.TableDefinition{{ 913 Name: "t1", 914 Columns: []string{"c1", "c2"}, 915 PrimaryKeyColumns: []string{"c1"}, 916 Fields: sqltypes.MakeTestFields("c1|c2", "int64|int64"), 917 }}, 918 } 919 env.tmc.schema = schm 920 921 fields := sqltypes.MakeTestFields( 922 "c1|c2", 923 "int64|int64", 924 ) 925 926 source := sqltypes.MakeTestStreamingResults(fields, 927 "1|3", 928 "2|4", 929 "---", 930 "3|1", 931 ) 932 target := source 933 env.tablets[101].setResults("select c1, c2 from t1 order by c1 asc", vdiffSourceGtid, source) 934 env.tablets[201].setResults("select c1, c2 from t1 order by c1 asc", vdiffTargetPrimaryPosition, target) 935 936 _, err := env.wr.VDiff(context.Background(), "target", env.workflow, "", "", "replica", 30*time.Second, "", 100, "", false /*debug*/, false /*onlyPks*/, 100) 937 require.NoError(t, err) 938 _, err = env.wr.VDiff(context.Background(), "target", env.workflow, "", env.cell, "replica", 30*time.Second, "", 100, "", false /*debug*/, false /*onlyPks*/, 100) 939 require.NoError(t, err) 940 941 var df map[string]*DiffReport 942 df, err = env.wr.VDiff(context.Background(), "target", env.workflow, env.cell, "", "replica", 30*time.Second, "", 100, "", false /*debug*/, false /*onlyPks*/, 100) 943 require.NoError(t, err) 944 require.Equal(t, df["t1"].ProcessedRows, 3) 945 df, err = env.wr.VDiff(context.Background(), "target", env.workflow, env.cell, "", "replica", 30*time.Second, "", 1, "", false /*debug*/, false /*onlyPks*/, 100) 946 require.NoError(t, err) 947 require.Equal(t, df["t1"].ProcessedRows, 1) 948 df, err = env.wr.VDiff(context.Background(), "target", env.workflow, env.cell, "", "replica", 30*time.Second, "", 0, "", false /*debug*/, false /*onlyPks*/, 100) 949 require.NoError(t, err) 950 require.Equal(t, df["t1"].ProcessedRows, 0) 951 952 _, err = env.wr.VDiff(context.Background(), "target", env.workflow, env.cell, "", "replica", 1*time.Nanosecond, "", 100, "", false /*debug*/, false /*onlyPks*/, 100) 953 require.Error(t, err) 954 err = topo.CheckKeyspaceLocked(context.Background(), "target") 955 require.EqualErrorf(t, err, "keyspace target is not locked (no locksInfo)", "") 956 err = topo.CheckKeyspaceLocked(context.Background(), "source") 957 require.EqualErrorf(t, err, "keyspace source is not locked (no locksInfo)", "") 958 } 959 960 func TestVDiffReplicationWait(t *testing.T) { 961 env := newTestVDiffEnv(t, []string{"0"}, []string{"0"}, "", nil) 962 defer env.close() 963 964 schm := &tabletmanagerdatapb.SchemaDefinition{ 965 TableDefinitions: []*tabletmanagerdatapb.TableDefinition{{ 966 Name: "t1", 967 Columns: []string{"c1", "c2"}, 968 PrimaryKeyColumns: []string{"c1"}, 969 Fields: sqltypes.MakeTestFields("c1|c2", "int64|int64"), 970 }}, 971 } 972 env.tmc.schema = schm 973 974 fields := sqltypes.MakeTestFields( 975 "c1|c2", 976 "int64|int64", 977 ) 978 979 source := sqltypes.MakeTestStreamingResults(fields, 980 "1|3", 981 "2|4", 982 "---", 983 "3|1", 984 ) 985 target := source 986 env.tablets[101].setResults("select c1, c2 from t1 order by c1 asc", vdiffSourceGtid, source) 987 env.tablets[201].setResults("select c1, c2 from t1 order by c1 asc", vdiffTargetPrimaryPosition, target) 988 989 _, err := env.wr.VDiff(context.Background(), "target", env.workflow, env.cell, env.cell, "replica", 0*time.Second, "", 100, "", false /*debug*/, false /*onlyPks*/, 100) 990 require.Error(t, err) 991 require.True(t, strings.Contains(err.Error(), "context deadline exceeded")) 992 } 993 994 func TestVDiffFindPKs(t *testing.T) { 995 996 testcases := []struct { 997 name string 998 table *tabletmanagerdatapb.TableDefinition 999 targetSelect *sqlparser.Select 1000 tdIn *tableDiffer 1001 tdOut *tableDiffer 1002 errorString string 1003 }{ 1004 { 1005 name: "", 1006 table: &tabletmanagerdatapb.TableDefinition{ 1007 Name: "t1", 1008 Columns: []string{"c1", "c2"}, 1009 PrimaryKeyColumns: []string{"c1"}, 1010 Fields: sqltypes.MakeTestFields("c1|c2", "int64|int64"), 1011 }, 1012 targetSelect: &sqlparser.Select{ 1013 SelectExprs: sqlparser.SelectExprs{ 1014 &sqlparser.AliasedExpr{Expr: &sqlparser.ColName{Name: sqlparser.NewIdentifierCI("c1")}}, 1015 &sqlparser.AliasedExpr{Expr: &sqlparser.ColName{Name: sqlparser.NewIdentifierCI("c2")}}, 1016 }, 1017 }, 1018 tdIn: &tableDiffer{ 1019 compareCols: []compareColInfo{{0, collations.Collation(nil), false}, {1, collations.Collation(nil), false}}, 1020 comparePKs: []compareColInfo{}, 1021 pkCols: []int{}, 1022 }, 1023 tdOut: &tableDiffer{ 1024 compareCols: []compareColInfo{{0, collations.Collation(nil), true}, {1, collations.Collation(nil), false}}, 1025 comparePKs: []compareColInfo{{0, collations.Collation(nil), true}}, 1026 pkCols: []int{0}, 1027 selectPks: []int{0}, 1028 }, 1029 }, { 1030 name: "", 1031 table: &tabletmanagerdatapb.TableDefinition{ 1032 Name: "t1", 1033 Columns: []string{"c1", "c2", "c3", "c4"}, 1034 PrimaryKeyColumns: []string{"c1", "c4"}, 1035 Fields: sqltypes.MakeTestFields("c1|c2|c3|c4", "int64|int64|varchar|int64"), 1036 }, 1037 targetSelect: &sqlparser.Select{ 1038 SelectExprs: sqlparser.SelectExprs{ 1039 &sqlparser.AliasedExpr{Expr: &sqlparser.ColName{Name: sqlparser.NewIdentifierCI("c1")}}, 1040 &sqlparser.AliasedExpr{Expr: &sqlparser.ColName{Name: sqlparser.NewIdentifierCI("c2")}}, 1041 &sqlparser.AliasedExpr{Expr: &sqlparser.FuncExpr{Name: sqlparser.NewIdentifierCI("c3")}}, 1042 &sqlparser.AliasedExpr{Expr: &sqlparser.ColName{Name: sqlparser.NewIdentifierCI("c4")}}, 1043 }, 1044 }, 1045 tdIn: &tableDiffer{ 1046 compareCols: []compareColInfo{{0, collations.Collation(nil), false}, {1, collations.Collation(nil), false}, {2, collations.Collation(nil), false}, {3, collations.Collation(nil), false}}, 1047 comparePKs: []compareColInfo{}, 1048 pkCols: []int{}, 1049 }, 1050 tdOut: &tableDiffer{ 1051 compareCols: []compareColInfo{{0, collations.Collation(nil), true}, {1, collations.Collation(nil), false}, {2, collations.Collation(nil), false}, {3, collations.Collation(nil), true}}, 1052 comparePKs: []compareColInfo{{0, collations.Collation(nil), true}, {3, collations.Collation(nil), true}}, 1053 pkCols: []int{0, 3}, 1054 selectPks: []int{0, 3}, 1055 }, 1056 }, 1057 } 1058 1059 for _, tc := range testcases { 1060 t.Run(tc.name, func(t *testing.T) { 1061 _, err := findPKs(tc.table, tc.targetSelect, tc.tdIn) 1062 require.NoError(t, err) 1063 require.EqualValues(t, tc.tdOut, tc.tdIn) 1064 }) 1065 } 1066 1067 } 1068 1069 func TestVDiffPlanInclude(t *testing.T) { 1070 schm := &tabletmanagerdatapb.SchemaDefinition{ 1071 TableDefinitions: []*tabletmanagerdatapb.TableDefinition{{ 1072 Name: "t1", 1073 Columns: []string{"c1", "c2"}, 1074 PrimaryKeyColumns: []string{"c1"}, 1075 Fields: sqltypes.MakeTestFields("c1|c2", "int64|int64"), 1076 }, { 1077 Name: "t2", 1078 Columns: []string{"c1", "c2"}, 1079 PrimaryKeyColumns: []string{"c1"}, 1080 Fields: sqltypes.MakeTestFields("c1|c2", "int64|int64"), 1081 }, { 1082 Name: "t3", 1083 Columns: []string{"c1", "c2"}, 1084 PrimaryKeyColumns: []string{"c1"}, 1085 Fields: sqltypes.MakeTestFields("c1|c2", "int64|int64"), 1086 }, { 1087 Name: "t4", 1088 Columns: []string{"c1", "c2"}, 1089 PrimaryKeyColumns: []string{"c1"}, 1090 Fields: sqltypes.MakeTestFields("c1|c2", "int64|int64"), 1091 }}, 1092 } 1093 1094 df := &vdiff{} 1095 rule := &binlogdatapb.Rule{ 1096 Match: "/.*", 1097 } 1098 filter := &binlogdatapb.Filter{Rules: []*binlogdatapb.Rule{rule}} 1099 var err error 1100 err = df.buildVDiffPlan(context.Background(), filter, schm, []string{"t2"}) 1101 require.NoError(t, err) 1102 require.Equal(t, 1, len(df.differs)) 1103 err = df.buildVDiffPlan(context.Background(), filter, schm, []string{"t2", "t3"}) 1104 require.NoError(t, err) 1105 require.Equal(t, 2, len(df.differs)) 1106 err = df.buildVDiffPlan(context.Background(), filter, schm, []string{"t1", "t2", "t3"}) 1107 require.NoError(t, err) 1108 require.Equal(t, 3, len(df.differs)) 1109 err = df.buildVDiffPlan(context.Background(), filter, schm, []string{"t1", "t2", "t3", "t4"}) 1110 require.NoError(t, err) 1111 require.Equal(t, 4, len(df.differs)) 1112 err = df.buildVDiffPlan(context.Background(), filter, schm, []string{"t1", "t2", "t3", "t5"}) 1113 require.Error(t, err) 1114 }