~percona-toolkit-dev/percona-toolkit/fix-log-parser-writer-bug-963225

« back to all changes in this revision

Viewing changes to t/lib/TableSyncNibble.t

  • Committer: Daniel Nichter
  • Date: 2011-06-24 17:22:06 UTC
  • Revision ID: daniel@percona.com-20110624172206-c7q4s4ad6r260zz6
Add lib/, t/lib/, and sandbox/.  All modules are updated and passing on MySQL 5.1.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
#!/usr/bin/perl
 
2
 
 
3
BEGIN {
 
4
   die "The PERCONA_TOOLKIT_BRANCH environment variable is not set.\n"
 
5
      unless $ENV{PERCONA_TOOLKIT_BRANCH} && -d $ENV{PERCONA_TOOLKIT_BRANCH};
 
6
   unshift @INC, "$ENV{PERCONA_TOOLKIT_BRANCH}/lib";
 
7
};
 
8
 
 
9
use strict;
 
10
use warnings FATAL => 'all';
 
11
use English qw(-no_match_vars);
 
12
use Test::More;
 
13
 
 
14
use DSNParser;
 
15
use Sandbox;
 
16
use TableSyncNibble;
 
17
use Quoter;
 
18
use ChangeHandler;
 
19
use TableChecksum;
 
20
use TableChunker;
 
21
use TableNibbler;
 
22
use TableParser;
 
23
use MySQLDump;
 
24
use VersionParser;
 
25
use MasterSlave;
 
26
use Retry;
 
27
use TableSyncer;
 
28
use MaatkitTest;
 
29
 
 
30
my $dp  = new DSNParser(opts=>$dsn_opts);
 
31
my $sb  = new Sandbox(basedir => '/tmp', DSNParser => $dp);
 
32
my $dbh = $sb->get_dbh_for('master');
 
33
 
 
34
if ( !$dbh ) {
 
35
   plan skip_all => 'Cannot connect to sandbox master';
 
36
}
 
37
else {
 
38
   plan tests => 36;
 
39
}
 
40
 
 
41
my $mysql = $sb->_use_for('master');
 
42
 
 
43
my $q  = new Quoter();
 
44
my $ms = new MasterSlave();
 
45
my $tp = new TableParser(Quoter=>$q);
 
46
my $du = new MySQLDump();
 
47
my $vp = new VersionParser();
 
48
my $rr = new Retry();
 
49
 
 
50
my $nibbler = new TableNibbler(
 
51
   TableParser => $tp,
 
52
   Quoter      => $q,
 
53
);
 
54
my $checksum = new TableChecksum(
 
55
   Quoter        => $q,
 
56
   VersionParser => $vp,
 
57
);
 
58
my $chunker = new TableChunker(
 
59
   MySQLDump => $du,
 
60
   Quoter    => $q
 
61
);
 
62
my $t = new TableSyncNibble(
 
63
   TableNibbler  => $nibbler,
 
64
   TableParser   => $tp,
 
65
   TableChunker  => $chunker,
 
66
   Quoter        => $q,
 
67
   VersionParser => $vp,
 
68
);
 
69
 
 
70
my @rows;
 
71
my $ch = new ChangeHandler(
 
72
   Quoter    => $q,
 
73
   right_db  => 'test',
 
74
   right_tbl => 'test1',
 
75
   left_db   => 'test',
 
76
   left_tbl  => 'test1',
 
77
   replace   => 0,
 
78
   actions   => [ sub { push @rows, $_[0] }, ],
 
79
   queue     => 0,
 
80
);
 
81
 
 
82
my $syncer = new TableSyncer(
 
83
   MasterSlave   => $ms,
 
84
   TableChecksum => $checksum,
 
85
   Quoter        => $q,
 
86
   VersionParser => $vp,
 
87
   Retry         => $rr,
 
88
);
 
89
 
 
90
$sb->create_dbs($dbh, ['test']);
 
91
diag(`$mysql < $trunk/t/lib/samples/before-TableSyncNibble.sql`);
 
92
my $ddl        = $du->get_create_table($dbh, $q, 'test', 'test1');
 
93
my $tbl_struct = $tp->parse($ddl);
 
94
my $src = {
 
95
   db  => 'test',
 
96
   tbl => 'test1',
 
97
   dbh => $dbh,
 
98
};
 
99
my $dst = {
 
100
   db  => 'test',
 
101
   tbl => 'test1',
 
102
   dbh => $dbh,
 
103
};
 
104
my %args       = (
 
105
   src           => $src,
 
106
   dst           => $dst,
 
107
   dbh           => $dbh,
 
108
   db            => 'test',
 
109
   tbl           => 'test1',
 
110
   tbl_struct    => $tbl_struct,
 
111
   cols          => $tbl_struct->{cols},
 
112
   chunk_size    => 1,
 
113
   chunk_index   => 'PRIMARY',
 
114
   key_cols      => $tbl_struct->{keys}->{PRIMARY}->{cols},
 
115
   crc_col       => '__crc',
 
116
   index_hint    => 'USE INDEX (`PRIMARY`)',
 
117
   ChangeHandler => $ch,
 
118
);
 
119
 
 
120
$t->prepare_to_sync(%args);
 
121
# Test with FNV_64 just to make sure there are no errors
 
122
eval { $dbh->do('select fnv_64(1)') };
 
123
SKIP: {
 
124
   skip 'No FNV_64 function installed', 1 if $EVAL_ERROR;
 
125
 
 
126
   $t->set_checksum_queries(
 
127
      $syncer->make_checksum_queries(%args, function => 'FNV_64')
 
128
   );
 
129
   is(
 
130
      $t->get_sql(
 
131
         database => 'test',
 
132
         table    => 'test1',
 
133
      ),
 
134
      q{SELECT /*test.test1:1/1*/ 0 AS chunk_num, COUNT(*) AS }
 
135
      . q{cnt, COALESCE(LOWER(CONV(BIT_XOR(CAST(FNV_64(`a`, `b`, `c`) AS UNSIGNED)), }
 
136
      . q{10, 16)), 0) AS crc FROM `test`.`test1` USE INDEX (`PRIMARY`) WHERE (((`a` < '1') OR (`a` = '1' }
 
137
      . q{AND `b` <= 'en')))},
 
138
      'First nibble SQL with FNV_64',
 
139
   );
 
140
}
 
141
 
 
142
$t->set_checksum_queries(
 
143
   $syncer->make_checksum_queries(%args, function => 'SHA1')
 
144
);
 
145
is(
 
146
   $t->get_sql(
 
147
      database => 'test',
 
148
      table    => 'test1',
 
149
   ),
 
150
   ($sandbox_version gt '4.0' ?
 
151
   q{SELECT /*test.test1:1/1*/ 0 AS chunk_num, COUNT(*) AS cnt, }
 
152
   . q{COALESCE(LOWER(CONCAT(LPAD(CONV(BIT_XOR(CAST(CONV(SUBSTRING(@crc, 1, 16), 16, }
 
153
   . q{10) AS UNSIGNED)), 10, 16), 16, '0'), LPAD(CONV(BIT_XOR(CAST(CONV(}
 
154
   . q{SUBSTRING(@crc, 17, 16), 16, 10) AS UNSIGNED)), 10, 16), 16, '0'), }
 
155
   . q{LPAD(CONV(BIT_XOR(CAST(CONV(SUBSTRING(@crc := SHA1(CONCAT_WS('#', `a`, }
 
156
   . q{`b`, `c`)), 33, 8), 16, 10) AS UNSIGNED)), 10, 16), 8, '0'))), 0) AS crc FROM }
 
157
   . q{`test`.`test1` USE INDEX (`PRIMARY`) WHERE (((`a` < '1') OR (`a` = '1' AND `b` <= 'en')))} :
 
158
   q{SELECT /*test.test1:1/1*/ 0 AS chunk_num, COUNT(*) AS cnt, }
 
159
   . q{COALESCE(RIGHT(MAX(@crc := CONCAT(LPAD(@cnt := @cnt + 1, 16, '0'), }
 
160
   . q{SHA1(CONCAT(@crc, SHA1(CONCAT_WS('#', `a`, `b`, `c`)))))), 40), 0) AS crc FROM }
 
161
   . q{`test`.`test1` USE INDEX (`PRIMARY`) WHERE (((`a` < '1') OR (`a` = '1' AND `b` <= 'en')))}
 
162
   ),
 
163
   'First nibble SQL',
 
164
);
 
165
 
 
166
is(
 
167
   $t->get_sql(
 
168
      database => 'test',
 
169
      table    => 'test1',
 
170
   ),
 
171
   ($sandbox_version gt '4.0' ?
 
172
   q{SELECT /*test.test1:1/1*/ 0 AS chunk_num, COUNT(*) AS cnt, }
 
173
   . q{COALESCE(LOWER(CONCAT(LPAD(CONV(BIT_XOR(CAST(CONV(SUBSTRING(@crc, 1, 16), 16, }
 
174
   . q{10) AS UNSIGNED)), 10, 16), 16, '0'), LPAD(CONV(BIT_XOR(CAST(CONV(}
 
175
   . q{SUBSTRING(@crc, 17, 16), 16, 10) AS UNSIGNED)), 10, 16), 16, '0'), }
 
176
   . q{LPAD(CONV(BIT_XOR(CAST(CONV(SUBSTRING(@crc := SHA1(CONCAT_WS('#', `a`, }
 
177
   . q{`b`, `c`)), 33, 8), 16, 10) AS UNSIGNED)), 10, 16), 8, '0'))), 0) AS crc FROM }
 
178
   . q{`test`.`test1` USE INDEX (`PRIMARY`) WHERE (((`a` < '1') OR (`a` = '1' AND `b` <= 'en')))} :
 
179
   q{SELECT /*test.test1:1/1*/ 0 AS chunk_num, COUNT(*) AS cnt, }
 
180
   . q{COALESCE(RIGHT(MAX(@crc := CONCAT(LPAD(@cnt := @cnt + 1, 16, '0'), }
 
181
   . q{SHA1(CONCAT(@crc, SHA1(CONCAT_WS('#', `a`, `b`, `c`)))))), 40), 0) AS crc FROM }
 
182
   . q{`test`.`test1` USE INDEX (`PRIMARY`) WHERE (((`a` < '1') OR (`a` = '1' AND `b` <= 'en')))}
 
183
   ),
 
184
   'First nibble SQL, again',
 
185
);
 
186
 
 
187
$t->{nibble} = 1;
 
188
delete $t->{cached_boundaries};
 
189
 
 
190
is(
 
191
   $t->get_sql(
 
192
      database => 'test',
 
193
      table    => 'test1',
 
194
   ),
 
195
   ($sandbox_version gt '4.0' ?
 
196
   q{SELECT /*test.test1:1/1*/ 0 AS chunk_num, COUNT(*) AS cnt, }
 
197
   . q{COALESCE(LOWER(CONCAT(LPAD(CONV(BIT_XOR(CAST(CONV(SUBSTRING(@crc, 1, 16), 16, }
 
198
   . q{10) AS UNSIGNED)), 10, 16), 16, '0'), LPAD(CONV(BIT_XOR(CAST(CONV(}
 
199
   . q{SUBSTRING(@crc, 17, 16), 16, 10) AS UNSIGNED)), 10, 16), 16, '0'), }
 
200
   . q{LPAD(CONV(BIT_XOR(CAST(CONV(SUBSTRING(@crc := SHA1(CONCAT_WS('#', `a`, }
 
201
   . q{`b`, `c`)), 33, 8), 16, 10) AS UNSIGNED)), 10, 16), 8, '0'))), 0) AS crc FROM }
 
202
   . q{`test`.`test1` USE INDEX (`PRIMARY`) WHERE ((((`a` > '1') OR (`a` = '1' AND `b` > 'en')) AND }
 
203
   . q{((`a` < '2') OR (`a` = '2' AND `b` <= 'ca'))))} :
 
204
   q{SELECT /*test.test1:1/1*/ 0 AS chunk_num, COUNT(*) AS cnt, }
 
205
   . q{COALESCE(RIGHT(MAX(@crc := CONCAT(LPAD(@cnt := @cnt + 1, 16, '0'), }
 
206
   . q{SHA1(CONCAT(@crc, SHA1(CONCAT_WS('#', `a`, `b`, `c`)))))), 40), 0) AS crc FROM }
 
207
   . q{`test`.`test1` USE INDEX (`PRIMARY`) WHERE ((((`a` > '1') OR (`a` = '1' AND `b` > 'en')) AND }
 
208
   . q{((`a` < '2') OR (`a` = '2' AND `b` <= 'ca'))))}
 
209
   ),
 
210
   'Second nibble SQL',
 
211
);
 
212
 
 
213
# Bump the nibble boundaries ahead until we run off the end of the table.
 
214
$t->done_with_rows();
 
215
$t->get_sql(
 
216
      database => 'test',
 
217
      table    => 'test1',
 
218
   );
 
219
$t->done_with_rows();
 
220
$t->get_sql(
 
221
      database => 'test',
 
222
      table    => 'test1',
 
223
   );
 
224
$t->done_with_rows();
 
225
$t->get_sql(
 
226
      database => 'test',
 
227
      table    => 'test1',
 
228
   );
 
229
 
 
230
is(
 
231
   $t->get_sql(
 
232
      database => 'test',
 
233
      table    => 'test1',
 
234
   ),
 
235
   ($sandbox_version gt '4.0' ?
 
236
   q{SELECT /*test.test1:1/1*/ 0 AS chunk_num, COUNT(*) AS cnt, }
 
237
   . q{COALESCE(LOWER(CONCAT(LPAD(CONV(BIT_XOR(CAST(CONV(SUBSTRING(@crc, 1, 16), 16, }
 
238
   . q{10) AS UNSIGNED)), 10, 16), 16, '0'), LPAD(CONV(BIT_XOR(CAST(CONV(}
 
239
   . q{SUBSTRING(@crc, 17, 16), 16, 10) AS UNSIGNED)), 10, 16), 16, '0'), }
 
240
   . q{LPAD(CONV(BIT_XOR(CAST(CONV(SUBSTRING(@crc := SHA1(CONCAT_WS('#', `a`, }
 
241
   . q{`b`, `c`)), 33, 8), 16, 10) AS UNSIGNED)), 10, 16), 8, '0'))), 0) AS crc FROM }
 
242
   . q{`test`.`test1` USE INDEX (`PRIMARY`) WHERE ((((`a` > '4') OR (`a` = '4' AND `b` > 'bz')) AND }
 
243
   . q{1=1))} :
 
244
   q{SELECT /*test.test1:1/1*/ 0 AS chunk_num, COUNT(*) AS cnt, }
 
245
   . q{COALESCE(RIGHT(MAX(@crc := CONCAT(LPAD(@cnt := @cnt + 1, 16, '0'), }
 
246
   . q{SHA1(CONCAT(@crc, SHA1(CONCAT_WS('#', `a`, `b`, `c`)))))), 40), 0) AS crc FROM }
 
247
   . q{`test`.`test1` USE INDEX (`PRIMARY`) WHERE ((((`a` > '4') OR (`a` = '4' AND `b` > 'bz')) AND }
 
248
   . q{1=1))}
 
249
   ),
 
250
   'End-of-table nibble SQL',
 
251
);
 
252
 
 
253
$t->done_with_rows();
 
254
ok($t->done(), 'Now done');
 
255
 
 
256
# Throw away and start anew, because it's off the end of the table
 
257
$t->{nibble} = 0;
 
258
delete $t->{cached_boundaries};
 
259
delete $t->{cached_nibble};
 
260
delete $t->{cached_row};
 
261
 
 
262
is_deeply($t->key_cols(), [qw(chunk_num)], 'Key cols in state 0');
 
263
$t->get_sql(
 
264
      database => 'test',
 
265
      table    => 'test1',
 
266
   );
 
267
$t->done_with_rows();
 
268
 
 
269
is($t->done(), '', 'Not done, because not reached end-of-table');
 
270
 
 
271
throws_ok(
 
272
   sub { $t->not_in_left() },
 
273
   qr/in state 0/,
 
274
   'not_in_(side) illegal in state 0',
 
275
);
 
276
 
 
277
# Now "find some bad chunks," as it were.
 
278
 
 
279
# "find a bad row"
 
280
$t->same_row(
 
281
   lr => { chunk_num => 0, cnt => 0, crc => 'abc' },
 
282
   rr => { chunk_num => 0, cnt => 1, crc => 'abc' },
 
283
);
 
284
ok($t->pending_changes(), 'Pending changes found');
 
285
is($t->{state}, 1, 'Working inside nibble');
 
286
$t->done_with_rows();
 
287
is($t->{state}, 2, 'Now in state to fetch individual rows');
 
288
ok($t->pending_changes(), 'Pending changes not done yet');
 
289
is($t->get_sql(database => 'test', table => 'test1'),
 
290
   q{SELECT /*rows in nibble*/ `a`, `b`, `c`, SHA1(CONCAT_WS('#', `a`, `b`, `c`)) AS __crc FROM }
 
291
   . q{`test`.`test1` USE INDEX (`PRIMARY`) WHERE ((((`a` > '1') OR (`a` = '1' AND `b` > 'en')) }
 
292
   . q{AND ((`a` < '2') OR (`a` = '2' AND `b` <= 'ca'))))}
 
293
   . q{ ORDER BY `a`, `b`},
 
294
   'SQL now working inside nibble'
 
295
);
 
296
ok($t->{state}, 'Still working inside nibble');
 
297
is(scalar(@rows), 0, 'No bad row triggered');
 
298
 
 
299
$t->not_in_left(rr => {a => 1, b => 'en'});
 
300
 
 
301
is_deeply(\@rows,
 
302
   ["DELETE FROM `test`.`test1` WHERE `a`='1' AND `b`='en' LIMIT 1"],
 
303
   'Working inside nibble, got a bad row',
 
304
);
 
305
 
 
306
# Shouldn't cause anything to happen
 
307
$t->same_row(
 
308
   lr => {a => 1, b => 'en', __crc => 'foo'},
 
309
   rr => {a => 1, b => 'en', __crc => 'foo'} );
 
310
 
 
311
is_deeply(\@rows,
 
312
   ["DELETE FROM `test`.`test1` WHERE `a`='1' AND `b`='en' LIMIT 1"],
 
313
   'No more rows added',
 
314
);
 
315
 
 
316
$t->same_row(
 
317
   lr => {a => 1, b => 'en', __crc => 'foo'},
 
318
   rr => {a => 1, b => 'en', __crc => 'bar'} );
 
319
 
 
320
is_deeply(\@rows,
 
321
   [
 
322
      "DELETE FROM `test`.`test1` WHERE `a`='1' AND `b`='en' LIMIT 1",
 
323
      "UPDATE `test`.`test1` SET `c`='a' WHERE `a`='1' AND `b`='en' LIMIT 1",
 
324
   ],
 
325
   'Row added to update differing row',
 
326
);
 
327
 
 
328
$t->done_with_rows();
 
329
is($t->{state}, 0, 'Now not working inside nibble');
 
330
is($t->pending_changes(), 0, 'No pending changes');
 
331
 
 
332
# Now test that SQL_BUFFER_RESULT is in the queries OK
 
333
$t->prepare_to_sync(%args, buffer_in_mysql=>1);
 
334
$t->{state} = 1;
 
335
like(
 
336
   $t->get_sql(
 
337
      database => 'test',
 
338
      table    => 'test1',
 
339
      buffer_in_mysql => 1,
 
340
   ),
 
341
   qr/SELECT ..rows in nibble.. SQL_BUFFER_RESULT/,
 
342
   'Buffering in first nibble',
 
343
);
 
344
 
 
345
# "find a bad row"
 
346
$t->same_row(
 
347
   lr => { chunk_num => 0, cnt => 0, __crc => 'abc' },
 
348
   rr => { chunk_num => 0, cnt => 1, __crc => 'abc' },
 
349
);
 
350
 
 
351
like(
 
352
   $t->get_sql(
 
353
      database => 'test',
 
354
      table    => 'test1',
 
355
      buffer_in_mysql => 1,
 
356
   ),
 
357
   qr/SELECT ..rows in nibble.. SQL_BUFFER_RESULT/,
 
358
   'Buffering in next nibble',
 
359
);
 
360
 
 
361
# #########################################################################
 
362
# Issue 96: mk-table-sync: Nibbler infinite loop
 
363
# #########################################################################
 
364
$sb->load_file('master', 't/lib/samples/issue_96.sql');
 
365
$tbl_struct = $tp->parse($du->get_create_table($dbh, $q, 'issue_96', 't'));
 
366
$t->prepare_to_sync(
 
367
   ChangeHandler  => $ch,
 
368
   cols           => $tbl_struct->{cols},
 
369
   dbh            => $dbh,
 
370
   db             => 'issue_96',
 
371
   tbl            => 't',
 
372
   tbl_struct     => $tbl_struct,
 
373
   chunk_size     => 2,
 
374
   chunk_index    => 'package_id',
 
375
   crc_col        => '__crc_col',
 
376
   index_hint     => 'FORCE INDEX(`package_id`)',
 
377
   key_cols       => $tbl_struct->{keys}->{package_id}->{cols},
 
378
);
 
379
 
 
380
# Test that we die if MySQL isn't using the chosen index (package_id)
 
381
# for the boundary sql.
 
382
 
 
383
my $sql = "SELECT /*nibble boundary 0*/ `package_id`,`location`,`from_city` FROM `issue_96`.`t` FORCE INDEX(`package_id`) ORDER BY `package_id`,`location` LIMIT 1, 1";
 
384
is(
 
385
   $t->__get_explain_index($sql),
 
386
   'package_id',
 
387
   '__get_explain_index()'
 
388
);
 
389
 
 
390
diag(`/tmp/12345/use -e 'ALTER TABLE issue_96.t DROP INDEX package_id'`);
 
391
 
 
392
is(
 
393
   $t->__get_explain_index($sql),
 
394
   undef,
 
395
   '__get_explain_index() for nonexistent index'
 
396
);
 
397
 
 
398
my %args2 = ( database=>'issue_96', table=>'t' );
 
399
eval {
 
400
   $t->get_sql(database=>'issue_96', tbl=>'t', %args2);
 
401
};
 
402
like(
 
403
   $EVAL_ERROR,
 
404
   qr/^Cannot nibble table `issue_96`.`t` because MySQL chose no index instead of the `package_id` index/,
 
405
   "Die if MySQL doesn't choose our index (issue 96)"
 
406
);
 
407
 
 
408
# Restore the index, get the first sql boundary and check that it
 
409
# has the proper ORDER BY clause which makes MySQL use the index.
 
410
diag(`/tmp/12345/use -e 'ALTER TABLE issue_96.t ADD UNIQUE INDEX package_id (package_id,location);'`);
 
411
eval {
 
412
   ($sql,undef) = $t->__make_boundary_sql(%args2);
 
413
};
 
414
is(
 
415
   $sql,
 
416
   "SELECT /*nibble boundary 0*/ `package_id`,`location`,`from_city` FROM `issue_96`.`t` FORCE INDEX(`package_id`) ORDER BY `package_id`,`location` LIMIT 1, 1",
 
417
   'Boundary SQL has ORDER BY key columns'
 
418
);
 
419
 
 
420
# If small_table is true, the index check should be skipped.
 
421
diag(`/tmp/12345/use -e 'create table issue_96.t3 (i int, unique index (i))'`);
 
422
diag(`/tmp/12345/use -e 'insert into issue_96.t3 values (1)'`);
 
423
$tbl_struct = $tp->parse($du->get_create_table($dbh, $q, 'issue_96', 't3'));
 
424
$t->prepare_to_sync(
 
425
   ChangeHandler  => $ch,
 
426
   cols           => $tbl_struct->{cols},
 
427
   dbh            => $dbh,
 
428
   db             => 'issue_96',
 
429
   tbl            => 't3',
 
430
   tbl_struct     => $tbl_struct,
 
431
   chunk_size     => 2,
 
432
   chunk_index    => 'i',
 
433
   crc_col        => '__crc_col',
 
434
   index_hint     => 'FORCE INDEX(`i`)',
 
435
   key_cols       => $tbl_struct->{keys}->{i}->{cols},
 
436
   small_table    => 1,
 
437
);
 
438
eval {
 
439
   $t->get_sql(database=>'issue_96', table=>'t3');
 
440
};
 
441
is(
 
442
   $EVAL_ERROR,
 
443
   '',
 
444
   "Skips index check when small table (issue 634)"
 
445
);
 
446
 
 
447
my ($can_sync, %plugin_args);
 
448
SKIP: {
 
449
   skip "Not tested on MySQL $sandbox_version", 5
 
450
      unless $sandbox_version gt '4.0';
 
451
 
 
452
# #############################################################################
 
453
# Issue 560: mk-table-sync generates impossible WHERE
 
454
# Issue 996: might not chunk inside of mk-table-checksum's boundaries
 
455
# #############################################################################
 
456
# Due to issue 996 this test has changed.  Now it *should* use the replicate
 
457
# boundary provided via the where arg and nibble just inside this boundary.
 
458
# If it does, then it will prevent the impossible WHERE of issue 560.
 
459
 
 
460
# The buddy_list table has 500 rows, so when it's chunk into 100 rows this is
 
461
# chunk 2:
 
462
my $where = '`player_id` >= 201 AND `player_id` < 301';
 
463
 
 
464
$sb->load_file('master', 't/pt-table-sync/samples/issue_560.sql');
 
465
$tbl_struct = $tp->parse($du->get_create_table($dbh, $q, 'issue_560', 'buddy_list'));
 
466
(undef, %plugin_args) = $t->can_sync(tbl_struct => $tbl_struct);
 
467
$t->prepare_to_sync(
 
468
   ChangeHandler  => $ch,
 
469
   cols           => $tbl_struct->{cols},
 
470
   dbh            => $dbh,
 
471
   db             => 'issue_560',
 
472
   tbl            => 'buddy_list',
 
473
   tbl_struct     => $tbl_struct,
 
474
   chunk_size     => 100,
 
475
   crc_col        => '__crc_col',
 
476
   %plugin_args,
 
477
   replicate      => 'issue_560.checksum',
 
478
   where          => $where,  # not used in sub but normally passed so we
 
479
                              # do the same to simulate a real run
 
480
);
 
481
 
 
482
# Must call this else $row_sql will have values from previous test.
 
483
$t->set_checksum_queries(
 
484
   $syncer->make_checksum_queries(
 
485
      src        => $src,
 
486
      dst        => $dst,
 
487
      tbl_struct => $tbl_struct,
 
488
   )
 
489
);
 
490
 
 
491
is(
 
492
   $t->get_sql(
 
493
      where    => $where,
 
494
      database => 'issue_560',
 
495
      table    => 'buddy_list', 
 
496
   ),
 
497
   "SELECT /*issue_560.buddy_list:1/1*/ 0 AS chunk_num, COUNT(*) AS cnt, COALESCE(LOWER(CONV(BIT_XOR(CAST(CRC32(CONCAT_WS('#', `player_id`, `buddy_id`)) AS UNSIGNED)), 10, 16)), 0) AS crc FROM `issue_560`.`buddy_list`  WHERE (((`player_id` < '300') OR (`player_id` = '300' AND `buddy_id` <= '2085'))) AND (($where))",
 
498
   'Nibble with chunk boundary (chunk sql)'
 
499
);
 
500
 
 
501
$t->{state} = 2;
 
502
is(
 
503
   $t->get_sql(
 
504
      where    => $where,
 
505
      database => 'issue_560',
 
506
      table    => 'buddy_list', 
 
507
   ),
 
508
   "SELECT /*rows in nibble*/ `player_id`, `buddy_id`, CRC32(CONCAT_WS('#', `player_id`, `buddy_id`)) AS __crc_col FROM `issue_560`.`buddy_list`  WHERE (((`player_id` < '300') OR (`player_id` = '300' AND `buddy_id` <= '2085'))) AND ($where) ORDER BY `player_id`, `buddy_id`",
 
509
   'Nibble with chunk boundary (row sql)'
 
510
);
 
511
 
 
512
$t->{state} = 0;
 
513
$t->done_with_rows();
 
514
is(
 
515
   $t->get_sql(
 
516
      where    => $where,
 
517
      database => 'issue_560',
 
518
      table    => 'buddy_list', 
 
519
   ),
 
520
   "SELECT /*issue_560.buddy_list:1/1*/ 0 AS chunk_num, COUNT(*) AS cnt, COALESCE(LOWER(CONV(BIT_XOR(CAST(CRC32(CONCAT_WS('#', `player_id`, `buddy_id`)) AS UNSIGNED)), 10, 16)), 0) AS crc FROM `issue_560`.`buddy_list`  WHERE ((((`player_id` > '300') OR (`player_id` = '300' AND `buddy_id` > '2085')) AND 1=1)) AND (($where))",
 
521
   "Next sub-nibble",
 
522
);
 
523
 
 
524
# Just like the previous tests but this time the chunk size is 50 so we
 
525
# should nibble two chunks within the larger range ($where).
 
526
$t->prepare_to_sync(
 
527
   ChangeHandler  => $ch,
 
528
   cols           => $tbl_struct->{cols},
 
529
   dbh            => $dbh,
 
530
   db             => 'issue_560',
 
531
   tbl            => 'buddy_list',
 
532
   tbl_struct     => $tbl_struct,
 
533
   chunk_size     => 50,              # 2 sub-nibbles
 
534
   crc_col        => '__crc_col',
 
535
   %plugin_args,
 
536
   replicate      => 'issue_560.checksum',
 
537
   where          => $where,  # not used in sub but normally passed so we
 
538
                              # do the same to simulate a real run
 
539
);
 
540
 
 
541
# Must call this else $row_sql will have values from previous test.
 
542
$t->set_checksum_queries(
 
543
   $syncer->make_checksum_queries(
 
544
      src        => $src,
 
545
      dst        => $dst,
 
546
      tbl_struct => $tbl_struct,
 
547
   )
 
548
);
 
549
 
 
550
is(
 
551
   $t->get_sql(
 
552
      where    => $where,
 
553
      database => 'issue_560',
 
554
      table    => 'buddy_list', 
 
555
   ),
 
556
   "SELECT /*issue_560.buddy_list:1/1*/ 0 AS chunk_num, COUNT(*) AS cnt, COALESCE(LOWER(CONV(BIT_XOR(CAST(CRC32(CONCAT_WS('#', `player_id`, `buddy_id`)) AS UNSIGNED)), 10, 16)), 0) AS crc FROM `issue_560`.`buddy_list`  WHERE (((`player_id` < '250') OR (`player_id` = '250' AND `buddy_id` <= '809'))) AND ((`player_id` >= 201 AND `player_id` < 301))",
 
557
   "Sub-nibble 1"
 
558
);
 
559
 
 
560
$t->done_with_rows();
 
561
is(
 
562
   $t->get_sql(
 
563
      where    => $where,
 
564
      database => 'issue_560',
 
565
      table    => 'buddy_list', 
 
566
   ),
 
567
   "SELECT /*issue_560.buddy_list:1/1*/ 0 AS chunk_num, COUNT(*) AS cnt, COALESCE(LOWER(CONV(BIT_XOR(CAST(CRC32(CONCAT_WS('#', `player_id`, `buddy_id`)) AS UNSIGNED)), 10, 16)), 0) AS crc FROM `issue_560`.`buddy_list`  WHERE ((((`player_id` > '250') OR (`player_id` = '250' AND `buddy_id` > '809')) AND ((`player_id` < '300') OR (`player_id` = '300' AND `buddy_id` <= '2085')))) AND ((`player_id` >= 201 AND `player_id` < 301))",
 
568
   "Sub-nibble 2"
 
569
);
 
570
}
 
571
 
 
572
# #############################################################################
 
573
# Issue 804: mk-table-sync: can't nibble because index name isn't lower case?
 
574
# #############################################################################
 
575
$sb->load_file('master', 't/lib/samples/issue_804.sql');
 
576
$tbl_struct = $tp->parse($du->get_create_table($dbh, $q, 'issue_804', 't'));
 
577
($can_sync, %plugin_args) = $t->can_sync(tbl_struct => $tbl_struct);
 
578
is(
 
579
   $can_sync,
 
580
   1,
 
581
   'Can sync issue_804 table'
 
582
);
 
583
is_deeply(
 
584
   \%plugin_args,
 
585
   {
 
586
      chunk_index => 'purchases_accountid_purchaseid',
 
587
      key_cols    => [qw(accountid purchaseid)],
 
588
      small_table => 0,
 
589
   },
 
590
   'Plugin args for issue_804 table'
 
591
);
 
592
 
 
593
$t->prepare_to_sync(
 
594
   ChangeHandler  => $ch,
 
595
   cols           => $tbl_struct->{cols},
 
596
   dbh            => $dbh,
 
597
   db             => 'issue_804',
 
598
   tbl            => 't',
 
599
   tbl_struct     => $tbl_struct,
 
600
   chunk_size     => 50,
 
601
   chunk_index    => $plugin_args{chunk_index},
 
602
   crc_col        => '__crc_col',
 
603
   index_hint     => 'FORCE INDEX(`'.$plugin_args{chunk_index}.'`)',
 
604
   key_cols       => $tbl_struct->{keys}->{$plugin_args{chunk_index}}->{cols},
 
605
);
 
606
 
 
607
# Must call this else $row_sql will have values from previous test.
 
608
$t->set_checksum_queries(
 
609
   $syncer->make_checksum_queries(
 
610
      src        => $src,
 
611
      dst        => $dst,
 
612
      tbl_struct => $tbl_struct,
 
613
   )
 
614
);
 
615
 
 
616
# Before fixing issue 804, the code would die during this call, saying:
 
617
# Cannot nibble table `issue_804`.`t` because MySQL chose the
 
618
# `purchases_accountId_purchaseId` index instead of the
 
619
# `purchases_accountid_purchaseid` index at TableSyncNibble.pm line 284.
 
620
$sql = $t->get_sql(database=>'issue_804', table=>'t');
 
621
is(
 
622
   $sql,
 
623
   ($sandbox_version gt '4.0' ?
 
624
   "SELECT /*issue_804.t:1/1*/ 0 AS chunk_num, COUNT(*) AS cnt, COALESCE(LOWER(CONV(BIT_XOR(CAST(CRC32(CONCAT_WS('#', `accountid`, `purchaseid`)) AS UNSIGNED)), 10, 16)), 0) AS crc FROM `issue_804`.`t` FORCE INDEX(`purchases_accountid_purchaseid`) WHERE (((`accountid` < '49') OR (`accountid` = '49' AND `purchaseid` <= '50')))" :
 
625
   "SELECT /*issue_804.t:1/1*/ 0 AS chunk_num, COUNT(*) AS cnt, COALESCE(RIGHT(MAX(\@crc := CONCAT(LPAD(\@cnt := \@cnt + 1, 16, '0'), MD5(CONCAT(\@crc, MD5(CONCAT_WS('#', `accountid`, `purchaseid`)))))), 32), 0) AS crc FROM `issue_804`.`t` FORCE INDEX(`purchases_accountid_purchaseid`) WHERE (((`accountid` < '49') OR (`accountid` = '49' AND `purchaseid` <= '50')))"
 
626
   ),
 
627
   'SQL nibble for issue_804 table'
 
628
);
 
629
 
 
630
# #############################################################################
 
631
# Done.
 
632
# #############################################################################
 
633
$sb->wipe_clean($dbh);
 
634
exit;